Additional Changes
This commit is contained in:
@@ -32,12 +32,17 @@ class Project extends Model
|
||||
'location',
|
||||
'status',
|
||||
'contract_value',
|
||||
'total_estimated_value',
|
||||
'contract_duration',
|
||||
'start_date',
|
||||
'target_end_date',
|
||||
'actual_end_date',
|
||||
'completion_percentage',
|
||||
'total_capitalization',
|
||||
'parent_project_id',
|
||||
'project_type',
|
||||
'is_unprofitable',
|
||||
'current_wizard_step',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
@@ -45,12 +50,14 @@ class Project extends Model
|
||||
return [
|
||||
'status' => ProjectStatus::class,
|
||||
'contract_value' => 'decimal:2',
|
||||
'total_estimated_value' => 'decimal:2',
|
||||
'total_capitalization' => 'decimal:2',
|
||||
'completion_percentage' => 'decimal:2',
|
||||
'contract_duration' => 'integer',
|
||||
'start_date' => 'date',
|
||||
'target_end_date' => 'date',
|
||||
'actual_end_date' => 'date',
|
||||
'is_unprofitable' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -130,6 +137,21 @@ class Project extends Model
|
||||
return $this->hasMany(BidPackage::class);
|
||||
}
|
||||
|
||||
public function parentProject(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Project::class, 'parent_project_id');
|
||||
}
|
||||
|
||||
public function extensionProjects(): HasMany
|
||||
{
|
||||
return $this->hasMany(Project::class, 'parent_project_id');
|
||||
}
|
||||
|
||||
public function materialsEstimates(): HasMany
|
||||
{
|
||||
return $this->hasMany(ProjectMaterialsEstimate::class);
|
||||
}
|
||||
|
||||
// --- State Machine Helpers ---
|
||||
|
||||
public function transitionTo(ProjectStatus $newStatus): void
|
||||
@@ -150,6 +172,16 @@ class Project extends Model
|
||||
}
|
||||
}
|
||||
|
||||
// Guard: prevent parent project from being completed/closed until all of its child projects are completed/closed
|
||||
if (in_array($newStatus, [ProjectStatus::Completed, ProjectStatus::Closed])) {
|
||||
$uncompletedChildren = $this->extensionProjects()
|
||||
->whereNotIn('status', [ProjectStatus::Completed->value, ProjectStatus::Closed->value])
|
||||
->exists();
|
||||
if ($uncompletedChildren) {
|
||||
throw new \InvalidArgumentException('Cannot complete or close parent project while there are active child projects (extensions).');
|
||||
}
|
||||
}
|
||||
|
||||
$this->update(['status' => $newStatus]);
|
||||
}
|
||||
|
||||
@@ -160,6 +192,11 @@ class Project extends Model
|
||||
return $query->where('status', $status);
|
||||
}
|
||||
|
||||
public function scopeActive($query)
|
||||
{
|
||||
return $query->whereNotIn('status', [ProjectStatus::Completed, ProjectStatus::Closed]);
|
||||
}
|
||||
|
||||
// --- Accessors ---
|
||||
|
||||
public function getProjectManagerAttribute(): ?User
|
||||
@@ -189,14 +226,42 @@ class Project extends Model
|
||||
|
||||
public function getMilestoneCompletionAttribute(): float
|
||||
{
|
||||
$milestones = $this->milestones;
|
||||
$milestones = $this->milestones()->with('tasks')->get();
|
||||
if ($milestones->isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (float) $milestones
|
||||
->where('actual_date', '!=', null)
|
||||
->sum('weight_percentage');
|
||||
$totalProgress = 0;
|
||||
foreach ($milestones as $milestone) {
|
||||
$tasks = $milestone->tasks;
|
||||
$milestoneProgress = 0;
|
||||
if ($tasks->isNotEmpty()) {
|
||||
$milestoneProgress = $tasks->avg('completion_percentage') ?? 0;
|
||||
} elseif ($milestone->actual_date !== null) {
|
||||
$milestoneProgress = 100;
|
||||
}
|
||||
$totalProgress += ($milestoneProgress * (float) $milestone->weight_percentage) / 100;
|
||||
}
|
||||
|
||||
return round($totalProgress, 2);
|
||||
}
|
||||
|
||||
public function getRollupCapitalizationAttribute(): float
|
||||
{
|
||||
$childSum = $this->extensionProjects->sum(fn ($child) => $child->rollup_capitalization);
|
||||
return (float) $this->total_capitalization + $childSum;
|
||||
}
|
||||
|
||||
public function getRollupEstimatedValueAttribute(): float
|
||||
{
|
||||
$childSum = $this->extensionProjects->sum(fn ($child) => $child->rollup_estimated_value);
|
||||
return (float) $this->total_estimated_value + $childSum;
|
||||
}
|
||||
|
||||
public function getRollupContractValueAttribute(): float
|
||||
{
|
||||
$childSum = $this->extensionProjects->sum(fn ($child) => $child->rollup_contract_value);
|
||||
return (float) $this->contract_value + $childSum;
|
||||
}
|
||||
|
||||
public function getWeatherDelayDaysAttribute(): int
|
||||
@@ -210,4 +275,18 @@ class Project extends Model
|
||||
|
||||
return $milestoneDays + (int) ceil($taskDelayHours / 8);
|
||||
}
|
||||
|
||||
public function onApprovalCompleted($chain): void
|
||||
{
|
||||
if ($chain->status->value === 'approved') {
|
||||
$this->update([
|
||||
'status' => ProjectStatus::InProgress,
|
||||
'current_wizard_step' => 8,
|
||||
]);
|
||||
} elseif ($chain->status->value === 'rejected') {
|
||||
$this->update([
|
||||
'current_wizard_step' => 6,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user