refactor(stock): centralize reservation aggregate
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::rename('stock_reservations', 'stock_reservation_lines');
|
||||
|
||||
Schema::create('stock_reservations', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->string('status')->default('active');
|
||||
$table->dateTime('expires_at')->nullable();
|
||||
$table->dateTime('committed_at')->nullable();
|
||||
$table->dateTime('released_at')->nullable();
|
||||
$table->dateTime('expired_at')->nullable();
|
||||
$table->string('release_reason')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['status', 'expires_at']);
|
||||
});
|
||||
|
||||
Schema::table('stock_reservation_lines', function (Blueprint $table): void {
|
||||
$table->foreignId('stock_reservation_id')->nullable()->after('id');
|
||||
$table->boolean('tracks_inventory')->default(true)->after('quantity');
|
||||
});
|
||||
|
||||
Schema::table('carritos', function (Blueprint $table): void {
|
||||
$table->foreignId('current_stock_reservation_id')->nullable()->after('current_purchase_id');
|
||||
});
|
||||
|
||||
Schema::table('compras', function (Blueprint $table): void {
|
||||
$table->foreignId('stock_reservation_id')->nullable()->after('cart_id');
|
||||
});
|
||||
|
||||
$cartIdsByItem = DB::table('carrito_items')->pluck('cart_id', 'id');
|
||||
$unlimitedInventoryIds = DB::table('catalog_items')
|
||||
->where('inventory_policy', 'unlimited')
|
||||
->whereNotNull('inventory_id')
|
||||
->pluck('inventory_id')
|
||||
->merge(
|
||||
DB::table('variantes')
|
||||
->join('catalog_items', 'catalog_items.id', '=', 'variantes.catalog_item_id')
|
||||
->where('catalog_items.inventory_policy', 'unlimited')
|
||||
->pluck('variantes.inventory_id'),
|
||||
)
|
||||
->map(fn ($id): int => (int) $id)
|
||||
->unique();
|
||||
$legacyRows = DB::table('stock_reservation_lines')->orderBy('id')->get();
|
||||
$groups = $legacyRows->groupBy(function (object $row) use ($cartIdsByItem): string {
|
||||
if ($row->purchase_id !== null) {
|
||||
return 'purchase:'.$row->purchase_id;
|
||||
}
|
||||
|
||||
$cartId = $row->cart_item_id === null ? null : $cartIdsByItem->get($row->cart_item_id);
|
||||
|
||||
return $cartId === null ? 'legacy:'.$row->id : 'cart:'.$cartId;
|
||||
});
|
||||
|
||||
foreach ($groups as $key => $rows) {
|
||||
$statuses = $rows->pluck('status');
|
||||
$status = $statuses->contains('active')
|
||||
? 'active'
|
||||
: ($statuses->contains('committed')
|
||||
? 'committed'
|
||||
: ($statuses->contains('expired') ? 'expired' : 'released'));
|
||||
$first = $rows->first();
|
||||
$reservationId = DB::table('stock_reservations')->insertGetId([
|
||||
'status' => $status,
|
||||
'expires_at' => $status === 'active' ? $rows->pluck('expires_at')->filter()->max() : null,
|
||||
'committed_at' => $status === 'committed' ? $rows->pluck('committed_at')->filter()->max() : null,
|
||||
'released_at' => $status === 'released' ? $rows->pluck('released_at')->filter()->max() : null,
|
||||
'expired_at' => $status === 'expired' ? $rows->pluck('released_at')->filter()->max() : null,
|
||||
'release_reason' => null,
|
||||
'created_at' => $first->created_at,
|
||||
'updated_at' => $rows->pluck('updated_at')->filter()->max() ?? $first->updated_at,
|
||||
]);
|
||||
|
||||
foreach ($rows->groupBy('inventory_id') as $inventoryRows) {
|
||||
$line = $inventoryRows->first();
|
||||
DB::table('stock_reservation_lines')->where('id', $line->id)->update([
|
||||
'stock_reservation_id' => $reservationId,
|
||||
'quantity' => $inventoryRows->sum('quantity'),
|
||||
'tracks_inventory' => ! $unlimitedInventoryIds->contains((int) $line->inventory_id),
|
||||
]);
|
||||
DB::table('stock_reservation_lines')
|
||||
->whereIn('id', $inventoryRows->pluck('id')->skip(1))
|
||||
->delete();
|
||||
}
|
||||
|
||||
if (str_starts_with($key, 'purchase:')) {
|
||||
$purchaseId = (int) substr($key, strlen('purchase:'));
|
||||
DB::table('compras')->where('id', $purchaseId)->update([
|
||||
'stock_reservation_id' => $reservationId,
|
||||
]);
|
||||
$cartId = DB::table('compras')->where('id', $purchaseId)->value('cart_id');
|
||||
$isCurrent = $cartId !== null
|
||||
&& (int) DB::table('carritos')->where('id', $cartId)->value('current_purchase_id') === $purchaseId;
|
||||
|
||||
if ($status === 'active' && $isCurrent) {
|
||||
DB::table('carritos')->where('id', $cartId)->update([
|
||||
'current_stock_reservation_id' => $reservationId,
|
||||
]);
|
||||
}
|
||||
} elseif (str_starts_with($key, 'cart:') && $status === 'active') {
|
||||
DB::table('carritos')->where('id', (int) substr($key, strlen('cart:')))->update([
|
||||
'current_stock_reservation_id' => $reservationId,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if (DB::getDriverName() !== 'sqlite') {
|
||||
Schema::table('stock_reservation_lines', function (Blueprint $table): void {
|
||||
$table->dropForeign('stock_reservations_cart_item_id_foreign');
|
||||
$table->dropForeign('stock_reservations_purchase_id_foreign');
|
||||
$table->dropUnique('stock_reservations_cart_item_id_inventory_id_unique');
|
||||
$table->dropIndex('stock_reservations_purchase_id_status_index');
|
||||
$table->dropIndex('stock_reservations_status_expires_at_index');
|
||||
});
|
||||
}
|
||||
|
||||
Schema::table('stock_reservation_lines', function (Blueprint $table): void {
|
||||
$table->unsignedBigInteger('stock_reservation_id')->nullable(false)->change();
|
||||
$table->dropColumn([
|
||||
'cart_item_id',
|
||||
'purchase_id',
|
||||
'status',
|
||||
'expires_at',
|
||||
'committed_at',
|
||||
'released_at',
|
||||
]);
|
||||
$table->foreign('stock_reservation_id', 'reservation_lines_reservation_fk')
|
||||
->references('id')
|
||||
->on('stock_reservations')
|
||||
->cascadeOnDelete();
|
||||
$table->unique(
|
||||
['stock_reservation_id', 'inventory_id'],
|
||||
'reservation_lines_reservation_inventory_unique',
|
||||
);
|
||||
});
|
||||
|
||||
Schema::table('carritos', function (Blueprint $table): void {
|
||||
$table->foreign('current_stock_reservation_id', 'carts_current_stock_reservation_fk')
|
||||
->references('id')
|
||||
->on('stock_reservations')
|
||||
->nullOnDelete();
|
||||
$table->unique('current_stock_reservation_id', 'carts_current_stock_reservation_unique');
|
||||
});
|
||||
|
||||
Schema::table('compras', function (Blueprint $table): void {
|
||||
$table->foreign('stock_reservation_id', 'purchases_stock_reservation_fk')
|
||||
->references('id')
|
||||
->on('stock_reservations')
|
||||
->nullOnDelete();
|
||||
$table->unique('stock_reservation_id', 'purchases_stock_reservation_unique');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('compras', function (Blueprint $table): void {
|
||||
$table->dropUnique('purchases_stock_reservation_unique');
|
||||
$table->dropForeign('purchases_stock_reservation_fk');
|
||||
$table->dropColumn('stock_reservation_id');
|
||||
});
|
||||
|
||||
Schema::table('carritos', function (Blueprint $table): void {
|
||||
$table->dropUnique('carts_current_stock_reservation_unique');
|
||||
$table->dropForeign('carts_current_stock_reservation_fk');
|
||||
$table->dropColumn('current_stock_reservation_id');
|
||||
});
|
||||
|
||||
Schema::dropIfExists('stock_reservation_lines');
|
||||
Schema::dropIfExists('stock_reservations');
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user