Further changes, remove Calculators

This commit is contained in:
Patrick Henninger
2022-02-05 23:37:59 +01:00
parent 4415db2be6
commit a4322a2ac3
6 changed files with 132 additions and 198 deletions

View File

@@ -1,37 +0,0 @@
<?php
namespace Gloudemans\Shoppingcart\Calculation;
use Gloudemans\Shoppingcart\CartItem;
use Gloudemans\Shoppingcart\Contracts\Calculator;
use Money\Money;
class DefaultCalculator implements Calculator
{
public static function getAttribute(string $attribute, CartItem $cartItem)
{
switch ($attribute) {
case 'discount':
return $cartItem->price->multiply($cartItem->discountRate, config('cart.rounding', Money::ROUND_UP));
case 'tax':
return $cartItem->priceTarget->multiply($cartItem->taxRate + 1, config('cart.rounding', Money::ROUND_UP));
case 'priceTax':
return $cartItem->priceTarget->add($cartItem->tax);
case 'discountTotal':
return $cartItem->discount->multiply($cartItem->qty, config('cart.rounding', Money::ROUND_UP));
case 'priceTotal':
return $cartItem->price->multiply($cartItem->qty, config('cart.rounding', Money::ROUND_UP));
case 'subtotal':
$subtotal = $cartItem->priceTotal->subtract($cartItem->discountTotal);
return $subtotal->isPositive() ? $subtotal : new Money(0, $cartItem->price->getCurrency());
case 'priceTarget':
return $cartItem->priceTotal->subtract($cartItem->discountTotal)->divide($cartItem->qty);
case 'taxTotal':
return $cartItem->subtotal->multiply($cartItem->taxRate + 1, config('cart.rounding', Money::ROUND_UP));
case 'total':
return $cartItem->subtotal->add($cartItem->taxTotal);
default:
return;
}
}
}

View File

@@ -1,39 +0,0 @@
<?php
namespace Gloudemans\Shoppingcart\Calculation;
use Gloudemans\Shoppingcart\CartItem;
use Gloudemans\Shoppingcart\Contracts\Calculator;
class GrossPrice implements Calculator
{
public static function getAttribute(string $attribute, CartItem $cartItem)
{
$decimals = config('cart.format.decimals', 2);
switch ($attribute) {
case 'priceNet':
return round($cartItem->price / (1 + ($cartItem->taxRate / 100)), $decimals);
case 'discount':
return $cartItem->priceNet * ($cartItem->getDiscountRate() / 100);
case 'tax':
return round($cartItem->priceTarget * ($cartItem->taxRate / 100), $decimals);
case 'priceTax':
return round($cartItem->priceTarget + $cartItem->tax, $decimals);
case 'discountTotal':
return round($cartItem->discount * $cartItem->qty, $decimals);
case 'priceTotal':
return round($cartItem->priceNet * $cartItem->qty, $decimals);
case 'subtotal':
return max(round($cartItem->priceTotal - $cartItem->discountTotal, $decimals), 0);
case 'priceTarget':
return round(($cartItem->priceTotal - $cartItem->discountTotal) / $cartItem->qty, $decimals);
case 'taxTotal':
return round($cartItem->subtotal * ($cartItem->taxRate / 100), $decimals);
case 'total':
return round($cartItem->subtotal + $cartItem->taxTotal, $decimals);
default:
return;
}
}
}

View File

@@ -184,7 +184,7 @@ class Cart
$item->setInstance($this->currentInstance());
if (! $keepDiscount) {
$item->setDiscountRate($this->discount);
$item->setDiscount($this->discount);
}
if (!$keepTax) {
@@ -350,33 +350,19 @@ class Cart
}
/**
* Get the total price of the items in the cart.
* Get the discount of the items in the cart.
*
* @return Money
*/
public function total(): Money
public function price(): Money
{
return $this->getContent()->reduce(function (Money $total, CartItem $cartItem) {
return $total->add($cartItem->total);
$calculated = $this->getContent()->reduce(function (Money $discount, CartItem $cartItem) {
return $discount->add($cartItem->price());
}, new Money(0, new Currency('USD')));
}
/**
* Get the total tax of the items in the cart.
*/
public function tax(): Money
{
return $this->getContent()->reduce(function (Money $tax, CartItem $cartItem) {
return $tax->add($cartItem->taxTotal);
}, new Money(0, new Currency('USD')));
}
/**
* Get the subtotal (total - tax) of the items in the cart.
*/
public function subtotal(): Money
{
return $this->getContent()->reduce(function (Money $subTotal, CartItem $cartItem) {
return $subTotal->add($cartItem->subtotal);
}, new Money(0, new Currency('USD')));
if ($calculated instanceof Money) {
return $calculated;
}
}
/**
@@ -386,38 +372,64 @@ class Cart
*/
public function discount(): Money
{
return $this->getContent()->reduce(function (Money $discount, CartItem $cartItem) {
return $discount->add($cartItem->discountTotal);
$calculated = $this->getContent()->reduce(function (Money $discount, CartItem $cartItem) {
return $discount->add($cartItem->discount());
}, new Money(0, new Currency('USD')));
if ($calculated instanceof Money) {
return $calculated;
}
}
/**
* Get the price of the items in the cart (not rounded).
* Get the subtotal (total - tax) of the items in the cart.
*/
public function initial(): Money
public function subtotal(): Money
{
return $this->getContent()->reduce(function (Money $initial, CartItem $cartItem) {
return $initial->add($cartItem->price->multiply($cartItem->qty));
$calculated = $this->getContent()->reduce(function (Money $subTotal, CartItem $cartItem) {
return $subTotal->add($cartItem->subtotal());
}, new Money(0, new Currency('USD')));
if ($calculated instanceof Money) {
return $calculated;
}
}
/**
* Get the price of the items in the cart (previously rounded).
* Get the total tax of the items in the cart.
*/
public function priceTotal(): Money
public function tax(): Money
{
return $this->getContent()->reduce(function (Money $initial, CartItem $cartItem) {
return $initial->add($cartItem->priceTotal);
$calculated = $this->getContent()->reduce(function (Money $tax, CartItem $cartItem) {
return $tax->add($cartItem->tax());
}, new Money(0, new Currency('USD')));
if ($calculated instanceof Money) {
return $calculated;
}
}
/**
* Get the total price of the items in the cart.
*/
public function total(): Money
{
$calculated = $this->getContent()->reduce(function (Money $total, CartItem $cartItem) {
return $total->add($cartItem->total());
}, new Money(0, new Currency('USD')));
if ($calculated instanceof Money) {
return $calculated;
}
}
/**
* Get the total weight of the items in the cart.
*/
public function weight(): float
public function weight(): int
{
return $this->getContent()->reduce(function (float $total, CartItem $cartItem) {
return $total + ($cartItem->qty * $cartItem->weight);
return $this->getContent()->reduce(function (int $total, CartItem $cartItem) {
return $total + $cartItem->weight();
}, 0);
}
@@ -509,7 +521,7 @@ class Cart
{
$cartItem = $this->get($rowId);
$cartItem->setDiscountRate($discount);
$cartItem->setDiscount($discount);
$content = $this->getContent();

View File

@@ -2,30 +2,15 @@
namespace Gloudemans\Shoppingcart;
use Gloudemans\Shoppingcart\Calculation\DefaultCalculator;
use Gloudemans\Shoppingcart\Contracts\Buyable;
use Gloudemans\Shoppingcart\Contracts\Calculator;
use Gloudemans\Shoppingcart\Exceptions\InvalidCalculatorException;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Contracts\Support\Jsonable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Money\Money;
use Money\Formatter\DecimalMoneyFormatter;
use Money\Currencies\ISOCurrencies;
use ReflectionClass;
/**
* @property-read mixed discount
* @property-read float discountTotal
* @property-read float priceTarget
* @property-read float priceNet
* @property-read float priceTotal
* @property-read float subtotal
* @property-read float taxTotal
* @property-read float tax
* @property-read float total
* @property-read float priceTax
*/
class CartItem implements Arrayable, Jsonable
{
/**
@@ -35,10 +20,8 @@ class CartItem implements Arrayable, Jsonable
/**
* The ID of the cart item.
*
* @var int|string
*/
public $id;
public int|string $id;
/**
* The quantity for this cart item.
@@ -47,8 +30,6 @@ class CartItem implements Arrayable, Jsonable
/**
* The name of the cart item.
*
* @var string
*/
public string $name;
@@ -74,15 +55,13 @@ class CartItem implements Arrayable, Jsonable
/**
* The FQN of the associated model.
*
* @var string|null
*/
private $associatedModel = null;
public ?string $associatedModel = null;
/**
* The discount rate for the cart item.
*/
public float $discountRate = 0;
public float|Money $discount = 0;
/**
* The cart instance of the cart item.
@@ -91,10 +70,6 @@ class CartItem implements Arrayable, Jsonable
public function __construct(int|string $id, string $name, Money $price, int $qty = 1, int $weight = 0, ?CartItemOptions $options = null)
{
if (!is_string($id) && !is_int($id)) {
throw new \InvalidArgumentException('Please supply a valid identifier.');
}
$this->id = $id;
$this->name = $name;
$this->price = $price;
@@ -142,7 +117,7 @@ class CartItem implements Arrayable, Jsonable
*
* @param mixed $model
*/
public function associate($model) : self
public function associate(string|Model $model) : self
{
$this->associatedModel = is_string($model) ? $model : get_class($model);
@@ -162,9 +137,9 @@ class CartItem implements Arrayable, Jsonable
/**
* Set the discount rate.
*/
public function setDiscountRate(float $discountRate) : self
public function setDiscount(float|Money $discount) : self
{
$this->discountRate = $discountRate;
$this->discount = $discount;
return $this;
}
@@ -179,39 +154,69 @@ class CartItem implements Arrayable, Jsonable
return $this;
}
/**
* Get an attribute from the cart item or get the associated model.
*
* @return mixed
*/
public function __get(string $attribute)
public function model(): ?Model
{
if (property_exists($this, $attribute)) {
return $this->{$attribute};
}
$decimals = config('cart.format.decimals', 2);
switch ($attribute) {
case 'model':
if (isset($this->associatedModel)) {
return with(new $this->associatedModel())->find($this->id);
}
// no break
case 'modelFQCN':
if (isset($this->associatedModel)) {
return $this->associatedModel;
}
// no break
case 'weightTotal':
return round($this->weight * $this->qty, $decimals);
if (isset($this->associatedModel)) {
return (new $this->associatedModel())->find($this->id);
}
$class = new ReflectionClass(config('cart.calculator', DefaultCalculator::class));
if (!$class->implementsInterface(Calculator::class)) {
throw new InvalidCalculatorException('The configured Calculator seems to be invalid. Calculators have to implement the Calculator Contract.');
}
return null;
}
return call_user_func($class->getName().'::getAttribute', $attribute, $this);
/**
* This will is the price of the CartItem considering the set quantity. If you need the raw price
* then simply access the price member.
*/
public function price(): Money
{
return $this->price()->multiply($this->qty);
}
/**
* This is the discount granted for this CartItem. It is based on the given price and, in case
* discount is a float, multiplied or, in case it is an absolute Money, subtracted. It will return
* a minimum value of 0.
*/
public function discount(): Money
{
if ($this->discount instanceof Money) {
return $this->price()->subtract($this->discountRate);
} else {
return $this->price()->multiply($this->discountRate, config('cart.rounding', Money::ROUND_UP));
}
}
/**
* This is the final price of the CartItem but without any tax applied. This does on the
* other hand include any discounts.
*/
public function subtotal(): Money
{
return Money::max(new Money(0, $this->price()->getCurrency()), $this->price()->add($this->discount()));
}
/**
* This is the tax, based on the subtotal (all previous calculations) and set tax rate
*/
public function tax(): Money
{
return $this->subtotal()->multiply($this->taxRate + 1, config('cart.rounding', Money::ROUND_UP));
}
/**
* This is the total price, consisting of the subtotal and tax applied.
*/
public function total(): Money
{
return $this->subtotal()->add($this->tax());
}
/**
* This is the total price, consisting of the subtotal and tax applied.
*/
public function weight(): int
{
return $this->qty * $this->weight;
}
/**
@@ -254,13 +259,16 @@ class CartItem implements Arrayable, Jsonable
'rowId' => $this->rowId,
'id' => $this->id,
'name' => $this->name,
'qty' => $this->qty,
'price' => self::formatMoney($this->price),
'qty' => $this->qty,
'weight' => $this->weight,
'options' => $this->options->toArray(),
'discount' => self::formatMoney($this->discount),
'tax' => self::formatMoney($this->tax),
'subtotal' => self::formatMoney($this->subtotal),
/* Calculated attributes */
'discount' => self::formatMoney($this->discount()),
'subtotal' => self::formatMoney($this->subtotal()),
'tax' => self::formatMoney($this->tax()),
'total' => self::formatMoney($this->total()),
];
}

View File

@@ -1,10 +0,0 @@
<?php
namespace Gloudemans\Shoppingcart\Contracts;
use Gloudemans\Shoppingcart\CartItem;
interface Calculator
{
public static function getAttribute(string $attribute, CartItem $cartItem);
}

View File

@@ -668,7 +668,7 @@ class CartTest extends TestCase
$cartItem = $cart->get('027c91341fd5cf4d2579b49c4b6a90da');
$this->assertEquals(BuyableProduct::class, $cartItem->modelFQCN);
$this->assertEquals(BuyableProduct::class, $cartItem->associatedModel);
}
/** @test */
@@ -682,7 +682,7 @@ class CartTest extends TestCase
$cartItem = $cart->get('027c91341fd5cf4d2579b49c4b6a90da');
$this->assertEquals(ProductModel::class, $cartItem->modelFQCN);
$this->assertEquals(ProductModel::class, $cartItem->associatedModel);
}
/**
@@ -711,8 +711,8 @@ class CartTest extends TestCase
$cartItem = $cart->get('027c91341fd5cf4d2579b49c4b6a90da');
$this->assertInstanceOf(ProductModel::class, $cartItem->model);
$this->assertEquals('Some value', $cartItem->model->someValue);
$this->assertInstanceOf(ProductModel::class, $cartItem->model());
$this->assertEquals('Some value', $cartItem->model()->someValue);
}
/** @test */
@@ -1134,7 +1134,7 @@ class CartTest extends TestCase
'name' => 'First item',
]), 1);
$cartItem = $cart->get('027c91341fd5cf4d2579b49c4b6a90da');
$this->assertEquals(50, $cartItem->discountRate);
$this->assertEquals(50, $cartItem->discount);
}
/** @test */
@@ -1158,7 +1158,7 @@ class CartTest extends TestCase
]), 1);
$cartItem = $cart->get('027c91341fd5cf4d2579b49c4b6a90da');
$cart->setDiscount('027c91341fd5cf4d2579b49c4b6a90da', 50);
$this->assertEquals(50, $cartItem->discountRate);
$this->assertEquals(50, $cartItem->discount);
}
/** @test */
@@ -1171,7 +1171,7 @@ class CartTest extends TestCase
]), 2);
$cartItem = $cart->get('027c91341fd5cf4d2579b49c4b6a90da');
$this->assertEquals(500, $cart->weight());
$this->assertEquals(500, $cartItem->weightTotal);
$this->assertEquals(500, $cartItem->weight());
}
/** @test */