264 lines
8.8 KiB
PHP
264 lines
8.8 KiB
PHP
<?php
|
|
|
|
namespace Modules\ProjectManagement\Tests\Feature;
|
|
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Modules\ProjectManagement\Models\Project;
|
|
use Modules\ProjectManagement\Models\ProjectMilestone;
|
|
use Modules\ProjectManagement\Models\Task;
|
|
use Modules\ProjectManagement\Enums\ProjectStatus;
|
|
use Modules\MasterData\Models\Material;
|
|
use Modules\Labors\Models\Labor;
|
|
use Modules\Equipments\Models\Equipment;
|
|
use Spatie\Permission\Models\Role;
|
|
use Spatie\Permission\Models\Permission;
|
|
use Tests\TestCase;
|
|
|
|
class ProjectWizardTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
protected User $user;
|
|
protected User $approver;
|
|
protected Project $project;
|
|
protected Material $material;
|
|
protected Labor $labor;
|
|
protected Equipment $equipment;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
// Seed roles & permissions
|
|
$role = Role::firstOrCreate(['name' => 'Super Admin']);
|
|
Permission::firstOrCreate(['name' => 'projects.access']);
|
|
|
|
$this->user = User::factory()->create([
|
|
'status' => 'active',
|
|
'user_type' => 'admin',
|
|
]);
|
|
$this->user->assignRole($role);
|
|
|
|
$this->approver = User::factory()->create([
|
|
'status' => 'active',
|
|
'user_type' => 'admin',
|
|
'email' => 'approver@example.com',
|
|
]);
|
|
$this->approver->assignRole($role);
|
|
|
|
// Standard Material
|
|
$this->material = Material::create([
|
|
'name' => 'Wizard Cement Bag',
|
|
'sku' => 'CEM-WIZ-101',
|
|
'unit' => 'bag',
|
|
'unit_cost' => 300.00,
|
|
'category' => 'cement',
|
|
'type' => 'raw',
|
|
'status' => 'active',
|
|
]);
|
|
|
|
// Standard Labor
|
|
$this->labor = Labor::create([
|
|
'name' => 'Lead Foreman',
|
|
'category' => 'skilled',
|
|
'hourly_rate' => 150.00,
|
|
'status' => 'active',
|
|
]);
|
|
|
|
// Standard Equipment
|
|
$this->equipment = Equipment::create([
|
|
'name' => '10-Wheeler Dump Truck',
|
|
'owner_name' => 'Company Owned',
|
|
'hourly_rate' => 1500.00,
|
|
'status' => 'active',
|
|
]);
|
|
|
|
// Create Project (starts with current_wizard_step = 1)
|
|
$this->project = Project::create([
|
|
'name' => 'Wizard Test Project',
|
|
'code' => 'PRJ-WIZ-2026',
|
|
'status' => ProjectStatus::Planning,
|
|
'project_type' => 'standard',
|
|
'is_unprofitable' => false,
|
|
'contract_value' => 500000.00,
|
|
'current_wizard_step' => 1,
|
|
]);
|
|
}
|
|
|
|
public function test_can_access_wizard_allowed_steps(): void
|
|
{
|
|
// Assert can load step 1
|
|
$response = $this->actingAs($this->user)->get(route('projects.wizard', [$this->project->ulid, 'step' => 1]));
|
|
$response->assertStatus(200);
|
|
|
|
// Assert attempting to access step 3 when current_wizard_step is 1 defaults back to current step
|
|
$response = $this->actingAs($this->user)->get(route('projects.wizard', [$this->project->ulid, 'step' => 3]));
|
|
$response->assertStatus(200);
|
|
// It should render step 1 (fallback)
|
|
$this->assertEquals(1, $response->viewData('page')['props']['step']);
|
|
}
|
|
|
|
public function test_can_save_wizard_tasks_and_advance_step(): void
|
|
{
|
|
$response = $this->actingAs($this->user)->post(route('projects.wizard.tasks', $this->project->ulid), [
|
|
'milestones' => [
|
|
[
|
|
'name' => 'Substructure Phase',
|
|
'weight_percentage' => 40,
|
|
]
|
|
],
|
|
'tasks' => [
|
|
[
|
|
'name' => 'Excavation Work',
|
|
'description' => 'Excavating the main foundation area',
|
|
'start_date' => '2026-06-01',
|
|
'end_date' => '2026-06-10',
|
|
]
|
|
]
|
|
]);
|
|
|
|
$response->assertRedirect(route('projects.wizard', [$this->project->ulid, 'step' => 3]));
|
|
|
|
// Assert milestones and tasks exist in database
|
|
$this->assertDatabaseHas('project_milestones', [
|
|
'project_id' => $this->project->id,
|
|
'name' => 'Substructure Phase',
|
|
'weight_percentage' => 40,
|
|
]);
|
|
|
|
$this->assertDatabaseHas('tasks', [
|
|
'project_id' => $this->project->id,
|
|
'name' => 'Excavation Work',
|
|
'description' => 'Excavating the main foundation area',
|
|
]);
|
|
|
|
// Check project step advanced
|
|
$this->project->refresh();
|
|
$this->assertEquals(3, $this->project->current_wizard_step);
|
|
}
|
|
|
|
public function test_can_save_wizard_estimates_and_advance_step(): void
|
|
{
|
|
// First set step to 3
|
|
$this->project->update(['current_wizard_step' => 3]);
|
|
|
|
$response = $this->actingAs($this->user)->post(route('projects.wizard.estimates', $this->project->ulid), [
|
|
'estimates' => [
|
|
[
|
|
'material_ulid' => $this->material->ulid,
|
|
'estimated_qty' => 100,
|
|
'unit_cost' => 300.00,
|
|
]
|
|
]
|
|
]);
|
|
|
|
$response->assertRedirect(route('projects.wizard', [$this->project->ulid, 'step' => 4]));
|
|
|
|
$this->assertDatabaseHas('project_materials_estimates', [
|
|
'project_id' => $this->project->id,
|
|
'material_id' => $this->material->id,
|
|
'estimated_qty' => 100,
|
|
'unit_cost' => 300.00,
|
|
]);
|
|
|
|
$this->project->refresh();
|
|
$this->assertEquals(4, $this->project->current_wizard_step);
|
|
}
|
|
|
|
public function test_can_save_wizard_labor_and_advance_step(): void
|
|
{
|
|
$this->project->update(['current_wizard_step' => 4]);
|
|
|
|
// Create a task to assign labor to
|
|
$task = $this->project->tasks()->create([
|
|
'name' => 'Concrete Pouring',
|
|
'status' => 'pending',
|
|
'labor_cost' => 0,
|
|
'estimated_hours' => 20,
|
|
'actual_hours' => 0,
|
|
'completion_percentage' => 0,
|
|
'sort_order' => 1,
|
|
]);
|
|
|
|
$response = $this->actingAs($this->user)->post(route('projects.wizard.labor', $this->project->ulid), [
|
|
'labor' => [
|
|
[
|
|
'task_ulid' => $task->ulid,
|
|
'labor_ulid' => $this->labor->ulid,
|
|
'estimated_hours' => 40,
|
|
]
|
|
]
|
|
]);
|
|
|
|
$response->assertRedirect(route('projects.wizard', [$this->project->ulid, 'step' => 5]));
|
|
|
|
$this->assertDatabaseHas('task_labors', [
|
|
'task_id' => $task->id,
|
|
'labor_id' => $this->labor->id,
|
|
'estimated_hours' => 40,
|
|
]);
|
|
|
|
$this->project->refresh();
|
|
$this->assertEquals(5, $this->project->current_wizard_step);
|
|
}
|
|
|
|
public function test_can_save_wizard_equipment_and_advance_step(): void
|
|
{
|
|
$this->project->update(['current_wizard_step' => 5]);
|
|
|
|
// Create a task to assign equipment to
|
|
$task = $this->project->tasks()->create([
|
|
'name' => 'Concrete Pouring',
|
|
'status' => 'pending',
|
|
'labor_cost' => 0,
|
|
'estimated_hours' => 20,
|
|
'actual_hours' => 0,
|
|
'completion_percentage' => 0,
|
|
'sort_order' => 1,
|
|
]);
|
|
|
|
$response = $this->actingAs($this->user)->post(route('projects.wizard.equipment', $this->project->ulid), [
|
|
'equipment' => [
|
|
[
|
|
'task_ulid' => $task->ulid,
|
|
'equipment_ulid' => $this->equipment->ulid,
|
|
'estimated_hours' => 12,
|
|
]
|
|
]
|
|
]);
|
|
|
|
$response->assertRedirect(route('projects.wizard', [$this->project->ulid, 'step' => 6]));
|
|
|
|
$this->assertDatabaseHas('task_equipments', [
|
|
'task_id' => $task->id,
|
|
'equipment_id' => $this->equipment->id,
|
|
'estimated_hours' => 12,
|
|
]);
|
|
|
|
$this->project->refresh();
|
|
$this->assertEquals(6, $this->project->current_wizard_step);
|
|
}
|
|
|
|
public function test_can_submit_wizard_for_approval(): void
|
|
{
|
|
$this->project->update(['current_wizard_step' => 6]);
|
|
|
|
$response = $this->actingAs($this->user)->post(route('projects.wizard.submit', $this->project->ulid), [
|
|
'approver_ids' => [$this->approver->ulid],
|
|
'notes' => 'Setup is completed, please review and approve budget.',
|
|
]);
|
|
|
|
$response->assertRedirect(route('projects.show', $this->project->ulid));
|
|
|
|
$this->project->refresh();
|
|
$this->assertEquals(7, $this->project->current_wizard_step);
|
|
|
|
$this->assertDatabaseHas('approval_chains', [
|
|
'approvable_type' => Project::class,
|
|
'approvable_id' => $this->project->id,
|
|
'status' => 'in_review',
|
|
]);
|
|
}
|
|
}
|