empty($x['value']) ? 1 : -1); // empty values to the end uasort($payments, fn ($x) => empty($x['value']) ? 1 : -1); $group['deliveries'] = $deliveries; $group['payments'] = $payments; $groups[] = $group; } $data['mappingGroups'] = $groups; return $data; } protected function getExternalData(\SimpleXMLElement $xml): array { return [ (string) $xml->EXTERNAL->ID, (array) $xml->EXTERNAL, ]; } protected function getDeliveryTypeByConfiguration(\SimpleXMLElement $order): ?\DeliveryType { $delivery = (string) $order->DELIVERY; $payment = (string) $order->PAYMENT; $country = (string) $order->DELIVERY_ADDRESS->COUNTRY; $mappingGroup = $this->getMappingGroup($order); $deliveryId = null; foreach ($mappingGroup['deliveries'] ?? [] as $deliveryConfig) { if ((empty($deliveryConfig['value']) || $deliveryConfig['value'] == $delivery) && (empty($deliveryConfig['country']) || $deliveryConfig['country'] == $country)) { $deliveryId = $deliveryConfig['id_delivery']; break; } } $paymentId = null; foreach ($mappingGroup['payments'] ?? [] as $paymentConfig) { if ((empty($paymentConfig['value']) || $paymentConfig['value'] == $payment) && (empty($paymentConfig['country']) || $paymentConfig['country'] == $country)) { $paymentId = $paymentConfig['id_payment']; break; } } return $this->findDeliveryType((int) $deliveryId, (int) $paymentId); } protected function getMappingGroup(\SimpleXMLElement $order): ?array { $mappingGroup = null; $default = null; foreach ($this->getConfiguration()['mappingGroups'] ?? [] as $group) { // if filter is set, then do check if ($filterTag = $order->xpath($group['filter']['tag'] ?? '')[0] ?? null) { $filterTagValue = (string) $filterTag; $filterValues = array_map('trim', explode(',', $group['filter']['value'] ?? '')); if (in_array($filterTagValue, $filterValues)) { $mappingGroup = $group; } } // if the group has no filter set, it is the default group if (empty($group['filter']['tag'])) { $default = $group; } } return $mappingGroup ?: $default; } public function in(array $config): void { $orders = $this->transformXML(); foreach ($orders->ORDER ?? [] as $xml) { [$externalId, $externalData] = $this->getExternalData($xml); if (empty($externalId)) { $this->addActivityLog( 'Nepodařilo se naimportovat objednávku do e-shopu, protože nemá externí ID. V XML souboru chybí "EXTERNAL/ID" element!', ); continue; } if (!$this->isDropshipOrderValidToImport($xml)) { continue; } // mapping group not found and ignore orders without mapping is enabled, so skip order if (($this->getConfiguration()['ignore_on_mapping_not_found'] ?? 'N') === 'Y' && !$this->getMappingGroup($xml)) { continue; } // pokud objednavka uz existuje, tak provedeme pouze aktualizaci if ($order = $this->getOrderByExternalId($externalId)) { $this->updateDropshipOrder($order, $xml); continue; } try { $order = sqlGetConnection()->transactional(function () use ($externalId, $externalData, $xml) { $currencyContext = Contexts::get(CurrencyContext::class); // nactu zakladni data o objednavce pomoci orderImporter servisy $data = $this->orderImporter->getOrderBaseData($xml); // nastavim jazyk objednavky if (findModule(\Modules::TRANSLATIONS)) { $groupSettings = $this->getMappingGroup($xml)['settings'] ?? []; $data['id_language'] = !empty($groupSettings['id_language']) ? $groupSettings['id_language'] : Contexts::get(LanguageContext::class)->getDefaultId(); } // pokud neni vyplnena currency, tak nastavim vychozi currency if (empty($data['currency'])) { $data['currency'] = $currencyContext->getDefaultId(); } // nactu si informace o mene, pokud mena neexistuje a mam vypnutou prices_to_default_currency, tak vyhazuju chybu $currencyInfo = $this->getCurrencyInfo($data['currency']); // pokud nemam currency rate, tak ho doplnim if (empty($data['currency_rate'])) { $data['currency_rate'] = $currencyInfo->rate; } $noteAdmin = json_decode($data['note_admin'] ?? '', true) ?: []; // najdu a vlozim dopravu k objednavce if ($deliveryType = $this->getDeliveryTypeByConfiguration($xml)) { $data['id_delivery'] = $deliveryType->id; // delivery point - napr.v pripade, ze se jedna o zasilkovnu $deliveryPoint = (string) $xml->DELIVERY_POINT; if (!empty($deliveryPoint) && method_exists($deliveryType->getDelivery(), 'getInfo')) { $noteAdmin['delivery_data'] = $deliveryType->getDelivery() ->setPointId($deliveryPoint) ->getInfo(); } } // ceny se budou konvertovat do vychozi meny, takze si pro to pripravim data if ($this->isPriceConvertionEnabled() && $currencyInfo->getCurrencyCode() !== $currencyContext->getDefaultId()) { $data['currency'] = $currencyContext->getDefaultId(); } $data['note_admin'] = json_encode($noteAdmin); $order = $this->createDropshipOrder($xml, $data); // obecny feed muze obsahovat i marketplace info, takze v tu chvili chci k objednavce zalogovat o jaky marketplace se jedna $this->logOrderMarketplaceInfo($order, $xml, $externalData); $lastItemTax = \DecimalConstants::zero(); foreach ($xml->ITEMS->ITEM as $item) { // zkontroluju, ze jsou vyplneny vsechny povinne udaje pro polozku objednavky if (!$this->checkRequiredXMLData($item, $this->getRequiredOrderItemFields())) { throw new TransferException( sprintf('Nepodařilo se naimportovat objednávku "%s": data položek objednávky nejsou validní', $externalId), (array) $item ); } // zkusim najit produkt a variantu [$productId, $variationId] = $this->getProductByItem($item); $isPriceWithVat = ((string) ($item->PIECE_PRICE->attributes()['with_vat'] ?? null)) === 'true'; $pieces = toDecimal((string) $item->PIECES); $piecePrice = $this->convertPrice(toDecimal((string) $item->PIECE_PRICE), $currencyInfo); // pokud je cena uvedena s DPH, tak DPH odectu if ($isPriceWithVat) { $piecePrice = $piecePrice->removeVat((string) $item->VAT); } $totalPrice = toDecimal($piecePrice)->mul($pieces); // odecist skladovost produktu if ($productId) { $product = \Variation::createProductOrVariation($productId, $variationId); $product->createFromDB(); $product->sell($variationId, $pieces->asFloat()); } $itemData = $this->modifyItem( [ 'id_order' => $order->id, 'id_product' => $productId, 'id_variation' => $variationId, 'pieces' => $pieces, 'pieces_reserved' => $pieces, 'piece_price' => $piecePrice, 'total_price' => $totalPrice, 'tax' => (string) $item->VAT, 'descr' => (string) $item->NAME, 'note' => json_encode(['item_type' => OrderItemInfo::TYPE_PRODUCT]), ], $item ); $lastItemTax = toDecimal($itemData['tax']); // vytvorim polozku objednavky sqlQueryBuilder() ->insert('order_items') ->directValues($itemData) ->execute(); $itemData['id'] = sqlInsertId(); $this->itemCreatedEvent( product: $product ?? null, idVariation: (int) $variationId, piecePrice: $piecePrice, pieces: (int) $pieces->asInteger(), data: [ 'row' => $itemData, 'items_table' => 'order_items', ], order: $order ); unset($product); } $deliveryPrice = (string) $xml->DELIVERY_PRICE; $paymentPrice = (string) $xml->PAYMENT_PRICE; $deliveryPrice = !empty($deliveryPrice) ? toDecimal($deliveryPrice) : \DecimalConstants::zero(); $paymentPrice = !empty($paymentPrice) ? toDecimal($paymentPrice) : \DecimalConstants::zero(); $deliveryPaymentPrice = $this->convertPrice($deliveryPrice->add($paymentPrice), $currencyInfo); // pridani polozky s dopravou a platbou do objednavky if ($deliveryItem = $this->getDeliveryPaymentItem($order, $deliveryType, $deliveryPaymentPrice, $lastItemTax)) { sqlQueryBuilder() ->insert('order_items') ->directValues($deliveryItem) ->execute(); } // prepocitam total price objednavky $order->recalculate(round: false); // oznacit objednavku jako zaplacenou, pokud prisel status_payed == 1 if (!$order->isPaid() && $data['status_payed'] == 1) { $this->payDropshipOrder($order); } return $order; }); $this->modifyInsertedOrder($order, $xml); } catch (\Throwable $e) { $this->transferWorker->logException($e, $this); } } } public function out(array $config): void { throw new \RuntimeException('Method "out" is not implemented for generic transfer'); } protected function updateDropshipOrder(\Order $order, \SimpleXMLElement $xml): void { $data = $this->orderImporter->getOrderBaseData($xml, false); $updateData = []; if (!empty($data['invoice_dic'])) { $updateData['invoice_dic'] = $data['invoice_dic']; } if (!empty($data['invoice_ico'])) { $updateData['invoice_ico'] = $data['invoice_ico']; } if (!empty($data['delivery_country'])) { $updateData['delivery_country'] = trim($data['delivery_country']); } if (!empty($data['invoice_country'])) { $updateData['invoice_country'] = trim($data['invoice_country']); } // aktualizovat stav zaplaceni if (!$order->isPaid() && $data['status_payed'] == 1) { $updateData['status_payed'] = 1; $this->payDropshipOrder($order); } if (!empty($updateData)) { sqlQueryBuilder() ->update('orders') ->directValues($updateData) ->where(Operator::equals(['id' => $order->id])) ->execute(); } } protected function getDeliveryPaymentItem(\Order $order, ?\DeliveryType $deliveryType, \Decimal $price, \Decimal $vat): ?array { $deliveryItemName = 'Doprava a platba'; if ($deliveryType) { $deliveryItemName = $deliveryType->name; } if (!$price->isPositive()) { return null; } $price = $price->removeVat($vat); return [ 'id_order' => $order->id, 'id_product' => null, 'id_variation' => null, 'pieces' => 1, 'pieces_reserved' => 1, 'piece_price' => $price, 'total_price' => $price, 'descr' => $deliveryItemName, 'tax' => $vat, 'note' => json_encode(['item_type' => OrderItemInfo::TYPE_DELIVERY]), ]; } protected function transformXML(): ?\SimpleXMLElement { try { if ($xml = $this->loadXML()) { if (!empty($this->dropshipment['transformation'])) { $xsl = new \DOMDocument(); $xsl->loadXML($this->dropshipment['transformation']); return ErrorHandler::call(fn () => simplexml_import_dom(\AutomaticImportTransform::TransformXml($xsl, $xml))); } return $xml; } } catch (\Throwable $e) { if (isLocalDevelopment()) { throw $e; } $this->addActivityLog( 'Nepodařilo se provést transformaci feedu!', ['error' => $e->getMessage()] ); } return null; } protected function logOrderMarketplaceInfo(\Order $order, \SimpleXMLElement $xml, array $externalData): void { // nazev marketplacu, ze ktereho objednavka pochazi if (!empty($externalData['marketplace'])) { $order->logHistory('[Dropshipment] Marketplace: '.$externalData['marketplace']); } // marketplace muze mit i nejaky vlastni ID - napr. v pripade baselinkeru do EXTERNAL/ID chodi ID baselinkeru // protoze to je to spravne ID pro pripadnou komunikaci s baselinkerem, ale nekdo muze chtit pracovat // i primo s ID z daneho marketplacu, takze tady je podpora, aby se pripadne zobrazilo aspon v historii if (!empty($externalData['marketplace_id'])) { $order->logHistory('[Dropshipment] Marketplace ID: '.$externalData['marketplace_id']); } } protected function getRequiredOrderItemFields(): array { return [ 'NAME', 'PIECES', 'PIECE_PRICE', 'VAT', ]; } private function checkRequiredXMLData(\SimpleXMLElement $xml, array $requiredFields): ?bool { foreach ($requiredFields as $field) { if (!isset($xml->{$field})) { return false; } } return true; } }