Additional Changes
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\ProjectManagement\Services;
|
||||
|
||||
use Modules\ProjectManagement\Models\Project;
|
||||
use Modules\ProjectManagement\Enums\ProjectStatus;
|
||||
|
||||
class ProjectWorkflowService
|
||||
{
|
||||
/**
|
||||
* Determine the current structural state of the project.
|
||||
* Enforces the sequence: Planning -> Estimated -> Scheduled -> Procuring -> InProgress
|
||||
*/
|
||||
public function getProjectWorkflowState(Project $project): string
|
||||
{
|
||||
$hasEstimates = $project->materialsEstimates()->exists();
|
||||
$hasTasks = $project->tasks()->exists();
|
||||
|
||||
if ($project->status === ProjectStatus::Completed || $project->status === ProjectStatus::Closed) {
|
||||
return $project->status->value;
|
||||
}
|
||||
|
||||
if ($project->status === ProjectStatus::InProgress) {
|
||||
return 'in_progress';
|
||||
}
|
||||
|
||||
if (!$hasEstimates) {
|
||||
return 'planning'; // Step 1: Project Creation / Planning
|
||||
}
|
||||
|
||||
if (!$hasTasks) {
|
||||
return 'estimated'; // Step 2: Materials Estimated, Tasks not yet scheduled
|
||||
}
|
||||
|
||||
// Has estimates and tasks, check if procurement (MR/PO) has occurred
|
||||
return 'scheduled'; // Step 3: Tasks Scheduled & Resources assigned
|
||||
}
|
||||
|
||||
/**
|
||||
* Guard: check if Material Requisitions can be created for this project.
|
||||
* Enforced strictly: Must have estimates and tasks first.
|
||||
*/
|
||||
public function canCreateRequisition(Project $project): bool
|
||||
{
|
||||
$state = $this->getProjectWorkflowState($project);
|
||||
|
||||
// Allowed if project is scheduled, procuring, or in progress
|
||||
return in_array($state, ['scheduled', 'procuring', 'in_progress']) ||
|
||||
$project->status === ProjectStatus::InProgress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guard: check if Purchase Orders can be created.
|
||||
* Enforced strictly: Requires an approved material requisition for the project.
|
||||
*/
|
||||
public function canCreatePurchaseOrder(Project $project): bool
|
||||
{
|
||||
// Requisition must exist and be approved
|
||||
$hasApprovedMr = \Modules\MaterialLogistics\Models\MaterialRequisition::whereHas('items.material', function ($q) use ($project) {
|
||||
// Check if the material is associated with this project's estimates
|
||||
$q->whereIn('materials.id', $project->materialsEstimates()->pluck('material_id'));
|
||||
})
|
||||
->where('status', 'approved')
|
||||
->exists();
|
||||
|
||||
return $hasApprovedMr || $project->status === ProjectStatus::InProgress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the budget analysis for a project (Estimated vs. Requisitioned vs. PO value)
|
||||
*/
|
||||
public function getBudgetAnalysis(Project $project): array
|
||||
{
|
||||
$estimatedValue = $project->materialsEstimates->sum(fn ($est) => (float) $est->estimated_qty * (float) $est->unit_cost);
|
||||
|
||||
// Sum labor & equipment estimates
|
||||
$laborEstimates = 0.00;
|
||||
$equipmentEstimates = 0.00;
|
||||
foreach ($project->tasks as $task) {
|
||||
$laborEstimates += $task->estimated_labor_cost;
|
||||
$equipmentEstimates += $task->estimated_equipment_cost;
|
||||
}
|
||||
|
||||
$totalEstimated = $estimatedValue + $laborEstimates + $equipmentEstimates;
|
||||
|
||||
// Sum actual PO costs for warehouses belonging to this project
|
||||
$poTotal = 0.00;
|
||||
$warehouseIds = \Modules\MaterialLogistics\Models\Warehouse::where('project_id', $project->id)->pluck('id');
|
||||
if ($warehouseIds->isNotEmpty()) {
|
||||
$poTotal = (float) \Modules\MaterialLogistics\Models\PurchaseOrder::whereIn('target_warehouse_id', $warehouseIds)
|
||||
->whereIn('status', ['approved', 'delivered'])
|
||||
->get()
|
||||
->sum('total_cost');
|
||||
}
|
||||
|
||||
return [
|
||||
'contract_value' => (float) $project->contract_value,
|
||||
'materials_estimated' => $estimatedValue,
|
||||
'labor_estimated' => $laborEstimates,
|
||||
'equipment_estimated' => $equipmentEstimates,
|
||||
'total_estimated' => $totalEstimated,
|
||||
'po_total' => $poTotal,
|
||||
'remaining_estimate' => max(0.00, $totalEstimated - $poTotal),
|
||||
'is_over_budget' => $poTotal > $totalEstimated || $project->rollup_capitalization > $project->rollup_contract_value,
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user