BF cart items
This commit is contained in:
parent
0e6b420297
commit
1398fd0c27
|
|
@ -451,11 +451,59 @@ class CartItem extends Model
|
||||||
// Calculate days using per-minute precision
|
// Calculate days using per-minute precision
|
||||||
$days = $this->calculateBookingDays($from, $until);
|
$days = $this->calculateBookingDays($from, $until);
|
||||||
|
|
||||||
// Get current price per day
|
// For pool products with an allocated single, use the allocated single's price
|
||||||
// Pass dates to ensure accurate pricing for pool products during date updates
|
// This ensures consistency when reallocatePoolItems has already assigned a specific single
|
||||||
// Pass cart item ID to exclude this item from usage calculation
|
$meta = $this->getMeta();
|
||||||
$pricePerDay = $product->getCurrentPrice(null, $this->cart, $from, $until, $this->id);
|
$allocatedSingleItemId = $meta->allocated_single_item_id ?? null;
|
||||||
$regularPricePerDay = $product->getCurrentPrice(false, $this->cart, $from, $until, $this->id) ?? $pricePerDay;
|
|
||||||
|
if ($product->isPool() && $allocatedSingleItemId) {
|
||||||
|
// Get the allocated single item
|
||||||
|
$allocatedSingle = Product::find($allocatedSingleItemId);
|
||||||
|
|
||||||
|
if ($allocatedSingle) {
|
||||||
|
// Get price from the allocated single, with fallback to pool price
|
||||||
|
$priceModel = $allocatedSingle->defaultPrice()->first();
|
||||||
|
$pricePerDay = $priceModel?->getCurrentPrice($allocatedSingle->isOnSale());
|
||||||
|
$regularPricePerDay = $priceModel?->getCurrentPrice(false) ?? $pricePerDay;
|
||||||
|
|
||||||
|
// Fallback to pool price if single has no price
|
||||||
|
if ($pricePerDay === null && $product->hasPrice()) {
|
||||||
|
$poolPriceModel = $product->defaultPrice()->first();
|
||||||
|
$pricePerDay = $poolPriceModel?->getCurrentPrice($product->isOnSale());
|
||||||
|
$regularPricePerDay = $poolPriceModel?->getCurrentPrice(false) ?? $pricePerDay;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Allocated single not found - this is an error state, mark as unavailable
|
||||||
|
$this->update([
|
||||||
|
'from' => $from,
|
||||||
|
'until' => $until,
|
||||||
|
'price' => null,
|
||||||
|
'regular_price' => null,
|
||||||
|
'unit_amount' => null,
|
||||||
|
'subtotal' => null,
|
||||||
|
]);
|
||||||
|
return $this->fresh();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Non-pool product or pool without allocation: use getCurrentPrice
|
||||||
|
// Pass dates to ensure accurate pricing for pool products during date updates
|
||||||
|
// Pass cart item ID to exclude this item from usage calculation
|
||||||
|
$pricePerDay = $product->getCurrentPrice(null, $this->cart, $from, $until, $this->id);
|
||||||
|
$regularPricePerDay = $product->getCurrentPrice(false, $this->cart, $from, $until, $this->id) ?? $pricePerDay;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no price found, mark as unavailable
|
||||||
|
if ($pricePerDay === null) {
|
||||||
|
$this->update([
|
||||||
|
'from' => $from,
|
||||||
|
'until' => $until,
|
||||||
|
'price' => null,
|
||||||
|
'regular_price' => null,
|
||||||
|
'unit_amount' => null,
|
||||||
|
'subtotal' => null,
|
||||||
|
]);
|
||||||
|
return $this->fresh();
|
||||||
|
}
|
||||||
|
|
||||||
// Store the base unit_amount (price for 1 quantity, 1 day) in cents
|
// Store the base unit_amount (price for 1 quantity, 1 day) in cents
|
||||||
$unitAmount = (int) round($pricePerDay);
|
$unitAmount = (int) round($pricePerDay);
|
||||||
|
|
|
||||||
|
|
@ -387,4 +387,103 @@ class CartItemAvailabilityValidationTest extends TestCase
|
||||||
$this->expectException(\Blax\Shop\Exceptions\CartItemMissingInformationException::class);
|
$this->expectException(\Blax\Shop\Exceptions\CartItemMissingInformationException::class);
|
||||||
$this->cart->checkout();
|
$this->cart->checkout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function checkoutSessionLink_throws_when_items_have_null_price()
|
||||||
|
{
|
||||||
|
$pool = $this->createPoolWithLimitedSingles(3);
|
||||||
|
|
||||||
|
$from = now()->addDays(1);
|
||||||
|
$until = now()->addDays(2);
|
||||||
|
|
||||||
|
// Add items
|
||||||
|
$this->cart->addToCart($pool, 3, [], $from, $until);
|
||||||
|
|
||||||
|
// Manually make one unavailable
|
||||||
|
$item = $this->cart->items()->first();
|
||||||
|
$item->update(['price' => null, 'subtotal' => null]);
|
||||||
|
|
||||||
|
// checkoutSessionLink should throw because item is unavailable
|
||||||
|
$this->expectException(\Blax\Shop\Exceptions\CartItemMissingInformationException::class);
|
||||||
|
$this->cart->checkoutSessionLink();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function checkoutSessionLink_throws_when_items_have_zero_price()
|
||||||
|
{
|
||||||
|
$pool = $this->createPoolWithLimitedSingles(3);
|
||||||
|
|
||||||
|
$from = now()->addDays(1);
|
||||||
|
$until = now()->addDays(2);
|
||||||
|
|
||||||
|
// Add items
|
||||||
|
$this->cart->addToCart($pool, 3, [], $from, $until);
|
||||||
|
|
||||||
|
// Manually set price to 0 (should also be considered unavailable)
|
||||||
|
$item = $this->cart->items()->first();
|
||||||
|
$item->update(['price' => 0, 'subtotal' => 0]);
|
||||||
|
|
||||||
|
// checkoutSessionLink should throw because item has 0 price
|
||||||
|
$this->expectException(\Blax\Shop\Exceptions\CartItemMissingInformationException::class);
|
||||||
|
$this->cart->checkoutSessionLink();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function pool_items_maintain_consistent_pricing_after_date_changes()
|
||||||
|
{
|
||||||
|
$pool = $this->createPoolWithLimitedSingles(3);
|
||||||
|
|
||||||
|
$from1 = now()->addDays(1);
|
||||||
|
$until1 = now()->addDays(2);
|
||||||
|
|
||||||
|
// Add 3 items with dates
|
||||||
|
$this->cart->addToCart($pool, 3, [], $from1, $until1);
|
||||||
|
|
||||||
|
// Get initial prices
|
||||||
|
$initialPrices = $this->cart->items->pluck('price')->sort()->values()->toArray();
|
||||||
|
|
||||||
|
// Change to different dates (same duration)
|
||||||
|
$from2 = now()->addDays(5);
|
||||||
|
$until2 = now()->addDays(6);
|
||||||
|
|
||||||
|
$this->cart->setDates($from2, $until2);
|
||||||
|
$this->cart->refresh();
|
||||||
|
$this->cart->load('items');
|
||||||
|
|
||||||
|
// Prices should be the same (only dates changed, not duration)
|
||||||
|
$newPrices = $this->cart->items->pluck('price')->sort()->values()->toArray();
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
$initialPrices,
|
||||||
|
$newPrices,
|
||||||
|
'Prices should remain consistent when only dates change (same duration)'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function price_zero_is_treated_as_unavailable()
|
||||||
|
{
|
||||||
|
$pool = $this->createPoolWithLimitedSingles(3);
|
||||||
|
|
||||||
|
$from = now()->addDays(1);
|
||||||
|
$until = now()->addDays(2);
|
||||||
|
|
||||||
|
$this->cart->addToCart($pool, 3, [], $from, $until);
|
||||||
|
|
||||||
|
// Set price to 0 (simulating an old bug where 0 was used instead of null)
|
||||||
|
$item = $this->cart->items()->first();
|
||||||
|
$item->update(['price' => 0, 'subtotal' => 0]);
|
||||||
|
$item->refresh();
|
||||||
|
|
||||||
|
// Item should NOT be ready for checkout
|
||||||
|
$this->assertFalse($item->is_ready_to_checkout, 'Item with price 0 should not be ready');
|
||||||
|
|
||||||
|
// requiredAdjustments should show price as unavailable
|
||||||
|
$adjustments = $item->requiredAdjustments();
|
||||||
|
$this->assertArrayHasKey('price', $adjustments);
|
||||||
|
$this->assertEquals('unavailable', $adjustments['price']);
|
||||||
|
|
||||||
|
// Cart should NOT be ready
|
||||||
|
$this->assertFalse($this->cart->fresh()->is_ready_to_checkout);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue