diff --git a/packages/core/database/migrations/2026_01_01_000073_add_refunded_quantity_to_order_lines_table.php b/packages/core/database/migrations/2026_01_01_000073_add_refunded_quantity_to_order_lines_table.php new file mode 100644 index 0000000000..32424503b7 --- /dev/null +++ b/packages/core/database/migrations/2026_01_01_000073_add_refunded_quantity_to_order_lines_table.php @@ -0,0 +1,32 @@ +prefix.'order_lines'; + + if (! Schema::hasTable($table) || Schema::hasColumn($table, 'refunded_quantity')) { + return; + } + + Schema::table($table, function (Blueprint $table) { + $table->unsignedInteger('refunded_quantity') + ->default(0) + ->comment('Rollup of refund_lines.quantity for this line'); + }); + } + + public function down(): void + { + // The flat baseline owns this column, so reconciliation must not remove it. + } +}; diff --git a/tests/core/Unit/OrderLineRefundedQuantityMigrationTest.php b/tests/core/Unit/OrderLineRefundedQuantityMigrationTest.php new file mode 100644 index 0000000000..673477cc63 --- /dev/null +++ b/tests/core/Unit/OrderLineRefundedQuantityMigrationTest.php @@ -0,0 +1,85 @@ +group('migrations', 'cross-db'); + +const REFUNDED_QUANTITY_PREFIX = 'refundqty_'; + +beforeEach(function () { + config(['lunar.database.table_prefix' => REFUNDED_QUANTITY_PREFIX]); +}); + +afterEach(function () { + Schema::dropIfExists(REFUNDED_QUANTITY_PREFIX.'order_lines'); +}); + +function refundedQuantityMigration(): object +{ + $path = glob(dirname(__DIR__, 3).'/packages/core/database/migrations/*add_refunded_quantity_to_order_lines_table.php'); + + return require $path[0]; +} + +function createExistingOrderLinesTable(bool $withRefundedQuantity = false): void +{ + Schema::create(REFUNDED_QUANTITY_PREFIX.'order_lines', function (Blueprint $table) use ($withRefundedQuantity) { + $table->id(); + $table->unsignedInteger('quantity'); + + if ($withRefundedQuantity) { + $table->unsignedInteger('refunded_quantity')->default(0); + } + }); +} + +test('it adds refunded quantity and preserves existing order lines', function () { + createExistingOrderLinesTable(); + + DB::table(REFUNDED_QUANTITY_PREFIX.'order_lines')->insert([ + 'id' => 1, + 'quantity' => 3, + ]); + + $migration = refundedQuantityMigration(); + $migration->up(); + $migration->up(); + + $line = DB::table(REFUNDED_QUANTITY_PREFIX.'order_lines')->find(1); + + expect(Schema::hasColumn(REFUNDED_QUANTITY_PREFIX.'order_lines', 'refunded_quantity'))->toBeTrue() + ->and($line->quantity)->toBe(3) + ->and($line->refunded_quantity)->toBe(0); + + $migration->down(); + + expect(Schema::hasColumn(REFUNDED_QUANTITY_PREFIX.'order_lines', 'refunded_quantity'))->toBeTrue(); +}); + +test('it is a no-op when refunded quantity already exists', function () { + createExistingOrderLinesTable(withRefundedQuantity: true); + + DB::table(REFUNDED_QUANTITY_PREFIX.'order_lines')->insert([ + 'id' => 1, + 'quantity' => 3, + 'refunded_quantity' => 2, + ]); + + refundedQuantityMigration()->up(); + + expect(DB::table(REFUNDED_QUANTITY_PREFIX.'order_lines')->value('refunded_quantity'))->toBe(2); +}); + +test('it is a no-op when the order lines table does not exist', function () { + $migration = refundedQuantityMigration(); + + $migration->up(); + $migration->down(); + + expect(Schema::hasTable(REFUNDED_QUANTITY_PREFIX.'order_lines'))->toBeFalse(); +});