refactor(checkout): materialize purchase items on confirmation

This commit is contained in:
2026-08-19 12:06:29 -03:00
parent e6c4b40a37
commit f1649e0e4b
14 changed files with 487 additions and 145 deletions

View File

@@ -30,7 +30,7 @@ class StorePurchaseTest extends TestCase
Queue::fake();
}
public function test_it_creates_an_independent_purchase_snapshot_from_cart(): void
public function test_it_starts_checkout_from_cart_without_materializing_purchase_items(): void
{
$tenant = $this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
$tenant->update([
@@ -90,7 +90,7 @@ class StorePurchaseTest extends TestCase
$response->assertJsonPath('data.email', null);
$response->assertJsonPath('data.tenant_codigo', 'sonder');
$response->assertJsonPath('data.status', Purchase::STATUS_CREATED);
$response->assertJsonPath('data.items_source', 'purchase');
$response->assertJsonPath('data.items_source', 'cart');
$response->assertJsonCount(1, 'data.items');
$response->assertJsonPath('data.subtotal', '100.00');
$response->assertJsonPath('data.total', '100.00');
@@ -109,12 +109,12 @@ class StorePurchaseTest extends TestCase
'status' => Purchase::STATUS_CREATED,
'total' => 100,
]);
$this->assertDatabaseHas('compra_items', [
'compra_id' => $purchaseId,
'source_catalog_item_id' => $catalogItem->id,
'source_variant_id' => $variant->id,
'cantidad' => 2,
'reservation_status' => 'active',
$this->assertDatabaseMissing('compra_items', ['compra_id' => $purchaseId]);
$this->assertDatabaseHas('stock_reservations', [
'inventory_id' => $inventory->id,
'purchase_id' => $purchaseId,
'quantity' => 2,
'status' => 'active',
]);
$this->assertDatabaseHas('carritos', [
'id' => $cartId,
@@ -133,9 +133,22 @@ class StorePurchaseTest extends TestCase
'real_stock' => 10,
'reserved_stock' => 2,
]);
$catalogItem->update([
'nombre' => 'Updated Product',
'precio' => '75.00',
]);
$this->actingAs($user, 'sanctum')
->getJson("/api/tenants/sonder/compras/{$purchaseId}")
->assertOk()
->assertJsonPath('data.items_source', 'cart')
->assertJsonPath('data.items.0.item_details.nombre', 'Updated Product')
->assertJsonPath('data.items.0.unit_price', '75.00')
->assertJsonPath('data.total', '150.00');
}
public function test_it_creates_a_direct_purchase_without_creating_or_changing_a_cart(): void
public function test_it_creates_a_direct_purchase_with_a_technical_checkout_cart(): void
{
$this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
$user = User::factory()->create();
@@ -152,18 +165,23 @@ class StorePurchaseTest extends TestCase
],
])
->assertCreated()
->assertJsonPath('data.cart_id', null)
->assertJsonPath('data.items_source', 'purchase')
->assertJsonPath('data.items_source', 'cart')
->assertJsonPath('data.items.0.quantity', 3)
->assertJsonPath('data.total', '150.00');
$this->assertDatabaseCount('carritos', 0);
$this->assertDatabaseHas('compra_items', [
$this->assertDatabaseHas('carritos', [
'id' => $response->json('data.cart_id'),
'status' => 'checkout',
'origin' => Cart::ORIGIN_DIRECT_CHECKOUT,
]);
$this->assertDatabaseMissing('compra_items', [
'compra_id' => $response->json('data.id'),
'source_catalog_item_id' => $variant->catalog_item_id,
'source_variant_id' => $variant->id,
'cantidad' => 3,
'reservation_status' => 'active',
]);
$this->assertDatabaseHas('stock_reservations', [
'inventory_id' => $variant->inventory_id,
'purchase_id' => $response->json('data.id'),
'quantity' => 3,
'status' => 'active',
]);
$this->assertDatabaseHas('inventories', [
'id' => $variant->inventory_id,
@@ -176,9 +194,9 @@ class StorePurchaseTest extends TestCase
->assertOk()
->assertJsonPath('data.status', Purchase::STATUS_CANCELLED);
$this->assertDatabaseHas('compra_items', [
'compra_id' => $response->json('data.id'),
'reservation_status' => 'released',
$this->assertDatabaseHas('stock_reservations', [
'purchase_id' => $response->json('data.id'),
'status' => 'released',
]);
$this->assertDatabaseHas('inventories', [
'id' => $variant->inventory_id,
@@ -215,7 +233,7 @@ class StorePurchaseTest extends TestCase
],
])
->assertCreated()
->assertJsonPath('data.cart_id', null)
->assertJsonPath('data.items_source', 'cart')
->assertJsonCount(2, 'data.items')
->assertJsonPath('data.items.0.source_variant_id', $firstVariant->id)
->assertJsonPath('data.items.1.source_variant_id', $secondVariant->id)
@@ -223,18 +241,22 @@ class StorePurchaseTest extends TestCase
$purchaseId = $response->json('data.id');
$this->assertDatabaseCount('carritos', 0);
$this->assertDatabaseHas('compra_items', [
'compra_id' => $purchaseId,
'source_variant_id' => $firstVariant->id,
'cantidad' => 1,
'reservation_status' => 'active',
$this->assertDatabaseHas('carritos', [
'id' => $response->json('data.cart_id'),
'origin' => Cart::ORIGIN_DIRECT_CHECKOUT,
]);
$this->assertDatabaseHas('compra_items', [
'compra_id' => $purchaseId,
'source_variant_id' => $secondVariant->id,
'cantidad' => 1,
'reservation_status' => 'active',
$this->assertDatabaseMissing('compra_items', ['compra_id' => $purchaseId]);
$this->assertDatabaseHas('stock_reservations', [
'inventory_id' => $firstVariant->inventory_id,
'purchase_id' => $purchaseId,
'quantity' => 1,
'status' => 'active',
]);
$this->assertDatabaseHas('stock_reservations', [
'inventory_id' => $secondVariant->inventory_id,
'purchase_id' => $purchaseId,
'quantity' => 1,
'status' => 'active',
]);
$this->assertDatabaseHas('inventories', [
'id' => $firstVariant->inventory_id,
@@ -462,7 +484,7 @@ class StorePurchaseTest extends TestCase
]);
}
public function test_it_creates_purchase_items_before_checkout_and_updates_customer_data(): void
public function test_it_keeps_cart_items_during_checkout_and_updates_customer_data(): void
{
$this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
$user = User::factory()->create();
@@ -479,7 +501,7 @@ class StorePurchaseTest extends TestCase
],
])
->assertCreated()
->assertJsonPath('data.items_source', 'purchase')
->assertJsonPath('data.items_source', 'cart')
->assertJsonCount(1, 'data.items')
->assertJsonPath('data.items.0.quantity', 2)
->assertJsonPath('data.dni', null)
@@ -543,7 +565,7 @@ class StorePurchaseTest extends TestCase
$user = User::factory()->create();
$variant = $this->createVariantForTenant('sonder', 10, '50.00');
$purchase = $this->createCheckoutPurchase($user, 'sonder', $variant, 2);
$itemId = $purchase->items->firstOrFail()->id;
$itemId = $purchase->cart->items->firstOrFail()->id;
$this->actingAs($user, 'sanctum')
->patchJson("/api/tenants/sonder/compras/{$purchase->id}/items/{$itemId}", [
@@ -587,7 +609,7 @@ class StorePurchaseTest extends TestCase
$variant = $this->createVariantForTenant('sonder', 10, '50.00');
$variant->catalogItem->update(['max_units_per_user' => 3]);
$purchase = $this->createCheckoutPurchase($user, 'sonder', $variant, 2);
$itemId = $purchase->items->firstOrFail()->id;
$itemId = $purchase->cart->items->firstOrFail()->id;
$this->actingAs($user, 'sanctum')
->patchJson("/api/tenants/sonder/compras/{$purchase->id}/items/{$itemId}", [
@@ -596,7 +618,7 @@ class StorePurchaseTest extends TestCase
->assertUnprocessable()
->assertJsonValidationErrors('quantity');
$this->assertDatabaseHas('compra_items', [
$this->assertDatabaseHas('carrito_items', [
'id' => $itemId,
'cantidad' => 2,
]);
@@ -797,9 +819,11 @@ class StorePurchaseTest extends TestCase
'id' => $purchase->id,
'status' => Purchase::STATUS_EXPIRED,
]);
$this->assertDatabaseHas('compra_items', [
'compra_id' => $purchase->id,
'reservation_status' => 'released',
$this->assertDatabaseMissing('compra_items', ['compra_id' => $purchase->id]);
$this->assertDatabaseHas('stock_reservations', [
'purchase_id' => null,
'quantity' => 3,
'status' => 'active',
]);
$this->assertDatabaseHas('inventories', [
'id' => $variant->inventory_id,
@@ -819,7 +843,7 @@ class StorePurchaseTest extends TestCase
->assertSuccessful();
}
public function test_purchase_detail_uses_purchase_items_for_created_purchase(): void
public function test_purchase_detail_uses_cart_items_for_created_purchase(): void
{
$this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
$user = User::factory()->create([
@@ -832,7 +856,7 @@ class StorePurchaseTest extends TestCase
->getJson("/api/tenants/sonder/compras/{$purchase->id}")
->assertOk()
->assertJsonPath('data.status', Purchase::STATUS_CREATED)
->assertJsonPath('data.items_source', 'purchase')
->assertJsonPath('data.items_source', 'cart')
->assertJsonCount(1, 'data.items')
->assertJsonPath('data.items.0.quantity', 2)
->assertJsonPath('data.items.0.unit_price', '50.00')
@@ -863,7 +887,7 @@ class StorePurchaseTest extends TestCase
'type' => AttachmentType::Image,
'mime_type' => 'image/png',
]);
$purchase->items()->firstOrFail()->update(['image_attachment_id' => $image->id]);
$variant->catalogItem->attachments()->attach($image->id, ['orden' => 0]);
$this->actingAs($user, 'sanctum')
->getJson("/api/tenants/sonder/compras/{$purchase->id}")
@@ -871,7 +895,7 @@ class StorePurchaseTest extends TestCase
->assertJsonPath('data.items.0.item_details.imagen', null);
}
public function test_purchase_detail_uses_purchase_items_for_pending_payment_purchase(): void
public function test_purchase_detail_uses_cart_items_for_pending_payment_purchase(): void
{
$this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
$user = User::factory()->create([
@@ -890,7 +914,7 @@ class StorePurchaseTest extends TestCase
->getJson("/api/tenants/sonder/compras/{$purchase->id}")
->assertOk()
->assertJsonPath('data.status', Purchase::STATUS_PENDING_PAYMENT)
->assertJsonPath('data.items_source', 'purchase')
->assertJsonPath('data.items_source', 'cart')
->assertJsonCount(1, 'data.items')
->assertJsonPath('data.items.0.quantity', 2)
->assertJsonPath('data.items.0.unit_price', '50.00')
@@ -925,6 +949,12 @@ class StorePurchaseTest extends TestCase
'reserved_stock' => 0,
'sold_units' => 2,
]);
$this->assertDatabaseHas('stock_reservations', [
'purchase_id' => $purchase->id,
'inventory_id' => $variant->inventory_id,
'quantity' => 2,
'status' => 'committed',
]);
$this->assertSoftDeleted('carritos', [
'id' => $purchase->cart_id,
@@ -956,7 +986,9 @@ class StorePurchaseTest extends TestCase
$variant = $this->createVariantForTenant('sonder', 10, '50.00');
$purchase = $this->createCheckoutPurchase($user, 'sonder', $variant, 2);
$purchase->update(['cart_id' => null]);
$purchase->update(['payment_method' => 'transfer']);
app(CheckoutService::class)->confirmPurchase($purchase);
$purchase->refresh()->update(['cart_id' => null]);
$this->actingAs($user, 'sanctum')
->getJson("/api/tenants/sonder/compras/{$purchase->id}")