'assessment', 'title' => 'ارزیابی و بازرسی اولیه'], ['key' => 'planning', 'title' => 'برنامه‌ریزی و تأیید'], ['key' => 'dismantling', 'title' => 'دمونتاژ و تخلیه'], ['key' => 'rebuilding', 'title' => 'بازسازی و مونتاژ'], ['key' => 'testing', 'title' => 'تست و راه‌اندازی آزمایشی'], ['key' => 'commissioning', 'title' => 'تحویل و بهره‌برداری'], ]; public const WORKFLOW_STATUSES = [ 'draft', 'assessment', 'planning', 'approved', 'dismantling', 'rebuilding', 'testing', 'commissioning', 'completed', 'on_hold', 'cancelled', ]; public function generateNumber(Business $business): string { $prefix = 'RB-'.now()->format('ym'); $last = RebuildProject::where('business_id', $business->id) ->where('rebuild_number', 'like', $prefix.'%') ->orderByDesc('id') ->value('rebuild_number'); $seq = 1; if ($last && preg_match('/(\d+)$/', $last, $m)) { $seq = (int) $m[1] + 1; } return $prefix.str_pad((string) $seq, 4, '0', STR_PAD_LEFT); } public function resolveEquipmentIds(string $scopeType, array $data): array { if ($scopeType === 'production_line') { if (empty($data['production_line_id'])) { return []; } return Equipment::where('business_id', $data['business_id']) ->where('production_line_id', $data['production_line_id']) ->pluck('id') ->map(fn ($id) => (int) $id) ->all(); } if (! empty($data['equipment_ids']) && is_array($data['equipment_ids'])) { return array_values(array_unique(array_map('intval', $data['equipment_ids']))); } if (! empty($data['equipment_id'])) { return [(int) $data['equipment_id']]; } return []; } public function createProject(Business $business, array $data, array $equipmentIds, ?User $user = null): RebuildProject { return DB::transaction(function () use ($business, $data, $equipmentIds, $user) { $status = $data['status'] ?? 'draft'; $project = RebuildProject::create(array_merge($data, [ 'business_id' => $business->id, 'rebuild_number' => $data['rebuild_number'] ?? $this->generateNumber($business), 'status' => $status, 'progress_percent' => $status === 'draft' ? 0 : 5, ])); $this->initializePhases($project); $this->syncEquipmentPivot($project, $equipmentIds); if ($status !== 'draft') { $this->startWorkflow($project, $user); } return $project->fresh(['phases', 'equipmentItems', 'productionLine', 'supervisor']); }); } public function initializePhases(RebuildProject $project): void { if ($project->phases()->exists()) { return; } foreach (self::PHASES as $index => $phase) { RebuildPhase::create([ 'rebuild_project_id' => $project->id, 'phase_key' => $phase['key'], 'title' => $phase['title'], 'sort_order' => $index + 1, 'status' => 'pending', ]); } } public function syncEquipmentPivot(RebuildProject $project, array $equipmentIds): void { $sync = []; foreach ($equipmentIds as $equipmentId) { $equipment = Equipment::find($equipmentId); $sync[$equipmentId] = [ 'pre_rebuild_health' => $equipment?->health_percentage, 'status' => 'pending', ]; } $project->equipmentItems()->sync($sync); } public function startWorkflow(RebuildProject $project, ?User $user = null): RebuildProject { return DB::transaction(function () use ($project, $user) { if ($project->status === 'cancelled' || $project->status === 'completed') { throw new \InvalidArgumentException('پروژه بازسازی در وضعیت فعلی قابل شروع نیست.'); } if ($project->equipmentItems()->count() === 0) { throw new \InvalidArgumentException('حداقل یک تجهیز برای شروع بازسازی لازم است.'); } $project->update([ 'status' => 'assessment', 'actual_start_date' => $project->actual_start_date ?? now()->toDateString(), 'progress_percent' => max($project->progress_percent, 10), ]); $this->activatePhase($project, 'assessment', $user); $this->syncEquipmentOperationalStatus($project); return $project->fresh(['phases', 'equipmentItems']); }); } public function advancePhase(RebuildProject $project, ?string $notes = null, ?User $user = null): RebuildProject { return DB::transaction(function () use ($project, $notes, $user) { $currentKey = $this->currentPhaseKey($project); if (! $currentKey) { throw new \InvalidArgumentException('فاز فعالی برای پیش‌برد یافت نشد.'); } $this->completePhase($project, $currentKey, $notes, $user); $nextKey = $this->nextPhaseKey($currentKey); if ($nextKey === null) { return $this->completeProject($project, $user); } $statusMap = [ 'planning' => ['status' => 'planning', 'progress' => 25], 'dismantling' => ['status' => 'dismantling', 'progress' => 45], 'rebuilding' => ['status' => 'rebuilding', 'progress' => 60], 'testing' => ['status' => 'testing', 'progress' => 80], 'commissioning' => ['status' => 'commissioning', 'progress' => 95], ]; if (isset($statusMap[$nextKey])) { $project->update([ 'status' => $statusMap[$nextKey]['status'], 'progress_percent' => $statusMap[$nextKey]['progress'], ]); $this->activatePhase($project, $nextKey, $user); $this->syncEquipmentOperationalStatus($project); } return $project->fresh(['phases', 'equipmentItems']); }); } public function completeProject(RebuildProject $project, ?User $user = null): RebuildProject { $project->update([ 'status' => 'completed', 'progress_percent' => 100, 'actual_end_date' => now()->toDateString(), ]); foreach ($project->equipmentItems as $equipment) { $postHealth = $equipment->pivot->post_rebuild_health ?? $equipment->health_percentage ?? 100; $equipment->update([ 'status' => 'active', 'health_percentage' => $postHealth, ]); $project->equipmentItems()->updateExistingPivot($equipment->id, ['status' => 'completed']); } return $project->fresh(['phases', 'equipmentItems']); } public function holdProject(RebuildProject $project): RebuildProject { $project->update(['status' => 'on_hold']); return $project; } public function cancelProject(RebuildProject $project): RebuildProject { return DB::transaction(function () use ($project) { $project->update([ 'status' => 'cancelled', 'actual_end_date' => now()->toDateString(), ]); foreach ($project->equipmentItems as $equipment) { if (in_array($equipment->status, ['under_rebuild', 'under_maintenance'], true)) { $equipment->update(['status' => 'active']); } } $project->phases()->where('status', 'in_progress')->update(['status' => 'pending']); return $project->fresh(['phases', 'equipmentItems']); }); } protected function currentPhaseKey(RebuildProject $project): ?string { $phase = $project->phases()->where('status', 'in_progress')->orderBy('sort_order')->first(); return $phase?->phase_key; } protected function nextPhaseKey(string $currentKey): ?string { $keys = array_column(self::PHASES, 'key'); $index = array_search($currentKey, $keys, true); if ($index === false || $index >= count($keys) - 1) { return null; } return $keys[$index + 1]; } protected function activatePhase(RebuildProject $project, string $phaseKey, ?User $user = null): void { $phase = $project->phases()->where('phase_key', $phaseKey)->first(); if (! $phase) { return; } $phase->update([ 'status' => 'in_progress', 'actual_start' => $phase->actual_start ?? now()->toDateString(), ]); } protected function completePhase(RebuildProject $project, string $phaseKey, ?string $notes, ?User $user): void { $phase = $project->phases()->where('phase_key', $phaseKey)->first(); if (! $phase) { return; } $phase->update([ 'status' => 'completed', 'actual_end' => now()->toDateString(), 'notes' => $notes ? trim(($phase->notes ? $phase->notes."\n" : '').$notes) : $phase->notes, 'completed_by' => $user?->id, ]); if ($phaseKey === 'planning') { $project->update(['status' => 'approved', 'progress_percent' => 40]); } } protected function syncEquipmentOperationalStatus(RebuildProject $project): void { $rebuildStatuses = ['dismantling', 'rebuilding', 'testing', 'commissioning', 'approved']; $equipmentStatus = in_array($project->status, $rebuildStatuses, true) ? 'under_rebuild' : 'under_maintenance'; foreach ($project->equipmentItems as $equipment) { if ($project->status === 'completed' || $project->status === 'cancelled') { continue; } $equipment->update(['status' => $equipmentStatus]); $project->equipmentItems()->updateExistingPivot($equipment->id, [ 'status' => in_array($project->status, ['testing', 'commissioning'], true) ? 'in_progress' : 'pending', ]); } } public function phaseLabels(): array { return collect(self::PHASES)->mapWithKeys(fn ($p) => [$p['key'] => $p['title']])->all(); } public function rebuildStatusLabels(): array { return [ 'draft' => 'پیش‌نویس', 'assessment' => 'ارزیابی', 'planning' => 'برنامه‌ریزی', 'approved' => 'تأیید‌شده', 'dismantling' => 'دمونتاژ', 'rebuilding' => 'بازسازی', 'testing' => 'تست', 'commissioning' => 'تحویل', 'completed' => 'تکمیل‌شده', 'on_hold' => 'معلق', 'cancelled' => 'لغو‌شده', ]; } public function scopeTypeLabels(): array { return [ 'equipment' => 'تجهیز', 'production_line' => 'خط تولید', ]; } }