Commit 781ec068 authored by Thomas Bilk's avatar Thomas Bilk

fixed issues around taxed money and added more tests

parent e28818f3
......@@ -510,7 +510,7 @@ class Cart implements Jsonable, \JsonSerializable, Arrayable{
return $subTotal;
}
/* all cart conditions like fees or discounts are applied */
/* all cart conditions like fees or shipping are applied */
$condTotal = $this->getConditions($only_with_condition_type)
->filter(function (Condition $cond) {
return $cond->getTarget() === Condition::TARGET_CART;
......@@ -544,7 +544,6 @@ class Cart implements Jsonable, \JsonSerializable, Arrayable{
return Helpers::intval($sum);
}
/**
* cart total WITH cart conditions and WITH item conditions
*
......@@ -562,15 +561,30 @@ class Cart implements Jsonable, \JsonSerializable, Arrayable{
return $itemConditionTotal;
}
$condTotal = $conditions
$condTotal = $this->totalOnlyCartConditions($type);
return $condTotal + $itemConditionTotal;
}
/**
* cart total WITH cart conditions WITHOUT item conditions
*
* @@param null|string $type Only with this conditionType
* @return int
*/
public function totalOnlyCartConditions($type = null) {
$subTotal = $this->subTotal();
$conditions = $type
? $this->getConditionsByType($type)
: $this->getConditions();
return $conditions
->filter(function (Condition $cond) {
return $cond->getTarget() === Condition::TARGET_CART;
})
->sum(function (Condition $cond) use ($subTotal) {
return $cond->applyCondition($subTotal);
});
return $itemConditionTotal + $condTotal;
}
/**
......@@ -646,7 +660,7 @@ class Cart implements Jsonable, \JsonSerializable, Arrayable{
* @return Conditions
*/
public function getPriceConditions() {
$subtotal_with_conditions = $this->totalItemsWithoutConditions() + $this->totalOnlyConditions();
$subtotal_with_conditions = $this->subTotal() + $this->totalOnlyCartConditions();
return $this->getConditions()
->filter(function (Condition $c) {
return $c->getTarget() === Condition::TARGET_PRICE;
......
......@@ -8,6 +8,8 @@
namespace Bnet\Cart;
use Bnet\Money\Currency;
use Bnet\Money\Money;
use Bnet\Money\MoneyException;
use Bnet\Money\TaxedMoney;
/**
* Class CurrencyCart - same as Cart, but work with Money Objects with Currency as pricees
......@@ -40,7 +42,7 @@ class CurrencyCart extends Cart {
*
* @param null|string $only_with_condition_type Only with this conditionType
* @return Money
* @throws \Bnet\Money\MoneyException
* @throws MoneyException
*/
public function subTotal($only_with_condition_type = null) {
$sum = $this->items()->sum(function (CurrencyItem $item) use ($only_with_condition_type) {
......@@ -55,7 +57,7 @@ class CurrencyCart extends Cart {
*
* @param null|string $only_with_condition_type Only with this conditionType
* @return Money
* @throws \Bnet\Money\MoneyException
* @throws MoneyException
*/
public function total($only_with_condition_type = null) {
$subTotal = $this->subTotal($only_with_condition_type);
......@@ -65,7 +67,7 @@ class CurrencyCart extends Cart {
$subTotal = $subTotal->amount();
/* all cart conditions like fees or discounts are applied */
/* all cart conditions like fees or shipping are applied */
$condTotal = $this->getConditions($only_with_condition_type)
->filter(function (Condition $cond) {
return $cond->getTarget() === Condition::TARGET_CART;
......@@ -92,6 +94,7 @@ class CurrencyCart extends Cart {
* get totalItems WITH conditions WITHOUT item price
* @param null|string $type type of conditions to calc only
* @return Money
* @throws MoneyException
*/
public function totalItemsOnlyConditions($type = null) {
$sum = $this->items()->sum(function (Item $item) use ($type) {
......@@ -101,17 +104,14 @@ class CurrencyCart extends Cart {
return new Money($sum, $this->currency);
}
/**
* cart totel WITH cart conditions and WITH item conditions
* cart total WITH cart conditions and WITH item conditions
*
* @param null|string $type Only with this conditionType
* @return Money
* @throws \Bnet\Money\MoneyException
* @throws MoneyException
*/
public function totalOnlyConditions($type = null) {
$subTotal = $this->subTotal();
$cond = $type
? $this->getConditionsByType($type)
: $this->getConditions();
......@@ -120,13 +120,35 @@ class CurrencyCart extends Cart {
return $itemConditionTotal;
}
/** @var Money $condTotal */
$condTotal = $cond
$condTotal = $this->totalOnlyCartConditions($type);
return $condTotal->add($itemConditionTotal);
}
/**
* cart total WITH cart conditions WITHOUT item conditions
*
* @@param null|string $type Only with this conditionType
* @return Money
* @throws MoneyException
*/
public function totalOnlyCartConditions($type = null) {
$subTotal = $this->subTotal();
$conditions = $type
? $this->getConditionsByType($type)
: $this->getConditions();
return $conditions
->filter(function (Condition $cond) {
return $cond->getTarget() === Condition::TARGET_CART;
})
->reduce(function (Money $carry, Condition $cond) use ($subTotal) {
$price = $cond->applyCondition($subTotal->amount());
->reduce(function (Money $carry, CurrencyCondition $cond) use ($subTotal) {
$price = $cond->applyCondition($subTotal);
/* the amount with tax is needed because we calculate a payment price */
if ($price instanceof TaxedMoney) {
$price = $price->amountWithTax();
}
// need a money obj to summarize
if (!$price instanceof Money) {
......@@ -135,15 +157,13 @@ class CurrencyCart extends Cart {
return $carry->add($price);
}, new Money(0));
return $condTotal->add($itemConditionTotal);
}
/**
* get cart sub total - items WITHOUT conditions and WITHOUT cart conditions
*
* @return Money
* @throws \Bnet\Money\MoneyException
* @throws MoneyException
*/
public function totalItemsWithoutConditions() {
$sum = $this->items()->sum(function (Item $item) {
......@@ -189,27 +209,27 @@ class CurrencyCart extends Cart {
/**
* get a collection of price conditions and their actual value
* @return Conditions
* @throws \Bnet\Money\MoneyException
* @throws MoneyException
*/
public function getPriceConditions() {
$subtotal_with_conditions = $this->totalItemsWithoutConditions()->add($this->totalOnlyConditions());
$subtotal_with_conditions = $this->subTotal()->add($this->totalOnlyCartConditions());
return $this->getConditions()
->filter(function (Condition $c) {
return $c->getTarget() === Condition::TARGET_PRICE;
})->map(function (CurrencyCondition $c) use (&$subtotal_with_conditions) {
$cond_value = $c->applyCondition($subtotal_with_conditions);
$cond_data = $c->all();
})->map(function (CurrencyCondition $cond) use (&$subtotal_with_conditions) {
$cond_value = $cond->applyCondition($subtotal_with_conditions);
$cond_clone = clone $cond;
/* when a negative condition results in a negative total
* only the remainder of the condition is used */
if (($diff = $subtotal_with_conditions->add($cond_value))->amount() < 0) {
$subtotal_with_conditions = new Money(0);
/* only the remainder is used for the condition value */
$cond_data['value'] = $cond_value->subtract($diff);
$cond_clone['value'] = $cond_value->subtract($diff);
} else {
$subtotal_with_conditions = $subtotal_with_conditions->add($cond_value);
$cond_data['value'] = $cond_value;
$cond_clone['value'] = $cond_value;
}
return new CurrencyCondition($cond_data);
return $cond_clone;
});
}
......
......@@ -10,6 +10,7 @@ namespace Bnet\Cart;
use Bnet\Money\Currency;
use Bnet\Money\Money;
use Bnet\Money\MoneyException;
use Bnet\Money\TaxedMoney;
class CurrencyCondition extends Condition {
......@@ -27,12 +28,21 @@ class CurrencyCondition extends Condition {
/**
* CurrencyCondition constructor.
* @param array $args
* @throws MoneyException
* @throws Exceptions\InvalidConditionException
*/
public function __construct(array $args) {
if (isset($args['tax'])) {
$this->tax = $args['tax'];
unset($args['tax']);
}
/* when the value is a `TaxedMoney` we use its tax and check if there is a mismatch */
if (isset($args['value']) && $args['value'] instanceof TaxedMoney) {
if (isset($this->tax) && $this->tax != $args['value']->tax) {
throw new MoneyException('The tax of the condition and the value do not match.');
}
$this->tax = $args['value']->tax;
}
// remove the "string" validator from the value field
$this->rules['value'] = 'required';
parent::__construct($args);
......@@ -121,8 +131,10 @@ class CurrencyCondition extends Condition {
* @return mixed
*/
protected function cleanValue($value) {
if ($this->tax && $value instanceof TaxedMoney) {
return parent::cleanValue($value->amountWithoutTax());
}
return parent::cleanValue($value instanceof Money ? $value->amount() : $value);
}
}
\ No newline at end of file
......@@ -1170,4 +1170,63 @@ class CartConditionTest extends PHPUnit_Framework_TestCase {
$this->assertEquals(0, abs($conditions->sum('value')));
$this->assertEquals(10000, $this->cart->total());
}
/**
* @throws InvalidConditionException
* @throws InvalidItemException
*/
public function test_item_with_condition_and_price_condition() {
$envelope = new Condition([
'name' => 'Envelope',
'type' => 'design',
'target' => Condition::TARGET_ITEM,
'value' => '199',
]);
$item = [
'id' => 999,
'name' => 'Gift Card',
'price' => 2500,
'quantity' => 1,
'conditions' => [$envelope],
];
$this->cart->add($item);
$this->assertEquals(2699, $this->cart->subTotal());
$this->assertEquals(2699, $this->cart->total());
$shipping = new Condition([
'name' => 'Shipping',
'type' => 'shipping',
'target' => Condition::TARGET_CART,
'value' => '600',
]);
$this->cart->condition($shipping);
$this->assertEquals(2699, $this->cart->subTotal());
$this->assertEquals(3299, $this->cart->total());
$coupon1 = new Condition([
'name' => 'Coupon 1',
'type' => 'coupon',
'target' => Condition::TARGET_PRICE,
'value' => '-1000',
]);
$this->cart->condition($coupon1);
$this->assertEquals(2699, $this->cart->subTotal());
$this->assertEquals(2299, $this->cart->total());
$this->assertEquals(-1000, $this->cart->getPriceConditions()->sum('value'));
$coupon2 = new Condition([
'name' => 'Coupon 2',
'type' => 'coupon',
'target' => Condition::TARGET_PRICE,
'value' => '-2500',
]);
$this->cart->condition($coupon2);
$this->assertEquals(2699, $this->cart->subTotal());
$this->assertEquals(0, $this->cart->total());
$this->assertEquals(-3299, $this->cart->getPriceConditions()->sum('value'));
}
}
......@@ -3,10 +3,12 @@
use Bnet\Cart\Condition;
use Bnet\Cart\CurrencyCart;
use Bnet\Cart\CurrencyCondition;
use Bnet\Cart\CurrencyItem;
use Bnet\Cart\Exceptions\InvalidConditionException;
use Bnet\Cart\Exceptions\InvalidItemException;
use Bnet\Money\Money;
use Bnet\Money\MoneyException;
use Bnet\Money\TaxedMoney;
use Mockery as m;
require_once __DIR__ . '/helpers/SessionMock.php';
......@@ -80,7 +82,7 @@ class CurrencyCartConditionTest extends PHPUnit_Framework_TestCase {
$this->assertEquals(18749, $this->cart->subTotal()->amount(), 'Cart should have sub total of 18749');
// add condition
$condition = new Condition(array(
$condition = new CurrencyCondition(array(
'name' => 'VAT 12.5%',
'type' => 'tax',
'target' => Condition::TARGET_CART,
......@@ -98,16 +100,17 @@ class CurrencyCartConditionTest extends PHPUnit_Framework_TestCase {
$this->assertEquals(2344, $this->cart->totalOnlyConditions()->amount());
}
/**
* @throws \Bnet\Cart\Exceptions\InvalidConditionException
*/
/**
* @throws InvalidConditionException
* @throws MoneyException
*/
public function test_total_with_more_conditions() {
$this->fillCart();
$this->assertEquals(18749, $this->cart->subTotal()->amount(), 'Cart should have sub total of 18749');
// add condition
$condition = new Condition(array(
$condition = new CurrencyCondition(array(
'name' => 'VAT 12.5%',
'type' => 'tax',
'target' => Condition::TARGET_CART,
......@@ -124,7 +127,7 @@ class CurrencyCartConditionTest extends PHPUnit_Framework_TestCase {
$this->assertEquals(2344, $this->cart->totalOnlyConditions()->amount());
$discount = new Condition([
$discount = new CurrencyCondition([
'name' => 'Discount',
'type' => 'discount',
'target' => Condition::TARGET_CART,
......@@ -1322,4 +1325,72 @@ class CurrencyCartConditionTest extends PHPUnit_Framework_TestCase {
$this->assertEquals(8100, $this->cart->subTotal()->amount());
$this->assertEquals(2695, $this->cart->total()->amount());
}
/**
* @throws MoneyException
* @throws InvalidItemException
* @throws InvalidConditionException
*/
public function test_item_with_condition_and_price_condition() {
$envelope = new CurrencyCondition([
'name' => 'Envelope',
'type' => 'design',
'target' => Condition::TARGET_ITEM,
'value' => new TaxedMoney(166, 'EUR', 20),
]);
$item = new CurrencyItem([
'id' => 999,
'name' => 'Gift Card',
'price' => new Money(2500),
'quantity' => 1,
'conditions' => [$envelope],
], 'EUR');
$this->cart->add($item);
$this->assertEquals(2699, $this->cart->subTotal()->amount());
$this->assertEquals(2699, $this->cart->total()->amount());
$shipping = new CurrencyCondition([
'name' => 'Shipping',
'type' => 'shipping',
'target' => Condition::TARGET_CART,
'value' => new TaxedMoney(500, 'EUR', 20),
]);
$this->cart->condition($shipping);
$this->assertEquals(2699, $this->cart->subTotal()->amount());
$this->assertEquals(3299, $this->cart->total()->amount());
$coupon1 = new CurrencyCondition([
'name' => 'Coupon 1',
'type' => 'coupon',
'target' => Condition::TARGET_PRICE,
'value' => new Money(-1000),
]);
$this->cart->condition($coupon1);
$this->assertEquals(2699, $this->cart->subTotal()->amount());
$this->assertEquals(2299, $this->cart->total()->amount());
$this->assertEquals(-1000,
$this->cart->getPriceConditions()->reduce(function (Money $acc, CurrencyCondition $cond) {
return $acc->add($cond->getValue());
}, new TaxedMoney(0))->amount()
);
$coupon2 = new CurrencyCondition([
'name' => 'Coupon 2',
'type' => 'coupon',
'target' => Condition::TARGET_PRICE,
'value' => new Money(-2500),
]);
$this->cart->condition($coupon2);
$this->assertEquals(2699, $this->cart->subTotal()->amount());
$this->assertEquals(0, $this->cart->total()->amount());
$this->assertEquals(-3299,
$this->cart->getPriceConditions()->reduce(function (Money $acc, CurrencyCondition $cond) {
return $acc->add($cond->getValue());
}, new TaxedMoney(0))->amount()
);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment