Merge branch 'feature/cart_editing_policy' into dev
This commit is contained in:
@@ -202,6 +202,7 @@ class CartControllerTest extends TestCase
|
||||
->assertOk()
|
||||
->assertJsonPath('data.items.0.catalog_item_id', $item->id)
|
||||
->assertJsonPath('data.items.0.variant_id', $variant->id)
|
||||
->assertJsonPath('data.items.0.variants.0.id', $variant->id)
|
||||
->assertJsonPath('data.items.0.cantidad', 5)
|
||||
->assertJsonPath('data.subtotal', '125.00');
|
||||
|
||||
@@ -476,6 +477,83 @@ class CartControllerTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_quantity_and_remove_policy_hides_variants_and_rejects_variant_changes(): void
|
||||
{
|
||||
$tenant = $this->createTenant('acme');
|
||||
$tenant->update(['cart_editing_policy' => 'quantity_and_remove']);
|
||||
[$item, $firstVariant] = $this->createVariantItem($tenant, 10, '15.00');
|
||||
$secondInventory = Inventory::query()->create(['real_stock' => 10]);
|
||||
$secondVariant = $item->variants()->create(['inventory_id' => $secondInventory->id]);
|
||||
$createResponse = $this->postJson('/api/tenants/acme/cart/items', [
|
||||
'catalog_item_id' => $item->id,
|
||||
'variant_id' => $firstVariant->id,
|
||||
'cantidad' => 1,
|
||||
])->assertJsonMissingPath('data.items.0.variants');
|
||||
$guestToken = $createResponse->getCookie('guest_token', false)?->getValue();
|
||||
$cartItemId = $createResponse->json('data.items.0.id');
|
||||
|
||||
$this->call(
|
||||
'PATCH',
|
||||
"/api/tenants/acme/cart/items/{$cartItemId}",
|
||||
[],
|
||||
['guest_token' => $guestToken],
|
||||
[],
|
||||
['HTTP_Accept' => 'application/json', 'CONTENT_TYPE' => 'application/json'],
|
||||
json_encode(['cantidad' => 2]),
|
||||
)->assertOk()->assertJsonPath('data.items.0.cantidad', 2);
|
||||
|
||||
$this->call(
|
||||
'PATCH',
|
||||
"/api/tenants/acme/cart/items/{$cartItemId}",
|
||||
[],
|
||||
['guest_token' => $guestToken],
|
||||
[],
|
||||
['HTTP_Accept' => 'application/json', 'CONTENT_TYPE' => 'application/json'],
|
||||
json_encode(['cantidad' => 2, 'variant_id' => $secondVariant->id]),
|
||||
)->assertUnprocessable()->assertJsonValidationErrors('variant_id');
|
||||
|
||||
$this->call(
|
||||
'DELETE',
|
||||
"/api/tenants/acme/cart/items/{$cartItemId}",
|
||||
[],
|
||||
['guest_token' => $guestToken],
|
||||
[],
|
||||
['HTTP_Accept' => 'application/json'],
|
||||
)->assertOk()->assertJsonPath('data.items', []);
|
||||
}
|
||||
|
||||
public function test_disabled_policy_rejects_quantity_changes_and_removal(): void
|
||||
{
|
||||
$tenant = $this->createTenant('acme');
|
||||
$tenant->update(['cart_editing_policy' => 'disabled']);
|
||||
$item = $this->createDirectItem($tenant, 10, '15.00');
|
||||
$createResponse = $this->postJson('/api/tenants/acme/cart/items', [
|
||||
'catalog_item_id' => $item->id,
|
||||
'cantidad' => 1,
|
||||
])->assertOk();
|
||||
$guestToken = $createResponse->getCookie('guest_token', false)?->getValue();
|
||||
$cartItemId = $createResponse->json('data.items.0.id');
|
||||
|
||||
$this->call(
|
||||
'PATCH',
|
||||
"/api/tenants/acme/cart/items/{$cartItemId}",
|
||||
[],
|
||||
['guest_token' => $guestToken],
|
||||
[],
|
||||
['HTTP_Accept' => 'application/json', 'CONTENT_TYPE' => 'application/json'],
|
||||
json_encode(['cantidad' => 2]),
|
||||
)->assertUnprocessable()->assertJsonValidationErrors('cantidad');
|
||||
|
||||
$this->call(
|
||||
'DELETE',
|
||||
"/api/tenants/acme/cart/items/{$cartItemId}",
|
||||
[],
|
||||
['guest_token' => $guestToken],
|
||||
[],
|
||||
['HTTP_Accept' => 'application/json'],
|
||||
)->assertUnprocessable()->assertJsonValidationErrors('cart_item');
|
||||
}
|
||||
|
||||
public function test_it_requires_a_variant_when_the_item_has_variant_inventory(): void
|
||||
{
|
||||
$tenant = $this->createTenant('acme');
|
||||
|
||||
@@ -10,10 +10,10 @@ class AddCartEditingEnabledToTenantsTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_it_disables_cart_editing_only_for_desfile(): void
|
||||
public function test_it_migrates_the_boolean_to_the_equivalent_policy(): void
|
||||
{
|
||||
$migration = require database_path(
|
||||
'migrations/2026_08_18_060000_add_cart_editing_enabled_to_tenants_table.php'
|
||||
'migrations/2026_08_19_000000_replace_cart_editing_enabled_with_policy.php'
|
||||
);
|
||||
|
||||
$migration->down();
|
||||
@@ -21,7 +21,17 @@ class AddCartEditingEnabledToTenantsTest extends TestCase
|
||||
|
||||
$this->assertDatabaseHas('tenants', [
|
||||
'codigo' => 'desfile_pura_tendencia',
|
||||
'cart_editing_enabled' => false,
|
||||
'cart_editing_policy' => 'disabled',
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('tenants', [
|
||||
'codigo' => 'sonder',
|
||||
'cart_editing_policy' => 'quantity_and_remove',
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('tenants', [
|
||||
'codigo' => 'fiesta_futbol_infantil',
|
||||
'cart_editing_policy' => 'full',
|
||||
]);
|
||||
|
||||
DB::table('tenants')->insert([
|
||||
@@ -32,7 +42,7 @@ class AddCartEditingEnabledToTenantsTest extends TestCase
|
||||
|
||||
$this->assertDatabaseHas('tenants', [
|
||||
'codigo' => 'cart-editing-default',
|
||||
'cart_editing_enabled' => true,
|
||||
'cart_editing_policy' => 'full',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -605,6 +605,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');
|
||||
|
||||
@@ -44,7 +44,7 @@ class DesfilePuraTendenciaSeederTest extends TestCase
|
||||
'footer_bg_color' => '#D4441C',
|
||||
'display_categories' => false,
|
||||
'display_seach_bar' => false,
|
||||
'cart_editing_enabled' => false,
|
||||
'cart_editing_policy' => 'disabled',
|
||||
'display_cart_item_images' => false,
|
||||
'site_title' => 'Desfile Pura Tendencia',
|
||||
]);
|
||||
|
||||
@@ -61,6 +61,7 @@ class TenantSeederTest extends TestCase
|
||||
->sole();
|
||||
|
||||
$this->assertSame('shopit', $sonder->website_type_code);
|
||||
$this->assertSame('quantity_and_remove', $sonder->cart_editing_policy->value);
|
||||
|
||||
$carousel = $sonder->websiteExtras
|
||||
->firstWhere('websiteTypeExtra.codigo', 'carousel');
|
||||
@@ -81,6 +82,7 @@ class TenantSeederTest extends TestCase
|
||||
->sole();
|
||||
|
||||
$this->assertSame('onticket', $fiesta->website_type_code);
|
||||
$this->assertSame('full', $fiesta->cart_editing_policy->value);
|
||||
|
||||
$extras = $fiesta->websiteExtras->keyBy('websiteTypeExtra.codigo');
|
||||
$this->assertEqualsCanonicalizing(
|
||||
|
||||
@@ -73,7 +73,7 @@ class BootstrapTenantControllerTest extends TestCase
|
||||
'display_categories' => false,
|
||||
'display_seach_bar' => false,
|
||||
'display_cart' => false,
|
||||
'cart_editing_enabled' => false,
|
||||
'cart_editing_policy' => 'disabled',
|
||||
'display_cart_item_images' => false,
|
||||
]);
|
||||
|
||||
@@ -91,7 +91,11 @@ class BootstrapTenantControllerTest extends TestCase
|
||||
->assertJsonPath('data.display_categories', false)
|
||||
->assertJsonPath('data.display_seach_bar', false)
|
||||
->assertJsonPath('data.display_cart', false)
|
||||
->assertJsonPath('data.cart_editing_enabled', false)
|
||||
->assertJsonPath('data.cart_editing_policy.code', 'disabled')
|
||||
->assertJsonPath('data.cart_editing_policy.allow_modify', false)
|
||||
->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.display_cart_item_images', false)
|
||||
->assertJsonPath('data.header_bg_color', '#ffffff')->assertJsonPath('data.footer_bg_color', '#ffffff');
|
||||
|
||||
@@ -758,7 +762,7 @@ class BootstrapTenantControllerTest extends TestCase
|
||||
|
||||
$response = $this->putJson("/api/tenants/{$tenant->codigo}", [
|
||||
'primary_color' => '#000000',
|
||||
'cart_editing_enabled' => false,
|
||||
'cart_editing_policy' => 'quantity_and_remove',
|
||||
'display_cart_item_images' => false,
|
||||
]);
|
||||
|
||||
@@ -767,7 +771,11 @@ class BootstrapTenantControllerTest extends TestCase
|
||||
->assertJsonPath('data.codigo', 'acme')
|
||||
->assertJsonPath('data.nombre', 'Acme')
|
||||
->assertJsonPath('data.primary_color', '#000000')
|
||||
->assertJsonPath('data.cart_editing_enabled', false)
|
||||
->assertJsonPath('data.cart_editing_policy.code', 'quantity_and_remove')
|
||||
->assertJsonPath('data.cart_editing_policy.allow_modify', true)
|
||||
->assertJsonPath('data.cart_editing_policy.allow_delete', true)
|
||||
->assertJsonPath('data.cart_editing_policy.allow_update_quantity', true)
|
||||
->assertJsonPath('data.cart_editing_policy.allow_update_variant', false)
|
||||
->assertJsonPath('data.display_cart_item_images', false);
|
||||
|
||||
$this->assertDatabaseHas('tenants', [
|
||||
@@ -775,7 +783,7 @@ class BootstrapTenantControllerTest extends TestCase
|
||||
'codigo' => 'acme',
|
||||
'nombre' => 'Acme',
|
||||
'primary_color' => '#000000',
|
||||
'cart_editing_enabled' => false,
|
||||
'cart_editing_policy' => 'quantity_and_remove',
|
||||
'display_cart_item_images' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user