ABFI cart, cartitems, pool/bookings, tests
This commit is contained in:
parent
67917e6a31
commit
d2cf70ce44
|
|
@ -75,6 +75,46 @@ class Cart extends Model
|
|||
return $this->items->sum('quantity');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all cart items that require adjustments before checkout.
|
||||
*
|
||||
* This method checks all cart items and returns a collection of items
|
||||
* that need additional information (like booking dates) before checkout.
|
||||
*
|
||||
* Example usage:
|
||||
* ```php
|
||||
* $incompleteItems = $cart->getItemsRequiringAdjustments();
|
||||
*
|
||||
* if ($incompleteItems->isNotEmpty()) {
|
||||
* foreach ($incompleteItems as $item) {
|
||||
* $adjustments = $item->requiredAdjustments();
|
||||
* // Display what's needed: ['from' => 'datetime', 'until' => 'datetime']
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @return \Illuminate\Support\Collection Collection of CartItem models requiring adjustments
|
||||
*/
|
||||
public function getItemsRequiringAdjustments()
|
||||
{
|
||||
return $this->items->filter(function ($item) {
|
||||
return !empty($item->requiredAdjustments());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if cart is ready for checkout.
|
||||
*
|
||||
* Returns true if all cart items have all required information set.
|
||||
* For booking products and pools with booking items, this means dates must be set.
|
||||
*
|
||||
* @return bool True if ready for checkout, false if any items need adjustments
|
||||
*/
|
||||
public function isReadyForCheckout(): bool
|
||||
{
|
||||
return $this->getItemsRequiringAdjustments()->isEmpty();
|
||||
}
|
||||
|
||||
public function getUnpaidAmount(): float
|
||||
{
|
||||
$paidAmount = $this->purchases()
|
||||
|
|
@ -143,25 +183,63 @@ class Cart extends Model
|
|||
* @param Model&Cartable $cartable The item to add to cart
|
||||
* @param int $quantity The quantity to add
|
||||
* @param array<string, mixed> $parameters Additional parameters for the cart item
|
||||
* @param \DateTimeInterface|null $from Optional start date for bookings
|
||||
* @param \DateTimeInterface|null $until Optional end date for bookings
|
||||
* @return CartItem
|
||||
* @throws \Exception If the item doesn't implement Cartable interface
|
||||
*/
|
||||
public function addToCart(
|
||||
Model $cartable,
|
||||
int $quantity = 1,
|
||||
array $parameters = []
|
||||
array $parameters = [],
|
||||
\DateTimeInterface $from = null,
|
||||
\DateTimeInterface $until = null
|
||||
): CartItem {
|
||||
// $cartable must implement Cartable
|
||||
if (! $cartable instanceof Cartable) {
|
||||
throw new \Exception("Item must implement the Cartable interface.");
|
||||
}
|
||||
|
||||
// Check if item already exists in cart with same parameters
|
||||
// Validate Product-specific requirements
|
||||
if ($cartable instanceof Product) {
|
||||
// Validate pricing before adding to cart
|
||||
$cartable->validatePricing(throwExceptions: true);
|
||||
|
||||
// Validate dates if both are provided (optional for cart, required at checkout)
|
||||
if ($from && $until) {
|
||||
// Validate from is before until
|
||||
if ($from >= $until) {
|
||||
throw new \Exception("The 'from' date must be before the 'until' date. Got from: {$from->format('Y-m-d H:i:s')}, until: {$until->format('Y-m-d H:i:s')}");
|
||||
}
|
||||
|
||||
// Check booking product availability if dates are provided
|
||||
if ($cartable->isBooking() && !$cartable->isAvailableForBooking($from, $until, $quantity)) {
|
||||
throw new \Blax\Shop\Exceptions\NotEnoughStockException(
|
||||
"Product '{$cartable->name}' is not available for the requested period ({$from->format('Y-m-d')} to {$until->format('Y-m-d')})."
|
||||
);
|
||||
}
|
||||
|
||||
// Check pool product availability if dates are provided
|
||||
if ($cartable->isPool()) {
|
||||
$maxQuantity = $cartable->getPoolMaxQuantity($from, $until);
|
||||
if ($quantity > $maxQuantity) {
|
||||
throw new \Blax\Shop\Exceptions\NotEnoughStockException(
|
||||
"Pool product '{$cartable->name}' has only {$maxQuantity} items available for the requested period ({$from->format('Y-m-d')} to {$until->format('Y-m-d')}). Requested: {$quantity}"
|
||||
);
|
||||
}
|
||||
}
|
||||
} elseif ($from || $until) {
|
||||
// If only one date is provided, it's an error
|
||||
throw new \Exception("Both 'from' and 'until' dates must be provided together, or both omitted.");
|
||||
}
|
||||
}
|
||||
|
||||
// Check if item already exists in cart with same parameters and dates
|
||||
$existingItem = $this->items()
|
||||
->where('purchasable_id', $cartable->getKey())
|
||||
->where('purchasable_type', get_class($cartable))
|
||||
->get()
|
||||
->first(function ($item) use ($parameters) {
|
||||
->first(function ($item) use ($parameters, $from, $until) {
|
||||
$existingParams = is_array($item->parameters)
|
||||
? $item->parameters
|
||||
: (array) $item->parameters;
|
||||
|
|
@ -170,15 +248,49 @@ class Cart extends Model
|
|||
ksort($existingParams);
|
||||
ksort($parameters);
|
||||
|
||||
return $existingParams === $parameters;
|
||||
// Check parameters match
|
||||
$paramsMatch = $existingParams === $parameters;
|
||||
|
||||
// Check dates match (important for bookings)
|
||||
$datesMatch = true;
|
||||
if ($from || $until) {
|
||||
$datesMatch = (
|
||||
($item->from?->format('Y-m-d H:i:s') === $from?->format('Y-m-d H:i:s')) &&
|
||||
($item->until?->format('Y-m-d H:i:s') === $until?->format('Y-m-d H:i:s'))
|
||||
);
|
||||
}
|
||||
|
||||
return $paramsMatch && $datesMatch;
|
||||
});
|
||||
|
||||
// Calculate price per day (base price)
|
||||
$pricePerDay = $cartable->getCurrentPrice();
|
||||
$regularPricePerDay = $cartable->getCurrentPrice(false) ?? $pricePerDay;
|
||||
|
||||
// Ensure prices are not null
|
||||
if ($pricePerDay === null) {
|
||||
throw new \Exception("Product '{$cartable->name}' has no valid price.");
|
||||
}
|
||||
|
||||
// Calculate days if booking dates provided
|
||||
$days = 1;
|
||||
if ($from && $until) {
|
||||
$days = max(1, $from->diff($until)->days);
|
||||
}
|
||||
|
||||
// Calculate price per unit for the entire period
|
||||
$pricePerUnit = $pricePerDay * $days;
|
||||
$regularPricePerUnit = $regularPricePerDay * $days;
|
||||
|
||||
// Calculate total price
|
||||
$totalPrice = $pricePerUnit * $quantity;
|
||||
|
||||
if ($existingItem) {
|
||||
// Update quantity and subtotal
|
||||
$newQuantity = $existingItem->quantity + $quantity;
|
||||
$existingItem->update([
|
||||
'quantity' => $newQuantity,
|
||||
'subtotal' => ($cartable->getCurrentPrice()) * $newQuantity,
|
||||
'subtotal' => $pricePerUnit * $newQuantity,
|
||||
]);
|
||||
|
||||
return $existingItem->fresh();
|
||||
|
|
@ -189,10 +301,12 @@ class Cart extends Model
|
|||
'purchasable_id' => $cartable->getKey(),
|
||||
'purchasable_type' => get_class($cartable),
|
||||
'quantity' => $quantity,
|
||||
'price' => $cartable->getCurrentPrice(),
|
||||
'regular_price' => $cartable->getCurrentPrice(false) ?? $cartable->unit_amount,
|
||||
'subtotal' => ($cartable->getCurrentPrice()) * $quantity,
|
||||
'price' => $pricePerUnit, // Price per unit for the period
|
||||
'regular_price' => $regularPricePerUnit,
|
||||
'subtotal' => $totalPrice, // Total for all units
|
||||
'parameters' => $parameters,
|
||||
'from' => $from,
|
||||
'until' => $until,
|
||||
]);
|
||||
|
||||
return $cartItem->fresh();
|
||||
|
|
|
|||
|
|
@ -103,4 +103,69 @@ class CartItem extends Model
|
|||
{
|
||||
return $query->where('product_id', $productId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get required adjustments for this cart item before checkout.
|
||||
*
|
||||
* Returns an array of fields that need to be set, with suggested field names.
|
||||
* For booking products and pools with booking items, dates are required.
|
||||
*
|
||||
* This method is useful for:
|
||||
* - Validating cart items before checkout
|
||||
* - Displaying missing information to users
|
||||
* - Checking if a cart item needs additional user input
|
||||
*
|
||||
* Example usage:
|
||||
* ```php
|
||||
* // Check if cart item needs adjustments
|
||||
* $adjustments = $cartItem->requiredAdjustments();
|
||||
*
|
||||
* if (!empty($adjustments)) {
|
||||
* // Item needs dates before checkout
|
||||
* // $adjustments = ['from' => 'datetime', 'until' => 'datetime']
|
||||
* echo "Please select booking dates";
|
||||
* }
|
||||
*
|
||||
* // Check all cart items before checkout
|
||||
* foreach ($cart->items as $item) {
|
||||
* $required = $item->requiredAdjustments();
|
||||
* if (!empty($required)) {
|
||||
* // Handle missing information
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @return array Array of required field adjustments, e.g., ['from' => 'datetime', 'until' => 'datetime']
|
||||
*/
|
||||
public function requiredAdjustments(): array
|
||||
{
|
||||
$adjustments = [];
|
||||
|
||||
// Only check if purchasable is a Product
|
||||
if ($this->purchasable_type !== config('shop.models.product', Product::class)) {
|
||||
return $adjustments;
|
||||
}
|
||||
|
||||
$product = $this->purchasable;
|
||||
|
||||
if (!$product) {
|
||||
return $adjustments;
|
||||
}
|
||||
|
||||
// Check if dates are required (for booking products or pools with booking items)
|
||||
$requiresDates = $product->isBooking() ||
|
||||
($product->isPool() && $product->hasBookingSingleItems());
|
||||
|
||||
if ($requiresDates) {
|
||||
if (is_null($this->from)) {
|
||||
$adjustments['from'] = 'datetime';
|
||||
}
|
||||
|
||||
if (is_null($this->until)) {
|
||||
$adjustments['until'] = 'datetime';
|
||||
}
|
||||
}
|
||||
|
||||
return $adjustments;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -513,7 +513,7 @@ class Product extends Model implements Purchasable, Cartable
|
|||
{
|
||||
// If this is a pool product and it has no direct price, inherit from single items
|
||||
if ($this->isPool() && !$this->hasPrice()) {
|
||||
return $this->getInheritedPoolPrice();
|
||||
return $this->getInheritedPoolPrice($sales_price);
|
||||
}
|
||||
|
||||
// If pool has a direct price, use it
|
||||
|
|
@ -528,7 +528,7 @@ class Product extends Model implements Purchasable, Cartable
|
|||
/**
|
||||
* Get inherited price from single items based on pricing strategy
|
||||
*/
|
||||
protected function getInheritedPoolPrice(): ?float
|
||||
protected function getInheritedPoolPrice(bool|null $sales_price = null): ?float
|
||||
{
|
||||
if (!$this->isPool()) {
|
||||
return null;
|
||||
|
|
@ -542,8 +542,8 @@ class Product extends Model implements Purchasable, Cartable
|
|||
return null;
|
||||
}
|
||||
|
||||
$prices = $singleItems->map(function ($item) {
|
||||
return $item->defaultPrice()->first()?->getCurrentPrice($item->isOnSale());
|
||||
$prices = $singleItems->map(function ($item) use ($sales_price) {
|
||||
return $item->defaultPrice()->first()?->getCurrentPrice($sales_price ?? $item->isOnSale());
|
||||
})->filter()->values();
|
||||
|
||||
if ($prices->isEmpty()) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,796 @@
|
|||
<?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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,535 @@
|
|||
<?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\CartItem;
|
||||
use Blax\Shop\Models\Product;
|
||||
use Blax\Shop\Models\ProductPrice;
|
||||
use Blax\Shop\Tests\TestCase;
|
||||
use Carbon\Carbon;
|
||||
use Workbench\App\Models\User;
|
||||
|
||||
class CartItemRequiredAdjustmentsTest extends TestCase
|
||||
{
|
||||
protected User $user;
|
||||
protected Cart $cart;
|
||||
|
||||
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),
|
||||
]);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_returns_empty_array_for_simple_product()
|
||||
{
|
||||
$product = Product::factory()->create([
|
||||
'type' => ProductType::SIMPLE,
|
||||
]);
|
||||
|
||||
ProductPrice::factory()->create([
|
||||
'purchasable_id' => $product->id,
|
||||
'purchasable_type' => Product::class,
|
||||
'unit_amount' => 5000, // 50.00
|
||||
'currency' => 'USD',
|
||||
'is_default' => true,
|
||||
]);
|
||||
|
||||
$cartItem = $this->cart->addToCart($product, 1);
|
||||
|
||||
$adjustments = $cartItem->requiredAdjustments();
|
||||
|
||||
$this->assertIsArray($adjustments);
|
||||
$this->assertEmpty($adjustments);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_returns_from_and_until_for_booking_product_without_dates()
|
||||
{
|
||||
$product = Product::factory()->create([
|
||||
'type' => ProductType::BOOKING,
|
||||
]);
|
||||
|
||||
ProductPrice::factory()->create([
|
||||
'purchasable_id' => $product->id,
|
||||
'purchasable_type' => Product::class,
|
||||
'unit_amount' => 5000,
|
||||
'currency' => 'USD',
|
||||
'is_default' => true,
|
||||
]);
|
||||
|
||||
$cartItem = $this->cart->addToCart($product, 1);
|
||||
|
||||
$adjustments = $cartItem->requiredAdjustments();
|
||||
|
||||
$this->assertEquals([
|
||||
'from' => 'datetime',
|
||||
'until' => 'datetime',
|
||||
], $adjustments);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_returns_only_until_for_booking_product_with_from_date()
|
||||
{
|
||||
$product = Product::factory()->create([
|
||||
'type' => ProductType::BOOKING,
|
||||
]);
|
||||
|
||||
ProductPrice::factory()->create([
|
||||
'purchasable_id' => $product->id,
|
||||
'purchasable_type' => Product::class,
|
||||
'unit_amount' => 5000,
|
||||
'currency' => 'USD',
|
||||
'is_default' => true,
|
||||
]);
|
||||
|
||||
$from = Carbon::now()->addDays(1);
|
||||
|
||||
$cartItem = $this->cart->addToCart($product, 1);
|
||||
$cartItem->update(['from' => $from]);
|
||||
|
||||
$adjustments = $cartItem->requiredAdjustments();
|
||||
|
||||
$this->assertEquals([
|
||||
'until' => 'datetime',
|
||||
], $adjustments);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_returns_only_from_for_booking_product_with_until_date()
|
||||
{
|
||||
$product = Product::factory()->create([
|
||||
'type' => ProductType::BOOKING,
|
||||
]);
|
||||
|
||||
ProductPrice::factory()->create([
|
||||
'purchasable_id' => $product->id,
|
||||
'purchasable_type' => Product::class,
|
||||
'unit_amount' => 5000,
|
||||
'currency' => 'USD',
|
||||
'is_default' => true,
|
||||
]);
|
||||
|
||||
$until = Carbon::now()->addDays(5);
|
||||
|
||||
$cartItem = $this->cart->addToCart($product, 1);
|
||||
$cartItem->update(['until' => $until]);
|
||||
|
||||
$adjustments = $cartItem->requiredAdjustments();
|
||||
|
||||
$this->assertEquals([
|
||||
'from' => 'datetime',
|
||||
], $adjustments);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_returns_empty_array_for_booking_product_with_both_dates()
|
||||
{
|
||||
$product = Product::factory()->create([
|
||||
'type' => ProductType::BOOKING,
|
||||
'manage_stock' => true,
|
||||
]);
|
||||
|
||||
$product->increaseStock(10);
|
||||
|
||||
ProductPrice::factory()->create([
|
||||
'purchasable_id' => $product->id,
|
||||
'purchasable_type' => Product::class,
|
||||
'unit_amount' => 5000,
|
||||
'currency' => 'USD',
|
||||
'is_default' => true,
|
||||
]);
|
||||
|
||||
$from = Carbon::now()->addDays(1);
|
||||
$until = Carbon::now()->addDays(5);
|
||||
|
||||
$cartItem = $this->cart->addToCart($product, 1, [], $from, $until);
|
||||
|
||||
$adjustments = $cartItem->requiredAdjustments();
|
||||
|
||||
$this->assertEmpty($adjustments);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_returns_dates_for_pool_with_booking_single_items_without_dates()
|
||||
{
|
||||
// Create pool product
|
||||
$poolProduct = Product::factory()->create([
|
||||
'name' => 'Parking Pool',
|
||||
'type' => ProductType::POOL,
|
||||
]);
|
||||
|
||||
// Create booking single items
|
||||
$singleItem1 = Product::factory()->create([
|
||||
'name' => 'Parking Spot 1',
|
||||
'type' => ProductType::BOOKING,
|
||||
]);
|
||||
|
||||
ProductPrice::factory()->create([
|
||||
'purchasable_id' => $singleItem1->id,
|
||||
'purchasable_type' => Product::class,
|
||||
'unit_amount' => 2000,
|
||||
'currency' => 'USD',
|
||||
'is_default' => true,
|
||||
]);
|
||||
|
||||
$singleItem2 = Product::factory()->create([
|
||||
'name' => 'Parking Spot 2',
|
||||
'type' => ProductType::BOOKING,
|
||||
]);
|
||||
|
||||
ProductPrice::factory()->create([
|
||||
'purchasable_id' => $singleItem2->id,
|
||||
'purchasable_type' => Product::class,
|
||||
'unit_amount' => 5000,
|
||||
'currency' => 'USD',
|
||||
'is_default' => true,
|
||||
]);
|
||||
|
||||
// Attach single items to pool
|
||||
$poolProduct->productRelations()->attach($singleItem1->id, [
|
||||
'type' => ProductRelationType::SINGLE->value,
|
||||
]);
|
||||
$poolProduct->productRelations()->attach($singleItem2->id, [
|
||||
'type' => ProductRelationType::SINGLE->value,
|
||||
]);
|
||||
|
||||
$cartItem = $this->cart->addToCart($poolProduct, 1);
|
||||
|
||||
$adjustments = $cartItem->requiredAdjustments();
|
||||
|
||||
$this->assertEquals([
|
||||
'from' => 'datetime',
|
||||
'until' => 'datetime',
|
||||
], $adjustments);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_returns_empty_array_for_pool_with_booking_items_with_dates()
|
||||
{
|
||||
// Create pool product
|
||||
$poolProduct = Product::factory()->create([
|
||||
'name' => 'Parking Pool',
|
||||
'type' => ProductType::POOL,
|
||||
]);
|
||||
|
||||
// Create booking single items
|
||||
$singleItem = Product::factory()->create([
|
||||
'name' => 'Parking Spot 1',
|
||||
'type' => ProductType::BOOKING,
|
||||
'manage_stock' => true,
|
||||
]);
|
||||
|
||||
$singleItem->increaseStock(10);
|
||||
|
||||
ProductPrice::factory()->create([
|
||||
'purchasable_id' => $singleItem->id,
|
||||
'purchasable_type' => Product::class,
|
||||
'unit_amount' => 2000,
|
||||
'currency' => 'USD',
|
||||
'is_default' => true,
|
||||
]);
|
||||
|
||||
// Attach single item to pool
|
||||
$poolProduct->productRelations()->attach($singleItem->id, [
|
||||
'type' => ProductRelationType::SINGLE->value,
|
||||
]);
|
||||
|
||||
$from = Carbon::now()->addDays(1);
|
||||
$until = Carbon::now()->addDays(5);
|
||||
|
||||
$cartItem = $this->cart->addToCart($poolProduct, 1, [], $from, $until);
|
||||
|
||||
$adjustments = $cartItem->requiredAdjustments();
|
||||
|
||||
$this->assertEmpty($adjustments);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_returns_empty_array_for_pool_with_simple_single_items()
|
||||
{
|
||||
// Create pool product
|
||||
$poolProduct = Product::factory()->create([
|
||||
'name' => 'Product Bundle',
|
||||
'type' => ProductType::POOL,
|
||||
]);
|
||||
|
||||
// Create simple single items
|
||||
$singleItem1 = Product::factory()->create([
|
||||
'name' => 'Item 1',
|
||||
'type' => ProductType::SIMPLE,
|
||||
]);
|
||||
|
||||
ProductPrice::factory()->create([
|
||||
'purchasable_id' => $singleItem1->id,
|
||||
'purchasable_type' => Product::class,
|
||||
'unit_amount' => 1000,
|
||||
'currency' => 'USD',
|
||||
'is_default' => true,
|
||||
]);
|
||||
|
||||
$singleItem2 = Product::factory()->create([
|
||||
'name' => 'Item 2',
|
||||
'type' => ProductType::SIMPLE,
|
||||
]);
|
||||
|
||||
ProductPrice::factory()->create([
|
||||
'purchasable_id' => $singleItem2->id,
|
||||
'purchasable_type' => Product::class,
|
||||
'unit_amount' => 2000,
|
||||
'currency' => 'USD',
|
||||
'is_default' => true,
|
||||
]);
|
||||
|
||||
// Attach single items to pool
|
||||
$poolProduct->productRelations()->attach($singleItem1->id, [
|
||||
'type' => ProductRelationType::SINGLE->value,
|
||||
]);
|
||||
$poolProduct->productRelations()->attach($singleItem2->id, [
|
||||
'type' => ProductRelationType::SINGLE->value,
|
||||
]);
|
||||
|
||||
$cartItem = $this->cart->addToCart($poolProduct, 1);
|
||||
|
||||
$adjustments = $cartItem->requiredAdjustments();
|
||||
|
||||
$this->assertEmpty($adjustments);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_returns_dates_for_pool_with_mixed_single_items_containing_bookings()
|
||||
{
|
||||
// Create pool product
|
||||
$poolProduct = Product::factory()->create([
|
||||
'name' => 'Mixed Pool',
|
||||
'type' => ProductType::POOL,
|
||||
]);
|
||||
|
||||
// Create simple single item
|
||||
$simpleItem = Product::factory()->create([
|
||||
'name' => 'Simple Item',
|
||||
'type' => ProductType::SIMPLE,
|
||||
]);
|
||||
|
||||
ProductPrice::factory()->create([
|
||||
'purchasable_id' => $simpleItem->id,
|
||||
'purchasable_type' => Product::class,
|
||||
'unit_amount' => 1000,
|
||||
'currency' => 'USD',
|
||||
'is_default' => true,
|
||||
]);
|
||||
|
||||
// Create booking single item
|
||||
$bookingItem = Product::factory()->create([
|
||||
'name' => 'Booking Item',
|
||||
'type' => ProductType::BOOKING,
|
||||
]);
|
||||
|
||||
ProductPrice::factory()->create([
|
||||
'purchasable_id' => $bookingItem->id,
|
||||
'purchasable_type' => Product::class,
|
||||
'unit_amount' => 2000,
|
||||
'currency' => 'USD',
|
||||
'is_default' => true,
|
||||
]);
|
||||
|
||||
// Attach single items to pool
|
||||
$poolProduct->productRelations()->attach($simpleItem->id, [
|
||||
'type' => ProductRelationType::SINGLE->value,
|
||||
]);
|
||||
$poolProduct->productRelations()->attach($bookingItem->id, [
|
||||
'type' => ProductRelationType::SINGLE->value,
|
||||
]);
|
||||
|
||||
$cartItem = $this->cart->addToCart($poolProduct, 1);
|
||||
|
||||
$adjustments = $cartItem->requiredAdjustments();
|
||||
|
||||
// Even though it has simple items, the booking item requires dates
|
||||
$this->assertEquals([
|
||||
'from' => 'datetime',
|
||||
'until' => 'datetime',
|
||||
], $adjustments);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_returns_empty_array_for_non_product_purchasable()
|
||||
{
|
||||
// Create a cart item with a non-product purchasable
|
||||
$cartItem = new CartItem([
|
||||
'cart_id' => $this->cart->id,
|
||||
'purchasable_id' => 1,
|
||||
'purchasable_type' => 'App\\Models\\Subscription', // Not a product
|
||||
'quantity' => 1,
|
||||
'price' => 1000,
|
||||
]);
|
||||
$cartItem->save();
|
||||
|
||||
$adjustments = $cartItem->requiredAdjustments();
|
||||
|
||||
$this->assertEmpty($adjustments);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_handles_null_purchasable_gracefully()
|
||||
{
|
||||
// Create a cart item with invalid purchasable_id
|
||||
$cartItem = new CartItem([
|
||||
'cart_id' => $this->cart->id,
|
||||
'purchasable_id' => 99999,
|
||||
'purchasable_type' => config('shop.models.product', Product::class),
|
||||
'quantity' => 1,
|
||||
'price' => 1000,
|
||||
]);
|
||||
$cartItem->save();
|
||||
|
||||
$adjustments = $cartItem->requiredAdjustments();
|
||||
|
||||
$this->assertEmpty($adjustments);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_can_validate_entire_cart_before_checkout()
|
||||
{
|
||||
// Create mixed cart with booking and simple products
|
||||
$simpleProduct = Product::factory()->create([
|
||||
'type' => ProductType::SIMPLE,
|
||||
]);
|
||||
|
||||
ProductPrice::factory()->create([
|
||||
'purchasable_id' => $simpleProduct->id,
|
||||
'purchasable_type' => Product::class,
|
||||
'unit_amount' => 1000,
|
||||
'currency' => 'USD',
|
||||
'is_default' => true,
|
||||
]);
|
||||
|
||||
$bookingProduct = Product::factory()->create([
|
||||
'type' => ProductType::BOOKING,
|
||||
]);
|
||||
|
||||
ProductPrice::factory()->create([
|
||||
'purchasable_id' => $bookingProduct->id,
|
||||
'purchasable_type' => Product::class,
|
||||
'unit_amount' => 5000,
|
||||
'currency' => 'USD',
|
||||
'is_default' => true,
|
||||
]);
|
||||
|
||||
// Add both to cart
|
||||
$this->cart->addToCart($simpleProduct, 1);
|
||||
$this->cart->addToCart($bookingProduct, 1);
|
||||
|
||||
// Check which items need adjustments
|
||||
$itemsNeedingAdjustments = $this->cart->items->filter(function ($item) {
|
||||
return !empty($item->requiredAdjustments());
|
||||
});
|
||||
|
||||
// Only the booking product should need adjustments
|
||||
$this->assertCount(1, $itemsNeedingAdjustments);
|
||||
$this->assertEquals($bookingProduct->id, $itemsNeedingAdjustments->first()->purchasable_id);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function cart_can_get_items_requiring_adjustments()
|
||||
{
|
||||
$simpleProduct = Product::factory()->create([
|
||||
'type' => ProductType::SIMPLE,
|
||||
]);
|
||||
|
||||
ProductPrice::factory()->create([
|
||||
'purchasable_id' => $simpleProduct->id,
|
||||
'purchasable_type' => Product::class,
|
||||
'unit_amount' => 1000,
|
||||
'currency' => 'USD',
|
||||
'is_default' => true,
|
||||
]);
|
||||
|
||||
$bookingProduct = Product::factory()->create([
|
||||
'type' => ProductType::BOOKING,
|
||||
]);
|
||||
|
||||
ProductPrice::factory()->create([
|
||||
'purchasable_id' => $bookingProduct->id,
|
||||
'purchasable_type' => Product::class,
|
||||
'unit_amount' => 5000,
|
||||
'currency' => 'USD',
|
||||
'is_default' => true,
|
||||
]);
|
||||
|
||||
$this->cart->addToCart($simpleProduct, 1);
|
||||
$this->cart->addToCart($bookingProduct, 1);
|
||||
|
||||
$incompleteItems = $this->cart->getItemsRequiringAdjustments();
|
||||
|
||||
$this->assertCount(1, $incompleteItems);
|
||||
$this->assertEquals($bookingProduct->id, $incompleteItems->first()->purchasable_id);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function cart_is_not_ready_for_checkout_when_items_need_adjustments()
|
||||
{
|
||||
$bookingProduct = Product::factory()->create([
|
||||
'type' => ProductType::BOOKING,
|
||||
]);
|
||||
|
||||
ProductPrice::factory()->create([
|
||||
'purchasable_id' => $bookingProduct->id,
|
||||
'purchasable_type' => Product::class,
|
||||
'unit_amount' => 5000,
|
||||
'currency' => 'USD',
|
||||
'is_default' => true,
|
||||
]);
|
||||
|
||||
$this->cart->addToCart($bookingProduct, 1);
|
||||
|
||||
$this->assertFalse($this->cart->isReadyForCheckout());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function cart_is_ready_for_checkout_when_all_items_complete()
|
||||
{
|
||||
$simpleProduct = Product::factory()->create([
|
||||
'type' => ProductType::SIMPLE,
|
||||
]);
|
||||
|
||||
ProductPrice::factory()->create([
|
||||
'purchasable_id' => $simpleProduct->id,
|
||||
'purchasable_type' => Product::class,
|
||||
'unit_amount' => 1000,
|
||||
'currency' => 'USD',
|
||||
'is_default' => true,
|
||||
]);
|
||||
|
||||
$bookingProduct = Product::factory()->create([
|
||||
'type' => ProductType::BOOKING,
|
||||
'manage_stock' => true,
|
||||
]);
|
||||
|
||||
$bookingProduct->increaseStock(10);
|
||||
|
||||
ProductPrice::factory()->create([
|
||||
'purchasable_id' => $bookingProduct->id,
|
||||
'purchasable_type' => Product::class,
|
||||
'unit_amount' => 5000,
|
||||
'currency' => 'USD',
|
||||
'is_default' => true,
|
||||
]);
|
||||
|
||||
$from = Carbon::now()->addDays(1);
|
||||
$until = Carbon::now()->addDays(5);
|
||||
|
||||
$this->cart->addToCart($simpleProduct, 1);
|
||||
$this->cart->addToCart($bookingProduct, 1, [], $from, $until);
|
||||
|
||||
$this->assertTrue($this->cart->isReadyForCheckout());
|
||||
}
|
||||
}
|
||||
|
|
@ -45,22 +45,12 @@ class CartManagementTest extends TestCase
|
|||
/** @test */
|
||||
public function it_can_add_items_to_cart()
|
||||
{
|
||||
$product = Product::factory()->create();
|
||||
$price = ProductPrice::factory()->create([
|
||||
'purchasable_id' => $product->id,
|
||||
'purchasable_type' => get_class($product),
|
||||
]);
|
||||
$product = Product::factory()->withPrices()->create();
|
||||
$price = $product->defaultPrice()->first();
|
||||
|
||||
$cart = Cart::create();
|
||||
|
||||
$cartItem = CartItem::create([
|
||||
'cart_id' => $cart->id,
|
||||
'purchasable_id' => $price->id,
|
||||
'purchasable_type' => get_class($price),
|
||||
'quantity' => 2,
|
||||
'price' => $price->unit_amount,
|
||||
'subtotal' => $price->unit_amount * 2,
|
||||
]);
|
||||
$cartItem = $cart->addToCart($price, quantity: 2);
|
||||
|
||||
$this->assertCount(1, $cart->fresh()->items);
|
||||
$this->assertEquals(2, $cart->items->first()->quantity);
|
||||
|
|
@ -70,12 +60,8 @@ class CartManagementTest extends TestCase
|
|||
public function it_can_update_cart_item_quantity()
|
||||
{
|
||||
$cart = Cart::create();
|
||||
$product = Product::factory()->create();
|
||||
$price = ProductPrice::factory()->create([
|
||||
'purchasable_id' => $product->id,
|
||||
'purchasable_type' => get_class($product),
|
||||
'unit_amount' => 50.00,
|
||||
]);
|
||||
$product = Product::factory()->withPrices(unit_amount: 50.00)->create();
|
||||
$price = $product->defaultPrice()->first();
|
||||
|
||||
$cartItem = $cart->addToCart($price, quantity: 1);
|
||||
$cartItem->update(['quantity' => 3]);
|
||||
|
|
@ -87,12 +73,8 @@ class CartManagementTest extends TestCase
|
|||
public function it_can_remove_items_from_cart()
|
||||
{
|
||||
$cart = Cart::create();
|
||||
$product = Product::factory()->create();
|
||||
$price = ProductPrice::factory()->create([
|
||||
'purchasable_id' => $product->id,
|
||||
'purchasable_type' => get_class($product),
|
||||
'unit_amount' => 100.00,
|
||||
]);
|
||||
$product = Product::factory()->withPrices(unit_amount: 100.00)->create();
|
||||
$price = $product->defaultPrice()->first();
|
||||
|
||||
$cartItem = $cart->addToCart($price, quantity: 1);
|
||||
|
||||
|
|
@ -107,20 +89,11 @@ class CartManagementTest extends TestCase
|
|||
public function it_calculates_cart_total_correctly()
|
||||
{
|
||||
$cart = Cart::create();
|
||||
$product1 = Product::factory()->create();
|
||||
$product2 = Product::factory()->create();
|
||||
$product1 = Product::factory()->withPrices(unit_amount: 50.00)->create();
|
||||
$product2 = Product::factory()->withPrices(unit_amount: 30.00)->create();
|
||||
|
||||
$productPrice1 = ProductPrice::factory()->create([
|
||||
'purchasable_id' => $product1->id,
|
||||
'purchasable_type' => get_class($product1),
|
||||
'unit_amount' => 50.00,
|
||||
]);
|
||||
|
||||
$productPrice2 = ProductPrice::factory()->create([
|
||||
'purchasable_id' => $product2->id,
|
||||
'purchasable_type' => get_class($product2),
|
||||
'unit_amount' => 30.00,
|
||||
]);
|
||||
$productPrice1 = $product1->defaultPrice()->first();
|
||||
$productPrice2 = $product2->defaultPrice()->first();
|
||||
|
||||
$cart->addToCart($productPrice1, quantity: 2);
|
||||
$cart->addToCart($productPrice2, quantity: 1);
|
||||
|
|
@ -134,20 +107,11 @@ class CartManagementTest extends TestCase
|
|||
public function it_calculates_total_items_correctly()
|
||||
{
|
||||
$cart = Cart::create();
|
||||
$product1 = Product::factory()->create();
|
||||
$product2 = Product::factory()->create();
|
||||
$product1 = Product::factory()->withPrices(unit_amount: 10.00)->create();
|
||||
$product2 = Product::factory()->withPrices(unit_amount: 20.00)->create();
|
||||
|
||||
$product1Price = ProductPrice::factory()->create([
|
||||
'purchasable_id' => $product1->id,
|
||||
'purchasable_type' => get_class($product1),
|
||||
'unit_amount' => 10.00,
|
||||
]);
|
||||
|
||||
$product2Price = ProductPrice::factory()->create([
|
||||
'purchasable_id' => $product2->id,
|
||||
'purchasable_type' => get_class($product2),
|
||||
'unit_amount' => 20.00,
|
||||
]);
|
||||
$product1Price = $product1->defaultPrice()->first();
|
||||
$product2Price = $product2->defaultPrice()->first();
|
||||
|
||||
$cart->addToCart($product1Price, quantity: 3);
|
||||
$cart->addToCart($product2Price, quantity: 2);
|
||||
|
|
@ -238,13 +202,8 @@ class CartManagementTest extends TestCase
|
|||
public function cart_items_have_correct_relationships()
|
||||
{
|
||||
$cart = Cart::create();
|
||||
$product = Product::factory()->create();
|
||||
|
||||
$productPrice = ProductPrice::factory()->create([
|
||||
'purchasable_id' => $product->id,
|
||||
'purchasable_type' => get_class($product),
|
||||
'unit_amount' => 45.00,
|
||||
]);
|
||||
$product = Product::factory()->withPrices(unit_amount: 45.00)->create();
|
||||
$productPrice = $product->defaultPrice()->first();
|
||||
|
||||
$cartItem = $cart->addToCart($productPrice, quantity: 1);
|
||||
|
||||
|
|
@ -256,13 +215,8 @@ class CartManagementTest extends TestCase
|
|||
public function it_calculates_cart_item_subtotal()
|
||||
{
|
||||
$cart = Cart::create();
|
||||
$product = Product::factory()->create();
|
||||
|
||||
$productPrice = ProductPrice::factory()->create([
|
||||
'purchasable_id' => $product->id,
|
||||
'purchasable_type' => get_class($product),
|
||||
'unit_amount' => 25.00,
|
||||
]);
|
||||
$product = Product::factory()->withPrices(unit_amount: 25.00)->create();
|
||||
$productPrice = $product->defaultPrice()->first();
|
||||
|
||||
$cartItem = $cart->addToCart($productPrice, quantity: 4);
|
||||
|
||||
|
|
@ -273,13 +227,8 @@ class CartManagementTest extends TestCase
|
|||
public function it_can_store_cart_item_attributes()
|
||||
{
|
||||
$cart = Cart::create();
|
||||
$product = Product::factory()->create();
|
||||
|
||||
$productPrice = ProductPrice::factory()->create([
|
||||
'purchasable_id' => $product->id,
|
||||
'purchasable_type' => get_class($product),
|
||||
'unit_amount' => 50.00,
|
||||
]);
|
||||
$product = Product::factory()->withPrices(unit_amount: 50.00)->create();
|
||||
$productPrice = $product->defaultPrice()->first();
|
||||
|
||||
$cartItem = $cart->addToCart(
|
||||
$productPrice,
|
||||
|
|
@ -298,13 +247,8 @@ class CartManagementTest extends TestCase
|
|||
public function it_can_have_multiple_items_of_same_product_with_different_attributes()
|
||||
{
|
||||
$cart = Cart::create();
|
||||
$product = Product::factory()->create();
|
||||
|
||||
$productPrice = ProductPrice::factory()->create([
|
||||
'purchasable_id' => $product->id,
|
||||
'purchasable_type' => get_class($product),
|
||||
'unit_amount' => 30.00,
|
||||
]);
|
||||
$product = Product::factory()->withPrices(unit_amount: 30.00)->create();
|
||||
$productPrice = $product->defaultPrice()->first();
|
||||
|
||||
$cart->addToCart(
|
||||
$productPrice,
|
||||
|
|
@ -326,13 +270,8 @@ class CartManagementTest extends TestCase
|
|||
public function it_deletes_cart_items_when_cart_is_deleted()
|
||||
{
|
||||
$cart = Cart::create();
|
||||
$product = Product::factory()->create();
|
||||
|
||||
$productPrice = ProductPrice::factory()->create([
|
||||
'purchasable_id' => $product->id,
|
||||
'purchasable_type' => get_class($product),
|
||||
'unit_amount' => 75.00,
|
||||
]);
|
||||
$product = Product::factory()->withPrices(unit_amount: 75.00)->create();
|
||||
$productPrice = $product->defaultPrice()->first();
|
||||
|
||||
$cartItem = $cart->addToCart(
|
||||
$productPrice,
|
||||
|
|
@ -398,15 +337,11 @@ class CartManagementTest extends TestCase
|
|||
}
|
||||
|
||||
/** @test */
|
||||
public function it_can_remove_item_from_cart_completely()
|
||||
public function it_can_remove_entire_cart_item()
|
||||
{
|
||||
$cart = Cart::create();
|
||||
$product = Product::factory()->create();
|
||||
$price = ProductPrice::factory()->create([
|
||||
'purchasable_id' => $product->id,
|
||||
'purchasable_type' => get_class($product),
|
||||
'unit_amount' => 50.00,
|
||||
]);
|
||||
$product = Product::factory()->withPrices(unit_amount: 50.00)->create();
|
||||
$price = $product->defaultPrice()->first();
|
||||
|
||||
$cartItem = $cart->addToCart($price, quantity: 2);
|
||||
$this->assertCount(1, $cart->items);
|
||||
|
|
@ -421,12 +356,8 @@ class CartManagementTest extends TestCase
|
|||
public function it_can_decrease_cart_item_quantity()
|
||||
{
|
||||
$cart = Cart::create();
|
||||
$product = Product::factory()->create();
|
||||
$price = ProductPrice::factory()->create([
|
||||
'purchasable_id' => $product->id,
|
||||
'purchasable_type' => get_class($product),
|
||||
'unit_amount' => 75.00,
|
||||
]);
|
||||
$product = Product::factory()->withPrices(unit_amount: 75.00)->create();
|
||||
$price = $product->defaultPrice()->first();
|
||||
|
||||
$cartItem = $cart->addToCart($price, quantity: 5);
|
||||
$this->assertEquals(5, $cartItem->quantity);
|
||||
|
|
@ -442,12 +373,8 @@ class CartManagementTest extends TestCase
|
|||
public function it_updates_subtotal_correctly_when_decreasing_quantity()
|
||||
{
|
||||
$cart = Cart::create();
|
||||
$product = Product::factory()->create();
|
||||
$price = ProductPrice::factory()->create([
|
||||
'purchasable_id' => $product->id,
|
||||
'purchasable_type' => get_class($product),
|
||||
'unit_amount' => 100.00,
|
||||
]);
|
||||
$product = Product::factory()->withPrices(unit_amount: 100.00)->create();
|
||||
$price = $product->defaultPrice()->first();
|
||||
|
||||
$cart->addToCart($price, quantity: 4);
|
||||
|
||||
|
|
@ -462,12 +389,8 @@ class CartManagementTest extends TestCase
|
|||
public function it_respects_parameters_when_removing_from_cart()
|
||||
{
|
||||
$cart = Cart::create();
|
||||
$product = Product::factory()->create();
|
||||
$price = ProductPrice::factory()->create([
|
||||
'purchasable_id' => $product->id,
|
||||
'purchasable_type' => get_class($product),
|
||||
'unit_amount' => 50.00,
|
||||
]);
|
||||
$product = Product::factory()->withPrices(unit_amount: 50.00)->create();
|
||||
$price = $product->defaultPrice()->first();
|
||||
|
||||
// Add same product with different parameters
|
||||
$cartItem1 = $cart->addToCart(
|
||||
|
|
@ -496,12 +419,8 @@ class CartManagementTest extends TestCase
|
|||
public function it_decreases_only_matching_parameters_when_removing()
|
||||
{
|
||||
$cart = Cart::create();
|
||||
$product = Product::factory()->create();
|
||||
$price = ProductPrice::factory()->create([
|
||||
'purchasable_id' => $product->id,
|
||||
'purchasable_type' => get_class($product),
|
||||
'unit_amount' => 50.00,
|
||||
]);
|
||||
$product = Product::factory()->withPrices(unit_amount: 50.00)->create();
|
||||
$price = $product->defaultPrice()->first();
|
||||
|
||||
$cart->addToCart(
|
||||
$price,
|
||||
|
|
@ -520,12 +439,8 @@ class CartManagementTest extends TestCase
|
|||
public function it_returns_cart_item_when_quantity_is_decreased()
|
||||
{
|
||||
$cart = Cart::create();
|
||||
$product = Product::factory()->create();
|
||||
$price = ProductPrice::factory()->create([
|
||||
'purchasable_id' => $product->id,
|
||||
'purchasable_type' => get_class($product),
|
||||
'unit_amount' => 50.00,
|
||||
]);
|
||||
$product = Product::factory()->withPrices(unit_amount: 50.00)->create();
|
||||
$price = $product->defaultPrice()->first();
|
||||
|
||||
$cart->addToCart($price, quantity: 5);
|
||||
|
||||
|
|
@ -539,12 +454,8 @@ class CartManagementTest extends TestCase
|
|||
public function it_handles_removing_nonexistent_item_gracefully()
|
||||
{
|
||||
$cart = Cart::create();
|
||||
$product = Product::factory()->create();
|
||||
$price = ProductPrice::factory()->create([
|
||||
'purchasable_id' => $product->id,
|
||||
'purchasable_type' => get_class($product),
|
||||
'unit_amount' => 50.00,
|
||||
]);
|
||||
$product = Product::factory()->withPrices(unit_amount: 50.00)->create();
|
||||
$price = $product->defaultPrice()->first();
|
||||
|
||||
$result = $cart->removeFromCart($price, quantity: 1);
|
||||
|
||||
|
|
@ -557,12 +468,8 @@ class CartManagementTest extends TestCase
|
|||
public function it_updates_cart_total_after_removing_items()
|
||||
{
|
||||
$cart = Cart::create();
|
||||
$product = Product::factory()->create();
|
||||
$price = ProductPrice::factory()->create([
|
||||
'purchasable_id' => $product->id,
|
||||
'purchasable_type' => get_class($product),
|
||||
'unit_amount' => 50.00,
|
||||
]);
|
||||
$product = Product::factory()->withPrices(unit_amount: 50.00)->create();
|
||||
$price = $product->defaultPrice()->first();
|
||||
|
||||
$cart->addToCart($price, quantity: 5);
|
||||
$this->assertEquals(250.00, $cart->getTotal());
|
||||
|
|
@ -576,20 +483,11 @@ class CartManagementTest extends TestCase
|
|||
public function it_can_remove_from_cart_with_multiple_items()
|
||||
{
|
||||
$cart = Cart::create();
|
||||
$product1 = Product::factory()->create();
|
||||
$product2 = Product::factory()->create();
|
||||
$product1 = Product::factory()->withPrices(unit_amount: 50.00)->create();
|
||||
$product2 = Product::factory()->withPrices(unit_amount: 75.00)->create();
|
||||
|
||||
$price1 = ProductPrice::factory()->create([
|
||||
'purchasable_id' => $product1->id,
|
||||
'purchasable_type' => get_class($product1),
|
||||
'unit_amount' => 50.00,
|
||||
]);
|
||||
|
||||
$price2 = ProductPrice::factory()->create([
|
||||
'purchasable_id' => $product2->id,
|
||||
'purchasable_type' => get_class($product2),
|
||||
'unit_amount' => 75.00,
|
||||
]);
|
||||
$price1 = $product1->defaultPrice()->first();
|
||||
$price2 = $product2->defaultPrice()->first();
|
||||
|
||||
$cart->addToCart($price1, quantity: 2);
|
||||
$cart->addToCart($price2, quantity: 3);
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ class ProductManagementTest extends TestCase
|
|||
{
|
||||
$product = Product::factory()->create();
|
||||
|
||||
ProductPrice::create([
|
||||
ProductPrice::factory()->create([
|
||||
'purchasable_id' => $product->id,
|
||||
'purchasable_type' => get_class($product),
|
||||
'type' => 'one_time',
|
||||
|
|
@ -139,11 +139,11 @@ class ProductManagementTest extends TestCase
|
|||
'active' => true,
|
||||
]);
|
||||
|
||||
ProductPrice::create([
|
||||
ProductPrice::factory()->create([
|
||||
'purchasable_id' => $product->id,
|
||||
'purchasable_type' => get_class($product),
|
||||
'type' => 'recurring',
|
||||
'price' => 1999,
|
||||
'unit_amount' => 1999,
|
||||
'currency' => 'USD',
|
||||
'interval' => 'month',
|
||||
'active' => true,
|
||||
|
|
|
|||
|
|
@ -21,16 +21,11 @@ class PurchaseFlowTest extends TestCase
|
|||
public function user_can_purchase_a_product_directly()
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$product = Product::factory()->create([
|
||||
$product = Product::factory()->withPrices(unit_amount: 49.99)->create([
|
||||
'manage_stock' => false,
|
||||
]);
|
||||
|
||||
$price = ProductPrice::create([
|
||||
'purchasable_id' => $product->id,
|
||||
'purchasable_type' => get_class($product),
|
||||
'amount' => 4999, // in cents
|
||||
'currency' => 'USD',
|
||||
]);
|
||||
$price = $product->defaultPrice()->first();
|
||||
|
||||
$purchase = $user->purchase($price, quantity: 1);
|
||||
|
||||
|
|
|
|||
|
|
@ -17,13 +17,8 @@ class CartTest extends TestCase
|
|||
public function cart_can_add_product_price_directly()
|
||||
{
|
||||
$cart = Cart::create();
|
||||
$product = Product::factory()->create();
|
||||
$price = ProductPrice::create([
|
||||
'purchasable_id' => $product->id,
|
||||
'purchasable_type' => get_class($product),
|
||||
'unit_amount' => 100.00,
|
||||
'currency' => 'USD',
|
||||
]);
|
||||
$product = Product::factory()->withPrices(unit_amount: 100.00)->create();
|
||||
$price = $product->defaultPrice()->first();
|
||||
|
||||
$cartItem = $cart->addToCart($price, quantity: 2);
|
||||
|
||||
|
|
@ -36,13 +31,8 @@ class CartTest extends TestCase
|
|||
public function cart_calculates_subtotal_automatically()
|
||||
{
|
||||
$cart = Cart::create();
|
||||
$product = Product::factory()->create();
|
||||
$price = ProductPrice::create([
|
||||
'purchasable_id' => $product->id,
|
||||
'purchasable_type' => get_class($product),
|
||||
'unit_amount' => 50.00,
|
||||
'currency' => 'USD',
|
||||
]);
|
||||
$product = Product::factory()->withPrices(unit_amount: 50.00)->create();
|
||||
$price = $product->defaultPrice()->first();
|
||||
|
||||
$cartItem = $cart->addToCart($price, quantity: 3);
|
||||
|
||||
|
|
@ -62,7 +52,8 @@ class CartTest extends TestCase
|
|||
'is_default' => false,
|
||||
]);
|
||||
|
||||
$price = ProductPrice::create([
|
||||
// Create a second price using factory
|
||||
$price = ProductPrice::factory()->create([
|
||||
'purchasable_id' => $product->id,
|
||||
'purchasable_type' => get_class($product),
|
||||
'unit_amount' => 100.00,
|
||||
|
|
@ -85,13 +76,8 @@ class CartTest extends TestCase
|
|||
public function cart_can_add_items_with_custom_parameters()
|
||||
{
|
||||
$cart = Cart::create();
|
||||
$product = Product::factory()->create();
|
||||
$price = ProductPrice::create([
|
||||
'purchasable_id' => $product->id,
|
||||
'purchasable_type' => get_class($product),
|
||||
'unit_amount' => 50.00,
|
||||
'currency' => 'USD',
|
||||
]);
|
||||
$product = Product::factory()->withPrices(unit_amount: 50.00)->create();
|
||||
$price = $product->defaultPrice()->first();
|
||||
|
||||
$parameters = [
|
||||
'color' => 'red',
|
||||
|
|
@ -111,21 +97,11 @@ class CartTest extends TestCase
|
|||
{
|
||||
$cart = Cart::create();
|
||||
|
||||
$product1 = Product::factory()->create();
|
||||
$price1 = ProductPrice::create([
|
||||
'purchasable_id' => $product1->id,
|
||||
'purchasable_type' => get_class($product1),
|
||||
'unit_amount' => 25.00,
|
||||
'currency' => 'USD',
|
||||
]);
|
||||
$product1 = Product::factory()->withPrices(unit_amount: 25.00)->create();
|
||||
$price1 = $product1->defaultPrice()->first();
|
||||
|
||||
$product2 = Product::factory()->create();
|
||||
$price2 = ProductPrice::create([
|
||||
'purchasable_id' => $product2->id,
|
||||
'purchasable_type' => get_class($product2),
|
||||
'unit_amount' => 50.00,
|
||||
'currency' => 'USD',
|
||||
]);
|
||||
$product2 = Product::factory()->withPrices(unit_amount: 50.00)->create();
|
||||
$price2 = $product2->defaultPrice()->first();
|
||||
|
||||
$cart->addToCart($price1, quantity: 2); // 50
|
||||
$cart->addToCart($price2, quantity: 3); // 150
|
||||
|
|
@ -194,13 +170,8 @@ class CartTest extends TestCase
|
|||
public function cart_deletes_items_on_deletion()
|
||||
{
|
||||
$cart = Cart::create();
|
||||
$product = Product::factory()->create();
|
||||
$price = ProductPrice::create([
|
||||
'purchasable_id' => $product->id,
|
||||
'purchasable_type' => get_class($product),
|
||||
'unit_amount' => 50.00,
|
||||
'currency' => 'USD',
|
||||
]);
|
||||
$product = Product::factory()->withPrices(unit_amount: 50.00)->create();
|
||||
$price = $product->defaultPrice()->first();
|
||||
|
||||
$cartItem = $cart->addToCart($price);
|
||||
$cartItemId = $cartItem->id;
|
||||
|
|
|
|||
Loading…
Reference in New Issue