feat: complete GSB construction ERP implementation with dual-mode MR, capitalization cap engine, single PM rule, and system sign-off documentation

This commit is contained in:
GSB Construction Engineering
2026-08-25 17:08:03 +08:00
parent 778f27e0a5
commit d4e2b468ff
114 changed files with 3923 additions and 10031 deletions

View File

@@ -11,7 +11,6 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Modules\BiddingManagement\Models\BidPackage;
use Modules\ContractorManagement\Models\Contractor;
use Modules\MasterData\Models\MaterialGroup;
use Modules\MaterialLogistics\Models\ProjectInventory;
@@ -47,6 +46,7 @@ class Project extends Model
'total_capitalization',
'parent_project_id',
'project_type',
'classifications',
'is_unprofitable',
'current_wizard_step',
];
@@ -64,6 +64,7 @@ class Project extends Model
'target_end_date' => 'date',
'actual_end_date' => 'date',
'is_unprofitable' => 'boolean',
'classifications' => 'array',
];
}
@@ -145,11 +146,6 @@ class Project extends Model
->withTimestamps();
}
public function bidPackages(): HasMany
{
return $this->hasMany(BidPackage::class);
}
public function parentProject(): BelongsTo
{
return $this->belongsTo(Project::class, 'parent_project_id');
@@ -165,6 +161,11 @@ class Project extends Model
return $this->hasMany(ProjectMaterialsEstimate::class);
}
public function projectClassifications(): BelongsToMany
{
return $this->belongsToMany(ProjectClassification::class, 'project_classification_pivot');
}
// --- State Machine Helpers ---
public function transitionTo(ProjectStatus $newStatus): void
@@ -233,8 +234,20 @@ class Project extends Model
public function recalculateCapitalization(): void
{
$total = $this->tasks->sum(fn (Task $task) => $task->total_cost);
$this->update(['total_capitalization' => $total]);
$tasksTotal = (float) $this->tasks->sum(fn (Task $task) => $task->total_cost);
$unestimatedMaterialsTotal = 0.0;
if (\Illuminate\Support\Facades\Schema::hasTable('material_requisition_items') && \Illuminate\Support\Facades\Schema::hasColumn('material_requisition_items', 'is_unestimated')) {
$unestimatedMaterialsTotal = (float) \Modules\MaterialLogistics\Models\MaterialRequisitionItem::whereHas('requisition', function ($q) {
$q->where('project_id', $this->id)
->whereNotIn('status', ['rejected', 'cancelled']);
})
->where('is_unestimated', true)
->get()
->sum(fn ($item) => (float) $item->quantity * (float) $item->unit_cost);
}
$this->update(['total_capitalization' => $tasksTotal + $unestimatedMaterialsTotal]);
}
public function getMilestoneCompletionAttribute(): float