fix(shipment): ensure pending review existence during status transitions

Add logic to automatically create a pending shipment review if one
does not exist when transitioning shipment statuses. This ensures
the review lifecycle remains intact for shipments created through
paths that bypass initial review generation.

Also include migrations to make `user_id` and `status` nullable on
`shipment_checklists` to support unassigned or incomplete checklists.
This commit is contained in:
Kazem Alghasi 2026-09-30 19:01:27 +03:30
parent b9b83ffecf
commit 8213ee7ee1
3 changed files with 92 additions and 0 deletions

View File

@ -79,6 +79,9 @@ class ShipmentReviewService
'این سفارش در وضعیت قابل تأیید نیست.'
);
// اگه review pending وجود نداره (سفارش بدون review ایجاد شده)، بساز
$this->ensurePendingReview($shipment);
$review = $this->getPendingReview($shipment);
$oldStatus = $shipment->status;
@ -133,6 +136,8 @@ class ShipmentReviewService
'این سفارش در وضعیت قابل رد نیست.'
);
$this->ensurePendingReview($shipment);
$review = $this->getPendingReview($shipment);
$oldStatus = $shipment->status;
@ -177,6 +182,25 @@ class ShipmentReviewService
}
}
/**
* اگه 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 فعلی که منتظر تصمیم کارمند است.
*

View File

@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
/**
* آیتم‌های خودکار چک‌لیست (ساخته‌شده از قالب هنگام approve) assignee ندارند
* تا یک کارمند آن‌ها را تکمیل کند. پس user_id باید nullable باشد.
*/
return new class extends Migration
{
public function up(): void
{
if (DB::connection()->getDriverName() === 'sqlite') {
return;
}
// MODIFY COLUMN روی ستون FK امن است؛ constraint باقی می‌ماند.
DB::statement("ALTER TABLE `shipment_checklists` MODIFY COLUMN `user_id` BIGINT UNSIGNED NULL");
}
public function down(): void
{
if (DB::connection()->getDriverName() === 'sqlite') {
return;
}
$nulls = DB::table('shipment_checklists')->whereNull('user_id')->count();
if ($nulls === 0) {
DB::statement("ALTER TABLE `shipment_checklists` MODIFY COLUMN `user_id` BIGINT UNSIGNED NOT NULL");
}
}
};

View File

@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
/**
* ستون status در shipment_checklists (deprecated — از item_title استفاده می‌شود)
* NOT NULL بدون پیش‌فرض بود و INSERT آیتم‌های خودکار را شکست می‌داد.
*/
return new class extends Migration
{
public function up(): void
{
if (DB::connection()->getDriverName() === 'sqlite') {
return;
}
DB::statement("ALTER TABLE `shipment_checklists` MODIFY COLUMN `status` VARCHAR(255) NULL");
}
public function down(): void
{
if (DB::connection()->getDriverName() === 'sqlite') {
return;
}
$nulls = DB::table('shipment_checklists')->whereNull('status')->count();
if ($nulls === 0) {
DB::statement("ALTER TABLE `shipment_checklists` MODIFY COLUMN `status` VARCHAR(255) NOT NULL");
} else {
DB::table('shipment_checklists')->whereNull('status')->update(['status' => 'pending']);
DB::statement("ALTER TABLE `shipment_checklists` MODIFY COLUMN `status` VARCHAR(255) NOT NULL");
}
}
};