refactor(checkout): disable purchase item editing
This commit is contained in:
@@ -194,8 +194,9 @@ class TelepagosWebhookTest extends TestCase
|
||||
'sold_units' => 1,
|
||||
]);
|
||||
|
||||
$this->assertDatabaseMissing('compra_items', [
|
||||
$this->assertDatabaseHas('compra_items', [
|
||||
'compra_id' => $newerPurchase->id,
|
||||
'cantidad' => 2,
|
||||
]);
|
||||
$this->assertDatabaseHas('stock_reservations', [
|
||||
'purchase_id' => $newerPurchase->id,
|
||||
|
||||
@@ -30,7 +30,7 @@ class StorePurchaseTest extends TestCase
|
||||
Queue::fake();
|
||||
}
|
||||
|
||||
public function test_it_starts_checkout_from_cart_without_materializing_purchase_items(): void
|
||||
public function test_it_starts_checkout_from_cart_with_purchase_item_snapshots(): 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', 'cart');
|
||||
$response->assertJsonPath('data.items_source', 'purchase');
|
||||
$response->assertJsonCount(1, 'data.items');
|
||||
$response->assertJsonPath('data.subtotal', '100.00');
|
||||
$response->assertJsonPath('data.total', '100.00');
|
||||
@@ -109,7 +109,14 @@ class StorePurchaseTest extends TestCase
|
||||
'status' => Purchase::STATUS_CREATED,
|
||||
'total' => 100,
|
||||
]);
|
||||
$this->assertDatabaseMissing('compra_items', ['compra_id' => $purchaseId]);
|
||||
$this->assertDatabaseHas('compra_items', [
|
||||
'compra_id' => $purchaseId,
|
||||
'source_catalog_item_id' => $catalogItem->id,
|
||||
'source_variant_id' => $variant->id,
|
||||
'cantidad' => 2,
|
||||
'precio_unitario' => '50.00',
|
||||
'total' => '100.00',
|
||||
]);
|
||||
$this->assertDatabaseHas('stock_reservations', [
|
||||
'inventory_id' => $inventory->id,
|
||||
'purchase_id' => $purchaseId,
|
||||
@@ -142,10 +149,10 @@ class StorePurchaseTest extends TestCase
|
||||
$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');
|
||||
->assertJsonPath('data.items_source', 'purchase')
|
||||
->assertJsonPath('data.items.0.item_details.nombre', 'Test Product')
|
||||
->assertJsonPath('data.items.0.unit_price', '50.00')
|
||||
->assertJsonPath('data.total', '100.00');
|
||||
}
|
||||
|
||||
public function test_it_creates_a_direct_purchase_with_a_technical_checkout_cart(): void
|
||||
@@ -165,7 +172,7 @@ class StorePurchaseTest extends TestCase
|
||||
],
|
||||
])
|
||||
->assertCreated()
|
||||
->assertJsonPath('data.items_source', 'cart')
|
||||
->assertJsonPath('data.items_source', 'purchase')
|
||||
->assertJsonPath('data.items.0.quantity', 3)
|
||||
->assertJsonPath('data.total', '150.00');
|
||||
|
||||
@@ -174,8 +181,12 @@ class StorePurchaseTest extends TestCase
|
||||
'status' => 'checkout',
|
||||
'origin' => Cart::ORIGIN_DIRECT_CHECKOUT,
|
||||
]);
|
||||
$this->assertDatabaseMissing('compra_items', [
|
||||
$this->assertDatabaseHas('compra_items', [
|
||||
'compra_id' => $response->json('data.id'),
|
||||
'source_variant_id' => $variant->id,
|
||||
'cantidad' => 3,
|
||||
'precio_unitario' => '50.00',
|
||||
'total' => '150.00',
|
||||
]);
|
||||
$this->assertDatabaseHas('stock_reservations', [
|
||||
'inventory_id' => $variant->inventory_id,
|
||||
@@ -233,7 +244,7 @@ class StorePurchaseTest extends TestCase
|
||||
],
|
||||
])
|
||||
->assertCreated()
|
||||
->assertJsonPath('data.items_source', 'cart')
|
||||
->assertJsonPath('data.items_source', 'purchase')
|
||||
->assertJsonCount(2, 'data.items')
|
||||
->assertJsonPath('data.items.0.source_variant_id', $firstVariant->id)
|
||||
->assertJsonPath('data.items.1.source_variant_id', $secondVariant->id)
|
||||
@@ -245,7 +256,7 @@ class StorePurchaseTest extends TestCase
|
||||
'id' => $response->json('data.cart_id'),
|
||||
'origin' => Cart::ORIGIN_DIRECT_CHECKOUT,
|
||||
]);
|
||||
$this->assertDatabaseMissing('compra_items', ['compra_id' => $purchaseId]);
|
||||
$this->assertDatabaseCount('compra_items', 2);
|
||||
$this->assertDatabaseHas('stock_reservations', [
|
||||
'inventory_id' => $firstVariant->inventory_id,
|
||||
'purchase_id' => $purchaseId,
|
||||
@@ -541,7 +552,7 @@ class StorePurchaseTest extends TestCase
|
||||
],
|
||||
])
|
||||
->assertCreated()
|
||||
->assertJsonPath('data.items_source', 'cart')
|
||||
->assertJsonPath('data.items_source', 'purchase')
|
||||
->assertJsonCount(1, 'data.items')
|
||||
->assertJsonPath('data.items.0.quantity', 2)
|
||||
->assertJsonPath('data.dni', null)
|
||||
@@ -599,373 +610,40 @@ class StorePurchaseTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_it_updates_a_checkout_cart_item_quantity_and_its_stock_reservation(): void
|
||||
{
|
||||
$tenant = $this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
|
||||
$tenant->update(['checkout_editing_policy' => 'full']);
|
||||
$user = User::factory()->create();
|
||||
$variant = $this->createVariantForTenant('sonder', 10, '50.00');
|
||||
$purchase = $this->createCheckoutPurchase($user, 'sonder', $variant, 2);
|
||||
$itemId = $purchase->cart->items->firstOrFail()->id;
|
||||
|
||||
$this->actingAs($user, 'sanctum')
|
||||
->patchJson("/api/tenants/sonder/checkout-carts/{$purchase->cart_id}/items/{$itemId}", [
|
||||
'cantidad' => 4,
|
||||
])
|
||||
->assertOk()
|
||||
->assertJsonPath('data.items.0.cantidad', 4)
|
||||
->assertJsonPath('data.subtotal', '200.00');
|
||||
|
||||
$this->assertDatabaseHas('inventories', [
|
||||
'id' => $variant->inventory_id,
|
||||
'reserved_stock' => 4,
|
||||
]);
|
||||
$this->assertDatabaseHas('carrito_items', [
|
||||
'cart_id' => $purchase->cart_id,
|
||||
'catalog_item_id' => $variant->catalog_item_id,
|
||||
'variant_id' => $variant->id,
|
||||
'cantidad' => 4,
|
||||
]);
|
||||
|
||||
$this->actingAs($user, 'sanctum')
|
||||
->patchJson("/api/tenants/sonder/checkout-carts/{$purchase->cart_id}/items/{$itemId}", [
|
||||
'cantidad' => 1,
|
||||
])
|
||||
->assertOk()
|
||||
->assertJsonPath('data.items.0.cantidad', 1)
|
||||
->assertJsonPath('data.subtotal', '50.00');
|
||||
|
||||
$this->assertDatabaseHas('compras', [
|
||||
'id' => $purchase->id,
|
||||
'total' => '50.00',
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('inventories', [
|
||||
'id' => $variant->inventory_id,
|
||||
'reserved_stock' => 1,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_checkout_cart_editing_uses_checkout_policy_independently_from_cart_policy(): void
|
||||
{
|
||||
$tenant = $this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
|
||||
$tenant->update([
|
||||
'cart_editing_policy' => 'full',
|
||||
'checkout_editing_policy' => 'disabled',
|
||||
]);
|
||||
$user = User::factory()->create();
|
||||
$variant = $this->createVariantForTenant('sonder', 10, '50.00');
|
||||
$purchase = $this->createCheckoutPurchase($user, 'sonder', $variant, 2);
|
||||
$itemId = $purchase->cart->items->firstOrFail()->id;
|
||||
|
||||
$this->actingAs($user, 'sanctum')
|
||||
->patchJson("/api/tenants/sonder/checkout-carts/{$purchase->cart_id}/items/{$itemId}", [
|
||||
'cantidad' => 4,
|
||||
])
|
||||
->assertUnprocessable()
|
||||
->assertJsonValidationErrors('cantidad');
|
||||
|
||||
$this->assertDatabaseHas('carrito_items', [
|
||||
'id' => $itemId,
|
||||
'cantidad' => 2,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_it_removes_an_item_from_the_checkout_cart_when_the_policy_allows_it(): void
|
||||
{
|
||||
$tenant = $this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
|
||||
$tenant->update(['checkout_editing_policy' => 'quantity_and_remove']);
|
||||
$user = User::factory()->create();
|
||||
$variant = $this->createVariantForTenant('sonder', 10, '50.00');
|
||||
$purchase = $this->createCheckoutPurchase($user, 'sonder', $variant, 2);
|
||||
$itemId = $purchase->cart->items->firstOrFail()->id;
|
||||
|
||||
$this->actingAs($user, 'sanctum')
|
||||
->deleteJson("/api/tenants/sonder/checkout-carts/{$purchase->cart_id}/items/{$itemId}")
|
||||
->assertOk()
|
||||
->assertJsonPath('data.items', [])
|
||||
->assertJsonPath('data.subtotal', '0.00');
|
||||
|
||||
$this->assertDatabaseMissing('carrito_items', ['id' => $itemId]);
|
||||
$this->assertDatabaseHas('compras', [
|
||||
'id' => $purchase->id,
|
||||
'total' => '0.00',
|
||||
]);
|
||||
$this->assertDatabaseHas('inventories', [
|
||||
'id' => $variant->inventory_id,
|
||||
'reserved_stock' => 0,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_it_changes_a_checkout_item_variant_and_moves_its_reservation(): void
|
||||
{
|
||||
$tenant = $this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
|
||||
$tenant->update(['checkout_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(['checkout_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(['checkout_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(['checkout_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
|
||||
{
|
||||
$tenant = $this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
|
||||
$tenant->update(['checkout_editing_policy' => 'full']);
|
||||
$user = User::factory()->create();
|
||||
$variant = $this->createVariantForTenant('sonder', 10, '50.00');
|
||||
$variant->catalogItem->update(['max_units_per_user' => 3]);
|
||||
$purchase = $this->createCheckoutPurchase($user, 'sonder', $variant, 2);
|
||||
$itemId = $purchase->cart->items->firstOrFail()->id;
|
||||
|
||||
$this->actingAs($user, 'sanctum')
|
||||
->patchJson("/api/tenants/sonder/checkout-carts/{$purchase->cart_id}/items/{$itemId}", [
|
||||
'cantidad' => 4,
|
||||
])
|
||||
->assertUnprocessable()
|
||||
->assertJsonValidationErrors('cantidad');
|
||||
|
||||
$this->assertDatabaseHas('carrito_items', [
|
||||
'id' => $itemId,
|
||||
'cantidad' => 2,
|
||||
]);
|
||||
$this->assertDatabaseHas('inventories', [
|
||||
'id' => $variant->inventory_id,
|
||||
'reserved_stock' => 2,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_it_invalidates_a_pending_payment_when_the_checkout_cart_changes(): void
|
||||
{
|
||||
$tenant = $this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
|
||||
$tenant->update(['checkout_editing_policy' => 'full']);
|
||||
$user = User::factory()->create();
|
||||
$variant = $this->createVariantForTenant('sonder', 10, '50.00');
|
||||
$purchase = $this->createCheckoutPurchase($user, 'sonder', $variant, 2);
|
||||
$purchase->update([
|
||||
'status' => Purchase::STATUS_PENDING_PAYMENT,
|
||||
'payment_method' => 'qr',
|
||||
]);
|
||||
$purchase->telepagosQr()->create([
|
||||
'qr_order_id' => 'stale-order',
|
||||
'qr_code' => 'stale-qr',
|
||||
]);
|
||||
$itemId = $purchase->cart->items->firstOrFail()->id;
|
||||
|
||||
$this->actingAs($user, 'sanctum')
|
||||
->patchJson("/api/tenants/sonder/checkout-carts/{$purchase->cart_id}/items/{$itemId}", [
|
||||
'cantidad' => 3,
|
||||
])
|
||||
->assertOk()
|
||||
->assertJsonPath('data.items.0.cantidad', 3);
|
||||
|
||||
$this->assertDatabaseHas('compras', [
|
||||
'id' => $purchase->id,
|
||||
'status' => Purchase::STATUS_CREATED,
|
||||
'payment_method' => null,
|
||||
'total' => '150.00',
|
||||
]);
|
||||
$this->assertDatabaseMissing('telepagos_qr', [
|
||||
'compra_id' => $purchase->id,
|
||||
'qr_order_id' => 'stale-order',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_a_no_op_checkout_cart_update_keeps_the_pending_payment_intact(): void
|
||||
public function test_checkout_items_are_immutable_and_editing_routes_are_unavailable(): void
|
||||
{
|
||||
$this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
|
||||
$user = User::factory()->create();
|
||||
$variant = $this->createVariantForTenant('sonder', 10, '50.00');
|
||||
$purchase = $this->createCheckoutPurchase($user, 'sonder', $variant, 2);
|
||||
$purchase->update([
|
||||
'status' => Purchase::STATUS_PENDING_PAYMENT,
|
||||
'payment_method' => 'qr',
|
||||
]);
|
||||
$purchase->telepagosQr()->create([
|
||||
'qr_order_id' => 'current-order',
|
||||
'qr_code' => 'current-qr',
|
||||
]);
|
||||
$itemId = $purchase->cart->items->firstOrFail()->id;
|
||||
$cartItemId = $purchase->cart->items->firstOrFail()->id;
|
||||
$purchaseItemId = $purchase->items->firstOrFail()->id;
|
||||
|
||||
$this->actingAs($user, 'sanctum')
|
||||
->patchJson("/api/tenants/sonder/checkout-carts/{$purchase->cart_id}/items/{$itemId}", [
|
||||
'cantidad' => 2,
|
||||
->patchJson("/api/tenants/sonder/checkout-carts/{$purchase->cart_id}/items/{$cartItemId}", [
|
||||
'cantidad' => 3,
|
||||
])
|
||||
->assertOk();
|
||||
->assertNotFound();
|
||||
|
||||
$this->assertDatabaseHas('compras', [
|
||||
'id' => $purchase->id,
|
||||
'status' => Purchase::STATUS_PENDING_PAYMENT,
|
||||
'payment_method' => 'qr',
|
||||
$this->patchJson("/api/tenants/sonder/compras/{$purchase->id}/items/{$purchaseItemId}", [
|
||||
'quantity' => 3,
|
||||
])->assertNotFound();
|
||||
|
||||
$this->deleteJson("/api/tenants/sonder/compras/{$purchase->id}/items/{$purchaseItemId}")
|
||||
->assertNotFound();
|
||||
|
||||
$this->postJson("/api/tenants/sonder/compras/{$purchase->id}/edit-items")
|
||||
->assertNotFound();
|
||||
|
||||
$this->assertDatabaseHas('compra_items', [
|
||||
'id' => $purchaseItemId,
|
||||
'cantidad' => 2,
|
||||
'precio_unitario' => '50.00',
|
||||
'total' => '100.00',
|
||||
]);
|
||||
$this->assertDatabaseHas('telepagos_qr', [
|
||||
'compra_id' => $purchase->id,
|
||||
'qr_order_id' => 'current-order',
|
||||
$this->assertDatabaseHas('inventories', [
|
||||
'id' => $variant->inventory_id,
|
||||
'reserved_stock' => 2,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -1129,7 +807,10 @@ class StorePurchaseTest extends TestCase
|
||||
'id' => $purchase->id,
|
||||
'status' => Purchase::STATUS_EXPIRED,
|
||||
]);
|
||||
$this->assertDatabaseMissing('compra_items', ['compra_id' => $purchase->id]);
|
||||
$this->assertDatabaseHas('compra_items', [
|
||||
'compra_id' => $purchase->id,
|
||||
'cantidad' => 3,
|
||||
]);
|
||||
$this->assertDatabaseHas('stock_reservations', [
|
||||
'purchase_id' => $purchase->id,
|
||||
'quantity' => 0,
|
||||
@@ -1153,7 +834,7 @@ class StorePurchaseTest extends TestCase
|
||||
->assertSuccessful();
|
||||
}
|
||||
|
||||
public function test_it_continues_expiring_purchases_after_an_inconsistent_reservation(): void
|
||||
public function test_it_expires_multiple_purchases_that_already_have_item_snapshots(): void
|
||||
{
|
||||
$this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
|
||||
$user = User::factory()->create();
|
||||
@@ -1176,13 +857,13 @@ class StorePurchaseTest extends TestCase
|
||||
$this->travel(31)->minutes();
|
||||
|
||||
$this->artisan('reservations:expire')
|
||||
->expectsOutput('Expired purchases: 1')
|
||||
->expectsOutput('Expired purchases: 2')
|
||||
->expectsOutput('Expired cart items: 0')
|
||||
->assertSuccessful();
|
||||
|
||||
$this->assertDatabaseHas('compras', [
|
||||
'id' => $inconsistentPurchase->id,
|
||||
'status' => Purchase::STATUS_CREATED,
|
||||
'status' => Purchase::STATUS_EXPIRED,
|
||||
]);
|
||||
$this->assertDatabaseHas('compras', [
|
||||
'id' => $validPurchase->id,
|
||||
@@ -1190,7 +871,7 @@ class StorePurchaseTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_purchase_detail_uses_cart_items_for_created_purchase(): void
|
||||
public function test_purchase_detail_uses_purchase_item_snapshot_for_created_purchase(): void
|
||||
{
|
||||
$this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
|
||||
$user = User::factory()->create([
|
||||
@@ -1203,7 +884,7 @@ class StorePurchaseTest extends TestCase
|
||||
->getJson("/api/tenants/sonder/compras/{$purchase->id}")
|
||||
->assertOk()
|
||||
->assertJsonPath('data.status', Purchase::STATUS_CREATED)
|
||||
->assertJsonPath('data.items_source', 'cart')
|
||||
->assertJsonPath('data.items_source', 'purchase')
|
||||
->assertJsonCount(1, 'data.items')
|
||||
->assertJsonPath('data.items.0.quantity', 2)
|
||||
->assertJsonPath('data.items.0.unit_price', '50.00')
|
||||
@@ -1242,7 +923,7 @@ class StorePurchaseTest extends TestCase
|
||||
->assertJsonPath('data.items.0.item_details.imagen', null);
|
||||
}
|
||||
|
||||
public function test_purchase_detail_uses_cart_items_for_pending_payment_purchase(): void
|
||||
public function test_purchase_detail_uses_purchase_item_snapshot_for_pending_payment_purchase(): void
|
||||
{
|
||||
$this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
|
||||
$user = User::factory()->create([
|
||||
@@ -1261,7 +942,7 @@ class StorePurchaseTest extends TestCase
|
||||
->getJson("/api/tenants/sonder/compras/{$purchase->id}")
|
||||
->assertOk()
|
||||
->assertJsonPath('data.status', Purchase::STATUS_PENDING_PAYMENT)
|
||||
->assertJsonPath('data.items_source', 'cart')
|
||||
->assertJsonPath('data.items_source', 'purchase')
|
||||
->assertJsonCount(1, 'data.items')
|
||||
->assertJsonPath('data.items.0.quantity', 2)
|
||||
->assertJsonPath('data.items.0.unit_price', '50.00')
|
||||
@@ -1272,7 +953,7 @@ class StorePurchaseTest extends TestCase
|
||||
->assertJsonPath('data.total', '100.00');
|
||||
}
|
||||
|
||||
public function test_created_and_pending_payment_purchase_details_use_the_current_cart_quantity(): void
|
||||
public function test_created_and_pending_payment_purchase_details_ignore_later_cart_changes(): void
|
||||
{
|
||||
$this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
|
||||
$user = User::factory()->create([
|
||||
@@ -1282,17 +963,6 @@ class StorePurchaseTest extends TestCase
|
||||
|
||||
foreach ([Purchase::STATUS_CREATED, Purchase::STATUS_PENDING_PAYMENT] as $status) {
|
||||
$purchase = $this->createCheckoutPurchase($user, 'sonder', $variant, 2);
|
||||
$purchase->items()->create([
|
||||
'source_catalog_item_id' => $variant->catalog_item_id,
|
||||
'source_variant_id' => $variant->id,
|
||||
'item_nombre' => $variant->catalogItem->nombre,
|
||||
'descripcion' => $variant->catalogItem->descripcion,
|
||||
'slug' => $variant->catalogItem->slug,
|
||||
'variant_attributes' => [],
|
||||
'cantidad' => 1,
|
||||
'precio_unitario' => '50.00',
|
||||
'total' => '50.00',
|
||||
]);
|
||||
$purchase->cart->items()->update(['cantidad' => 3]);
|
||||
$purchase->update(['status' => $status]);
|
||||
|
||||
@@ -1300,11 +970,11 @@ class StorePurchaseTest extends TestCase
|
||||
->getJson("/api/tenants/sonder/compras/{$purchase->id}")
|
||||
->assertOk()
|
||||
->assertJsonPath('data.status', $status)
|
||||
->assertJsonPath('data.items_source', 'cart')
|
||||
->assertJsonPath('data.items.0.quantity', 3)
|
||||
->assertJsonPath('data.items.0.line_total', '150.00')
|
||||
->assertJsonPath('data.subtotal', '150.00')
|
||||
->assertJsonPath('data.total', '150.00');
|
||||
->assertJsonPath('data.items_source', 'purchase')
|
||||
->assertJsonPath('data.items.0.quantity', 2)
|
||||
->assertJsonPath('data.items.0.line_total', '100.00')
|
||||
->assertJsonPath('data.subtotal', '100.00')
|
||||
->assertJsonPath('data.total', '100.00');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,9 +7,12 @@ use App\Domains\Authorization\Enums\RoleCode;
|
||||
use App\Domains\Cart\Models\Cart;
|
||||
use App\Domains\Cart\Models\CartItem;
|
||||
use App\Domains\Catalog\Models\CatalogItem;
|
||||
use App\Domains\Catalog\Models\Inventory;
|
||||
use App\Domains\Catalog\Models\Variant;
|
||||
use App\Domains\Purchase\Events\PurchasePaid;
|
||||
use App\Domains\Purchase\Models\Purchase;
|
||||
use App\Domains\Purchase\Models\PurchaseItem;
|
||||
use App\Domains\Purchase\Services\CheckoutService;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use App\Domains\Tenant\Models\WebsiteType;
|
||||
use App\Domains\Ticket\Models\Ticket;
|
||||
@@ -31,7 +34,7 @@ class AdminAppSaleControllerTest extends TestCase
|
||||
WebsiteType::query()->create(['codigo' => 'onticket', 'nombre' => 'OnTicket']);
|
||||
}
|
||||
|
||||
public function test_sales_list_uses_cart_quantity_for_created_and_pending_payment_sales(): void
|
||||
public function test_sales_list_uses_purchase_item_snapshots_for_every_status(): void
|
||||
{
|
||||
$tenant = $this->createTenant('acme');
|
||||
Sanctum::actingAs($this->createAdminAppUser($tenant));
|
||||
@@ -62,9 +65,9 @@ class AdminAppSaleControllerTest extends TestCase
|
||||
'compra_id' => $createdPurchase->id,
|
||||
'source_catalog_item_id' => $catalogItem->id,
|
||||
'item_nombre' => $catalogItem->nombre,
|
||||
'cantidad' => 1,
|
||||
'cantidad' => 3,
|
||||
'precio_unitario' => '10000.00',
|
||||
'total' => '10000.00',
|
||||
'total' => '30000.00',
|
||||
]);
|
||||
|
||||
$pendingCart = Cart::query()->create([
|
||||
@@ -82,6 +85,14 @@ class AdminAppSaleControllerTest extends TestCase
|
||||
'status' => Purchase::STATUS_PENDING_PAYMENT,
|
||||
'total' => '40000.00',
|
||||
]);
|
||||
PurchaseItem::query()->create([
|
||||
'compra_id' => $pendingPurchase->id,
|
||||
'source_catalog_item_id' => $catalogItem->id,
|
||||
'item_nombre' => $catalogItem->nombre,
|
||||
'cantidad' => 4,
|
||||
'precio_unitario' => '10000.00',
|
||||
'total' => '40000.00',
|
||||
]);
|
||||
|
||||
$paidPurchase = Purchase::query()->create([
|
||||
'tenant_codigo' => $tenant->codigo,
|
||||
@@ -149,7 +160,7 @@ class AdminAppSaleControllerTest extends TestCase
|
||||
->assertJsonPath('data.total', '40000.00');
|
||||
}
|
||||
|
||||
public function test_an_adminapp_user_can_read_cart_items_from_an_unconfirmed_sale(): void
|
||||
public function test_an_adminapp_user_can_read_purchase_item_snapshots_from_an_unconfirmed_sale(): void
|
||||
{
|
||||
$tenant = $this->createTenant('acme');
|
||||
Sanctum::actingAs($this->createAdminAppUser($tenant));
|
||||
@@ -176,10 +187,18 @@ class AdminAppSaleControllerTest extends TestCase
|
||||
'status' => Purchase::STATUS_PENDING_PAYMENT,
|
||||
'total' => '25000.00',
|
||||
]);
|
||||
$purchaseItem = PurchaseItem::query()->create([
|
||||
'compra_id' => $purchase->id,
|
||||
'source_catalog_item_id' => $catalogItem->id,
|
||||
'item_nombre' => $catalogItem->nombre,
|
||||
'cantidad' => 2,
|
||||
'precio_unitario' => '12500.00',
|
||||
'total' => '25000.00',
|
||||
]);
|
||||
|
||||
$this->getJson("/api/v1/adminapp/tenant/sales/{$purchase->id}")
|
||||
->assertOk()
|
||||
->assertJsonPath('data.items.0.id', $cartItem->id)
|
||||
->assertJsonPath('data.items.0.id', $purchaseItem->id)
|
||||
->assertJsonPath('data.items.0.product', 'Entrada general')
|
||||
->assertJsonPath('data.items.0.event_dates', [])
|
||||
->assertJsonPath('data.items.0.quantity', 2)
|
||||
@@ -187,7 +206,7 @@ class AdminAppSaleControllerTest extends TestCase
|
||||
->assertJsonPath('data.items.0.total', '25000.00');
|
||||
}
|
||||
|
||||
public function test_sales_list_uses_cart_quantity_until_purchase_items_exist(): void
|
||||
public function test_sales_list_quantity_always_comes_from_purchase_items(): void
|
||||
{
|
||||
$tenant = $this->createTenant('acme');
|
||||
Sanctum::actingAs($this->createAdminAppUser($tenant));
|
||||
@@ -214,13 +233,7 @@ class AdminAppSaleControllerTest extends TestCase
|
||||
'status' => Purchase::STATUS_PENDING_PAYMENT,
|
||||
'total' => '37500.00',
|
||||
]);
|
||||
|
||||
$this->getJson('/api/v1/adminapp/tenant/sales')
|
||||
->assertOk()
|
||||
->assertJsonPath('data.0.id', $purchase->id)
|
||||
->assertJsonPath('data.0.quantity', 3);
|
||||
|
||||
PurchaseItem::query()->create([
|
||||
$purchaseItem = PurchaseItem::query()->create([
|
||||
'compra_id' => $purchase->id,
|
||||
'source_catalog_item_id' => $catalogItem->id,
|
||||
'nombre' => 'Entrada general',
|
||||
@@ -232,7 +245,17 @@ class AdminAppSaleControllerTest extends TestCase
|
||||
|
||||
$this->getJson('/api/v1/adminapp/tenant/sales')
|
||||
->assertOk()
|
||||
->assertJsonPath('data.0.id', $purchase->id)
|
||||
->assertJsonPath('data.0.quantity', 2);
|
||||
|
||||
$purchaseItem->update([
|
||||
'cantidad' => 4,
|
||||
'total' => '50000.00',
|
||||
]);
|
||||
|
||||
$this->getJson('/api/v1/adminapp/tenant/sales')
|
||||
->assertOk()
|
||||
->assertJsonPath('data.0.quantity', 4);
|
||||
}
|
||||
|
||||
public function test_an_adminapp_user_cannot_read_a_sale_from_another_tenant(): void
|
||||
@@ -329,11 +352,25 @@ class AdminAppSaleControllerTest extends TestCase
|
||||
$admin = $this->createAdminAppUser($tenant);
|
||||
Sanctum::actingAs($admin);
|
||||
|
||||
$purchase = Purchase::query()->create([
|
||||
'tenant_codigo' => $tenant->codigo,
|
||||
'status' => Purchase::STATUS_PENDING_PAYMENT,
|
||||
'total' => '10000.00',
|
||||
$inventory = Inventory::query()->create(['real_stock' => 10]);
|
||||
$catalogItem = CatalogItem::query()->create([
|
||||
'tenant_code' => $tenant->codigo,
|
||||
'slug' => 'confirmable-item',
|
||||
'nombre' => 'Confirmable item',
|
||||
'precio' => '10000.00',
|
||||
]);
|
||||
$variant = Variant::query()->create([
|
||||
'catalog_item_id' => $catalogItem->id,
|
||||
'inventory_id' => $inventory->id,
|
||||
]);
|
||||
$purchase = app(CheckoutService::class)->startCheckout($tenant, $admin->id, [
|
||||
'direct_items' => [[
|
||||
'catalog_item_id' => $catalogItem->id,
|
||||
'variant_id' => $variant->id,
|
||||
'cantidad' => 1,
|
||||
]],
|
||||
]);
|
||||
$purchase->update(['status' => Purchase::STATUS_PENDING_PAYMENT]);
|
||||
|
||||
$this->postJson("/api/v1/adminapp/tenant/sales/{$purchase->id}/confirm")
|
||||
->assertOk()
|
||||
@@ -385,6 +422,14 @@ class AdminAppSaleControllerTest extends TestCase
|
||||
'status' => Purchase::STATUS_PENDING_PAYMENT,
|
||||
'total' => '10000.00',
|
||||
]);
|
||||
PurchaseItem::query()->create([
|
||||
'compra_id' => $purchase->id,
|
||||
'source_catalog_item_id' => $catalogItem->id,
|
||||
'item_nombre' => $catalogItem->nombre,
|
||||
'cantidad' => 2,
|
||||
'precio_unitario' => '10000.00',
|
||||
'total' => '20000.00',
|
||||
]);
|
||||
|
||||
$this->postJson("/api/v1/adminapp/tenant/sales/{$purchase->id}/cancel")
|
||||
->assertOk()
|
||||
|
||||
@@ -76,7 +76,7 @@ class BootstrapTenantControllerTest extends TestCase
|
||||
'display_seach_bar' => false,
|
||||
'display_cart' => false,
|
||||
'cart_editing_policy' => 'disabled',
|
||||
'checkout_editing_policy' => 'quantity_and_remove',
|
||||
'checkout_editing_policy' => 'disabled',
|
||||
'display_cart_item_images' => false,
|
||||
]);
|
||||
|
||||
@@ -101,10 +101,10 @@ class BootstrapTenantControllerTest extends TestCase
|
||||
->assertJsonPath('data.cart_editing_policy.allow_delete', false)
|
||||
->assertJsonPath('data.cart_editing_policy.allow_update_quantity', false)
|
||||
->assertJsonPath('data.cart_editing_policy.allow_update_variant', false)
|
||||
->assertJsonPath('data.checkout_editing_policy.code', 'quantity_and_remove')
|
||||
->assertJsonPath('data.checkout_editing_policy.allow_modify', true)
|
||||
->assertJsonPath('data.checkout_editing_policy.allow_delete', true)
|
||||
->assertJsonPath('data.checkout_editing_policy.allow_update_quantity', true)
|
||||
->assertJsonPath('data.checkout_editing_policy.code', 'disabled')
|
||||
->assertJsonPath('data.checkout_editing_policy.allow_modify', false)
|
||||
->assertJsonPath('data.checkout_editing_policy.allow_delete', false)
|
||||
->assertJsonPath('data.checkout_editing_policy.allow_update_quantity', false)
|
||||
->assertJsonPath('data.checkout_editing_policy.allow_update_variant', false)
|
||||
->assertJsonPath('data.display_cart_item_images', false)
|
||||
->assertJsonPath('data.header_bg_color', '#ffffff')->assertJsonPath('data.footer_bg_color', '#ffffff');
|
||||
|
||||
Reference in New Issue
Block a user