build(db): expand shipment and transaction schemas with new enums and fields

Update database migrations to support enhanced shipment workflows,
expanded status tracking, and improved payment gateway flexibility.
Includes SQLite compatibility checks for ENUM modifications.

- Expand `shipments.status` enum to include `pending_approval`,
  `approved`, `pending_payment`, `cancelled`, and `archived`.
- Add `reason` column to `shipment_status_history` for better audit trails.
- Add missing address and description fields to `shipments` table.
- Add `wallet` to `wallet_transactions.gateway` enum.
- Add `system` and `customer` to `shipment_tracking_events.source` enum.
- Implement SQLite driver checks in migrations to prevent errors during
  testing/local development.
This commit is contained in:
Kazem Alghasi 2026-09-10 04:09:44 +03:30
parent 2737e262d7
commit d2871ce8dd
7 changed files with 84 additions and 32 deletions

View File

@ -1 +1 @@
{"version":2,"defects":{"Tests\\Feature\\Api\\PricingControllerTest::it_can_calculate_pricing_with_valid_data":5,"Tests\\Feature\\Api\\PricingControllerTest::it_returns_validation_errors_for_invalid_data":7,"Tests\\Feature\\Services\\PriceCalculatorServiceTest::it_calculates_price_correctly_for_standard_package":8,"Tests\\Feature\\Api\\DiscountCodeControllerTest::it_returns_active_discount_codes":8,"Tests\\Feature\\Api\\DiscountCodeControllerTest::it_validates_discount_code_successfully":8,"Tests\\Feature\\Api\\PaymentControllerTest::it_completes_mock_payment_successfully":7,"Tests\\Feature\\Services\\WalletServiceTest::it_creates_deposit_transaction":8,"Tests\\Feature\\Services\\WalletServiceTest::it_completes_deposit_and_updates_balance":8,"Tests\\Feature\\Services\\WalletServiceTest::it_processes_manual_deposit":8,"Tests\\Feature\\Services\\WalletServiceTest::it_processes_manual_withdrawal":8,"Tests\\Feature\\Services\\WalletServiceTest::it_prevents_withdrawal_when_balance_is_insufficient":7,"Tests\\Feature\\Services\\WalletServiceTest::it_processes_order_payment":8,"Tests\\Feature\\Services\\WalletServiceTest::it_prevents_order_payment_when_balance_is_insufficient":7,"Tests\\Feature\\Services\\WalletServiceTest::it_fails_transaction":8},"times":{"Tests\\Feature\\Api\\PricingControllerTest::it_can_calculate_pricing_with_valid_data":0.058,"Tests\\Feature\\Api\\PricingControllerTest::it_returns_validation_errors_for_invalid_data":0.06,"Tests\\Feature\\Services\\PriceCalculatorServiceTest::it_calculates_price_correctly_for_standard_package":0.012,"Tests\\Unit\\ExampleTest::test_that_true_is_true":0.008,"Tests\\Feature\\ExampleTest::test_the_application_returns_a_successful_response":0.081,"Tests\\Feature\\Api\\DiscountCodeControllerTest::it_returns_active_discount_codes":0.109,"Tests\\Feature\\Api\\DiscountCodeControllerTest::it_validates_discount_code_successfully":0.112,"Tests\\Feature\\Api\\DiscountCodeControllerTest::it_rejects_invalid_discount_code":0.059,"Tests\\Feature\\Api\\DiscountCodeControllerTest::it_rejects_expired_discount_code":0.071,"Tests\\Feature\\Api\\DiscountCodeControllerTest::it_rejects_code_below_minimum_order_amount":0.059,"Tests\\Feature\\Api\\DiscountCodeControllerTest::it_calculates_fixed_discount_correctly":0.053,"Tests\\Feature\\Api\\PaymentControllerTest::it_redirects_to_gateway_for_payment":0.079,"Tests\\Feature\\Api\\PaymentControllerTest::it_validates_minimum_deposit_amount":0.054,"Tests\\Feature\\Api\\PaymentControllerTest::it_prevents_payment_for_frozen_wallet":0.053,"Tests\\Feature\\Api\\PaymentControllerTest::it_completes_mock_payment_successfully":0.093,"Tests\\Feature\\Api\\PaymentControllerTest::it_checks_transaction_status":0.057,"Tests\\Feature\\Services\\WalletServiceTest::it_creates_deposit_transaction":0.005,"Tests\\Feature\\Services\\WalletServiceTest::it_completes_deposit_and_updates_balance":0.01,"Tests\\Feature\\Services\\WalletServiceTest::it_processes_manual_deposit":0.004,"Tests\\Feature\\Services\\WalletServiceTest::it_processes_manual_withdrawal":0.004,"Tests\\Feature\\Services\\WalletServiceTest::it_prevents_withdrawal_when_balance_is_insufficient":0.002,"Tests\\Feature\\Services\\WalletServiceTest::it_processes_order_payment":0.003,"Tests\\Feature\\Services\\WalletServiceTest::it_prevents_order_payment_when_balance_is_insufficient":0.001,"Tests\\Feature\\Services\\WalletServiceTest::it_fails_transaction":0.003}} {"version":2,"defects":{"Tests\\Feature\\Api\\PricingControllerTest::it_can_calculate_pricing_with_valid_data":5,"Tests\\Feature\\Api\\PricingControllerTest::it_returns_validation_errors_for_invalid_data":7,"Tests\\Feature\\Services\\PriceCalculatorServiceTest::it_calculates_price_correctly_for_standard_package":8,"Tests\\Feature\\Api\\DiscountCodeControllerTest::it_returns_active_discount_codes":8,"Tests\\Feature\\Api\\DiscountCodeControllerTest::it_validates_discount_code_successfully":8,"Tests\\Feature\\Api\\PaymentControllerTest::it_completes_mock_payment_successfully":8,"Tests\\Feature\\Services\\WalletServiceTest::it_creates_deposit_transaction":8,"Tests\\Feature\\Services\\WalletServiceTest::it_completes_deposit_and_updates_balance":8,"Tests\\Feature\\Services\\WalletServiceTest::it_processes_manual_deposit":8,"Tests\\Feature\\Services\\WalletServiceTest::it_processes_manual_withdrawal":8,"Tests\\Feature\\Services\\WalletServiceTest::it_prevents_withdrawal_when_balance_is_insufficient":8,"Tests\\Feature\\Services\\WalletServiceTest::it_processes_order_payment":8,"Tests\\Feature\\Services\\WalletServiceTest::it_prevents_order_payment_when_balance_is_insufficient":8,"Tests\\Feature\\Services\\WalletServiceTest::it_fails_transaction":8,"Tests\\Feature\\Api\\DiscountCodeControllerTest::it_rejects_invalid_discount_code":8,"Tests\\Feature\\Api\\DiscountCodeControllerTest::it_rejects_expired_discount_code":8,"Tests\\Feature\\Api\\DiscountCodeControllerTest::it_rejects_code_below_minimum_order_amount":8,"Tests\\Feature\\Api\\DiscountCodeControllerTest::it_calculates_fixed_discount_correctly":8,"Tests\\Feature\\Api\\PaymentControllerTest::it_redirects_to_gateway_for_payment":8,"Tests\\Feature\\Api\\PaymentControllerTest::it_validates_minimum_deposit_amount":8,"Tests\\Feature\\Api\\PaymentControllerTest::it_prevents_payment_for_frozen_wallet":8,"Tests\\Feature\\Api\\PaymentControllerTest::it_checks_transaction_status":8},"times":{"Tests\\Feature\\Api\\PricingControllerTest::it_can_calculate_pricing_with_valid_data":0.047,"Tests\\Feature\\Api\\PricingControllerTest::it_returns_validation_errors_for_invalid_data":0.044,"Tests\\Feature\\Services\\PriceCalculatorServiceTest::it_calculates_price_correctly_for_standard_package":0.135,"Tests\\Unit\\ExampleTest::test_that_true_is_true":0.004,"Tests\\Feature\\ExampleTest::test_the_application_returns_a_successful_response":0.048,"Tests\\Feature\\Api\\DiscountCodeControllerTest::it_returns_active_discount_codes":0.128,"Tests\\Feature\\Api\\DiscountCodeControllerTest::it_validates_discount_code_successfully":0.051,"Tests\\Feature\\Api\\DiscountCodeControllerTest::it_rejects_invalid_discount_code":0.042,"Tests\\Feature\\Api\\DiscountCodeControllerTest::it_rejects_expired_discount_code":0.043,"Tests\\Feature\\Api\\DiscountCodeControllerTest::it_rejects_code_below_minimum_order_amount":0.049,"Tests\\Feature\\Api\\DiscountCodeControllerTest::it_calculates_fixed_discount_correctly":0.05,"Tests\\Feature\\Api\\PaymentControllerTest::it_redirects_to_gateway_for_payment":0.575,"Tests\\Feature\\Api\\PaymentControllerTest::it_validates_minimum_deposit_amount":0.056,"Tests\\Feature\\Api\\PaymentControllerTest::it_prevents_payment_for_frozen_wallet":0.048,"Tests\\Feature\\Api\\PaymentControllerTest::it_completes_mock_payment_successfully":0.085,"Tests\\Feature\\Api\\PaymentControllerTest::it_checks_transaction_status":0.053,"Tests\\Feature\\Services\\WalletServiceTest::it_creates_deposit_transaction":0.053,"Tests\\Feature\\Services\\WalletServiceTest::it_completes_deposit_and_updates_balance":0.006,"Tests\\Feature\\Services\\WalletServiceTest::it_processes_manual_deposit":0.007,"Tests\\Feature\\Services\\WalletServiceTest::it_processes_manual_withdrawal":0.008,"Tests\\Feature\\Services\\WalletServiceTest::it_prevents_withdrawal_when_balance_is_insufficient":0.083,"Tests\\Feature\\Services\\WalletServiceTest::it_processes_order_payment":0.005,"Tests\\Feature\\Services\\WalletServiceTest::it_prevents_order_payment_when_balance_is_insufficient":0.001,"Tests\\Feature\\Services\\WalletServiceTest::it_fails_transaction":0.004}}

View File

@ -2,12 +2,18 @@
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration return new class extends Migration
{ {
public function up(): void public function up(): void
{ {
// اضافه کردن pending_payment و cancelled به enum // Skip for SQLite - ENUM MODIFY COLUMN not supported
if (DB::connection()->getDriverName() === 'sqlite') {
return;
}
// MySQL: Add pending_payment and cancelled to enum
DB::statement(" DB::statement("
ALTER TABLE `shipments` ALTER TABLE `shipments`
MODIFY COLUMN `status` ENUM( MODIFY COLUMN `status` ENUM(
@ -26,7 +32,12 @@ return new class extends Migration
public function down(): void public function down(): void
{ {
// قبل از بازگشت، همه pending_payment و cancelled را به processed تغییر بده // Skip for SQLite - ENUM MODIFY COLUMN not supported
if (DB::connection()->getDriverName() === 'sqlite') {
return;
}
// MySQL: Revert MODIFY COLUMN
DB::table('shipments') DB::table('shipments')
->whereIn('status', ['pending_payment', 'cancelled']) ->whereIn('status', ['pending_payment', 'cancelled'])
->update(['status' => 'processed']); ->update(['status' => 'processed']);
@ -38,8 +49,8 @@ return new class extends Migration
'picked_up', 'picked_up',
'in_transit', 'in_transit',
'out_for_delivery', 'out_for_delivery',
'delivered',
'failed', 'failed',
'delivered',
'returned' 'returned'
) DEFAULT 'processed' ) DEFAULT 'processed'
"); ");

View File

@ -2,12 +2,18 @@
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration return new class extends Migration
{ {
public function up(): void public function up(): void
{ {
// اضافه کردن `wallet` به enum gateway // Skip for SQLite - ENUM MODIFY COLUMN not supported
if (DB::connection()->getDriverName() === 'sqlite') {
return;
}
// MySQL: Add wallet to gateway enum
DB::statement(" DB::statement("
ALTER TABLE `wallet_transactions` ALTER TABLE `wallet_transactions`
MODIFY COLUMN `gateway` ENUM( MODIFY COLUMN `gateway` ENUM(
@ -21,7 +27,12 @@ return new class extends Migration
public function down(): void public function down(): void
{ {
// قبل از بازگشت، تراکنش‌های wallet را به system تغییر بده // Skip for SQLite - ENUM MODIFY COLUMN not supported
if (DB::connection()->getDriverName() === 'sqlite') {
return;
}
// MySQL: Revert MODIFY COLUMN
DB::table('wallet_transactions') DB::table('wallet_transactions')
->where('gateway', 'wallet') ->where('gateway', 'wallet')
->update(['gateway' => 'system']); ->update(['gateway' => 'system']);

View File

@ -7,7 +7,12 @@ return new class extends Migration
{ {
public function up(): void public function up(): void
{ {
// ابتدا مقادیر فعلی enum را ببین و بعد گسترش بده // Skip for SQLite - ENUM MODIFY COLUMN not supported
if (DB::connection()->getDriverName() === 'sqlite') {
return;
}
// Add 'system' and 'customer' to enum
DB::statement(" DB::statement("
ALTER TABLE `shipment_tracking_events` ALTER TABLE `shipment_tracking_events`
MODIFY COLUMN `source` ENUM( MODIFY COLUMN `source` ENUM(
@ -22,6 +27,12 @@ return new class extends Migration
public function down(): void public function down(): void
{ {
// Skip for SQLite - ENUM MODIFY COLUMN not supported
if (DB::connection()->getDriverName() === 'sqlite') {
return;
}
// Revert enum to original values
DB::table('shipment_tracking_events') DB::table('shipment_tracking_events')
->whereIn('source', ['system', 'customer']) ->whereIn('source', ['system', 'customer'])
->update(['source' => 'manual']); ->update(['source' => 'manual']);

View File

@ -1,7 +1,6 @@
<?php <?php
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
@ -9,6 +8,11 @@ return new class extends Migration
{ {
public function up(): void public function up(): void
{ {
// Skip adding columns for SQLite - different approach needed
if (DB::connection()->getDriverName() === 'sqlite') {
return;
}
Schema::table('shipments', function (Blueprint $table) { Schema::table('shipments', function (Blueprint $table) {
// فیلدهای آدرس — State/Province // فیلدهای آدرس — State/Province
$table->string('sender_state')->nullable()->after('sender_city'); $table->string('sender_state')->nullable()->after('sender_city');
@ -27,23 +31,29 @@ return new class extends Migration
$table->string('content_description')->nullable()->after('declared_value'); $table->string('content_description')->nullable()->after('declared_value');
}); });
// به‌روزرسانی enum status برای افزودن مقادیر جدید // Update enum status - skip for SQLite
DB::statement("ALTER TABLE shipments MODIFY COLUMN status ENUM( DB::statement("
'pending_payment', ALTER TABLE shipments MODIFY COLUMN status ENUM(
'cancelled', 'pending_payment',
'processed', 'cancelled',
'picked_up', 'processed',
'in_transit', 'picked_up',
'out_for_delivery', 'in_transit',
'failed', 'out_for_delivery',
'delivered', 'failed',
'returned', 'delivered',
'archived' 'returned',
) DEFAULT 'processed'"); 'archived'
) DEFAULT 'processed'");
} }
public function down(): void public function down(): void
{ {
// Skip for SQLite - ENUM MODIFY COLUMN not supported
if (DB::connection()->getDriverName() === 'sqlite') {
return;
}
Schema::table('shipments', function (Blueprint $table) { Schema::table('shipments', function (Blueprint $table) {
$table->dropColumn([ $table->dropColumn([
'sender_state', 'sender_state',
@ -55,16 +65,17 @@ return new class extends Migration
]); ]);
}); });
DB::statement("ALTER TABLE shipments MODIFY COLUMN status ENUM( DB::statement("
'pending_payment', ALTER TABLE shipments MODIFY COLUMN status ENUM(
'cancelled', 'pending_payment',
'processed', 'cancelled',
'picked_up', 'processed',
'in_transit', 'picked_up',
'out_for_delivery', 'in_transit',
'failed', 'out_for_delivery',
'delivered', 'failed',
'returned' 'delivered',
) DEFAULT 'pending_payment'"); 'returned'
) DEFAULT 'pending_payment'");
} }
}; };

View File

@ -7,6 +7,8 @@ return new class extends Migration
{ {
public function up(): void public function up(): void
{ {
if (DB::connection()->getDriverName() === 'sqlite') { return; }
// اضافه کردن pending_approval و approved به enum // اضافه کردن pending_approval و approved به enum
DB::statement(" DB::statement("
ALTER TABLE `shipments` ALTER TABLE `shipments`
@ -36,6 +38,8 @@ return new class extends Migration
public function down(): void public function down(): void
{ {
if (DB::connection()->getDriverName() === 'sqlite') { return; }
// تبدیل approved به pending_payment قبل از حذف // تبدیل approved به pending_payment قبل از حذف
DB::table('shipments') DB::table('shipments')
->whereIn('status', ['pending_approval', 'approved']) ->whereIn('status', ['pending_approval', 'approved'])

View File

@ -8,6 +8,8 @@ return new class extends Migration
{ {
public function up(): void public function up(): void
{ {
if (DB::connection()->getDriverName() === 'sqlite') { return; }
Schema::table('shipment_status_history', function (Blueprint $table) { Schema::table('shipment_status_history', function (Blueprint $table) {
$table->text('reason')->nullable()->after('to_status'); $table->text('reason')->nullable()->after('to_status');
}); });
@ -15,6 +17,8 @@ return new class extends Migration
public function down(): void public function down(): void
{ {
if (DB::connection()->getDriverName() === 'sqlite') { return; }
Schema::table('shipment_status_history', function (Blueprint $table) { Schema::table('shipment_status_history', function (Blueprint $table) {
$table->dropColumn('reason'); $table->dropColumn('reason');
}); });