797 lines
28 KiB
PHP
797 lines
28 KiB
PHP
<?php
|
||
|
||
namespace Blax\Shop\Tests\Feature;
|
||
|
||
use Blax\Shop\Enums\ProductRelationType;
|
||
use Blax\Shop\Enums\ProductType;
|
||
use Blax\Shop\Models\Cart;
|
||
use Blax\Shop\Models\Product;
|
||
use Blax\Shop\Models\ProductPrice;
|
||
use Blax\Shop\Tests\TestCase;
|
||
use Carbon\Carbon;
|
||
use Workbench\App\Models\User;
|
||
|
||
class CartAddToCartPoolPricingTest extends TestCase
|
||
{
|
||
protected User $user;
|
||
protected Cart $cart;
|
||
protected Product $poolProduct;
|
||
protected Product $singleItem1;
|
||
protected Product $singleItem2;
|
||
|
||
protected function setUp(): void
|
||
{
|
||
parent::setUp();
|
||
|
||
$this->user = User::factory()->create();
|
||
$this->cart = Cart::factory()->create([
|
||
'customer_id' => $this->user->id,
|
||
'customer_type' => get_class($this->user),
|
||
]);
|
||
|
||
// Create pool product
|
||
$this->poolProduct = Product::factory()->create([
|
||
'name' => 'Parking Pool',
|
||
'type' => ProductType::POOL,
|
||
'manage_stock' => false,
|
||
]);
|
||
|
||
// Create single items
|
||
$this->singleItem1 = Product::factory()->create([
|
||
'name' => 'Parking Spot 1',
|
||
'type' => ProductType::BOOKING,
|
||
'manage_stock' => true,
|
||
]);
|
||
$this->singleItem1->increaseStock(1);
|
||
|
||
$this->singleItem2 = Product::factory()->create([
|
||
'name' => 'Parking Spot 2',
|
||
'type' => ProductType::BOOKING,
|
||
'manage_stock' => true,
|
||
]);
|
||
$this->singleItem2->increaseStock(1);
|
||
|
||
// Link single items to pool
|
||
$this->poolProduct->productRelations()->attach($this->singleItem1->id, [
|
||
'type' => ProductRelationType::SINGLE->value,
|
||
]);
|
||
$this->poolProduct->productRelations()->attach($this->singleItem2->id, [
|
||
'type' => ProductRelationType::SINGLE->value,
|
||
]);
|
||
}
|
||
|
||
/** @test */
|
||
public function it_adds_pool_with_direct_price_to_cart_without_dates()
|
||
{
|
||
// Set direct price on pool
|
||
ProductPrice::factory()->create([
|
||
'purchasable_id' => $this->poolProduct->id,
|
||
'purchasable_type' => Product::class,
|
||
'unit_amount' => 3000, // 30.00 per day
|
||
'currency' => 'USD',
|
||
'is_default' => true,
|
||
]);
|
||
|
||
$cartItem = $this->cart->addToCart($this->poolProduct, 1);
|
||
|
||
$this->assertNotNull($cartItem);
|
||
$this->assertEquals($this->poolProduct->id, $cartItem->purchasable_id);
|
||
$this->assertEquals(1, $cartItem->quantity);
|
||
$this->assertEquals(3000, $cartItem->price); // 30.00 per day × 1 day
|
||
$this->assertEquals(3000, $cartItem->subtotal); // 30.00 × 1 quantity
|
||
$this->assertNull($cartItem->from);
|
||
$this->assertNull($cartItem->until);
|
||
}
|
||
|
||
/** @test */
|
||
public function it_adds_pool_with_inherited_price_to_cart_without_dates()
|
||
{
|
||
// Set prices on single items (20€ and 50€)
|
||
ProductPrice::factory()->create([
|
||
'purchasable_id' => $this->singleItem1->id,
|
||
'purchasable_type' => Product::class,
|
||
'unit_amount' => 2000, // 20.00
|
||
'currency' => 'USD',
|
||
'is_default' => true,
|
||
]);
|
||
|
||
ProductPrice::factory()->create([
|
||
'purchasable_id' => $this->singleItem2->id,
|
||
'purchasable_type' => Product::class,
|
||
'unit_amount' => 5000, // 50.00
|
||
'currency' => 'USD',
|
||
'is_default' => true,
|
||
]);
|
||
|
||
// Pool should inherit average: (2000 + 5000) / 2 = 3500
|
||
$cartItem = $this->cart->addToCart($this->poolProduct, 1);
|
||
|
||
$this->assertNotNull($cartItem);
|
||
$this->assertEquals($this->poolProduct->id, $cartItem->purchasable_id);
|
||
$this->assertEquals(1, $cartItem->quantity);
|
||
$this->assertEquals(3500, $cartItem->price); // Average: 35.00
|
||
$this->assertEquals(3500, $cartItem->subtotal);
|
||
}
|
||
|
||
/** @test */
|
||
public function it_adds_pool_with_direct_price_to_cart_with_booking_dates()
|
||
{
|
||
// Set direct price on pool
|
||
ProductPrice::factory()->create([
|
||
'purchasable_id' => $this->poolProduct->id,
|
||
'purchasable_type' => Product::class,
|
||
'unit_amount' => 3000, // 30.00 per day
|
||
'currency' => 'USD',
|
||
'is_default' => true,
|
||
]);
|
||
|
||
$from = Carbon::now()->addDays(1)->startOfDay();
|
||
$until = Carbon::now()->addDays(4)->startOfDay(); // 3 days
|
||
$days = $from->diffInDays($until);
|
||
|
||
$cartItem = $this->cart->addToCart($this->poolProduct, 1, [], $from, $until);
|
||
|
||
$this->assertNotNull($cartItem);
|
||
$this->assertEquals($this->poolProduct->id, $cartItem->purchasable_id);
|
||
$this->assertEquals(1, $cartItem->quantity);
|
||
$this->assertEquals(9000, $cartItem->price); // 30.00 × 3 days
|
||
$this->assertEquals(9000, $cartItem->subtotal); // 90.00
|
||
$this->assertEquals($from->format('Y-m-d H:i:s'), $cartItem->from->format('Y-m-d H:i:s'));
|
||
$this->assertEquals($until->format('Y-m-d H:i:s'), $cartItem->until->format('Y-m-d H:i:s'));
|
||
}
|
||
|
||
/** @test */
|
||
public function it_adds_pool_with_inherited_price_to_cart_with_booking_dates()
|
||
{
|
||
// Set prices on single items (20€ and 50€)
|
||
ProductPrice::factory()->create([
|
||
'purchasable_id' => $this->singleItem1->id,
|
||
'purchasable_type' => Product::class,
|
||
'unit_amount' => 2000, // 20.00 per day
|
||
'currency' => 'USD',
|
||
'is_default' => true,
|
||
]);
|
||
|
||
ProductPrice::factory()->create([
|
||
'purchasable_id' => $this->singleItem2->id,
|
||
'purchasable_type' => Product::class,
|
||
'unit_amount' => 5000, // 50.00 per day
|
||
'currency' => 'USD',
|
||
'is_default' => true,
|
||
]);
|
||
|
||
$from = Carbon::now()->addDays(1)->startOfDay();
|
||
$until = Carbon::now()->addDays(3)->startOfDay(); // 2 days
|
||
$days = $from->diffInDays($until);
|
||
|
||
// Pool inherits average: (2000 + 5000) / 2 = 3500 per day
|
||
$cartItem = $this->cart->addToCart($this->poolProduct, 1, [], $from, $until);
|
||
|
||
$this->assertNotNull($cartItem);
|
||
$this->assertEquals($this->poolProduct->id, $cartItem->purchasable_id);
|
||
$this->assertEquals(1, $cartItem->quantity);
|
||
$this->assertEquals(7000, $cartItem->price); // 35.00 × 2 days
|
||
$this->assertEquals(7000, $cartItem->subtotal);
|
||
$this->assertEquals($from->format('Y-m-d H:i:s'), $cartItem->from->format('Y-m-d H:i:s'));
|
||
$this->assertEquals($until->format('Y-m-d H:i:s'), $cartItem->until->format('Y-m-d H:i:s'));
|
||
}
|
||
|
||
/** @test */
|
||
public function it_calculates_price_for_multiple_pool_items_with_booking_dates()
|
||
{
|
||
// Set direct price on pool
|
||
ProductPrice::factory()->create([
|
||
'purchasable_id' => $this->poolProduct->id,
|
||
'purchasable_type' => Product::class,
|
||
'unit_amount' => 2500, // 25.00 per day
|
||
'currency' => 'USD',
|
||
'is_default' => true,
|
||
]);
|
||
|
||
$from = Carbon::now()->addDays(1)->startOfDay();
|
||
$until = Carbon::now()->addDays(6)->startOfDay(); // 5 days
|
||
|
||
$cartItem = $this->cart->addToCart($this->poolProduct, 2, [], $from, $until);
|
||
|
||
$this->assertEquals(2, $cartItem->quantity);
|
||
$this->assertEquals(12500, $cartItem->price); // 25.00 × 5 days per unit
|
||
$this->assertEquals(25000, $cartItem->subtotal); // 125.00 × 2 units = 250.00
|
||
}
|
||
|
||
/** @test */
|
||
public function it_uses_lowest_pricing_strategy_with_mixed_single_item_prices()
|
||
{
|
||
// Set prices on single items (20€ and 50€)
|
||
ProductPrice::factory()->create([
|
||
'purchasable_id' => $this->singleItem1->id,
|
||
'purchasable_type' => Product::class,
|
||
'unit_amount' => 2000, // 20.00
|
||
'currency' => 'USD',
|
||
'is_default' => true,
|
||
]);
|
||
|
||
ProductPrice::factory()->create([
|
||
'purchasable_id' => $this->singleItem2->id,
|
||
'purchasable_type' => Product::class,
|
||
'unit_amount' => 5000, // 50.00
|
||
'currency' => 'USD',
|
||
'is_default' => true,
|
||
]);
|
||
|
||
// Set pricing strategy to lowest
|
||
$this->poolProduct->setPoolPricingStrategy('lowest');
|
||
|
||
$cartItem = $this->cart->addToCart($this->poolProduct, 1);
|
||
|
||
$this->assertEquals(2000, $cartItem->price); // Lowest: 20.00
|
||
$this->assertEquals(2000, $cartItem->subtotal);
|
||
}
|
||
|
||
/** @test */
|
||
public function it_uses_highest_pricing_strategy_with_mixed_single_item_prices()
|
||
{
|
||
// Set prices on single items (20€ and 50€)
|
||
ProductPrice::factory()->create([
|
||
'purchasable_id' => $this->singleItem1->id,
|
||
'purchasable_type' => Product::class,
|
||
'unit_amount' => 2000, // 20.00
|
||
'currency' => 'USD',
|
||
'is_default' => true,
|
||
]);
|
||
|
||
ProductPrice::factory()->create([
|
||
'purchasable_id' => $this->singleItem2->id,
|
||
'purchasable_type' => Product::class,
|
||
'unit_amount' => 5000, // 50.00
|
||
'currency' => 'USD',
|
||
'is_default' => true,
|
||
]);
|
||
|
||
// Set pricing strategy to highest
|
||
$this->poolProduct->setPoolPricingStrategy('highest');
|
||
|
||
$cartItem = $this->cart->addToCart($this->poolProduct, 1);
|
||
|
||
$this->assertEquals(5000, $cartItem->price); // Highest: 50.00
|
||
$this->assertEquals(5000, $cartItem->subtotal);
|
||
}
|
||
|
||
/** @test */
|
||
public function it_uses_lowest_pricing_strategy_with_booking_dates()
|
||
{
|
||
// Set prices on single items (20€ and 50€)
|
||
ProductPrice::factory()->create([
|
||
'purchasable_id' => $this->singleItem1->id,
|
||
'purchasable_type' => Product::class,
|
||
'unit_amount' => 2000, // 20.00 per day
|
||
'currency' => 'USD',
|
||
'is_default' => true,
|
||
]);
|
||
|
||
ProductPrice::factory()->create([
|
||
'purchasable_id' => $this->singleItem2->id,
|
||
'purchasable_type' => Product::class,
|
||
'unit_amount' => 5000, // 50.00 per day
|
||
'currency' => 'USD',
|
||
'is_default' => true,
|
||
]);
|
||
|
||
// Set pricing strategy to lowest
|
||
$this->poolProduct->setPoolPricingStrategy('lowest');
|
||
|
||
$from = Carbon::now()->addDays(1)->startOfDay();
|
||
$until = Carbon::now()->addDays(4)->startOfDay(); // 3 days
|
||
|
||
$cartItem = $this->cart->addToCart($this->poolProduct, 1, [], $from, $until);
|
||
|
||
$this->assertEquals(6000, $cartItem->price); // 20.00 × 3 days
|
||
$this->assertEquals(6000, $cartItem->subtotal);
|
||
}
|
||
|
||
/** @test */
|
||
public function it_adds_regular_product_to_cart_without_dates()
|
||
{
|
||
$regularProduct = Product::factory()->create([
|
||
'name' => 'Regular Product',
|
||
'type' => ProductType::SIMPLE,
|
||
'manage_stock' => true,
|
||
]);
|
||
$regularProduct->increaseStock(10);
|
||
|
||
ProductPrice::factory()->create([
|
||
'purchasable_id' => $regularProduct->id,
|
||
'purchasable_type' => Product::class,
|
||
'unit_amount' => 1500, // 15.00
|
||
'currency' => 'USD',
|
||
'is_default' => true,
|
||
]);
|
||
|
||
$cartItem = $this->cart->addToCart($regularProduct, 2);
|
||
|
||
$this->assertEquals(2, $cartItem->quantity);
|
||
$this->assertEquals(1500, $cartItem->price);
|
||
$this->assertEquals(3000, $cartItem->subtotal);
|
||
$this->assertNull($cartItem->from);
|
||
$this->assertNull($cartItem->until);
|
||
}
|
||
|
||
/** @test */
|
||
public function it_increases_quantity_when_adding_same_pool_product_with_same_dates()
|
||
{
|
||
ProductPrice::factory()->create([
|
||
'purchasable_id' => $this->poolProduct->id,
|
||
'purchasable_type' => Product::class,
|
||
'unit_amount' => 3000,
|
||
'currency' => 'USD',
|
||
'is_default' => true,
|
||
]);
|
||
|
||
$from = Carbon::now()->addDays(1)->startOfDay();
|
||
$until = Carbon::now()->addDays(3)->startOfDay();
|
||
|
||
$cartItem1 = $this->cart->addToCart($this->poolProduct, 1, [], $from, $until);
|
||
$cartItem2 = $this->cart->addToCart($this->poolProduct, 1, [], $from, $until);
|
||
|
||
$this->assertEquals($cartItem1->id, $cartItem2->id);
|
||
$this->assertEquals(2, $cartItem2->quantity);
|
||
$this->assertEquals(12000, $cartItem2->subtotal); // 3000 × 2 days × 2 units
|
||
}
|
||
|
||
/** @test */
|
||
public function it_creates_separate_cart_items_for_same_pool_with_different_dates()
|
||
{
|
||
ProductPrice::factory()->create([
|
||
'purchasable_id' => $this->poolProduct->id,
|
||
'purchasable_type' => Product::class,
|
||
'unit_amount' => 3000,
|
||
'currency' => 'USD',
|
||
'is_default' => true,
|
||
]);
|
||
|
||
$from1 = Carbon::now()->addDays(1)->startOfDay();
|
||
$until1 = Carbon::now()->addDays(3)->startOfDay();
|
||
|
||
$from2 = Carbon::now()->addDays(5)->startOfDay();
|
||
$until2 = Carbon::now()->addDays(7)->startOfDay();
|
||
|
||
$cartItem1 = $this->cart->addToCart($this->poolProduct, 1, [], $from1, $until1);
|
||
$cartItem2 = $this->cart->addToCart($this->poolProduct, 1, [], $from2, $until2);
|
||
|
||
$this->assertNotEquals($cartItem1->id, $cartItem2->id);
|
||
$this->assertEquals(1, $cartItem1->quantity);
|
||
$this->assertEquals(1, $cartItem2->quantity);
|
||
$this->assertEquals(2, $this->cart->items()->count());
|
||
}
|
||
|
||
/** @test */
|
||
public function it_calculates_correct_total_for_cart_with_multiple_pool_items()
|
||
{
|
||
ProductPrice::factory()->create([
|
||
'purchasable_id' => $this->poolProduct->id,
|
||
'purchasable_type' => Product::class,
|
||
'unit_amount' => 2500, // 25.00 per day
|
||
'currency' => 'USD',
|
||
'is_default' => true,
|
||
]);
|
||
|
||
$from = Carbon::now()->addDays(1)->startOfDay();
|
||
$until = Carbon::now()->addDays(4)->startOfDay(); // 3 days
|
||
|
||
// Add 2 units
|
||
$this->cart->addToCart($this->poolProduct, 2, [], $from, $until);
|
||
|
||
$total = $this->cart->getTotal();
|
||
|
||
// 25.00 × 3 days × 2 units = 150.00
|
||
$this->assertEquals(15000, $total);
|
||
}
|
||
|
||
/** @test */
|
||
public function it_handles_pool_with_sale_price()
|
||
{
|
||
ProductPrice::factory()->create([
|
||
'purchasable_id' => $this->poolProduct->id,
|
||
'purchasable_type' => Product::class,
|
||
'unit_amount' => 5000, // 50.00
|
||
'sale_unit_amount' => 3000, // 30.00 (sale)
|
||
'currency' => 'USD',
|
||
'is_default' => true,
|
||
]);
|
||
|
||
// Set sale period
|
||
$this->poolProduct->update([
|
||
'sale_start' => now()->subDay(),
|
||
'sale_end' => now()->addDay(),
|
||
]);
|
||
|
||
$from = Carbon::now()->addDays(1)->startOfDay();
|
||
$until = Carbon::now()->addDays(3)->startOfDay(); // 2 days
|
||
|
||
$cartItem = $this->cart->addToCart($this->poolProduct, 1, [], $from, $until);
|
||
|
||
$this->assertEquals(6000, $cartItem->price); // 30.00 × 2 days (sale price)
|
||
$this->assertEquals(10000, $cartItem->regular_price); // 50.00 × 2 days (regular price)
|
||
}
|
||
|
||
/** @test */
|
||
public function it_handles_pool_with_inherited_sale_price()
|
||
{
|
||
ProductPrice::factory()->create([
|
||
'purchasable_id' => $this->singleItem1->id,
|
||
'purchasable_type' => Product::class,
|
||
'unit_amount' => 5000,
|
||
'sale_unit_amount' => 3000,
|
||
'currency' => 'USD',
|
||
'is_default' => true,
|
||
]);
|
||
|
||
ProductPrice::factory()->create([
|
||
'purchasable_id' => $this->singleItem2->id,
|
||
'purchasable_type' => Product::class,
|
||
'unit_amount' => 7000,
|
||
'sale_unit_amount' => 5000,
|
||
'currency' => 'USD',
|
||
'is_default' => true,
|
||
]);
|
||
|
||
// Set sale period on single items
|
||
$this->singleItem1->update([
|
||
'sale_start' => now()->subDay(),
|
||
'sale_end' => now()->addDay(),
|
||
]);
|
||
|
||
$this->singleItem2->update([
|
||
'sale_start' => now()->subDay(),
|
||
'sale_end' => now()->addDay(),
|
||
]);
|
||
|
||
$from = Carbon::now()->addDays(1)->startOfDay();
|
||
$until = Carbon::now()->addDays(2)->startOfDay(); // 1 day
|
||
|
||
$cartItem = $this->cart->addToCart($this->poolProduct, 1, [], $from, $until);
|
||
|
||
// Average sale price: (3000 + 5000) / 2 = 4000 per day
|
||
$this->assertEquals(4000, $cartItem->price);
|
||
// Average regular price: (5000 + 7000) / 2 = 6000 per day
|
||
$this->assertEquals(6000, $cartItem->regular_price);
|
||
}
|
||
|
||
/** @test */
|
||
public function it_handles_zero_days_as_one_day_minimum()
|
||
{
|
||
ProductPrice::factory()->create([
|
||
'purchasable_id' => $this->poolProduct->id,
|
||
'purchasable_type' => Product::class,
|
||
'unit_amount' => 3000,
|
||
'currency' => 'USD',
|
||
'is_default' => true,
|
||
]);
|
||
|
||
// Same day booking (0 days diff)
|
||
$from = Carbon::now()->addDays(1)->setTime(10, 0);
|
||
$until = Carbon::now()->addDays(1)->setTime(14, 0);
|
||
|
||
$cartItem = $this->cart->addToCart($this->poolProduct, 1, [], $from, $until);
|
||
|
||
// Should treat as minimum 1 day
|
||
$this->assertEquals(3000, $cartItem->price); // 30.00 × 1 day
|
||
}
|
||
|
||
/** @test */
|
||
public function it_throws_exception_when_adding_pool_without_any_pricing()
|
||
{
|
||
// Pool with no direct price and single items with no prices
|
||
$pool = Product::factory()->create([
|
||
'name' => 'Pool Without Pricing',
|
||
'type' => ProductType::POOL,
|
||
'manage_stock' => false,
|
||
]);
|
||
|
||
$spot = Product::factory()->create([
|
||
'name' => 'Spot Without Price',
|
||
'type' => ProductType::BOOKING,
|
||
'manage_stock' => true,
|
||
]);
|
||
$spot->increaseStock(1);
|
||
|
||
$pool->productRelations()->attach($spot->id, [
|
||
'type' => ProductRelationType::SINGLE->value,
|
||
]);
|
||
|
||
$this->expectException(\Blax\Shop\Exceptions\HasNoPriceException::class);
|
||
$this->cart->addToCart($pool, 1);
|
||
}
|
||
|
||
/** @test */
|
||
public function it_throws_exception_when_pool_not_available_for_booking_period()
|
||
{
|
||
ProductPrice::factory()->create([
|
||
'purchasable_id' => $this->poolProduct->id,
|
||
'purchasable_type' => Product::class,
|
||
'unit_amount' => 3000,
|
||
'currency' => 'USD',
|
||
'is_default' => true,
|
||
]);
|
||
|
||
$from = Carbon::now()->addDays(1)->startOfDay();
|
||
$until = Carbon::now()->addDays(3)->startOfDay();
|
||
|
||
// Claim all single items for the period
|
||
$this->singleItem1->claimStock(1, null, $from, $until);
|
||
$this->singleItem2->claimStock(1, null, $from, $until);
|
||
|
||
// Try to add pool for same period
|
||
$this->expectException(\Blax\Shop\Exceptions\NotEnoughStockException::class);
|
||
$this->expectExceptionMessage('has only 0 items available');
|
||
$this->cart->addToCart($this->poolProduct, 1, [], $from, $until);
|
||
}
|
||
|
||
/** @test */
|
||
public function it_throws_exception_when_booking_product_not_available_for_period()
|
||
{
|
||
$bookingProduct = Product::factory()->create([
|
||
'name' => 'Meeting Room',
|
||
'type' => ProductType::BOOKING,
|
||
'manage_stock' => true,
|
||
]);
|
||
$bookingProduct->increaseStock(1);
|
||
|
||
ProductPrice::factory()->create([
|
||
'purchasable_id' => $bookingProduct->id,
|
||
'purchasable_type' => Product::class,
|
||
'unit_amount' => 5000,
|
||
'currency' => 'USD',
|
||
'is_default' => true,
|
||
]);
|
||
|
||
$from = Carbon::now()->addDays(1)->startOfDay();
|
||
$until = Carbon::now()->addDays(3)->startOfDay();
|
||
|
||
// Claim the booking product for the period
|
||
$bookingProduct->claimStock(1, null, $from, $until);
|
||
|
||
// Try to add for overlapping period
|
||
$this->expectException(\Blax\Shop\Exceptions\NotEnoughStockException::class);
|
||
$this->expectExceptionMessage('not available for the requested period');
|
||
$this->cart->addToCart($bookingProduct, 1, [], $from, $until);
|
||
}
|
||
|
||
/** @test */
|
||
public function it_throws_exception_when_only_from_date_provided()
|
||
{
|
||
ProductPrice::factory()->create([
|
||
'purchasable_id' => $this->poolProduct->id,
|
||
'purchasable_type' => Product::class,
|
||
'unit_amount' => 3000,
|
||
'currency' => 'USD',
|
||
'is_default' => true,
|
||
]);
|
||
|
||
$from = Carbon::now()->addDays(1);
|
||
|
||
$this->expectException(\Exception::class);
|
||
$this->expectExceptionMessage("Both 'from' and 'until' dates must be provided together");
|
||
$this->cart->addToCart($this->poolProduct, 1, [], $from, null);
|
||
}
|
||
|
||
/** @test */
|
||
public function it_throws_exception_when_only_until_date_provided()
|
||
{
|
||
ProductPrice::factory()->create([
|
||
'purchasable_id' => $this->poolProduct->id,
|
||
'purchasable_type' => Product::class,
|
||
'unit_amount' => 3000,
|
||
'currency' => 'USD',
|
||
'is_default' => true,
|
||
]);
|
||
|
||
$until = Carbon::now()->addDays(3);
|
||
|
||
$this->expectException(\Exception::class);
|
||
$this->expectExceptionMessage("Both 'from' and 'until' dates must be provided together");
|
||
$this->cart->addToCart($this->poolProduct, 1, [], null, $until);
|
||
}
|
||
|
||
/** @test */
|
||
public function it_throws_exception_when_from_is_after_until()
|
||
{
|
||
ProductPrice::factory()->create([
|
||
'purchasable_id' => $this->poolProduct->id,
|
||
'purchasable_type' => Product::class,
|
||
'unit_amount' => 3000,
|
||
'currency' => 'USD',
|
||
'is_default' => true,
|
||
]);
|
||
|
||
$from = Carbon::now()->addDays(5);
|
||
$until = Carbon::now()->addDays(2); // Before from
|
||
|
||
$this->expectException(\Exception::class);
|
||
$this->expectExceptionMessage("'from' date must be before the 'until' date");
|
||
$this->cart->addToCart($this->poolProduct, 1, [], $from, $until);
|
||
}
|
||
|
||
/** @test */
|
||
public function it_throws_exception_when_from_equals_until()
|
||
{
|
||
ProductPrice::factory()->create([
|
||
'purchasable_id' => $this->poolProduct->id,
|
||
'purchasable_type' => Product::class,
|
||
'unit_amount' => 3000,
|
||
'currency' => 'USD',
|
||
'is_default' => true,
|
||
]);
|
||
|
||
$date = Carbon::now()->addDays(1);
|
||
|
||
$this->expectException(\Exception::class);
|
||
$this->expectExceptionMessage("'from' date must be before the 'until' date");
|
||
$this->cart->addToCart($this->poolProduct, 1, [], $date, $date);
|
||
}
|
||
|
||
/** @test */
|
||
public function it_creates_separate_items_for_same_product_same_dates_different_parameters()
|
||
{
|
||
ProductPrice::factory()->create([
|
||
'purchasable_id' => $this->poolProduct->id,
|
||
'purchasable_type' => Product::class,
|
||
'unit_amount' => 3000,
|
||
'currency' => 'USD',
|
||
'is_default' => true,
|
||
]);
|
||
|
||
$from = Carbon::now()->addDays(1);
|
||
$until = Carbon::now()->addDays(3);
|
||
|
||
$cartItem1 = $this->cart->addToCart($this->poolProduct, 1, ['zone' => 'A'], $from, $until);
|
||
$cartItem2 = $this->cart->addToCart($this->poolProduct, 1, ['zone' => 'B'], $from, $until);
|
||
|
||
$this->assertNotEquals($cartItem1->id, $cartItem2->id);
|
||
$this->assertEquals(2, $this->cart->items()->count());
|
||
$this->assertEquals(['zone' => 'A'], $cartItem1->parameters);
|
||
$this->assertEquals(['zone' => 'B'], $cartItem2->parameters);
|
||
}
|
||
|
||
/** @test */
|
||
public function it_throws_exception_when_pool_quantity_exceeds_available_items()
|
||
{
|
||
ProductPrice::factory()->create([
|
||
'purchasable_id' => $this->poolProduct->id,
|
||
'purchasable_type' => Product::class,
|
||
'unit_amount' => 3000,
|
||
'currency' => 'USD',
|
||
'is_default' => true,
|
||
]);
|
||
|
||
$from = Carbon::now()->addDays(1);
|
||
$until = Carbon::now()->addDays(3);
|
||
|
||
// Pool has 2 single items, try to add 3 (with dates to check availability)
|
||
$this->expectException(\Blax\Shop\Exceptions\NotEnoughStockException::class);
|
||
$this->expectExceptionMessage('has only 2 items available');
|
||
$this->cart->addToCart($this->poolProduct, 3, [], $from, $until);
|
||
}
|
||
|
||
/** @test */
|
||
public function it_handles_partial_pool_availability()
|
||
{
|
||
ProductPrice::factory()->create([
|
||
'purchasable_id' => $this->poolProduct->id,
|
||
'purchasable_type' => Product::class,
|
||
'unit_amount' => 3000,
|
||
'currency' => 'USD',
|
||
'is_default' => true,
|
||
]);
|
||
|
||
$from = Carbon::now()->addDays(1);
|
||
$until = Carbon::now()->addDays(3);
|
||
|
||
// Claim one of the two single items
|
||
$this->singleItem1->claimStock(1, null, $from, $until);
|
||
|
||
// Should be able to add 1 (one spot still available)
|
||
$cartItem = $this->cart->addToCart($this->poolProduct, 1, [], $from, $until);
|
||
$this->assertNotNull($cartItem);
|
||
|
||
// But not 2
|
||
$this->expectException(\Blax\Shop\Exceptions\NotEnoughStockException::class);
|
||
$this->cart->addToCart($this->poolProduct, 2, [], $from, $until);
|
||
}
|
||
|
||
/** @test */
|
||
public function it_throws_exception_for_regular_product_without_price()
|
||
{
|
||
$regularProduct = Product::factory()->create([
|
||
'name' => 'Product Without Price',
|
||
'type' => ProductType::SIMPLE,
|
||
'manage_stock' => true,
|
||
]);
|
||
$regularProduct->increaseStock(10);
|
||
|
||
$this->expectException(\Blax\Shop\Exceptions\HasNoPriceException::class);
|
||
$this->cart->addToCart($regularProduct, 1);
|
||
}
|
||
|
||
/** @test */
|
||
public function it_allows_adding_booking_product_without_dates()
|
||
{
|
||
$bookingProduct = Product::factory()->create([
|
||
'name' => 'Meeting Room',
|
||
'type' => ProductType::BOOKING,
|
||
'manage_stock' => true,
|
||
]);
|
||
$bookingProduct->increaseStock(5);
|
||
|
||
ProductPrice::factory()->create([
|
||
'purchasable_id' => $bookingProduct->id,
|
||
'purchasable_type' => Product::class,
|
||
'unit_amount' => 5000,
|
||
'currency' => 'USD',
|
||
'is_default' => true,
|
||
]);
|
||
|
||
// Should be able to add without dates
|
||
$cartItem = $this->cart->addToCart($bookingProduct, 1);
|
||
|
||
$this->assertNotNull($cartItem);
|
||
$this->assertEquals($bookingProduct->id, $cartItem->purchasable_id);
|
||
$this->assertNull($cartItem->from);
|
||
$this->assertNull($cartItem->until);
|
||
$this->assertEquals(5000, $cartItem->price); // 1 day default
|
||
}
|
||
|
||
/** @test */
|
||
public function it_allows_adding_pool_product_without_dates()
|
||
{
|
||
ProductPrice::factory()->create([
|
||
'purchasable_id' => $this->poolProduct->id,
|
||
'purchasable_type' => Product::class,
|
||
'unit_amount' => 3000,
|
||
'currency' => 'USD',
|
||
'is_default' => true,
|
||
]);
|
||
|
||
// Should be able to add without dates
|
||
$cartItem = $this->cart->addToCart($this->poolProduct, 1);
|
||
|
||
$this->assertNotNull($cartItem);
|
||
$this->assertEquals($this->poolProduct->id, $cartItem->purchasable_id);
|
||
$this->assertNull($cartItem->from);
|
||
$this->assertNull($cartItem->until);
|
||
$this->assertEquals(3000, $cartItem->price); // 1 day default
|
||
}
|
||
|
||
/** @test */
|
||
public function it_allows_updating_cart_item_dates_later()
|
||
{
|
||
ProductPrice::factory()->create([
|
||
'purchasable_id' => $this->poolProduct->id,
|
||
'purchasable_type' => Product::class,
|
||
'unit_amount' => 3000,
|
||
'currency' => 'USD',
|
||
'is_default' => true,
|
||
]);
|
||
|
||
// Add without dates
|
||
$cartItem = $this->cart->addToCart($this->poolProduct, 1);
|
||
$this->assertNull($cartItem->from);
|
||
$this->assertNull($cartItem->until);
|
||
|
||
// Update with dates
|
||
$from = Carbon::now()->addDays(1);
|
||
$until = Carbon::now()->addDays(3);
|
||
|
||
$cartItem->update([
|
||
'from' => $from,
|
||
'until' => $until,
|
||
'price' => 3000 * 2, // 2 days
|
||
'subtotal' => 3000 * 2 * 1, // 2 days × 1 quantity
|
||
]);
|
||
|
||
$cartItem->refresh();
|
||
$this->assertEquals($from->format('Y-m-d H:i:s'), $cartItem->from->format('Y-m-d H:i:s'));
|
||
$this->assertEquals($until->format('Y-m-d H:i:s'), $cartItem->until->format('Y-m-d H:i:s'));
|
||
$this->assertEquals(6000, $cartItem->price);
|
||
}
|
||
}
|