fix discount calulation for percentage coupon, various bug fixes in tax rounding, add test

This commit is contained in:
Steffen Mahler 2020-05-05 15:46:02 +02:00
parent 7f3e564c14
commit de1f75df12
5 changed files with 151 additions and 37 deletions

View File

@ -324,16 +324,14 @@ class CartRule
break;
}
$item->discount_amount = round(
min(
$item->discount_amount + $discountAmount,
$item->price * $quantity + $item->tax_amount
),2);
$item->base_discount_amount = round(
min(
$item->base_discount_amount + $baseDiscountAmount,
$item->base_price * $quantity + $item->base_tax_amount
), 2);
$item->discount_amount = min(
$item->discount_amount + $discountAmount,
$item->price * $quantity + $item->tax_amount
);
$item->base_discount_amount = min(
$item->base_discount_amount + $baseDiscountAmount,
$item->base_price * $quantity + $item->base_tax_amount
);
$appliedRuleIds[$rule->id] = $rule->id;

View File

@ -2,6 +2,7 @@
namespace Webkul\Checkout;
use Webkul\Checkout\Models\Cart as CartModel;
use Webkul\Checkout\Models\CartAddress;
use Webkul\Checkout\Repositories\CartRepository;
use Webkul\Checkout\Repositories\CartItemRepository;
@ -600,6 +601,8 @@ class Cart
$cart->base_discount_amount += $shipping->base_discount_amount;
}
$cart = $this->finalizeCartTotals($cart);
$quantities = 0;
foreach ($cart->items as $item) {
@ -1052,6 +1055,25 @@ class Cart
}
}
/**
* Round cart totals
*
* @param \Webkul\Checkout\Models\Cart $cart
*
* @return \Webkul\Checkout\Models\Cart
*/
private function finalizeCartTotals(CartModel $cart): CartModel
{
$cart->discount_amount = round($cart->discount_amount, 2);
$cart->base_discount_amount = round($cart->base_discount_amount, 2);
$cart->grand_total = round($cart->grand_total, 2);
$cart->grand_total = round($cart->grand_total, 2);
$cart->base_grand_total = round($cart->base_grand_total, 2);
return $cart;
}
/**
* @param $user
*

View File

@ -13,16 +13,16 @@ class Tax
/**
* Returns an array with tax rates and tax amount
*
* @param \Webkul\Checkout\Contracts\Cart $cart
* @param bool $asBase
* @param object $that
* @param bool $asBase
*
* @return array
*/
public static function getTaxRatesWithAmount(\Webkul\Checkout\Contracts\Cart $cart, bool $asBase = false): array
public static function getTaxRatesWithAmount(object $that, bool $asBase = false): array
{
$taxes = [];
foreach ($cart->items as $item) {
foreach ($that->items as $item) {
$taxRate = (string) round((float) $item->tax_percent, self::TAX_RATE_PRECISION);
if (! array_key_exists($taxRate, $taxes)) {
@ -43,14 +43,14 @@ class Tax
/**
* Returns the total tax amount
*
* @param \Webkul\Checkout\Contracts\Cart $cart
* @param bool $asBase
* @param object $that
* @param bool $asBase
*
* @return float
*/
public static function getTaxTotal(\Webkul\Checkout\Contracts\Cart $cart, bool $asBase = false): float
public static function getTaxTotal(object $that, bool $asBase = false): float
{
$taxes = self::getTaxRatesWithAmount($cart, $asBase);
$taxes = self::getTaxRatesWithAmount($that, $asBase);
$result = 0;

View File

@ -36,7 +36,7 @@ class cartRuleWithCoupon
class expectedCartItem
{
public const ITEM_DISCOUNT_AMOUNT_PRECISION = 2;
public const ITEM_DISCOUNT_AMOUNT_PRECISION = 4;
public const ITEM_TAX_AMOUNT_PRECISION = 4;
public $cart_id;
@ -106,6 +106,7 @@ class expectedCartItem
class expectedCart
{
public const CART_TOTAL_PRECISION = 2;
public const CART_GRAND_TOTAL_PRECISION = 4;
public $customer_id;
public $id;
@ -138,11 +139,15 @@ class expectedCart
{
$this->sub_total = round($this->sub_total, self::CART_TOTAL_PRECISION);
$this->tax_total = round($this->tax_total, self::CART_TOTAL_PRECISION);
$this->grand_total = round($this->sub_total + $this->tax_total - $this->discount_amount, self::CART_TOTAL_PRECISION);
$this->discount_amount = round($this->discount_amount, self::CART_TOTAL_PRECISION);
$this->grand_total = round($this->sub_total + $this->tax_total - $this->discount_amount,
self::CART_GRAND_TOTAL_PRECISION);
$this->base_sub_total = round($this->base_sub_total, self::CART_TOTAL_PRECISION);
$this->base_tax_total = round($this->base_tax_total, self::CART_TOTAL_PRECISION);
$this->base_grand_total = round($this->base_sub_total + $this->base_tax_total - $this->base_discount_amount, self::CART_TOTAL_PRECISION);
$this->base_discount_amount = round($this->base_discount_amount, self::CART_TOTAL_PRECISION);
$this->base_grand_total = round($this->base_sub_total + $this->base_tax_total - $this->base_discount_amount,
self::CART_GRAND_TOTAL_PRECISION);
}
public function toArray(): array
@ -579,6 +584,102 @@ class CartRuleCest
}
}
public function checkExampleCase(UnitTester $I)
{
config(['app.default_country' => 'DE']);
$faker = Factory::create();
$customer = $I->have(Customer::class);
auth()->guard('customer')->loginUsingId($customer->id);
Event::dispatch('customer.after.login', $customer['email']);
$this->sessionToken = $faker->uuid;
session(['_token' => $this->sessionToken]);
$tax = $I->have(TaxRate::class, [
'country' => 'DE',
'tax_rate' => 19.0,
]);
$taxCategorie = $I->have(TaxCategory::class);
$I->have(TaxMap::class, [
'tax_rate_id' => $tax->id,
'tax_category_id' => $taxCategorie->id,
]);
$productConfig = [
'attributeValues' => [
'price' => 23.92,
'tax_category_id' => $taxCategorie->id,
],
];
$product = $I->haveProduct(Laravel5Helper::SIMPLE_PRODUCT, $productConfig);
$ruleConfig = [
'action_type' => self::ACTION_TYPE_PERCENTAGE,
'discount_amount' => 100,
'conditions' => [
[
'attribute' => 'product|sku',
'value' => $product->sku,
'operator' => '==',
'attribute_type' => 'text',
],
],
];
$cartRule = $I->have(CartRule::class, $ruleConfig);
DB::table('cart_rule_channels')->insert([
'cart_rule_id' => $cartRule->id,
'channel_id' => core()->getCurrentChannel()->id,
]);
$guestCustomerGroup = $I->grabRecord('customer_groups', ['code' => 'guest']);
DB::table('cart_rule_customer_groups')->insert([
'cart_rule_id' => $cartRule->id,
'customer_group_id' => $guestCustomerGroup['id'],
]);
$generalCustomerGroup = $I->grabRecord('customer_groups', ['code' => 'general']);
DB::table('cart_rule_customer_groups')->insert([
'cart_rule_id' => $cartRule->id,
'customer_group_id' => $generalCustomerGroup['id'],
]);
$coupon = $I->have(CartRuleCoupon::class, [
'code' => 'AWESOME',
'cart_rule_id' => $cartRule->id,
]);
$data = [
'_token' => session('_token'),
'product_id' => $product->id,
'quantity' => 1,
];
cart()->addProduct($product->id, $data);
cart()->setCouponCode('AWESOME')->collectTotals();
$cart = cart()->getCart();
$cartItem = $cart->items()->first();
$I->assertEquals('AWESOME', $cartItem['coupon_code']);
$I->assertEquals(23.92, $cartItem['price']);
$I->assertEquals(19.0, $cartItem['tax_percent']);
$I->assertEquals(4.5448, $cartItem['tax_amount']);
$I->assertEquals(28.4648, $cartItem['discount_amount']);
$I->assertEquals('AWESOME', $cart->coupon_code);
$I->assertEquals(23.92, $cart->sub_total);
$I->assertEquals(4.54, $cart->tax_total);
$I->assertEquals(28.46, $cart->discount_amount);
// 23.92 + 4.54 - 28.46 = 0.00
$I->assertEquals(0.00, $cart->grand_total);
}
/**
* @param \Codeception\Example $scenario
* @param \Tests\Unit\Category\cartRuleWithCoupon $cartRuleWithCoupon
@ -586,19 +687,15 @@ class CartRuleCest
*
* @return array
*/
private function getExpectedCartItems(
Example $scenario,
?cartRuleWithCoupon $cartRuleWithCoupon,
int $cartID
): array {
private function getExpectedCartItems(Example $scenario, ?cartRuleWithCoupon $cartRuleWithCoupon, int $cartID): array
{
$cartItems = [];
foreach ($scenario['productSequence'] as $key => $item) {
$pos = $this->array_find(
'product_id',
$this->products[$scenario['productSequence'][$key]]->id,
$cartItems,
true
$cartItems
);
if ($pos === null) {
@ -793,11 +890,8 @@ class CartRuleCest
*
* @return \Tests\Unit\Category\expectedCart
*/
private function getExpectedCart(
int $cartId,
array $expectedCartItems,
?cartRuleWithCoupon $cartRuleWithCoupon
): expectedCart {
private function getExpectedCart(int $cartId, array $expectedCartItems, ?cartRuleWithCoupon $cartRuleWithCoupon): expectedCart
{
$cart = new expectedCart(
$cartId,
auth()->guard('customer')->user()->id

View File

@ -17,7 +17,6 @@ class TaxCest
private const PRODUCT2_QTY = 7;
private const CART_TOTAL_PRECISION = 2;
private const TAX_AMOUNT_PRECISION = 2;
public function _before(UnitTester $I)
{
@ -73,16 +72,17 @@ class TaxCest
'quantity' => self::PRODUCT2_QTY,
]);
// rounded by precision of 2 because this are sums of corresponding tax categories
$expectedTaxAmount1 = round(
round(self::PRODUCT1_QTY * $product1->price, self::CART_TOTAL_PRECISION)
* $tax1->tax_rate / 100,
self::TAX_AMOUNT_PRECISION
self::CART_TOTAL_PRECISION
);
$expectedTaxAmount2 = round(
round(self::PRODUCT2_QTY * $product2->price, self::CART_TOTAL_PRECISION)
* $tax2->tax_rate / 100,
self::TAX_AMOUNT_PRECISION
self::CART_TOTAL_PRECISION
);
$this->scenario = [
@ -92,7 +92,7 @@ class TaxCest
(string)round((float)$tax2->tax_rate, 4) => $expectedTaxAmount2,
],
'expectedTaxTotal' =>
round($expectedTaxAmount1 + $expectedTaxAmount2, self::TAX_AMOUNT_PRECISION),
round($expectedTaxAmount1 + $expectedTaxAmount2, self::CART_TOTAL_PRECISION),
];
}