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; } }