Files
kupshop/bundles/KupShop/OrderDiscountBundle/Tests/OrderDiscountsTest.php
2025-08-02 16:30:27 +02:00

396 lines
14 KiB
PHP

<?php
namespace KupShop\OrderDiscountBundle\Tests;
use KupShop\DevelopmentBundle\Util\Tests\CartTestTrait;
use KupShop\KupShopBundle\Context\CurrencyContext;
use KupShop\KupShopBundle\Util\Price\Price;
use KupShop\OrderDiscountBundle\Entity\OrderDiscount;
use KupShop\OrderDiscountBundle\OrderDiscountLocator;
use KupShop\OrderDiscountBundle\Util\DiscountManager;
use KupShop\OrderingBundle\Entity\Purchase\DiscountPurchaseItem;
use KupShop\OrderingBundle\Entity\Purchase\ProductPurchaseItem;
use KupShop\OrderingBundle\Entity\Purchase\PurchaseState;
use KupShop\OrderingBundle\Util\Purchase\PurchaseUtil;
class OrderDiscountsTest extends \DatabaseTestCase
{
use CartTestTrait;
use OrderTestDiscountTrait;
public function testTriggersLocator()
{
$discountLocator = $this->get(OrderDiscountLocator::class);
$triggers = $discountLocator->getTriggers();
$this->assertNotNull($triggers);
}
public function testFilterDiscounts()
{
$this->activateDiscount([1]);
$discountManager = $this->get(DiscountManager::class);
$purchaseStateUtil = $this->get(PurchaseUtil::class);
$this->prepareCart();
$this->insertProduct(9);
$this->cart->addCoupon('TEST321');
$this->cart->createFromDB();
$pState = $purchaseStateUtil->createStateFromCart($this->cart);
$discountManager->setPurchaseState($pState);
$discounts = $discountManager->getDiscountForCheck();
$this->assertEquals([
1 => [
'id' => '1',
'date_created' => '2019-09-13 12:00:00',
'name' => 'Test discount',
'display_name' => 'Discount 10%',
'uses_count' => '0',
'active' => 'Y',
'data' => null,
'position' => 0,
'type' => null,
],
], $discounts);
}
public function testApplyAction()
{
$this->activateDiscount([1]);
$discountManager = $this->get(DiscountManager::class);
$purchaseStateUtil = $this->get(PurchaseUtil::class);
$this->prepareCart();
$this->insertProduct(9);
$this->cart->addCoupon('TEST321');
$this->cart->createFromDB();
$pState = $purchaseStateUtil->createStateFromCart($this->cart);
$discountManager->setPurchaseState($pState);
$discountManager->recalculate();
$this->assertEquals('Discount 10% (TEST321) (10%)', $pState->getDiscounts()[0]->name);
}
public function testGetOrderDiscount()
{
/** @var DiscountManager $discountManager */
$discountManager = $this->get(DiscountManager::class);
$orderDiscount = $this->selectSQL('order_discounts', ['id' => 1])->fetch();
$orderDiscount = $discountManager->getOrderDiscount($orderDiscount);
$this->assertInstanceOf(OrderDiscount::class, $orderDiscount);
$this->assertEquals(1, $orderDiscount->getId());
}
public function testProductDiscountInCart(): void
{
$this->activateDiscount([14]);
// Create cart
$this->prepareCart();
$this->insertProduct(9);
$e = null;
try {
$this->cart->createFromDB();
} catch (\Exception $e) {
}
// Odhalí například exceptionu "Serialization of 'Closure' is not allowed" při serializaci PurchaseStatu
$this->assertNull($e, 'Exception should not be thrown when creating cart');
$purchaseState = $this->cart->getPurchaseState();
$this->assertCount(1, $purchaseState->getDiscounts());
$this->assertInstanceOf(DiscountPurchaseItem::class, $purchaseState->getDiscounts()[0]);
}
public function testDiscountsInCart()
{
$this->activateDiscount([1, 2]);
// Create cart
$this->prepareCart();
$this->mockDiscountManager();
$this->insertProduct(9);
$this->cart->createFromDB();
$purchaseState = $this->cart->getPurchaseState();
$this->assertCount(2, $purchaseState->getDiscounts());
$this->assertInstanceOf(DiscountPurchaseItem::class, $purchaseState->getDiscounts()[0]);
$this->assertInstanceOf(ProductPurchaseItem::class, $purchaseState->getDiscounts()[1]);
$this->assertEquals(5899, $this->cart->totalPricePay->asFloat(), '5999 (cena produktu) - 100 (sleva) = 5899');
}
public function testDiscountsInOrder()
{
$this->activateDiscount([1, 2]);
// Create cart
$this->prepareCart();
$this->mockDiscountManager();
$this->insertProduct(9);
$this->checkOrderPriceIsSameAsCart();
$order = $this->order;
$items = $order->fetchItems();
$this->assertCount(3, $items);
$discounts = sqlQuery('SELECT * FROM order_discounts')->fetchAll();
$this->assertEquals(1, $discounts[0]['uses_count'], 'Otestuje, ze se navysi pocet pouziti');
$this->assertEquals(1, $discounts[1]['uses_count'], 'Otestuje, ze se navysi pocet pouziti');
$orderDiscountsOrders = sqlQuery('SELECT * FROM order_discounts_orders')->fetchAll();
$this->assertCount(2, $orderDiscountsOrders, 'Otestuje, ze se vytvori relace mezi objednavkou a slevou');
}
public function testMultipleCouponsUsed()
{
$this->activateDiscount([1]);
$this->prepareCart();
$this->insertProduct(9);
$this->cart->addCoupon('TEST321');
$this->cart->addCoupon('TEST3213');
$this->checkOrderPriceIsSameAsCart();
$order = $this->order;
$items = $order->fetchItems();
$this->assertCount(3, $items);
}
public function testUseCouponActionUnusedDiscount()
{
$this->activateDiscount([361]);
$this->prepareCart();
$this->setDeliveryType(6);
$this->insertProduct(1, 16);
$this->cart->addCoupon('OVEOMN46X5');
$this->checkOrderPriceIsSameAsCart();
$order = $this->order;
$items = $order->fetchItems();
// Sleva se nevztahuje na dopravu a kupon není vyčerpaný celý
$this->assertCount(4, $items, 'Produkt + kupon + doprava/platby + zbytek kuponu = 4');
$key = array_keys($items)[1];
$this->assertEqualsWithDelta(-10000.0, $items[$key]['total_price']['value_with_vat']->asFloat(), 0.01, 'Nesedi uplatnena castka s castkou kuponu - kupon musi byt uplatnen cely 10000 Kc');
$this->assertEqualsWithDelta(0, $order->getTotalPrice()->getPriceWithVat()->asFloat(), 0.01, 'Objednavka ma byt zadarmo');
}
public function testUseCouponActionFreeDelivery()
{
$this->activateDiscount([361]);
$this->prepareCart();
$this->setDeliveryType(4); // nezapočítat od 2000
$this->insertProduct(1, 16, 13); // 12 * 800 = 10400 => doprava zdarma
$this->cart->addCoupon('OVEOMN46X5'); // poukaz 10000
$this->checkOrderPriceIsSameAsCart();
$this->assertEquals(10400, $this->cart->totalPriceWithVat->asFloat()); // produkty
$this->assertEquals(10000, $this->cart->totalDiscountPrice->asFloat()); // slevy
$this->assertEquals(400, $this->cart->totalPricePay->asFloat()); // k uhrade
$priceForDelivery = $this->cart->getTotalPriceForDelivery(); // cena pro vypocet ceny dopravy
$this->assertEquals(10400, $priceForDelivery->getPriceWithVat()->asFloat(), 'Aplikovaný poukaz nesnižuje hodnotu objednávky pro dopravu zdarma.');
$order = $this->order;
$items = $order->fetchItems();
// doprava zdarma (polozka doprava/platba neni) a kupon je vyčerpaný celý (polozka zbytek kuponu neni)
$this->assertCount(2, $items, 'Produkt + kupon = 2');
$key = array_keys($items)[1]; // uplatneni kuponu = 10000
$this->assertEqualsWithDelta(-10000.0, $items[$key]['total_price']['value_with_vat']->asFloat(), 0.01, 'Nesedi uplatnena castka s castkou kuponu - kupon musi byt uplatnen cely 10000 Kc');
$this->assertEqualsWithDelta(400, $order->getTotalPrice()->getPriceWithVat()->asFloat(), 0.01);
}
public function testUseCouponAction()
{
$this->activateDiscount([361]);
$this->prepareCart();
$this->insertProduct(9, null, 2);
$this->cart->addCoupon('OVEOMN46X5');
$this->checkOrderPriceIsSameAsCart();
$order = $this->order;
$items = $order->fetchItems();
// Kupon je vyčerpaný celý a zbývá něco doplatit
$this->assertCount(2, $items, 'Produkt + uplatneny cely kupon = 2');
$this->assertEqualsWithDelta(1998.0, $order->getTotalPrice()->getPriceWithVat()->asFloat(), 0.01, 'Za objednavku zbyva doplatit 11998 - 10000 = 1998');
}
public function testUseCouponActionEurCzk()
{
$this->activateDiscount([503]);
$this->prepareCart();
$this->insertProduct(9);
$this->cart->addCoupon('TEST_ZBYTEK');
$this->checkOrderPriceIsSameAsCart();
$order = $this->order;
$items = $order->fetchItems();
// Přebytek v jiné měně
$this->assertCount(3, $items, 'Produkt + kupon + zbytek = 3ks');
$key = array_keys($items)[1];
$this->assertEqualsWithDelta(-52000.0, $items[$key]['total_price']['value_with_vat']->asFloat(), 0.1, 'Kupon 2000 EUR je uplatnen jako kupon 52 000Kc');
$key = array_keys($items)[2];
$this->assertEqualsWithDelta(46001.0, $items[$key]['total_price']['value_with_vat']->asFloat(), 0.1, 'Zbytek kuponu 52 000 - 5 999 = 46001 Kc');
$this->assertEqualsWithDelta(0, $order->getTotalPrice()->getPriceWithVat()->asFloat(), 0.01, 'Objednavka ma byt zadarmo');
$this->assertEqualsWithDelta(-1041.15, $order->getTotalPrice()->getPriceWithoutVat()->asFloat(), 0.01, 'Kdyz mam jiny DPH nez produkty, tam cena s DPH muze byt zaporna 4957.85 - 52 000 + 46 001 = -1041,15');
}
/**
* BUG: Hodnota kuponu je nastavena na `500.500089`. Do objednavky je vlozena spravne hodnota jako 500.50, ale nasledne je
* tady `KupShop\OrderDiscountBundle\EventListener\OrderItemSubscriber::addRemainingValue` neuplatnena castka `0.0001`
* protoze tam vznikne mikrorozdil kvuli pouziti `->value(2)` pri ukladani slevy do obj, ale pri porovnavani ty remaining hodnoty
* tam je sleva bez tohohle zaokrouhleni na dve mista.
*
* To stejny vznikalo napr. pri prepoctu CZK do EUR, kdy ten prepocet vysel napr. 19.960078 a do objednavky pak byla vlozena nesmyslne
* neuplatnena cast i kdyz se to uplatnilo cely.
*/
public function testUseCouponActionUnusedDiscountBug(): void
{
$this->activateDiscount([785]);
$this->prepareCart();
$this->insertProduct(9);
$this->cart->addCoupon('KUP500');
$this->checkOrderPriceIsSameAsCart();
$items = $this->order->fetchItems();
$this->assertCount(2, $items, 'Produkt + uplatneny cely kupon = 2');
$this->assertEquals(-500.50, array_values($items)[1]['total_price']['value_with_vat']->asFloat(), 'Sleva 500.50 Kč');
}
public function testDiscountOnDelivery()
{
$this->addModule(\Modules::DELIVERY_TYPES, \Modules::SUB_APPLY_DISCOUNT_ON_DELIVERY);
$this->activateDiscount([361]);
$this->prepareCart();
$this->setDeliveryType(6);
$this->insertProduct(1, 16);
$this->cart->addCoupon('OVEOMN46X5');
$this->checkOrderPriceIsSameAsCart();
$order = $this->order;
$items = $order->fetchItems();
// Sleva se vztahuje na dopravu a kupon není vyčerpaný celý
$this->assertCount(4, $items, 'Produkt + kupon + doprava + zbytek kuponu = 4');
$this->assertEqualsWithDelta(0.0, $order->getTotalPrice()->getPriceWithVat()->asFloat(), 0.01, 'Objednavka ma byt zadarmo');
$key = array_keys($items)[3];
$this->assertEqualsWithDelta(9152.0, $items[$key]['total_price']['value_with_vat']->asFloat(), 0.1, 'Zbytek je 10000 - 800 - 48 = 9182 Kc');
}
/** @dataProvider data_purchaseStateProvider */
public function testGift()
{
$this->activateDiscount([15]);
$this->prepareCart();
$this->insertProduct(1, 16);
$this->cart->setActionHandlersData([
15 => [
'gift' => true,
'id_product' => 1,
],
]);
$this->checkOrderPriceIsSameAsCart();
$order = $this->order;
$items = array_values($order->fetchItems());
// Sleva přidává 2ks dárkového produktu za celkem 100 Kc
$this->assertCount(2, $items, 'Produkt + Dárek');
$this->assertEqualsWithDelta(900, $order->getTotalPrice()->getPriceWithVat()->asFloat(), 0.01, '800 za produkt + 100 za 2ks dárků');
$this->assertEqualsWithDelta(100, $items[1]['total_price']['value_with_vat']->asFloat(), 0.1, 'Dárek celkem za 100 Kc');
$this->assertEqualsWithDelta(50, $items[1]['piece_price']['value_with_vat']->asFloat(), 0.1, 'Dárek za 50 Kc kus');
}
private function mockDiscountManager()
{
$this->set(
DiscountManager::class,
new MockDiscountManager(
$this->get(OrderDiscountLocator::class),
$this->get(PurchaseUtil::class),
$this->get(CurrencyContext::class)
)
);
}
public function getDataSet()
{
return $this->getJsonDataSetFromFile('data.json');
}
}
class MockDiscountManager extends DiscountManager
{
private $purchaseState;
private $purchaseUtil;
private $currencyContext;
public function __construct(OrderDiscountLocator $orderDiscountLocator, PurchaseUtil $purchaseUtil, CurrencyContext $currencyContext)
{
parent::__construct($orderDiscountLocator, $purchaseUtil);
$this->purchaseUtil = $purchaseUtil;
$this->currencyContext = $currencyContext;
}
public function recalculate($discountsToCheck = null): PurchaseState
{
$this->purchaseState->addDiscount(
new DiscountPurchaseItem('Sleva 100 Kč na nákup', new Price(toDecimal(-100)->removeVat(21), $this->currencyContext->getActive(), 21), 1)
);
$this->purchaseState->addDiscount(
new ProductPurchaseItem(3, null, 1, new Price(toDecimal(0), $this->currencyContext->getActive(), 21), null, 2)
);
return $this->purchaseUtil->recalculateTotalPrices($this->purchaseState);
}
public function setPurchaseState(PurchaseState $purchaseState): void
{
$this->purchaseState = $purchaseState;
}
}