diff --git a/Modules/ProjectManagement/app/Http/Controllers/ProjectController.php b/Modules/ProjectManagement/app/Http/Controllers/ProjectController.php index 1395057..a6d8af8 100644 --- a/Modules/ProjectManagement/app/Http/Controllers/ProjectController.php +++ b/Modules/ProjectManagement/app/Http/Controllers/ProjectController.php @@ -29,37 +29,55 @@ class ProjectController extends Controller public function index(Request $request) { $user = auth()->user(); - + $query = Project::query() ->where('current_wizard_step', '>=', 7) + ->whereNotIn('status', ['completed', 'closed']) ->with(['personnel:id,name']); + $historyQuery = Project::query() + ->where('current_wizard_step', '>=', 7) + ->whereIn('status', ['completed', 'closed']) + ->with(['personnel:id,name']); + $draftsQuery = Project::query() ->where('current_wizard_step', '<', 7) ->with(['personnel:id,name']); - + // Non-platform admins only see projects they are assigned to if ($user && !$user->hasRole('Super Admin')) { $query->whereHas('personnel', function ($q) use ($user) { $q->where('users.id', $user->id); }); + $historyQuery->whereHas('personnel', function ($q) use ($user) { + $q->where('users.id', $user->id); + }); $draftsQuery->whereHas('personnel', function ($q) use ($user) { $q->where('users.id', $user->id); }); } - + $projects = $query ->when($request->search, fn ($q, $s) => $q->where('name', 'like', "%{$s}%")->orWhere('code', 'like', "%{$s}%")) ->when($request->status, fn ($q, $s) => $q->where('status', $s)) ->latest() - ->paginate(15) + ->paginate(15, ['*'], 'page') ->withQueryString() ->through(fn ($p) => $p->append(['capitalization_percentage', 'is_over_budget'])); + $history = $historyQuery + ->when($request->search, fn ($q, $s) => $q->where('name', 'like', "%{$s}%")->orWhere('code', 'like', "%{$s}%")) + ->when($request->status, fn ($q, $s) => $q->where('status', $s)) + ->latest() + ->paginate(15, ['*'], 'history_page') + ->withQueryString() + ->through(fn ($p) => $p->append(['capitalization_percentage', 'is_over_budget'])); + $drafts = $draftsQuery->latest()->get(); - + return Inertia::render('ProjectManagement::Projects/Index', [ 'projects' => $projects, + 'history' => $history, 'drafts' => $drafts, 'filters' => $request->only(['search', 'status']), 'statuses' => collect(ProjectStatus::cases())->map(fn ($s) => [ @@ -296,14 +314,6 @@ class ProjectController extends Controller ->with('success', "Project \"{$project->name}\" updated."); } - public function destroy(Project $project) - { - $name = $project->name; - $project->delete(); - - return redirect()->route('projects.index') - ->with('success', "Project \"{$name}\" deleted."); - } public function transition(Request $request, Project $project) { diff --git a/Modules/ProjectManagement/database/seeders/FgenPpaServiceRoadSeeder.php b/Modules/ProjectManagement/database/seeders/FgenPpaServiceRoadSeeder.php new file mode 100644 index 0000000..d0b2bed --- /dev/null +++ b/Modules/ProjectManagement/database/seeders/FgenPpaServiceRoadSeeder.php @@ -0,0 +1,677 @@ +check() && !auth()->user()->hasRole('Super Admin')) { + throw new \Illuminate\Auth\Access\AuthorizationException('Only Super Admin is authorized to run this seeder.'); + } + + // 2. Setup Contractor + $contractor = Contractor::firstOrCreate( + ['email' => 'contractor-fgen@demo.com'], + [ + 'company_name' => 'FGEN Construction Corp', + 'contact_person' => 'Juan dela Cruz', + 'phone' => '+63 917 123 4567', + 'specialization' => 'Civil Works', + 'address' => 'Batangas, Philippines', + 'status' => 'active', + 'type' => 'main', + ] + ); + + // 3. Setup Customer User (Client) + $customer = User::where('user_type', 'customer')->first(); + if (!$customer) { + $customer = User::create([ + 'name' => 'First Gen Power Corporation', + 'email' => 'client-fgen@demo.com', + 'password' => bcrypt('password'), + 'user_type' => 'customer', + 'status' => 'active', + ]); + } + + // 4. Setup Employees and Roles + $pm = User::where('email', 'pm@fgen.com')->first(); + if (!$pm) { + $pm = User::create([ + 'name' => 'Pedro Penduko', + 'email' => 'pm@fgen.com', + 'password' => bcrypt('password'), + 'user_type' => 'employee', + 'status' => 'active', + 'contractor_id' => $contractor->id, + ]); + $pm->assignRole('Project Manager'); + } + + $engineer = User::where('email', 'engineer@fgen.com')->first(); + if (!$engineer) { + $engineer = User::create([ + 'name' => 'Engr. Maria Santos', + 'email' => 'engineer@fgen.com', + 'password' => bcrypt('password'), + 'user_type' => 'employee', + 'status' => 'active', + 'contractor_id' => $contractor->id, + ]); + $engineer->assignRole('Site Technical'); + } + + $foreman = User::where('email', 'foreman@fgen.com')->first(); + if (!$foreman) { + $foreman = User::create([ + 'name' => 'Foreman Tomas', + 'email' => 'foreman@fgen.com', + 'password' => bcrypt('password'), + 'user_type' => 'employee', + 'status' => 'active', + 'contractor_id' => $contractor->id, + ]); + $foreman->assignRole('Construction Supervisor'); + } + + // 5. Seed Materials + $materialsData = [ + 'Selected Fill Materials' => ['category' => 'Aggregates', 'unit' => 'Ton', 'unit_cost' => 18.00, 'description' => 'Selected fill materials for compaction and grading.'], + 'Water for Compaction' => ['category' => 'Aggregates', 'unit' => 'Kiloliter', 'unit_cost' => 50.00, 'description' => 'Water used for moisture conditioning and compaction.'], + 'Reinforced Concrete Pipes (RCP)' => ['category' => 'Concrete & Cement', 'unit' => 'Piece', 'unit_cost' => 150.00, 'description' => 'Reinforced Concrete Pipes (RCP) for drainage lines.'], + 'Portland Cement Type I' => ['category' => 'Concrete & Cement', 'unit' => 'Bag (40kg)', 'unit_cost' => 8.50, 'description' => 'Standard portland cement.'], + 'River Sand' => ['category' => 'Aggregates', 'unit' => 'Ton', 'unit_cost' => 20.00, 'description' => 'Washed river sand.'], + 'Crushed Stone (Gravel) 3/4"' => ['category' => 'Aggregates', 'unit' => 'Ton', 'unit_cost' => 25.00, 'description' => '3/4 inch crushed gravel.'], + 'Rebar #4 (1/2" x 20 ft)' => ['category' => 'Steel & Metal', 'unit' => 'Piece', 'unit_cost' => 6.20, 'description' => 'Standard #4 reinforcing bar.'], + 'Plywood (CDX) 15/32" (4x8 ft)' => ['category' => 'Timber & Wood', 'unit' => 'Sheet', 'unit_cost' => 22.00, 'description' => 'Plywood for concrete formworks.'], + 'Backfill Materials' => ['category' => 'Aggregates', 'unit' => 'Ton', 'unit_cost' => 15.00, 'description' => 'Backfill materials for drainage structures.'], + 'Soil Stabilizer' => ['category' => 'Concrete & Cement', 'unit' => 'Bag (40kg)', 'unit_cost' => 12.00, 'description' => 'Chemical stabilizer for subgrade improvement.'], + 'Base Gravel (Class 5)' => ['category' => 'Aggregates', 'unit' => 'Ton', 'unit_cost' => 18.00, 'description' => 'Class 5 aggregate base course.'], + 'Ready Mix Concrete (30 MPa)' => ['category' => 'Concrete & Cement', 'unit' => 'Cubic Meter', 'unit_cost' => 130.00, 'description' => '30 MPa Ready-mix concrete.'], + 'Tie Bars' => ['category' => 'Steel & Metal', 'unit' => 'Piece', 'unit_cost' => 4.50, 'description' => 'Steel tie bars for pavement joints.'], + 'Dowel Bars' => ['category' => 'Steel & Metal', 'unit' => 'Piece', 'unit_cost' => 5.50, 'description' => 'Steel dowel bars for load transfer.'], + 'Joint Sealant' => ['category' => 'Consumables', 'unit' => 'Tube', 'unit_cost' => 7.00, 'description' => 'Elastomeric joint sealant.'], + 'Curing Compound' => ['category' => 'Consumables', 'unit' => 'Pail (20L)', 'unit_cost' => 45.00, 'description' => 'Concrete curing compound.'], + 'Riprap Stones' => ['category' => 'Aggregates', 'unit' => 'Ton', 'unit_cost' => 35.00, 'description' => 'Stones for slope protection riprap.'], + 'Geotextile Fabric' => ['category' => 'Consumables', 'unit' => 'Roll', 'unit_cost' => 150.00, 'description' => 'Filter fabric for drainage and slope protection.'], + 'Traffic Signs' => ['category' => 'Safety & Finishing', 'unit' => 'Piece', 'unit_cost' => 85.00, 'description' => 'Reflective road signs.'], + 'Guardrails' => ['category' => 'Safety & Finishing', 'unit' => 'Piece', 'unit_cost' => 110.00, 'description' => 'Metal guardrail beams.'], + 'Thermoplastic Paint' => ['category' => 'Safety & Finishing', 'unit' => 'Bag (25kg)', 'unit_cost' => 40.00, 'description' => 'Thermoplastic road marking paint.'], + 'Glass Beads' => ['category' => 'Safety & Finishing', 'unit' => 'Bag (25kg)', 'unit_cost' => 25.00, 'description' => 'Reflective glass beads for thermoplastic paint.'], + 'Reflectors' => ['category' => 'Safety & Finishing', 'unit' => 'Piece', 'unit_cost' => 12.00, 'description' => 'Cat-eye road reflectors.'], + 'Delineators' => ['category' => 'Safety & Finishing', 'unit' => 'Piece', 'unit_cost' => 15.00, 'description' => 'Flexible guide posts.'], + 'Topsoil' => ['category' => 'Aggregates', 'unit' => 'Ton', 'unit_cost' => 22.00, 'description' => 'Topsoil for landscape restoration.'], + 'Landscaping Materials' => ['category' => 'Aggregates', 'unit' => 'Pack', 'unit_cost' => 50.00, 'description' => 'Grass seeds, turf, and other plantings.'], + ]; + + $materials = []; + foreach ($materialsData as $name => $meta) { + $materials[$name] = Material::where('name', $name) + ->where(fn($q) => $q->where('contractor_id', $contractor->id)->orWhereNull('contractor_id')) + ->first(); + if (!$materials[$name]) { + $baseSku = strtoupper(substr($meta['category'], 0, 3)) . '-' . rand(1000, 9999); + $materials[$name] = Material::create([ + 'name' => $name, + 'contractor_id' => $contractor->id, + 'sku' => $baseSku, + 'category' => $meta['category'], + 'unit' => $meta['unit'], + 'unit_cost' => $meta['unit_cost'], + 'description' => $meta['description'], + 'type' => 'single', + 'status' => 'active', + ]); + } + } + + // 6. Seed Equipment + $equipmentsData = [ + 'Excavator' => ['hourly_rate' => 1500.00], + 'Bulldozer' => ['hourly_rate' => 1600.00], + 'Motor Grader' => ['hourly_rate' => 1400.00], + 'Vibratory Roller' => ['hourly_rate' => 1200.00], + 'Water Truck' => ['hourly_rate' => 1000.00], + 'Dump Truck' => ['hourly_rate' => 1200.00], + 'Concrete Mixer' => ['hourly_rate' => 350.00], + 'Concrete Vibrator' => ['hourly_rate' => 150.00], + 'Plate Compactor' => ['hourly_rate' => 200.00], + 'Wheel Loader' => ['hourly_rate' => 1300.00], + 'Transit Mixer' => ['hourly_rate' => 1100.00], + 'Concrete Cutter' => ['hourly_rate' => 250.00], + 'Power Trowel' => ['hourly_rate' => 200.00], + 'Crane' => ['hourly_rate' => 2500.00], + 'Road Marking Machine' => ['hourly_rate' => 300.00], + 'Air Compressor' => ['hourly_rate' => 250.00], + 'Pickup Truck' => ['hourly_rate' => 400.00], + ]; + + $equipments = []; + foreach ($equipmentsData as $name => $meta) { + $equipments[$name] = Equipment::firstOrCreate( + ['name' => $name], + [ + 'owner_name' => 'Company Owned', + 'hourly_rate' => $meta['hourly_rate'], + 'status' => 'active', + ] + ); + } + + // 7. Seed Project + $project = Project::firstOrCreate( + ['name' => 'Construction of FGEN-PPA Service Road'], + [ + 'description' => 'Construction of the FGEN-PPA Service Road including site clearing, drainage, subgrade prep, base course, concrete pavement, shoulder protection, safety markings, and cleanup.', + 'customer_id' => $customer->id ?? null, + 'client_name' => $customer ? $customer->name : 'First Gen Power Corporation', + 'contractor_id' => $contractor->id, + 'location' => 'Batangas, Philippines', + 'status' => ProjectStatus::Planning, + 'contract_value' => 15500000.00, + 'total_estimated_value' => 15500000.00, + 'contract_duration' => 8, + 'start_date' => '2026-07-01', + 'target_end_date' => '2027-02-28', + 'completion_percentage' => 0.00, + 'total_capitalization' => 0.00, + ] + ); + + // Sync contractor relation safely + $contractor->projects()->syncWithoutDetaching([ + $project->id => ['role' => 'Primary Contractor', 'contract_amount' => $project->contract_value] + ]); + + // Assign PM to Project + $project->personnel()->syncWithoutDetaching([ + $pm->id => ['role' => 'pm'], + $engineer->id => ['role' => 'engineer'], + $foreman->id => ['role' => 'laborer'], + ]); + + // Transition Project Status to In Progress + if ($project->status !== ProjectStatus::InProgress) { + if ($project->status === ProjectStatus::UnderBidding) { + $project->transitionTo(ProjectStatus::Planning); + } + if ($project->status === ProjectStatus::Planning) { + $project->transitionTo(ProjectStatus::InProgress); + } + } + + // 8. Seed Milestones and Tasks + $milestonesConfig = [ + [ + 'name' => 'Milestone 1: Site Clearing and Earthworks', + 'description' => 'Site clearing, grubbing, unsuitable soil removal, and embankment compaction.', + 'weight' => 10.0, + 'tasks' => [ + [ + 'name' => 'Clearing and grubbing using excavators, bulldozers, chainsaws, and dump trucks', + 'description' => 'Remove vegetation, roots, and topsoil to clear the road path.', + 'est_hours' => 80, + 'labor_cost' => 50000.00, + 'equipment_cost' => 120000.00, + 'materials' => [], + 'equipments' => ['Excavator' => 80, 'Bulldozer' => 40, 'Dump Truck' => 80], + ], + [ + 'name' => 'Removal and hauling of unsuitable materials using excavators and dump trucks', + 'description' => 'Excavate unsuitable materials and transport them to the designated disposal site.', + 'est_hours' => 60, + 'labor_cost' => 40000.00, + 'equipment_cost' => 90000.00, + 'materials' => [], + 'equipments' => ['Excavator' => 60, 'Dump Truck' => 120], + ], + [ + 'name' => 'Excavation and embankment works using excavators, bulldozers, graders, and dump trucks', + 'description' => 'Perform roadbed cutting and filling to meet design elevation.', + 'est_hours' => 120, + 'labor_cost' => 80000.00, + 'equipment_cost' => 200000.00, + 'materials' => [], + 'equipments' => ['Excavator' => 120, 'Bulldozer' => 60, 'Motor Grader' => 60, 'Dump Truck' => 120], + ], + [ + 'name' => 'Filling and spreading of selected fill materials using graders and bulldozers', + 'description' => 'Spread selected borrow materials on the roadbed in layers.', + 'est_hours' => 80, + 'labor_cost' => 50000.00, + 'equipment_cost' => 130000.00, + 'materials' => ['Selected Fill Materials' => 500.0], + 'equipments' => ['Motor Grader' => 80, 'Bulldozer' => 40], + ], + [ + 'name' => 'Compaction of embankment and subgrade using vibratory rollers and water trucks', + 'description' => 'Apply water and compact the fill layers to the required density.', + 'est_hours' => 60, + 'labor_cost' => 30000.00, + 'equipment_cost' => 100000.00, + 'materials' => ['Water for Compaction' => 200.0], + 'equipments' => ['Vibratory Roller' => 60, 'Water Truck' => 60], + ], + ] + ], + [ + 'name' => 'Milestone 2: Drainage Construction', + 'description' => 'Drainage excavation, RCP installation, catch basins, and backfilling.', + 'weight' => 15.0, + 'tasks' => [ + [ + 'name' => 'Excavation for drainage lines and structures', + 'description' => 'Excavate trenches for stormwater drainage pipes and structures.', + 'est_hours' => 50, + 'labor_cost' => 30000.00, + 'equipment_cost' => 75000.00, + 'materials' => [], + 'equipments' => ['Excavator' => 50], + ], + [ + 'name' => 'Installation of reinforced concrete pipes (RCP)', + 'description' => 'Lay and align reinforced concrete pipes in the excavated trenches.', + 'est_hours' => 80, + 'labor_cost' => 60000.00, + 'equipment_cost' => 80000.00, + 'materials' => ['Reinforced Concrete Pipes (RCP)' => 150.0], + 'equipments' => ['Excavator' => 40, 'Dump Truck' => 40], + ], + [ + 'name' => 'Placement of bedding materials', + 'description' => 'Place sand or gravel bedding at the bottom of the trenches before laying pipes.', + 'est_hours' => 40, + 'labor_cost' => 25000.00, + 'equipment_cost' => 15000.00, + 'materials' => ['Crushed Stone (Gravel) 3/4"' => 80.0, 'River Sand' => 50.0], + 'equipments' => [], + ], + [ + 'name' => 'Construction of catch basins and manholes', + 'description' => 'Form and pour concrete for catch basins and junction manholes.', + 'est_hours' => 100, + 'labor_cost' => 90000.00, + 'equipment_cost' => 30000.00, + 'materials' => ['Portland Cement Type I' => 120.0, 'River Sand' => 15.0, 'Crushed Stone (Gravel) 3/4"' => 25.0, 'Rebar #4 (1/2" x 20 ft)' => 100.0, 'Plywood (CDX) 15/32" (4x8 ft)' => 20.0], + 'equipments' => ['Concrete Mixer' => 80, 'Concrete Vibrator' => 80], + ], + [ + 'name' => 'Concrete pouring for drainage structures', + 'description' => 'Pour concrete for headwalls, wingwalls, and drainage aprons.', + 'est_hours' => 60, + 'labor_cost' => 50000.00, + 'equipment_cost' => 20000.00, + 'materials' => ['Portland Cement Type I' => 80.0, 'River Sand' => 10.0, 'Crushed Stone (Gravel) 3/4"' => 18.0, 'Rebar #4 (1/2" x 20 ft)' => 50.0, 'Plywood (CDX) 15/32" (4x8 ft)' => 15.0], + 'equipments' => ['Concrete Mixer' => 40, 'Concrete Vibrator' => 40], + ], + [ + 'name' => 'Backfilling and compaction around drainage facilities', + 'description' => 'Backfill around catch basins and pipes, then compact using plate compactors.', + 'est_hours' => 40, + 'labor_cost' => 20000.00, + 'equipment_cost' => 25000.00, + 'materials' => ['Backfill Materials' => 120.0], + 'equipments' => ['Dump Truck' => 40, 'Plate Compactor' => 40], + ], + ] + ], + [ + 'name' => 'Milestone 3: Subgrade Preparation', + 'description' => 'Final roadbed grading, moisture conditioning, and stabilization.', + 'weight' => 10.0, + 'tasks' => [ + [ + 'name' => 'Final grading and shaping of roadbed', + 'description' => 'Grade the road subgrade to the design crown and cross-slope.', + 'est_hours' => 50, + 'labor_cost' => 30000.00, + 'equipment_cost' => 70000.00, + 'materials' => [], + 'equipments' => ['Motor Grader' => 50], + ], + [ + 'name' => 'Moisture conditioning', + 'description' => 'Sprinkle water or aerate subgrade soil to achieve optimum moisture content.', + 'est_hours' => 30, + 'labor_cost' => 15000.00, + 'equipment_cost' => 30000.00, + 'materials' => ['Water for Compaction' => 100.0], + 'equipments' => ['Water Truck' => 30], + ], + [ + 'name' => 'Compaction of subgrade layer', + 'description' => 'Roll the subgrade to achieve at least 95% maximum dry density.', + 'est_hours' => 40, + 'labor_cost' => 20000.00, + 'equipment_cost' => 50000.00, + 'materials' => [], + 'equipments' => ['Vibratory Roller' => 40, 'Plate Compactor' => 20], + ], + [ + 'name' => 'Subgrade stabilization (if required)', + 'description' => 'Mix in soil stabilizers to improve bearing capacity in soft areas.', + 'est_hours' => 60, + 'labor_cost' => 45000.00, + 'equipment_cost' => 84000.00, + 'materials' => ['Soil Stabilizer' => 150.0, 'Base Gravel (Class 5)' => 100.0], + 'equipments' => ['Motor Grader' => 40, 'Vibratory Roller' => 30], + ], + ] + ], + [ + 'name' => 'Milestone 4: Aggregate Base Course Installation', + 'description' => 'Delivery, spreading, leveling, and compacting aggregate base course.', + 'weight' => 15.0, + 'tasks' => [ + [ + 'name' => 'Delivery and stockpiling of aggregate base materials', + 'description' => 'Haul and dump aggregate base material at designated intervals on the road.', + 'est_hours' => 80, + 'labor_cost' => 40000.00, + 'equipment_cost' => 96000.00, + 'materials' => ['Base Gravel (Class 5)' => 1200.0], + 'equipments' => ['Dump Truck' => 80], + ], + [ + 'name' => 'Spreading and leveling of aggregate base course', + 'description' => 'Spread base gravel to a uniform thickness using graders.', + 'est_hours' => 60, + 'labor_cost' => 35000.00, + 'equipment_cost' => 110000.00, + 'materials' => [], + 'equipments' => ['Motor Grader' => 60, 'Wheel Loader' => 30], + ], + [ + 'name' => 'Water application and compaction', + 'description' => 'Condition aggregate base course with water and compact thoroughly.', + 'est_hours' => 50, + 'labor_cost' => 25000.00, + 'equipment_cost' => 85000.00, + 'materials' => ['Water for Compaction' => 300.0], + 'equipments' => ['Vibratory Roller' => 50, 'Water Truck' => 50], + ], + [ + 'name' => 'Layer thickness adjustments', + 'description' => 'Correct grade deviations and ensure thickness meets target specifications.', + 'est_hours' => 30, + 'labor_cost' => 18000.00, + 'equipment_cost' => 42000.00, + 'materials' => [], + 'equipments' => ['Motor Grader' => 30], + ], + ] + ], + [ + 'name' => 'Milestone 5: Concrete Pavement Construction', + 'description' => 'Formworks, rebar/dowel setup, concrete pouring, finishing, curing, and jointing.', + 'weight' => 30.0, + 'tasks' => [ + [ + 'name' => 'Installation of formworks', + 'description' => 'Set up and align steel or timber forms to define the concrete road edge.', + 'est_hours' => 80, + 'labor_cost' => 60000.00, + 'equipment_cost' => 15000.00, + 'materials' => ['Plywood (CDX) 15/32" (4x8 ft)' => 100.0], + 'equipments' => ['Pickup Truck' => 40], + ], + [ + 'name' => 'Placement of reinforcing steel (if specified)', + 'description' => 'Assemble and lay down tie bars and dowel bars at joint assemblies.', + 'est_hours' => 80, + 'labor_cost' => 70000.00, + 'equipment_cost' => 20000.00, + 'materials' => ['Rebar #4 (1/2" x 20 ft)' => 200.0, 'Tie Bars' => 250.0, 'Dowel Bars' => 250.0], + 'equipments' => ['Pickup Truck' => 20], + ], + [ + 'name' => 'Concrete batching and delivery', + 'description' => 'Batch concrete and deliver to site using transit mixers.', + 'est_hours' => 120, + 'labor_cost' => 80000.00, + 'equipment_cost' => 160000.00, + 'materials' => ['Ready Mix Concrete (30 MPa)' => 600.0], + 'equipments' => ['Transit Mixer' => 120, 'Concrete Mixer' => 40], + ], + [ + 'name' => 'Concrete pouring and finishing', + 'description' => 'Pour concrete into forms, consolidate with vibrators, and strike off.', + 'est_hours' => 100, + 'labor_cost' => 110000.00, + 'equipment_cost' => 40000.00, + 'materials' => [], + 'equipments' => ['Concrete Vibrator' => 100, 'Power Trowel' => 50], + ], + [ + 'name' => 'Concrete curing', + 'description' => 'Apply curing compound or pond water to ensure proper hydration.', + 'est_hours' => 40, + 'labor_cost' => 20000.00, + 'equipment_cost' => 40000.00, + 'materials' => ['Curing Compound' => 30.0, 'Water for Compaction' => 150.0], + 'equipments' => ['Water Truck' => 40], + ], + [ + 'name' => 'Joint cutting and sealing', + 'description' => 'Saw contraction joints in hardened concrete and apply joint sealant.', + 'est_hours' => 50, + 'labor_cost' => 35000.00, + 'equipment_cost' => 20000.00, + 'materials' => ['Joint Sealant' => 80.0], + 'equipments' => ['Concrete Cutter' => 50], + ], + ] + ], + [ + 'name' => 'Milestone 6: Shoulder and Slope Protection Works', + 'description' => 'Shoulder fill placement, riprap construction, and compaction.', + 'weight' => 10.0, + 'tasks' => [ + [ + 'name' => 'Placement of shoulder materials', + 'description' => 'Lay aggregate shoulder material along the edges of the concrete pavement.', + 'est_hours' => 60, + 'labor_cost' => 35000.00, + 'equipment_cost' => 75000.00, + 'materials' => ['Selected Fill Materials' => 200.0, 'Base Gravel (Class 5)' => 150.0], + 'equipments' => ['Excavator' => 30, 'Dump Truck' => 30, 'Wheel Loader' => 30], + ], + [ + 'name' => 'Construction of riprap or slope protection', + 'description' => 'Place riprap stones and geotextile fabric on slopes to prevent erosion.', + 'est_hours' => 100, + 'labor_cost' => 85000.00, + 'equipment_cost' => 90000.00, + 'materials' => ['Riprap Stones' => 400.0, 'Geotextile Fabric' => 10.0], + 'equipments' => ['Excavator' => 60, 'Dump Truck' => 40], + ], + [ + 'name' => 'Compaction of shoulder areas', + 'description' => 'Compact the shoulder aggregate to align with road surface.', + 'est_hours' => 40, + 'labor_cost' => 20000.00, + 'equipment_cost' => 48000.00, + 'materials' => [], + 'equipments' => ['Vibratory Roller' => 40], + ], + ] + ], + [ + 'name' => 'Milestone 7: Road Safety and Finishing Works', + 'description' => 'Road signs, guardrails, pavement markings, and reflectors.', + 'weight' => 5.0, + 'tasks' => [ + [ + 'name' => 'Installation of road signs', + 'description' => 'Erect regulatory, warning, and guide signs along the road alignment.', + 'est_hours' => 30, + 'labor_cost' => 20000.00, + 'equipment_cost' => 15000.00, + 'materials' => ['Traffic Signs' => 15.0], + 'equipments' => ['Pickup Truck' => 30], + ], + [ + 'name' => 'Installation of guardrails', + 'description' => 'Install steel guardrails in high embankment or curve zones.', + 'est_hours' => 50, + 'labor_cost' => 35000.00, + 'equipment_cost' => 25000.00, + 'materials' => ['Guardrails' => 50.0], + 'equipments' => ['Pickup Truck' => 40], + ], + [ + 'name' => 'Application of pavement markings', + 'description' => 'Apply thermoplastic markings for centerlines and edge lines.', + 'est_hours' => 40, + 'labor_cost' => 30000.00, + 'equipment_cost' => 20000.00, + 'materials' => ['Thermoplastic Paint' => 25.0, 'Glass Beads' => 10.0], + 'equipments' => ['Road Marking Machine' => 40, 'Air Compressor' => 40], + ], + [ + 'name' => 'Installation of reflectors and delineators', + 'description' => 'Fix cat-eye reflectors and flexible delineator posts on the road surface.', + 'est_hours' => 30, + 'labor_cost' => 15000.00, + 'equipment_cost' => 5000.00, + 'materials' => ['Reflectors' => 120.0, 'Delineators' => 80.0], + 'equipments' => [], + ], + ] + ], + [ + 'name' => 'Milestone 8: Final Restoration and Site Cleanup', + 'description' => 'Removal of excess materials, site grading, restoration, and debris disposal.', + 'weight' => 5.0, + 'tasks' => [ + [ + 'name' => 'Removal of excess materials', + 'description' => 'Load and transport leftover gravel, wood, and other items from the site.', + 'est_hours' => 40, + 'labor_cost' => 20000.00, + 'equipment_cost' => 60000.00, + 'materials' => [], + 'equipments' => ['Excavator' => 20, 'Dump Truck' => 40, 'Wheel Loader' => 20], + ], + [ + 'name' => 'Site grading and restoration', + 'description' => 'Perform cosmetic grading and layout topsoil/vegetation along the roadside.', + 'est_hours' => 50, + 'labor_cost' => 30000.00, + 'equipment_cost' => 70000.00, + 'materials' => ['Topsoil' => 100.0, 'Landscaping Materials' => 20.0], + 'equipments' => ['Motor Grader' => 30, 'Wheel Loader' => 20], + ], + [ + 'name' => 'Disposal of construction debris', + 'description' => 'Haul construction debris and waste materials to authorized landfills.', + 'est_hours' => 30, + 'labor_cost' => 15000.00, + 'equipment_cost' => 36000.00, + 'materials' => [], + 'equipments' => ['Dump Truck' => 30], + ], + ] + ], + ]; + + $startDate = Carbon::parse($project->start_date); + + foreach ($milestonesConfig as $idx => $mConfig) { + $plannedDate = $startDate->copy()->addMonths($idx)->endOfMonth(); + + $milestone = ProjectMilestone::firstOrCreate( + [ + 'project_id' => $project->id, + 'name' => $mConfig['name'] + ], + [ + 'description' => $mConfig['description'], + 'planned_date' => $plannedDate->format('Y-m-d'), + 'sort_order' => $idx + 1, + 'weight_percentage' => $mConfig['weight'], + 'is_default' => false, + ] + ); + + foreach ($mConfig['tasks'] as $tIdx => $tData) { + $task = Task::firstOrCreate( + [ + 'project_id' => $project->id, + 'milestone_id' => $milestone->id, + 'name' => $tData['name'] + ], + [ + 'description' => $tData['description'], + 'status' => TaskStatus::Pending, + 'sort_order' => $tIdx + 1, + 'labor_cost' => $tData['labor_cost'], + 'equipment_cost' => $tData['equipment_cost'], + 'estimated_hours' => $tData['est_hours'], + 'actual_hours' => 0.00, + 'start_date' => $startDate->copy()->addMonths($idx)->startOfMonth()->format('Y-m-d'), + 'end_date' => $plannedDate->format('Y-m-d'), + 'completion_percentage' => 0.00, + ] + ); + + // Assign users to the task + $task->users()->syncWithoutDetaching([$engineer->id, $foreman->id]); + + // Seed task materials + foreach ($tData['materials'] as $mName => $qty) { + $material = $materials[$mName]; + TaskMaterial::firstOrCreate( + [ + 'task_id' => $task->id, + 'material_id' => $material->id + ], + [ + 'planned_qty' => $qty, + 'actual_qty' => 0.00, + 'unit_cost' => $material->unit_cost, + ] + ); + } + + // Seed task equipment + foreach ($tData['equipments'] as $eqName => $estHours) { + $eq = $equipments[$eqName]; + TaskEquipment::firstOrCreate( + [ + 'task_id' => $task->id, + 'equipment_id' => $eq->id + ], + [ + 'estimated_hours' => $estHours, + 'actual_hours' => 0.00, + ] + ); + } + } + } + + // 9. Recalculate Project Capitalization + $project->recalculateCapitalization(); + } +} diff --git a/Modules/ProjectManagement/database/seeders/ProjectManagementDatabaseSeeder.php b/Modules/ProjectManagement/database/seeders/ProjectManagementDatabaseSeeder.php index 3aadc15..3758426 100644 --- a/Modules/ProjectManagement/database/seeders/ProjectManagementDatabaseSeeder.php +++ b/Modules/ProjectManagement/database/seeders/ProjectManagementDatabaseSeeder.php @@ -27,5 +27,9 @@ class ProjectManagementDatabaseSeeder extends Seeder if ($admin) { $project->personnel()->attach($admin->id, ['role' => 'pm']); } + + $this->call([ + FgenPpaServiceRoadSeeder::class, + ]); } } diff --git a/Modules/ProjectManagement/resources/js/Pages/Projects/Index.tsx b/Modules/ProjectManagement/resources/js/Pages/Projects/Index.tsx index f683a85..a02cd7e 100644 --- a/Modules/ProjectManagement/resources/js/Pages/Projects/Index.tsx +++ b/Modules/ProjectManagement/resources/js/Pages/Projects/Index.tsx @@ -12,7 +12,7 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/Components/ui/table'; import { PaginatedData, PageProps } from '@/types'; -import { Plus, Eye, Pencil, Trash2, FolderKanban } from 'lucide-react'; +import { Plus, Eye, Pencil, FolderKanban } from 'lucide-react'; import { FormEvent, useMemo, useState } from 'react'; interface Project { @@ -41,6 +41,7 @@ interface StatusOption { interface Props extends PageProps { projects: PaginatedData; + history: PaginatedData; drafts: Project[]; filters: { search?: string; status?: string }; statuses: StatusOption[]; @@ -66,12 +67,18 @@ const formatCurrency = (val: string) => { return new Intl.NumberFormat('en-PH', { style: 'currency', currency: 'PHP' }).format(Number(val)); }; -export default function Index({ projects, drafts = [], filters, statuses }: Props) { +export default function Index({ projects, history, drafts = [], filters, statuses }: Props) { const { flash } = usePage().props; const { can } = usePermission(); const [search, setSearch] = useState(filters.search || ''); const [statusFilter, setStatusFilter] = useState(filters.status || 'all'); - const [activeTab, setActiveTab] = useState<'active' | 'drafts'>('active'); + const [activeTab, setActiveTab] = useState<'active' | 'drafts' | 'history'>(() => { + const params = new URLSearchParams(window.location.search); + if (params.has('history_page')) { + return 'history'; + } + return 'active'; + }); // Items array for Select label lookup const statusFilterItems = useMemo(() => [{ value: 'all', label: 'All Statuses' }, ...statuses.map(s => ({ value: s.value, label: s.label }))], [statuses]); @@ -84,12 +91,6 @@ export default function Index({ projects, drafts = [], filters, statuses }: Prop }, { preserveState: true, replace: true }); }; - const handleDelete = (project: Project) => { - if (confirm(`Delete project "${project.name}"?`)) { - router.delete(route('projects.destroy', project.ulid)); - } - }; - return ( Active Projects ({projects.total}) + - {activeTab === 'active' ? ( + {activeTab === 'active' && ( <> @@ -242,11 +253,6 @@ export default function Index({ projects, drafts = [], filters, statuses }: Prop )} - {can('delete', 'projects') && ( - - )} @@ -275,7 +281,119 @@ export default function Index({ projects, drafts = [], filters, statuses }: Prop )} - ) : ( + )} + + {activeTab === 'history' && ( + <> +
+ + + Code + Name + Client + Status + Contract Value + Capitalization + Progress + Actions + + + + {history.data.length === 0 ? ( + + + No history projects found. + + + ) : ( + history.data.map((project) => ( + + {project.code} + {project.name} + {project.client_name || '-'} + + + {statusLabel(project.status)} + + + + {formatCurrency(project.contract_value)} + + +
+
+
= 80 ? 'bg-amber-500' : 'bg-emerald-500' + }`} + style={{ width: `${Math.min(project.capitalization_percentage, 100)}%` }} + /> +
+ = 80 ? 'text-amber-600' : 'text-gray-600' + }`}> + {project.capitalization_percentage.toFixed(1)}% + +
+ + +
+
+
+
+ + {Number(project.completion_percentage).toFixed(0)}% + +
+ + +
+ + + + {can('edit', 'projects') && ( + + + + )} +
+
+ + )) + )} + +
+ + {history.last_page > 1 && ( +
+

+ Showing {history.from} to {history.to} of {history.total} +

+
+ {history.prev_page_url && ( + + + + )} + {history.next_page_url && ( + + + + )} +
+
+ )} + + )} + + {activeTab === 'drafts' && ( @@ -315,11 +433,6 @@ export default function Index({ projects, drafts = [], filters, statuses }: Prop Resume Setup - {can('delete', 'projects') && ( - - )} diff --git a/Modules/ProjectManagement/routes/web.php b/Modules/ProjectManagement/routes/web.php index e4b4221..ca92297 100644 --- a/Modules/ProjectManagement/routes/web.php +++ b/Modules/ProjectManagement/routes/web.php @@ -4,7 +4,7 @@ use Illuminate\Support\Facades\Route; use Modules\ProjectManagement\Http\Controllers\ProjectController; Route::middleware(['web', 'auth', 'permission:projects.access'])->group(function () { - Route::resource('projects', ProjectController::class); + Route::resource('projects', ProjectController::class)->except(['destroy']); Route::patch('projects/{project}/transition', [ProjectController::class, 'transition'])->name('projects.transition'); // Project Wizard Routes diff --git a/docs/PLAN.md b/docs/PLAN.md index 41eeae1..95bdab2 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -1,46 +1,72 @@ -# Plan: Group Labors and Equipment & Tools under Master Data Module +# Plan: Seeding FGEN-PPA Service Road Project Data -This plan details the reorganization of the sidebar navigation to group the **Labors** and **Equipment & Tools** modules under a unified **Master Data** module. +This document outlines the design and implementation plan for seeding the "Construction of FGEN-PPA Service Road" project data in the system. -## User Review Required - -We need your feedback on the preferred UI/UX pattern for grouping: - -> [!IMPORTANT] -> **Option 1: New Top-Level Group (Recommended)** -> - A new section header **Master Data** will be added to the sidebar, positioned below **Management** or **Overview**. -> - **Labors** and **Equipment & Tools** will be moved directly under this new group as distinct top-level items. -> -> **Option 2: Collapsible Menu Item** -> - A new sidebar menu item named **Master Data** will be added under the **Management** section. -> - Clicking it will expand a submenu showing **Labors** and **Equipment & Tools**. +## 1. Objectives & Scope +Create a Laravel Database Seeder class named `FgenPpaServiceRoadSeeder` located under `Modules/ProjectManagement/database/seeders/`. +This seeder will: +- Validate that it can only be executed by a **Super Admin** (or in CLI mode). +- Create the project "Construction of FGEN-PPA Service Road". +- Build 8 Milestones with their planned dates, weight percentages, and sort orders. +- Seed Tasks for each Milestone. +- Seed, map, and associate relevant Materials to tasks (using `task_materials` pivot table). +- Seed, map, and associate relevant Equipments to tasks (using `task_equipments` table). +- Assign personnel (Project Manager, Site Engineer, Foreman) to the Project and specific Tasks (using `task_user` table). --- -## Proposed Changes +## 2. Model & Database Mapping -### Component: Navigation Configuration (`resources/js/lib`) +### A. Project details +- **Name**: Construction of FGEN-PPA Service Road +- **Description**: Construction of the FGEN-PPA Service Road including site clearing, drainage, subgrade prep, base course, concrete pavement, shoulder protection, safety markings, and cleanup. +- **Location**: Batangas, Philippines +- **Status**: `planning` +- **Contract Value**: 15,500,000.00 +- **Duration**: 8 Months +- **Start Date**: 2026-07-01 +- **Target End Date**: 2027-02-28 + +### B. Milestones and Tasks +The 8 milestones will be seeded with: +- **Milestone 1: Site Clearing and Earthworks** (Weight: 10%) +- **Milestone 2: Drainage Construction** (Weight: 15%) +- **Milestone 3: Subgrade Preparation** (Weight: 10%) +- **Milestone 4: Aggregate Base Course Installation** (Weight: 15%) +- **Milestone 5: Concrete Pavement Construction** (Weight: 30%) +- **Milestone 6: Shoulder and Slope Protection Works** (Weight: 10%) +- **Milestone 7: Road Safety and Finishing Works** (Weight: 5%) +- **Milestone 8: Final Restoration and Site Cleanup** (Weight: 5%) + +### C. Materials Mapping (via `task_materials` table) +We will firstOrCreate materials: +- Selected Fill Materials, Water for Compaction, Reinforced Concrete Pipes (RCP), Portland Cement Type I, River Sand, Crushed Stone (Gravel) 3/4", Rebar #4 (1/2" x 20 ft), Plywood (CDX) 15/32" (4x8 ft), Backfill Materials, Soil Stabilizer, Base Gravel (Class 5), Ready Mix Concrete (30 MPa), Tie Bars, Dowel Bars, Joint Sealant, Curing Compound, Riprap Stones, Geotextile Fabric, Traffic Signs, Guardrails, Thermoplastic Paint, Glass Beads, Reflectors, Delineators, Topsoil, Landscaping Materials. + +### D. Equipment Mapping (via `task_equipments` table) +We will firstOrCreate equipment: +- Excavator, Bulldozer, Motor Grader, Vibratory Roller, Water Truck, Dump Truck, Concrete Mixer, Concrete Vibrator, Plate Compactor, Wheel Loader, Transit Mixer, Concrete Cutter, Power Trowel, Crane, Road Marking Machine, Air Compressor, Pickup Truck. --- -#### [MODIFY] [nav-config.ts](file:///c:/laragon/www/gsb-cons/resources/js/lib/nav-config.ts) -- Remove **Labors** and **Equipment & Tools** items from the `Management` group. -- Create a new `NavGroup` or nested menu item structure for **Master Data** containing both items. -- Ensure the respective icons (`Hammer` and `Truck`), permissions, routes, and layout match existing patterns. +## 3. Security & Access Control +To ensure only a Super Admin can run it: +- Within the `run()` method of the Seeder, if a web request or session is active (`auth()->check()`), we will verify the user has the `Super Admin` role. +- If not, we will abort with an unauthorized exception. +- Running via `artisan db:seed` from command line (where `auth()->check()` is false) will bypass the session check. --- -## Verification Plan +## 4. User Assignments +We will fetch or create the following users: +1. **Project Manager**: `pm@fgen.com` (assigned to project as `pm` and associated tasks). +2. **Site Technical (Site Engineer)**: `engineer@fgen.com` (assigned to tasks as technical member). +3. **Construction Supervisor (Foreman)**: `foreman@fgen.com` (assigned to tasks as field supervisor). -### Automated Tests -- Since client-side routing and sidebar visibility are driven by `nav-config.ts` and React components, we will verify compiling the frontend assets using: - ```powershell - npm run build - ``` +They will be attached to the tasks via the `task_user` relation. -### Manual Verification -1. Access the web interface. -2. Confirm the new **Master Data** section/item is rendered correctly in the sidebar. -3. Verify that clicking **Labors** redirects to the Labors module. -4. Verify that clicking **Equipment & Tools** redirects to the Equipments module. -5. Verify that permissions (e.g., hiding modules if the user lacks access) still work as expected. +--- + +## 5. Verification Plan +- Register `FgenPpaServiceRoadSeeder` inside `Modules/ProjectManagement/database/seeders/ProjectManagementDatabaseSeeder.php` or invoke it directly. +- Execute the seeder command: `php artisan db:seed --class=Modules\ProjectManagement\Database\Seeders\FgenPpaServiceRoadSeeder`. +- Verify database records for the project, milestones, tasks, task materials, task equipments, and user assignments are created correctly.