mirror of
https://github.com/wearepixel/laravel-cart.git
synced 2026-11-14 13:51:44 +00:00
feat: add applies_to support for quantity-limited item conditions
This commit is contained in:
@@ -607,6 +607,42 @@ #### Conditions on items
|
||||
Cart::add($item);
|
||||
```
|
||||
|
||||
##### Limiting a condition to a specific quantity of items
|
||||
|
||||
By default, an item condition applies to every unit of the item. Use `applies_to` to limit it to the first N units:
|
||||
|
||||
```php
|
||||
// 5% off the first item only - for 2x $50 items: (50*0.95) + 50 = $97.50
|
||||
$firstItemDiscount = new CartCondition([
|
||||
'name' => '5% Off First Item',
|
||||
'type' => 'discount',
|
||||
'value' => '-5%',
|
||||
'applies_to' => 1,
|
||||
]);
|
||||
|
||||
Cart::add([
|
||||
'id' => 1,
|
||||
'name' => 'Widget',
|
||||
'price' => 50.00,
|
||||
'quantity' => 2,
|
||||
'attributes' => [],
|
||||
'conditions' => $firstItemDiscount,
|
||||
]);
|
||||
|
||||
Cart::getSubTotal(); // 97.50
|
||||
```
|
||||
|
||||
When combining conditions, those without `applies_to` apply to every unit while those with `applies_to` only apply to the first N:
|
||||
|
||||
```php
|
||||
$limitedDiscount = new CartCondition(['name' => '10% Off First', 'type' => 'discount', 'value' => '-10%', 'applies_to' => 1]);
|
||||
$globalDiscount = new CartCondition(['name' => '5% Off All', 'type' => 'discount', 'value' => '-5%']);
|
||||
|
||||
// Item 1: 100 * 0.90 * 0.95 = 85.50
|
||||
// Item 2: 100 * 0.95 = 95.00
|
||||
// Subtotal: 180.50
|
||||
```
|
||||
|
||||
> NOTE: All cart per-item conditions should be added before calling **Cart::getSubTotal()**
|
||||
|
||||
Then Finally you can call **Cart::getSubTotal()** to get the Cart sub total with the applied conditions on each of the items.
|
||||
|
||||
@@ -78,6 +78,14 @@ public function getMaximum(): int|float|null
|
||||
return $this->args['maximum'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of items this condition applies to (null = all items)
|
||||
*/
|
||||
public function getAppliesToQuantity(): int|null
|
||||
{
|
||||
return isset($this->args['applies_to']) ? (int) $this->args['applies_to'] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the additional attributes of a condition
|
||||
*
|
||||
|
||||
+26
-1
@@ -170,6 +170,31 @@ public function getPriceWithConditions($formatted = true)
|
||||
*/
|
||||
public function getPriceSumWithConditions($formatted = true)
|
||||
{
|
||||
return Helpers::formatValue($this->getPriceWithConditions(false) * $this->quantity, $formatted, $this->config);
|
||||
$conditions = $this->hasConditions() ? $this->getConditions() : [];
|
||||
$conditions = is_array($conditions) ? $conditions : [$conditions];
|
||||
|
||||
$hasLimit = collect($conditions)->some(fn ($c) => $c->getAppliesToQuantity() !== null);
|
||||
|
||||
if (! $hasLimit) {
|
||||
return Helpers::formatValue($this->getPriceWithConditions(false) * $this->quantity, $formatted, $this->config);
|
||||
}
|
||||
|
||||
$total = 0.0;
|
||||
|
||||
for ($i = 1; $i <= $this->quantity; $i++) {
|
||||
$price = $this->price;
|
||||
|
||||
foreach ($conditions as $condition) {
|
||||
$limit = $condition->getAppliesToQuantity();
|
||||
|
||||
if ($limit === null || $i <= $limit) {
|
||||
$price = $condition->applyCondition($price);
|
||||
}
|
||||
}
|
||||
|
||||
$total += $price;
|
||||
}
|
||||
|
||||
return Helpers::formatValue($total, $formatted, $this->config);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
|
||||
use Wearepixel\Cart\Cart;
|
||||
use Wearepixel\Cart\CartCondition;
|
||||
use Wearepixel\Cart\Drivers\SessionDriver;
|
||||
use Wearepixel\Cart\Tests\Helpers\SessionMock;
|
||||
|
||||
beforeEach(function () {
|
||||
$events = Mockery::mock('Illuminate\Contracts\Events\Dispatcher');
|
||||
$events->shouldReceive('dispatch');
|
||||
|
||||
$this->cart = new Cart(
|
||||
new SessionDriver(new SessionMock, 'SAMPLESESSIONKEY'),
|
||||
$events,
|
||||
'cart',
|
||||
require (__DIR__ . '/../Helpers/ConfigConditionsMock.php')
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
Mockery::close();
|
||||
});
|
||||
|
||||
describe('quantity-limited item conditions', function () {
|
||||
test('condition without applies_to applies to all items', function () {
|
||||
$discount = new CartCondition([
|
||||
'name' => '5% Off',
|
||||
'type' => 'discount',
|
||||
'value' => '-5%',
|
||||
]);
|
||||
|
||||
$this->cart->add([
|
||||
'id' => 1,
|
||||
'name' => 'Widget',
|
||||
'price' => 50.00,
|
||||
'quantity' => 2,
|
||||
'attributes' => [],
|
||||
'conditions' => $discount,
|
||||
]);
|
||||
|
||||
// Without applies_to: 50*0.95 * 2 = 95
|
||||
expect($this->cart->get(1)->getPriceSumWithConditions())->toEqual(95.00);
|
||||
expect($this->cart->getSubTotal())->toEqual(95.00);
|
||||
});
|
||||
|
||||
test('condition with applies_to only discounts that many items', function () {
|
||||
$discount = new CartCondition([
|
||||
'name' => '5% Off First Item',
|
||||
'type' => 'discount',
|
||||
'value' => '-5%',
|
||||
'applies_to' => 1,
|
||||
]);
|
||||
|
||||
$this->cart->add([
|
||||
'id' => 1,
|
||||
'name' => 'Widget',
|
||||
'price' => 50.00,
|
||||
'quantity' => 2,
|
||||
'attributes' => [],
|
||||
'conditions' => $discount,
|
||||
]);
|
||||
|
||||
// applies_to: 1 → (50*0.95) + 50 = 97.5
|
||||
expect($this->cart->get(1)->getPriceSumWithConditions())->toEqual(97.50);
|
||||
expect($this->cart->getSubTotal())->toEqual(97.50);
|
||||
});
|
||||
|
||||
test('applies_to greater than or equal to quantity discounts all items', function () {
|
||||
$discount = new CartCondition([
|
||||
'name' => '5% Off',
|
||||
'type' => 'discount',
|
||||
'value' => '-5%',
|
||||
'applies_to' => 5,
|
||||
]);
|
||||
|
||||
$this->cart->add([
|
||||
'id' => 1,
|
||||
'name' => 'Widget',
|
||||
'price' => 50.00,
|
||||
'quantity' => 2,
|
||||
'attributes' => [],
|
||||
'conditions' => $discount,
|
||||
]);
|
||||
|
||||
// applies_to 5 >= qty 2, so all items discounted: 50*0.95 * 2 = 95
|
||||
expect($this->cart->get(1)->getPriceSumWithConditions())->toEqual(95.00);
|
||||
});
|
||||
|
||||
test('applies_to of zero means condition does not apply to any item', function () {
|
||||
$discount = new CartCondition([
|
||||
'name' => '5% Off',
|
||||
'type' => 'discount',
|
||||
'value' => '-5%',
|
||||
'applies_to' => 0,
|
||||
]);
|
||||
|
||||
$this->cart->add([
|
||||
'id' => 1,
|
||||
'name' => 'Widget',
|
||||
'price' => 50.00,
|
||||
'quantity' => 2,
|
||||
'attributes' => [],
|
||||
'conditions' => $discount,
|
||||
]);
|
||||
|
||||
// applies_to 0: no items discounted → 50 * 2 = 100
|
||||
expect($this->cart->get(1)->getPriceSumWithConditions())->toEqual(100.00);
|
||||
});
|
||||
|
||||
test('multiple conditions where only one has applies_to', function () {
|
||||
$limitedDiscount = new CartCondition([
|
||||
'name' => '10% Off First Item',
|
||||
'type' => 'discount',
|
||||
'value' => '-10%',
|
||||
'applies_to' => 1,
|
||||
]);
|
||||
|
||||
$globalDiscount = new CartCondition([
|
||||
'name' => '5% Off All',
|
||||
'type' => 'discount',
|
||||
'value' => '-5%',
|
||||
]);
|
||||
|
||||
$this->cart->add([
|
||||
'id' => 1,
|
||||
'name' => 'Widget',
|
||||
'price' => 100.00,
|
||||
'quantity' => 2,
|
||||
'attributes' => [],
|
||||
'conditions' => [$limitedDiscount, $globalDiscount],
|
||||
]);
|
||||
|
||||
// Item 1: 100 * 0.90 * 0.95 = 85.50
|
||||
// Item 2: 100 * 0.95 = 95.00 (only global applies)
|
||||
// Total: 180.50
|
||||
expect($this->cart->get(1)->getPriceSumWithConditions())->toEqual(180.50);
|
||||
});
|
||||
|
||||
test('getAppliesToQuantity returns null when not set', function () {
|
||||
$condition = new CartCondition([
|
||||
'name' => 'Discount',
|
||||
'type' => 'discount',
|
||||
'value' => '-5%',
|
||||
]);
|
||||
|
||||
expect($condition->getAppliesToQuantity())->toBeNull();
|
||||
});
|
||||
|
||||
test('getAppliesToQuantity returns the set value', function () {
|
||||
$condition = new CartCondition([
|
||||
'name' => 'Discount',
|
||||
'type' => 'discount',
|
||||
'value' => '-5%',
|
||||
'applies_to' => 3,
|
||||
]);
|
||||
|
||||
expect($condition->getAppliesToQuantity())->toBe(3);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user