first commit
This commit is contained in:
542
bundles/External/PompoBundle/Util/Api/ApiUtil.php
vendored
Normal file
542
bundles/External/PompoBundle/Util/Api/ApiUtil.php
vendored
Normal file
@@ -0,0 +1,542 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace External\PompoBundle\Util\Api;
|
||||
|
||||
use KupShop\AdminBundle\Util\ActivityLog;
|
||||
use KupShop\CatalogBundle\ProductList\ProductList;
|
||||
use KupShop\KupShopBundle\Context\ContextManager;
|
||||
use KupShop\KupShopBundle\Context\CurrencyContext;
|
||||
use KupShop\KupShopBundle\Util\Contexts;
|
||||
use KupShop\KupShopBundle\Util\Functional\Mapping;
|
||||
use KupShop\KupShopBundle\Util\Price\Price;
|
||||
use KupShop\OrderDiscountBundle\Entity\OrderDiscount;
|
||||
use KupShop\OrderDiscountBundle\Triggers\CouponTrigger;
|
||||
use KupShop\OrderDiscountBundle\Triggers\DateTimeTrigger;
|
||||
use KupShop\OrderDiscountBundle\Triggers\GeneratedCouponTrigger;
|
||||
use KupShop\OrderDiscountBundle\Triggers\UsesCountTrigger;
|
||||
use KupShop\OrderDiscountBundle\Util\DiscountManager;
|
||||
use KupShop\OrderingBundle\Entity\Purchase\ProductPurchaseItem;
|
||||
use KupShop\OrderingBundle\Entity\Purchase\PurchaseState;
|
||||
use KupShop\OrderingBundle\Exception\CartValidationException;
|
||||
use KupShop\OrderingBundle\Util\Purchase\PurchaseUtil;
|
||||
use Query\Operator;
|
||||
use Query\QueryBuilder;
|
||||
|
||||
class ApiUtil
|
||||
{
|
||||
private ActivityLog $activityLog;
|
||||
private PurchaseUtil $purchaseUtil;
|
||||
private ProductList $productList;
|
||||
private DiscountManager $discountManager;
|
||||
private ContextManager $contextManager;
|
||||
|
||||
public function __construct(
|
||||
ActivityLog $activityLog,
|
||||
PurchaseUtil $purchaseUtil,
|
||||
ProductList $productList,
|
||||
DiscountManager $discountManager,
|
||||
ContextManager $contextManager,
|
||||
) {
|
||||
$this->activityLog = $activityLog;
|
||||
$this->purchaseUtil = $purchaseUtil;
|
||||
$this->productList = $productList;
|
||||
$this->discountManager = $discountManager;
|
||||
$this->contextManager = $contextManager;
|
||||
}
|
||||
|
||||
public function auth(array $data): ?array
|
||||
{
|
||||
$username = $data['user'] ?? null;
|
||||
$password = $data['password'] ?? '';
|
||||
|
||||
$admin = sqlQueryBuilder()
|
||||
->select('id, password, token')
|
||||
->from('admins')
|
||||
->where(Operator::equals(['login' => $username]))
|
||||
->execute()->fetchAssociative();
|
||||
|
||||
if (!$admin) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!password_verify((string) $password, $admin['password'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'value' => 'Bearer '.$admin['token'],
|
||||
'expiresIn' => $this->formatDateTime((new \DateTime())->add(new \DateInterval('P1M'))),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Kontrola / uplatnění existujícího voucheru. Backward compatibility s API od Air&Me od kterého přecházeli - je to kvůli tomu, aby se
|
||||
* nemuselo měnit nic na straně pokladen.
|
||||
*/
|
||||
public function useVoucher(array $data, bool $try = false, bool $get = false): array
|
||||
{
|
||||
/** @var CurrencyContext $currencyContext */
|
||||
$currencyContext = Contexts::get(CurrencyContext::class);
|
||||
|
||||
$currency = $data['localcurrencyisocode'] ?? null;
|
||||
if (!array_key_exists($currency, $currencyContext->getAll())) {
|
||||
$currency = $currencyContext->getDefaultId();
|
||||
}
|
||||
|
||||
$coupon = $data['number'] ?? null;
|
||||
|
||||
$message = '[PompoAPI] Volání metody "UseVoucher", try='.((int) $try).', kupón: %s';
|
||||
if ($get) {
|
||||
$message = '[PompoAPI] Volání metody "GetVoucher", kupón: %s';
|
||||
}
|
||||
|
||||
// zalogovat volani
|
||||
$this->activityLog->addActivityLog(
|
||||
ActivityLog::SEVERITY_NOTICE,
|
||||
ActivityLog::TYPE_SYNC,
|
||||
sprintf($message, $coupon),
|
||||
[
|
||||
'data' => $data,
|
||||
]
|
||||
);
|
||||
|
||||
// aktivuju spravnou currency
|
||||
[$orderDiscount, $result] = $this->contextManager->activateContexts([CurrencyContext::class => $currency], function () use ($coupon, $data) {
|
||||
$purchaseState = $this->createPurchaseStateFromProductsInfo($data['productsinfo'] ?? []);
|
||||
$purchaseState->addCoupon($coupon);
|
||||
|
||||
$this->discountManager->setPurchaseState($purchaseState);
|
||||
$this->discountManager->recalculate();
|
||||
|
||||
return $this->getUseVoucherData($purchaseState, $coupon);
|
||||
});
|
||||
|
||||
// uplatnuji kupon
|
||||
if (!$try && $result['isValid'] && $result['isUsable']) {
|
||||
// pridavam pocet pouziti ke sleve
|
||||
sqlQueryBuilder()
|
||||
->update('order_discounts')
|
||||
->set('uses_count', 'uses_count + 1')
|
||||
->where(Operator::equals(['id' => $orderDiscount->getId()]))
|
||||
->execute();
|
||||
|
||||
if ($generatedCouponTrigger = $this->getTriggerByType($orderDiscount->getTriggers(), GeneratedCouponTrigger::getType())) {
|
||||
// hledam prodejnu, na ktery byl uplatnen
|
||||
$sellerId = null;
|
||||
if (!empty($data['locationnumber'])) {
|
||||
$sellerId = $this->getSellerByBranchId((string) $data['locationnumber']);
|
||||
}
|
||||
|
||||
// ulozim si nejake informace o pouziti kuponu
|
||||
// primarne jde o prodejnu, na ktere byl kupon pouzit
|
||||
$usageInfo = [
|
||||
'sellerId' => $sellerId,
|
||||
'date' => (new \DateTime())->format('Y-m-d H:i:s'),
|
||||
'metadata' => $data['operationmetadata'] ?? '',
|
||||
'apiResult' => $result,
|
||||
];
|
||||
|
||||
sqlQueryBuilder()
|
||||
->update('discounts_coupons')
|
||||
->directValues(
|
||||
[
|
||||
'used' => 'Y',
|
||||
'data' => json_encode($usageInfo),
|
||||
]
|
||||
)
|
||||
->where(
|
||||
Operator::equals(
|
||||
[
|
||||
'code' => $coupon,
|
||||
]
|
||||
)
|
||||
)->execute();
|
||||
|
||||
// zalogovat uplatneni poukazu
|
||||
$this->activityLog->addActivityLog(
|
||||
ActivityLog::SEVERITY_NOTICE,
|
||||
ActivityLog::TYPE_SYNC,
|
||||
sprintf('[PompoAPI] Poukaz "%s" byl úspěšně uplatněn.', $coupon),
|
||||
[
|
||||
'sellerId' => $sellerId,
|
||||
'apiResult' => $result,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reaktivuje kupón - změní stav z Uplatněno na Neuplatněno.
|
||||
*/
|
||||
public function rechargeVoucher(array $data): bool
|
||||
{
|
||||
$couponNumber = $data['number'] ?? null;
|
||||
|
||||
$coupon = sqlQueryBuilder()
|
||||
->select('*')
|
||||
->from('discounts_coupons')
|
||||
->where(Operator::equals(['code' => $couponNumber]))
|
||||
->execute()->fetchAssociative();
|
||||
|
||||
if (!$coupon) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($coupon['used'] != 'Y') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->activityLog->addActivityLog(
|
||||
ActivityLog::SEVERITY_NOTICE,
|
||||
ActivityLog::TYPE_SYNC,
|
||||
sprintf('[PompoAPI] Volání metody "RechargeVoucher", kupón: %s', $couponNumber),
|
||||
[
|
||||
'data' => $data,
|
||||
]
|
||||
);
|
||||
|
||||
sqlQueryBuilder()
|
||||
->update('discounts_coupons')
|
||||
->directValues(['used' => 'N'])
|
||||
->where(Operator::equals(['id' => $coupon['id']]))
|
||||
->execute();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Backward compatibility s API od Air&Me od kterého přecházeli. Vrací to obecné data o voucheru - na které produktu lze uplatnit, zda
|
||||
* existuje, zda se dá použít...
|
||||
*/
|
||||
private function getUseVoucherData(PurchaseState $purchaseState, string $coupon): array
|
||||
{
|
||||
// zda kupon existuje a je mozne ho uplatnit
|
||||
$isValid = false;
|
||||
$message = null;
|
||||
// usability status - pozustatek z Air&me / Pokusil jsem se ho zjednodusit na dva stavy, protoze to nechceme rozlisovat
|
||||
$usabilityStatus = 0;
|
||||
// kontroluju validitu kuponu
|
||||
if ($this->discountManager->couponNumberExists($coupon, $orderDiscounts)) {
|
||||
try {
|
||||
if ($this->discountManager->isCouponValid($coupon, $orderDiscounts)) {
|
||||
$isValid = true;
|
||||
$usabilityStatus = 1;
|
||||
}
|
||||
} catch (CartValidationException $e) {
|
||||
$message = $e->getMessage();
|
||||
}
|
||||
}
|
||||
$discountId = array_keys($orderDiscounts ?? [])[0] ?? null;
|
||||
|
||||
// nactu si produkty, na ktere byla sleva uplatnena
|
||||
$usableForProducts = [];
|
||||
|
||||
$purchaseStateProducts = array_filter($purchaseState->getProducts(), function ($x) {
|
||||
return strpos($x->getNote()['description'] ?? '', 'tmp product') === false;
|
||||
});
|
||||
foreach ($purchaseStateProducts as $item) {
|
||||
$productDiscounts = array_filter($item->getDiscounts(), function ($x) use ($discountId) { return $x['id'] == $discountId; });
|
||||
if (empty($productDiscounts)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$usableForProducts[] = $item->getProduct()->code;
|
||||
}
|
||||
|
||||
// pokud z pokladny prijde produkt, kterej neni na shopu, tak je v purchase statu tmp product, abych mohl spocitat slevu
|
||||
// zaroven potrebuju vratit, pro ktery produkty je ta sleva pouzitelna, takze v pripade, ze mam tmp product vratim vsechny produkty
|
||||
// ktere mi prisly
|
||||
if ($this->purchaseStateHasTmpItem($purchaseState)) {
|
||||
$usableForProducts = array_merge(
|
||||
$purchaseState->getCustomData('productNumbers') ?: [],
|
||||
$usableForProducts
|
||||
);
|
||||
}
|
||||
|
||||
// zda byl kupon pouzit
|
||||
$isUsable = $isValid && (!empty($usableForProducts) || empty($purchaseStateProducts)) && $discountId && in_array($discountId, $purchaseState->getUsedDiscounts() ?? []);
|
||||
$orderDiscount = null;
|
||||
if ($discountId) {
|
||||
$orderDiscount = $this->discountManager->getOrderDiscountById($discountId);
|
||||
}
|
||||
|
||||
$resultData = [
|
||||
'isValid' => $isValid,
|
||||
'isUsable' => $isUsable,
|
||||
'usabilityStatus' => $usabilityStatus,
|
||||
'message' => $message,
|
||||
'voucher' => ($isUsable && $discountId) ? $this->getCouponData($purchaseState, $orderDiscount, $coupon) : null,
|
||||
'usableForProducts' => $usableForProducts,
|
||||
];
|
||||
|
||||
return [$orderDiscount, $resultData];
|
||||
}
|
||||
|
||||
/**
|
||||
* Backward compatibility s API od Air&Me od kterého přecházeli. Vrací to informace o konkrétním voucheru - platnost, hodnotu atd...
|
||||
*/
|
||||
private function getCouponData(PurchaseState $purchaseState, OrderDiscount $orderDiscount, string $coupon): array
|
||||
{
|
||||
$discountItem = array_filter($purchaseState->getDiscounts(), function ($x) use ($orderDiscount) { return $x->getIdDiscount() == $orderDiscount->getId(); });
|
||||
$discountItem = reset($discountItem);
|
||||
|
||||
try {
|
||||
$date = new \DateTime($orderDiscount->getDateCreated());
|
||||
} catch (\Throwable $e) {
|
||||
$date = new \DateTime();
|
||||
}
|
||||
|
||||
// One-time
|
||||
$useType = 1;
|
||||
|
||||
$discountType = 'value';
|
||||
foreach ($orderDiscount->getActions() as $action) {
|
||||
if (($action['data']['unit'] ?? false) === 'perc') {
|
||||
$discountType = 'perc';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!($trigger = $this->getTriggerByType($orderDiscount->getTriggers(), CouponTrigger::getType()))) {
|
||||
$trigger = $this->getTriggerByType($orderDiscount->getTriggers(), GeneratedCouponTrigger::getType());
|
||||
}
|
||||
|
||||
$dateFrom = (new \DateTime())->setTime(0, 0);
|
||||
$dateTo = (new \DateTime())->add(new \DateInterval('P1Y'));
|
||||
|
||||
// je to generated coupon
|
||||
if ($trigger['data']['generate_coupon'] ?? false) {
|
||||
if ($generatedCoupon = $this->getGeneratedCoupon((int) $trigger['data']['generate_coupon'])) {
|
||||
if ($generatedCoupon['date_from'] ?? false) {
|
||||
$dateFrom = $this->createDateTimeFromString($generatedCoupon['date_from'] ?: '');
|
||||
}
|
||||
|
||||
if ($generatedCoupon['date_to'] ?? false) {
|
||||
$dateTo = $this->createDateTimeFromString($generatedCoupon['date_to'] ?: '');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Repeatedly
|
||||
$useType = 2;
|
||||
// pokud to neni generovany kod a ma use count trigger
|
||||
if ($this->getTriggerByType($orderDiscount->getTriggers(), UsesCountTrigger::getType())) {
|
||||
// RepeatedlyCountDecreasing
|
||||
$useType = 4;
|
||||
}
|
||||
}
|
||||
|
||||
// zkusim najit date interval trigger
|
||||
if ($dateTimeTrigger = $this->getTriggerByType($orderDiscount->getTriggers(), DateTimeTrigger::getType())) {
|
||||
if ($dateTimeTrigger['data']['dateFrom'] ?? false) {
|
||||
$dateFrom = $this->createDateTimeFromString($dateTimeTrigger['data']['dateFrom'] ?: '');
|
||||
}
|
||||
|
||||
if ($dateTimeTrigger['data']['dateTo'] ?? false) {
|
||||
$dateTo = $this->createDateTimeFromString($dateTimeTrigger['data']['dateTo'] ?: '');
|
||||
}
|
||||
}
|
||||
|
||||
$discountValue = $discountItem ? $discountItem->getPriceWithVat()->abs()->asFloat() : 0;
|
||||
$discountValueType = 0;
|
||||
// pokud je to precentuelni sleva, tak si procenta vytahnu z nazvu slevy
|
||||
if ($discountType === 'perc') {
|
||||
if ($discountItem && preg_match('/\((?<discount>\d+)%\)$/', $discountItem->getName(), $match)) {
|
||||
$discountValue = (float) $match['discount'];
|
||||
} else {
|
||||
$discountValue = 0;
|
||||
}
|
||||
$discountValueType = 1;
|
||||
}
|
||||
|
||||
return [
|
||||
'voucherID' => (int) $trigger['id'],
|
||||
'templateID' => (int) $orderDiscount->getId(),
|
||||
'number' => $coupon,
|
||||
'shortNumber' => '',
|
||||
'userID' => 0,
|
||||
'value' => $discountValue,
|
||||
'status' => 1,
|
||||
'created' => $this->formatDateTime($date),
|
||||
'modified' => $this->formatDateTime($date),
|
||||
'template' => [
|
||||
'voucherTemplateId' => (int) $orderDiscount->getId(),
|
||||
'name' => $orderDiscount->getDisplayName(),
|
||||
'description' => $orderDiscount->getDisplayName(),
|
||||
'valueType' => $discountValueType,
|
||||
'useType' => $useType,
|
||||
'value' => $discountValue,
|
||||
'isActive' => $orderDiscount->isActive() === 'Y',
|
||||
'numberMaskId' => (int) $orderDiscount->getId(),
|
||||
'numberMask' => '',
|
||||
'shortNumberMaskId' => (int) $orderDiscount->getId(),
|
||||
'shortNumberMask' => '',
|
||||
'discountRelativeValueType' => 0,
|
||||
'validityRelative' => 0,
|
||||
'validityFixed' => $this->formatDateTime($dateTo),
|
||||
'created' => $this->formatDateTime($date),
|
||||
'updateTime' => $this->formatDateTime($date),
|
||||
'categoryCodes' => [],
|
||||
],
|
||||
'attributes' => [],
|
||||
'detailURL' => null,
|
||||
'validSince' => $this->formatDateTime($dateFrom),
|
||||
'validTill' => $this->formatDateTime($dateTo),
|
||||
];
|
||||
}
|
||||
|
||||
private function formatDateTime(\DateTime $dateTime): string
|
||||
{
|
||||
return $dateTime->format('Y-m-d\TH:i:s');
|
||||
}
|
||||
|
||||
private function createDateTimeFromString(string $datetime): \DateTime
|
||||
{
|
||||
$defaultDateTime = (new \DateTime())->setTime(0, 0, 0);
|
||||
if (empty($datetime) || $datetime === '0000-00-00 00:00:00') {
|
||||
return $defaultDateTime;
|
||||
}
|
||||
|
||||
try {
|
||||
return new \DateTime($datetime);
|
||||
} catch (\Throwable $e) {
|
||||
return $defaultDateTime;
|
||||
}
|
||||
}
|
||||
|
||||
private function getGeneratedCoupon(int $id): ?array
|
||||
{
|
||||
$coupon = sqlQueryBuilder()
|
||||
->select('*')
|
||||
->from('discounts_coupons')
|
||||
->where(Operator::equals(['id' => $id]))
|
||||
->execute()->fetchAssociative();
|
||||
|
||||
if (!$coupon) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $coupon;
|
||||
}
|
||||
|
||||
private function getTriggerByType(array $triggers, string $type): ?array
|
||||
{
|
||||
$trigger = array_filter($triggers, function ($x) use ($type) { return $x['type'] === $type; });
|
||||
if (!($trigger = reset($trigger))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $trigger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vytvori mi PurchaseState z produktů, které jsou poslány do API.
|
||||
*/
|
||||
private function createPurchaseStateFromProductsInfo(array $items): PurchaseState
|
||||
{
|
||||
$purchaseState = new PurchaseState([]);
|
||||
|
||||
$productNumbers = array_map(function ($x) { return $x['productnumber']; }, $items);
|
||||
$this->productList->andSpec(function (QueryBuilder $qb) use ($productNumbers) {
|
||||
return Operator::inStringArray($productNumbers, 'p.code');
|
||||
});
|
||||
|
||||
$products = Mapping::mapKeys($this->productList->getProducts()->toArray(), function ($k, $v) {
|
||||
return [$v->code, $v];
|
||||
});
|
||||
|
||||
$currency = Contexts::get(CurrencyContext::class)->getActive();
|
||||
|
||||
foreach ($items as $item) {
|
||||
$price = toDecimal($item['totalprice']);
|
||||
if ($price->isZero()) {
|
||||
$price = \DecimalConstants::one();
|
||||
}
|
||||
$price = $price->div(toDecimal($item['quantity']));
|
||||
|
||||
/** @var \Product $product */
|
||||
if (!($product = ($products[$item['productnumber']] ?? false))) {
|
||||
$purchaseState->addProduct(
|
||||
$this->getTmpProductPurchaseItem(
|
||||
$price,
|
||||
(float) $item['quantity']
|
||||
)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
$purchaseState->addProduct(
|
||||
new ProductPurchaseItem(
|
||||
$product->id,
|
||||
$product->variationId ?? null,
|
||||
$item['quantity'],
|
||||
new Price($price, $currency, 0)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// hack pro prazdnej purchasestate
|
||||
if (empty($purchaseState->getProducts())) {
|
||||
$purchaseState->addProduct(
|
||||
$this->getTmpProductPurchaseItem(
|
||||
toDecimal(999999),
|
||||
1
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$purchaseState->setCustomData([
|
||||
'isPompoApi' => true,
|
||||
'productNumbers' => $productNumbers,
|
||||
]);
|
||||
|
||||
return $this->purchaseUtil->recalculateTotalPrices($purchaseState);
|
||||
}
|
||||
|
||||
private function getSellerByBranchId(string $branchId): ?int
|
||||
{
|
||||
$sellerId = sqlQueryBuilder()
|
||||
->select('id')
|
||||
->from('sellers')
|
||||
->where(
|
||||
Operator::equals(['JSON_VALUE(data, \'$.branchId\')' => $branchId])
|
||||
)
|
||||
->execute()->fetchOne();
|
||||
|
||||
if (!$sellerId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int) $sellerId;
|
||||
}
|
||||
|
||||
private function getTmpProductPurchaseItem(\Decimal $price, float $pieces): ProductPurchaseItem
|
||||
{
|
||||
static $tmpCounter = 1;
|
||||
|
||||
return new ProductPurchaseItem(
|
||||
sqlQuery('SELECT id FROM products LIMIT 1')->fetchOne(),
|
||||
null,
|
||||
$pieces,
|
||||
new Price($price, Contexts::get(CurrencyContext::class)->getActive(), 0),
|
||||
['description' => 'tmp product ('.$tmpCounter++.')']
|
||||
);
|
||||
}
|
||||
|
||||
private function purchaseStateHasTmpItem(PurchaseState $purchaseState): bool
|
||||
{
|
||||
$result = false;
|
||||
foreach ($purchaseState->getProducts() as $item) {
|
||||
if (strpos($item->getNote()['description'] ?? '', 'tmp product') !== false) {
|
||||
$result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
70
bundles/External/PompoBundle/Util/AutomaticImport/KnizniWeb/KnizniWebAPI.php
vendored
Normal file
70
bundles/External/PompoBundle/Util/AutomaticImport/KnizniWeb/KnizniWebAPI.php
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace External\PompoBundle\Util\AutomaticImport\KnizniWeb;
|
||||
|
||||
use External\PompoBundle\Exception\PompoException;
|
||||
use KupShop\KupShopBundle\Util\System\CurlUtil;
|
||||
use Symfony\Component\HttpClient\CurlHttpClient;
|
||||
|
||||
class KnizniWebAPI
|
||||
{
|
||||
private const API_BASE_URL = 'https://vo.knizniweb.cz/b2bGate/v2';
|
||||
|
||||
private ?CurlHttpClient $client = null;
|
||||
|
||||
public function __construct(
|
||||
private readonly CurlUtil $curlUtil,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getFeedZipFile(string $token): ?array
|
||||
{
|
||||
return $this->request(['token' => $token]) ?? null;
|
||||
}
|
||||
|
||||
public function requestNewFeed(KnizniWebTypeEnum $type): array
|
||||
{
|
||||
return $this->request(['synctype' => $type->value]);
|
||||
}
|
||||
|
||||
private function request(array $params): array
|
||||
{
|
||||
$response = $this->getClient()->request(
|
||||
'GET',
|
||||
$this->getAPIUrl($params)
|
||||
);
|
||||
|
||||
if ($response->getStatusCode() >= 400) {
|
||||
throw new PompoException('KnizniWeb: api request failure!', null, [
|
||||
'statusCode' => $response->getStatusCode(),
|
||||
'data' => $response->getContent(false),
|
||||
], $response->getStatusCode());
|
||||
}
|
||||
|
||||
return json_decode($response->getContent() ?: '', true) ?: [];
|
||||
}
|
||||
|
||||
private function getAPIUrl(array $params): string
|
||||
{
|
||||
return self::API_BASE_URL.'?'.http_build_query(array_merge($this->getCredentials(), $params));
|
||||
}
|
||||
|
||||
private function getCredentials(): array
|
||||
{
|
||||
return [
|
||||
'login' => 'jakub.sula@pompo.cz',
|
||||
'password' => 'Pompo@123',
|
||||
];
|
||||
}
|
||||
|
||||
private function getClient(): CurlHttpClient
|
||||
{
|
||||
if (!$this->client) {
|
||||
$this->client = $this->curlUtil->getClient();
|
||||
}
|
||||
|
||||
return $this->client;
|
||||
}
|
||||
}
|
||||
11
bundles/External/PompoBundle/Util/AutomaticImport/KnizniWeb/KnizniWebTypeEnum.php
vendored
Normal file
11
bundles/External/PompoBundle/Util/AutomaticImport/KnizniWeb/KnizniWebTypeEnum.php
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace External\PompoBundle\Util\AutomaticImport\KnizniWeb;
|
||||
|
||||
enum KnizniWebTypeEnum: string
|
||||
{
|
||||
case TITULY = 'T';
|
||||
case ANOTACE = 'A';
|
||||
case SKLAD = 'S';
|
||||
case CISELNIKY = 'C';
|
||||
}
|
||||
175
bundles/External/PompoBundle/Util/AutomaticImport/KnizniWeb/KnizniWebUtil.php
vendored
Normal file
175
bundles/External/PompoBundle/Util/AutomaticImport/KnizniWeb/KnizniWebUtil.php
vendored
Normal file
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace External\PompoBundle\Util\AutomaticImport\KnizniWeb;
|
||||
|
||||
use External\PompoBundle\Exception\PompoException;
|
||||
use KupShop\AdminBundle\Util\ActivityLog;
|
||||
use KupShop\KupShopBundle\Util\FileUtil;
|
||||
use KupShop\KupShopBundle\Util\System\PathFinder;
|
||||
|
||||
class KnizniWebUtil
|
||||
{
|
||||
public function __construct(
|
||||
private KnizniWebAPI $api,
|
||||
private PathFinder $pathFinder,
|
||||
private ActivityLog $activityLog,
|
||||
) {
|
||||
}
|
||||
|
||||
public function requestNewFeed(KnizniWebTypeEnum $type): ?string
|
||||
{
|
||||
$dbcfg = \Settings::getDefault();
|
||||
|
||||
$result = $this->api->requestNewFeed($type);
|
||||
|
||||
$this->addActivityLog('Byla odeslána žádost o nový feed: '.$type->name, [
|
||||
'type' => $type->value,
|
||||
'result' => $result,
|
||||
]);
|
||||
|
||||
// ulozim si token pro dalsi feed
|
||||
if (!empty($result['token'])) {
|
||||
$config = $dbcfg->loadValue('knizniweb') ?: [];
|
||||
$config['tokens'][$type->value] = $result['token'];
|
||||
|
||||
$dbcfg->saveValue('knizniweb', $config, false);
|
||||
}
|
||||
|
||||
return !empty($result['token']) ? $result['token'] : null;
|
||||
}
|
||||
|
||||
public function getFeedFile(KnizniWebTypeEnum $type, string $token, bool $withNewFeedRequest = true): ?string
|
||||
{
|
||||
$delay = false;
|
||||
|
||||
if ($withNewFeedRequest) {
|
||||
$token = $this->requestNewFeed($type);
|
||||
$delay = true;
|
||||
}
|
||||
|
||||
try {
|
||||
$file = $this->api->getFeedZipFile($token);
|
||||
} catch (PompoException $e) {
|
||||
if ($e->getCode() == '403' || $e->getCode() == '410') {
|
||||
return null;
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$file || ($file['status'] ?? null) != 200) {
|
||||
$delay = true;
|
||||
}
|
||||
|
||||
if ($delay) {
|
||||
$this->addActivityLog('Čekám na vygenerování XML: '.$type->name, [
|
||||
'type' => $type->value,
|
||||
'token' => $token,
|
||||
]);
|
||||
|
||||
$try = 1;
|
||||
do {
|
||||
sleep(60);
|
||||
$file = $this->api->getFeedZipFile($token);
|
||||
} while ($try++ < 6);
|
||||
|
||||
$this->addActivityLog('Vygenerované XML - '.($file ? 'success' : 'timeout').': '.$type->name, [
|
||||
'type' => $type->value,
|
||||
'token' => $token,
|
||||
'file' => $file,
|
||||
]);
|
||||
}
|
||||
|
||||
$this->addActivityLog('Byly získány infromace o vygenerovaném feedu: '.$type->name, [
|
||||
'type' => $type->value,
|
||||
'token' => $token,
|
||||
'file' => $file,
|
||||
]);
|
||||
|
||||
if (empty($file['url'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->addActivityLog('Stahuji feed pro import: '.$type->name.' - '.$file['url']);
|
||||
|
||||
$result = $this->zipDownloadAndUnzip($file['url'], $this->getFeedFolder($token))[0] ?? null;
|
||||
|
||||
$this->addActivityLog('Byl stažen feed pro import: '.$type->name.' - '.$file['url'], [
|
||||
'result' => $result,
|
||||
]);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function cleanup(): array
|
||||
{
|
||||
$freshCache = [];
|
||||
|
||||
foreach (scandir($this->pathFinder->getTmpDir()) as $file) {
|
||||
if (!str_starts_with($file, 'knizniweb')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$filePath = $this->pathFinder->tmpPath($file);
|
||||
|
||||
// pokud je to jeste fresh
|
||||
if ((time() - filemtime($filePath)) <= 1800) {
|
||||
$token = str_replace('knizniweb_', '', $file);
|
||||
|
||||
$xmlFile = array_values(array_map(
|
||||
fn ($x) => rtrim($filePath, '/').'/'.$x,
|
||||
array_filter(scandir($filePath), fn ($x) => !empty($x) && !in_array($x, ['.', '..']))
|
||||
));
|
||||
|
||||
$freshCache[$token] = $xmlFile[0] ?? false;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
FileUtil::deleteDir($filePath);
|
||||
}
|
||||
|
||||
return $freshCache;
|
||||
}
|
||||
|
||||
private function getFeedFolder(string $token): string
|
||||
{
|
||||
return $this->pathFinder->tmpPath("knizniweb_{$token}");
|
||||
}
|
||||
|
||||
private function zipDownloadAndUnzip(string $url, string $destination): array
|
||||
{
|
||||
$downloader = new \Downloader();
|
||||
$downloader->setMethod('curl');
|
||||
|
||||
$tmpZip = $this->pathFinder->tmpPath('knizniweb.zip');
|
||||
|
||||
// stahnu zip soubor
|
||||
$downloader->copyRemoteFile($url, $tmpZip);
|
||||
|
||||
if (!file_exists($destination)) {
|
||||
mkdir($destination);
|
||||
}
|
||||
|
||||
// unzipnu soubor do slozky
|
||||
$files = FileUtil::unzip($tmpZip, $destination);
|
||||
|
||||
// smazu zip
|
||||
unlink($tmpZip);
|
||||
|
||||
// vratim obsah slozky, kam jsem rozbalil zip
|
||||
return $files;
|
||||
}
|
||||
|
||||
private function addActivityLog(string $message, array $data = [], string $severity = ActivityLog::SEVERITY_NOTICE): void
|
||||
{
|
||||
$this->activityLog->addActivityLog(
|
||||
$severity,
|
||||
ActivityLog::TYPE_IMPORT,
|
||||
'KnizniWeb: '.$message,
|
||||
$data
|
||||
);
|
||||
}
|
||||
}
|
||||
134
bundles/External/PompoBundle/Util/Configuration.php
vendored
Normal file
134
bundles/External/PompoBundle/Util/Configuration.php
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace External\PompoBundle\Util;
|
||||
|
||||
use Query\Operator;
|
||||
|
||||
class Configuration
|
||||
{
|
||||
public const SHOP_POMPO = 'pompo';
|
||||
public const SHOP_NEJHRACKA = 'nejhracka';
|
||||
|
||||
private array $configuration = [];
|
||||
|
||||
public function setConfiguration(array $configuration): void
|
||||
{
|
||||
$this->configuration = $configuration;
|
||||
}
|
||||
|
||||
public function has(string $name): bool
|
||||
{
|
||||
return isset($this->configuration[$name]);
|
||||
}
|
||||
|
||||
public function get(string $name, $default = null)
|
||||
{
|
||||
if (!$this->has($name)) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
return $this->configuration[$name];
|
||||
}
|
||||
|
||||
public function getShop(): string
|
||||
{
|
||||
return $this->get('shop');
|
||||
}
|
||||
|
||||
public function getOrderFinalStatus(): int
|
||||
{
|
||||
return (int) $this->get('order.final_status');
|
||||
}
|
||||
|
||||
public function isPompo(): bool
|
||||
{
|
||||
return $this->getShop() === self::SHOP_POMPO;
|
||||
}
|
||||
|
||||
public function isNejhracka(): bool
|
||||
{
|
||||
return $this->getShop() === self::SHOP_NEJHRACKA;
|
||||
}
|
||||
|
||||
public function getMainStoreId(): int
|
||||
{
|
||||
return $this->get('store.id');
|
||||
}
|
||||
|
||||
public function getDefaultSupplierId(): array
|
||||
{
|
||||
return $this->get('supplier.defaults', []);
|
||||
}
|
||||
|
||||
public function getMarketplaceSuppliers(): array
|
||||
{
|
||||
if (!findModule(\Modules::PRODUCTS_SUPPLIERS)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
static $suppliersCache;
|
||||
|
||||
if ($suppliersCache === null) {
|
||||
$qb = sqlQueryBuilder()
|
||||
->select('id, name, data')
|
||||
->from('suppliers')
|
||||
->where(Operator::not(Operator::inIntArray($this->getDefaultSupplierId(), 'id')));
|
||||
|
||||
$suppliersCache = [];
|
||||
foreach ($qb->execute() as $item) {
|
||||
$item['data'] = json_decode($item['data'] ?: '', true) ?: [];
|
||||
$suppliersCache[$item['id']] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
return $suppliersCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vrátí pole [MENA => ID_CENIKU], ktere rika jaky cenik plati pro jakou menu.
|
||||
*/
|
||||
public function getPriceLists(): array
|
||||
{
|
||||
return [
|
||||
'EUR' => 1,
|
||||
];
|
||||
}
|
||||
|
||||
public function getUsersPriceLists(): array
|
||||
{
|
||||
if ($this->isNejhracka()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
'CZK' => 2,
|
||||
'EUR' => 3,
|
||||
];
|
||||
}
|
||||
|
||||
public function getDMOCPriceLists(): array
|
||||
{
|
||||
if ($this->isNejhracka()) {
|
||||
return [
|
||||
'CZK' => 2,
|
||||
'EUR' => 3,
|
||||
'RON' => 2,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'CZK' => 5,
|
||||
'EUR' => 6,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Cenik s cenou, kde cena = nakupni cena + 5%.
|
||||
*/
|
||||
public function getVIPPriceListId(): int
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
21
bundles/External/PompoBundle/Util/Email/Preprocessor.php
vendored
Normal file
21
bundles/External/PompoBundle/Util/Email/Preprocessor.php
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace External\PompoBundle\Util\Email;
|
||||
|
||||
use External\PompoBundle\Email\UserPOSRegistrationEmail;
|
||||
|
||||
class Preprocessor extends \KupShop\KupShopBundle\Util\Mail\Preprocessor
|
||||
{
|
||||
public function getUtmString(string $campaign): string
|
||||
{
|
||||
$utmString = parent::getUtmString($campaign);
|
||||
|
||||
if ($campaign === mb_strtolower(UserPOSRegistrationEmail::getType())) {
|
||||
return $utmString.'#user-register-form';
|
||||
}
|
||||
|
||||
return $utmString;
|
||||
}
|
||||
}
|
||||
45
bundles/External/PompoBundle/Util/Export/OSSExport.php
vendored
Normal file
45
bundles/External/PompoBundle/Util/Export/OSSExport.php
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace External\PompoBundle\Util\Export;
|
||||
|
||||
use Query\Product;
|
||||
use Query\QueryBuilder;
|
||||
|
||||
class OSSExport
|
||||
{
|
||||
public function export()
|
||||
{
|
||||
$qb = $this->getQueryBuilder();
|
||||
|
||||
header('Content-type: application/xls');
|
||||
header('Content-Disposition: attachment; filename="products-oss-export.csv"');
|
||||
|
||||
$header = [
|
||||
'Kód produktu',
|
||||
'Název produktu',
|
||||
'CN kód',
|
||||
'DPH u produktu',
|
||||
'DPH [CZ]',
|
||||
'DPH [SK]',
|
||||
];
|
||||
|
||||
$fp = fopen('php://output', 'w');
|
||||
fputcsv($fp, $header);
|
||||
foreach ($qb->execute() as $item) {
|
||||
fputcsv($fp, $item, ';');
|
||||
}
|
||||
fclose($fp);
|
||||
}
|
||||
|
||||
public function getQueryBuilder(): QueryBuilder
|
||||
{
|
||||
return sqlQueryBuilder()
|
||||
->select('p.code, p.title, p.id_cn, v.vat')
|
||||
->fromProducts()
|
||||
->leftJoin('p', 'vats', 'v', 'v.id = p.vat')
|
||||
->andWhere(Product::withVats(['CZ', 'SK']))
|
||||
->groupBy('p.id');
|
||||
}
|
||||
}
|
||||
57
bundles/External/PompoBundle/Util/Export/OrdersExcelExport.php
vendored
Normal file
57
bundles/External/PompoBundle/Util/Export/OrdersExcelExport.php
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace External\PompoBundle\Util\Export;
|
||||
|
||||
use External\PompoBundle\Util\Configuration;
|
||||
use External\PompoBundle\Util\Ordering\OrderType;
|
||||
use Query\QueryBuilder;
|
||||
|
||||
class OrdersExcelExport extends \KupShop\OrderingBundle\OrdersExcelExport
|
||||
{
|
||||
/** @required */
|
||||
public Configuration $configuration;
|
||||
|
||||
protected function prepareFields()
|
||||
{
|
||||
$fields = parent::prepareFields();
|
||||
|
||||
$prependFields = [];
|
||||
if ($this->configuration->isPompo()) {
|
||||
$prependFields = [
|
||||
'drsUserId' => [
|
||||
'name' => 'Reg. zákazník',
|
||||
'spec' => function (QueryBuilder $qb) {
|
||||
$qb->leftJoin('o', 'drs_users', 'du', 'du.id_user = o.id_user');
|
||||
|
||||
return 'du.id_drs as drsUserId';
|
||||
},
|
||||
],
|
||||
'sellerBranchId' => [
|
||||
'name' => 'Cílová prodejna',
|
||||
'spec' => function (QueryBuilder $qb) {
|
||||
$qb->leftJoin('o', 'order_sellers', 'os', 'o.id = os.id_order')
|
||||
->leftJoin('os', 'sellers', 'sell', 'sell.id = os.id_seller');
|
||||
|
||||
return 'JSON_VALUE(sell.data, "$.branchId") as sellerBranchId';
|
||||
},
|
||||
],
|
||||
'expectedTod' => [
|
||||
'name' => 'Datum závozu',
|
||||
'spec' => function () {
|
||||
return 'IF(JSON_VALUE(o.note_admin, "$.orderType")="'.OrderType::ORDER_TRANSPORT_RESERVATION.'", JSON_VALUE(o.note_admin, "$.delivery_data.deliveryDate"), "") as expectedTod';
|
||||
},
|
||||
],
|
||||
'originOfGoods' => [
|
||||
'name' => 'Odbavuje',
|
||||
'spec' => function () {
|
||||
return 'IF(JSON_VALUE(o.note_admin, "$.orderType")="'.OrderType::ORDER_RESERVATION.'", "Prodejna", "Centrala") as originOfGoods';
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return array_merge($prependFields, $fields);
|
||||
}
|
||||
}
|
||||
221
bundles/External/PompoBundle/Util/Import/PompoImport.php
vendored
Normal file
221
bundles/External/PompoBundle/Util/Import/PompoImport.php
vendored
Normal file
@@ -0,0 +1,221 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace External\PompoBundle\Util\Import;
|
||||
|
||||
use External\PompoBundle\DRS\Synchronizer\POSOrderSynchronizer;
|
||||
use External\PompoBundle\DRS\Util\DRSApi;
|
||||
use Query\Operator;
|
||||
|
||||
class PompoImport
|
||||
{
|
||||
private DRSApi $drsApi;
|
||||
private POSOrderSynchronizer $posOrderSynchronizer;
|
||||
|
||||
private ?\Closure $writeLineCallback = null;
|
||||
|
||||
public function __construct(DRSApi $drsApi, POSOrderSynchronizer $posOrderSynchronizer)
|
||||
{
|
||||
$this->drsApi = $drsApi;
|
||||
$this->posOrderSynchronizer = $posOrderSynchronizer;
|
||||
}
|
||||
|
||||
public function setWriteLineCallback(callable $writeLineCallback): void
|
||||
{
|
||||
$this->writeLineCallback = $writeLineCallback;
|
||||
}
|
||||
|
||||
public function importDRSCoupons(): void
|
||||
{
|
||||
$couponsMap = [
|
||||
'Poukaz za body - NaturaMed' => 95,
|
||||
'Poukaz za body - Sleva 500 Kč' => 69,
|
||||
'Poukaz za body - TOBOGA' => 65,
|
||||
'Poukaz za body - Dinopark Ostrava' => 61,
|
||||
'Poukaz za body - Dinopark Praha' => 62,
|
||||
'Poukaz za body - Fruitisimo' => 116,
|
||||
'Poukaz za body - RAK' => 158,
|
||||
'Poukaz za body - Aqualand Moravia' => 59,
|
||||
'Poukaz za body - Chocotopia' => 160,
|
||||
'Poukaz za body - POEX' => 161,
|
||||
'Poukaz za body - Sleva 200 Kč' => 94,
|
||||
'Voucher - Narozeniny' => 159,
|
||||
'Poukaz za body - Sleva 250 Kč' => 68,
|
||||
'Poukaz za body - Sleva 100 Kč' => 67,
|
||||
'Poukaz za body - Sleva 50 Kč' => 93,
|
||||
'Poukaz za body - IQLANDIA' => 126,
|
||||
'Poukaz za body - Terezia' => 98,
|
||||
'Voucher - Narozeniny - Kopie' => 159,
|
||||
'Poukaz za body - Dinopark' => 60,
|
||||
];
|
||||
|
||||
$count = 0;
|
||||
foreach ($this->drsApi->getUsersCoupons() as $item) {
|
||||
if (empty($item['code'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$exists = sqlQueryBuilder()
|
||||
->select('id')
|
||||
->from('discounts_coupons')
|
||||
->where(Operator::equals(['code' => $item['code']]))
|
||||
->execute()->fetchOne();
|
||||
|
||||
// kupon uz existuje
|
||||
if ($exists) {
|
||||
// zaktualizuju platnost
|
||||
sqlQueryBuilder()
|
||||
->update('discounts_coupons')
|
||||
->directValues(['used' => ($item['status'] == 2 || $item['status'] == 3) ? 'Y' : 'N'])
|
||||
->where(Operator::equals(['id' => $exists]))
|
||||
->execute();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$userId = sqlQueryBuilder()
|
||||
->select('id_user')
|
||||
->from('drs_users')
|
||||
->where(Operator::equals(['id_drs' => $item['customerId']]))
|
||||
->execute()->fetchOne();
|
||||
|
||||
// nemam usera
|
||||
if (!$userId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// nemam ID generovanych kodu
|
||||
if (!($discountId = ($couponsMap[$item['name']] ?? null))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// importuju kod
|
||||
$couponId = sqlGetConnection()->transactional(function () use ($discountId, $userId, $item) {
|
||||
sqlQueryBuilder()
|
||||
->insert('discounts_coupons')
|
||||
->directValues(
|
||||
[
|
||||
'id_discount' => $discountId,
|
||||
'code' => $item['code'],
|
||||
'date_from' => (new \DateTime($item['dateFrom']))->format('Y-m-d H:i:s'),
|
||||
'date_to' => (new \DateTime($item['dateTo']))->format('Y-m-d H:i:s'),
|
||||
'id_user' => $userId,
|
||||
'used' => ($item['status'] == 2 || $item['status'] == 3) ? 'Y' : 'N',
|
||||
]
|
||||
)->execute();
|
||||
|
||||
return (int) sqlInsertId();
|
||||
});
|
||||
|
||||
sqlQueryBuilder()
|
||||
->insert('drs_user_coupons')
|
||||
->directValues(
|
||||
[
|
||||
'id_drs' => $item['id'],
|
||||
'id_coupon' => $couponId,
|
||||
]
|
||||
)->execute();
|
||||
|
||||
$count++;
|
||||
}
|
||||
|
||||
foreach ($couponsMap as $_ => $discountId) {
|
||||
$count = sqlQueryBuilder()
|
||||
->select('COUNT(id)')
|
||||
->from('discounts_coupons')
|
||||
->where(Operator::equals(['id_discount' => $discountId]))
|
||||
->execute()->fetchOne();
|
||||
|
||||
sqlQueryBuilder()
|
||||
->update('discounts')
|
||||
->directValues(
|
||||
[
|
||||
'uses_max' => $count,
|
||||
]
|
||||
)
|
||||
->where(Operator::equals(['id' => $discountId]))
|
||||
->execute();
|
||||
}
|
||||
|
||||
$this->writeLine('Imported coupons: '.$count);
|
||||
}
|
||||
|
||||
public function importPOSOrders(): void
|
||||
{
|
||||
$dbcfg = \Settings::getDefault();
|
||||
$startTimestamp = $dbcfg->loadValue('drsPOSOrdersTimestampCustom');
|
||||
if (!$startTimestamp) {
|
||||
$startTimestamp = 38096494;
|
||||
}
|
||||
|
||||
$timestampUpdater = function (int $timestamp) use ($dbcfg) {
|
||||
$dbcfg->saveValue('drsPOSOrdersTimestampCustom', $timestamp, false);
|
||||
};
|
||||
|
||||
$forceEnd = false;
|
||||
do {
|
||||
try {
|
||||
$this->writeLine('Processing POS orders...');
|
||||
|
||||
$this->posOrderSynchronizer->processWithCustomTimestamp(
|
||||
$startTimestamp,
|
||||
$timestampUpdater
|
||||
);
|
||||
|
||||
$forceEnd = true;
|
||||
|
||||
$this->writeLine('Done');
|
||||
} catch (\Throwable $e) {
|
||||
if (isDevelopment()) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->writeLine('[ERROR] '.$e->getMessage());
|
||||
sleep(30);
|
||||
}
|
||||
} while ($forceEnd === false);
|
||||
}
|
||||
|
||||
public function importDRSOrders(): void
|
||||
{
|
||||
$dbcfg = \Settings::getDefault();
|
||||
|
||||
$forceEnd = false;
|
||||
do {
|
||||
$startDate = new \DateTime('2022-10-04');
|
||||
// if ($timestamp = $dbcfg->loadValue('pompoOldOrdersTimestamp')) {
|
||||
// $startDate = (new \DateTime())->setTimestamp($timestamp);
|
||||
// }
|
||||
|
||||
try {
|
||||
$this->writeLine('Processing orders...');
|
||||
foreach ($this->drsApi->getOrders($startDate) as $order) {
|
||||
if ($order instanceof \DateTime) {
|
||||
$dbcfg->saveValue('pompoOldOrdersTimestamp', $order->getTimestamp(), false);
|
||||
sleep(5);
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->posOrderSynchronizer->processOrder($order);
|
||||
}
|
||||
|
||||
$forceEnd = true;
|
||||
} catch (\Exception $e) {
|
||||
if (isDevelopment()) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->writeLine('[ERROR] '.$e->getMessage());
|
||||
sleep(30);
|
||||
}
|
||||
} while ($forceEnd === false);
|
||||
|
||||
$this->writeLine('Import done');
|
||||
}
|
||||
|
||||
private function writeLine(string $message): void
|
||||
{
|
||||
call_user_func($this->writeLineCallback, $message);
|
||||
}
|
||||
}
|
||||
127
bundles/External/PompoBundle/Util/Incomaker/PompoContactXMLElement.php
vendored
Normal file
127
bundles/External/PompoBundle/Util/Incomaker/PompoContactXMLElement.php
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace External\PompoBundle\Util\Incomaker;
|
||||
|
||||
use External\PompoBundle\Util\Configuration;
|
||||
use KupShop\IncomakerBundle\Util\XMLElements\ContactXMLElement;
|
||||
use Query\Operator;
|
||||
|
||||
class PompoContactXMLElement extends ContactXMLElement
|
||||
{
|
||||
private Configuration $configuration;
|
||||
|
||||
public function __construct(Configuration $configuration)
|
||||
{
|
||||
$this->configuration = $configuration;
|
||||
}
|
||||
|
||||
protected function getUsers(): array
|
||||
{
|
||||
$users = parent::getUsers();
|
||||
|
||||
// nafetchovat dodatecna data pro pompo
|
||||
if ($this->configuration->isPompo()) {
|
||||
$this->fetchUsersCards($users);
|
||||
$this->fetchUsersChildren($users);
|
||||
$this->fetchUserDRSId($users);
|
||||
}
|
||||
|
||||
return $users;
|
||||
}
|
||||
|
||||
protected function getCustomFields(array &$user)
|
||||
{
|
||||
$customFields = parent::getCustomFields($user);
|
||||
|
||||
foreach ($user['cards'] ?? [] as $i => $card) {
|
||||
$customFields['card_code_'.$i] = ['key' => 'card', 'group' => 'card'.$i, 'value' => $card['code']];
|
||||
$customFields['card_isActive_'.$i] = ['key' => 'isActive', 'group' => 'card'.$i, 'value' => $card['active']];
|
||||
}
|
||||
|
||||
foreach ($user['children'] ?? [] as $i => $child) {
|
||||
try {
|
||||
$birthdate = (new \DateTime($child['date_birth']))->format('Y-m-d');
|
||||
} catch (\Throwable $e) {
|
||||
$birthdate = '';
|
||||
}
|
||||
|
||||
$customFields['child_name_'.$i] = ['key' => 'name', 'group' => 'child'.$i, 'value' => $child['name']];
|
||||
$customFields['child_birthday_'.$i] = ['key' => 'birthday', 'group' => 'child'.$i, 'value' => $birthdate];
|
||||
$customFields['child_gender_'.$i] = ['key' => 'gender', 'group' => 'child'.$i, 'value' => $child['gender']];
|
||||
}
|
||||
|
||||
$customFields['pompoId'] = ['key' => 'pompoId', 'value' => $user['pompoId'] ?? null];
|
||||
|
||||
unset($user['cards'], $user['children'], $user['pompoId']);
|
||||
|
||||
return $customFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Nafetchuje deti k uzivatelum.
|
||||
*/
|
||||
private function fetchUsersChildren(array &$users): void
|
||||
{
|
||||
$userIds = array_map(function ($x) { return $x['id']; }, $users);
|
||||
|
||||
$qb = sqlQueryBuilder()
|
||||
->select('id_user, name, gender, date_birth')
|
||||
->from('users_family')
|
||||
->where(Operator::inIntArray($userIds, 'id_user'));
|
||||
|
||||
$children = [];
|
||||
foreach ($qb->execute() as $item) {
|
||||
$children[$item['id_user']][] = $item;
|
||||
}
|
||||
|
||||
foreach ($users as &$user) {
|
||||
$user['children'] = $children[$user['id']] ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Nafetchuje zakaznicke karty k uzivatelum.
|
||||
*/
|
||||
private function fetchUsersCards(array &$users): void
|
||||
{
|
||||
$userIds = array_map(function ($x) { return $x['id']; }, $users);
|
||||
|
||||
$qb = sqlQueryBuilder()
|
||||
->select('id_user, code, active')
|
||||
->from('user_cards')
|
||||
->where(Operator::inIntArray($userIds, 'id_user'));
|
||||
|
||||
$cards = [];
|
||||
foreach ($qb->execute() as $item) {
|
||||
$cards[$item['id_user']][] = $item;
|
||||
}
|
||||
|
||||
foreach ($users as &$user) {
|
||||
$user['cards'] = $cards[$user['id']] ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Nafetchuje cislo zakaznika (id_drs).
|
||||
*/
|
||||
private function fetchUserDRSId(array &$users): void
|
||||
{
|
||||
$userIds = array_map(function ($x) { return $x['id']; }, $users);
|
||||
|
||||
$qb = sqlQueryBuilder()
|
||||
->select('id_drs, id_user')
|
||||
->from('drs_users')
|
||||
->where(Operator::inIntArray($userIds, 'id_user'));
|
||||
|
||||
$pompoIds = [];
|
||||
foreach ($qb->execute() as $item) {
|
||||
$pompoIds[$item['id_user']] = $item['id_drs'];
|
||||
}
|
||||
|
||||
foreach ($users as &$user) {
|
||||
$user['pompoId'] = $pompoIds[$user['id']] ?? null;
|
||||
}
|
||||
}
|
||||
}
|
||||
59
bundles/External/PompoBundle/Util/Incomaker/PompoProductXMLElement.php
vendored
Normal file
59
bundles/External/PompoBundle/Util/Incomaker/PompoProductXMLElement.php
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace External\PompoBundle\Util\Incomaker;
|
||||
|
||||
use KupShop\IncomakerBundle\Util\XMLElements\ProductXMLElement;
|
||||
use KupShop\KupShopBundle\Context\LanguageContext;
|
||||
use Query\QueryBuilder;
|
||||
|
||||
class PompoProductXMLElement extends ProductXMLElement
|
||||
{
|
||||
private const CAMPAIGNS = ['LC', 'TP', 'QA', 'KA', 'EX', 'BC', 'CH'];
|
||||
|
||||
protected function createQB($specs = null): QueryBuilder
|
||||
{
|
||||
$this->productList->fetchStoresInStore();
|
||||
|
||||
return parent::createQB($specs);
|
||||
}
|
||||
|
||||
protected function createXmlElement($product)
|
||||
{
|
||||
$product->inStore = $product->storesInStore[2]['in_store'] ?? 0; // 2 - Centrální sklad
|
||||
|
||||
parent::createXmlElement($product);
|
||||
}
|
||||
|
||||
protected function getLanguagesAttributes(\Product $product, string $language): array
|
||||
{
|
||||
$attributes = parent::getLanguagesAttributes($product, $language);
|
||||
|
||||
// get campaigns with correctly translated names
|
||||
$campaigns = $this->contextManager->activateContexts([LanguageContext::class => $language], function () use ($product) {
|
||||
if (empty($product->campaign_codes)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$campaigns = implode(',', array_keys($product->campaign_codes));
|
||||
|
||||
productCampaign($campaigns, $codes);
|
||||
|
||||
$result = [];
|
||||
foreach ($codes as $code => $item) {
|
||||
if (in_array($code, self::CAMPAIGNS)) {
|
||||
$result[$code] = $item['plural'];
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
});
|
||||
|
||||
foreach ($campaigns as $key => $name) {
|
||||
$attributes[] = ['attributes' => ['key' => $key], 'value' => $name];
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
}
|
||||
194
bundles/External/PompoBundle/Util/Order/OrderSynchronizerTrait.php
vendored
Normal file
194
bundles/External/PompoBundle/Util/Order/OrderSynchronizerTrait.php
vendored
Normal file
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace External\PompoBundle\Util\Order;
|
||||
|
||||
use External\PompoBundle\Util\Ordering\OrderingUtil;
|
||||
use KupShop\KupShopBundle\Util\Price\Price;
|
||||
use KupShop\OrderingBundle\Entity\Order\OrderItem;
|
||||
use KupShop\OrderingBundle\Util\Order\OrderItemInfo;
|
||||
use Query\Operator;
|
||||
|
||||
/**
|
||||
* Common functions for order synchronization (DataGo, DRS).
|
||||
*/
|
||||
trait OrderSynchronizerTrait
|
||||
{
|
||||
protected function getOrderTypeSpec(?array $types): callable
|
||||
{
|
||||
$field = 'JSON_VALUE(COALESCE(o.note_admin, "{}"), "$.orderType")';
|
||||
|
||||
return $types === null ? Operator::equalsNullable([$field => $types]) : Operator::inStringArray($types, $field);
|
||||
}
|
||||
|
||||
protected function getOrderNumber(\Order $order): string
|
||||
{
|
||||
if ($order->getFlags()['DSE'] ?? false) {
|
||||
if ($expandoOrderId = ($order->getData('expando')['orderId'] ?? false)) {
|
||||
return $expandoOrderId;
|
||||
}
|
||||
}
|
||||
|
||||
return $order->order_no;
|
||||
}
|
||||
|
||||
protected function getOrderNote(\Order $order): string
|
||||
{
|
||||
$note = [(string) $order->note_user];
|
||||
|
||||
$discounts = $order->getData('discounts');
|
||||
if (!empty($discounts['used_coupons'])) {
|
||||
$note[] = 'Pouk: '.implode(', ', $discounts['used_coupons']);
|
||||
}
|
||||
|
||||
return implode('; ', array_filter($note));
|
||||
}
|
||||
|
||||
protected function isOrderItemTransportCharge(OrderItem $item): bool
|
||||
{
|
||||
if (($item->getNote()['item_type'] ?? false) === OrderItemInfo::TYPE_CHARGE) {
|
||||
if (in_array($item->getNote()['id_charge'] ?? false, OrderingUtil::CHARGES_HANDLING_FEE)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function changeOrderStatus(\Order $order, int $status, ?string $emailType = null): void
|
||||
{
|
||||
if (!$this->isOrderStatusNext($order, $status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$forceSendMail = null;
|
||||
// U Expando objednavek nechceme posilat maily
|
||||
if ($order->getFlags()['DSE'] ?? false) {
|
||||
$forceSendMail = false;
|
||||
}
|
||||
|
||||
$order->changeStatus($status, null, $forceSendMail, null, $emailType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vrati true/false na zaklade toho, zda $status je vyssí stav, nez aktualni stav objednavky.
|
||||
*
|
||||
* Pouziva se ke kontrole, aby se neprovedo prepnuti stavu zpet (do nizsiho stavu).
|
||||
*/
|
||||
protected function isOrderStatusNext(\Order $order, int $status): bool
|
||||
{
|
||||
$statuses = array_keys(getOrderStatuses());
|
||||
|
||||
if (($orderStatusIndex = array_search($order->status, $statuses)) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (($newStatusIndex = array_search($status, $statuses)) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($newStatusIndex > $orderStatusIndex) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function isOrderInPerson(\Order $order): bool
|
||||
{
|
||||
if ($deliveryType = $order->getDeliveryType()) {
|
||||
if ($delivery = $deliveryType->getDelivery()) {
|
||||
if ($delivery instanceof \OdberNaProdejne || $delivery->isInPerson()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function setOrderPackageNumber(\Order $order, string $packageNumber): void
|
||||
{
|
||||
if (!empty($order->package_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$order->package_id = $packageNumber;
|
||||
|
||||
sqlQueryBuilder()
|
||||
->update('orders')
|
||||
->directValues(
|
||||
[
|
||||
'package_id' => $packageNumber,
|
||||
]
|
||||
)
|
||||
->where(Operator::equals(['id' => $order->id]))
|
||||
->execute();
|
||||
|
||||
$order->logHistory(sprintf('[DataGo] Číslo balíku: %s', $packageNumber));
|
||||
}
|
||||
|
||||
/** Returns split delivery and payment into single items */
|
||||
protected function splitDeliveryItem(\Order $order, ?OrderItem $deliveryItem): array
|
||||
{
|
||||
if (!$deliveryItem || !($deliveryType = $this->getOrderDeliveryType($order))) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$deliveryPrice = 0;
|
||||
$paymentPrice = 0;
|
||||
|
||||
$deliveryItemPrice = $deliveryItem->getTotalPrice()->getPriceWithVat()->abs()->asFloat();
|
||||
if ($deliveryItemPrice > 0 && $deliveryType->price_payment) {
|
||||
$paymentPrice = $deliveryType->price_payment->getPriceWithVat()->asFloat();
|
||||
$deliveryPrice = $deliveryItemPrice - $paymentPrice;
|
||||
if ($deliveryPrice < 0) {
|
||||
$deliveryPrice = 0;
|
||||
}
|
||||
}
|
||||
|
||||
$result = [];
|
||||
|
||||
if ($deliveryPrice > 0) {
|
||||
$tmpDeliveryItem = [
|
||||
'id' => 'D-'.$deliveryItem->getId(),
|
||||
'price' => $deliveryPrice,
|
||||
'vat' => $deliveryItem->getVat(),
|
||||
'quantity' => $deliveryItem->getPieces(),
|
||||
];
|
||||
|
||||
$result['delivery'] = $tmpDeliveryItem;
|
||||
}
|
||||
|
||||
if ($paymentPrice > 0) {
|
||||
$tmpPaymentType = [
|
||||
'id' => 'P-'.$deliveryItem->getId(),
|
||||
'price' => $paymentPrice,
|
||||
'vat' => $deliveryItem->getVat(),
|
||||
'quantity' => $deliveryItem->getPieces(),
|
||||
];
|
||||
|
||||
$result['payment'] = $tmpPaymentType;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function getOrderDeliveryType(\Order $order): ?\DeliveryType
|
||||
{
|
||||
if ($deliveryType = $order->getDeliveryType()) {
|
||||
$deliveryType->accept(
|
||||
new Price($order->getTotalPrice()->getPriceWithVat(), $order->getTotalPrice()->getCurrency(), 0),
|
||||
false,
|
||||
$order->getPurchaseState()
|
||||
);
|
||||
|
||||
$deliveryType->getPrice();
|
||||
|
||||
return $deliveryType;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
252
bundles/External/PompoBundle/Util/Order/PompoOrderUtil.php
vendored
Normal file
252
bundles/External/PompoBundle/Util/Order/PompoOrderUtil.php
vendored
Normal file
@@ -0,0 +1,252 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace External\PompoBundle\Util\Order;
|
||||
|
||||
use External\PompoBundle\DataGo\Util\DataGoApi;
|
||||
use External\PompoBundle\DRS\Synchronizer\OrderSynchronizer;
|
||||
use External\PompoBundle\DRS\Util\DRSApi;
|
||||
use External\PompoBundle\Util\PompoUtil;
|
||||
use KupShop\OrderingBundle\Entity\Order\OrderItem;
|
||||
use Query\Operator;
|
||||
|
||||
class PompoOrderUtil
|
||||
{
|
||||
public const SYNCHRONIZER_DATAGO = 'dataGo';
|
||||
public const SYNCHRONIZER_DRS = 'drs';
|
||||
|
||||
private DRSApi $drsApi;
|
||||
private DataGoApi $dataGoApi;
|
||||
private PompoUtil $pompoUtil;
|
||||
private OrderSynchronizer $drsOrderSynchronizer;
|
||||
|
||||
public function __construct(DRSApi $drsApi, DataGoApi $dataGoApi, PompoUtil $pompoUtil, OrderSynchronizer $drsOrderSynchronizer)
|
||||
{
|
||||
$this->drsApi = $drsApi;
|
||||
$this->dataGoApi = $dataGoApi;
|
||||
$this->pompoUtil = $pompoUtil;
|
||||
$this->drsOrderSynchronizer = $drsOrderSynchronizer;
|
||||
}
|
||||
|
||||
public function getOrderSynchronizerType(\Order $order): ?string
|
||||
{
|
||||
// pokud je to rezervace, tak jde objednavka pres DRS
|
||||
if (($order->getFlags()['RZ'] ?? false) || ($order->getFlags()['RT'] ?? false)) {
|
||||
return self::SYNCHRONIZER_DRS;
|
||||
}
|
||||
|
||||
// jinak jde objednavka pres DataGo
|
||||
return self::SYNCHRONIZER_DATAGO;
|
||||
}
|
||||
|
||||
public function getOrderItemChargeCode(OrderItem $item): ?string
|
||||
{
|
||||
$chargeId = ($item->getNote()['charge']['id_charge'] ?? null) ?: ($item->getNote()['id_charge'] ?? null);
|
||||
if (!$chargeId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = sqlQueryBuilder()
|
||||
->select('data')
|
||||
->from('charges')
|
||||
->where(Operator::equals(['id' => $chargeId]))
|
||||
->execute()->fetchOne();
|
||||
|
||||
$data = json_decode($data ?: '', true) ?: [];
|
||||
|
||||
if (!empty($data['pompo_sync_code'])) {
|
||||
return $data['pompo_sync_code'];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getOrderItemDifference(\Order $order, array $orderItems = []): array
|
||||
{
|
||||
$currentItems = [];
|
||||
$orderItems = empty($orderItems) ? $this->getOrderItemsCounts($order) : $orderItems;
|
||||
|
||||
try {
|
||||
if ($order->getData('pompoCreateItemDiffFromLocalData')) {
|
||||
// projdu polozky objednavky a vlozim si je do $currentItems
|
||||
foreach ($order->fetchItems() as $item) {
|
||||
$currentItems[$item['id']] = (float) $item['pieces'];
|
||||
}
|
||||
// pokud nejaka polozka v $currentItems chybi, ale v puvodnim stavu byla ($orderItems), tak ji doplnim
|
||||
// do $currentItems s nulovym poctem kusu
|
||||
foreach ($orderItems as $key => $quantity) {
|
||||
if (!isset($currentItems[$key])) {
|
||||
$currentItems[$key] = 0;
|
||||
}
|
||||
}
|
||||
} elseif ($this->getOrderSynchronizerType($order) === self::SYNCHRONIZER_DRS) {
|
||||
$currentItems = $this->drsApi->getOrderCurrentItems(
|
||||
$order->order_no,
|
||||
$this->drsOrderSynchronizer->getLoginBranch($order),
|
||||
array_keys($orderItems)
|
||||
);
|
||||
} else {
|
||||
$dataGoId = sqlQueryBuilder()
|
||||
->select('id_datago')
|
||||
->from('datago_orders')
|
||||
->where(Operator::equals(['id_order' => $order->id]))
|
||||
->execute()->fetchOne();
|
||||
|
||||
if ($dataGoId) {
|
||||
[$user, $pass] = $this->pompoUtil->getDataGoCredentialsByOrder($order);
|
||||
|
||||
$currentItems = $this->dataGoApi->setCredentials($user, $pass)
|
||||
->getOrderCurrentItems($dataGoId);
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
|
||||
return $this->getOrderItemDifferenceResult($orderItems, $currentItems);
|
||||
}
|
||||
|
||||
public function getOrderItemDifferenceResult(array $orderItems, array $dataGoItems): array
|
||||
{
|
||||
$hasDiff = false;
|
||||
foreach ($orderItems as $id => $pieces) {
|
||||
if (($dataGoItems[$id] ?? null) === null || ($dataGoItems[$id] == $pieces)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$hasDiff = true;
|
||||
break;
|
||||
}
|
||||
|
||||
$dataGoItems = empty($dataGoItems) ? [] : $dataGoItems;
|
||||
|
||||
return [
|
||||
'hasDiff' => $hasDiff,
|
||||
'items' => $dataGoItems,
|
||||
];
|
||||
}
|
||||
|
||||
public function getOrderChangedItems(\Order $order): array
|
||||
{
|
||||
$originalStates = $order->getData('orderOriginalStates') ?: [];
|
||||
$originalState = reset($originalStates);
|
||||
$orderItems = [];
|
||||
if (!empty($originalState)) {
|
||||
foreach ($originalState as $itemId => $originalItem) {
|
||||
$orderItems[$itemId] = $originalItem['pieces'];
|
||||
}
|
||||
} else {
|
||||
// pokud nemam original state, tak ho vyrobim
|
||||
foreach ($this->getOrderItems($order) as $item) {
|
||||
if ($item['id_product']) {
|
||||
$originalState[$item->getId()] = $this->prepareSimpleOrderItem($item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$result = $this->getOrderItemDifference($order, $orderItems);
|
||||
|
||||
$changedItems = [];
|
||||
if ($result['hasDiff']) {
|
||||
foreach ($originalState as $originalItemId => $originalItem) {
|
||||
$newPieces = $result['items'][$originalItemId] ?? null;
|
||||
if ($newPieces === null || $newPieces == $originalItem['pieces']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$changedItems[] = array_merge($originalItem, [
|
||||
'oldPieces' => $originalItem['pieces'],
|
||||
'newPieces' => $newPieces,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return $changedItems;
|
||||
}
|
||||
|
||||
public function updateOrderByDifference(\Order $order, array $diffData): void
|
||||
{
|
||||
$newItems = $diffData['items'] ?? [];
|
||||
if (empty($newItems)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$originalState = [];
|
||||
$changeLog = [];
|
||||
foreach ($this->getOrderItems($order) as $item) {
|
||||
if (!$item['id_product']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$originalState[$item->getId()] = $this->prepareSimpleOrderItem($item);
|
||||
|
||||
$newPieces = $newItems[$item->getId()] ?? 0;
|
||||
// nic se nezmenilo
|
||||
if ($item->getPieces() == $newPieces) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// aktualizuju polozky, ktera se zmenila
|
||||
if ($newPieces > 0) {
|
||||
$changeLog[] = sprintf('Změna počtu kusů u položky <strong>%s</strong> (%s): %s -> %s', $item->getDescr(), $item->getId(), $item->getPieces(), $newPieces);
|
||||
$order->updateItem($item->getId(), $newPieces);
|
||||
} else {
|
||||
$changeLog[] = sprintf('Odebrána položka <strong>%s</strong> (%s): %s -> %s', $item->getDescr(), $item->getId(), $item->getPieces(), $newPieces);
|
||||
$order->updateItem($item->getId(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($changeLog)) {
|
||||
// save original state
|
||||
$originalStates = $order->getData('orderOriginalStates') ?: [];
|
||||
$originalStates[] = $originalState;
|
||||
$order->setData('orderOriginalStates', $originalStates);
|
||||
|
||||
if ($this->getOrderSynchronizerType($order) === self::SYNCHRONIZER_DRS) {
|
||||
$logPrefix = '[DRS]';
|
||||
} else {
|
||||
$logPrefix = '[DataGo]';
|
||||
}
|
||||
|
||||
// log info to order history
|
||||
$changeLog = [-1 => '<strong>'.$logPrefix.' Změny položek v objednávce:</strong>'] + array_map(fn ($x) => ' - '.$x, $changeLog);
|
||||
|
||||
$order->logHistory(
|
||||
implode('<br>', $changeLog)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function getOrderItemsCounts(\Order $order): array
|
||||
{
|
||||
$orderItems = [];
|
||||
foreach ($order->fetchItems() as $item) {
|
||||
if ($item['id_product']) {
|
||||
$orderItems[$item['id']] = $item['pieces'];
|
||||
}
|
||||
}
|
||||
|
||||
return $orderItems;
|
||||
}
|
||||
|
||||
public function prepareSimpleOrderItem(OrderItem $item): array
|
||||
{
|
||||
return [
|
||||
'id' => $item->getId(),
|
||||
'productId' => $item->getProductId(),
|
||||
'variationId' => $item->getVariationId(),
|
||||
'pieces' => $item->getPieces(),
|
||||
'price' => $item->getTotalPrice()->getPriceWithVat()->asFloat(),
|
||||
'tax' => $item->getVat(),
|
||||
'name' => $item->getDescr(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return OrderItem[]
|
||||
*/
|
||||
private function getOrderItems(\Order $order): array
|
||||
{
|
||||
return $order->getItems();
|
||||
}
|
||||
}
|
||||
24
bundles/External/PompoBundle/Util/Ordering/OrderType.php
vendored
Normal file
24
bundles/External/PompoBundle/Util/Ordering/OrderType.php
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace External\PompoBundle\Util\Ordering;
|
||||
|
||||
/**
|
||||
* Konstanty s typem objednávky.
|
||||
*/
|
||||
class OrderType
|
||||
{
|
||||
/**
|
||||
* Čistě rezervace na prodejně. Nemá manipulační poplatek a objednávka jde pouze do DRSu - neodesílá se do DataGo.
|
||||
*/
|
||||
public const ORDER_RESERVATION = 'reservation';
|
||||
/**
|
||||
* Objednávka přes centrálu na prodejnu - má manipulační poplatek a objednávka jde jak do DRSu, tak do DataGo.
|
||||
*/
|
||||
public const ORDER_TRANSPORT_RESERVATION = 'transport_reservation';
|
||||
/**
|
||||
* Objednávka přes přepravce - jde do DataGo i do DRSu.
|
||||
*/
|
||||
public const ORDER_TRANSPORT = 'transport';
|
||||
}
|
||||
1152
bundles/External/PompoBundle/Util/Ordering/OrderingUtil.php
vendored
Normal file
1152
bundles/External/PompoBundle/Util/Ordering/OrderingUtil.php
vendored
Normal file
File diff suppressed because it is too large
Load Diff
23
bundles/External/PompoBundle/Util/Ordering/ProductAvailability.php
vendored
Normal file
23
bundles/External/PompoBundle/Util/Ordering/ProductAvailability.php
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace External\PompoBundle\Util\Ordering;
|
||||
|
||||
/**
|
||||
* Konstanty s typem dostupnosti.
|
||||
*/
|
||||
class ProductAvailability
|
||||
{
|
||||
/** Skladem - skladem komplet na prodejne nebo na hlavnim skladu */
|
||||
public const IN_STORE = 4;
|
||||
/** Skladem u dodavatele - produkt je skladem pouze u dodavatele */
|
||||
public const IN_STORE_SUPPLIER = 3;
|
||||
/** Nedostupné */
|
||||
public const NOT_IN_STORE = 0;
|
||||
|
||||
/** Na prodejnu zavezeme - není skladem na prodejně, ale je skladem na hlavním skladu */
|
||||
public const SELLER_IN_STORE_TRANSFER = 1;
|
||||
/** Částečně skladem - neco je skladem na prodejne a neco je skladem na centralnim skladu, kombinace */
|
||||
public const SELLER_IN_STORE_PARTIALLY = 2;
|
||||
}
|
||||
132
bundles/External/PompoBundle/Util/PompoContentUtil.php
vendored
Normal file
132
bundles/External/PompoBundle/Util/PompoContentUtil.php
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace External\PompoBundle\Util;
|
||||
|
||||
use External\PompoBundle\Exception\PompoException;
|
||||
use KupShop\KupShopBundle\Util\FileUtil;
|
||||
use KupShop\KupShopBundle\Util\System\PathFinder;
|
||||
|
||||
class PompoContentUtil
|
||||
{
|
||||
public const CONTENT_TYPE_SECTION = 'section';
|
||||
public const CONTENT_TYPE_PRODUCER = 'producer';
|
||||
|
||||
private PathFinder $pathFinder;
|
||||
|
||||
public function __construct(PathFinder $pathFinder)
|
||||
{
|
||||
$this->pathFinder = $pathFinder;
|
||||
}
|
||||
|
||||
public function uploadContent(array $file, string $subfolder, string $type): bool
|
||||
{
|
||||
// zkontroluju, ze nahravaji ZIP soubor
|
||||
if (!in_array($file['type'], $this->getZipMimeTypes())) {
|
||||
throw new PompoException('Nahrávaný soubor musí být ZIP soubor');
|
||||
}
|
||||
|
||||
$tmpPath = $this->pathFinder->dataPath($type.'/pompo_content/_tmp_'.$subfolder);
|
||||
$savePath = $this->getContentPath($subfolder, $type);
|
||||
|
||||
// smazu aktualni content
|
||||
if (file_exists($savePath)) {
|
||||
FileUtil::deleteDir($savePath);
|
||||
}
|
||||
|
||||
// vytvorim slozku
|
||||
if (!file_exists($tmpPath)) {
|
||||
mkdir($tmpPath, 0777, true);
|
||||
}
|
||||
|
||||
$originalTmpPath = $tmpPath;
|
||||
|
||||
$zip = new \ZipArchive();
|
||||
// rozbalim zip
|
||||
if ($zip->open($file['tmp_name'])) {
|
||||
// Rozbalim si ZIP do tmp slozky
|
||||
$zip->extractTo($tmpPath);
|
||||
$zip->close();
|
||||
|
||||
$files = array_filter(scandir($tmpPath), function ($x) { return !empty(trim($x, '.')); });
|
||||
// Pokud je v ZIPu jeste slozka, ve ktere ten obsah je, tak to z ni vytahnu
|
||||
if (count($files) === 1) {
|
||||
if ($dir = reset($files)) {
|
||||
$tmpPath .= '/'.$dir;
|
||||
}
|
||||
}
|
||||
// Presunu soubory do slozky, kde je ocekavam
|
||||
rename($tmpPath, $savePath);
|
||||
}
|
||||
|
||||
// Smazu tmp slozku
|
||||
FileUtil::deleteDir($originalTmpPath);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function deleteCustomContent(string $subfolder, string $type): bool
|
||||
{
|
||||
$path = $this->getContentPath($subfolder, $type);
|
||||
if (file_exists($path)) {
|
||||
FileUtil::deleteDir($path);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function downloadCustomContent(string $subfolder, string $type): void
|
||||
{
|
||||
$path = $this->getContentPath($subfolder, $type);
|
||||
|
||||
if (file_exists($path)) {
|
||||
$zip = new \ZipArchive();
|
||||
$tmpFile = tempnam($this->pathFinder->getTmpDir(), 'pompo_content_');
|
||||
$zip->open($tmpFile, \ZipArchive::CREATE);
|
||||
|
||||
$dir = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS);
|
||||
$files = new \RecursiveIteratorIterator($dir, \RecursiveIteratorIterator::CHILD_FIRST);
|
||||
// nahazet soubory do zipu
|
||||
foreach ($files as $file) {
|
||||
if ($file->isDir()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$zipName = ltrim($file->getPathname(), $path);
|
||||
$zip->addFile($file->getPathname(), $zipName);
|
||||
}
|
||||
|
||||
if ($result = $zip->close()) {
|
||||
header('Content-disposition: attachment; filename='.$type.'_content_'.$subfolder.'.zip');
|
||||
header('Content-type: application/zip');
|
||||
readfile($tmpFile);
|
||||
|
||||
unlink($tmpFile);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
unlink($tmpFile);
|
||||
|
||||
throw new PompoException('Vlastní obsah se nepodařilo stáhnout.', '', [
|
||||
'result' => $result,
|
||||
'zipStatusString' => $zip->getStatusString(),
|
||||
'type' => $type,
|
||||
'folder' => $subfolder,
|
||||
]);
|
||||
}
|
||||
|
||||
throw new PompoException('Nebyl nalezen žádný vlastní obsah.');
|
||||
}
|
||||
|
||||
private function getContentPath(string $subfolder, string $type): string
|
||||
{
|
||||
return $this->pathFinder->dataPath($type.'/pompo_content/'.$subfolder);
|
||||
}
|
||||
|
||||
private function getZipMimeTypes(): array
|
||||
{
|
||||
return ['application/zip', 'application/octet-stream', 'application/x-zip-compressed', 'multipart/x-zip'];
|
||||
}
|
||||
}
|
||||
443
bundles/External/PompoBundle/Util/PompoGraphQLUpdater.php
vendored
Normal file
443
bundles/External/PompoBundle/Util/PompoGraphQLUpdater.php
vendored
Normal file
@@ -0,0 +1,443 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace External\PompoBundle\Util;
|
||||
|
||||
use KupShop\CatalogBundle\ProductList\ProductList;
|
||||
use KupShop\ContentBundle\Util\Block;
|
||||
use KupShop\KupShopBundle\Context\ContextManager;
|
||||
use KupShop\KupShopBundle\Context\LanguageContext;
|
||||
use KupShop\KupShopBundle\Util\Contexts;
|
||||
use KupShop\KupShopBundle\Util\Functional\Mapping;
|
||||
use KupShop\KupShopBundle\Util\StringUtil;
|
||||
use Query\Operator;
|
||||
use WpjShop\GraphQL\Client;
|
||||
use WpjShop\GraphQL\DataObjects\Product\CollectionItem;
|
||||
use WpjShop\GraphQL\DataObjects\Product\LinkItem;
|
||||
use WpjShop\GraphQL\DataObjects\Product\ParameterValue;
|
||||
use WpjShop\GraphQL\DataObjects\Product\ProductParameter;
|
||||
use WpjShop\GraphQL\DataObjects\Product\RelatedItem;
|
||||
use WpjShop\GraphQL\Enums\ProductVisibility;
|
||||
|
||||
/**
|
||||
* Slouží pro komunikaci mezi `prod/pompo` a `prod/nejhracka`. Z pompa se na nejhracku přenáší informace o produktu.
|
||||
*/
|
||||
class PompoGraphQLUpdater
|
||||
{
|
||||
private const GRAPHQL_URL = 'https://www.nejhracka.cz/admin/graphql';
|
||||
private const GRAPHQL_ACCESS_TOKEN = '201ac0b08889d173f99cfc7230f3f327';
|
||||
|
||||
private ?Client $client = null;
|
||||
|
||||
private ProductList $productList;
|
||||
private Block $block;
|
||||
private ContextManager $contextManager;
|
||||
|
||||
private array $parametersCache = [];
|
||||
|
||||
private array $sectionsListCache = [];
|
||||
private array $producersListCache = [];
|
||||
|
||||
public function __construct(ProductList $productList, Block $block, ContextManager $contextManager)
|
||||
{
|
||||
$this->productList = $productList;
|
||||
$this->block = $block;
|
||||
$this->contextManager = $contextManager;
|
||||
}
|
||||
|
||||
public function updateEditableContent(int $id, array $data): bool
|
||||
{
|
||||
return $this->getClient()->editableContent->update($id, $data)['result'] ?? false;
|
||||
}
|
||||
|
||||
public function updateProduct(int $id, array $data): array
|
||||
{
|
||||
return $this->getClient()->product
|
||||
->addSelection(['descriptionPlus' => ['id']])
|
||||
->update($id, $data) ?? [];
|
||||
}
|
||||
|
||||
public function updateProductLinks(int $externalProductId, \Product $product): void
|
||||
{
|
||||
$links = [];
|
||||
foreach ($product->links ?? [] as $link) {
|
||||
$links[] = new LinkItem(
|
||||
$link['title'],
|
||||
$link['link'],
|
||||
$link['type']
|
||||
);
|
||||
}
|
||||
|
||||
$this->client->product->updateLinks($externalProductId, $links);
|
||||
}
|
||||
|
||||
public function updateProductRelated(int $externalProductId, \Product $product): void
|
||||
{
|
||||
$relatedProducts = [];
|
||||
foreach ($product->products_related ?? [] as $item) {
|
||||
if (empty($item->code)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($relatedExternalId = $this->getProductIdByCode($item->code)) {
|
||||
$relatedProducts[] = new RelatedItem($relatedExternalId);
|
||||
}
|
||||
}
|
||||
|
||||
$this->client->product->updateRelated($externalProductId, $relatedProducts);
|
||||
}
|
||||
|
||||
public function updateProductCollection(int $externalProductId, \Product $product): void
|
||||
{
|
||||
$collectionProducts = [];
|
||||
foreach ($product->collections['own'] ?? [] as $item) {
|
||||
if (empty($item->code)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($relatedExternalId = $this->getProductIdByCode($item->code)) {
|
||||
$collectionProducts[] = new CollectionItem($relatedExternalId);
|
||||
}
|
||||
}
|
||||
|
||||
$this->client->product->updateCollections($externalProductId, $collectionProducts);
|
||||
}
|
||||
|
||||
public function updateProductTranslations(int $externalProductId, int $externalEditableContentId, string $language, \Product $product): void
|
||||
{
|
||||
$translation = sqlQueryBuilder()
|
||||
->select('*')
|
||||
->from('products_translations')
|
||||
->where(Operator::equals(['id_product' => $product->id, 'id_language' => $language]))
|
||||
->execute()->fetchAssociative();
|
||||
|
||||
$productData = [];
|
||||
|
||||
if (!empty($translation['title'])) {
|
||||
$productData['title'] = $translation['title'];
|
||||
}
|
||||
|
||||
if (!empty($translation['short_descr'])) {
|
||||
$productData['description'] = $translation['short_descr'];
|
||||
}
|
||||
|
||||
if (!empty($translation['long_descr'])) {
|
||||
$productData['longDescription'] = $translation['long_descr'];
|
||||
}
|
||||
|
||||
if (!empty($translation['figure'])) {
|
||||
$productData['visibility'] = $this->getProductVisibility($translation['figure']);
|
||||
}
|
||||
|
||||
if (!empty($translation['meta_title']) || !empty($translation['meta_description']) || !empty($translation['meta_keywords'])) {
|
||||
$seoData = [];
|
||||
|
||||
if (!empty($translation['meta_title'])) {
|
||||
$seoData['title'] = $translation['meta_title'];
|
||||
}
|
||||
|
||||
if (!empty($translation['meta_description'])) {
|
||||
$seoData['description'] = $translation['meta_description'];
|
||||
}
|
||||
|
||||
if (!empty($translation['meta_keywords'])) {
|
||||
$seoData['keywords'] = $translation['meta_keywords'];
|
||||
}
|
||||
|
||||
$productData['seo'] = $seoData;
|
||||
}
|
||||
|
||||
if (!empty($productData)) {
|
||||
$this->getClient()->product->translate($externalProductId, $language, $productData);
|
||||
}
|
||||
|
||||
// prelozit popis+
|
||||
if (!empty($product->id_block)) {
|
||||
$defaultBlocks = $this->getBlocks((int) $product->id_block);
|
||||
$translatedBlocks = $this->getBlocks((int) $product->id_block, $language);
|
||||
|
||||
if (json_encode($defaultBlocks) !== json_encode($translatedBlocks)) {
|
||||
$this->getClient()->editableContent->translate(
|
||||
$externalEditableContentId,
|
||||
$language,
|
||||
$this->createEditableContentFromBlocks($translatedBlocks)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function updateProductParameters(int $externalProductId, \Product $product): void
|
||||
{
|
||||
$parameterUpdates = [];
|
||||
|
||||
foreach ($this->getParameters() as $externalParameter) {
|
||||
$parameterUpdates[$externalParameter['id']] = [];
|
||||
}
|
||||
|
||||
foreach ($product->param as $parameter) {
|
||||
// pokud nenajdu parametr, tak ho vytvorim
|
||||
if (!($externalParameterId = ($this->getParameterByName($parameter['name'])['id'] ?? false))) {
|
||||
// nejdriv rozhodnu typ parametru
|
||||
switch ($parameter['value_type']) {
|
||||
case 'float':
|
||||
$type = 'NUMBER';
|
||||
break;
|
||||
case 'char':
|
||||
$type = 'TEXT';
|
||||
break;
|
||||
default:
|
||||
$type = 'LIST';
|
||||
}
|
||||
|
||||
// vytvorim parametr
|
||||
$externalParameterId = $this->createParameter($parameter['name'],
|
||||
$type,
|
||||
empty($parameter['unit']) ? null : $parameter['unit']
|
||||
)['id'];
|
||||
}
|
||||
|
||||
$parameterUpdates[$externalParameterId] = array_map(fn ($x) => $x['value'], $parameter['values']);
|
||||
}
|
||||
|
||||
// aktualizuju parametry na nejhracce
|
||||
$productParameters = [];
|
||||
foreach ($parameterUpdates as $externalParameterId => $values) {
|
||||
$parameterType = $this->getParameterType($externalParameterId);
|
||||
|
||||
$productParameters[] = new ProductParameter(
|
||||
$externalProductId,
|
||||
$externalParameterId,
|
||||
array_map(fn ($x) => new ParameterValue($x, $parameterType), $values)
|
||||
);
|
||||
}
|
||||
|
||||
$this->getClient()->product->updateParameterBulk($productParameters);
|
||||
}
|
||||
|
||||
public function createParameter(string $name, string $type, ?string $unit = null): array
|
||||
{
|
||||
$result = $this->getClient()->parameter->create(
|
||||
[
|
||||
'name' => $name,
|
||||
'type' => $type,
|
||||
'unit' => $unit,
|
||||
]
|
||||
)['parameterCreate'];
|
||||
|
||||
// Pridam parametr do cache
|
||||
return $this->parametersCache[] = $result;
|
||||
}
|
||||
|
||||
public function getSections(bool $force = false): array
|
||||
{
|
||||
if (!$force && $this->sectionsListCache) {
|
||||
return $this->sectionsListCache;
|
||||
}
|
||||
|
||||
return $this->sectionsListCache = $this->getClient()->section->setSelection(
|
||||
[
|
||||
'id',
|
||||
'name',
|
||||
'visible',
|
||||
'url',
|
||||
'isVirtual',
|
||||
'parent' => [
|
||||
'id',
|
||||
'name',
|
||||
'parent' => [
|
||||
'id',
|
||||
'name',
|
||||
'parent' => [
|
||||
'id',
|
||||
'name',
|
||||
'parent' => [
|
||||
'id',
|
||||
'name',
|
||||
'parent' => [
|
||||
'id',
|
||||
'name',
|
||||
'parent' => [
|
||||
'id',
|
||||
'name',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
]
|
||||
)->list()['items'] ?? [];
|
||||
}
|
||||
|
||||
public function getSectionIdByPath(array $path): ?int
|
||||
{
|
||||
static $sectionsByKey = [];
|
||||
|
||||
$createKey = function (array $path) {
|
||||
return StringUtil::slugify(implode('/', $path));
|
||||
};
|
||||
|
||||
if (empty($sectionsByKey)) {
|
||||
$sections = $this->getSections();
|
||||
foreach ($sections as $section) {
|
||||
$remotePath = $this->getRemoteSectionPath($section);
|
||||
|
||||
$sectionsByKey[$createKey($remotePath)] = $section['id'];
|
||||
}
|
||||
}
|
||||
|
||||
$key = $createKey($path);
|
||||
|
||||
return $sectionsByKey[$key] ?? null;
|
||||
}
|
||||
|
||||
public function getRemoteSectionPath(array $section): array
|
||||
{
|
||||
$path = [];
|
||||
|
||||
$currentSection = $section;
|
||||
while ($currentSection) {
|
||||
$path[$currentSection['id']] = $currentSection['name'];
|
||||
|
||||
$currentSection = $currentSection['parent'];
|
||||
}
|
||||
|
||||
return array_reverse($path, true);
|
||||
}
|
||||
|
||||
public function getProducers(): array
|
||||
{
|
||||
if (!empty($this->producersListCache)) {
|
||||
return $this->producersListCache;
|
||||
}
|
||||
|
||||
return $this->producersListCache = $this->client->producer->list()['items'] ?? [];
|
||||
}
|
||||
|
||||
public function getParameters(bool $force = false): array
|
||||
{
|
||||
if (!$force && !empty($this->parametersCache)) {
|
||||
return $this->parametersCache;
|
||||
}
|
||||
|
||||
return $this->parametersCache = $this->getClient()->parameter->list()['items'] ?? [];
|
||||
}
|
||||
|
||||
public function getParameterType(int $parameterId): string
|
||||
{
|
||||
static $parameterTypes;
|
||||
|
||||
if (!$parameterTypes) {
|
||||
$parameterTypes = Mapping::mapKeys($this->getParameters(), fn ($k, $v) => [$v['id'], $v['type']]);
|
||||
}
|
||||
|
||||
return $parameterTypes[$parameterId] ?? 'LIST';
|
||||
}
|
||||
|
||||
public function getParameterByName(string $name): ?array
|
||||
{
|
||||
$nameLower = mb_strtolower($name);
|
||||
|
||||
foreach ($this->getParameters() as $parameter) {
|
||||
if (mb_strtolower($parameter['name']) === $nameLower) {
|
||||
return $parameter;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getProducerIdByName(string $name): ?int
|
||||
{
|
||||
$nameLower = mb_strtolower($name);
|
||||
|
||||
foreach ($this->getProducers() as $producer) {
|
||||
if (mb_strtolower($producer['name']) === $nameLower) {
|
||||
return $producer['id'];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getProductIdByCode(string $code): ?int
|
||||
{
|
||||
return $this->getClient()->product->getByCode($code)['id'] ?? null;
|
||||
}
|
||||
|
||||
public function getProductVisibility(string $visibility): string
|
||||
{
|
||||
switch ($visibility) {
|
||||
case 'Y':
|
||||
return ProductVisibility::VISIBLE;
|
||||
case 'O':
|
||||
return ProductVisibility::CLOSED;
|
||||
default:
|
||||
return ProductVisibility::HIDDEN;
|
||||
}
|
||||
}
|
||||
|
||||
public function getBlocks(int $rootBlockId, ?string $language = null): ?array
|
||||
{
|
||||
$language = $language ?: Contexts::get(LanguageContext::class)->getDefaultId();
|
||||
|
||||
return $this->contextManager->activateContexts([LanguageContext::class => $language], function () use ($rootBlockId) {
|
||||
return $this->block->getBlocks($rootBlockId);
|
||||
});
|
||||
}
|
||||
|
||||
public function getProduct(int $productId, ?string $language = null): ?\Product
|
||||
{
|
||||
$productList = clone $this->productList;
|
||||
$productList->andSpec(Operator::equals(['p.id' => $productId]));
|
||||
$productList->fetchParameters();
|
||||
$productList->fetchSections();
|
||||
$productList->fetchLinks();
|
||||
$productList->fetchCollections();
|
||||
$productList->fetchProductsRelated();
|
||||
$productList->fetchProducers();
|
||||
|
||||
$language = $language ?: Contexts::get(LanguageContext::class)->getDefaultId();
|
||||
|
||||
$products = $this->contextManager->activateContexts([LanguageContext::class => $language], function () use ($productList) {
|
||||
return $productList->getProducts();
|
||||
});
|
||||
|
||||
if (empty($products[$productId])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $products[$productId];
|
||||
}
|
||||
|
||||
public function createEditableContentFromBlocks(array $blocks): array
|
||||
{
|
||||
$data = [
|
||||
'areas' => [],
|
||||
'overwrite' => true,
|
||||
];
|
||||
|
||||
foreach ($blocks as $block) {
|
||||
$data['areas'][] = [
|
||||
'id' => null,
|
||||
'name' => $block['name'],
|
||||
'data' => $block['json_content'] ?: '[]',
|
||||
];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function getClient(): Client
|
||||
{
|
||||
if (!$this->client) {
|
||||
$this->client = new Client(
|
||||
self::GRAPHQL_URL,
|
||||
self::GRAPHQL_ACCESS_TOKEN
|
||||
);
|
||||
}
|
||||
|
||||
return $this->client;
|
||||
}
|
||||
}
|
||||
71
bundles/External/PompoBundle/Util/PompoLogger.php
vendored
Normal file
71
bundles/External/PompoBundle/Util/PompoLogger.php
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace External\PompoBundle\Util;
|
||||
|
||||
use Doctrine\DBAL\Exception\DeadlockException;
|
||||
use Doctrine\DBAL\Exception\LockWaitTimeoutException;
|
||||
use External\PompoBundle\DataGo\Exception\DataGoException;
|
||||
use External\PompoBundle\Exception\PompoException;
|
||||
use KupShop\AdminBundle\Util\ActivityLog;
|
||||
use KupShop\KupShopBundle\Util\Logging\SentryLogger;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class PompoLogger
|
||||
{
|
||||
private ActivityLog $activityLog;
|
||||
private SentryLogger $sentryLogger;
|
||||
private LoggerInterface $logger;
|
||||
|
||||
public function __construct(ActivityLog $activityLog, SentryLogger $sentryLogger, LoggerInterface $logger)
|
||||
{
|
||||
$this->activityLog = $activityLog;
|
||||
$this->sentryLogger = $sentryLogger;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
public function logException(\Throwable $e, string $message, array $data = []): void
|
||||
{
|
||||
if (isDevelopment()) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
// TODO: mozna casem odebrat, at logujeme jen do activity logu
|
||||
if (!($e instanceof PompoException) && !($e instanceof \SoapFault)) {
|
||||
$this->sentryLogger->captureException($e);
|
||||
}
|
||||
|
||||
// PompoException na sobe muze mit i nejaky data na vic
|
||||
if ($e instanceof PompoException) {
|
||||
$prefix = $e instanceof DataGoException ? '[DataGo]' : '[DRS]';
|
||||
$exceptionData = array_merge(['exceptionMessage' => $prefix.' '.$e->getMessage()], $e->getData());
|
||||
if ($e->getDetailedMessage()) {
|
||||
$exceptionData = array_merge(['detailedMessage' => $e->getDetailedMessage()], $exceptionData);
|
||||
}
|
||||
|
||||
$data = array_merge($exceptionData, $data);
|
||||
}
|
||||
|
||||
// Lock wait timeout a deadlock nechci logovat do ActivityLogu
|
||||
if ($e instanceof LockWaitTimeoutException || $e instanceof DeadlockException) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->activityLog->addActivityLog(
|
||||
ActivityLog::SEVERITY_ERROR,
|
||||
ActivityLog::TYPE_SYNC,
|
||||
$message,
|
||||
$data
|
||||
);
|
||||
}
|
||||
|
||||
public function log(string $message, array $data): void
|
||||
{
|
||||
if (isDevelopment()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->logger->notice($message, $data);
|
||||
}
|
||||
}
|
||||
64
bundles/External/PompoBundle/Util/PompoNotifier.php
vendored
Normal file
64
bundles/External/PompoBundle/Util/PompoNotifier.php
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace External\PompoBundle\Util;
|
||||
|
||||
use External\PompoBundle\Email\PompoPartnerCouponsEmail;
|
||||
use Query\Operator;
|
||||
|
||||
class PompoNotifier
|
||||
{
|
||||
private const NOTIFY_LOW_PARTNER_COUPONS_COUNT = 5;
|
||||
|
||||
private PompoPartnerCouponsEmail $partnerCouponsEmail;
|
||||
|
||||
public function __construct(PompoPartnerCouponsEmail $partnerCouponsEmail)
|
||||
{
|
||||
$this->partnerCouponsEmail = $partnerCouponsEmail;
|
||||
}
|
||||
|
||||
/**
|
||||
* Upozornění v případě, že budou docházet partnerské kódy.
|
||||
*/
|
||||
public function notifyLowPartnerCoupons(): void
|
||||
{
|
||||
if (!($notifyEmail = $this->getNotifyEmail())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$qb = sqlQueryBuilder()
|
||||
->select('bpe.id, bpe.name, COUNT(dc.id) as count, MIN(dc.date_to) as min_date')
|
||||
->from('bonus_points_exchange', 'bpe')
|
||||
->leftJoin('bpe', 'discounts_coupons', 'dc', 'dc.id_discount = bpe.id_discount AND dc.used = \'N\' AND dc.id_user IS NULL AND (dc.date_to >= NOW() OR dc.date_to IS NULL)')
|
||||
->where(Operator::equals(['bpe.partner' => 'Y']))
|
||||
->groupBy('bpe.id');
|
||||
|
||||
$lowPartnerCoupons = [];
|
||||
foreach ($qb->execute() as $item) {
|
||||
if ($item['count'] <= self::NOTIFY_LOW_PARTNER_COUPONS_COUNT) {
|
||||
$lowPartnerCoupons[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($lowPartnerCoupons)) {
|
||||
$this->partnerCouponsEmail->setCoupons($lowPartnerCoupons);
|
||||
$email = $this->partnerCouponsEmail->getEmail();
|
||||
$email['to'] = $notifyEmail;
|
||||
|
||||
$this->partnerCouponsEmail->sendEmail($email);
|
||||
}
|
||||
}
|
||||
|
||||
private function getNotifyEmail(): ?string
|
||||
{
|
||||
$dbcfg = \Settings::getDefault();
|
||||
|
||||
$email = $dbcfg->loadValue('pompoSettings')['notify_email'] ?? null;
|
||||
if (empty($email)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $email;
|
||||
}
|
||||
}
|
||||
304
bundles/External/PompoBundle/Util/PompoUtil.php
vendored
Normal file
304
bundles/External/PompoBundle/Util/PompoUtil.php
vendored
Normal file
@@ -0,0 +1,304 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace External\PompoBundle\Util;
|
||||
|
||||
use External\PompoBundle\Admin\Tabs\PompoSettingsTab;
|
||||
use External\PompoBundle\DataGo\Synchronizer\ProductSynchronizer;
|
||||
use External\PompoBundle\DataGo\Util\DataGoLocator;
|
||||
use External\PompoBundle\DRS\Synchronizer\SynchronizerInterface;
|
||||
use External\PompoBundle\DRS\Util\DRSApi;
|
||||
use External\PompoBundle\DRS\Util\DRSLocator;
|
||||
use KupShop\DropshipBundle\Transfer\BaseLinkerTransfer;
|
||||
use KupShop\KupShopBundle\Context\LanguageContext;
|
||||
use KupShop\KupShopBundle\Util\Contexts;
|
||||
use KupShop\KupShopBundle\Util\StringUtil;
|
||||
use KupShop\OrderingBundle\Util\Order\OrderInfo;
|
||||
use KupShop\SellerBundle\Utils\SellerUtil;
|
||||
use Query\Operator;
|
||||
|
||||
class PompoUtil
|
||||
{
|
||||
public function __construct(
|
||||
private DataGoLocator $dataGoLocator,
|
||||
private DRSLocator $drsLocator,
|
||||
private DRSApi $drsApi,
|
||||
private OrderInfo $orderInfo,
|
||||
private ?SellerUtil $sellerUtil,
|
||||
private PompoLogger $pompoLogger,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Vrací jméno a heslo pro zápis objednávek do DataGo, protože každá prodejna nebo jazyková mutace má jiné údaje.
|
||||
*/
|
||||
public function getDataGoCredentialsByOrder(\Order $order): array
|
||||
{
|
||||
$dbcfg = \Settings::getDefault();
|
||||
|
||||
// pokud je to Expando objednávka
|
||||
if ($order->getFlags()['DSE'] ?? false) {
|
||||
if ($expandoCredentials = $this->getDataGoExpandoCredentials($order)) {
|
||||
return $expandoCredentials;
|
||||
}
|
||||
}
|
||||
|
||||
if ($dropshipmentCredentials = $this->getDataGoDropshipmentCredentials($order)) {
|
||||
return $dropshipmentCredentials;
|
||||
}
|
||||
|
||||
// v pripade, ze mame prodejce
|
||||
if ($this->sellerUtil) {
|
||||
// pokud je objednavka s vyzvednutim na prodejne, tak vezmu ID prodejce
|
||||
if ($sellerId = ($order->getData('delivery_data')['seller_id'] ?? false)) {
|
||||
// pokud sellera nenajdu, tak zkusim prenacist sellery, protoze synchronizace bezu v jednom cykly, takze se ta member cache
|
||||
// muze drzet treba i mesic, takze kdyz pridaji noveho sellera, tak v te cache chybi... takze zkusim znovu prenacist sellery
|
||||
if (!$this->sellerUtil->getSeller((int) $sellerId)) {
|
||||
$this->sellerUtil->getSellers(true);
|
||||
}
|
||||
|
||||
if ($seller = $this->sellerUtil->getSeller((int) $sellerId)) {
|
||||
// z prodejce si z custom dat vytahnu udaje pro zapis objednavky
|
||||
$dataGo = $seller['data']['datago'] ?? [];
|
||||
if (!empty($dataGo['user']) && !empty($dataGo['pass'])) {
|
||||
return [$dataGo['user'], $dataGo['pass']];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// defaultni udaje pro zapis objednavky do DataGo podle jazyka
|
||||
if ($credentials = ($dbcfg->datago['default'][$order->getLanguage()] ?? null)) {
|
||||
return [$credentials['user'], $credentials['password']];
|
||||
}
|
||||
|
||||
// Pokud nemam v adminu vyplnene default udaje, tak pouziju default udaje na tvrdo
|
||||
if ($order->getLanguage() === 'sk') {
|
||||
return ['eshopsk', 'kspohse'];
|
||||
}
|
||||
|
||||
return ['PompoShop', 'pohSopmoP'];
|
||||
}
|
||||
|
||||
public function getDataGoDropshipmentCredentials(\Order $order): ?array
|
||||
{
|
||||
if (!($dropshipmentInfo = $this->orderInfo->getOrderDropshipmentInfo($order->id))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$dropshipment = $dropshipmentInfo['dropshipment'];
|
||||
if ($dropshipment['type'] !== BaseLinkerTransfer::getType()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!($marketplace = $dropshipmentInfo['external']['data']['marketplace'] ?? null)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$marketplacesCredentials = [];
|
||||
foreach ($dropshipment['data']['pompo']['datago'] ?? [] as $item) {
|
||||
$marketplacesCredentials[$item['marketplace']][$item['country'] ?? null] = $item;
|
||||
}
|
||||
|
||||
$credentials = $marketplacesCredentials[$marketplace][$order->delivery_country] ?? $marketplacesCredentials[$marketplace][null] ?? null;
|
||||
|
||||
if (!$credentials) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [$credentials['user'], $credentials['password']];
|
||||
}
|
||||
|
||||
/** Vrací jméno a heslo pro zápis objednávky do DataGo pro Expando objednávku. Jméno a heslo podle marketplace, případně ještě podle země. */
|
||||
public function getDataGoExpandoCredentials(\Order $order): ?array
|
||||
{
|
||||
// nactu si marketplace dane objednavky
|
||||
$marketplace = $order->getData('expando')['marketplace'] ?? null;
|
||||
if (!$marketplace) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$key = StringUtil::slugify($marketplace);
|
||||
|
||||
$dbcfg = \Settings::getDefault();
|
||||
|
||||
// vychozi udaje pro dany marketplace
|
||||
$marketplaceDefaultCredentials = $dbcfg->datago['default'][$key] ?? null;
|
||||
if (empty($marketplaceDefaultCredentials['user']) || empty($marketplaceDefaultCredentials['password'])) {
|
||||
$marketplaceDefaultCredentials = $dbcfg->datago['default'][PompoSettingsTab::MARKETPLACE_DEFAULT_KEY] ?? null;
|
||||
}
|
||||
|
||||
// udaje podle marketplacu a podle zeme
|
||||
$credentials = $dbcfg->datago['default'][$key][$order->delivery_country] ?? null;
|
||||
if (empty($credentials['user']) || empty($credentials['password'])) {
|
||||
$credentials = $marketplaceDefaultCredentials;
|
||||
}
|
||||
|
||||
if (empty($credentials['user']) || empty($credentials['password'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [$credentials['user'], $credentials['password']];
|
||||
}
|
||||
|
||||
/**
|
||||
* Vrací výchozí jméno a heslo pro zápis objednávek podle jazykové mutace.
|
||||
*/
|
||||
public function getDataGoDefaultCredentials(?string $language = null): array
|
||||
{
|
||||
$language = $language ?: Contexts::get(LanguageContext::class)->getActiveId();
|
||||
|
||||
$dbcfg = \Settings::getDefault();
|
||||
if ($credentials = ($dbcfg->datago['default'][$language] ?? null)) {
|
||||
return [$credentials['user'], $credentials['password']];
|
||||
}
|
||||
|
||||
if ($language === 'sk') {
|
||||
return ['eshopsk', 'kspohse'];
|
||||
}
|
||||
|
||||
return ['PompoShop', 'pohSopmoP'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Přepočítání skladu. Přenese sklad ze stores_items na produkt, aby seděla zásoba podle stores_items.
|
||||
*/
|
||||
public function recalculateStores(): void
|
||||
{
|
||||
$getQuantitySubQuery = function (bool $variations = false) {
|
||||
$alias = 'p';
|
||||
if ($variations) {
|
||||
$alias = 'pv';
|
||||
}
|
||||
|
||||
sqlQueryBuilder()
|
||||
->update('stores_items')
|
||||
->set('quantity', 0)
|
||||
->where('quantity < 0')
|
||||
->execute();
|
||||
|
||||
$qb = sqlQueryBuilder()
|
||||
->select('COALESCE(SUM(GREATEST(si.quantity, 0)), 0)')
|
||||
->from('stores_items', 'si')
|
||||
->where('si.id_product = '.$alias.'.id');
|
||||
|
||||
if ($variations) {
|
||||
$qb->andWhere('si.id_variation = pv.id');
|
||||
} else {
|
||||
$qb->andWhere('si.id_variation IS NULL');
|
||||
}
|
||||
|
||||
if ($ignoredStores = $this->getIgnoredStores()) {
|
||||
$qb->andWhere(
|
||||
Operator::not(Operator::inIntArray($ignoredStores, 'si.id_store'))
|
||||
);
|
||||
}
|
||||
|
||||
return $qb;
|
||||
};
|
||||
|
||||
$productsSubQuery = $getQuantitySubQuery();
|
||||
sqlQuery('UPDATE products p
|
||||
SET in_store = ('.$productsSubQuery->getSQL().')
|
||||
WHERE in_store != ('.$productsSubQuery->getSQL().');', $productsSubQuery->getParameters(), $productsSubQuery->getParameterTypes());
|
||||
|
||||
$variationsSubQuery = $getQuantitySubQuery(true);
|
||||
sqlQuery(
|
||||
'UPDATE products_variations pv
|
||||
SET pv.in_store = ('.$variationsSubQuery->getSQL().')
|
||||
WHERE pv.in_store != ('.$variationsSubQuery->getSQL().');', $variationsSubQuery->getParameters(), $variationsSubQuery->getParameterTypes());
|
||||
|
||||
\Variations::recalcInStore();
|
||||
}
|
||||
|
||||
/**
|
||||
* Vrací sklady, které se mají ignorovat. Nemá se přenášet jejich skladovost na produkt.
|
||||
*/
|
||||
public function getIgnoredStores(): array
|
||||
{
|
||||
return array_map(function ($x) {
|
||||
return $x['id'];
|
||||
},
|
||||
sqlQueryBuilder()
|
||||
->select('id')
|
||||
->from('stores')
|
||||
->where(Operator::equals(['disabled' => 1]))
|
||||
->execute()->fetchAllAssociative()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Spustí synchronizaci produktů s DataGo.
|
||||
*/
|
||||
public function synchronizeDataGoProducts(array $processTypes): void
|
||||
{
|
||||
foreach ($processTypes as $processType) {
|
||||
try {
|
||||
$synchronizer = $this->dataGoLocator->get(
|
||||
ProductSynchronizer::getType()
|
||||
);
|
||||
$synchronizer->setMode(\External\PompoBundle\DataGo\Synchronizer\SynchronizerInterface::MODE_CSV);
|
||||
$synchronizer->setProcessType($processType);
|
||||
$synchronizer->process();
|
||||
} catch (\Throwable $e) {
|
||||
if (isDevelopment()) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->pompoLogger->logException($e, '[DataGo] Během synchronizace produktů se vyskytla chyba', [
|
||||
'processType' => $processType,
|
||||
'message' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Spustí synchronizaci zadaných typů s DataGo.
|
||||
*/
|
||||
public function synchronizeDataGo(array $types, int $mode = \External\PompoBundle\DataGo\Synchronizer\SynchronizerInterface::MODE_NORMAL): void
|
||||
{
|
||||
foreach ($types as $type) {
|
||||
try {
|
||||
$synchronizer = $this->dataGoLocator->get($type);
|
||||
$synchronizer->setMode($mode);
|
||||
$synchronizer->process();
|
||||
} catch (\Throwable $e) {
|
||||
if (isDevelopment()) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->pompoLogger->logException($e, '[DataGo] Během synchronizace se vyskytla chyba', [
|
||||
'type' => $type,
|
||||
'message' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Spustí synchronizaci zadaných typů s DRSem.
|
||||
*/
|
||||
public function synchronizeDRS(array $types, int $mode = SynchronizerInterface::MODE_NORMAL): void
|
||||
{
|
||||
foreach ($types as $type) {
|
||||
try {
|
||||
$synchronizer = $this->drsLocator->get($type);
|
||||
$synchronizer->setMode($mode);
|
||||
$synchronizer->process();
|
||||
} catch (\Throwable $e) {
|
||||
if (isDevelopment()) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->pompoLogger->logException($e, '[DRS] Během synchronizace se vyskytla chyba', [
|
||||
'type' => $type,
|
||||
'message' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->drsApi->closeConnection();
|
||||
}
|
||||
}
|
||||
756
bundles/External/PompoBundle/Util/ProductUtil.php
vendored
Normal file
756
bundles/External/PompoBundle/Util/ProductUtil.php
vendored
Normal file
@@ -0,0 +1,756 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace External\PompoBundle\Util;
|
||||
|
||||
use KupShop\CatalogBundle\ProductList\MultiFetch;
|
||||
use KupShop\CatalogBundle\ProductList\ProductCollection;
|
||||
use KupShop\CatalogBundle\ProductList\ProductList;
|
||||
use KupShop\KupShopBundle\Context\CurrencyContext;
|
||||
use KupShop\KupShopBundle\Context\UserContext;
|
||||
use KupShop\KupShopBundle\Util\Contexts;
|
||||
use KupShop\KupShopBundle\Util\DateUtil;
|
||||
use KupShop\KupShopBundle\Util\Functional\Mapping;
|
||||
use KupShop\KupShopBundle\Util\Price\Price;
|
||||
use KupShop\KupShopBundle\Util\Price\PriceCalculator;
|
||||
use KupShop\KupShopBundle\Util\Price\ProductPrice;
|
||||
use KupShop\LabelsBundle\Util\LabelUtil;
|
||||
use KupShop\SellerBundle\Context\SellerContext;
|
||||
use Query\Operator;
|
||||
use Query\Product;
|
||||
|
||||
class ProductUtil
|
||||
{
|
||||
private const int MAIN_STORE_ID = 2;
|
||||
private const int SUPPLIER_ID = 3;
|
||||
|
||||
public function __construct(
|
||||
private Configuration $configuration,
|
||||
private ProductList $productList,
|
||||
private LabelUtil $labelUtil,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Multifetch pro nafetchovani dalsich cen, ktere potrebujeme zobrazovat na katalogu / detailu produktu.
|
||||
*
|
||||
* Nafetchuje DMOC cenu, VIP cenu a "Pompo klub setri" cenu.
|
||||
* Zaroven to prida dmocDiscount, coz je sleva vypocitana z DMOC ceny.
|
||||
*/
|
||||
public function fetchAdditionalPrices(ProductCollection $products): void
|
||||
{
|
||||
if ($products->count() === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($products->_pompoVipPricesFetched)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$products->fetchPricelists();
|
||||
$this->fetchDefaultPriceForDiscount($products);
|
||||
|
||||
$currencyContext = Contexts::get(CurrencyContext::class);
|
||||
|
||||
// pokud existuje cenik s DMOC, tak ho nactu
|
||||
if ($dmocPriceListId = ($this->configuration->getDMOCPriceLists()[$currencyContext->getActiveId()] ?? false)) {
|
||||
foreach ($products as $product) {
|
||||
$product->dmocPrice = $product->pricelists[$dmocPriceListId]['productPrice'] ?? null;
|
||||
$product->dmocDiscount = \DecimalConstants::zero();
|
||||
if ($product->dmocPrice) {
|
||||
$product->dmocPrice = PriceCalculator::convert($product->dmocPrice, $product->getProductPrice()->getCurrency());
|
||||
$productPriceWithVat = $product->getProductPrice()->getPriceWithVat();
|
||||
$dmocPriceWithVat = $product->dmocPrice->getPriceWithVat();
|
||||
if ($dmocPriceWithVat->isPositive()) {
|
||||
$product->dmocDiscount = \DecimalConstants::one()->sub($productPriceWithVat->div($dmocPriceWithVat))->mul(\DecimalConstants::hundred());
|
||||
} else {
|
||||
$product->dmocDiscount = \DecimalConstants::zero();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pokud existuje cenik pro prihlaseneho uzivatele, tak nactu VIP cenu
|
||||
if ($vipPriceListId = ($this->configuration->getUsersPriceLists()[$currencyContext->getActiveId()] ?? false)) {
|
||||
$userIsActive = Contexts::get(UserContext::class)->isActive();
|
||||
foreach ($products as $product) {
|
||||
$product->userVipPrice = $product->pricelists[$vipPriceListId]['productPrice'] ?? null;
|
||||
$product->vipOriginalPrice = null;
|
||||
|
||||
// pokud jsem prihlasenej a existuje VIP cena, tak chci nacist "Pompo setri" cenu
|
||||
if ($userIsActive && $product->userVipPrice) {
|
||||
$userVipPrice = $product->userVipPrice;
|
||||
// puvodni cena produktu (neprihlaseny uzivatel)
|
||||
// pokud mam puvodni cenu brat z ceniku
|
||||
if ($originalPriceListId = ($this->configuration->getPriceLists()[$currencyContext->getActiveId()] ?? false)) {
|
||||
$originalPrice = $product->pricelists[$originalPriceListId]['productPrice'] ?? $product->priceOriginal->getObject();
|
||||
$defaultPriceForDiscount = $originalPrice->getPriceWithoutDiscount();
|
||||
if (!empty($product->pricelists[$originalPriceListId]['price_for_discount'])) {
|
||||
$defaultPriceForDiscount = new ProductPrice(
|
||||
toDecimal($product->pricelists[$originalPriceListId]['price_for_discount']),
|
||||
Contexts::get(CurrencyContext::class)->getOrDefault($product->pricelists[$originalPriceListId]['currency']),
|
||||
$product->vat
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$originalPrice = $product->priceOriginal->getObject();
|
||||
$defaultPriceForDiscount = $product->defaultPriceForDiscount ?? $originalPrice->getPriceWithoutDiscount();
|
||||
}
|
||||
|
||||
// prevod cen na stejnou menu
|
||||
PriceCalculator::makeCompatible($userVipPrice, $originalPrice, false);
|
||||
|
||||
// pokud je VIP cena i original cena stejna
|
||||
$vipSavingsEnabled = true;
|
||||
if ($userVipPrice->getPriceWithVat()->equals($originalPrice->getPriceWithVat())) {
|
||||
$originalPrice = $defaultPriceForDiscount;
|
||||
$vipSavingsEnabled = false;
|
||||
}
|
||||
|
||||
// prevod cen na stejnou menu
|
||||
PriceCalculator::makeCompatible($userVipPrice, $originalPrice, false);
|
||||
|
||||
$product->priceForDiscount = $originalPrice;
|
||||
$product->originalPrice = $originalPrice;
|
||||
$product->vipOriginalPrice = $originalPrice;
|
||||
|
||||
// spocitam rozdil - tim zjistim kolik mi setri Pompo klub
|
||||
$diff = $originalPrice->getPriceWithVat()->sub($userVipPrice->getPriceWithVat());
|
||||
$product->vipSavingsPrice = null;
|
||||
// pokud je rozdil vetsi jak 0, tak ho ulozim at ho muzu zobrazit
|
||||
if ($vipSavingsEnabled && $diff->isPositive()) {
|
||||
$product->vipSavingsPrice = new Price($diff, $userVipPrice->getCurrency(), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$products->_pompoVipPricesFetched = true;
|
||||
}
|
||||
|
||||
public function fetchDefaultPriceForDiscount(ProductCollection $products): void
|
||||
{
|
||||
if ($products->count() === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$qb = sqlQueryBuilder()
|
||||
->select('id, COALESCE(price_for_discount, price) as price_for_discount, vat')
|
||||
->from('products')
|
||||
->where(Operator::inIntArray($products->getProductIds(), 'id'));
|
||||
|
||||
$defaultCurrency = Contexts::get(CurrencyContext::class)->getDefault();
|
||||
|
||||
foreach ($qb->execute() as $item) {
|
||||
if (isset($products[$item['id']]) && !empty($item['price_for_discount'])) {
|
||||
$price_for_discount = toDecimal($item['price_for_discount']);
|
||||
$products[$item['id']]->defaultPriceForDiscount = new ProductPrice($price_for_discount, $defaultCurrency, getVat($item['vat']));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Multifetch pro nafetchovani informaci okolo skladu / prodejen, ktere se pouzivaji v katalogu, v kosiku atd...
|
||||
*
|
||||
* Nafetchuje skladovosti rozdelene podle skladu - jde o centralni sklad, sklad aktualne vybrane prodejny a soucet kusu na ostatnich prodejnach.
|
||||
*/
|
||||
public function fetchStoreInfo(ProductCollection $products): void
|
||||
{
|
||||
if (!findModule(\Modules::SELLERS) || $products->count() === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
static $storeIdBySeller = [];
|
||||
|
||||
$sellerContext = Contexts::get(SellerContext::class);
|
||||
|
||||
// nacist ID aktivniho skladu podle aktivni prodejny
|
||||
if (!($storeIdBySeller[$sellerContext->getActiveId()] ?? false)) {
|
||||
$storeId = sqlQueryBuilder()
|
||||
->select('id_store')
|
||||
->from('sellers')
|
||||
->where(Operator::equals(['id' => $sellerContext->getActiveId()]))
|
||||
->execute()->fetchOne();
|
||||
|
||||
if ($storeId) {
|
||||
$storeIdBySeller[$sellerContext->getActiveId()] = (int) $storeId;
|
||||
}
|
||||
}
|
||||
|
||||
$activeStoreId = $storeIdBySeller[$sellerContext->getActiveId()] ?? null;
|
||||
|
||||
// nemusim to fetchovat znova, protoze uz to je fetchnuty
|
||||
if ($products->_pompoStoresFetched[$activeStoreId] ?? false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// multifetchu informace o skaldech
|
||||
$products->fetchStoresInStore();
|
||||
$products->fetchProductOfSuppliersInStore();
|
||||
|
||||
$sellersByStoreId = $this->getSellersByStoreId();
|
||||
|
||||
// k produktum doplnim potrebne informace
|
||||
foreach ($products as $product) {
|
||||
// skladovost na centralnim skladu
|
||||
$product->inStoreMain = (int) ($product->storesInStore[$this->configuration->getMainStoreId()]['in_store'] ?? 0);
|
||||
// skladovost u dodavatele
|
||||
$product->inStoreSupplier = (int) ($product->in_store_suppliers ?? 0);
|
||||
// skladovost na moji prodejne
|
||||
$product->inStoreSeller = (int) ($product->storesInStore[$activeStoreId]['in_store'] ?? 0);
|
||||
// skladovost na ostatnich prodejnach
|
||||
$inStoreSellerOther = 0;
|
||||
foreach ($product->storesInStore ?? [] as $storeId => $item) {
|
||||
// v $sellersByStoreId mam prodejce indexovany podle ID skladu
|
||||
// zaroven tam jsou odfiltrovany prodejny podle aktivniho jazyku - pro cs jen CZ prodejny a pro sk jen SK prodejny
|
||||
// takze na CZ shopu nebudeme zobrazovat skladovost SK prodejen, protoze SK prodejna stejne na CZ shopu nejde vybrat
|
||||
if (!($sellersByStoreId[$storeId] ?? false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$inStoreSellerOther += max(0, $item['in_store']);
|
||||
}
|
||||
$product->inStoreSellerOther = (int) $inStoreSellerOther;
|
||||
}
|
||||
|
||||
if (!isset($products->_pompoStoresFetched)) {
|
||||
$products->_pompoStoresFetched = [];
|
||||
}
|
||||
|
||||
$products->_pompoStoresFetched[$activeStoreId] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vrací, zda je daný produkt skladem u marketplace dodavatele.
|
||||
*/
|
||||
public function isInStoreMarketplace(\Product $product, float $quantity): bool
|
||||
{
|
||||
$marketplaceSuppliers = $this->configuration->getMarketplaceSuppliers();
|
||||
$suppliers = $product->getSuppliers();
|
||||
|
||||
foreach ($marketplaceSuppliers as $marketplaceSupplierId => $_) {
|
||||
if (($suppliers[0][$marketplaceSupplierId] ?? 0) > $quantity) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vrátí datum dopravy navýšený a potřebný počet dnů.
|
||||
*
|
||||
* @deprecated `External\PompoBundle\Overrides\Order\DeliveryInfo::calculateCollectionDate` is used instead
|
||||
*/
|
||||
public function getDeliveryDateIncremented(\DateTime $deliveryDate, array $items, ?\Delivery $delivery = null): \DateTime
|
||||
{
|
||||
return $deliveryDate;
|
||||
}
|
||||
|
||||
public function getDeliveryExpeditionDate(\Delivery $delivery): \DateTime
|
||||
{
|
||||
// Kontroluju kvuli tomu, zda se stihne odeslat jeste dnes nebo ne
|
||||
$expeditionDate = new \DateTime();
|
||||
|
||||
$timeHours = '13:00:00';
|
||||
if (!empty($delivery->time_hours)) {
|
||||
$timeHours = $delivery->time_hours;
|
||||
}
|
||||
|
||||
try {
|
||||
$hourParts = explode(':', $timeHours);
|
||||
$maxHoursToday = (new \DateTime())
|
||||
->setTime((int) ($hourParts[0] ?? 0), (int) ($hourParts[1] ?? 0), (int) ($hourParts[2] ?? 0));
|
||||
|
||||
// Pokud se nestihne odeslat jeste dnes, tak navysim datum jeste o jeden den
|
||||
if ((new \DateTime()) > $maxHoursToday) {
|
||||
$expeditionDate = DateUtil::calcWorkingDays(1, $expeditionDate);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
|
||||
return $expeditionDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vrací date increment - počet dní o kolik se má navýšit datum doručení.
|
||||
*
|
||||
* Date increment se urcuje podle dodavatele, zavrene expedice z centraly nebo podle specialne nastaveneho incrementu pro dany den.
|
||||
*/
|
||||
public function getDateIncrement(array $items, ?array $orderedQuantities = [], ?\DateTime $expeditionDate = null): int
|
||||
{
|
||||
$increment = 0;
|
||||
|
||||
if (empty($items)) {
|
||||
return $increment;
|
||||
}
|
||||
|
||||
$filterItems = [];
|
||||
foreach ($items as $key => $_) {
|
||||
$parts = explode('/', (string) $key);
|
||||
$filterItems[$parts[0]] = $filterItems[$parts[0]] ?? null;
|
||||
if ($parts[1] ?? null) {
|
||||
$filterItems[$parts[0]][] = $parts[1];
|
||||
}
|
||||
}
|
||||
|
||||
// nactu si produkty
|
||||
$products = $this->getProducts($filterItems);
|
||||
// fetchnu si k produktum dodatecne info
|
||||
$this->fetchProductsSupplierDeliveryDateIncrement($products);
|
||||
$this->fetchStoreInfo($products);
|
||||
|
||||
// projdu produkty
|
||||
foreach ($products as $key => $product) {
|
||||
// pokud je to nejhracka, tak nemame prodejny a zajima nas inStore field na produktu, ale pokud
|
||||
// jsme na pompu, tak nas zajima inStoreMain, kde je skladovost na hlavnim skladu bez prodejen
|
||||
$inStoreMain = $this->configuration->isNejhracka() ? $product->inStore : ($product->inStoreMain ?? 0);
|
||||
|
||||
$tmpIncrement = 0;
|
||||
// pocet kusu skladem, ktere objednavam
|
||||
$requiredQuantity = $items[$key] ?? 0;
|
||||
// pocet kusu, ktere jsou objednane v uz vytvorene objednavce
|
||||
$orderedQuantity = $orderedQuantities[$key] ?? 0;
|
||||
// pocet kusu skladem na hlavnim skladu
|
||||
$inStore = $inStoreMain + min($orderedQuantity, $inStoreMain);
|
||||
// pocet kusu skladem u dodavatele
|
||||
$inStoreSuppliers = $product->in_store_suppliers;
|
||||
|
||||
// navysit increment podle skladovosti - pokud neni skladem na hlavnim skladu, ale je skladem u dodavatele, tak dorucuji pozdeji
|
||||
if ($inStore < $requiredQuantity && $inStoreSuppliers > 0) {
|
||||
$tmpIncrement = (int) ($product->supplierDeliveryDateIncrement ?? 0);
|
||||
if ($supplierIncrement = $this->getSupplierOrderIncrement($product->_pompoSupplierId ?? self::SUPPLIER_ID)) {
|
||||
$tmpIncrement += $supplierIncrement;
|
||||
}
|
||||
}
|
||||
|
||||
$increment = max($increment, $tmpIncrement);
|
||||
}
|
||||
|
||||
$expeditionDate ??= new \DateTime();
|
||||
|
||||
// pokud je napr. expedice z centraly v dany den uzavrena, tak musim doruceni navysit
|
||||
if ($mainStoreIncrement = $this->getMainStoreDeliveryDateIncrement($expeditionDate)) {
|
||||
$increment += $mainStoreIncrement;
|
||||
}
|
||||
|
||||
// pokud je v dany den nastaven nejaky dodatecny increment (Nastaveni e-shopu, Pompo a "Navýšení data doručení"
|
||||
if ($dayIncrement = $this->getDeliveryIncrementByDay($expeditionDate)) {
|
||||
$increment += $dayIncrement;
|
||||
}
|
||||
|
||||
return $increment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vrací datumy, kdy je hlavní sklad uzavřen a tímpádem je uzavčena i expedice z něj.
|
||||
*/
|
||||
public function getMainStoreClosedDates(): array
|
||||
{
|
||||
$dbcfg = \Settings::getDefault();
|
||||
|
||||
$from = $dbcfg->datago['mainStoreClosed']['from'] ?? null;
|
||||
$to = $dbcfg->datago['mainStoreClosed']['to'] ?? null;
|
||||
|
||||
if (!empty($from) && !empty($to)) {
|
||||
try {
|
||||
$dateFrom = (new \DateTime($from))->setTime(0, 0);
|
||||
$dateTo = (new \DateTime($to))->setTime(23, 59, 59);
|
||||
|
||||
return [$dateFrom, $dateTo];
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
}
|
||||
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
/**
|
||||
* Vrátí počet dní, o které je potřeba navýšit datum doručení pokud je expedice z centrály uzavřena.
|
||||
* Např. kvůli inventuře, svátku, nebo Vánocům.
|
||||
*/
|
||||
public function getMainStoreDeliveryDateIncrement(\DateTime $expeditionDate): int
|
||||
{
|
||||
$increment = 0;
|
||||
|
||||
[$from, $to] = $this->getMainStoreClosedDates();
|
||||
|
||||
if (!empty($from) && !empty($to)) {
|
||||
try {
|
||||
// Pokud je centralni sklad z nejakyho duvodu uzavren
|
||||
if ($expeditionDate >= $from && $expeditionDate <= $to) {
|
||||
// rozdil mezi datumama
|
||||
$diff = (new \DateTime())->diff($to);
|
||||
// Ensure the increment is at least 1 day
|
||||
$increment = max(1, abs($diff->days));
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
}
|
||||
|
||||
return $increment;
|
||||
}
|
||||
|
||||
public function getDeliveryIncrementByDay(\DateTime $expeditionDate): int
|
||||
{
|
||||
$dbcfg = \Settings::getDefault();
|
||||
|
||||
if ($increment = $dbcfg->pompoSettings['dayDeliveryIncrement'][$expeditionDate->format('N')] ?? 0) {
|
||||
return (int) $increment;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Odecte od incrementu nepracovni dny, aby kdyz se zavola DateUtil::calcWorkingDays nebylo datum o ty nepracovni dny posunuty.
|
||||
*
|
||||
* TODO: Zpusobilo to problemy s datumem doruceni pred svatkama (4.7.2023). Upravovalo se to pred Vanocema, takze tam to taky delalo nejakej problem. Nasimulovat, projit a vyresit!
|
||||
*/
|
||||
private function getDateIncrementWithoutWorkdays(int $increment): int
|
||||
{
|
||||
if ($increment <= 0) {
|
||||
return $increment;
|
||||
}
|
||||
|
||||
foreach (range(1, $increment) as $i) {
|
||||
$date = (new \DateTime())->add(new \DateInterval('P'.$i.'D'));
|
||||
if (!DateUtil::isWorkday($date)) {
|
||||
$increment--;
|
||||
}
|
||||
}
|
||||
|
||||
return $increment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vraci pocet dnu do dalsiho objednavaciho dne od dodavatele.
|
||||
*/
|
||||
private function getSupplierOrderIncrement(int $supplierId = self::SUPPLIER_ID): int
|
||||
{
|
||||
static $supplierIncrement = [];
|
||||
|
||||
if (!($supplierIncrement[$supplierId] ?? false)) {
|
||||
$supplier = sqlQueryBuilder()
|
||||
->select('*')
|
||||
->from('suppliers')
|
||||
->where(Operator::equals(['id' => $supplierId]))
|
||||
->execute()->fetchAssociative();
|
||||
|
||||
$supplier['data'] = json_decode($supplier['data'] ?? '', true) ?? [];
|
||||
|
||||
$today = new \DateTime();
|
||||
|
||||
$orderHours = array_filter($supplier['data']['order_hours'] ?? []);
|
||||
|
||||
$increment = null;
|
||||
// zkontrolovat, zda se bude dneska jeste objednavat od dodavatele
|
||||
if ($todayOrderHour = $orderHours[$today->format('N')] ?? null) {
|
||||
[$hour, $minute] = explode(':', $todayOrderHour);
|
||||
$todayOrderDate = (new \DateTime())->setTime((int) $hour, (int) $minute);
|
||||
// stiham objednat jeste dneska
|
||||
if ($today < $todayOrderDate) {
|
||||
$increment = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if ($increment === null) {
|
||||
// najit nejblizsi den, kdy se delaji objednavky dodavatelovi
|
||||
$increment = $this->recursivelyGetClosestDeliveryDateIncrement($supplier['data']['order_hours'] ?? []);
|
||||
}
|
||||
|
||||
$supplierIncrement[$supplierId] = $increment;
|
||||
}
|
||||
|
||||
return $supplierIncrement[$supplierId];
|
||||
}
|
||||
|
||||
/**
|
||||
* Rekurzivne prochazi objednaci casy nastavene u dodavatele, aby se nasel nejblizsi den, kdy se od dodavatele bude objednavat.
|
||||
*/
|
||||
private function recursivelyGetClosestDeliveryDateIncrement(array $orderHours, int $increment = 0, bool $deep = false): int
|
||||
{
|
||||
$today = new \DateTime();
|
||||
|
||||
$found = false;
|
||||
foreach ($orderHours as $day => $hour) {
|
||||
if (!$deep && $day <= $today->format('N')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if is weekday and hour is empty, skip
|
||||
// weekday should not be added to increment, because we are adding only working days
|
||||
if (in_array($day, [6, 7]) && empty($hour)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$increment++;
|
||||
|
||||
if (!empty($hour)) {
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$found) {
|
||||
if ($deep) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$increment = $this->recursivelyGetClosestDeliveryDateIncrement($orderHours, $increment, true);
|
||||
}
|
||||
|
||||
return $increment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Nafetchuje k produktum increment podle zaznamu v products of suppliers - kazdy produkt muze byt totiz delivery increment jeste povyseny.
|
||||
*/
|
||||
public function fetchProductsSupplierDeliveryDateIncrement(ProductCollection $products): ProductCollection
|
||||
{
|
||||
if ($products->count() === 0) {
|
||||
return $products;
|
||||
}
|
||||
|
||||
if (isset($products->_pompoSupplierDeliveryDateIncrementFetched)) {
|
||||
return $products;
|
||||
}
|
||||
|
||||
$qb = sqlQueryBuilder()
|
||||
->select('pos.id_supplier, pos.id_product, pos.id_variation, MIN(COALESCE(pos.note, COALESCE(IF(JSON_VALUE(data, \'$.delivery_time\')=\'\', 4, JSON_VALUE(data, \'$.delivery_time\')), 4))) as note')
|
||||
->from('products_of_suppliers', 'pos')
|
||||
->join('pos', 'suppliers', 's', 's.id = pos.id_supplier')
|
||||
->andWhere('pos.in_store > 0')
|
||||
->groupBy('pos.id_product, pos.id_variation');
|
||||
|
||||
$specs = [];
|
||||
foreach ($products as $key => $product) {
|
||||
$id = explode('/', (string) $key);
|
||||
$productId = (int) $id[0];
|
||||
$variationId = null;
|
||||
if (!empty($id[1])) {
|
||||
$variationId = (int) $id[1];
|
||||
}
|
||||
|
||||
$specs[] = Operator::equalsNullable(['id_product' => $productId, 'id_variation' => $variationId]);
|
||||
}
|
||||
|
||||
$qb->andWhere(Operator::orX($specs));
|
||||
|
||||
foreach ($qb->execute() as $item) {
|
||||
$key = $item['id_product'];
|
||||
if ($item['id_variation']) {
|
||||
$key .= '/'.$item['id_variation'];
|
||||
}
|
||||
|
||||
if ($products[$key] ?? false) {
|
||||
$products[$key]->_pompoSupplierId = $item['id_supplier'] ?? null;
|
||||
$products[$key]->supplierDeliveryDateIncrement = !empty($item['note']) ? (int) $item['note'] : null;
|
||||
}
|
||||
}
|
||||
|
||||
$products->_pompoSupplierDeliveryDateIncrementFetched = true;
|
||||
|
||||
return $products;
|
||||
}
|
||||
|
||||
/**
|
||||
* Nastaví produktům stitek podle podmínek.
|
||||
*
|
||||
* Bezva cena - DMOC sleva >= 16
|
||||
* Cenový hit - DMOC sleva >= 36
|
||||
*/
|
||||
public function generateProductDiscountLabels(): void
|
||||
{
|
||||
// Bezva cena
|
||||
$labelIdBC = $this->labelUtil->getLabelIdByCode('BC');
|
||||
// Cenový hit
|
||||
$labelIdCH = $this->labelUtil->getLabelIdByCode('CH');
|
||||
|
||||
if (!$labelIdBC || !$labelIdCH) {
|
||||
return;
|
||||
}
|
||||
|
||||
$productList = clone $this->productList;
|
||||
$productList->fetchProductOfSuppliersInStore();
|
||||
$productList->applyDefaultFilterParams();
|
||||
$productList->addResultModifiers(fn (ProductCollection $products) => $this->fetchAdditionalPrices($products));
|
||||
|
||||
$batchSize = 500;
|
||||
|
||||
$updateData = [
|
||||
$labelIdBC => [],
|
||||
$labelIdCH => [],
|
||||
];
|
||||
|
||||
// projdu produkty abych nasel ty, kterym potrebuju nastavit kampan
|
||||
$iterationLimit = $productList->getProductsCount() / $batchSize;
|
||||
for ($i = 0; $i < $iterationLimit; $i++) {
|
||||
$productList->limit($batchSize, $i * $batchSize);
|
||||
|
||||
foreach ($productList->getProducts() as $product) {
|
||||
if (!isset($product->dmocDiscount)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$product->dmocDiscount->isPositive()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$discount = $product->dmocDiscount->asFloat();
|
||||
|
||||
// Cenovy hit
|
||||
if ($discount >= 36) {
|
||||
$updateData[$labelIdCH][] = $product->id;
|
||||
// Bezva cena
|
||||
} elseif ($discount >= 16) {
|
||||
$updateData[$labelIdBC][] = $product->id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($updateData as $labelId => $data) {
|
||||
// smazu vygenerovane stitky od produktu
|
||||
sqlQueryBuilder()
|
||||
->delete('product_labels_relation')
|
||||
->where(Operator::equals(['id_label' => $labelId]))
|
||||
->execute();
|
||||
|
||||
$baseInsertQb = sqlQueryBuilder()
|
||||
->insert('product_labels_relation')
|
||||
->onDuplicateKeyUpdate(['id_label', 'id_product']);
|
||||
|
||||
// nastavim stitek k produktum
|
||||
foreach (array_chunk($data, 500) as $products) {
|
||||
$qb = clone $baseInsertQb;
|
||||
foreach ($products as $productId) {
|
||||
$qb->multiDirectValues(['id_label' => $labelId, 'id_product' => $productId, 'generated' => 1]);
|
||||
}
|
||||
$qb->execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function updateProductsVIPPriceList(): void
|
||||
{
|
||||
$selectQb = sqlQueryBuilder()
|
||||
->select($this->configuration->getVIPPriceListId(), 'p.id', 'ROUND(p.price_buy * 1.05, 4) as vip_price')
|
||||
->from('products', 'p')
|
||||
->where('price_buy IS NOT NULL AND price_buy > 0');
|
||||
|
||||
sqlQuery("INSERT INTO pricelists_products (id_pricelist, id_product, price)
|
||||
{$selectQb->getSQL()}
|
||||
ON DUPLICATE KEY UPDATE price = VALUES(price);");
|
||||
}
|
||||
|
||||
/**
|
||||
* Nastaví produktům flagy podle podmínek.
|
||||
*
|
||||
* Bezva cena - DMOC sleva >= 16
|
||||
* Cenový hit - DMOC sleva >= 36
|
||||
*
|
||||
* @depracated Delete me when labels are fully used instead of campaigns
|
||||
*/
|
||||
public function generateProductDiscountFlags(): void
|
||||
{
|
||||
$campaigns = getCampaigns();
|
||||
if (!isset($campaigns['BC']) || !isset($campaigns['CH'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$productList = clone $this->productList;
|
||||
$productList->fetchProductOfSuppliersInStore();
|
||||
$productList->applyDefaultFilterParams();
|
||||
$productList->addResultModifiers(fn (ProductCollection $products) => $this->fetchAdditionalPrices($products));
|
||||
|
||||
$batchSize = 500;
|
||||
|
||||
$updateData = [
|
||||
'BC' => [],
|
||||
'CH' => [],
|
||||
];
|
||||
|
||||
// projdu produkty abych nasel ty, kterym potrebuju nastavit kampan
|
||||
$iterationLimit = $productList->getProductsCount() / $batchSize;
|
||||
for ($i = 0; $i < $iterationLimit; $i++) {
|
||||
$productList->limit($batchSize, $i * $batchSize);
|
||||
|
||||
foreach ($productList->getProducts() as $product) {
|
||||
if (!isset($product->dmocDiscount)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$product->dmocDiscount->isPositive()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$discount = $product->dmocDiscount->asFloat();
|
||||
|
||||
// Cenovy hit
|
||||
if ($discount >= 36) {
|
||||
$updateData['CH'][] = $product->id;
|
||||
// Bezva cena
|
||||
} elseif ($discount >= 16) {
|
||||
$updateData['BC'][] = $product->id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($updateData as $campaign => $data) {
|
||||
// smazu kampan od produktu
|
||||
sqlQueryBuilder()
|
||||
->update('products')
|
||||
->set('campaign', 'REMOVE_FROM_SET(:campaign, campaign)')
|
||||
->where(Operator::findInSet([$campaign], 'campaign'))
|
||||
->setParameter('campaign', $campaign)
|
||||
->execute();
|
||||
|
||||
// nastavim kampan k produktum
|
||||
foreach (array_chunk($data, 500) as $products) {
|
||||
sqlQueryBuilder()
|
||||
->update('products')
|
||||
->set('campaign', 'ADD_TO_SET(:campaign, campaign)')
|
||||
->where(Operator::inIntArray(array_values($products), 'id'))
|
||||
->setParameter('campaign', $campaign)
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vrací prodejce indexované podle ID skladu.
|
||||
*/
|
||||
private function getSellersByStoreId(): array
|
||||
{
|
||||
return Mapping::mapKeys(
|
||||
Contexts::get(SellerContext::class)->getSupported(),
|
||||
function ($k, $v) {
|
||||
return [$v['id_store'], $v];
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vrací `ProductCollection` podle zadaných ID produktů.
|
||||
*/
|
||||
private function getProducts(array $products): ProductCollection
|
||||
{
|
||||
static $productsCache = [];
|
||||
|
||||
$cacheKey = md5(serialize($products));
|
||||
|
||||
if (!($productsCache[$cacheKey] ?? false)) {
|
||||
$productList = clone $this->productList;
|
||||
$productList->setVariationsAsResult(true);
|
||||
$collection = $productList->andSpec(Product::productsAndVariationsIds($products))
|
||||
->getProducts();
|
||||
|
||||
$collection->fetchProductOfSuppliersInStore();
|
||||
|
||||
$productsCache[$cacheKey] = $collection;
|
||||
}
|
||||
|
||||
return $productsCache[$cacheKey];
|
||||
}
|
||||
}
|
||||
278
bundles/External/PompoBundle/Util/User/PompoUserUtil.php
vendored
Normal file
278
bundles/External/PompoBundle/Util/User/PompoUserUtil.php
vendored
Normal file
@@ -0,0 +1,278 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace External\PompoBundle\Util\User;
|
||||
|
||||
use External\PompoBundle\DRS\Synchronizer\UserSynchronizer;
|
||||
use External\PompoBundle\Email\UserPOSRegistrationEmail;
|
||||
use External\PompoBundle\Util\PompoLogger;
|
||||
use KupShop\AdminBundle\Util\ActivityLog;
|
||||
use KupShop\KupShopBundle\Context\ContextManager;
|
||||
use KupShop\KupShopBundle\Context\DomainContext;
|
||||
use KupShop\KupShopBundle\Context\LanguageContext;
|
||||
use KupShop\KupShopBundle\Util\Contexts;
|
||||
use KupShop\OrderingBundle\Util\Discount\DiscountGenerator;
|
||||
use Query\Operator;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
|
||||
class PompoUserUtil
|
||||
{
|
||||
public function __construct(
|
||||
private UserSynchronizer $userSynchronizer,
|
||||
private PompoLogger $pompoLogger,
|
||||
private DiscountGenerator $discountGenerator,
|
||||
private ContextManager $contextManager,
|
||||
private UserPOSRegistrationEmail $userPOSRegistrationEmail,
|
||||
) {
|
||||
}
|
||||
|
||||
public function sendPreregistrationEmail(int $userId, ?int $drsId = null, ?string $email = null, ?string $country = null): void
|
||||
{
|
||||
$preregistrationEmail = clone $this->userPOSRegistrationEmail;
|
||||
|
||||
$languageMap = [
|
||||
'CZ' => 'cs',
|
||||
'SK' => 'sk',
|
||||
];
|
||||
|
||||
$userData = sqlQueryBuilder()
|
||||
->select('du.id_drs, u.email, u.country')
|
||||
->from('users', 'u')
|
||||
->leftJoin('u', 'drs_users', 'du', 'du.id_user = u.id')
|
||||
->andWhere(Operator::equals(['u.id' => $userId]))
|
||||
->sendToMaster()
|
||||
->execute()->fetchAssociative();
|
||||
|
||||
$drsId ??= $userData['id_drs'];
|
||||
$email ??= $userData['email'];
|
||||
$country ??= $userData['country'] ?? '';
|
||||
|
||||
$userLanguage = $languageMap[$country] ?? 'cs';
|
||||
|
||||
// potrebuju aktivovat spravnou domenu, kdyz budu posilat mail
|
||||
$domainContext = Contexts::get(DomainContext::class);
|
||||
|
||||
$originalDomain = $domainContext->getActiveId();
|
||||
$domainContext->activate(
|
||||
(string) $this->contextManager->getDomainFromLanguage($userLanguage)
|
||||
);
|
||||
|
||||
// aktivuju jazyk podle zeme
|
||||
$this->contextManager->activateContexts(
|
||||
[LanguageContext::class => $userLanguage],
|
||||
function () use ($preregistrationEmail, $email, $drsId) {
|
||||
$message = $preregistrationEmail->getEmail(
|
||||
[
|
||||
'ODKAZ_REGISTRACE' => path('register',
|
||||
[
|
||||
'customer' => base64_encode((string) $drsId),
|
||||
],
|
||||
RouterInterface::ABSOLUTE_URL
|
||||
),
|
||||
]
|
||||
);
|
||||
$message['to'] = $email;
|
||||
// odeslat mail
|
||||
$preregistrationEmail->sendEmail($message);
|
||||
});
|
||||
|
||||
$domainContext->activate($originalDomain);
|
||||
|
||||
addActivityLog(
|
||||
ActivityLog::SEVERITY_NOTICE,
|
||||
ActivityLog::TYPE_SYNC,
|
||||
sprintf('[DRS] Byl odeslán e-mail na dokončení registrace: %s', $email),
|
||||
[
|
||||
'userId' => $userId,
|
||||
'email' => $email,
|
||||
'userLanguage' => $userLanguage,
|
||||
'userCountry' => $country,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Funkce, ktera uzivatelum generuje narozeninove kupony. Kazdy uzivatel muze mit v uctu pridane
|
||||
* svoje deti a pokud nejake dite ma narozeniny, tak se vygeneruje narozeninovy poukaz.
|
||||
*/
|
||||
public function generateBirthdayDiscountCoupons(): void
|
||||
{
|
||||
$currentYear = (new \DateTime())->format('Y');
|
||||
|
||||
// selectuju uzivatele, kteri me zajimaji
|
||||
$qb = sqlQueryBuilder()
|
||||
->select('u.id, uf.id as id_child, uf.data as child_data, u.id_language, u.country, u.delivery_country, DATE_FORMAT(uf.date_birth, "%m-%d") as child_birth')
|
||||
->from('users_family', 'uf')
|
||||
->join('uf', 'users', 'u', 'u.id = uf.id_user')
|
||||
// musi být mladší než 14 let - radši přičítám 15 dní místo 14, ať je jistota, že mi tam náhodou nevleze
|
||||
->andWhere('TIMESTAMPDIFF(YEAR, uf.date_birth, CURDATE() + INTERVAL 15 DAY) < 14')
|
||||
// nesmi mit vygenerovany poukaz
|
||||
->andWhere('JSON_VALUE(uf.data, \'$.coupons.'.$currentYear.'\') IS NULL')
|
||||
// podle data narozeni - poukazy generujeme 14 dni dopredu + pripadne pokud je to min jak 14 dnu, tak ho vygenerujeme taky
|
||||
->andWhere(Operator::inStringArray($this->getDaysParameter(), 'DATE_FORMAT(uf.date_birth, "%m-%d")'))
|
||||
->orderBy('DATE_FORMAT(uf.date_birth, "%m-%d")')
|
||||
->groupBy('uf.id');
|
||||
|
||||
// tohle je kvuli konci roku, kdy mi dny za 14 dni skoci uz do noveho roku
|
||||
$nextYearDays = $this->getNextYearDays();
|
||||
$nextYear = (new \DateTime())->add(new \DateInterval('P1Y'))->format('Y');
|
||||
|
||||
$generated = [];
|
||||
foreach ($qb->execute() as $item) {
|
||||
// rok kterej kontroluju
|
||||
$currentYearForCheck = $currentYear;
|
||||
// Tohle je kvuli konci roku, kdy chci pro lednovy deti generovat poukaz jen jednou - abych ho jednou nevygeneroval v jednom roce
|
||||
// a po druhe zase v novym roce
|
||||
if (in_array($item['child_birth'], $nextYearDays)) {
|
||||
$currentYearForCheck = $nextYear;
|
||||
}
|
||||
|
||||
$childData = json_decode($item['child_data'] ?? '', true) ?? [];
|
||||
// safe check
|
||||
if ($childData['coupons'][$currentYearForCheck] ?? false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// podle zeme uzivatele si nastavim jazyk
|
||||
$language = 'cs';
|
||||
if ($item['id_language'] === 'sk' || $item['country'] === 'SK' || $item['delivery_country'] === 'SK') {
|
||||
$language = 'sk';
|
||||
}
|
||||
|
||||
// id generovaneho kodu, pro ktery budu kupon generovat - rozlisujeme CZ a SK kupony kvuli mene
|
||||
if (!($discountId = $this->getBirthdayDiscountIdByLanguage($language))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// vygeneruju kupon
|
||||
$result = $this->discountGenerator->generateNewCoupons(id_discount: $discountId, userId: (int) $item['id']);
|
||||
$generated[$language] = ($generated[$language] ?? 0) + $result['count'];
|
||||
|
||||
// ulozim si k diteti, ze tenhle rok uz poukaz dostal, takze nebudu generovat dalsi
|
||||
$childData['coupons'][$currentYearForCheck] = reset($result['coupons']);
|
||||
sqlQueryBuilder()
|
||||
->update('users_family')
|
||||
->directValues(
|
||||
[
|
||||
'data' => json_encode($childData),
|
||||
]
|
||||
)
|
||||
->where(Operator::equals(['id' => $item['id_child']]))
|
||||
->execute();
|
||||
}
|
||||
|
||||
// zaloguju zpravu do activitylogu o tom, ze jsem vygeneroval poukazy
|
||||
if (!empty($generated) && max($generated) > 0) {
|
||||
$generatedText = [];
|
||||
foreach ($generated as $lang => $count) {
|
||||
$generatedText[] = $lang.'='.$count;
|
||||
}
|
||||
|
||||
addActivityLog(
|
||||
ActivityLog::SEVERITY_SUCCESS,
|
||||
ActivityLog::TYPE_SYNC,
|
||||
sprintf('Byly vygenerovány narozeninové poukazy: %s', implode(';', $generatedText)),
|
||||
$generated
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pokud se uzivatel registroval na prodejne a pak prisel an e-shop a zvolil, ze se registroval na prodejne
|
||||
* a timpadem paruje uz existujici ucet (kvuli bodum ve vernostim programu) tak ho hned po registraci sesynchronizuju.
|
||||
*/
|
||||
public function synchronizeRegisteredUser(\User $user): void
|
||||
{
|
||||
try {
|
||||
if (!($customerId = ($user->getCustomData()['forceCustomerSynchronization'] ?? false))) {
|
||||
return;
|
||||
}
|
||||
|
||||
sqlGetConnection()->transactional(function () use ($customerId, $user) {
|
||||
// Vytvorim mapping
|
||||
sqlQueryBuilder()
|
||||
->insert('drs_users')
|
||||
->directValues(
|
||||
[
|
||||
'id_drs' => $customerId,
|
||||
'id_user' => $user->id,
|
||||
]
|
||||
)->execute();
|
||||
|
||||
// Sesynchronizuju karty a deti
|
||||
$this->userSynchronizer->updateUser(
|
||||
[
|
||||
'id' => (int) $customerId,
|
||||
'email' => $user->email,
|
||||
],
|
||||
[
|
||||
'cards' => true,
|
||||
'children' => true,
|
||||
]
|
||||
);
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
$this->pompoLogger->logException($e, 'Selhala synchronizace uživatele při registraci přes prodejnu.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pomocná funkce při generování poukazů.
|
||||
* Vrací pole s datumama dnů ode dneška až po den za 14 dní. Ve formátu m-d, protože mě při generování poukazů zajíma jen měsíc a den.
|
||||
*/
|
||||
private function getDaysParameter(): array
|
||||
{
|
||||
$days = [];
|
||||
foreach ($this->getDatePeriod() as $date) {
|
||||
$days[] = $date->format('m-d');
|
||||
}
|
||||
|
||||
return $days;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pomocná funkce, která vrací mezi dneškem a datem za 14 dnů všechny dny, které jsou v dalším roce.
|
||||
* Používá se při generování poukazů abych správně generoval u konce roku.
|
||||
*/
|
||||
private function getNextYearDays(): array
|
||||
{
|
||||
$currentYear = (new \DateTime())->format('Y');
|
||||
|
||||
$days = [];
|
||||
foreach ($this->getDatePeriod() as $date) {
|
||||
if ($date->format('Y') === $currentYear) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$days[] = $date->format('m-d');
|
||||
}
|
||||
|
||||
return $days;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vrací datumy ode dneška až po datum za 14 dní.
|
||||
*/
|
||||
private function getDatePeriod(): \DatePeriod
|
||||
{
|
||||
return new \DatePeriod(
|
||||
new \DateTime(),
|
||||
new \DateInterval('P1D'),
|
||||
(new \DateTime())->add(new \DateInterval('P14D'))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vrací ID generovaného poukazu podle jazyka.
|
||||
*/
|
||||
private function getBirthdayDiscountIdByLanguage(string $language): ?int
|
||||
{
|
||||
$map = [
|
||||
'cs' => 159,
|
||||
'sk' => 162,
|
||||
];
|
||||
|
||||
return $map[$language] ?? null;
|
||||
}
|
||||
}
|
||||
83
bundles/External/PompoBundle/Util/User/UserCardUtil.php
vendored
Normal file
83
bundles/External/PompoBundle/Util/User/UserCardUtil.php
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace External\PompoBundle\Util\User;
|
||||
|
||||
use Query\Operator;
|
||||
|
||||
class UserCardUtil
|
||||
{
|
||||
/** Délka kódu karty, kterou generujeme */
|
||||
private const CARD_LENGTH = 9;
|
||||
|
||||
/**
|
||||
* Vrací všechny věrnostní kartičky daného uživatele.
|
||||
*/
|
||||
public function getUserCards(int $userId): array
|
||||
{
|
||||
$cards = [];
|
||||
|
||||
$qb = sqlQueryBuilder()
|
||||
->select('uc.*')
|
||||
->from('user_cards', 'uc')
|
||||
->where(Operator::equals(['id_user' => $userId]));
|
||||
|
||||
foreach ($qb->execute() as $item) {
|
||||
$item['data'] = json_decode($item['data'] ?? '', true) ?? [];
|
||||
$cards[$item['id']] = $item;
|
||||
}
|
||||
|
||||
return $cards;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vygeneruje novou věrnostní kartu k danému uživateli.
|
||||
*/
|
||||
public function generateUserCard(int $userId): void
|
||||
{
|
||||
sqlQueryBuilder()
|
||||
->insert('user_cards')
|
||||
->directValues(
|
||||
[
|
||||
'id_user' => $userId,
|
||||
'code' => $this->generateCardCode(),
|
||||
]
|
||||
)->execute();
|
||||
}
|
||||
|
||||
public function generateCardCode(): string
|
||||
{
|
||||
$codeAlphabet = 'ABCDEFGHJKLMNPRSTUVWX';
|
||||
$codeAlphabet .= '23456789';
|
||||
$max = strlen($codeAlphabet);
|
||||
|
||||
$token = '';
|
||||
for ($i = 0; $i < self::CARD_LENGTH; $i++) {
|
||||
$token .= $codeAlphabet[$this->cryptoRandSecure($max - 1)];
|
||||
}
|
||||
|
||||
return implode('-', str_split($token, 3));
|
||||
}
|
||||
|
||||
private function cryptoRandSecure(int $max): int
|
||||
{
|
||||
$min = 0;
|
||||
|
||||
$range = $max - $min;
|
||||
if ($range < 1) {
|
||||
return $min;
|
||||
}
|
||||
|
||||
$log = ceil(log($range, 2));
|
||||
$bytes = (int) ($log / 8) + 1; // length in bytes
|
||||
$bits = (int) $log + 1; // length in bits
|
||||
$filter = (int) (1 << $bits) - 1; // set all lower bits to 1
|
||||
do {
|
||||
$rnd = hexdec(bin2hex(openssl_random_pseudo_bytes($bytes)));
|
||||
$rnd = $rnd & $filter; // discard irrelevant bits
|
||||
} while ($rnd > $range);
|
||||
|
||||
return $min + $rnd;
|
||||
}
|
||||
}
|
||||
26
bundles/External/PompoBundle/Util/Watchdog/PompoWatchdog.php
vendored
Normal file
26
bundles/External/PompoBundle/Util/Watchdog/PompoWatchdog.php
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace External\PompoBundle\Util\Watchdog;
|
||||
|
||||
use External\PompoBundle\DataGo\Synchronizer\StockSynchronizer;
|
||||
use KupShop\WatchdogBundle\Util\Watchdog;
|
||||
use Query\QueryBuilder;
|
||||
|
||||
class PompoWatchdog extends Watchdog
|
||||
{
|
||||
/**
|
||||
* Předefinovávám `getInStoreField`, protože chci vracet skladovost pouze pro centrální sklad. Pokud je produkt skladem třeba
|
||||
* jen na jedný prodejně, tak to zákazníka nemusí zajímat, protože ta prodejna může být na druhý straně republiky.
|
||||
*/
|
||||
public function getInStoreField(QueryBuilder $qb, string $productAlias = 'p', string $variationAlias = 'pv'): string
|
||||
{
|
||||
// Najoinuju si skladovost na centralnim skladu
|
||||
$qb->leftJoin($productAlias, 'stores_items', 'si_wd', "si_wd.id_product = {$productAlias}.id AND {$variationAlias}.id IS NULL AND si_wd.id_store = :wdStoreId")
|
||||
->setParameter('wdStoreId', StockSynchronizer::STORE_ID);
|
||||
|
||||
// Vracim skladovost na centralnim skladu
|
||||
return 'COALESCE(si_wd.quantity, 0)';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user