442 lines
17 KiB
PHP
442 lines
17 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace KupShop\DropshipBundle\Transfer;
|
|
|
|
use KupShop\DropshipBundle\Exception\TransferException;
|
|
use KupShop\DropshipBundle\TransferInterface;
|
|
use KupShop\DropshipBundle\Util\TransferWorker;
|
|
use KupShop\KupShopBundle\Context\CurrencyContext;
|
|
use KupShop\KupShopBundle\Context\LanguageContext;
|
|
use KupShop\KupShopBundle\Util\Contexts;
|
|
use KupShop\OrderingBundle\Util\Order\OrderImporter;
|
|
use KupShop\OrderingBundle\Util\Order\OrderItemInfo;
|
|
use Query\Operator;
|
|
use Symfony\Component\ErrorHandler\ErrorHandler;
|
|
|
|
class GenericTransfer extends AbstractTransfer implements TransferInterface
|
|
{
|
|
protected static string $type = 'generic';
|
|
protected static string $name = 'Obecný';
|
|
|
|
/** @required */
|
|
public OrderImporter $orderImporter;
|
|
/** @required */
|
|
public TransferWorker $transferWorker;
|
|
|
|
public function prepareConfigurationData(array $data): array
|
|
{
|
|
$groups = [];
|
|
|
|
foreach ($data['mappingGroups'] ?? [] as $group) {
|
|
if (!empty($group['delete'])) {
|
|
continue;
|
|
}
|
|
|
|
$deliveries = [];
|
|
foreach ($group['deliveries'] ?? [] as $delivery) {
|
|
if (!empty($delivery['delete']) || empty($delivery['id_delivery'])) {
|
|
continue;
|
|
}
|
|
|
|
$deliveries[] = $delivery;
|
|
}
|
|
|
|
$payments = [];
|
|
foreach ($group['payments'] ?? [] as $payment) {
|
|
if (!empty($payment['delete']) || empty($payment['id_payment'])) {
|
|
continue;
|
|
}
|
|
|
|
$payments[] = $payment;
|
|
}
|
|
|
|
// empty values to the end
|
|
uasort($deliveries, fn ($x) => 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;
|
|
}
|
|
}
|