lockForUpdate() ->findOrFail($shipment->id); $this->assertPendingReview( $shipment, 'این سفارش در وضعیت قابل درخواست اصلاح نیست.' ); $revisionNo = $this->nextReviewRevision($shipment); $shipment->update([ 'review_state' => ReviewState::ChangesRequested, ]); ShipmentReview::create([ 'shipment_id' => $shipment->id, 'revision_no' => $revisionNo, 'decision' => ReviewState::ChangesRequested->value, 'reason' => $reason, 'notes' => $notes, 'reviewed_by' => $user->id, 'reviewed_at' => now(), ]); // اعلان به مشتری: سفارش نیازمند اصلاح است $shipment->user->notify( new \App\Notifications\OrderNeedsChangesNotification($shipment, $reason, $notes) ); return $shipment->fresh(); }); } /** * تأیید سفارش توسط کارمند. * * تأیید، Review جدید ایجاد نمی‌کند. * آخرین revision با وضعیت pending را finalize می‌کند. */ public function approve( Shipment $shipment, User $user, ?string $notes = null ): Shipment { return DB::transaction(function () use ($shipment, $user, $notes) { $shipment = Shipment::query() ->lockForUpdate() ->findOrFail($shipment->id); $this->assertPendingReview( $shipment, 'این سفارش در وضعیت قابل تأیید نیست.' ); // اگه review pending وجود نداره (سفارش بدون review ایجاد شده)، بساز $this->ensurePendingReview($shipment); $review = $this->getPendingReview($shipment); $oldStatus = $shipment->status; $shipment->update([ 'status' => ShipmentStatus::Approved, 'review_state' => ReviewState::Approved, ]); ShipmentStatusHistory::create([ 'shipment_id' => $shipment->id, 'from_status' => $oldStatus->value, 'to_status' => ShipmentStatus::Approved->value, 'reason' => $notes ?: 'تأیید توسط کارمند', 'changed_by' => $user->id, ]); $review->update([ 'decision' => ReviewState::Approved->value, 'reason' => null, 'notes' => $notes, 'reviewed_by' => $user->id, 'reviewed_at' => now(), ]); // خودکارسازی چک‌لیست از قالب‌های فعال (بعد از تأیید سفارش) $this->instantiateChecklist($shipment); // اعلان به مشتری: سفارش تأیید شد، می‌تونه پرداخت کنه $shipment->user->notify( new \App\Notifications\OrderApprovedForPaymentNotification($shipment, $user) ); return $shipment->fresh(); }); } /** * رد سفارش توسط کارمند. * * رد، Review جدید ایجاد نمی‌کند. * آخرین revision با وضعیت pending را finalize می‌کند. */ public function reject( Shipment $shipment, User $user, string $reason ): Shipment { return DB::transaction(function () use ($shipment, $user, $reason) { $shipment = Shipment::query() ->lockForUpdate() ->findOrFail($shipment->id); $this->assertPendingReview( $shipment, 'این سفارش در وضعیت قابل رد نیست.' ); $this->ensurePendingReview($shipment); $review = $this->getPendingReview($shipment); $oldStatus = $shipment->status; $shipment->update([ 'status' => ShipmentStatus::Cancelled, 'review_state' => ReviewState::Rejected, ]); ShipmentStatusHistory::create([ 'shipment_id' => $shipment->id, 'from_status' => $oldStatus->value, 'to_status' => ShipmentStatus::Cancelled->value, 'reason' => 'رد شده: ' . $reason, 'changed_by' => $user->id, ]); $review->update([ 'decision' => ReviewState::Rejected->value, 'reason' => $reason, 'notes' => null, 'reviewed_by' => $user->id, 'reviewed_at' => now(), ]); return $shipment->fresh(); }); } /** * بررسی وضعیت فعلی Shipment برای عملیات Review. */ private function assertPendingReview( Shipment $shipment, string $message ): void { if ( $shipment->status !== ShipmentStatus::PendingApproval || $shipment->review_state !== ReviewState::Pending ) { throw new RuntimeException($message); } } /** * اگه review pending وجود نداره (سفارش از مسیری بدون ساخت review ایجاد شده)، * revision بعدی با decision=pending می‌سازد تا چرخه تأیید/رد کامل بشه. */ private function ensurePendingReview(Shipment $shipment): void { $exists = $shipment->reviews() ->where('decision', ReviewState::Pending->value) ->exists(); if (!$exists) { ShipmentReview::create([ 'shipment_id' => $shipment->id, 'revision_no' => $this->nextReviewRevision($shipment), 'decision' => ReviewState::Pending->value, ]); } } /** * پیدا کردن Review فعلی که منتظر تصمیم کارمند است. * * بعد از resubmit باید دقیقاً یک revision با decision=pending * وجود داشته باشد. */ private function getPendingReview(Shipment $shipment): ShipmentReview { $review = $shipment->reviews() ->where('decision', ReviewState::Pending->value) ->orderByDesc('revision_no') ->first(); if (!$review) { throw new RuntimeException( 'برای این سفارش هیچ Review در وضعیت pending پیدا نشد.' ); } return $review; } /** * تعیین شماره revision بعدی. * * هر resubmission یک revision جدید ایجاد می‌کند. */ private function nextReviewRevision(Shipment $shipment): int { return ((int) $shipment->reviews()->max('revision_no')) + 1; } /** * ساخت instance‌های چک‌لیست از قالب‌های فعال متناسب با جهت سفارش. * فقط اگه هنوز چک‌لیستی برای این سفارش ساخته نشده باشد. */ private function instantiateChecklist(Shipment $shipment): void { // جلوگیری از تکرار اگه قبلاً ساخته شده if ($shipment->checklists()->exists()) { return; } $direction = $shipment->direction->value; // 'export' | 'import' $templates = ShipmentChecklistTemplate::query() ->where('is_active', true) ->where(function ($q) use ($direction) { $q->where('direction', 'both')->orWhere('direction', $direction); }) ->orderBy('sort_order') ->get(); if ($templates->isEmpty()) { return; // قالب فعالی نیست — چک‌لیست ساخته نمی‌شه } $rows = $templates->map(fn ($t) => [ 'shipment_id' => $shipment->id, 'item_title' => $t->item_title, 'is_required' => $t->is_required, 'sort_order' => $t->sort_order, 'is_completed' => false, ])->toArray(); $shipment->checklists()->createMany($rows); } }