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

287 lines
11 KiB
PHP

<?php
declare(strict_types=1);
namespace KupShop\BonusProgramBundle\Tests;
use Doctrine\DBAL\Exception;
use KupShop\BonusProgramBundle\Utils\BonusProvider;
use KupShop\DevelopmentBundle\Util\Tests\CartTestTrait;
use KupShop\KupShopBundle\Context\UserContext;
use KupShop\KupShopBundle\Util\Contexts;
use KupShop\OrderDiscountBundle\Util\DiscountManager;
use KupShop\OrderingBundle\Util\Order\OrderInfo;
use KupShop\OrderingBundle\Util\Purchase\PurchaseUtil;
use Query\Operator;
class BonusProgramDiscountsTest extends \DatabaseTestCase
{
use CartTestTrait;
private const USER_WITH_POINTS = 1;
private const DISCOUNT_ACTION_ID = 11;
private const POINTS_USED = 200;
private PurchaseUtil $purchaseUtil;
private DiscountManager $discountManager;
private OrderInfo $orderInfo;
private BonusProvider $bonusProvider;
public function setUp(): void
{
parent::setUp();
$this->purchaseUtil = $this->get(PurchaseUtil::class);
$this->discountManager = $this->get(DiscountManager::class);
$this->orderInfo = $this->get(OrderInfo::class);
$this->bonusProvider = $this->get(BonusProvider::class);
}
/**
* Test pro bonus program akci s produktovým filtrem.
*
* Košík s 1 produktem, uživatel chce použít body pro získání slevy, kterou na základě nastaveného produkt filtru
* buď získá a nebo nezíská.
*
* @dataProvider data_testBonusProgramActionWithProductsFilter
*/
public function testBonusProgramActionWithProductsFilter(callable $setModule, ?string $filter, ?array $expectedDiscount): void
{
$setModule();
$this->setBonusProgramActionProductsFilter(
filter: $filter ? json_decode($filter, true) : null,
);
$this->loginUser(self::USER_WITH_POINTS);
$pointsBefore = $this->getActivePoints();
$this->prepareCart();
$this->insertProduct(3);
$this->applyBonusPoints(self::POINTS_USED);
$order = $this->submitOrder();
$itemsDiscounts = $this->orderInfo->getDiscounts($order);
if (!$expectedDiscount) {
// discount is not expected
$this->assertEmpty($itemsDiscounts, 'Žádna sleva by se neměla aplikovat');
} else {
// discount is expected
$discount = reset($itemsDiscounts);
$this->assertEquals($expectedDiscount['name'], $discount['name']);
$this->assertEqualsWithDelta($expectedDiscount['price'], $discount['totalPrice']->asFloat(), 0.01);
}
$pointsAfter = $this->getActivePoints();
$this->assertEquals($expectedDiscount ? ($pointsBefore - self::POINTS_USED) : $pointsBefore, $pointsAfter, 'Špatně se odečetly body z bonus programu po použití!');
}
public function data_testBonusProgramActionWithProductsFilter(): iterable
{
foreach ($this->data_purchaseStateProvider() as $key => [$setModule]) {
yield "{$key}: Bonus program action is not used when product filter enabled and does not match any product" => [
$setModule, '{"enabled":"Y","products":["3"],"products_invert":"invert"}', null,
];
yield "{$key}: Bonus program action is used on products applicable by filter" => [
$setModule, '{"enabled":"Y","products":["3"]}', ['name' => 'Uplatnění bodů (200)', 'price' => 200],
];
yield "{$key}: Filter is disabled so discount is applicable" => [
$setModule, '{"enabled":"N"}', ['name' => 'Uplatnění bodů (200)', 'price' => 200],
];
}
}
/**
* Test uplatnění bodů z bon. prog. vytvořením ne-produktové položky objednávky.
* - Pre-cond: košík s 2 produkty (1 + 2 ks), dostatek bodů na účtu uživatele, vytovřená sleva v DB (1 bod = 1 Kč slevy).
* - Steps:
* - využiju 200 bodů,
* - vytvořím objednávku.
* - Expected:
* - 1) přídá se mi 3. položka objednávky se zápornou cenou 200 Kč,
* - 2) cena produktů zůstane stejná, celková cena objednávky se sníží o 200 Kč,
* - 3) uživateli se odečtou body.
*
* @dataProvider data_purchaseStateProvider
*/
public function testBonusProgramSeparateItem(callable $setModule): void
{
$setModule();
$this->setDivideDiscountOnBonusAction(false);
$this->loginUser(self::USER_WITH_POINTS);
$pointsBefore = $this->getActivePoints();
$this->prepareCart();
$this->insertProduct(3);
$this->insertProduct(11, 22, 2);
$this->applyBonusPoints(self::POINTS_USED);
$order = $this->submitOrder();
$items = array_values($order->fetchItems());
$this->assertCount(3, $items); // 1)
$this->assertEqualsWithDelta(732., $order->getTotalPrice()->getPriceWithVat()->asFloat(), 0.01); // 2)
$this->assertEquals(800., $items[0]['piece_price']['value_with_vat']->asFloat()); // 2)
$this->assertEquals(800., $items[0]['total_price']['value_with_vat']->asFloat());
$this->assertEquals(1, $items[0]['pieces']);
$this->assertEquals(66., $items[1]['piece_price']['value_with_vat']->asFloat()); // 2)
$this->assertEquals(132., $items[1]['total_price']['value_with_vat']->asFloat());
$this->assertEquals(2, $items[1]['pieces']);
$this->assertEquals(-200., $items[2]['piece_price']['value_with_vat']->asFloat()); // 1)
$this->assertEquals(-200., $items[2]['total_price']['value_with_vat']->asFloat());
$this->assertEquals(1, $items[2]['pieces']);
$orderDiscounts = $this->orderInfo->getUsedDiscounts($order->id);
$this->assertEquals([11 => 'Uplatnění bodů'], $orderDiscounts);
$itemsDiscounts = $this->orderInfo->getDiscounts($order);
$this->assertCount(1, $itemsDiscounts);
$this->assertArrayHasKey('11-Uplatnění bodů (200)', $itemsDiscounts);
$discount = array_values($itemsDiscounts)[0];
$this->assertEqualsWithDelta(200., $discount['totalPrice']->asFloat(), 0.01);
$this->assertEquals('Uplatnění bodů (200)', $discount['name']);
$pointsAfter = $this->getActivePoints();
$this->assertEquals($pointsBefore - self::POINTS_USED, $pointsAfter, 'Špatně se odečetly body z bonus programu po použití!'); // 3)
}
/**
* Test uplatnění bodů z bon. prog. rozpočítáním slevy na produkty objednávky.
* - Pre-cond: košík s 2 produkty (1 + _1_ ks), dostatek bodů na účtu uživatele, vytovřená sleva v DB (1 bod = 1 Kč slevy).
* - Steps:
* - využiju 200 bodů,
* - vytvořím objednávku.
* - Expected:
* - 1) počet produktů v objednávce se nezmění,
* - 2) cena produktů se sníží o 200 Kč (rozpočítáno), celková cena objednávky se sníží o 200 Kč,
* - 3) uživateli se odečtou body.
*
* @dataProvider data_purchaseStateProvider
*/
public function testBonusProgramDiscountDivide(callable $setModule): void
{
$setModule();
$this->setDivideDiscountOnBonusAction(true);
$this->loginUser(self::USER_WITH_POINTS);
$pointsBefore = $this->getActivePoints();
$this->prepareCart();
$this->insertProduct(3);
$this->insertProduct(11, 22);
$this->applyBonusPoints(self::POINTS_USED);
$order = $this->submitOrder();
$items = array_values($order->fetchItems());
$this->assertCount(2, $items, 'Měly by tu být 2 položky, ale jsou: '.implode(','.PHP_EOL, array_column($items, 'descr')));
$this->assertEqualsWithDelta(666., $order->getTotalPrice()->getPriceWithVat()->asFloat(), 0.01);
$this->assertEquals(615.24, $items[0]['piece_price']['value_with_vat']->asFloat());
$this->assertEquals(615.24, $items[0]['total_price']['value_with_vat']->asFloat());
$this->assertEquals(1, $items[0]['pieces']);
$this->assertEquals(50.76, $items[1]['piece_price']['value_with_vat']->asFloat());
$this->assertEquals(50.76, $items[1]['total_price']['value_with_vat']->asFloat());
$this->assertEquals(1, $items[1]['pieces']);
$this->assertArrayNotHasKey(2, $items);
$orderDiscounts = $this->orderInfo->getUsedDiscounts($order->id);
$this->assertEquals([11 => 'Uplatnění bodů'], $orderDiscounts);
$itemsDiscounts = $this->orderInfo->getDiscounts($order);
$this->assertCount(1, $itemsDiscounts);
$this->assertArrayHasKey('11-Uplatnění bodů (200)', $itemsDiscounts);
$discount = array_values($itemsDiscounts)[0];
$this->assertEqualsWithDelta(200., $discount['totalPrice']->asFloat(), 0.01);
$this->assertEquals('Uplatnění bodů (200)', $discount['name']);
$partials = array_map(fn ($partial) => $partial->asFloat(), $discount['itemsPartials']);
$this->assertEqualsWithDelta([184.76, 15.24], array_values($partials), 0.01);
$pointsAfter = $this->getActivePoints();
$this->assertEquals($pointsBefore - self::POINTS_USED, $pointsAfter, 'Špatně se odečetly body z bonus programu po použití!');
}
private function applyBonusPoints(int $numberOfPoints): void
{
$this->discountManager->setPurchaseState($this->purchaseUtil->getPurchaseState());
foreach ($this->discountManager->loadDiscountsEntities($this->discountManager->getDiscountForCheck()) as $discount) {
foreach ($discount->getActions() as $action) {
if (($action['type'] ?? null) !== 'bonus_program') {
continue;
}
$this->cart->setActionHandlersData([
$action['id'] => ['bonus_points' => (string) $numberOfPoints],
]);
}
}
$this->cart->invalidatePurchaseState();
}
/**
* @throws Exception
*/
private function setDivideDiscountOnBonusAction(bool $divideDiscount): void
{
$value = $divideDiscount ? 'Y' : 'N';
$query = sqlQueryBuilder()
->update('order_discounts_actions')
->andWhere(Operator::equals(['id' => self::DISCOUNT_ACTION_ID]))
->set('data', "JSON_SET(data, '$.divideDiscountPrice', '{$value}')");
$query->execute();
}
private function getActivePoints(): int
{
$user = Contexts::get(UserContext::class)->getActive();
return $this->bonusProvider->getActivePointsAmount($user);
}
private function setBonusProgramActionProductsFilter(?array $filter): void
{
$data = sqlQueryBuilder()
->select('data')
->from('order_discounts_actions')
->where(Operator::equals(['id' => self::DISCOUNT_ACTION_ID]))
->execute()->fetchOne();
$data = json_decode($data ?: '', true) ?: [];
$data['filter'] = $filter;
sqlQueryBuilder()
->update('order_discounts_actions')
->directValues(['data' => json_encode($data)])
->where(Operator::equals(['id' => self::DISCOUNT_ACTION_ID]))
->execute();
}
public function getDataSet(): object
{
return $this->getJsonDataSetFromFile();
}
}