1702 lines
63 KiB
PHP
1702 lines
63 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace External\ZNZBundle\Synchronizers;
|
|
|
|
use External\ZNZBundle\Exception\ZNZException;
|
|
use External\ZNZBundle\Util\Order\ZNZOrderUtil;
|
|
use External\ZNZBundle\Util\ZNZApi;
|
|
use KupShop\I18nBundle\Entity\Currency;
|
|
use KupShop\KupShopBundle\Context\ContextManager;
|
|
use KupShop\KupShopBundle\Context\CountryContext;
|
|
use KupShop\KupShopBundle\Context\CurrencyContext;
|
|
use KupShop\KupShopBundle\Context\LanguageContext;
|
|
use KupShop\KupShopBundle\Util\Contexts;
|
|
use KupShop\KupShopBundle\Util\Database\QueryHint;
|
|
use KupShop\KupShopBundle\Util\Functional\Mapping;
|
|
use KupShop\KupShopBundle\Util\Price\Price;
|
|
use KupShop\KupShopBundle\Util\Price\PriceCalculator;
|
|
use KupShop\OrderingBundle\Entity\Order\OrderItem;
|
|
use KupShop\OrderingBundle\OrderList\OrderList;
|
|
use KupShop\OrderingBundle\Util\Order\OrderInfo;
|
|
use KupShop\OrderingBundle\Util\Order\OrderItemInfo;
|
|
use KupShop\StoresBundle\Utils\StoresInStore;
|
|
use Query\Operator;
|
|
use Query\Order;
|
|
use Query\QueryBuilder;
|
|
use Symfony\Component\Routing\Router;
|
|
use Symfony\Contracts\Service\Attribute\Required;
|
|
|
|
class OrderSynchronizer extends BaseSynchronizer implements SynchronizerOutInterface
|
|
{
|
|
protected static string $type = 'order';
|
|
|
|
#[Required]
|
|
public ZNZApi $znzApi;
|
|
|
|
#[Required]
|
|
public OrderItemInfo $orderItemInfo;
|
|
|
|
#[Required]
|
|
public StoresInStore $storesInStore;
|
|
|
|
#[Required]
|
|
public OrderList $orderList;
|
|
|
|
#[Required]
|
|
public ZNZOrderUtil $znzOrderUtil;
|
|
|
|
#[Required]
|
|
public ContextManager $contextManager;
|
|
|
|
public static function getPriority(): int
|
|
{
|
|
return 90;
|
|
}
|
|
|
|
public static function getHandledTables(): array
|
|
{
|
|
return [
|
|
'Objednavky' => 'processOrder',
|
|
'ObjednavkyZmenyStavu' => 'processOrderStatusChange',
|
|
'Posta' => 'processOrderTracking',
|
|
'Doklady' => 'processOrderDocument',
|
|
];
|
|
}
|
|
|
|
public function useTestConnection(): bool
|
|
{
|
|
return !$this->configuration->useProductionHelios();
|
|
}
|
|
|
|
public function processToHelios(): void
|
|
{
|
|
$this->processOrdersToHelios();
|
|
$this->processPaidOrdersToHelios();
|
|
}
|
|
|
|
/**
|
|
* Funkce, která zaktualizuje objednávky ve stavu chyba do stavu přijatá, aby se zkusily znovu odeslat do Heliosu.
|
|
* Spoouští se každou hodinu.
|
|
*/
|
|
public function updateOrdersInErrorState(): void
|
|
{
|
|
$orderList = $this->getOrderList();
|
|
$orderList->andSpec(Operator::andX(
|
|
// neni v heliosu
|
|
Operator::equalsNullable(['zo.id_znz' => null]),
|
|
// a je ve stavu chyba
|
|
Operator::equals(['o.status' => $this->configuration->getOrderErrorStatus()])
|
|
));
|
|
|
|
foreach ($orderList->getOrders() as $order) {
|
|
// nastavim znzTry na 1, aby se resetoval pocet pokusu
|
|
$order->setData('znzTry', 1);
|
|
// zmenim stav na 0, aby se objednavka zkusila znovu zapsat
|
|
$order->changeStatus(
|
|
0,
|
|
'[Helios] Objednávka byla automaticky přepnuta ze stavu "chyba" do stavu "přijatá", aby se zkusila znovu zapsat do Heliosu',
|
|
false
|
|
);
|
|
}
|
|
}
|
|
|
|
public function getOrderData(\Order $order): array
|
|
{
|
|
$deliveryTypeInfo = $this->getOrderDeliveryTypeInfo($order);
|
|
|
|
$customerId = null;
|
|
$customerEmail = $order->invoice_email;
|
|
if ($order->id_user) {
|
|
$customerId = QueryHint::withRouteToMaster(fn () => $this->znzUtil->getZNZId(UserSynchronizer::getType(), $order->id_user));
|
|
if ($userEmail = $order->getUser()?->email) {
|
|
$customerEmail = $userEmail;
|
|
}
|
|
}
|
|
|
|
$orderLanguage = Contexts::get(LanguageContext::class)->getAll()[$order->getLanguage()] ?? null;
|
|
|
|
$dropshipmentId = null;
|
|
if ($order->dropshipmentInfo['dropshipment']['id'] ?? false) {
|
|
$dropshipmentId = $order->dropshipmentInfo['dropshipment']['id'];
|
|
}
|
|
|
|
$orderData = [
|
|
'entity_id' => (int) $order->id,
|
|
'increment_id' => $this->getOrderNumber($order),
|
|
'created_at' => $order->date_created->format('Y-m-d\TH:i:s'),
|
|
'ZabalitBezFaktury' => isset($order->getFlags()['OEI']),
|
|
'IdLanguage' => $orderLanguage?->getLocale() ?? 'cs_CZ',
|
|
'WPJ_dropshipID' => $dropshipmentId,
|
|
'WPJ_zdroj' => $order->source,
|
|
'url' => $this->getOrderUrl($order),
|
|
'store_id' => $this->getOrderStoreId($order),
|
|
'customer_is_guest' => $customerId ? 0 : 1,
|
|
'customer_email' => $customerEmail,
|
|
'customer_id' => $customerId,
|
|
'order_id_customer' => $this->getOrderCustomerNumber($order),
|
|
'order_currency_code' => $order->getCurrency(),
|
|
'shipping_method' => $this->getOrderShippingMethod($order),
|
|
'payment_method' => $this->getOrderPaymentMethod($order),
|
|
'shipping_incl_tax' => $deliveryTypeInfo['deliveryPrice'],
|
|
'shipping_description' => $order->delivery_type,
|
|
'transaction_fee' => $deliveryTypeInfo['paymentPrice'],
|
|
'total_due' => $this->getOrderTotalPrice($order, $deliveryTypeInfo),
|
|
'id_zakaznik' => null,
|
|
'id_dodaci' => null,
|
|
'customer_note' => '',
|
|
'note_public' => $order->note_user,
|
|
// 'rada' => '',
|
|
'address' => $this->getOrderAddresses($order),
|
|
'item' => [],
|
|
];
|
|
|
|
/** @var OrderItem $item */
|
|
foreach ($order->fetchItems() as $item) {
|
|
if ($this->orderItemInfo->getItemType($item) === OrderItemInfo::TYPE_DELIVERY) {
|
|
continue;
|
|
}
|
|
|
|
$itemData = $this->getNonProductItemData($order, $item);
|
|
|
|
$setData = $item->getNote()['setData'] ?? [];
|
|
$isSet = !empty($setData);
|
|
$sets = $isSet ? $this->getProductSets($item->getProduct()) : [];
|
|
|
|
$orderData['item'][] = [
|
|
'item' => [
|
|
'order_id' => (int) $order->id,
|
|
'item_id' => $item->getId(),
|
|
'parent_id' => null,
|
|
'product_type' => $isSet ? 'bundle' : 'simple',
|
|
'sku' => $item->getCode() ?: $itemData['code'] ?? null,
|
|
'name' => $item->getDescr(),
|
|
'qty_ordered' => $item->getPieces(),
|
|
'price' => $isSet ? 0 : $this->getOrderItemPrice($item, false),
|
|
'tax_percent' => $item->getVat(),
|
|
'price_incl_tax' => $isSet ? 0 : $this->getOrderItemPrice($item),
|
|
'source_code' => $this->getOrderItemStoreId($order, $item) ?: $itemData['sourceCode'] ?? null,
|
|
'price_debug' => $this->getOrderItemPriceDebug($order, $item),
|
|
],
|
|
];
|
|
|
|
$setProductTotalPrice = \DecimalConstants::zero();
|
|
// pokud se jedna o set, tak zapisu i polozky setu, ktere maji pomerove rozpadlou cenu
|
|
foreach ($setData as $setProductId => $setItem) {
|
|
$piecePrice = toDecimal(toDecimal($setItem['piecePrice'])->printFloatValue(-2));
|
|
$setProduct = $sets[$setProductId] ?? null;
|
|
|
|
$setProductTotalPrice = $setProductTotalPrice->add($piecePrice);
|
|
|
|
// pokud je to posledni polozka
|
|
if (array_key_last($setData) === $setProductId) {
|
|
// pokud tam vznikl nejaky rozdil kvuli roundingu, tak ho posledni polozkou opravim
|
|
$diff = $item->getPiecePrice()->getPriceWithVat()->sub($setProductTotalPrice);
|
|
if (!$diff->isZero()) {
|
|
$piecePrice = $piecePrice->add($diff);
|
|
}
|
|
}
|
|
|
|
$code = $setProduct ? $setProduct->code : null;
|
|
if ($setProduct instanceof \Variation && !empty($setProduct->variationCode)) {
|
|
$code = $setProduct->variationCode;
|
|
}
|
|
|
|
$orderData['item'][] = [
|
|
'item' => [
|
|
'order_id' => (int) $order->id,
|
|
'item_id' => null,
|
|
'parent_id' => $item->getId(),
|
|
'product_type' => 'simple',
|
|
'sku' => $code,
|
|
'name' => $setProduct ? $setProduct->title : $item->getDescr(),
|
|
'qty_ordered' => $setItem['pieces'],
|
|
'price' => $piecePrice->removeVat($item->getVat())->asFloat(),
|
|
'tax_percent' => $item->getVat(),
|
|
'price_incl_tax' => (float) $piecePrice->printFloatValue(-2),
|
|
'source_code' => $this->getOrderItemStoreId($order, $item),
|
|
],
|
|
];
|
|
}
|
|
}
|
|
|
|
return $orderData;
|
|
}
|
|
|
|
protected function processOrdersToHelios(): void
|
|
{
|
|
if (isLocalDevelopment()) {
|
|
return;
|
|
}
|
|
|
|
$orderList = $this->getOrderList();
|
|
$orderList->fetchDropshipmentInfo();
|
|
$orderList->andSpec(function () {
|
|
return Operator::andX(
|
|
// objednavka jeste neni nahrana do Heliosu
|
|
Operator::equalsNullable(['zo.id_znz' => null]),
|
|
// objednavka neni stornovana
|
|
Operator::equals(['o.status_storno' => 0]),
|
|
// objednavka nesmi byt ve stavu chyba
|
|
Operator::not(
|
|
Operator::equals(['o.status' => $this->configuration->getOrderErrorStatus()])
|
|
)
|
|
);
|
|
});
|
|
|
|
// use withRouteToMaster to avoid `Duplicate entry` errors for znz_orders table
|
|
$orders = QueryHint::withRouteToMaster(fn () => $orderList->getOrders());
|
|
foreach ($orders as $order) {
|
|
// pocet pokusu, kolikrat se objednavka zkusila zapsat do Heliosu
|
|
if (!($try = $order->getData('znzTry'))) {
|
|
$try = 1;
|
|
}
|
|
|
|
try {
|
|
$znzId = $this->createOrderToHelios($order);
|
|
} catch (\Throwable $e) {
|
|
$this->logger->log($e, data: ['try' => $try]);
|
|
|
|
if ($e instanceof ZNZException) {
|
|
$order->setData('znzTry', ++$try);
|
|
if ($try > 9) {
|
|
$order->changeStatus(
|
|
$this->configuration->getOrderErrorStatus(),
|
|
sprintf('[Helios] Objednávku se nepodařilo zapsat do Heliosu. Chyba: %s', $e->getMessage()),
|
|
false
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public function getOrderPaymentData(\Order $order): array
|
|
{
|
|
$payment = $order->getDeliveryType()->getPayment();
|
|
$payments = $order->getPaymentsArray();
|
|
if (empty($payments)) {
|
|
return [];
|
|
}
|
|
|
|
$paymentsData = [];
|
|
$transactionsData = [];
|
|
|
|
foreach ($payments as $paymentInfo) {
|
|
$paymentData = json_decode($paymentInfo['payment_data'] ?: '', true) ?: [];
|
|
|
|
// add payment data
|
|
$paymentsData[] = [
|
|
'item' => [
|
|
'entity_id' => $paymentInfo['id'],
|
|
'method' => $payment->getCustomData()['id_znz'] ?? $payment->getName(),
|
|
],
|
|
];
|
|
|
|
// pokud je platba dokoncena, tak posilame is_closed=1, jinak je is_closed=0
|
|
$isClosed = $paymentInfo['status'] === \Payment::STATUS_FINISHED ? 1 : 0;
|
|
|
|
// add transaction data
|
|
$transactionsData[] = [
|
|
'item' => [
|
|
'transaction_id' => $paymentInfo['id'],
|
|
'payment_id' => $paymentInfo['id'],
|
|
'txn_id' => $paymentData['transactionID'] ?? $paymentData['session'] ?? $paymentInfo['id'],
|
|
'created_at' => $paymentInfo['date']->format('Y-m-d\TH:i:s'),
|
|
'is_closed' => (string) $isClosed,
|
|
'additional_information' => json_encode([
|
|
'raw_details_info' => [
|
|
'sum' => (float) $paymentInfo['price'],
|
|
'fee' => 0,
|
|
'currency' => $order->getCurrency(),
|
|
'transaction_date' => $paymentInfo['date']->format('Y-m-d\TH:i:s'),
|
|
],
|
|
]),
|
|
],
|
|
];
|
|
}
|
|
|
|
return [
|
|
'GUIDObjednavka' => $this->znzUtil->getZNZId(static::getType(), $order->id),
|
|
'entity_id' => (int) $order->id,
|
|
'increment_id' => $this->getOrderNumber($order),
|
|
'store_id' => $this->getOrderStoreId($order),
|
|
'customer_id' => $order->id_user ? (int) $order->id_user : null,
|
|
'customer_note' => $order->note_user,
|
|
'payment' => $paymentsData,
|
|
'transaction' => $transactionsData,
|
|
];
|
|
}
|
|
|
|
public function createOrderToHelios(\Order $order): string
|
|
{
|
|
$znzId = $this->getZNZApi()->createOrder(
|
|
$this->getOrderData($order)
|
|
);
|
|
|
|
// ulozim si mapping objednavky
|
|
$this->znzUtil->createMapping(static::getType(), $znzId, (int) $order->id);
|
|
|
|
// zmenim stav objednavky na "potvrzena"
|
|
$order->changeStatus(1, sprintf('[Helios] Objednávka byla úspěšně nahrána do Heliosu; ID: %s;', $this->getZNZIdInHex($znzId)), false);
|
|
|
|
return $znzId;
|
|
}
|
|
|
|
protected function processPaidOrdersToHelios(): void
|
|
{
|
|
if (isLocalDevelopment()) {
|
|
return;
|
|
}
|
|
|
|
$orderList = $this->getOrderList();
|
|
$orderList->andSpec(function () {
|
|
return Operator::andX(
|
|
// pouze platba prevodem nebo platba online
|
|
Order::byPaymentMethods([\Payment::METHOD_TRANSFER, \Payment::METHOD_ONLINE]),
|
|
// objednavka neni vyrizena
|
|
Operator::inIntArray(getStatuses('nothandled'), 'o.status'),
|
|
// objednavka je zapsana v Heliosu
|
|
'zo.id_znz IS NOT NULL',
|
|
// objednavka neni stornovana
|
|
Operator::equals(['o.status_storno' => 0]),
|
|
// objednavka je zaplacena
|
|
Operator::equals(['o.status_payed' => 1]),
|
|
// objednavka jeste neni zaplacena v Heliosu
|
|
'JSON_VALUE(zo.data, "$.paidSent") IS NULL'
|
|
);
|
|
});
|
|
|
|
foreach ($orderList->getOrders() as $order) {
|
|
try {
|
|
$paymentData = $this->getOrderPaymentData($order);
|
|
|
|
// pokud nemam payment data tak je to asi importovana objednavka z Heliosu (nemam payments, ale status_payed=1)
|
|
if (empty($paymentData)) {
|
|
$this->setOrderMappingData($order->id, ['paidSent' => true]);
|
|
continue;
|
|
}
|
|
|
|
$result = $this->getZNZApi()->updateOrderPaymentStatus($paymentData);
|
|
|
|
if ($result) {
|
|
$this->setOrderMappingData($order->id, ['paidSent' => true]);
|
|
$order->logHistory('[Helios] Byla odeslána informace o zaplacení objednávky do Heliosu');
|
|
}
|
|
} catch (\Throwable $e) {
|
|
$this->logger->log($e, 'Nepodařilo se aktualizovat stav zaplacení u objednávky: '.$order->order_no, [
|
|
'orderId' => $order->id,
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected function processOrder(array $item): void
|
|
{
|
|
if (!($order = $this->getOrderByItem($item))) {
|
|
$this->createOrderFromHelios($item);
|
|
|
|
return;
|
|
}
|
|
|
|
$orderId = $order->id;
|
|
$activityLogErrors = [];
|
|
|
|
// opravit jazyk pokud je na shopu spatne u historickych objednavek spatne
|
|
if ($order->source === OrderInfo::ORDER_SOURCE_IMPORT) {
|
|
$language = $this->getOrderLanguageByWebsite($item);
|
|
if ($order->id_language != $language) {
|
|
sqlQueryBuilder()
|
|
->update('orders')
|
|
->directValues(['id_language' => $language])
|
|
->where(Operator::equals(['id' => $orderId]))
|
|
->execute();
|
|
}
|
|
}
|
|
|
|
// aktualizace zpusobu doruceni
|
|
$this->updateOrderDeliveryTypeByHelios($order, $item, $activityLogErrors);
|
|
|
|
$update = [];
|
|
$updateLog = [];
|
|
|
|
$addUpdateLog = function (string $message, ?OrderItem $item = null) use (&$updateLog) {
|
|
if ($item) {
|
|
$message = '<strong>Položka '.$item->getId().':</strong> '.$message;
|
|
}
|
|
|
|
$updateLog[] = $message;
|
|
};
|
|
|
|
// detekce zmeny mailu
|
|
if (!empty($item['email']) && $item['email'] !== $order->invoice_email) {
|
|
$update['invoice_email'] = $item['email'];
|
|
$addUpdateLog(sprintf('<strong>Aktualizace e-mailu:</strong> %s -> %s', $order->invoice_email, $item['email']));
|
|
}
|
|
|
|
// detekce zmen v adrese
|
|
foreach ($item['Adresy'] ?? [] as $heliosAddress) {
|
|
$userName = $this->znzUtil->getUserNameParts($heliosAddress['Nazev']);
|
|
|
|
// pokud je tam jen predcisli, tak to rovnou vyprazdnim
|
|
if (preg_match('/^\+(\d{1,3})$/', $heliosAddress['Telefon'] ?? '')) {
|
|
$heliosAddress['Telefon'] = '';
|
|
}
|
|
|
|
$prefix = 'invoice_';
|
|
if ($heliosAddress['TypAdresy'] !== 'billing') {
|
|
$prefix = 'delivery_';
|
|
}
|
|
|
|
$heliosAddressParts = [
|
|
$prefix.'name' => $userName['name'],
|
|
$prefix.'surname' => $userName['surname'],
|
|
$prefix.'phone' => $heliosAddress['Telefon'] ?? '',
|
|
$prefix.'firm' => $heliosAddress['DruhyNazev'],
|
|
$prefix.'city' => $heliosAddress['Misto'],
|
|
$prefix.'zip' => $heliosAddress['PSC'],
|
|
$prefix.'street' => $heliosAddress['Ulice'],
|
|
$prefix.'country' => $this->znzUtil->getCountryCodeByHelios($heliosAddress['IdZeme'] ?? Contexts::get(CountryContext::class)->getDefaultId()),
|
|
];
|
|
|
|
$shopAddressParts = [
|
|
$prefix.'name' => $order->{$prefix.'name'},
|
|
$prefix.'surname' => $order->{$prefix.'surname'},
|
|
$prefix.'phone' => $order->{$prefix.'phone'},
|
|
$prefix.'firm' => $order->{$prefix.'firm'},
|
|
$prefix.'city' => $order->{$prefix.'city'},
|
|
$prefix.'zip' => $order->{$prefix.'zip'},
|
|
$prefix.'street' => $order->{$prefix.'street'},
|
|
$prefix.'country' => $this->znzUtil->getCountryCodeByHelios($order->{$prefix.'country'}),
|
|
];
|
|
|
|
if (array_diff($shopAddressParts, $heliosAddressParts)) {
|
|
$update = array_merge($update, $heliosAddressParts);
|
|
|
|
$addUpdateLog(
|
|
sprintf('<strong>Aktualizace adresy \'%s\':</strong> %s -> %s', $heliosAddress['TypAdresy'], implode(';', $shopAddressParts), implode(';', $heliosAddressParts))
|
|
);
|
|
}
|
|
}
|
|
|
|
// aktualizace polozek objednavky
|
|
if ($this->updateOrderItems($order, $item)) {
|
|
$addUpdateLog(
|
|
'Provedena aktualizace položek objednávky'
|
|
);
|
|
}
|
|
|
|
if (!empty($update)) {
|
|
sqlQueryBuilder()
|
|
->update('orders')
|
|
->directValues($update)
|
|
->where(Operator::equals(['id' => $orderId]))
|
|
->execute();
|
|
}
|
|
|
|
// GUID objednavky, ktera byla do tehle aktualni objednavky sloucena
|
|
if (!empty($item['GUIDObjednvkaSlouceno'])) {
|
|
if ($oldOrderId = $this->znzUtil->getMapping(static::getType(), $item['GUIDObjednvkaSlouceno'])) {
|
|
// pokud jsme mergnuti plateb jeste neprovedli, tak ho provedeme. Ale nechceme ho provadet furt, tak to takhle ifneme
|
|
if (!$this->getOrderMappingData($orderId, 'mergedWithOrder')) {
|
|
$paymentIds = array_map(fn ($x) => $x['id'], sqlQueryBuilder()
|
|
->select('id')
|
|
->from('order_payments')
|
|
->where(Operator::equals(['id_order' => $oldOrderId]))
|
|
->execute()->fetchAllAssociative());
|
|
|
|
// pokud jsou u puvodni objednavky nejake platby
|
|
if (!empty($paymentIds)) {
|
|
// prenesu platbu od puvodni objednavky k aktualni objednavce
|
|
sqlQueryBuilder()
|
|
->update('order_payments')
|
|
->directValues(['id_order' => $orderId])
|
|
->where(Operator::equals(['id_order' => $oldOrderId]))
|
|
->execute();
|
|
|
|
$addUpdateLog('<strong>Platba:</strong> K této objednávce byla přenesena platba od objednávky <a href="javascript:nw(\'orders\', '.$oldOrderId.')">'.$oldOrderId.'</a>');
|
|
|
|
$order->updatePayments();
|
|
}
|
|
|
|
$this->setOrderMappingData($orderId, ['mergedWithOrder' => $oldOrderId]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// detekce zaplaceni / odznaceni zaplaceni
|
|
QueryHint::withRouteToMaster(fn () => $this->updateOrderPayments($order, $item));
|
|
|
|
if (!empty($updateLog)) {
|
|
$order->logHistory(
|
|
implode('<br>', array_merge(
|
|
['<strong>[Helios] Provedeny změny v objednávce</strong>'],
|
|
$updateLog
|
|
))
|
|
);
|
|
}
|
|
|
|
if (!empty($activityLogErrors)) {
|
|
$this->logger->activity(
|
|
sprintf('Během aktualizace objednávky "%s" z Heliosu se vyskytly chyby: %s', $order->order_no, count($activityLogErrors)),
|
|
[
|
|
'orderId' => $orderId,
|
|
'errors' => $activityLogErrors,
|
|
]
|
|
);
|
|
}
|
|
}
|
|
|
|
protected function processOrderStatusChange(array $item): void
|
|
{
|
|
if (!($order = $this->getOrderByItem($item))) {
|
|
return;
|
|
}
|
|
|
|
$maxStatusId = $order->getData('znzStatusMaxId');
|
|
// starsi zmena, ktera nas uz nezajima, protoze mame novejsi
|
|
if ($maxStatusId && $maxStatusId >= $item['meta']['id_change']) {
|
|
return;
|
|
}
|
|
|
|
$order->setData('znzStatusMaxId', $item['meta']['id_change']);
|
|
|
|
$heliosStatus = $item['StavNovy3'] ?? null;
|
|
if ($heliosStatus === null) {
|
|
return;
|
|
}
|
|
|
|
// storno objednavky je pod stavem s ID 99
|
|
if ($heliosStatus === 99) {
|
|
if ($order->status_storno == 0) {
|
|
$order->storno(false);
|
|
$order->logHistory('[Helios] Objednávka byla stornována');
|
|
}
|
|
|
|
// pokud nema nastaveny i stav "stornovana", tak ho nastavim
|
|
if ($order->status != $this->configuration->getOrderStornoStatus()) {
|
|
$order->changeStatus(
|
|
$this->configuration->getOrderStornoStatus(),
|
|
forceSendMail: false
|
|
);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// ostatni ID stavu by mely odpovidat nasemu ID stavu (config.php)
|
|
// pokud je to teda neznamy stav, tak nic neudelam... aspon zaloguju chybu do activity logu
|
|
if ((getOrderStatuses()[$heliosStatus] ?? false) === false) {
|
|
throw new ZNZException(
|
|
'Unable to update order status: Unknown status ID "'.$heliosStatus.'"!',
|
|
$item
|
|
);
|
|
}
|
|
|
|
// pokud je stav jiny, nez mame na e-shopu, tak provedeme zmenu stavu
|
|
if ($order->status != $heliosStatus) {
|
|
$order->changeStatus($heliosStatus, sprintf('[Helios] Změna stavu objednavky: %s -> %s', $order->status, $heliosStatus), false);
|
|
}
|
|
}
|
|
|
|
protected function processOrderTracking(array $item): void
|
|
{
|
|
if (!($order = $this->getOrderByItem($item))) {
|
|
return;
|
|
}
|
|
|
|
// package ids that are already set on order object
|
|
$packages = explode(',', $order->package_id ?: '');
|
|
// package id from Helios
|
|
$packageId = $item['Znacka'] ?? null;
|
|
$isStorno = ($item['JeStorno'] ?? 0) == 1;
|
|
|
|
// nothing to save
|
|
if (empty($packageId)) {
|
|
return;
|
|
}
|
|
|
|
// already have the package id saved, so i dont need to save it again
|
|
if (($key = array_search($packageId, $packages)) !== false) {
|
|
// package id storno
|
|
if ($isStorno) {
|
|
// remove package id from packages
|
|
unset($packages[$key]);
|
|
|
|
// remove tracking URL
|
|
$trackingUrls = $order->getData('znzTrackingUrls') ?: [];
|
|
unset($trackingUrls[$packageId]);
|
|
$order->setData('znzTrackingUrls', $trackingUrls);
|
|
|
|
// update package ids
|
|
$order->setPackageId(implode(',', array_filter($packages)));
|
|
|
|
$order->logHistory('[Helios] Storno čísla balíku: '.$packageId);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// package id storno, nothing to do
|
|
if ($isStorno) {
|
|
return;
|
|
}
|
|
|
|
$packages[] = $packageId;
|
|
if (!empty($item['TrackURL'])) {
|
|
$order->setData('znzTrackingUrl', $item['TrackURL']);
|
|
// set package id to znzTrackingUrls array (znzTrackingUrls are not used)
|
|
$trackingUrls = $order->getData('znzTrackingUrls') ?: [];
|
|
$trackingUrls[$packageId] = $item['TrackURL'];
|
|
$order->setData('znzTrackingUrls', $trackingUrls);
|
|
}
|
|
|
|
// save package id
|
|
$order->setPackageId(implode(',', array_filter($packages)));
|
|
|
|
$order->logHistory('[Helios] Číslo balíku: '.$packageId);
|
|
}
|
|
|
|
public function processOrderDocument(array $item): void
|
|
{
|
|
// pokud se jedna o delete message
|
|
if ($this->isDeleteMessage($item)) {
|
|
$documentId = $item['meta']['unique_id'];
|
|
|
|
// najdu si objednavku podle ID dokladu
|
|
$orderId = sqlQueryBuilder()
|
|
->select('id')
|
|
->from('orders')
|
|
->where('JSON_EXTRACT(note_admin, \'$.znzDocuments."'.$documentId.'"\') IS NOT NULL')
|
|
->execute()->fetchOne();
|
|
|
|
// pokud jsem nasel objednavku, tak provedu odebrani dokladu
|
|
if ($orderId) {
|
|
$order = \Order::get($orderId);
|
|
$documents = $order->getData('znzDocuments') ?: [];
|
|
// pokud mam ulozenej doklad, tak provedu jeho odebrani
|
|
if ($documents[$documentId] ?? false) {
|
|
unset($documents[$documentId]);
|
|
$order->setData('znzDocuments', $documents);
|
|
// zaloguju info o odebrani dokladu
|
|
$order->logHistory(sprintf('[Helios] Doklad "%s" byl od objednávky odebrán', $documentId));
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (!($order = $this->getOrderByItem($item))) {
|
|
return;
|
|
}
|
|
|
|
$documents = $order->getData('znzDocuments');
|
|
// doklad uz existuje, takze nemusim nic delat
|
|
if ($documents[$item['IdDoklad']] ?? false) {
|
|
return;
|
|
}
|
|
|
|
$filePath = ltrim($item['URL'], '\\');
|
|
|
|
// ulozim doklad
|
|
$documents[$item['IdDoklad']] = [
|
|
'type' => $item['DruhDokladu'],
|
|
'path' => $filePath,
|
|
];
|
|
|
|
$order->setData('znzDocuments', $documents);
|
|
|
|
$documentUrl = $this->contextManager->activateOrder($order, fn () => path('kupshop_orderingbundle_pdfinvoice', ['id_order' => $order->id, 'cf' => $order->getSecurityCode(), 'documentId' => $item['IdDoklad']], Router::ABSOLUTE_URL));
|
|
|
|
// zaloguju info k objednavce o dokladu
|
|
$order->logHistory(
|
|
sprintf(
|
|
'[Helios] Doklad "%s": <a href="%s" target="_blank">%s</a>',
|
|
$item['IdDoklad'],
|
|
$documentUrl,
|
|
$filePath
|
|
)
|
|
);
|
|
|
|
if (!empty($item['Splatnost'])) {
|
|
try {
|
|
$dateDue = new \DateTime($item['Splatnost']);
|
|
} catch (\Throwable) {
|
|
$dateDue = null;
|
|
}
|
|
|
|
sqlQueryBuilder()
|
|
->update('orders')
|
|
->directValues(['date_due' => $dateDue?->format('Y-m-d H:i:s')])
|
|
->where(Operator::equals(['id' => $order->id]))
|
|
->execute();
|
|
}
|
|
}
|
|
|
|
protected function updateOrderPayments(\Order $order, array $item): void
|
|
{
|
|
// pokud objednavka nema cenu, tak nemam co updatovat
|
|
if ($item['Celkem'] <= 0) {
|
|
return;
|
|
}
|
|
|
|
$orderPaymentMethod = null;
|
|
// kvuli rychlosti pri vytvareni objednavek z Heliosu
|
|
if ($order->getDeliveryId()) {
|
|
$deliveryType = \DeliveryType::get(
|
|
$order->getDeliveryId(),
|
|
true
|
|
);
|
|
|
|
$orderPaymentMethod = $deliveryType?->getPayment()?->getPayMethod();
|
|
}
|
|
|
|
// pokud se jedna o platbu online
|
|
if ($orderPaymentMethod && in_array($orderPaymentMethod, [\Payment::METHOD_TRANSFER, \Payment::METHOD_ONLINE])) {
|
|
// nebudu odebirat zaplaceni, dela to akorat bordel
|
|
if ($order->isPaid(true) && $item['Zaplaceno'] !== true) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// pokud se jedna o platbu online nebo prevodem a jeste nebylo do Heliosu odeslana informace o zaplaceni
|
|
if ($orderPaymentMethod && in_array($orderPaymentMethod, [\Payment::METHOD_TRANSFER, \Payment::METHOD_ONLINE]) && !$this->getOrderMappingData($order->id, 'paidSent')) {
|
|
// pokud prislo, ze objednavka neni zaplacena, ale na e-shopu je zaplacena, tak skocim pryc
|
|
// protoze jinak se stalo to, ze zakaznik zaplatil objednavku a mezitim nez se do Heliosu odeslala informace
|
|
// o zaplaceni, tak prisla zmena z Heliosu, kde nebyla zaplacena a od objednavky se tak odebrala platba
|
|
if ($order->isPaid(true) && $item['Zaplaceno'] !== true) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// pokud objednavka na shopu neni zaplacena a v Heliosu ji oznacili jako zaplacenou
|
|
if ($item['Zaplaceno'] === true && !$order->isPaid(true)) {
|
|
sqlQueryBuilder()
|
|
->delete('order_payments')
|
|
->where(Operator::equals(['id_order' => $order->id, 'note' => 'Zaplaceno z Heliosu']))
|
|
->execute();
|
|
|
|
$order->insertPayment($order->getTotalPrice()->getPriceWithVat(), 'Zaplaceno z Heliosu', null, true);
|
|
$this->setOrderMappingData($order->id, ['paidSent' => true]);
|
|
if ($order->status <= 1) {
|
|
$order->changeStatus(1, '[Helios] Objednávka byla zaplacena', false);
|
|
} else {
|
|
$order->logHistory('[Helios] Objednávka byla zaplacena');
|
|
}
|
|
}
|
|
|
|
// pokud objednavka na shopu je zaplacena a v Heliosu ji oznacili jako nezaplacenou
|
|
if ($item['Zaplaceno'] === false && $order->isPaid(true)) {
|
|
$order->insertPayment(toDecimal($order->getPayments())->mul(\DecimalConstants::negativeOne()), 'Odebráno zaplacení z Heliosu', null, true);
|
|
$order->logHistory('[Helios] Objednávce bylo odebráno zaplacení');
|
|
}
|
|
}
|
|
|
|
protected function getOrderItemPriceDebug(\Order $order, OrderItem $item): ?string
|
|
{
|
|
$keysToExport = ['priceWithoutDiscounts', 'totalDiscount', 'discounts'];
|
|
|
|
$result = [];
|
|
|
|
foreach ($item->getNote() as $key => $note) {
|
|
if (!in_array($key, $keysToExport)) {
|
|
continue;
|
|
}
|
|
|
|
$result[$key] = $note;
|
|
}
|
|
|
|
return !empty($result) ? json_encode($result) : null;
|
|
}
|
|
|
|
protected function getOrderTotalPrice(\Order $order, array $deliveryTypeInfo): float
|
|
{
|
|
return $order->getTotalPrice()->getPriceWithVat()->asFloat();
|
|
}
|
|
|
|
protected function getOrderUrl(\Order $order): string
|
|
{
|
|
return $this->contextManager->activateOrder($order, fn (): string => $order->getUrl());
|
|
}
|
|
|
|
protected function getOrderStoreId(\Order $order): ?int
|
|
{
|
|
$storeIds = $this->configuration->getSettings()['order']['storeId'] ?? [];
|
|
|
|
$storeId = $storeIds[$order->getLanguage()] ?? null;
|
|
if (!empty($storeId)) {
|
|
return (int) $storeId;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
protected function getOrderNumber(\Order $order): string
|
|
{
|
|
return $this->znzUtil->getOrderNumberWithPrefix($order);
|
|
}
|
|
|
|
protected function getOrderCustomerNumber(\Order $order): ?string
|
|
{
|
|
if (isset($order->dropshipmentInfo['external']['id'])) {
|
|
return $order->dropshipmentInfo['external']['id'];
|
|
}
|
|
|
|
return $order->user_order_no ?: null;
|
|
}
|
|
|
|
protected function getNonProductItemData(\Order $order, OrderItem $item): ?array
|
|
{
|
|
if ($item->getProductId()) {
|
|
return null;
|
|
}
|
|
|
|
if ($this->orderItemInfo->getItemType($item) === OrderItemInfo::TYPE_DISCOUNT) {
|
|
if (!empty($item->getNote()['generated_coupon'])) {
|
|
return [
|
|
'code' => '5',
|
|
'sourceCode' => $this->getDefaultSourceCode($order),
|
|
];
|
|
} else {
|
|
// fallback pokud se do objednavky dostane nejaka neproduktova polozka (sleva)
|
|
return [
|
|
'code' => '1',
|
|
'sourceCode' => $this->getDefaultSourceCode($order),
|
|
];
|
|
}
|
|
}
|
|
|
|
if ($this->orderItemInfo->getItemType($item) === OrderItemInfo::TYPE_CHARGE) {
|
|
foreach ($order->fetchItems() as $orderItem) {
|
|
if (empty($orderItem->getNote()['generated_coupon'])) {
|
|
continue;
|
|
}
|
|
|
|
if (str_starts_with($item->getDescr(), $orderItem->getDescr())) {
|
|
return [
|
|
'code' => '5',
|
|
'sourceCode' => $this->getDefaultSourceCode($order),
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
protected function getOrderItemStoreId(\Order $order, OrderItem $item): ?string
|
|
{
|
|
if (!$item->getProductId()) {
|
|
return null;
|
|
}
|
|
|
|
$storeId = $this->znzOrderUtil->getOrderStoreId($order);
|
|
|
|
if ($storeId && ($znzId = $this->znzUtil->getZNZId('store', $storeId))) {
|
|
return $znzId;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
protected function getDefaultSourceCode(\Order $order): ?string
|
|
{
|
|
if ($store = $this->znzUtil->getDefaultStore($order->getLanguage())) {
|
|
if ($znzId = $this->znzUtil->getZNZId('store', $store['id'])) {
|
|
return $znzId;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
protected function getOrderShippingMethod(\Order $order): string
|
|
{
|
|
$delivery = $order->getDeliveryType()->getDelivery();
|
|
|
|
$znzIdByCountry = $delivery->getCustomData()['id_znz_by_country'] ?? [];
|
|
|
|
if (!empty($znzIdByCountry[$order->delivery_country])) {
|
|
return $znzIdByCountry[$order->delivery_country];
|
|
}
|
|
|
|
$znzIds = explode(',', $delivery->getCustomData()['id_znz'] ?? '');
|
|
if (empty($znzIds)) {
|
|
return '';
|
|
}
|
|
|
|
return reset($znzIds);
|
|
}
|
|
|
|
protected function getOrderPaymentMethod(\Order $order): string
|
|
{
|
|
if (!($payment = $order->getDeliveryType()->getPayment())) {
|
|
return '';
|
|
}
|
|
|
|
$znzIds = explode(',', $payment->getCustomData()['id_znz'] ?? '');
|
|
if (empty($znzIds)) {
|
|
return '';
|
|
}
|
|
|
|
return reset($znzIds);
|
|
}
|
|
|
|
protected function getOrderDeliveryTypeInfo(\Order $order): array
|
|
{
|
|
$deliveryItem = null;
|
|
/** @var OrderItem $item */
|
|
foreach ($order->fetchItems() as $item) {
|
|
if ($this->orderItemInfo->getItemType($item) === OrderItemInfo::TYPE_DELIVERY) {
|
|
$deliveryItem = $item;
|
|
break;
|
|
}
|
|
}
|
|
|
|
$deliveryPrice = \DecimalConstants::zero();
|
|
$paymentPrice = \DecimalConstants::zero();
|
|
|
|
if ($deliveryItem) {
|
|
return $this->contextManager->activateOrder($order, function () use ($order, $deliveryItem) {
|
|
$deliveryType = $order->getDeliveryType();
|
|
|
|
$totalPrice = $order->getPurchaseState()->getTotalPriceForDiscounts();
|
|
|
|
$deliveryType->accept(
|
|
new Price(
|
|
$totalPrice->getPriceWithVat(),
|
|
$totalPrice->getCurrency(),
|
|
0
|
|
),
|
|
false,
|
|
$order->getPurchaseState()
|
|
);
|
|
|
|
$paymentPrice = PriceCalculator::sub($deliveryItem->getTotalPrice(), $deliveryType->getDelivery()->getPrice());
|
|
$deliveryPrice = PriceCalculator::sub($deliveryItem->getTotalPrice(), $paymentPrice);
|
|
|
|
$deliveryPrice = $deliveryPrice->getPriceWithVat();
|
|
|
|
// pravdepodobne chybka kvuli nejakymu jinymu kurzu meny
|
|
if ($paymentPrice->getPriceWithVat()->asFloat() <= 0.2) {
|
|
$paymentPrice = \DecimalConstants::zero();
|
|
} else {
|
|
$paymentPrice = $paymentPrice->getPriceWithVat();
|
|
}
|
|
|
|
// asi nejakej problem zaorkouhleni.. ufff rozdelovani delivery itemu na cenu dopravy a platby je trosku narocnejsi :)
|
|
$diff = $deliveryItem->getTotalPrice()->getPriceWithVat()->sub($deliveryPrice->add($paymentPrice));
|
|
if (!$diff->isZero()) {
|
|
if ($deliveryPrice->isPositive()) {
|
|
$deliveryPrice = $deliveryPrice->add($diff);
|
|
} elseif ($paymentPrice->isPositive()) {
|
|
$paymentPrice = $paymentPrice->add($diff);
|
|
}
|
|
}
|
|
|
|
return [
|
|
'deliveryPrice' => $deliveryPrice->asFloat(),
|
|
'paymentPrice' => $paymentPrice->asFloat(),
|
|
];
|
|
});
|
|
}
|
|
|
|
return [
|
|
'deliveryPrice' => $deliveryPrice->asFloat(),
|
|
'paymentPrice' => $paymentPrice->asFloat(),
|
|
];
|
|
}
|
|
|
|
protected function updateOrderItems(\Order $order, array $heliosOrder): bool
|
|
{
|
|
// pokud se nic nezmenilo, tak nic neprovadim
|
|
if (!$this->hasHeliosOrderItemsChanged($order, $heliosOrder)) {
|
|
return false;
|
|
}
|
|
|
|
// pokud se zmenila celkova cena objednavky, nebo se zmenil celkovy pocet polozek v objednavce, tak provedu aktualizaci polozek
|
|
sqlGetConnection()->transactional(function () use ($order, $heliosOrder) {
|
|
$currentItems = Mapping::mapKeys(sqlQueryBuilder()
|
|
->select('*')
|
|
->from('order_items')
|
|
->where(Operator::equals(['id_order' => $order->id]))
|
|
->execute(), fn ($k, $v) => [$v['id'], $v]);
|
|
|
|
// smazu polozky objednavky
|
|
sqlQueryBuilder()
|
|
->delete('order_items')
|
|
->where(Operator::equals(['id_order' => $order->id]))
|
|
->execute();
|
|
|
|
$this->insertOrderItems(
|
|
$order->id,
|
|
Contexts::get(CurrencyContext::class)->getOrDefault($order->getCurrency()),
|
|
$heliosOrder,
|
|
$currentItems
|
|
);
|
|
|
|
// recalculate order kvůli zpetnemu reimportu polozek z heliosu
|
|
$order->recalculate();
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
protected function getOrderAddresses(\Order $order): array
|
|
{
|
|
$delivery = $order->getDeliveryType()->getDelivery();
|
|
$deliveryData = $order->getData('delivery_data');
|
|
|
|
$invoiceCompany = [$order->invoice_firm];
|
|
if (!empty($order->invoice_custom_address)) {
|
|
$invoiceCompany[] = $order->invoice_custom_address;
|
|
}
|
|
|
|
$invoiceCompany = array_unique($invoiceCompany);
|
|
|
|
$deliveryCompany = [$order->delivery_firm];
|
|
if ($delivery instanceof \DHLServicePoint) {
|
|
$deliveryCompany = [];
|
|
}
|
|
if (!empty($order->delivery_custom_address)) {
|
|
$deliveryCompany[] = $order->delivery_custom_address;
|
|
} elseif ($delivery instanceof \DHLServicePoint && ($deliveryData['keyword'] ?? null) === 'Postfiliale') {
|
|
$order->delivery_street = "{$deliveryData['keyword']} {$deliveryData['harmonisedId']}";
|
|
}
|
|
|
|
$deliveryCompany = array_unique($deliveryCompany);
|
|
|
|
$pointId = null;
|
|
if ($delivery->getType() === \Delivery::TYPE_POINT) {
|
|
$pointId = $delivery->getPointId();
|
|
}
|
|
|
|
return [
|
|
[
|
|
'item' => [
|
|
'address_type' => 'billing',
|
|
'firstname' => $order->invoice_name,
|
|
'middlename' => '',
|
|
'lastname' => $order->invoice_surname,
|
|
'company' => implode(',', array_filter($invoiceCompany)),
|
|
'street' => $order->invoice_street,
|
|
'popCislo' => '',
|
|
'city' => $order->invoice_city,
|
|
'postcode' => $order->invoice_zip,
|
|
'region_id' => $order->invoice_state,
|
|
'country_id' => $this->znzUtil->getCountryCodeForHelios($order->invoice_country),
|
|
'email' => $order->invoice_email,
|
|
'telephone' => $order->invoice_phone,
|
|
'helios_company_registration_number' => $order->invoice_ico,
|
|
'vat_id' => $order->invoice_dic,
|
|
'orcislo' => '',
|
|
],
|
|
],
|
|
[
|
|
'item' => [
|
|
'address_type' => 'shipping',
|
|
'firstname' => $order->delivery_name,
|
|
'middlename' => '',
|
|
'lastname' => $order->delivery_surname,
|
|
'company' => implode(',', array_filter($deliveryCompany)),
|
|
'street' => $order->delivery_street,
|
|
'popCislo' => '',
|
|
'city' => $order->delivery_city,
|
|
'postcode' => $order->delivery_zip,
|
|
'region_id' => $order->invoice_state,
|
|
'country_id' => $this->znzUtil->getCountryCodeForHelios($order->delivery_country),
|
|
'email' => $order->invoice_email,
|
|
'telephone' => $order->delivery_phone,
|
|
'helios_company_registration_number' => '',
|
|
'vat_id' => '',
|
|
'orcislo' => '',
|
|
'pobox' => $pointId,
|
|
],
|
|
],
|
|
];
|
|
}
|
|
|
|
private function hasHeliosOrderItemsChanged(\Order $order, array $heliosOrder): bool
|
|
{
|
|
// sortovaci fce, aby oba seznamy byly serazeny stejne
|
|
$withSorted = function (array $data) {
|
|
usort($data, function ($a, $b) {
|
|
return $a['znzId'] <=> $b['znzId'];
|
|
});
|
|
|
|
return $data;
|
|
};
|
|
|
|
// vytvorim seznam polozek podle Heliosu
|
|
$compareItemsHelios = $withSorted(array_map(
|
|
function (array $item) {
|
|
return [
|
|
'znzId' => $item['RegCis'],
|
|
'pieces' => $item['Mnozstvi'],
|
|
'price' => $item['Celkem'],
|
|
];
|
|
},
|
|
$heliosOrder['Polozky'] ?? []
|
|
));
|
|
|
|
// vytvorim seznam polozek podle shopu
|
|
$compareItemsShop = $withSorted(array_map(
|
|
function (OrderItem $item) use ($order) {
|
|
$itemData = $this->getNonProductItemData($order, $item);
|
|
|
|
return [
|
|
'znzId' => $item->getCode() ?: $itemData['code'] ?? null,
|
|
'pieces' => $item->getPieces(),
|
|
'price' => $this->getOrderItemPrice($item),
|
|
];
|
|
},
|
|
array_values($order->getItems())
|
|
));
|
|
|
|
// pokud to neni stejny, tak se neco muselo zmenit
|
|
return $compareItemsShop != $compareItemsHelios;
|
|
}
|
|
|
|
private function getProductSets(\Product $product): array
|
|
{
|
|
$sets = $product->fetchSets(true);
|
|
|
|
$result = [];
|
|
foreach ($sets as $set) {
|
|
$key = $set->id;
|
|
if ($set instanceof \Variation) {
|
|
$key .= '/'.$set->variationId;
|
|
}
|
|
|
|
$result[$key] = $set;
|
|
}
|
|
|
|
$variationIds = array_filter(array_map(fn ($x) => $x->variationId ?? null, $result));
|
|
|
|
$variationCodes = sqlQueryBuilder()
|
|
->select('id, code')
|
|
->from('products_variations')
|
|
->where(Operator::inIntArray($variationIds, 'id'))
|
|
->execute()->fetchAllAssociativeIndexed();
|
|
|
|
foreach ($result as $item) {
|
|
if (!($item instanceof \Variation)) {
|
|
continue;
|
|
}
|
|
|
|
$item->variationCode = $variationCodes[$item->variationId]['code'] ?? null;
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
private function createOrderFromHelios(array $item): void
|
|
{
|
|
if (!$this->isOrderNumberPrefixForCurrentWebsite($item['IdObjednavka'])) {
|
|
return;
|
|
}
|
|
|
|
$orderNumber = $item['IdObjednavka'];
|
|
|
|
if ($this->getOrderByOrderNumber($orderNumber)) {
|
|
return;
|
|
}
|
|
|
|
foreach ($this->configuration->getOrderPrefixes() as $prefix) {
|
|
if (str_starts_with($orderNumber, $prefix)) {
|
|
$orderNumber = str_replace($prefix, '', $orderNumber);
|
|
break;
|
|
}
|
|
}
|
|
|
|
$userId = null;
|
|
if (!empty($item['IdZakaznik'])) {
|
|
$userId = $this->znzUtil->getMapping(UserSynchronizer::getType(), $item['IdZakaznik']);
|
|
}
|
|
|
|
$data = [
|
|
'id_language' => $this->getOrderLanguageByWebsite($item),
|
|
'source' => OrderInfo::ORDER_SOURCE_IMPORT,
|
|
'order_no' => $orderNumber,
|
|
'id_user' => $userId,
|
|
'date_created' => $item['DatumPripadu'],
|
|
'currency' => $item['Mena'],
|
|
'note_admin' => json_encode([]),
|
|
'status' => 1,
|
|
];
|
|
|
|
if (!empty($item['email'])) {
|
|
$data['invoice_email'] = $item['email'];
|
|
}
|
|
|
|
foreach ($item['Adresy'] ?? [] as $heliosAddress) {
|
|
$userName = $this->znzUtil->getUserNameParts($heliosAddress['Nazev']);
|
|
|
|
// pokud je tam jen predcisli, tak to rovnou vyprazdnim
|
|
if (preg_match('/^\+(\d{1,3})$/', $heliosAddress['Telefon'] ?? '')) {
|
|
$heliosAddress['Telefon'] = '';
|
|
}
|
|
|
|
$prefix = 'invoice_';
|
|
if ($heliosAddress['TypAdresy'] !== 'billing') {
|
|
$prefix = 'delivery_';
|
|
}
|
|
|
|
$heliosAddressParts = [
|
|
$prefix.'name' => $userName['name'],
|
|
$prefix.'surname' => $userName['surname'],
|
|
$prefix.'phone' => $heliosAddress['Telefon'] ?? '',
|
|
$prefix.'firm' => $heliosAddress['DruhyNazev'],
|
|
$prefix.'city' => $heliosAddress['Misto'],
|
|
$prefix.'zip' => $heliosAddress['PSC'] ?? '',
|
|
$prefix.'street' => $heliosAddress['Ulice'],
|
|
$prefix.'country' => $this->znzUtil->getCountryCodeByHelios($heliosAddress['IdZeme'] ?? Contexts::get(CountryContext::class)->getDefaultId()),
|
|
];
|
|
|
|
$data = array_merge($data, $heliosAddressParts);
|
|
}
|
|
|
|
// najdu dopravu a platbu
|
|
$deliveryId = null;
|
|
if (!empty($item['ZpusobDopravy'])) {
|
|
$deliveryId = $this->getDeliveryIdByHeliosId($item['ZpusobDopravy']);
|
|
}
|
|
|
|
$paymentId = null;
|
|
if (!empty($item['ZpusobPlatby'])) {
|
|
$paymentId = $this->getPaymentIdByHeliosId($item['ZpusobPlatby']);
|
|
}
|
|
|
|
if ($deliveryId && $paymentId) {
|
|
$data['id_delivery'] = $this->getDeliveryTypeByDeliveryAndPayment($deliveryId, $paymentId);
|
|
}
|
|
|
|
$currency = Contexts::get(CurrencyContext::class)->getOrDefault($item['Mena']);
|
|
|
|
$order = sqlGetConnection()->transactional(function () use ($item, $data, $currency) {
|
|
sqlQueryBuilder()
|
|
->insert('orders')
|
|
->directValues($data)
|
|
->execute();
|
|
|
|
$orderId = (int) sqlInsertId();
|
|
|
|
$this->insertOrderItems($orderId, $currency, $item);
|
|
|
|
$round = true;
|
|
if ($currency != 'CZK') {
|
|
$round = false;
|
|
}
|
|
|
|
$order = \Order::get($orderId);
|
|
$order->recalculate(round: $round);
|
|
|
|
$order->logHistory($item['VerejnaPoznamka'] ?? '');
|
|
$order->logHistory('[Helios] Vytvořeno synchronizací z Heliosu; ID: '.$this->getZNZIdInHex($item['GUIDObjednavka']).';');
|
|
|
|
$this->znzUtil->createMapping(static::getType(), $item['GUIDObjednavka'], $orderId);
|
|
|
|
if ($item['Zaplaceno'] === true) {
|
|
$this->setOrderMappingData($orderId, ['paidSent' => true]);
|
|
}
|
|
|
|
return $order;
|
|
});
|
|
|
|
$this->updateOrderPayments($order, $item);
|
|
}
|
|
|
|
private function updateOrderDeliveryTypeByHelios(\Order $order, array $item, ?array &$activityLogErrors = null): void
|
|
{
|
|
// najdu dopravu a platbu
|
|
$delivery = null;
|
|
if (!empty($item['ZpusobDopravy'])) {
|
|
if (!($delivery = $this->getDeliveryByHeliosId($item['ZpusobDopravy']))) {
|
|
$activityLogErrors[] = sprintf('Nepodařilo se dohledat dopravu podle ID: %s', $item['ZpusobDopravy']);
|
|
}
|
|
}
|
|
|
|
$deliveryId = $delivery ? (int) $delivery->id : null;
|
|
|
|
$paymentId = null;
|
|
if (!empty($item['ZpusobPlatby'])) {
|
|
if (!($paymentId = $this->getPaymentIdByHeliosId($item['ZpusobPlatby']))) {
|
|
$activityLogErrors[] = sprintf('Nepodařilo se dohledat platbu podle ID: %s', $item['ZpusobPlatby']);
|
|
}
|
|
}
|
|
|
|
if ($deliveryId && $paymentId) {
|
|
if ($deliveryTypeId = $this->getDeliveryTypeByDeliveryAndPayment($deliveryId, $paymentId)) {
|
|
if ($deliveryTypeId != $order->getDeliveryId()) {
|
|
$previousDeliveryTypeName = $this->getDeliveryTypeName((int) $order->getDeliveryId());
|
|
$newDeliveryTypeName = $this->getDeliveryTypeName($deliveryTypeId);
|
|
|
|
sqlQueryBuilder()
|
|
->update('orders')
|
|
->directValues(['id_delivery' => $deliveryTypeId])
|
|
->where(Operator::equals(['id' => $order->id]))
|
|
->execute();
|
|
|
|
$order->logHistory(sprintf('[Helios] Byl aktualizován způsob doručení: "%s" -> %s', $previousDeliveryTypeName, $newDeliveryTypeName));
|
|
}
|
|
} else {
|
|
$activityLogErrors[] = [
|
|
'message' => sprintf('Nepodařilo se dohledat způsob doručení pro dopravu "%s" (%s) a platbu "%s" (%s)', $item['ZpusobDopravy'], $deliveryId, $item['ZpusobPlatby'], $paymentId),
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
private function getDeliveryTypeName(int $deliveryTypeId): string
|
|
{
|
|
$types = \DeliveryType::getAll(true);
|
|
|
|
if ($deliveryType = ($types[$deliveryTypeId] ?? null)) {
|
|
return $deliveryType->name;
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
private function insertOrderItems(int $orderId, Currency $currency, array $heliosOrder, ?array $currentItems = null): void
|
|
{
|
|
foreach ($heliosOrder['Polozky'] ?? [] as $orderItem) {
|
|
[$productId, $variationId] = [null, null];
|
|
if ($orderItem['IdProdukt'] ?? false) {
|
|
[$productId, $variationId] = $this->znzUtil->getProductMapping($orderItem['IdProdukt']);
|
|
}
|
|
|
|
if (!isset($orderItem['Celkem'], $orderItem['Celkem'], $orderItem['CelkemBezDPH'], $orderItem['Mnozstvi'])) {
|
|
continue;
|
|
}
|
|
|
|
$itemId = null;
|
|
$itemData = [];
|
|
if ($currentItem = ($currentItems[$orderItem['IdObjednavkaPolozka']] ?? false)) {
|
|
$itemId = $currentItem['id'];
|
|
$itemData = json_decode($currentItem['note'] ?: '', true) ?: [];
|
|
}
|
|
|
|
$totalPrice = roundPrice(toDecimal($orderItem['Celkem']), currency: $currency);
|
|
$totalPriceWithoutVat = toDecimal($orderItem['CelkemBezDPH']);
|
|
$pieces = toDecimal($orderItem['Mnozstvi']);
|
|
|
|
// pokud ma polozka 0 kusu, tak nema byt v objednavce
|
|
if ($pieces->isZero()) {
|
|
continue;
|
|
}
|
|
|
|
$vat = $this->calculateVat($totalPrice->abs(), $totalPriceWithoutVat->abs());
|
|
|
|
$insertData = [
|
|
'id_order' => $orderId,
|
|
'id_product' => $productId,
|
|
'id_variation' => $variationId,
|
|
'pieces' => $pieces,
|
|
'pieces_reserved' => $orderItem['Mnozstvi'],
|
|
'piece_price' => $totalPriceWithoutVat->div($pieces),
|
|
'total_price' => $totalPriceWithoutVat,
|
|
'tax' => $vat->printFloatValue(2),
|
|
'descr' => $orderItem['Nazev'] ?? 'unknown item',
|
|
'note' => json_encode(array_merge($itemData, ['heliosGuid' => $orderItem['GUIDObjednavkaPolozka'] ?? null])),
|
|
];
|
|
|
|
if ($itemId) {
|
|
$insertData['id'] = $itemId;
|
|
}
|
|
|
|
sqlQueryBuilder()
|
|
->insert('order_items')
|
|
->directValues($insertData)
|
|
->execute();
|
|
}
|
|
|
|
$deliveryItem = null;
|
|
foreach ($currentItems ?: [] as $currentItem) {
|
|
$note = json_decode($currentItem['note'] ?? '', true) ?: [];
|
|
if (($note['item_type'] ?? null) === OrderItemInfo::TYPE_DELIVERY) {
|
|
$deliveryItem = $currentItem;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// insertnout dopravne
|
|
if (!empty($heliosOrder['DopravneCelkem']) && !empty($heliosOrder['DopravneCelkemBezDPH'])) {
|
|
$deliveryItemData = [
|
|
'id_order' => $orderId,
|
|
'id_product' => null,
|
|
'id_variation' => null,
|
|
'pieces' => 1,
|
|
'pieces_reserved' => 1,
|
|
'piece_price' => $heliosOrder['DopravneCelkemBezDPH'],
|
|
'total_price' => $heliosOrder['DopravneCelkemBezDPH'],
|
|
'tax' => $this->calculateVat(toDecimal($heliosOrder['DopravneCelkem']), toDecimal($heliosOrder['DopravneCelkemBezDPH'])),
|
|
'descr' => $deliveryItem ? $deliveryItem['descr'] : 'Dopravné',
|
|
'note' => $deliveryItem['note'] ?? '[]',
|
|
];
|
|
|
|
if ($deliveryItem) {
|
|
$deliveryItemData['id'] = $deliveryItem['id'];
|
|
}
|
|
|
|
sqlQueryBuilder()
|
|
->insert('order_items')
|
|
->directValues($deliveryItemData)
|
|
->execute();
|
|
}
|
|
}
|
|
|
|
private function getOrderItemPrice(OrderItem $item, bool $withVat = true): float
|
|
{
|
|
$roundPrice = true;
|
|
|
|
// pokud je aplikovana sleva, ktera je rozpocitana do polozek, tak chci vracet cenu bez zaorkouhleni, aby obsahovala i desetiny
|
|
if (($item->getNote()['totalDiscount'] ?? false) && fmod((float) $item->getNote()['totalDiscount'], 1) !== 0.00) {
|
|
$roundPrice = false;
|
|
}
|
|
|
|
// pokud je cena mensi jak 1, tak nechci provadet zaorkouhleni
|
|
if ($item->getPiecePrice()->getPriceWithVat(false)->lowerThan(\DecimalConstants::one())) {
|
|
$roundPrice = false;
|
|
}
|
|
|
|
if ($withVat) {
|
|
return (float) $item->getPiecePrice()->getPriceWithVat($roundPrice)->printFloatValue(2);
|
|
}
|
|
|
|
return (float) $item->getPiecePrice()->getPriceWithoutVat($roundPrice)->printFloatValue(4);
|
|
}
|
|
|
|
private function calculateVat(\Decimal $priceWithVat, \Decimal $priceWithoutVat): \Decimal
|
|
{
|
|
$vat = \DecimalConstants::zero();
|
|
if ($priceWithoutVat->isPositive()) {
|
|
$vat = $priceWithVat->div($priceWithoutVat)->sub(\DecimalConstants::one())->mul(\DecimalConstants::hundred());
|
|
}
|
|
|
|
return toDecimal($vat->printFloatValue(-1));
|
|
}
|
|
|
|
private function getDeliveryByHeliosId(string $heliosId): ?\Delivery
|
|
{
|
|
$heliosId = mb_strtolower($heliosId);
|
|
|
|
foreach (\DeliveryType::getDeliveries(true) as $delivery) {
|
|
$znzIds = array_map(fn ($x) => mb_strtolower(trim($x)), explode(',', $delivery->getCustomData()['id_znz'] ?? ''));
|
|
if (in_array($heliosId, $znzIds)) {
|
|
return $delivery;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function getDeliveryIdByHeliosId(string $heliosId): ?int
|
|
{
|
|
$heliosId = mb_strtolower($heliosId);
|
|
|
|
foreach (\DeliveryType::getDeliveries(true) as $delivery) {
|
|
$znzIds = array_map(fn ($x) => mb_strtolower(trim($x)), explode(',', $delivery->getCustomData()['id_znz'] ?? ''));
|
|
if (in_array($heliosId, $znzIds)) {
|
|
return (int) $delivery->id;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function getPaymentIdByHeliosId(string $heliosId): ?int
|
|
{
|
|
$heliosId = mb_strtolower($heliosId);
|
|
|
|
foreach (\DeliveryType::getPayments(true) as $payment) {
|
|
$data = json_decode($payment['data'] ?: '', true) ?: [];
|
|
$znzIds = array_map(fn ($x) => mb_strtolower(trim($x)), explode(',', $data['id_znz'] ?? ''));
|
|
|
|
if (in_array($heliosId, $znzIds)) {
|
|
return (int) $payment['id'];
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function getDeliveryTypeByDeliveryAndPayment(int $deliveryId, int $paymentId): ?int
|
|
{
|
|
foreach (\DeliveryType::getAll(true) as $deliveryType) {
|
|
if ($deliveryType->id_delivery == $deliveryId && $deliveryType->id_payment == $paymentId) {
|
|
return $deliveryType->id;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Obecna funkce pro ziskani `Order` objektu z dat zmeny.
|
|
*
|
|
* Primarne to kouka na `GUIDObjednavka` pokud existuje (postupne by melo zacit exisovat na vsech zmenach).
|
|
* Pak to fallbackuje na `IdObjednavka` (postupne by melo zaniknout).
|
|
*/
|
|
private function getOrderByItem(array $item): ?\Order
|
|
{
|
|
// uprednostuju najiti objednavky podle GUID pokud ho mam v datech
|
|
if (!empty($item['GUIDObjednavka'])) {
|
|
if ($orderId = $this->znzUtil->getMapping(self::getType(), $item['GUIDObjednavka'])) {
|
|
return \Order::get($orderId);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// fallback na pole IdObjednavka
|
|
if (!empty($item['IdObjednavka'])) {
|
|
return $this->getOrderByOrderNumber($item['IdObjednavka']);
|
|
}
|
|
|
|
// pokud v datech nemam ani jedno, tak hazim chybu at je to videt
|
|
throw new ZNZException(
|
|
message: 'Změnu objednávky se nepodařilo zpracovat, protože neobsahuje identifikátor pro načtení objednávky',
|
|
data: $item
|
|
);
|
|
}
|
|
|
|
private function getOrderByOrderNumber(string $orderNumber): ?\Order
|
|
{
|
|
if (!$this->isOrderNumberPrefixForCurrentWebsite($orderNumber)) {
|
|
return null;
|
|
}
|
|
|
|
foreach ($this->configuration->getOrderPrefixes() as $prefix) {
|
|
if (str_starts_with($orderNumber, $prefix)) {
|
|
$orderNumber = str_replace($prefix, '', $orderNumber);
|
|
break;
|
|
}
|
|
}
|
|
|
|
$orderId = sqlQueryBuilder()
|
|
->select('id')
|
|
->from('orders')
|
|
->where(Operator::equals(['order_no' => $orderNumber]))
|
|
->execute()->fetchOne();
|
|
|
|
if ($orderId) {
|
|
return \Order::get((int) $orderId);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function setOrderMappingData(int $orderId, array $data): void
|
|
{
|
|
$currentData = sqlQueryBuilder()
|
|
->select('data')
|
|
->from('znz_orders')
|
|
->where(Operator::equals(['id_order' => $orderId]))
|
|
->sendToMaster()
|
|
->execute()->fetchOne();
|
|
|
|
$currentData = json_decode($currentData ?: '', true) ?: [];
|
|
$currentData = array_merge($currentData, $data);
|
|
|
|
sqlQueryBuilder()
|
|
->update('znz_orders')
|
|
->directValues(['data' => json_encode($currentData)])
|
|
->where(Operator::equals(['id_order' => $orderId]))
|
|
->execute();
|
|
}
|
|
|
|
private function getOrderMappingData(int $orderId, string $key): mixed
|
|
{
|
|
$currentData = sqlQueryBuilder()
|
|
->select('data')
|
|
->from('znz_orders')
|
|
->where(Operator::equals(['id_order' => $orderId]))
|
|
->sendToMaster()
|
|
->execute()->fetchOne();
|
|
|
|
$currentData = json_decode($currentData ?: '', true) ?: [];
|
|
|
|
return $currentData[$key] ?? null;
|
|
}
|
|
|
|
private function isOrderNumberPrefixForCurrentWebsite(string $number): bool
|
|
{
|
|
// pokud nema prefix, tak nekontroluju prefix a vracim true
|
|
if (is_numeric($number)) {
|
|
return true;
|
|
}
|
|
|
|
$prefixes = $this->configuration->getOrderPrefixes();
|
|
foreach ($prefixes as $prefix) {
|
|
// cislo objednavky musi zacinat podporovanym prefixem
|
|
if (str_starts_with($number, $prefix)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private function getOrderList(): OrderList
|
|
{
|
|
$orderList = clone $this->orderList;
|
|
$orderList->fetchItems();
|
|
$orderList->andSpec(function (QueryBuilder $qb) {
|
|
$qb->leftJoin('o', 'znz_orders', 'zo', 'zo.id_order = o.id');
|
|
});
|
|
|
|
return $orderList;
|
|
}
|
|
|
|
private function getEnabledStores(): array
|
|
{
|
|
return $this->znzUtil->getEnabledStores();
|
|
}
|
|
|
|
private function getLanguageByCountryCode(?string $country): string
|
|
{
|
|
$languageContext = Contexts::get(LanguageContext::class);
|
|
|
|
$websites = $this->configuration->getSupportedWebsites();
|
|
$website = reset($websites);
|
|
|
|
$languageId = match ($country) {
|
|
'SK' => 'sk',
|
|
'DE' => 'de',
|
|
'FR' => 'fr',
|
|
'IT' => 'it',
|
|
default => $website['language'] ?? $languageContext->getDefaultId(),
|
|
};
|
|
|
|
$supported = $languageContext->getAll();
|
|
if (!isset($supported[$languageId])) {
|
|
$languageId = $languageContext->getDefaultId();
|
|
}
|
|
|
|
return $languageId;
|
|
}
|
|
|
|
private function getOrderLanguageByWebsite(array $item): string
|
|
{
|
|
if ($website = ($this->configuration->getSupportedWebsites()[$item['IdWebsite']] ?? false)) {
|
|
$website = $this->configuration->getSupportedWebsites()[$this->configuration->getMainWebsite()];
|
|
}
|
|
|
|
$language = $website['language'];
|
|
|
|
// jedna domena podporuje vice jazyku
|
|
if (is_array($language)) {
|
|
$language = reset($language);
|
|
}
|
|
|
|
return $language;
|
|
}
|
|
|
|
private function getZNZIdInHex(string $znzId): string
|
|
{
|
|
return '0x'.mb_strtoupper(bin2hex(base64_decode($znzId)));
|
|
}
|
|
}
|