Squashed commit of the following:
commit1dc4e29c69Author: ncoronel <ncoronel@quo.ar> Date: Wed Aug 19 13:58:03 2026 -0300 refactor(reservations): unify expiration command commit093e894cc3Author: ncoronel <ncoronel@quo.ar> Date: Wed Aug 19 13:48:09 2026 -0300 feat(cart): expire abandoned stock reservations commitfdf0f3328fAuthor: ncoronel <ncoronel@quo.ar> Date: Wed Aug 19 12:53:12 2026 -0300 refactor(stock): implement expiration for stock reservations and add configuration commit8d6bcdcc43Author: ncoronel <ncoronel@quo.ar> Date: Wed Aug 19 12:38:21 2026 -0300 refactor(cart): invalidate payment on actual changes commit3206e293ebAuthor: ncoronel <ncoronel@quo.ar> Date: Wed Aug 19 12:24:48 2026 -0300 refactor(cart): own checkout item editing commitaed99bd05eAuthor: ncoronel <ncoronel@quo.ar> Date: Wed Aug 19 12:14:57 2026 -0300 refactor(checkout): remove legacy purchase item reservations commitf1649e0e4bAuthor: ncoronel <ncoronel@quo.ar> Date: Wed Aug 19 12:06:29 2026 -0300 refactor(checkout): materialize purchase items on confirmation commite6c4b40a37Author: ncoronel <ncoronel@quo.ar> Date: Wed Aug 19 12:06:19 2026 -0300 feat(inventory): add traceable cart stock reservations
This commit is contained in:
@@ -73,6 +73,87 @@ class CartControllerTest extends TestCase
|
||||
'id' => $item->inventory_id,
|
||||
'reserved_stock' => 2,
|
||||
]);
|
||||
$this->assertDatabaseHas('stock_reservations', [
|
||||
'inventory_id' => $item->inventory_id,
|
||||
'quantity' => 2,
|
||||
'status' => 'active',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_it_sets_the_configured_expiration_when_creating_a_stock_reservation(): void
|
||||
{
|
||||
config()->set('catalog.stock_reservation_expiration_minutes', 45);
|
||||
$now = now()->startOfSecond();
|
||||
$this->travelTo($now);
|
||||
|
||||
$tenant = $this->createTenant('acme');
|
||||
$item = $this->createDirectItem($tenant, 10, '49.90');
|
||||
|
||||
$this->postJson('/api/tenants/acme/cart/items', [
|
||||
'catalog_item_id' => $item->id,
|
||||
'cantidad' => 2,
|
||||
])->assertOk();
|
||||
|
||||
$this->assertDatabaseHas('stock_reservations', [
|
||||
'inventory_id' => $item->inventory_id,
|
||||
'quantity' => 2,
|
||||
'status' => 'active',
|
||||
'expires_at' => $now->copy()->addMinutes(45)->toDateTimeString(),
|
||||
]);
|
||||
|
||||
$this->travelBack();
|
||||
}
|
||||
|
||||
public function test_it_expires_abandoned_cart_reservations_and_removes_empty_carts(): void
|
||||
{
|
||||
config()->set('catalog.stock_reservation_expiration_minutes', 30);
|
||||
$tenant = $this->createTenant('acme');
|
||||
$item = $this->createDirectItem($tenant, 10, '49.90');
|
||||
|
||||
$response = $this->postJson('/api/tenants/acme/cart/items', [
|
||||
'catalog_item_id' => $item->id,
|
||||
'cantidad' => 2,
|
||||
])->assertOk();
|
||||
$cartId = $response->json('data.id');
|
||||
$cartItemId = $response->json('data.items.0.id');
|
||||
|
||||
$this->artisan('reservations:expire')
|
||||
->expectsOutput('Expired purchases: 0')
|
||||
->expectsOutput('Expired cart items: 0')
|
||||
->assertSuccessful();
|
||||
|
||||
$this->travel(31)->minutes();
|
||||
|
||||
$this->artisan('reservations:expire')
|
||||
->expectsOutput('Expired purchases: 0')
|
||||
->expectsOutput('Expired cart items: 1')
|
||||
->assertSuccessful();
|
||||
|
||||
$this->assertDatabaseHas('inventories', [
|
||||
'id' => $item->inventory_id,
|
||||
'real_stock' => 10,
|
||||
'reserved_stock' => 0,
|
||||
]);
|
||||
$this->assertDatabaseMissing('carrito_items', ['id' => $cartItemId]);
|
||||
$this->assertSoftDeleted('carritos', [
|
||||
'id' => $cartId,
|
||||
'status' => 'expired',
|
||||
]);
|
||||
$this->assertDatabaseHas('stock_reservations', [
|
||||
'inventory_id' => $item->inventory_id,
|
||||
'cart_item_id' => null,
|
||||
'purchase_id' => null,
|
||||
'quantity' => 0,
|
||||
'status' => 'expired',
|
||||
'expires_at' => null,
|
||||
]);
|
||||
|
||||
$this->artisan('reservations:expire')
|
||||
->expectsOutput('Expired purchases: 0')
|
||||
->expectsOutput('Expired cart items: 0')
|
||||
->assertSuccessful();
|
||||
|
||||
$this->travelBack();
|
||||
}
|
||||
|
||||
public function test_it_filters_item_images_when_the_tenant_disables_them(): void
|
||||
@@ -129,6 +210,12 @@ class CartControllerTest extends TestCase
|
||||
'id' => $variant->inventory_id,
|
||||
'reserved_stock' => 5,
|
||||
]);
|
||||
$this->assertDatabaseHas('stock_reservations', [
|
||||
'cart_item_id' => $response->json('data.items.0.id'),
|
||||
'inventory_id' => $variant->inventory_id,
|
||||
'quantity' => 5,
|
||||
'status' => 'active',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_authenticated_cart_respects_previous_purchases_and_repeated_additions(): void
|
||||
@@ -283,6 +370,12 @@ class CartControllerTest extends TestCase
|
||||
'id' => $variant->inventory_id,
|
||||
'reserved_stock' => 0,
|
||||
]);
|
||||
$this->assertDatabaseHas('stock_reservations', [
|
||||
'cart_item_id' => null,
|
||||
'inventory_id' => $variant->inventory_id,
|
||||
'quantity' => 0,
|
||||
'status' => 'released',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_it_changes_an_item_variant_and_moves_the_stock_reservation(): void
|
||||
@@ -506,7 +599,6 @@ class CartControllerTest extends TestCase
|
||||
'cantidad' => $quantity,
|
||||
'precio_unitario' => $item->precio,
|
||||
'total' => (float) $item->precio * $quantity,
|
||||
'reservation_status' => PurchaseItem::RESERVATION_COMMITTED,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user