feat(cart): enhance item update functionality to support variant changes and improve cart editing policies

This commit is contained in:
2026-08-19 10:46:19 -03:00
parent 014b5bb012
commit 7cea8d3495
17 changed files with 514 additions and 52 deletions

View File

@@ -580,6 +580,182 @@ class StorePurchaseTest extends TestCase
]);
}
public function test_it_changes_a_checkout_item_variant_and_moves_its_reservation(): void
{
$tenant = $this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
$tenant->update(['cart_editing_policy' => 'full']);
$user = User::factory()->create();
$firstVariant = $this->createVariantForTenant('sonder', 10, '50.00');
$secondInventory = Inventory::query()->create(['real_stock' => 10]);
$secondVariant = $firstVariant->catalogItem->variants()->create([
'inventory_id' => $secondInventory->id,
'precio' => '70.00',
]);
$purchase = $this->createCheckoutPurchase($user, 'sonder', $firstVariant, 2);
$itemId = $purchase->items->firstOrFail()->id;
$this->actingAs($user, 'sanctum')
->patchJson("/api/tenants/sonder/compras/{$purchase->id}/items/{$itemId}", [
'variant_id' => $secondVariant->id,
])
->assertOk()
->assertJsonPath('data.items.0.source_variant_id', $secondVariant->id)
->assertJsonPath('data.items.0.quantity', 2)
->assertJsonPath('data.items.0.unit_price', '70.00')
->assertJsonPath('data.items.0.line_total', '140.00')
->assertJsonPath('data.total', '140.00')
->assertJsonPath('data.items.0.variants.0.id', $firstVariant->id)
->assertJsonPath('data.items.0.variants.1.id', $secondVariant->id);
$this->assertDatabaseHas('inventories', [
'id' => $firstVariant->inventory_id,
'reserved_stock' => 0,
]);
$this->assertDatabaseHas('inventories', [
'id' => $secondInventory->id,
'reserved_stock' => 2,
]);
$this->assertDatabaseHas('carrito_items', [
'cart_id' => $purchase->cart_id,
'catalog_item_id' => $firstVariant->catalog_item_id,
'variant_id' => $secondVariant->id,
'cantidad' => 2,
]);
$this->assertDatabaseMissing('carrito_items', [
'cart_id' => $purchase->cart_id,
'variant_id' => $firstVariant->id,
]);
}
public function test_it_rejects_checkout_variant_changes_when_the_policy_does_not_allow_them(): void
{
$tenant = $this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
$tenant->update(['cart_editing_policy' => 'quantity_and_remove']);
$user = User::factory()->create();
$firstVariant = $this->createVariantForTenant('sonder', 10, '50.00');
$secondInventory = Inventory::query()->create(['real_stock' => 10]);
$secondVariant = $firstVariant->catalogItem->variants()->create([
'inventory_id' => $secondInventory->id,
]);
$purchase = $this->createCheckoutPurchase($user, 'sonder', $firstVariant, 2);
$itemId = $purchase->items->firstOrFail()->id;
$this->actingAs($user, 'sanctum')
->patchJson("/api/tenants/sonder/compras/{$purchase->id}/items/{$itemId}", [
'variant_id' => $secondVariant->id,
])
->assertUnprocessable()
->assertJsonValidationErrors('variant_id');
$this->assertDatabaseHas('compra_items', [
'id' => $itemId,
'source_variant_id' => $firstVariant->id,
'cantidad' => 2,
]);
$this->assertDatabaseHas('inventories', [
'id' => $firstVariant->inventory_id,
'reserved_stock' => 2,
]);
$this->assertDatabaseHas('inventories', [
'id' => $secondInventory->id,
'reserved_stock' => 0,
]);
}
public function test_checkout_variant_change_rolls_back_when_the_target_has_insufficient_stock(): void
{
$tenant = $this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
$tenant->update(['cart_editing_policy' => 'full']);
$user = User::factory()->create();
$firstVariant = $this->createVariantForTenant('sonder', 10, '50.00');
$secondInventory = Inventory::query()->create(['real_stock' => 1]);
$secondVariant = $firstVariant->catalogItem->variants()->create([
'inventory_id' => $secondInventory->id,
'precio' => '70.00',
]);
$purchase = $this->createCheckoutPurchase($user, 'sonder', $firstVariant, 2);
$itemId = $purchase->items->firstOrFail()->id;
$this->actingAs($user, 'sanctum')
->patchJson("/api/tenants/sonder/compras/{$purchase->id}/items/{$itemId}", [
'variant_id' => $secondVariant->id,
])
->assertUnprocessable()
->assertJsonValidationErrors('variant_id');
$this->assertDatabaseHas('compra_items', [
'id' => $itemId,
'source_variant_id' => $firstVariant->id,
'cantidad' => 2,
'precio_unitario' => '50.00',
]);
$this->assertDatabaseHas('inventories', [
'id' => $firstVariant->inventory_id,
'reserved_stock' => 2,
]);
$this->assertDatabaseHas('inventories', [
'id' => $secondInventory->id,
'reserved_stock' => 0,
]);
$this->assertDatabaseHas('carrito_items', [
'cart_id' => $purchase->cart_id,
'variant_id' => $firstVariant->id,
'cantidad' => 2,
]);
}
public function test_changing_to_an_existing_checkout_variant_merges_purchase_and_cart_rows(): void
{
$tenant = $this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
$tenant->update(['cart_editing_policy' => 'full']);
$user = User::factory()->create();
$firstVariant = $this->createVariantForTenant('sonder', 10, '50.00');
$secondInventory = Inventory::query()->create(['real_stock' => 10]);
$secondVariant = $firstVariant->catalogItem->variants()->create([
'inventory_id' => $secondInventory->id,
'precio' => '70.00',
]);
$cart = Cart::query()->create([
'tenant_codigo' => 'sonder',
'user_id' => $user->id,
'status' => 'active',
]);
$cart->addItem($firstVariant->catalog_item_id, $firstVariant->id, 2);
$cart->addItem($secondVariant->catalog_item_id, $secondVariant->id, 3);
$purchase = app(CheckoutService::class)->startCheckout($tenant, $user->id, [
'cart_id' => $cart->id,
]);
$sourceItem = $purchase->items->firstWhere('source_variant_id', $firstVariant->id);
$this->assertNotNull($sourceItem);
$this->actingAs($user, 'sanctum')
->patchJson("/api/tenants/sonder/compras/{$purchase->id}/items/{$sourceItem->id}", [
'variant_id' => $secondVariant->id,
])
->assertOk()
->assertJsonCount(1, 'data.items')
->assertJsonPath('data.items.0.source_variant_id', $secondVariant->id)
->assertJsonPath('data.items.0.quantity', 5)
->assertJsonPath('data.items.0.line_total', '350.00')
->assertJsonPath('data.total', '350.00');
$this->assertDatabaseCount('compra_items', 1);
$this->assertDatabaseHas('inventories', [
'id' => $firstVariant->inventory_id,
'reserved_stock' => 0,
]);
$this->assertDatabaseHas('inventories', [
'id' => $secondInventory->id,
'reserved_stock' => 5,
]);
$this->assertDatabaseCount('carrito_items', 1);
$this->assertDatabaseHas('carrito_items', [
'cart_id' => $cart->id,
'variant_id' => $secondVariant->id,
'cantidad' => 5,
]);
}
public function test_it_rejects_a_quantity_update_above_the_user_purchase_limit(): void
{
$this->createTenant('sonder', 'Sonder', 'sonder.com.ar');