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

451 lines
16 KiB
PHP

<?php
namespace KupShop\POSBundle\Tests;
use Doctrine\DBAL\Exception;
use KupShop\GraphQLBundle\ApiAdmin\Types;
use KupShop\KupShopBundle\Context\CurrencyContext;
use KupShop\KupShopBundle\Context\PosContext;
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
use KupShop\KupShopBundle\Util\Contexts;
use KupShop\KupShopBundle\Util\Price\Price;
use KupShop\OrderingBundle\Entity\Order\OrderItem;
use KupShop\OrderingBundle\Entity\Purchase\PurchaseState;
use KupShop\OrderingBundle\Entity\Purchase\TextPurchaseItem;
use KupShop\OrderingBundle\Util\Purchase\PurchaseUtil;
use KupShop\POSBundle\Util\PosEntity;
use KupShop\POSBundle\Util\PosOrderUtil;
use KupShop\POSBundle\Util\PosPaymentUtil;
use KupShop\POSBundle\Util\PosPurchaseStateUtil;
use KupShop\POSBundle\Util\PosWarehouseUtil;
use KupShop\WarehouseBundle\Util\StoreItemWorker;
class PosOrderTest extends \DatabaseTestCase
{
public function getDataSet()
{
return $this->getJsonDataSetFromFile();
}
protected PosPurchaseStateUtil $posPurchaseStateUtil;
protected PurchaseUtil $purchaseUtil;
protected CurrencyContext $currencyContext;
protected PosOrderUtil $posOrderUtil;
protected PosWarehouseUtil $posWarehouseUtil;
private StoreItemWorker $storeItemWorker;
private PosPaymentUtil $posPaymentUtil;
protected function setUp(): void
{
global $adminID;
$adminID = null;
parent::setUp();
}
protected function setEnvironment()
{
$this->purchaseUtil = ServiceContainer::getService(PurchaseUtil::class);
$this->currencyContext = ServiceContainer::getService(CurrencyContext::class);
$this->posOrderUtil = ServiceContainer::getService(PosOrderUtil::class);
$this->posWarehouseUtil = ServiceContainer::getService(PosWarehouseUtil::class);
$this->storeItemWorker = ServiceContainer::getService(StoreItemWorker::class);
$this->posEntity = ServiceContainer::getService(PosEntity::class);
$this->posPaymentUtil = ServiceContainer::getService(PosPaymentUtil::class);
$this->posPurchaseStateUtil = ServiceContainer::getService(PosPurchaseStateUtil::class);
}
private function getPosEntity(): PosEntity
{
$posContext = Contexts::get(PosContext::class);
$posContext->activate(2);
return $posContext->getActive();
}
private function getTestOrderItems($idOrderItem1 = null, $idOrderItem2 = null): array
{
return [
new Types\Order\OrderItem(
new OrderItem(
[
'idProduct' => 43866,
'idVariation' => 24004,
'pieces' => 1,
'vat' => '21',
'priceWithoutVat' => 3297.5207,
'discount' => null,
'idOrderItem' => $idOrderItem1,
]
)
),
new Types\Order\OrderItem(
new OrderItem(
[
'idProduct' => 12145,
'idVariation' => null,
'pieces' => 1,
'vat' => '21',
'priceWithoutVat' => 252.0661,
'discount' => null,
'idOrderItem' => $idOrderItem2,
]
)
),
];
}
private function getTestPurchaseState(): PurchaseState
{
$couponItems = null;
return $this->posPurchaseStateUtil->createStateFromApi($this->getTestOrderItems(), $couponItems);
}
public function testCreatePurchaseStateFromPos()
{
$this->setEnvironment();
$purchaseState = $this->getTestPurchaseState();
$this->assertEquals(toDecimal('4295.0000')->round(2), $purchaseState->getTotalPrice()->getPriceWithVat()->round(2));
$this->assertEquals(toDecimal('3549.5868')->round(2), $purchaseState->getTotalPrice()->getPriceWithoutVat()->round(2));
$purchaseState->addProduct(
new TextPurchaseItem(
'Textová položka',
2,
new Price(toDecimal('1239.6694'),
$this->currencyContext->getActive(),
'21'
),
'21')
);
$purchaseState = $this->purchaseUtil->recalculateTotalPrices($purchaseState);
$this->assertEquals(toDecimal('5795.0000')->round(2), $purchaseState->getTotalPrice()->getPriceWithVat()->round(2));
$this->assertEquals(toDecimal('4789.2562')->round(2), $purchaseState->getTotalPrice()->getPriceWithoutVat()->round(2));
}
public function testCreateOrderFromPurchaseState()
{
$this->setEnvironment();
$this->getPosEntity();
// ---- check before update -----
$inStoreVar = sqlQueryBuilder()
->select('in_store')
->from('products_variations')
->andWhere('id_product = 43866 AND id = 24004')
->execute()
->fetchOne();
$this->assertEquals(2, $inStoreVar);
$inStoreStoreItem = sqlQueryBuilder()
->select('quantity')
->from('stores_items')
->andWhere('id = 78')
->execute()
->fetchOne();
$this->assertEquals(20, $inStoreStoreItem);
// ------------------------------
$orderResponse = $this->posOrderUtil->recalculateAndUpdateOrder(
$this->getTestOrderItems(),
[],
[],
[],
null,
null,
null,
true,
'CASH',
'1',
true
);
$this->assertCount(2, $orderResponse->getItems());
$insertedOrder = sqlQueryBuilder()
->select('*')
->from('orders')
->where('id=:id_order')
->setParameter('id_order', $orderResponse->getOrderInfo()->getIdOrder())
->execute()
->fetch();
$this->assertEquals(toDecimal('4295')->round(2), $insertedOrder['total_price']);
$this->assertEquals(toDecimal('3549.5868')->round(2), $insertedOrder['total_price_without_vat']);
$orderItems = sqlQueryBuilder()
->select('*')
->from('order_items')
->where('id_order=:id_order')
->setParameter('id_order', $insertedOrder['id'])
->execute()
->fetchAll();
$this->assertCount(2, $orderItems);
// ---- after before update -----
$inStoreVar = sqlQueryBuilder()
->select('in_store')
->from('products_variations')
->andWhere('id_product = 43866 AND id = 24004')
->execute()
->fetchOne();
$this->assertEquals(1, $inStoreVar);
$inStoreStoreItem = sqlQueryBuilder()
->select('quantity')
->from('stores_items')
->andWhere('id = 78')
->execute()
->fetchOne();
$this->assertEquals(19, $inStoreStoreItem);
// ------------------------------
}
public function testPurchaseStateStockInItemsCheck()
{
$this->setEnvironment();
$posEntity = $this->getPosEntity();
$purchaseState = $this->getTestPurchaseState();
$purchaseState->addProduct(
new TextPurchaseItem(
'Textová položka',
2,
new Price(toDecimal('1239.6694'),
$this->currencyContext->getActive(),
'21'
),
'21')
);
$status = 'OK';
try {
$this->posWarehouseUtil->checkPurchaseStateItemsStockIn($purchaseState, $posEntity);
} catch (\Exception $e) {
$status = $e->getMessage();
}
$this->assertEquals('OK', $status);
$purchaseState->getProducts()[1]->pieces = 2;
try {
$this->posWarehouseUtil->checkPurchaseStateItemsStockIn($purchaseState, $posEntity);
} catch (\Exception $e) {
$status = $e->getMessage();
}
$this->assertEquals('Produkt Bosch vzduchový filtr Octavia 1Z TSI není v požadovaném množství na skladu.', $status);
}
public function testMoveProductsToVirtualBox()
{
$this->removeModule(\Modules::PRODUCTS_BATCHES);
$this->setEnvironment();
$posEntity = $this->getPosEntity();
$order = new \Order();
$order->createFromDB(183932);
$order->fetchItems();
$this->posWarehouseUtil->checkPurchaseStateItemsStockIn($order->getPurchaseState(), $posEntity);
// Check positions before move
$positions = $this->storeItemWorker->checkProductByEanAndLocation('8596389006365')['positions'];
$this->assertEquals(1, array_values(array_filter($positions, fn ($x) => $x['id_position'] == '1138'))[0]['pieces']);
$this->assertEquals(2, array_values(array_filter($positions, fn ($x) => $x['id_position'] == '1139'))[0]['pieces']);
// Najde se rozdíl položek mezi boxem a objednávkou
$warehouseDiff = $this->storeItemWorker->checkOrderMatchesBoxContent(
id_warehouse_order: $warehouseOrder['id'] ?? null,
id_order: $order->id,
id_box: $posEntity->getVirtualBox()
);
// Provede vyrovnání boxu
$this->posWarehouseUtil->updateBoxToMatchOrder(
order: $order,
warehouseDiffs: $warehouseDiff,
idBox: $posEntity->getVirtualBox()
);
// Check positions after move
$positions = $this->storeItemWorker->checkProductByEanAndLocation('8596389006365')['positions'];
$this->assertEquals(0, array_values(array_filter($positions, fn ($x) => $x['id_position'] == '1138'))[0]['pieces']);
$this->assertEquals(1, array_values(array_filter($positions, fn ($x) => $x['id_position'] == '1139'))[0]['pieces']);
}
public function getOrderItems()
{
return [
new \KupShop\GraphQLBundle\ApiAdmin\Types\Order\OrderItem(new \KupShop\OrderingBundle\Entity\Order\OrderItem([
'discount' => null,
'idCharge' => null,
'idOrderItem' => null,
'idProduct' => 21113,
'idVariation' => null,
'pieces' => 1,
'priceWithoutVat' => '139.0000',
'serialNumbers' => null,
'title' => 'Na kytaru bez not',
'uuid' => '8129826c-21de-47cf-a660-eb8c7309dae7',
'vat' => '0',
])),
];
}
/**
* @throws Exception
*/
public function testWholeProcessCreateOrderFromPosWithSeparatePaymentAndCompleting()
{
$this->setEnvironment();
$posEntity = $this->getPosEntity();
$GLOBALS['adminID'] = 0;
// Update And Recalculate pos state (API object simulated)
$response = $this->posOrderUtil->recalculateAndUpdateOrder(
$this->getTestOrderItems(),
[],
[],
[],
null,
null,
null,
false,
'UNDEFINED',
'0',
true
);
// 3297.5207 (Položka 1) + 252.0661 (Položka 2) = 3549.5868 -> 4295.000028 (s DPH)
$this->assertEquals(toDecimal('4295.00')->round(2), toDecimal($response->getPriceWithVat())->round(2));
$this->assertEquals(toDecimal('3549.59')->round(2), toDecimal($response->getPriceWithoutVat())->round(2));
$this->assertCount(2, $response->getItems());
// Create order process with not finish payment
$response = $this->posOrderUtil->recalculateAndUpdateOrder(
$this->getTestOrderItems(),
[],
[],
[],
null,
null,
null,
true,
'CASH',
'100',
true
);
$this->assertEquals(-1, $response->getOrderInfo()->getStatus());
$this->assertEquals('4295', $response->getOrderInfo()->getRemainingPayment());
$this->assertFalse($response->getOrderInfo()->getIsPaid());
$this->assertCount(2, $response->getItems());
$this->assertEquals(43866, $response->getItems()[0]->getIdProduct());
$this->assertEquals('3297.5207', $response->getItems()[0]->getPiecePrice());
$idOrder = $response->getOrderInfo()->getIdOrder();
// Mám uloženo (mergnu idOrderItem jinak se bude znova hledat 1 kus na skladu) - tohle udělá pokladna
$items = $this->getTestOrderItems($response->getItems()[0]->getId(), $response->getItems()[1]->getId());
// Confirm payment
$response = $this->posPaymentUtil->confirmPayment($idOrder, $response->getOrderInfo()->getUuidPayment(), '100', 'CASH');
$this->assertEquals($idOrder, $response->getOrderInfo()->getIdOrder());
$this->assertTrue($response->getSaved());
$this->assertEquals('4195', $response->getOrderInfo()->getRemainingPayment()); // 4295 - 100 = 4195
$this->assertFalse($response->getOrderInfo()->getIsPaid());
// Create payment CARD - 4195
$response = $this->posOrderUtil->recalculateAndUpdateOrder(
$items,
[],
[],
[],
null,
null,
$idOrder,
true,
'CARD',
'4195',
true
);
$this->assertTrue($response->getSaved());
// STATUS PAID
$response = $this->posPaymentUtil->confirmPayment($response->getOrderInfo()->getIdOrder(), $response->getOrderInfo()->getUuidPayment(), '4195', 'CARD');
$this->assertEquals($idOrder, $response->getOrderInfo()->getIdOrder());
$this->assertTrue($response->getSaved());
$this->assertEquals('0', $response->getOrderInfo()->getRemainingPayment());
$this->assertTrue($response->getOrderInfo()->getIsPaid());
// Finish order
$response = $this->posOrderUtil->finishOrder($idOrder);
$this->assertNull($response->getException());
$this->assertEquals($idOrder, $response->getIdOrder());
// Check order DB data
$qb = sqlQueryBuilder()
->select('*')
->from('orders')
->andWhere('id = :idOrder')
->setParameter('idOrder', $idOrder)
->execute()
->fetchAssociative();
$this->assertEquals('POS', $qb['flags']);
$this->assertEquals('pos', $qb['source']);
$this->assertEquals(1, $qb['status_payed']);
$this->assertEquals('Platba kartou na pobočce - Osobní odběr Praha', $qb['delivery_type']); // Saves last one -> CARD
$this->assertEquals('Maloobchodní prodej', $qb['invoice_name']);
$this->assertEquals(toDecimal('4295')->round(2), toDecimal($qb['total_price'])->round(2));
$this->assertEquals(toDecimal('3549.5868')->round(2), toDecimal($qb['total_price_without_vat'])->round(2));
$qb = sqlQueryBuilder()
->select('*')
->from('pos_payments_relation', 'ppr')
->leftJoin('ppr', 'order_payments', 'op', 'ppr.id_payment = op.id')
->andWhere('ppr.id_pos = :idPos AND op.id_order = :idOrder')
->setParameter('idPos', $posEntity->getId())
->setParameter('idOrder', $idOrder)
->execute()
->fetchAllAssociative();
$this->assertCount(2, $qb);
$this->assertEquals(1, $qb[0]['method']);
$this->assertEquals(toDecimal('100')->round(2), toDecimal($qb[0]['price'])->round(2));
$this->assertEquals(2, $qb[1]['method']);
$this->assertEquals(toDecimal('4195')->round(2), toDecimal($qb[1]['price'])->round(2));
}
/**
* @dataProvider data_testFindProduct
*/
public function testFindProduct(array $testData)
{
$this->setEnvironment();
$this->getPosEntity();
$response = $this->posOrderUtil->recalculateAndUpdateOrder(
$this->getOrderItems(),
[],
$testData['search'],
[],
null,
null,
null,
false,
'CARD',
'4195',
true
);
$this->assertEquals($testData['expectedIdProduct'], $response->getItems()[1]->getIdProduct() ?? null);
}
public function data_testFindProduct()
{
yield 'code' => [['search' => ['1F0129620/ 1987429405'], 'expectedIdProduct' => '12145']];
yield 'normalEan' => [['search' => ['3165143344057'], 'expectedIdProduct' => '12145']];
yield 'eanWithZero' => [['search' => ['003165143344057'], 'expectedIdProduct' => '12145']];
}
}