first commit
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace KupShop\DropshipBundle\Admin\Actions;
|
||||
|
||||
use KupShop\AdminBundle\Admin\Actions\AbstractAction;
|
||||
use KupShop\AdminBundle\Admin\Actions\ActionResult;
|
||||
use KupShop\AdminBundle\Admin\Actions\IAction;
|
||||
use KupShop\DropshipBundle\Util\TransferWorker;
|
||||
use Symfony\Contracts\Service\Attribute\Required;
|
||||
|
||||
class DropshipRunAction extends AbstractAction implements IAction
|
||||
{
|
||||
#[Required]
|
||||
public TransferWorker $transferWorker;
|
||||
|
||||
public function getTypes(): array
|
||||
{
|
||||
return ['Dropshipment'];
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return 'Spustit dropshipment';
|
||||
}
|
||||
|
||||
public function execute(&$data, array $config, string $type): ActionResult
|
||||
{
|
||||
$this->transferWorker->run(
|
||||
(int) $this->getId()
|
||||
);
|
||||
|
||||
return new ActionResult(true);
|
||||
}
|
||||
}
|
||||
111
bundles/KupShop/DropshipBundle/Admin/Dropshipment.php
Normal file
111
bundles/KupShop/DropshipBundle/Admin/Dropshipment.php
Normal file
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace KupShop\DropshipBundle\Admin;
|
||||
|
||||
use KupShop\DropshipBundle\Util\TransferLocator;
|
||||
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
|
||||
use KupShop\KupShopBundle\Util\System\BundleFinder;
|
||||
|
||||
class Dropshipment extends \Window
|
||||
{
|
||||
protected $required = [
|
||||
'name' => true,
|
||||
'source_url' => true,
|
||||
];
|
||||
|
||||
protected $defaults = [
|
||||
'active' => 1,
|
||||
];
|
||||
|
||||
protected TransferLocator $transferLocator;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->transferLocator = ServiceContainer::getService(TransferLocator::class);
|
||||
}
|
||||
|
||||
public function processFormData(): array
|
||||
{
|
||||
$data = parent::processFormData();
|
||||
$this->unserializeCustomData($data);
|
||||
$r = array_filter($data['data']['restrictions'] ?? [], fn ($v) => !empty($v));
|
||||
if (count($r) > 0) {
|
||||
if (empty($r['values']) || empty($r['tagName'])) {
|
||||
$this->addError(translate('restrictionsValidationError'));
|
||||
}
|
||||
|
||||
if (!empty($r['values'])) {
|
||||
$data['data']['restrictions']['values'] = preg_replace('/\s+/', ' ', trim($r['values']));
|
||||
}
|
||||
}
|
||||
|
||||
$this->serializeCustomData($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function get_vars()
|
||||
{
|
||||
$vars = parent::get_vars();
|
||||
|
||||
$vars['body']['configurationTemplate'] = $this->getConfigurationTemplate($vars['body']['data']['type'] ?? null);
|
||||
$vars['body']['types'] = [];
|
||||
foreach ($this->transferLocator->getTransfers() as $type => $transfer) {
|
||||
$vars['body']['types'][$type] = $transfer::getName();
|
||||
}
|
||||
if ($this->getAction() == 'edit') {
|
||||
$vars['body']['configurationData'] = $this->transferLocator->getTransfer($vars['body']['data']['type'])->getConfigurationVariables();
|
||||
}
|
||||
if (findModule(\Modules::INVOICES)) {
|
||||
$vars['body']['invoices'] = sqlQueryBuilder()
|
||||
->select('id, name')
|
||||
->from('invoice_numbers')
|
||||
->execute()
|
||||
->fetchAllKeyValue();
|
||||
|
||||
$vars['body']['invoices'] = ['' => 'Žádná fakturační řada'] + $vars['body']['invoices'];
|
||||
}
|
||||
|
||||
$vars['body']['data']['configuration'] = json_decode($vars['body']['data']['configuration'] ?? '', true) ?: [];
|
||||
|
||||
$this->unserializeCustomData($vars['body']['data']);
|
||||
|
||||
return $vars;
|
||||
}
|
||||
|
||||
public function getData()
|
||||
{
|
||||
$data = parent::getData();
|
||||
|
||||
if (getVal('Submit')) {
|
||||
$data['active'] = $data['active'] === 'Y' ? 1 : 0;
|
||||
|
||||
if (!empty($data['configuration'])) {
|
||||
$data['configuration'] = $this->transferLocator->getTransfer($data['type'])
|
||||
->prepareConfigurationData($data['configuration']);
|
||||
}
|
||||
|
||||
$data['configuration'] = json_encode($data['configuration'] ?? []);
|
||||
|
||||
$this->serializeCustomData($data);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function getConfigurationTemplate(?string $type): ?string
|
||||
{
|
||||
$bundleFinder = ServiceContainer::getService(BundleFinder::class);
|
||||
|
||||
$template = 'dropshipment.configuration.'.$type.'.tpl';
|
||||
if (file_exists($bundleFinder->getBundlesPath('Admin/templates/window/')['DropshipBundle'].$template)) {
|
||||
return $template;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return Dropshipment::class;
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace KupShop\DropshipBundle\Admin\Tabs;
|
||||
|
||||
use KupShop\AdminBundle\Admin\WindowTab;
|
||||
|
||||
class DropshipmentRestrictionsTab extends WindowTab
|
||||
{
|
||||
protected $title = 'flapRestrictions';
|
||||
protected $template = 'window/dropshipment.restrictions.tpl';
|
||||
|
||||
public static function getTypes()
|
||||
{
|
||||
return [
|
||||
'Dropshipment' => 98,
|
||||
];
|
||||
}
|
||||
|
||||
public function isVisible()
|
||||
{
|
||||
return $this->getAction() === 'edit';
|
||||
}
|
||||
|
||||
public function getLabel()
|
||||
{
|
||||
return translate('restrictions', 'Dropshipment');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace KupShop\DropshipBundle\Admin\Tabs;
|
||||
|
||||
use KupShop\AdminBundle\Admin\WindowTab;
|
||||
use KupShop\DropshipBundle\Transfer\GenericTransfer;
|
||||
use Query\Operator;
|
||||
|
||||
class DropshipmentTransformationTab extends WindowTab
|
||||
{
|
||||
protected $title = 'flapTransformation';
|
||||
protected $template = 'window/dropshipment.transformation.tpl';
|
||||
|
||||
public static function getTypes()
|
||||
{
|
||||
return [
|
||||
'Dropshipment' => 99,
|
||||
];
|
||||
}
|
||||
|
||||
public function isVisible()
|
||||
{
|
||||
if ($id = getVal('ID')) {
|
||||
$type = sqlQueryBuilder()
|
||||
->select('type')
|
||||
->from('dropshipment')
|
||||
->where(Operator::equals(['id' => $id]))
|
||||
->execute()->fetchOne();
|
||||
|
||||
return $type === GenericTransfer::getType();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getLabel()
|
||||
{
|
||||
return translate('transformation', 'Dropshipment');
|
||||
}
|
||||
}
|
||||
109
bundles/KupShop/DropshipBundle/Admin/lang/czech/Dropshipment.php
Normal file
109
bundles/KupShop/DropshipBundle/Admin/lang/czech/Dropshipment.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
$txt_str['Dropshipment'] = [
|
||||
'menu' => 'Dropshipment',
|
||||
|
||||
'activityAdded' => 'Přidán nový dropshipment: %s',
|
||||
'activityEdited' => 'Upraven dropshipment: %s',
|
||||
|
||||
'titleAdd' => 'Nastavení dropshipmentu',
|
||||
'titleEdit' => 'Upravit nastavení dropshipmentu',
|
||||
|
||||
'toolbar_list' => 'Dropshipment',
|
||||
'toolbar_add' => 'Přidat',
|
||||
|
||||
'type' => 'Typ',
|
||||
'name' => 'Název',
|
||||
'active' => 'Aktivní',
|
||||
'lastSync' => 'Poslední synchronizace',
|
||||
|
||||
'generic' => 'Obecné',
|
||||
|
||||
'apiKey' => 'API klíč',
|
||||
'companyId' => 'ID firmy',
|
||||
'projectId' => 'ID projektu',
|
||||
|
||||
'flapDropshipment' => 'Dropshipment',
|
||||
'flapMall' => 'MALL',
|
||||
'flapExpando' => 'Expando',
|
||||
|
||||
'configuration' => 'Konfigurace',
|
||||
'configurationInfo' => 'Konfiguraci lze nastavovat až po uložení.',
|
||||
'sourceUrl' => 'Zdrojový XML soubor',
|
||||
'settings' => 'Nastavení',
|
||||
'orderLanguage' => 'Jazyk objednávky',
|
||||
'deliveries' => 'Dopravy',
|
||||
'deliveryType' => 'Způsob doručení',
|
||||
'payments' => 'Platby',
|
||||
'stores' => 'Prodejny',
|
||||
'addDeliveryMapping' => 'Přidat párování',
|
||||
'addMapping' => 'Přidat párování',
|
||||
'mallDeliveryId' => 'MALL ID',
|
||||
'delivery' => 'Doprava',
|
||||
'allCountries' => 'Všechny země',
|
||||
'country' => 'Země',
|
||||
'out' => 'Odesílat aktualizace stavů objednávek',
|
||||
'doNotStornoOrders' => 'Nestornovat objednávky v Channable',
|
||||
|
||||
'importStatuses' => 'Stavy k importu',
|
||||
'importStatusesInfo' => 'Vyberte stavy objednávek, které chcete importovat do e-shopu. Pokud ponecháte prázdné, tak se použije výchozí nastavení e-shopu, kdy se do e-shopu importují pouze objednávky ve stavu "Unshipped".',
|
||||
|
||||
'expandoOutTooltip' => 'Odešle do Expanda informaci o vyřízení objednávky společně s URL adresou pro sledování zásilky.',
|
||||
'channableOutTooltip' => 'Odešle do Channable informaci s číslem zásilky.',
|
||||
|
||||
'ordersUpdate' => 'Aktualizovat objednávky z Expanda',
|
||||
'ordersUpdateTooltip' => 'Aktualizace objednávek v e-shopu podle Expanda.',
|
||||
'ordersStatusUpdateShipped' => 'po expedici',
|
||||
'ordersStatusUpdateShippedTooltip' => 'Aktualizace objednávky po expedici kvůli případné úpravě DIČ a DPH.',
|
||||
'ordersStatusUpdateStorno' => 'storno',
|
||||
'ordersStatusUpdateStornoTooltip' => 'Pokud se objednávka v Expandu stornuje, tak se storno přenese na objednávku v e-shopu.',
|
||||
|
||||
'value' => 'Hodnota',
|
||||
'valueDeliveryInfo' => 'Do pole "Hodnota" vyplňte hodnotu, která se vyskytuje ve feedu pod elementem <DELIVERY>. Pokud necháte prázdno, tak se bude toto párování vztahovat an všechny hodnoty.',
|
||||
'valuePaymentInfo' => 'Do pole "Hodnota" vyplňte hodnotu, která se vyskytuje ve feedu pod elementem <PAYMENT>. Pokud necháte prázdno, tak se bude toto párování vztahovat an všechny hodnoty.',
|
||||
'selectDelivery' => 'Vyberte dopravu',
|
||||
'selectPayment' => 'Vyberte platbu',
|
||||
|
||||
'transformation' => 'Transformace',
|
||||
'updateTitle' => 'Aktualizace',
|
||||
'mappingTitle' => 'Mapování',
|
||||
|
||||
'convertToDefaultCurrency' => 'Převádět ceny do výchozí měny',
|
||||
'convertToDefaultCurrencyInfo' => 'Pokud je v transformaci specifikované měna (CURRENCY) a bude jiná, než je výchozí měna, tak se ceny automaticky převedou do výchozí měny. Zároveň se u cen nastaví výchozí DPH.',
|
||||
|
||||
'genericSourceInfo' => 'Validní XML příklad můžete stáhnout <a href="/admin/static/files/dropshipment_order.xml" target="_blank">zde</a>.
|
||||
Pokud vaše struktura neodpovídá, tak můžete použít transformaci, která vaše XML převede na vyžadovanou strukturu.',
|
||||
|
||||
'genericMappingInfo' => '<p>Mapovaní doprav a plateb, které přijdou ve feedu v elementech <strong>DELIVERY</strong> nebo
|
||||
<strong>PAYMENT</strong>. Do pole hodnota je potřeba vyplnit hodnotu, která se vyskytuje ve feedu, pak
|
||||
případně vybrat ještě zemi, která se vyskytuje v <strong>DELIVERY_COUNTRY</strong> a nakonec je potřeba
|
||||
zvolit dopravu, na kterou se má daná kombinace navázat na e-shopu.</p>
|
||||
|
||||
<p><strong>POZOR</strong>, že pro dané kombinace doprav a plateb musí existovat vytvořený způsob doručení.</p>',
|
||||
|
||||
'invoice_number' => 'Fakturační řada',
|
||||
|
||||
'groupName' => 'Název',
|
||||
|
||||
'ignoreOnMappingNotFound' => 'Ignorovat objednávku pokud není nalezeno mapování',
|
||||
'ignoreOnMappingNotFoundTooltip' => 'Pokud jsou nastaveny mapování pouze s filtrem a import narazí na objednávku, ke které se nepodaří dohledat žádné mapování, tak se objednávka bude ignorovat. Pokud je tato funkce vypnuta, tak se objednávka založí bez vazby na dopravu a platbu.',
|
||||
|
||||
'filter' => 'Filtr',
|
||||
'filterTag' => 'Název elementu',
|
||||
'filterValues' => 'Hodnoty',
|
||||
'filterTooltip' => 'Filtr, který určuje element a hodnotu v XML podle kterého se mapování přiřadí a aplikuje u dané objednávky. Např. název elementu `EXTERNAL/marketplace` a hodnota `kaufland` řekne, že se daná pravidla mapování mají aplikovat pouze pro objednávky s marketplace `kaufland`. Pokud necháte prázdné, tak se pravidla mapování aplikují na všechny objendávky.',
|
||||
|
||||
'restrictions' => 'Omezení',
|
||||
'tagValuesRestriction' => 'Omezení podle hodnot v tagu',
|
||||
'restrictionsTagName' => 'Tag',
|
||||
'restrictionsTagNameInfo' => 'Hodnota tagu je brána přímo z feedu. Ve značkách <tag>. Tag se bere pouze z první úrovně feedu.',
|
||||
'restrictionsValues' => 'Hodnoty',
|
||||
'restrictionsValuesInfo' => 'Hodnoty musí být odděleny čárkou, hodnota musí odpovídat.',
|
||||
'restrictionsValidationError' => 'Musí být vyplněny obě hodnoty omezení, Tag i Hodnoty.',
|
||||
|
||||
'statuses' => 'Stav objednávek pro odeslání informací o dopravě do Channable',
|
||||
'use_channable_order_no' => 'Přebírat číslo objednávky z channable',
|
||||
|
||||
'useBaselinkerIntegration' => 'Použít napojení ze strany Baselinkeru',
|
||||
'useBaselinkerIntegrationInfo' => 'Na straně e-shopu není potřeba žádné nastavování, protože je napojení nastaveno kompletně na straně Baselinkeru, který s e-shopem komunikuje pomocí API.',
|
||||
];
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
$txt_str['Dropshipment'] = [
|
||||
'menu' => 'Dropshipment',
|
||||
|
||||
'activityAdded' => 'Přidán nový dropshipment: %s',
|
||||
'activityEdited' => 'Upraven dropshipment: %s',
|
||||
|
||||
'titleAdd' => 'Nastavení dropshipmentu',
|
||||
'titleEdit' => 'Upravit nastavení dropshipmentu',
|
||||
|
||||
'toolbar_list' => 'Dropshipment',
|
||||
'toolbar_add' => 'Přidat',
|
||||
|
||||
'type' => 'Typ',
|
||||
'name' => 'Název',
|
||||
'active' => 'Active',
|
||||
'lastSync' => 'Poslední synchronizace',
|
||||
|
||||
'generic' => 'Obecné',
|
||||
|
||||
'apiKey' => 'API key',
|
||||
|
||||
'flapDropshipment' => 'Dropshipment',
|
||||
'flapMall' => 'MALL',
|
||||
'flapExpando' => 'Expando',
|
||||
|
||||
'configuration' => 'Konfigurace',
|
||||
'configurationInfo' => 'Konfiguraci lze nastavovat až po uložení.',
|
||||
'sourceUrl' => 'Zdrojový XML soubor',
|
||||
'settings' => 'Nastavení',
|
||||
'orderLanguage' => 'Jazyk objednávky',
|
||||
'deliveries' => 'Dopravy',
|
||||
'payments' => 'Platby',
|
||||
'stores' => 'Prodejny',
|
||||
'addDeliveryMapping' => 'Přidat párování',
|
||||
'addMapping' => 'Přidat párování',
|
||||
'mappingTitle' => 'Delivery type mapping',
|
||||
'mallDeliveryId' => 'MALL ID',
|
||||
'delivery' => 'Doprava',
|
||||
'allCountries' => 'Všechny země',
|
||||
'country' => 'Země',
|
||||
'out' => 'Send order status updates',
|
||||
'doNotStornoOrders' => 'Storno orders in Channable',
|
||||
|
||||
'expandoOutTooltip' => 'Odešle do Expanda informaci o vyřízení objednávky společně s URL adresou pro sledování zásilky.',
|
||||
|
||||
'ordersUpdate' => 'Update orders from Expando',
|
||||
'ordersUpdateTooltip' => 'Update orders in the e-shop by Expando.',
|
||||
'ordersStatusUpdateShipped' => 'after shipping',
|
||||
'ordersStatusUpdateShippedTooltip' => 'Update orders after shipping for possible VAT and VAT adjustment',
|
||||
'ordersStatusUpdateStorno' => 'cancelled',
|
||||
'ordersStatusUpdateStornoTooltip' => 'If the order is cancelled in Expando, the cancellation is transferred to the order in the e-shop',
|
||||
|
||||
'value' => 'Hodnota',
|
||||
'valueDeliveryInfo' => 'Do pole "Hodnota" vyplňte hodnotu, která se vyskytuje ve feedu pod elementem <DELIVERY>. Pokud necháte prázdno, tak se bude toto párování vztahovat an všechny hodnoty.',
|
||||
'valuePaymentInfo' => 'Do pole "Hodnota" vyplňte hodnotu, která se vyskytuje ve feedu pod elementem <PAYMENT>. Pokud necháte prázdno, tak se bude toto párování vztahovat an všechny hodnoty.',
|
||||
'selectDelivery' => 'Vyberte dopravu',
|
||||
'selectPayment' => 'Vyberte platbu',
|
||||
|
||||
'transformation' => 'Transformace',
|
||||
|
||||
'convertToDefaultCurrency' => 'Převádět ceny do výchozí měny',
|
||||
'convertToDefaultCurrencyInfo' => 'Pokud je v transformaci specifikované měna (CURRENCY) a bude jiná, než je výchozí měna, tak se ceny automaticky převedou do výchozí měny.',
|
||||
|
||||
'genericSourceInfo' => 'Validní XML příklad můžete stáhnout <a href="/admin/static/files/dropshipment_order.xml" target="_blank">zde</a>.
|
||||
Pokud vaše struktura neodpovídá, tak můžete použít transformaci, která vaše XML převede na vyžadovanou strukturu.',
|
||||
|
||||
'genericMappingInfo' => '<p>Mapovaní doprav a plateb, které přijdou ve feedu v elementech <strong>DELIVERY</strong> nebo
|
||||
<strong>PAYMENT</strong>. Do pole hodnota je potřeba vyplnit hodnotu, která se vyskytuje ve feedu, pak
|
||||
případně vybrat ještě zemi, která se vyskytuje v <strong>DELIVERY_COUNTRY</strong> a nakonec je potřeba
|
||||
zvolit dopravu, na kterou se má daná kombinace navázat na e-shopu.</p>
|
||||
|
||||
<p><strong>POZOR</strong>, že pro dané kombinace doprav a plateb musí existovat vytvořený způsob doručení.</p>',
|
||||
|
||||
'invoice_number' => 'Invoice number',
|
||||
|
||||
'restrictions' => 'Restrictions',
|
||||
'restrictionsTagName' => 'Tag',
|
||||
'restrictionsTagNameInfo' => 'The tag value is taken directly from the feed. In <tag> tags. The tag is taken only from the first level of the feed.',
|
||||
'restrictionsValues' => 'Values',
|
||||
'restrictionsValuesInfo' => 'Values must be separated by a comma, the value must match.',
|
||||
'restrictionsValidationError' => 'Both Tag and Value constraint values must be filled in.',
|
||||
|
||||
'companyId' => 'Company ID',
|
||||
'projectId' => 'Project ID',
|
||||
'statuses' => 'Send tracking info to channable if order has this status',
|
||||
'use_channable_order_no' => 'Use Channable order number',
|
||||
'tagValuesRestriction' => 'Restrictions by tag values',
|
||||
];
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace KupShop\DropshipBundle\Admin\lists;
|
||||
|
||||
use KupShop\AdminBundle\AdminList\BaseList;
|
||||
use KupShop\DropshipBundle\Util\TransferLocator;
|
||||
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
|
||||
|
||||
class DropshipmentList extends BaseList
|
||||
{
|
||||
protected $tableDef = [
|
||||
'id' => 'd.id',
|
||||
'fields' => [
|
||||
'ID' => ['field' => 'd.id'],
|
||||
'name' => ['translate' => true, 'field' => 'd.name'],
|
||||
'type' => ['translate' => true, 'field' => 'd.type', 'render' => 'renderDropshipmentType'],
|
||||
'active' => ['translate' => true, 'field' => 'd.active', 'render' => 'renderBoolean'],
|
||||
'lastSync' => ['translate' => true, 'field' => 'd.last_sync', 'render' => 'renderDateTime'],
|
||||
],
|
||||
];
|
||||
|
||||
protected $tableName = 'dropshipment';
|
||||
protected ?string $tableAlias = 'd';
|
||||
|
||||
protected TransferLocator $transferLocator;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->transferLocator = ServiceContainer::getService(TransferLocator::class);
|
||||
}
|
||||
|
||||
public function renderDropshipmentType(array $values): string
|
||||
{
|
||||
$transfer = $this->transferLocator->getTransfer($values['type']);
|
||||
|
||||
return $transfer::getName();
|
||||
}
|
||||
}
|
||||
|
||||
return DropshipmentList::class;
|
||||
@@ -0,0 +1,4 @@
|
||||
{extends "[AdminBundle]actions/baseAction.tpl"}
|
||||
|
||||
{block actionContent}
|
||||
{/block}
|
||||
@@ -0,0 +1,26 @@
|
||||
{extends "[DropshipBundle]window/dropshipment.configuration.generic.tpl"}
|
||||
|
||||
{block 'configuration'}
|
||||
<div class="form-group">
|
||||
<div class="col-md-2 control-label">
|
||||
<label>{'useBaselinkerIntegration'|translate}</label>
|
||||
<a class="help-tip" data-toggle="tooltip" title=""
|
||||
data-original-title="{'useBaselinkerIntegrationInfo'|translate}">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
{print_toggle nameRaw="data[configuration][use_baselinker_integration]" value=$body.data.configuration.use_baselinker_integration}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="baselinker-deprecated-config">
|
||||
{$smarty.block.parent}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$('input[name="data[configuration][use_baselinker_integration]"]').change(function() {
|
||||
$('#baselinker-deprecated-config').toggle(!$(this).is(':checked'));
|
||||
}).change();
|
||||
</script>
|
||||
{/block}
|
||||
@@ -0,0 +1,286 @@
|
||||
<div id="configurationChannable" class="tab-pane fade in boxFlex">
|
||||
<div class="form-group form-group-flex">
|
||||
<div class="col-md-2 control-label">
|
||||
<label>{'apiKey'|translate}</label>
|
||||
</div>
|
||||
<div class="col-md-10">
|
||||
<input class="form-control input-sm" type="text" name="data[source_url]"
|
||||
value="{$body.data.source_url}" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group form-group-flex">
|
||||
<div class="col-md-2 control-label">
|
||||
<label>{'companyId'|translate}</label>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<input class="form-control input-sm" type="text" name="data[data][company_id]"
|
||||
value="{$body.data.data.company_id}" required>
|
||||
</div>
|
||||
<div class="col-md-2 control-label">
|
||||
<label>{'projectId'|translate}</label>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<input class="form-control input-sm" type="text" name="data[data][project_id]"
|
||||
value="{$body.data.data.project_id}" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{get_contexts language=1 assign='contexts'}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h1 class="h4 main-panel-title">{'settings'|translate}</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3 control-label">
|
||||
<label>
|
||||
{'out'|translate}
|
||||
<a class="help-tip" data-toggle="tooltip" title="{'channableOutTooltip'|translate}">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
{print_toggle nameRaw="data[configuration][out]" value=$body.data.configuration.out}
|
||||
</div>
|
||||
|
||||
<div class="col-md-3 control-label">
|
||||
<label>{'doNotStornoOrders'|translate}</label>
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
{print_toggle nameRaw="data[configuration][do_not_storno_orders]" value=$body.data.configuration.do_not_storno_orders}
|
||||
</div>
|
||||
|
||||
<div class="col-md-3 control-label">
|
||||
<label>
|
||||
{'use_channable_order_no'|translate}
|
||||
<a class="help-tip" data-toggle="tooltip" title="Přebírat číslo objednávky z channable">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
{print_toggle nameRaw="data[configuration][use_marketplace_order_no]" value=$body.data.configuration.use_marketplace_order_no}
|
||||
</div>
|
||||
|
||||
<div class="wpj-form-group col-md-12">
|
||||
<label>{'statuses'|translate}</label>
|
||||
<div class="input-group col-md-2">
|
||||
{print_select name="data[configuration][statuses]" var=$cfg.Order.Status.global selected=$body.data.configuration.statuses}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h1 class="h4 main-panel-title">{'mappingTitle'|translate}</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row bottom-space">
|
||||
<div class="col-md-3">
|
||||
<a href="#" data-marketplace="add" class="btn btn-success btn-block"><span
|
||||
class="glyphicon glyphicon-plus"></span> Přidat marketplace</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="marketplaces" class="panel-group panel-group-lists ui-sortable">
|
||||
{foreach array_merge([[]], $body.data.configuration.marketplaces|default:[]) as $key => $marketplace}
|
||||
<div {if $key == 0}data-marketplace="template" style="display: none" {else}data-marketplace="item"
|
||||
style="margin-bottom: 20px"{/if}>
|
||||
<div class="panel">
|
||||
<div class="panel-heading">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<input name="data[configuration][marketplaces][{$key}][name]" class="form-control input-sm"
|
||||
type="text"
|
||||
value="{$marketplace.name}"
|
||||
placeholder="{if $key == 0}Marketplace...{else}Všechny marketplace{/if}">
|
||||
<a class="help-tip" data-toggle="tooltip" title=""
|
||||
data-original-title="Název marketplacu. Pokud nevyplníte, tak se bude nastavení vztahovat na všechny marketplacy.">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<a class="btn-sm btn btn-danger pull-right" data-marketplace="delete">
|
||||
<span class="glyphicon glyphicon-remove"></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-group panel-group-lists panel">
|
||||
<div class="panel-body border"
|
||||
style="border:1px solid rgba(0, 0, 0, 0.1);border-top:none;border-radius:3px;">
|
||||
|
||||
{ifmodule TRANSLATIONS}
|
||||
<div class="row">
|
||||
<div class="col-md-1 control-label">
|
||||
<label>{'orderLanguage'|translate}</label>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select name="data[configuration][marketplaces][{$key}][settings][id_language]" class="selecter">
|
||||
<option value="">-- výchozí jazyk --</option>
|
||||
{foreach $contexts.language->getSupported() as $lang}
|
||||
<option value="{$lang->getId()}"
|
||||
{if $marketplace.settings.id_language == $lang->getId()}selected{/if}>{$lang->getName()}</option>
|
||||
{/foreach}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{/ifmodule}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h1 class="h6"><strong>{'deliveries'|translate}</strong></h1>
|
||||
<hr>
|
||||
</div>
|
||||
</div>
|
||||
<div id="deliveryForm_{$key}">
|
||||
{$deliveries = $marketplace.deliveries}
|
||||
{if empty($deliveries)}
|
||||
{$deliveries = []}
|
||||
{/if}
|
||||
|
||||
{foreach array_merge([[]], $deliveries) as $dKey => $delivery}
|
||||
<div id="deliveryRow_{$dKey}" {if $dKey == 0}data-form-new="" style="display: none"
|
||||
{else}data-form-item=""{/if}>
|
||||
<div class="row bottom-space">
|
||||
<div class="col-md-1 control-label">
|
||||
<label>{'country'|translate}</label>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select
|
||||
name="data[configuration][marketplaces][{$key}][deliveries][{$dKey}][country]"
|
||||
class="selecter"
|
||||
data-autocomplete="countries" data-preload="countries">
|
||||
<option value="">{'allCountries'|translate}</option>
|
||||
{if $delivery.country}
|
||||
<option value="{$delivery.country}" selected>{$delivery.country}</option>
|
||||
{/if}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-1 control-label">
|
||||
<label>{'delivery'|translate}</label>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select
|
||||
name="data[configuration][marketplaces][{$key}][deliveries][{$dKey}][id_delivery]"
|
||||
class="selecter"
|
||||
data-autocomplete="deliveries" data-preload="deliveries">
|
||||
{if $delivery.id_delivery}
|
||||
<option value="{$delivery.id_delivery}"
|
||||
selected>{$delivery.id_delivery}</option>
|
||||
{/if}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
<a class="btn-sm btn btn-danger" data-form-delete>
|
||||
<input class="hidden" type="checkbox"
|
||||
name="data[configuration][marketplaces][{$key}][deliveries][{$dKey}][delete]"/>
|
||||
<span class="glyphicon glyphicon-remove"></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/foreach}
|
||||
|
||||
<div class="row bottom-space">
|
||||
<div class="col-md-3">
|
||||
<a href="#" data-form-add="">
|
||||
<span class="glyphicon glyphicon-plus"></span> Přidat párování
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{if $key != 0}
|
||||
<script>
|
||||
initForm({
|
||||
selector: '#deliveryForm_{$key}',
|
||||
beforeAdd: function (original) {
|
||||
console.log("original: ", original);
|
||||
var $addedItem = original();
|
||||
window.preloadAutocompletes($addedItem);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h1 class="h6"><strong>{'payments'|translate}</strong></h1>
|
||||
<hr>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row bottom-space">
|
||||
<div class="col-md-1 control-label">
|
||||
<label>Platba</label>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<select name="data[configuration][marketplaces][{$key}][payments][default]" class="selecter"
|
||||
data-autocomplete="payments" data-preload="payments">
|
||||
{if $marketplace.payments.default}
|
||||
<option value="{$marketplace.payments.default}"
|
||||
selected>{$marketplace.payments.default}</option>
|
||||
{/if}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/foreach}
|
||||
</div>
|
||||
|
||||
<script type="application/javascript">
|
||||
var index = 1;
|
||||
var $marketplaceWrapper = $('#marketplaces');
|
||||
|
||||
var $template = $('[data-marketplace="template"]').clone();
|
||||
$('[data-marketplace="template"]').remove();
|
||||
|
||||
$('[data-marketplace="add"]').on('click', function (e) {
|
||||
console.log('hello')
|
||||
var $item = $template.clone();
|
||||
$item.removeAttr('style');
|
||||
$item.removeAttr('data-marketplace');
|
||||
|
||||
$item.attr('data-marketplace', 'item')
|
||||
|
||||
$item.css('margin-bottom', '20px');
|
||||
|
||||
replaceAttribute($item.find('input, select'), 'name', '[marketplaces][0]', '[marketplaces][-' + index + ']');
|
||||
index++;
|
||||
|
||||
$item.prependTo($marketplaceWrapper);
|
||||
window.preloadAutocompletes($item);
|
||||
|
||||
initForm({
|
||||
selector: $item,
|
||||
beforeAdd: function (original) {
|
||||
var $addedItem = original();
|
||||
window.preloadAutocompletes($addedItem);
|
||||
}
|
||||
});
|
||||
|
||||
e.preventDefault();
|
||||
return false;
|
||||
});
|
||||
|
||||
$marketplaceWrapper.on('click', '[data-marketplace="delete"]', function (e) {
|
||||
$(this).parents('[data-marketplace="item"]').remove();
|
||||
|
||||
e.preventDefault();
|
||||
return false;
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,324 @@
|
||||
<div id="configurationExpando">
|
||||
<div class="form-group form-group-flex">
|
||||
<div class="col-md-2 control-label">
|
||||
<label>{'sourceUrl'|translate}</label>
|
||||
</div>
|
||||
<div class="col-md-10">
|
||||
<input class="form-control input-sm" type="text" name="data[source_url]"
|
||||
value="{$body.data.source_url}" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-md-2 control-label">
|
||||
<label>{'convertToDefaultCurrency'|translate}</label>
|
||||
<a class="help-tip"
|
||||
data-toggle="tooltip" title=""
|
||||
data-original-title="{'convertToDefaultCurrencyInfo'|translate}">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
{print_toggle nameRaw="data[configuration][prices_to_default_currency]" value=$body.data.configuration.prices_to_default_currency}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-md-2 control-label">
|
||||
<label>
|
||||
{'importStatuses'|translate}
|
||||
<a class="help-tip"
|
||||
data-toggle="tooltip" title=""
|
||||
data-original-title="{'importStatusesInfo'|translate}">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-md-10">
|
||||
<select class="selecter" name="data[configuration][import_statuses][]" multiple>
|
||||
<option value="*" {'*'|selected:$body.data.configuration.import_statuses}>Všechny stavy</option>
|
||||
<option value="pending" {'pending'|selected:$body.data.configuration.import_statuses}>Pending</option>
|
||||
<option value="unshipped" {'unshipped'|selected:$body.data.configuration.import_statuses}>Unshipped</option>
|
||||
<option value="shipped" {'shipped'|selected:$body.data.configuration.import_statuses}>Shipped</option>
|
||||
<option value="canceled" {'canceled'|selected:$body.data.configuration.import_statuses}>Canceled</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h1 class="h4 main-panel-title">{'updateTitle'|translate}</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-md-2 control-label">
|
||||
<label>{'apiKey'|translate}</label>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<input type="text" class="form-control input-sm" name="data[configuration][api_key]"
|
||||
value="{$body.data.configuration.api_key}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-md-2 control-label">
|
||||
<label>
|
||||
{'out'|translate}
|
||||
<a class="help-tip" data-toggle="tooltip" title="{'expandoOutTooltip'|translate}">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
{print_toggle nameRaw="data[configuration][out]" value=$body.data.configuration.out}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-md-2 control-label">
|
||||
<label>
|
||||
{'ordersUpdate'|translate}
|
||||
<a class="help-tip" data-toggle="tooltip" title="{'ordersUpdateTooltip'|translate}">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="checkbox pull-left">
|
||||
<input type="checkbox" id="update-shipped" name="data[configuration][update][shipped]" value="shipped"
|
||||
class="check"
|
||||
{if isset($body.data.configuration.update['shipped'])}checked{/if}>
|
||||
<label for="update-shipped">
|
||||
{'ordersStatusUpdateShipped'|translate}
|
||||
<a class="help-tip" data-toggle="tooltip" title="{'ordersStatusUpdateShippedTooltip'|translate}">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</label>
|
||||
</div>
|
||||
<div class="checkbox pull-left">
|
||||
<input type="checkbox" id="update-canceled" name="data[configuration][update][canceled]" value="canceled"
|
||||
class="check"
|
||||
{if isset($body.data.configuration.update['canceled'])}checked{/if}>
|
||||
<label for="update-canceled">
|
||||
{'ordersStatusUpdateStorno'|translate}
|
||||
<a class="help-tip" data-toggle="tooltip" title="{'ordersStatusUpdateStornoTooltip'|translate}">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h1 class="h4 main-panel-title">{'mappingTitle'|translate}</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row bottom-space">
|
||||
<div class="col-md-3">
|
||||
<a href="#" data-marketplace="add" class="btn btn-success btn-block"><span
|
||||
class="glyphicon glyphicon-plus"></span> Přidat marketplace</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{get_contexts language=1 assign='contexts'}
|
||||
|
||||
<div id="marketplaces" class="panel-group panel-group-lists ui-sortable">
|
||||
{foreach array_merge([[]], $body.data.configuration.marketplaces|default:[]) as $key => $marketplace}
|
||||
<div {if $key == 0}data-marketplace="template" style="display: none" {else}data-marketplace="item"
|
||||
style="margin-bottom: 20px"{/if}>
|
||||
<div class="panel">
|
||||
<div class="panel-heading">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<input name="data[configuration][marketplaces][{$key}][name]" class="form-control input-sm"
|
||||
type="text"
|
||||
value="{$marketplace.name}"
|
||||
placeholder="{if $key == 0}Marketplace...{else}Všechny marketplace{/if}">
|
||||
<a class="help-tip" data-toggle="tooltip" title=""
|
||||
data-original-title="Název marketplacu, který se nachází ve feedu v tagu <marketplace>. Pokud nevyplníte, tak se bude nastavení vztahovat na všechny marketplacy.">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<a class="btn-sm btn btn-danger pull-right" data-marketplace="delete">
|
||||
<span class="glyphicon glyphicon-remove"></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-group panel-group-lists panel">
|
||||
<div class="panel-body border"
|
||||
style="border:1px solid rgba(0, 0, 0, 0.1);border-top:none;border-radius:3px;">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h1 class="h6"><strong>{'settings'|translate}</strong></h1>
|
||||
<hr>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{ifmodule TRANSLATIONS}
|
||||
<div class="row">
|
||||
<div class="col-md-1 control-label">
|
||||
<label>{'orderLanguage'|translate}</label>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select name="data[configuration][marketplaces][{$key}][settings][id_language]" class="selecter">
|
||||
<option value="">-- výchozí jazyk --</option>
|
||||
{foreach $contexts.language->getAll() as $lang}
|
||||
<option value="{$lang->getId()}" {if $marketplace.settings.id_language == $lang->getId()}selected{/if}>{$lang->getName()}</option>
|
||||
{/foreach}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{/ifmodule}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h1 class="h6"><strong>{'deliveries'|translate}</strong></h1>
|
||||
<hr>
|
||||
</div>
|
||||
</div>
|
||||
<div id="deliveryForm_{$key}">
|
||||
{$deliveries = $marketplace.deliveries}
|
||||
{if empty($deliveries)}
|
||||
{$deliveries = []}
|
||||
{/if}
|
||||
|
||||
{foreach array_merge([[]], $deliveries) as $dKey => $delivery}
|
||||
<div id="deliveryRow_{$dKey}" {if $dKey == 0}data-form-new="" style="display: none"
|
||||
{else}data-form-item=""{/if}>
|
||||
<div class="row bottom-space">
|
||||
<div class="col-md-1 control-label">
|
||||
<label>{'country'|translate}</label>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select
|
||||
name="data[configuration][marketplaces][{$key}][deliveries][{$dKey}][country]"
|
||||
class="selecter"
|
||||
data-autocomplete="countries" data-preload="countries">
|
||||
<option value="">{'allCountries'|translate}</option>
|
||||
{if $delivery.country}
|
||||
<option value="{$delivery.country}" selected>{$delivery.country}</option>
|
||||
{/if}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-1 control-label">
|
||||
<label>{'delivery'|translate}</label>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select
|
||||
name="data[configuration][marketplaces][{$key}][deliveries][{$dKey}][id_delivery]"
|
||||
class="selecter"
|
||||
data-autocomplete="deliveries" data-preload="deliveries">
|
||||
{if $delivery.id_delivery}
|
||||
<option value="{$delivery.id_delivery}"
|
||||
selected>{$delivery.id_delivery}</option>
|
||||
{/if}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
<a class="btn-sm btn btn-danger" data-form-delete>
|
||||
<input class="hidden" type="checkbox"
|
||||
name="data[configuration][marketplaces][{$key}][deliveries][{$dKey}][delete]"/>
|
||||
<span class="glyphicon glyphicon-remove"></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/foreach}
|
||||
|
||||
<div class="row bottom-space">
|
||||
<div class="col-md-3">
|
||||
<a href="#" data-form-add="">
|
||||
<span class="glyphicon glyphicon-plus"></span> Přidat párování
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{if $key != 0}
|
||||
<script>
|
||||
initForm({
|
||||
selector: '#deliveryForm_{$key}',
|
||||
beforeAdd: function (original) {
|
||||
var $addedItem = original();
|
||||
window.preloadAutocompletes($addedItem);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h1 class="h6"><strong>{'payments'|translate}</strong></h1>
|
||||
<hr>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row bottom-space">
|
||||
<div class="col-md-1 control-label">
|
||||
<label>Platba</label>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<select name="data[configuration][marketplaces][{$key}][payments][default]" class="selecter"
|
||||
data-autocomplete="payments" data-preload="payments">
|
||||
{if $marketplace.payments.default}
|
||||
<option value="{$marketplace.payments.default}"
|
||||
selected>{$marketplace.payments.default}</option>
|
||||
{/if}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/foreach}
|
||||
</div>
|
||||
|
||||
<script type="application/javascript">
|
||||
var index = 1;
|
||||
var $marketplaceWrapper = $('#marketplaces');
|
||||
|
||||
var $template = $('[data-marketplace="template"]').clone();
|
||||
$('[data-marketplace="template"]').remove();
|
||||
|
||||
$('[data-marketplace="add"]').click(function (e) {
|
||||
var $item = $template.clone();
|
||||
$item.removeAttr('style');
|
||||
$item.removeAttr('data-marketplace');
|
||||
|
||||
$item.attr('data-marketplace', 'item')
|
||||
|
||||
$item.css('margin-bottom', '20px');
|
||||
|
||||
replaceAttribute($item.find('input, select'), 'name', '[marketplaces][0]', '[marketplaces][-' + index + ']');
|
||||
index++;
|
||||
|
||||
$item.prependTo($marketplaceWrapper);
|
||||
window.preloadAutocompletes($item);
|
||||
|
||||
initForm({
|
||||
selector: $item,
|
||||
beforeAdd: function (original) {
|
||||
var $addedItem = original();
|
||||
window.preloadAutocompletes($addedItem);
|
||||
}
|
||||
});
|
||||
|
||||
e.preventDefault();
|
||||
return false;
|
||||
});
|
||||
|
||||
$marketplaceWrapper.on('click', '[data-marketplace="delete"]', function (e) {
|
||||
$(this).parents('[data-marketplace="item"]').remove();
|
||||
|
||||
e.preventDefault();
|
||||
return false;
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
@@ -0,0 +1,318 @@
|
||||
{block 'configuration'}
|
||||
<div class="form-group form-group-flex">
|
||||
<div class="col-md-2 control-label">
|
||||
<label>{'sourceUrl'|translate}</label>
|
||||
</div>
|
||||
<div class="col-md-10">
|
||||
<input class="form-control input-sm" type="text" name="data[source_url]"
|
||||
value="{$body.data.source_url}" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="configurationGeneric" class="tab-pane fade in boxFlex">
|
||||
<div class="form-group">
|
||||
<div class="col-md-2 control-label">
|
||||
<label>{'convertToDefaultCurrency'|translate}</label>
|
||||
<a class="help-tip" data-toggle="tooltip" title=""
|
||||
data-original-title="{'convertToDefaultCurrencyInfo'|translate}">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
{print_toggle nameRaw="data[configuration][prices_to_default_currency]" value=$body.data.configuration.prices_to_default_currency}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-md-2 control-label">
|
||||
<label>{'ignoreOnMappingNotFound'|translate}</label>
|
||||
<a class="help-tip" data-toggle="tooltip" title=""
|
||||
data-original-title="{'ignoreOnMappingNotFoundTooltip'|translate}">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
{print_toggle nameRaw="data[configuration][ignore_on_mapping_not_found]" value=$body.data.configuration.ignore_on_mapping_not_found}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h1 class="h4 main-panel-title">{'mappingTitle'|translate}</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="infobox">
|
||||
{'genericMappingInfo'|translate nofilter}
|
||||
</div>
|
||||
|
||||
{get_contexts language=1 assign='contexts'}
|
||||
|
||||
<div class="row bottom-space">
|
||||
<div class="col-md-3">
|
||||
<a href="#" data-mapping="add" class="btn btn-success btn-block"><span
|
||||
class="glyphicon glyphicon-plus"></span> Přidat mapování</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="mapping-groups" class="panel-group panel-group-lists ui-sortable">
|
||||
{foreach array_merge([[]], $body.data.configuration.mappingGroups|default:[]) as $key => $group}
|
||||
<div {if $key == 0}data-mapping="template" style="display: none" {else}data-mapping="item" style="margin-bottom: 20px"{/if}>
|
||||
<div class="panel">
|
||||
<div class="panel-heading">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<a class="btn-sm btn btn-danger pull-right" data-mapping="delete">
|
||||
<span class="glyphicon glyphicon-remove"></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-group panel-group-lists panel">
|
||||
<div class="panel-body border" style="border:1px solid rgba(0, 0, 0, 0.1);border-top:none;border-radius:3px;">
|
||||
<div class="row bottom-space">
|
||||
<div class="col-md-1 control-label">
|
||||
<label>{'groupName'|translate}</label>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<input class="form-control input-sm" type="text"
|
||||
name="data[configuration][mappingGroups][{$key}][name]"
|
||||
value="{$group.name}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h1 class="h6"><strong>{'settings'|translate}</strong></h1>
|
||||
<hr>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row bottom-space">
|
||||
<div class="col-md-1 control-label">
|
||||
<label>{'filter'|translate}</label>
|
||||
<a class="help-tip" data-toggle="tooltip" title=""
|
||||
data-original-title="{'filterTooltip'|translate}">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<input class="form-control input-sm" type="text"
|
||||
name="data[configuration][mappingGroups][{$key}][filter][tag]"
|
||||
value="{$group.filter.tag}" placeholder="{'filterTag'|translate}">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<input class="form-control input-sm" type="text"ß
|
||||
name="data[configuration][mappingGroups][{$key}][filter][value]"
|
||||
value="{$group.filter.value}" placeholder="{'filterValues'|translate}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{ifmodule TRANSLATIONS}
|
||||
{if empty($group.settings.id_language)}
|
||||
{$group.settings.id_language = $contexts.language->getDefaultId()}
|
||||
{/if}
|
||||
<div class="row">
|
||||
<div class="col-md-1 control-label">
|
||||
<label>{'orderLanguage'|translate}</label>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select name="data[configuration][mappingGroups][{$key}][settings][id_language]" class="selecter"
|
||||
data-autocomplete="languages" data-preload="languages">
|
||||
<option value="{$group.settings.id_language}" selected>{$group.settings.id_language}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{/ifmodule}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h1 class="h6"><strong>{'deliveries'|translate}</strong></h1>
|
||||
<hr>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{* Mapovani doprav pro danou skupinu *}
|
||||
<div data-mapping-form="deliveryForm_{$key}">
|
||||
{$deliveries = $group.deliveries}
|
||||
{if empty($deliveries)}
|
||||
{$deliveries = []}
|
||||
{/if}
|
||||
|
||||
{foreach array_merge([[]], $deliveries) as $dKey => $delivery}
|
||||
<div id="deliveryRow_{$dKey}" {if $dKey == 0}data-form-new="" style="display: none" {else}data-form-item=""{/if}>
|
||||
<div class="row bottom-space">
|
||||
<div class="col-md-4 col-md-offset-1">
|
||||
<input class="form-control input-sm" type="text"
|
||||
name="data[configuration][mappingGroups][{$key}][deliveries][{$dKey}][value]"
|
||||
value="{$delivery.value}" placeholder="{'value'|translate}">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select name="data[configuration][mappingGroups][{$key}][deliveries][{$dKey}][country]" class="selecter"
|
||||
data-autocomplete="countries" data-preload="countries">
|
||||
<option value="">{'allCountries'|translate}</option>
|
||||
{if $delivery.country}
|
||||
<option value="{$delivery.country}" selected>{$delivery.country}</option>
|
||||
{/if}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select name="data[configuration][mappingGroups][{$key}][deliveries][{$dKey}][id_delivery]" class="selecter"
|
||||
data-autocomplete="deliveries" data-preload="deliveries" {'selectDelivery'|translate}>
|
||||
{if $delivery.id_delivery}
|
||||
<option value="{$delivery.id_delivery}" selected>{$delivery.id_delivery}</option>
|
||||
{/if}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
<a class="btn-sm btn btn-danger" data-form-delete>
|
||||
<input class="hidden" type="checkbox" name="data[configuration][mappingGroups][{$key}][deliveries][{$dKey}][delete]"/>
|
||||
<span class="glyphicon glyphicon-remove"></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/foreach}
|
||||
|
||||
<div class="row bottom-space">
|
||||
<div class="col-md-3">
|
||||
<a href="#" data-form-add="">
|
||||
<span class="glyphicon glyphicon-plus"></span> Přidat párování
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{if $key != 0}
|
||||
<script>
|
||||
initForm({
|
||||
selector: '[data-mapping-form="deliveryForm_{$key}"]',
|
||||
beforeAdd: function (original) {
|
||||
var $addedItem = original();
|
||||
window.preloadAutocompletes($addedItem);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h1 class="h6"><strong>{'payments'|translate}</strong></h1>
|
||||
<hr>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{* Mapovani plateb pro danou skupinu *}
|
||||
<div data-mapping-form="paymentForm_{$key}">
|
||||
{$payments = $group.payments}
|
||||
{if empty($payments)}
|
||||
{$payments = []}
|
||||
{/if}
|
||||
|
||||
{foreach array_merge([[]], $payments) as $pKey => $payment}
|
||||
<div id="paymentRow_{$pKey}" {if $pKey == 0}data-form-new="" style="display: none" {else}data-form-item=""{/if}>
|
||||
<div class="row bottom-space">
|
||||
<div class="col-md-4 col-md-offset-1">
|
||||
<input class="form-control input-sm" type="text"
|
||||
name="data[configuration][mappingGroups][{$key}][payments][{$pKey}][value]"
|
||||
value="{$payment.value}" placeholder="{'value'|translate}">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select name="data[configuration][mappingGroups][{$key}][payments][{$pKey}][country]" class="selecter"
|
||||
data-autocomplete="countries" data-preload="countries">
|
||||
<option value="">{'allCountries'|translate}</option>
|
||||
{if $payment.country}
|
||||
<option value="{$payment.country}" selected>{$payment.country}</option>
|
||||
{/if}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select name="data[configuration][mappingGroups][{$key}][payments][{$pKey}][id_payment]" class="selecter"
|
||||
data-autocomplete="payments" data-preload="payments" {'selectDelivery'|translate}>
|
||||
{if $payment.id_payment}
|
||||
<option value="{$payment.id_payment}" selected>{$payment.id_payment}</option>
|
||||
{/if}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
<a class="btn-sm btn btn-danger" data-form-delete>
|
||||
<input class="hidden" type="checkbox" name="data[configuration][mappingGroups][{$key}][payments][{$pKey}][delete]"/>
|
||||
<span class="glyphicon glyphicon-remove"></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/foreach}
|
||||
|
||||
<div class="row bottom-space">
|
||||
<div class="col-md-3">
|
||||
<a href="#" data-form-add="">
|
||||
<span class="glyphicon glyphicon-plus"></span> Přidat párování
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{if $key != 0}
|
||||
<script>
|
||||
initForm({
|
||||
selector: '[data-mapping-form="paymentForm_{$key}"]',
|
||||
beforeAdd: function (original) {
|
||||
var $addedItem = original();
|
||||
window.preloadAutocompletes($addedItem);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/foreach}
|
||||
</div>
|
||||
|
||||
<script type="application/javascript">
|
||||
var index = 1;
|
||||
var $groupWrapper = $('#mapping-groups');
|
||||
|
||||
var $template = $('[data-mapping="template"]').clone();
|
||||
$('[data-mapping="template"]').remove();
|
||||
|
||||
$('[data-mapping="add"]').click(function (e) {
|
||||
var $item = $template.clone();
|
||||
$item.removeAttr('style');
|
||||
$item.removeAttr('data-mapping');
|
||||
|
||||
$item.attr('data-mapping', 'item')
|
||||
$item.css('margin-bottom', '20px');
|
||||
|
||||
replaceAttribute($item.find('input, select'), 'name', '[mappingGroups][0]', '[mappingGroups][-' + index + ']');
|
||||
index++;
|
||||
|
||||
$item.prependTo($groupWrapper);
|
||||
window.preloadAutocompletes($item);
|
||||
|
||||
$item.find('[data-mapping-form]').each(function () {
|
||||
initForm({
|
||||
selector: $(this),
|
||||
beforeAdd: function (original) {
|
||||
var $addedItem = original();
|
||||
window.preloadAutocompletes($addedItem);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
e.preventDefault();
|
||||
return false;
|
||||
});
|
||||
|
||||
$groupWrapper.on('click', '[data-mapping="delete"]', function (e) {
|
||||
$(this).parents('[data-mapping="item"]').remove();
|
||||
|
||||
e.preventDefault();
|
||||
return false;
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
{/block}
|
||||
@@ -0,0 +1,101 @@
|
||||
<div id="configurationHeureka" class="tab-pane fade in boxFlex">
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-md-2 control-label">
|
||||
<label>Client ID</label>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<input type="text" class="form-control input-sm" name="data[configuration][api_key]" value="{$body.data.configuration.api_key}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-md-2 control-label">
|
||||
<label>Heureka endpoint</label>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<select name="data[configuration][endpoint]" class="selecter">
|
||||
<option value="heureka.cz" {if $body.data.configuration.endpoint == "heureka.cz"}selected{/if}>heureka.cz</option>
|
||||
<option value="heureka.sk" {if $body.data.configuration.endpoint == "heureka.sk"}selected{/if}>heureka.sk</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{get_contexts domain=1 assign='contexts'}
|
||||
<input type="text" class="form-control input-sm" size="5" name="endpointURL"
|
||||
value="https://{$contexts.domain->getActiveId()}/_dropshipment/heureka/{$body.data.id}"
|
||||
placeholder="Heureka endpoint URL" readonly>
|
||||
</div>
|
||||
<div class="col-md-1 control-label">
|
||||
<label>Test</label>
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
{print_toggle value=$body.data.configuration.test nameRaw="data[configuration][test]"}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="heurekaPayments">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h1 class="h4 main-panel-title">{'payments'|translate}</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-group panel-group-lists">
|
||||
<div class="panel">
|
||||
<div class="row bottom-space">
|
||||
<div class="col-md-2 control-label">
|
||||
<label>Platební karta</label>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<select name="data[configuration][payment_online_id]" class="selecter"
|
||||
data-autocomplete="payments" data-preload="payments">
|
||||
{if $body.data.configuration.payment_online_id}
|
||||
<option value="{$body.data.configuration.payment_online_id}"
|
||||
selected>{$body.data.configuration.payment_online_id}</option>
|
||||
{/if}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel">
|
||||
<div class="row bottom-space">
|
||||
<div class="col-md-2 control-label">
|
||||
<label>Převod na účet</label>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<select name="data[configuration][payment_bank_id]" class="selecter"
|
||||
data-autocomplete="payments" data-preload="payments">
|
||||
{if $body.data.configuration.payment_bank_id}
|
||||
<option value="{$body.data.configuration.payment_bank_id}"
|
||||
selected>{$body.data.configuration.payment_bank_id}</option>
|
||||
{/if}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="heurekaStores">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h1 class="h4 main-panel-title">{'stores'|translate}</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-group panel-group-lists">
|
||||
{foreach $body.configurationData.deliveryTypes as $id => $value}
|
||||
<div class="form-group">
|
||||
<label class="col-md-3 control-label">
|
||||
{$value}
|
||||
</label>
|
||||
<div class="col-md-3">
|
||||
<div class="input-group" data-input-overwrite="">
|
||||
<input type="text" class="form-control input-sm" size="5" name="data[configuration][deliveryTypes][{$id}]"
|
||||
value="{$body.data.configuration.deliveryTypes[$id]}" placeholder="HeurekaID">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/foreach}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,136 @@
|
||||
<div class="form-group form-group-flex">
|
||||
<div class="col-md-2 control-label">
|
||||
<label>{'sourceUrl'|translate}</label>
|
||||
</div>
|
||||
<div class="col-md-10">
|
||||
<input class="form-control input-sm" type="text" name="data[source_url]"
|
||||
value="{$body.data.source_url}" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="configurationMall" class="tab-pane fade in boxFlex">
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-md-2 control-label">
|
||||
<label>Client ID</label>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<input type="text" class="form-control input-sm" name="data[configuration][api_key]" value="{$body.data.configuration.api_key}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-md-1 col-md-offset-2">
|
||||
{print_toggle nameRaw="data[configuration][out]" value=$body.data.configuration.out}
|
||||
</div>
|
||||
<div class="col-md-5 control-label" style="text-align: left;">
|
||||
<label>{'out'|translate}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="mallDeliveries">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h1 class="h4 main-panel-title">{'deliveries'|translate}</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row bottom-space">
|
||||
<div class="col-md-3">
|
||||
<a href="#" data-form-add class="btn btn-success btn-block"><span
|
||||
class="glyphicon glyphicon-plus"></span> {'addDeliveryMapping'|translate}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-group panel-group-lists">
|
||||
{foreach array_merge([[]], $body.data.configuration.deliveries|default:[]) as $key => $row}
|
||||
<div class="panel" {if $key == 0}data-form-new style="display:none" {else}data-form-item{/if}>
|
||||
<div class="row bottom-space">
|
||||
<div class="col-md-2 control-label">
|
||||
<label>{'mallDeliveryId'|translate}</label>
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
<input class="form-control input-sm" type="text" name="data[configuration][deliveries][{$key}][id_external]"
|
||||
value="{$row.id_external}">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select name="data[configuration][deliveries][{$key}][country]" class="selecter"
|
||||
data-autocomplete="countries" data-preload="countries">
|
||||
<option value="">{'allCountries'|translate}</option>
|
||||
{if $row.country}
|
||||
<option value="{$row.country}" selected>{$row.country}</option>
|
||||
{/if}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2 control-label">
|
||||
<label>{'delivery'|translate}</label>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select name="data[configuration][deliveries][{$key}][id_delivery]" class="selecter"
|
||||
data-autocomplete="deliveries" data-preload="deliveries">
|
||||
{if $row.id_delivery}
|
||||
<option value="{$row.id_delivery}" selected>{$row.id_delivery}</option>
|
||||
{/if}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
<a class="btn-sm btn btn-danger" data-form-delete>
|
||||
<input class="hidden" type="checkbox" name="data[configuration][deliveries][{$key}][delete]"/>
|
||||
<span class="glyphicon glyphicon-remove"></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/foreach}
|
||||
</div>
|
||||
</div>
|
||||
<div id="mallPayments">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h1 class="h4 main-panel-title">{'payments'|translate}</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-group panel-group-lists">
|
||||
<div class="panel">
|
||||
<div class="row bottom-space">
|
||||
<div class="col-md-2 control-label">
|
||||
<label>Dobírka</label>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<select name="data[configuration][payments][cod]" class="selecter"
|
||||
data-autocomplete="payments" data-preload="payments">
|
||||
{if $body.data.configuration.payments.cod}
|
||||
<option value="{$body.data.configuration.payments.cod}" selected>{$body.data.configuration.payments.cod}</option>
|
||||
{/if}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel">
|
||||
<div class="row bottom-space">
|
||||
<div class="col-md-2 control-label">
|
||||
<label>Bez dobírky</label>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<select name="data[configuration][payments][paid]" class="selecter"
|
||||
data-autocomplete="payments" data-preload="payments">
|
||||
{if $body.data.configuration.payments.paid}
|
||||
<option value="{$body.data.configuration.payments.paid}" selected>{$body.data.configuration.payments.paid}</option>
|
||||
{/if}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="application/javascript">
|
||||
initForm({
|
||||
selector: '#mallDeliveries',
|
||||
beforeAdd: function (original) {
|
||||
var $item = original();
|
||||
window.preloadAutocompletes($item);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
@@ -0,0 +1,40 @@
|
||||
<div id="flapRestrictions" class="tab-pane fade in boxFlex">
|
||||
<div class="form-group">
|
||||
<div class="col-md-12">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading panel-heading-flex">
|
||||
<small class="panel-title">{'tagValuesRestriction'|translate}</small>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="col-md-12">
|
||||
<div class="wpj-form-group">
|
||||
<label>
|
||||
{'restrictionsTagName'|translate}
|
||||
<a class="help-tip"
|
||||
data-toggle="tooltip" title=""
|
||||
data-original-title="{'restrictionsTagNameInfo'|translate}">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</label>
|
||||
<input id="configurationExpando-restrictions-tag" type="text" class="form-control input-sm"
|
||||
name="data[data][restrictions][tagName]"
|
||||
value="{$body.data.data.restrictions.tagName}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label>
|
||||
{'restrictionsValues'|translate}
|
||||
<a class="help-tip"
|
||||
data-toggle="tooltip" title=""
|
||||
data-original-title="{'restrictionsValuesInfo'|translate}">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</label>
|
||||
<textarea id="configurationExpando-restrictions-values" rows="2" class="form-control input-sm"
|
||||
name="data[data][restrictions][values]">{if $body.data.data.restrictions.values}{$body.data.data.restrictions.values}{/if}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,95 @@
|
||||
{extends "[shared]/window.tpl"}
|
||||
|
||||
{block js}
|
||||
{$smarty.block.parent}
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.41.0/codemirror.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.41.0/mode/xml/xml.js"></script>
|
||||
{/block}
|
||||
{block css append}
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.41.0/codemirror.css">
|
||||
<style>
|
||||
.CodeMirror {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
{/block}
|
||||
|
||||
{block tabs}
|
||||
{windowTab id='flapDropshipment' label=translate('flapDropshipment')}
|
||||
{/block}
|
||||
|
||||
{block tabsContent}
|
||||
<div id="flapDropshipment" class="tab-pane fade active in boxStatic box">
|
||||
|
||||
<div class="form-group form-group-flex">
|
||||
<div class="col-md-2 control-label">
|
||||
<label>{'name'|translate}</label>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<input class="form-control input-sm" type="text" name="data[name]"
|
||||
value="{$body.data.name}" required>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2 control-label">
|
||||
<label>{'type'|translate}</label>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{if $body.acn != 'add'}
|
||||
<input type="hidden" name="data[type]" value="{$body.data.type}">
|
||||
{/if}
|
||||
<select class="selecter" name="data[type]" {if $body.acn != 'add'}disabled{/if}>
|
||||
{foreach $body.types as $type => $name}
|
||||
<option value="{$type}" {if $type == $body.data.type}selected{/if}>{$name}</option>
|
||||
{/foreach}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{if $body.data.type == 'generic'}
|
||||
<div class="form-group form-group-flex">
|
||||
<div class="col-md-10 col-md-offset-2">
|
||||
<small>
|
||||
{'genericSourceInfo'|translate nofilter}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="form-group form-group-flex">
|
||||
<div class="col-md-2 control-label">
|
||||
<label>{'active'|translate}</label>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{print_toggle name='active'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{if $body.acn == 'add'}
|
||||
<div class="infobox">
|
||||
{'configurationInfo'|translate}
|
||||
</div>
|
||||
{elseif $body.configurationTemplate}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h1 class="h4 main-panel-title">{'configuration'|translate}</h1>
|
||||
</div>
|
||||
</div>
|
||||
{include "[DropshipBundle]/window/{$body.configurationTemplate}"}
|
||||
{/if}
|
||||
{ifmodule INVOICES}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h1 class="h4 main-panel-title">{'invoice_number'|translate}</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-2 control-label">
|
||||
<label>{'invoice_number'|translate}</label>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{print_select var=$body.invoices selected=$body.data.id_invoice_number name='data[id_invoice_number]'}
|
||||
</div>
|
||||
</div>
|
||||
{/ifmodule}
|
||||
</div>
|
||||
{/block}
|
||||
@@ -0,0 +1,20 @@
|
||||
<div id="flapTransformation" class="tab-pane fade in boxFlex">
|
||||
<legend>Transformace</legend>
|
||||
<div class="row boxFlex box">
|
||||
<div class="col-md-12 boxFlex box">
|
||||
<textarea name="data[transformation]" id="transform-textarea" class="form-control input-sm">{$body.data.transformation}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
var editor = CodeMirror.fromTextArea(document.getElementById('transform-textarea'), {
|
||||
mode: 'xml',
|
||||
lineNumbers: true
|
||||
});
|
||||
|
||||
$('#windowTables a').on('shown.bs.tab', function () {
|
||||
editor.refresh();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace KupShop\DropshipBundle\AdminRegister;
|
||||
|
||||
use KupShop\AdminBundle\AdminRegister\AdminRegister;
|
||||
use KupShop\AdminBundle\AdminRegister\IAdminRegisterDynamic;
|
||||
use KupShop\AdminBundle\AdminRegister\IAdminRegisterStatic;
|
||||
|
||||
class DropshipAdminRegister extends AdminRegister implements IAdminRegisterDynamic, IAdminRegisterStatic
|
||||
{
|
||||
public function getDynamicMenu(): array
|
||||
{
|
||||
return [
|
||||
self::createMenuItem('settingsMenu', [
|
||||
'name' => 'Dropshipment',
|
||||
'title' => translate('menu', 'Dropshipment'),
|
||||
'left' => 's=menu.php&type=Dropshipment',
|
||||
'right' => 's=list.php&type=Dropshipment',
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPermissions(): array
|
||||
{
|
||||
return [
|
||||
self::createPermissions('Dropshipment', [\Modules::DROPSHIP], ['DROPSHIPMENT']),
|
||||
];
|
||||
}
|
||||
}
|
||||
15
bundles/KupShop/DropshipBundle/DropshipBundle.php
Normal file
15
bundles/KupShop/DropshipBundle/DropshipBundle.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace KupShop\DropshipBundle;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
class DropshipBundle extends Bundle
|
||||
{
|
||||
public function build(ContainerBuilder $container)
|
||||
{
|
||||
$container->registerForAutoconfiguration(TransferInterface::class)
|
||||
->addTag('dropship.transfer');
|
||||
}
|
||||
}
|
||||
24
bundles/KupShop/DropshipBundle/Entity/CurrencyInfo.php
Normal file
24
bundles/KupShop/DropshipBundle/Entity/CurrencyInfo.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace KupShop\DropshipBundle\Entity;
|
||||
|
||||
use KupShop\I18nBundle\Entity\Currency;
|
||||
|
||||
class CurrencyInfo
|
||||
{
|
||||
public Currency $currency;
|
||||
public \Decimal $rate;
|
||||
|
||||
public function __construct(Currency $currency, \Decimal $rate)
|
||||
{
|
||||
$this->currency = $currency;
|
||||
$this->rate = $rate;
|
||||
}
|
||||
|
||||
public function getCurrencyCode(): string
|
||||
{
|
||||
return $this->currency->getId();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace KupShop\DropshipBundle\Event;
|
||||
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
class DropshipOrderCreatedEvent extends Event
|
||||
{
|
||||
public function __construct(
|
||||
public \Order $order,
|
||||
public array $dropshipment,
|
||||
public \SimpleXMLElement $xml,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace KupShop\DropshipBundle\EventListener;
|
||||
|
||||
use KupShop\DropshipBundle\Util\TransferWorker;
|
||||
use KupShop\KupShopBundle\Event\CronEvent;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
class CronListener implements EventSubscriberInterface
|
||||
{
|
||||
private TransferWorker $worker;
|
||||
|
||||
public function __construct(TransferWorker $worker)
|
||||
{
|
||||
$this->worker = $worker;
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
return [
|
||||
CronEvent::RUN_FREQUENT => [
|
||||
['handleTransfers', 200],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function handleTransfers(): void
|
||||
{
|
||||
$this->worker->run();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace KupShop\DropshipBundle\Exception;
|
||||
|
||||
class TransferException extends \Exception
|
||||
{
|
||||
private array $data;
|
||||
|
||||
public function __construct($message = '', array $data = [], $code = 0, ?Throwable $previous = null)
|
||||
{
|
||||
$this->data = $data;
|
||||
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
|
||||
public function getData(): array
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace KupShop\DropshipBundle\Exception;
|
||||
|
||||
class TransferNotFoundException extends \Exception
|
||||
{
|
||||
}
|
||||
10
bundles/KupShop/DropshipBundle/Resources/config/services.yml
Normal file
10
bundles/KupShop/DropshipBundle/Resources/config/services.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
services:
|
||||
_defaults:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
||||
KupShop\DropshipBundle\:
|
||||
resource: ../../{Admin/Actions,Admin/Tabs,AdminRegister,EventListener,Transfer,Util}
|
||||
|
||||
KupShop\DropshipBundle\Util\TransferLocator:
|
||||
arguments: [!tagged dropship.transfer]
|
||||
@@ -0,0 +1,221 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace KupShop\DropshipBundle\Resources\upgrade;
|
||||
|
||||
use KupShop\DropshipBundle\Transfer\BaseLinkerTransfer;
|
||||
use KupShop\DropshipBundle\Transfer\ExpandoTransfer;
|
||||
use KupShop\DropshipBundle\Transfer\GenericTransfer;
|
||||
use KupShop\DropshipBundle\Transfer\MallTransfer;
|
||||
use KupShop\DropshipBundle\Util\TransferLocator;
|
||||
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
|
||||
use Query\Operator;
|
||||
|
||||
class DropshipUpgrade extends \UpgradeNew
|
||||
{
|
||||
public function check_DropshipmentTable(): bool
|
||||
{
|
||||
return $this->checkTableExists('dropshipment');
|
||||
}
|
||||
|
||||
/** Add 'dropshipment' table */
|
||||
public function upgrade_DropshipmentTable(): void
|
||||
{
|
||||
$locator = ServiceContainer::getService(TransferLocator::class);
|
||||
|
||||
$types = array_map(fn ($x) => '"'.$x.'"', $locator->getTypes());
|
||||
|
||||
sqlQuery('CREATE TABLE dropshipment (
|
||||
id INT(11) PRIMARY KEY AUTO_INCREMENT,
|
||||
type ENUM('.implode(',', $types).') NOT NULL,
|
||||
source_url VARCHAR(255) NOT NULL,
|
||||
name VARCHAR(50) NOT NULL,
|
||||
active TINYINT DEFAULT 1,
|
||||
configuration LONGTEXT DEFAULT NULL,
|
||||
transformation LONGTEXT DEFAULT NULL,
|
||||
data MEDIUMTEXT DEFAULT NULL,
|
||||
last_sync DATETIME DEFAULT NULL
|
||||
)');
|
||||
|
||||
$this->upgradeOK();
|
||||
}
|
||||
|
||||
public function check_OrderDropshipment(): bool
|
||||
{
|
||||
return $this->checkTableExists('order_dropshipment');
|
||||
}
|
||||
|
||||
/** Add table `order_dropshipment` */
|
||||
public function upgrade_OrderDropshipment(): void
|
||||
{
|
||||
sqlQuery('CREATE TABLE order_dropshipment
|
||||
(
|
||||
id_order INT(11) UNSIGNED NOT NULL,
|
||||
id_dropshipment INT(11),
|
||||
id_external VARCHAR(250) NOT NULL,
|
||||
data MEDIUMTEXT DEFAULT NULL,
|
||||
UNIQUE INDEX `order_dropshipment_id` (id_dropshipment, id_external),
|
||||
CONSTRAINT `FK_order_dropshipment_id_order` FOREIGN KEY (`id_order`) REFERENCES `orders` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT `FK_order_dropshipment_id_dropshipment` FOREIGN KEY (`id_dropshipment`) REFERENCES `dropshipment` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);');
|
||||
|
||||
$this->upgradeOK();
|
||||
}
|
||||
|
||||
public function check_DropshipmentTypeEnum(): bool
|
||||
{
|
||||
$locator = ServiceContainer::getService(TransferLocator::class);
|
||||
|
||||
return $this->checkEnumOptions('dropshipment', 'type', $locator->getTypes());
|
||||
}
|
||||
|
||||
/** Update dropshipment.type enum */
|
||||
public function upgrade_DropshipmentTypeEnum(): void
|
||||
{
|
||||
$locator = ServiceContainer::getService(TransferLocator::class);
|
||||
|
||||
$this->updateEnumOptions('dropshipment', 'type', $locator->getTypes());
|
||||
|
||||
$this->upgradeOK();
|
||||
}
|
||||
|
||||
public function check_DropshipmentInSettings(): bool
|
||||
{
|
||||
$settings = \Settings::getDefault();
|
||||
$dropship = $settings->loadValue('dropship');
|
||||
|
||||
return !empty($dropship);
|
||||
}
|
||||
|
||||
/** Move dropshipment configuration from `settings` into `dropshipment` table */
|
||||
public function upgrade_DropshipmentInSettings(): void
|
||||
{
|
||||
$settings = \Settings::getDefault();
|
||||
$dropship = $settings->loadValue('dropship');
|
||||
|
||||
// mall to dropshipment table
|
||||
if (!empty($dropship['mall']['api_key'])) {
|
||||
$mallDropshipmentId = sqlGetConnection()->transactional(function () use ($dropship) {
|
||||
sqlQueryBuilder()
|
||||
->insert('dropshipment')
|
||||
->directValues(
|
||||
[
|
||||
'type' => MallTransfer::getType(),
|
||||
'source_url' => 'https://partners.mallgroup.com/orders.xml?client_id='.$dropship['mall']['api_key'],
|
||||
'name' => 'MALL',
|
||||
'active' => 1,
|
||||
'configuration' => json_encode($dropship['mall'] ?? []),
|
||||
]
|
||||
)->execute();
|
||||
|
||||
return (int) sqlInsertId();
|
||||
});
|
||||
|
||||
// naplnim order_dropshipment objednavkama z MALLu
|
||||
sqlQuery('INSERT IGNORE INTO order_dropshipment (id_order, id_dropshipment, id_external, data)
|
||||
SELECT
|
||||
id,
|
||||
'.$mallDropshipmentId.' as id_dropshipment,
|
||||
JSON_VALUE(note_admin, \'$.mall.order_id\'),
|
||||
JSON_EXTRACT(note_admin, \'$.mall\')
|
||||
FROM orders
|
||||
WHERE FIND_IN_SET("DSM", flags);');
|
||||
}
|
||||
|
||||
// expando to dropshipment table
|
||||
if (!empty($dropship['expando']['api_key'])) {
|
||||
$expandoDropshipmentId = sqlGetConnection()->transactional(function () use ($dropship) {
|
||||
sqlQueryBuilder()
|
||||
->insert('dropshipment')
|
||||
->directValues(
|
||||
[
|
||||
'type' => ExpandoTransfer::getType(),
|
||||
'source_url' => 'https://app.expan.do/api/v2/orderfeed?days=3&access_token='.$dropship['expando']['api_key'],
|
||||
'name' => 'Expando',
|
||||
'active' => 1,
|
||||
'configuration' => json_encode($dropship['expando'] ?? []),
|
||||
]
|
||||
)->execute();
|
||||
|
||||
return (int) sqlInsertId();
|
||||
});
|
||||
|
||||
// naplnim order_dropshipment objednavkama z Expanda
|
||||
sqlQuery('INSERT IGNORE INTO order_dropshipment (id_order, id_dropshipment, id_external, data)
|
||||
SELECT
|
||||
id,
|
||||
'.$expandoDropshipmentId.' as id_dropshipment,
|
||||
JSON_VALUE(note_admin, \'$.expando.orderId\'),
|
||||
JSON_EXTRACT(note_admin, \'$.expando\')
|
||||
FROM orders
|
||||
WHERE FIND_IN_SET("DSE", flags);');
|
||||
}
|
||||
|
||||
$settings->deleteValue('dropship');
|
||||
|
||||
$this->upgradeOK();
|
||||
}
|
||||
|
||||
public function check_invoiceNumberIdColumn(): bool
|
||||
{
|
||||
return findModule(\Modules::INVOICES) && $this->checkColumnExists('dropshipment', 'id_invoice_number');
|
||||
}
|
||||
|
||||
/** Add dropshipment.id_invoice_number column */
|
||||
public function upgrade_invoiceNumberIdColumn(): void
|
||||
{
|
||||
if (findModule(\Modules::INVOICES)) {
|
||||
sqlQuery('ALTER TABLE dropshipment ADD COLUMN id_invoice_number INT DEFAULT NULL');
|
||||
sqlQuery('ALTER TABLE dropshipment ADD FOREIGN KEY (id_invoice_number) REFERENCES invoice_numbers (id) ON DELETE CASCADE ON UPDATE CASCADE');
|
||||
|
||||
$this->upgradeOK();
|
||||
}
|
||||
}
|
||||
|
||||
public function check_GenericTypeMappingGroups(): bool
|
||||
{
|
||||
return sqlQueryBuilder()
|
||||
->select('id')
|
||||
->from('dropshipment')
|
||||
->andWhere(Operator::inStringArray([BaseLinkerTransfer::getType(), GenericTransfer::getType()], 'type'))
|
||||
->andWhere('configuration NOT LIKE "%ignore_on_mapping_not_found%"')
|
||||
->execute()->rowCount() > 0;
|
||||
}
|
||||
|
||||
/** Update dropship config for types: generic and baselinker */
|
||||
public function upgrade_GenericTypeMappingGroups(): void
|
||||
{
|
||||
$qb = sqlQueryBuilder()
|
||||
->select('id, configuration')
|
||||
->from('dropshipment')
|
||||
->andWhere(Operator::inStringArray([BaseLinkerTransfer::getType(), GenericTransfer::getType()], 'type'))
|
||||
->andWhere('configuration NOT LIKE "%ignore_on_mapping_not_found%"');
|
||||
|
||||
foreach ($qb->execute() as $item) {
|
||||
$configuration = json_decode($item['configuration'] ?: '', true) ?: [];
|
||||
$configuration['ignore_on_mapping_not_found'] = 'N';
|
||||
|
||||
if (!array_key_exists('mappingGroups', $configuration)
|
||||
&& (array_key_exists('deliveries', $configuration) || array_key_exists('payments', $configuration))) {
|
||||
$group = [
|
||||
'name' => 'Výchozí',
|
||||
'deliveries' => $configuration['deliveries'],
|
||||
'payments' => $configuration['payments'],
|
||||
];
|
||||
|
||||
$configuration['mappingGroups'] = [$group];
|
||||
|
||||
unset($configuration['deliveries'], $configuration['payments']);
|
||||
}
|
||||
|
||||
sqlQueryBuilder()
|
||||
->update('dropshipment')
|
||||
->directValues(['configuration' => json_encode($configuration)])
|
||||
->where(Operator::equals(['id' => $item['id']]))
|
||||
->execute();
|
||||
}
|
||||
|
||||
$this->upgradeOK();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace KupShop\DropshipBundle\Tests;
|
||||
|
||||
use KupShop\DropshipBundle\Transfer\ExpandoTransfer;
|
||||
|
||||
class TransferRestrictionsTest extends \DatabaseTestCase
|
||||
{
|
||||
public function testEmptyDropshipment(): void
|
||||
{
|
||||
$xml = simplexml_load_file(__DIR__.'/files/xml_order_1.xml');
|
||||
|
||||
/** @var ExpandoTransfer $dropshipment */
|
||||
$dropshipment = $this->get(ExpandoTransfer::class);
|
||||
$this->assertEquals(true, $dropshipment->isValidRestrictionByTag($xml));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideCases
|
||||
*/
|
||||
public function testRestrictions(array $restrictions, bool $isValid): void
|
||||
{
|
||||
$xml = simplexml_load_file(__DIR__.'/files/xml_order_1.xml');
|
||||
|
||||
/** @var ExpandoTransfer $dropshipment */
|
||||
$dropshipment = $this->get(ExpandoTransfer::class);
|
||||
$dropshipment->dropshipment['data']['restrictions'] = $restrictions;
|
||||
$this->assertEquals($isValid, $dropshipment->isValidRestrictionByTag($xml));
|
||||
}
|
||||
|
||||
public function provideCases(): \Generator
|
||||
{
|
||||
yield 'empty restrictions' => [
|
||||
'restrictions' => [],
|
||||
'isValid' => true,
|
||||
];
|
||||
|
||||
yield 'single invalid value in tagName' => [
|
||||
'restrictions' => [
|
||||
'tagName' => 'marketplace',
|
||||
'values' => 'AMAZON ES',
|
||||
],
|
||||
'isValid' => false,
|
||||
];
|
||||
|
||||
yield 'match case-sensitive case in values (preventive for AMAZON Uk)' => [
|
||||
'restrictions' => [
|
||||
'tagName' => 'marketplace',
|
||||
'values' => 'aMaZoN ES',
|
||||
],
|
||||
'isValid' => false,
|
||||
];
|
||||
|
||||
yield 'restriction value => out of range' => [
|
||||
'restrictions' => [
|
||||
'tagName' => 'marketplace',
|
||||
'values' => 'AMAZON UK',
|
||||
],
|
||||
'isValid' => true,
|
||||
];
|
||||
|
||||
yield 'tagName change => match value' => [
|
||||
'restrictions' => [
|
||||
'tagName' => 'fulfillmentChannel',
|
||||
'values' => 'FBA',
|
||||
],
|
||||
'isValid' => false,
|
||||
];
|
||||
|
||||
yield 'tagName change => unmatch value' => [
|
||||
'restrictions' => [
|
||||
'tagName' => 'fulfillmentChannel',
|
||||
'values' => 'FBAA',
|
||||
],
|
||||
'isValid' => true,
|
||||
];
|
||||
|
||||
yield 'tagName does not exists' => [
|
||||
'restrictions' => [
|
||||
'tagName' => 'foofoofoo',
|
||||
'values' => 'FBA',
|
||||
],
|
||||
'isValid' => true,
|
||||
];
|
||||
}
|
||||
}
|
||||
124
bundles/KupShop/DropshipBundle/Tests/TransferTest.json
Normal file
124
bundles/KupShop/DropshipBundle/Tests/TransferTest.json
Normal file
@@ -0,0 +1,124 @@
|
||||
{
|
||||
"dropshipment" : [
|
||||
{
|
||||
"id" : 2,
|
||||
"type" : "expando",
|
||||
"source_url" : "https://api.expan.do/api/v2/orderfeed?access_token=0FC-nKJ~hr.TuX1W2tzLJl&days=5",
|
||||
"name" : "Expando - Amazon",
|
||||
"active" : 1,
|
||||
"configuration" : "{\"prices_to_default_currency\":\"N\",\"api_key\":\"0FC-nKJ~hr.TuX1W2tzLJl\",\"out\":\"Y\",\"update\":{\"shipped\":\"shipped\"},\"marketplaces\":[{\"name\":\"Amazon ES\",\"settings\":{\"id_language\":\"\"},\"deliveries\":[]},{\"name\":\"Amazon IT\",\"settings\":{\"id_language\":\"\"},\"deliveries\":[]},{\"name\":\"Amazon FR\",\"settings\":{\"id_language\":\"\"},\"deliveries\":[]},{\"name\":\"Amazon DE\",\"settings\":{\"id_language\":\"\"},\"deliveries\":[]}]}",
|
||||
"transformation" : null,
|
||||
"data" : "{\"restrictions\":{\"tagName\":\"marketplace\",\"values\":\"AMAZON UK, AMAZON SE\"}}",
|
||||
"last_sync" : "2024-10-17 07:27:44"
|
||||
}
|
||||
],
|
||||
"stores" : [
|
||||
{
|
||||
"id" : 7,
|
||||
"position" : 3,
|
||||
"name" : "Amazon",
|
||||
"id_delivery" : null,
|
||||
"type" : 2,
|
||||
"figure" : "N",
|
||||
"data" : null
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"position": 0,
|
||||
"name": "Hlavní sklad",
|
||||
"id_delivery": null,
|
||||
"type": 1,
|
||||
"figure": "Y",
|
||||
"data": null
|
||||
}
|
||||
],
|
||||
"products" : [
|
||||
{
|
||||
"id" : 76,
|
||||
"id_block" : null,
|
||||
"title" : "Stříbrný prsten se Swarovski® Zirconia",
|
||||
"code" : "FNJR085sw",
|
||||
"ean" : 8596012204380,
|
||||
"short_descr" : "",
|
||||
"long_descr" : "<p>Zásnubní stříbrný prsten v lesklém provedení je skvostně zkrášlen průzračným a výrazně třpytivým zirkonem 3 mm se Swarovski Zirconia.</p>\r\n\r\n<p>Vhodné pro: Každodenní nošení, zásnubní prsten, dárek pro ženu.<br />\r\nPrstýnek bude hezkým dárkem věnovaným z lásky.<br />\r\n<br />\r\nMateriál: Stříbro ryzost 925/1000<br />\r\nPovrchová úprava: Rhodiovaný povrch - vysoký lesk, vzhled bílého zlata, šperk je odolný vůči oxidaci (nečerná) a je hypoalergenní (mohou ho nosit i lidé s alergií na stříbro). </p>\r\n\r\n<p>Kámen: 3 mm Swarovski® Zirconia</p>",
|
||||
"parameters" : "",
|
||||
"price" : 818.1818,
|
||||
"price_for_discount" : 818.1818,
|
||||
"price_common" : 0.0000,
|
||||
"vat" : 1,
|
||||
"id_cn" : null,
|
||||
"discount" : 0.00000000,
|
||||
"producer" : null,
|
||||
"guarantee" : 0,
|
||||
"in_store" : 319,
|
||||
"pieces_sold" : 1701,
|
||||
"delivery_time" : 0,
|
||||
"campaign" : "NO",
|
||||
"updated" : "2024-10-15 08:16:08",
|
||||
"date_added" : "2016-11-22 07:57:32",
|
||||
"figure" : "Y",
|
||||
"show_raw_price" : "N",
|
||||
"position" : 0,
|
||||
"meta_title" : "Silvego stříbrný prsten se Swarovski® Zirconia",
|
||||
"meta_description" : null,
|
||||
"meta_keywords" : "stříbrný prsten, Swarovski zirconia, zásnubní,dárek",
|
||||
"show_in_feed" : "Y",
|
||||
"max_cpc" : 0,
|
||||
"note" : null,
|
||||
"weight" : 0.001000,
|
||||
"data" : "{\"generate_coupon\":\"N\",\"mainPhoto\":\"fnjr085sw-1.jpg\",\"generate_coupon_discount\":\"4\"}",
|
||||
"bonus_points" : null,
|
||||
"show_in_search" : "Y",
|
||||
"width" : null,
|
||||
"height" : null,
|
||||
"depth" : null,
|
||||
"date_stock_in" : "2024-09-06 15:08:40",
|
||||
"price_buy" : 125.9917
|
||||
}
|
||||
],
|
||||
"products_variations" : [
|
||||
{
|
||||
"id" : 401,
|
||||
"id_product" : 76,
|
||||
"code" : "FNJR085sw-obvod 58 mm",
|
||||
"ean" : 8596012211777,
|
||||
"title" : "Velikost: obvod 58 mm",
|
||||
"in_store" : 319,
|
||||
"delivery_time" : 0,
|
||||
"price" : 818.1818,
|
||||
"price_for_discount" : 818.1820,
|
||||
"figure" : "Y",
|
||||
"updated" : "2024-10-14 14:32:18",
|
||||
"date_added" : "2017-09-04 08:39:15",
|
||||
"note" : "R52",
|
||||
"weight" : 0.002000,
|
||||
"bonus_points" : null,
|
||||
"width" : null,
|
||||
"height" : null,
|
||||
"depth" : null,
|
||||
"data" : "{\"mainPhoto\":\"\"}",
|
||||
"price_common" : null,
|
||||
"price_buy" : 158.6612
|
||||
}
|
||||
],
|
||||
"stores_items" : [
|
||||
{
|
||||
"id" : 565,
|
||||
"id_store" : 7,
|
||||
"id_product" : 76,
|
||||
"id_variation" : 401,
|
||||
"quantity" : 272,
|
||||
"min_quantity" : null,
|
||||
"integrity_unique" : "7-76-401"
|
||||
},
|
||||
{
|
||||
"id": 6952,
|
||||
"id_store": 1,
|
||||
"id_product": 76,
|
||||
"id_variation": 401,
|
||||
"quantity": 47,
|
||||
"min_quantity": 0,
|
||||
"integrity_unique": "1-76-401"
|
||||
}
|
||||
]
|
||||
}
|
||||
70
bundles/KupShop/DropshipBundle/Tests/TransferTest.php
Normal file
70
bundles/KupShop/DropshipBundle/Tests/TransferTest.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace KupShop\DropshipBundle\Tests;
|
||||
|
||||
use KupShop\DropshipBundle\Transfer\ExpandoTransfer;
|
||||
use KupShop\DropshipBundle\Util\DropshipmentUtil;
|
||||
use PHPUnit\DbUnit\DataSet\ArrayDataSet;
|
||||
use PHPUnit\DbUnit\DataSet\DefaultDataSet;
|
||||
|
||||
class TransferTest extends \DatabaseTestCase
|
||||
{
|
||||
protected ExpandoTransfer $expandoTransfer;
|
||||
protected DropshipmentUtil $dropShipmentUtil;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->expandoTransfer = $this->get(ExpandoTransfer::class);
|
||||
$this->dropShipmentUtil = $this->get(DropshipmentUtil::class);
|
||||
}
|
||||
|
||||
public function testStoresExpandoTransfer(): void
|
||||
{
|
||||
$this->expandoTransfer->setup($this->dropShipmentUtil->getDropshipment(2)); // expando
|
||||
$this->expandoTransfer->dropshipment['source_url'] = __DIR__.'/files/xml_expando_feed.xml';
|
||||
|
||||
$stores_items = sqlQueryBuilder()
|
||||
->select('*')
|
||||
->from('stores_items')
|
||||
->where('id_product = 76 AND id_variation = 401')
|
||||
->execute()
|
||||
->fetchAllAssociative();
|
||||
|
||||
$variation = sqlQueryBuilder()
|
||||
->select('*')
|
||||
->from('products_variations')
|
||||
->where('id = 401 AND id_product = 76')
|
||||
->execute()
|
||||
->fetchAssociative();
|
||||
|
||||
$this->assertEquals(47, $stores_items[1]['quantity']);
|
||||
$this->assertEquals(319, $variation['in_store']);
|
||||
|
||||
$this->expandoTransfer->process();
|
||||
|
||||
$stores_items = sqlQueryBuilder()
|
||||
->select('*')
|
||||
->from('stores_items')
|
||||
->where('id_product = 76 AND id_variation = 401')
|
||||
->execute()
|
||||
->fetchAllAssociative();
|
||||
|
||||
$variation = sqlQueryBuilder()
|
||||
->select('*')
|
||||
->from('products_variations')
|
||||
->where('id = 401 AND id_product = 76')
|
||||
->execute()
|
||||
->fetchAssociative();
|
||||
|
||||
// Kontrola odebraného kusu
|
||||
$this->assertEquals(46, $stores_items[1]['quantity']);
|
||||
$this->assertEquals(318, $variation['in_store']);
|
||||
}
|
||||
|
||||
public function getDataSet(): ArrayDataSet|DefaultDataSet
|
||||
{
|
||||
return $this->getJsonDataSetFromFile();
|
||||
}
|
||||
}
|
||||
169
bundles/KupShop/DropshipBundle/Tests/files/xml_expando_feed.xml
Normal file
169
bundles/KupShop/DropshipBundle/Tests/files/xml_expando_feed.xml
Normal file
@@ -0,0 +1,169 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<orders>
|
||||
<order>
|
||||
<orderId>304-0019058-9770707</orderId>
|
||||
<orderStatus>New</orderStatus>
|
||||
<purchaseDate>2024-10-01 07:48:20</purchaseDate>
|
||||
<marketplace>AMAZON DE</marketplace>
|
||||
<venue>AMAZON</venue>
|
||||
<fulfillmentChannel>FBA</fulfillmentChannel>
|
||||
<businessOrder>false</businessOrder>
|
||||
<totalPrice>54.9</totalPrice>
|
||||
<totalItemTax>8.77</totalItemTax>
|
||||
<currencyCode>EUR</currencyCode>
|
||||
<language>DE</language>
|
||||
<paymentMethod>Other</paymentMethod>
|
||||
<shippingMethod>Expedited</shippingMethod>
|
||||
<shipServiceLevel>Expedited</shipServiceLevel>
|
||||
<deliveryBranchId/>
|
||||
<shippingPrice>0</shippingPrice>
|
||||
<latestShipDate>2024-10-01 21:59:59</latestShipDate>
|
||||
<latestDeliveryDate/>
|
||||
<isPremiumOrder>false</isPremiumOrder>
|
||||
<isPrime>false</isPrime>
|
||||
<isComplete>true</isComplete>
|
||||
<isRefunded>true</isRefunded>
|
||||
<invoiceUrls>
|
||||
<url>https://sellercentral.amazon.de/document/download?v=urn%3aalx%3adoc%3a9d8cab0f-1f96-4462-8642-ace99a371392%3ab43a6044-a30d-4437-bf56-6de3e97e5007&t=EU_Retail_Forward</url>
|
||||
</invoiceUrls>
|
||||
<invoices>
|
||||
<invoice>
|
||||
<id>INV-DE-157682631-2024-2132</id>
|
||||
<type>SHIPMENT</type>
|
||||
<url>https://sellercentral.amazon.de/document/download?v=urn%3aalx%3adoc%3a9d8cab0f-1f96-4462-8642-ace99a371392%3ab43a6044-a30d-4437-bf56-6de3e97e5007&t=EU_Retail_Forward</url>
|
||||
<date>2024-10-01 00:00:00</date>
|
||||
</invoice>
|
||||
</invoices>
|
||||
<billingAddress>
|
||||
<companyName/>
|
||||
<name/>
|
||||
<email/>
|
||||
<phone/>
|
||||
<address1/>
|
||||
<address2/>
|
||||
<address3/>
|
||||
<city/>
|
||||
<province/>
|
||||
<zip/>
|
||||
<countryCode/>
|
||||
</billingAddress>
|
||||
<customer>
|
||||
<companyName>-</companyName>
|
||||
<firstname>DHL</firstname>
|
||||
<surname/>
|
||||
<email>hy43dcn96qblxvl@marketplace.amazon.de</email>
|
||||
<phone>-</phone>
|
||||
<taxId/>
|
||||
<taxCountry/>
|
||||
<address>
|
||||
<address1>Packstation 220</address1>
|
||||
<address2>Sturmstr. 80</address2>
|
||||
<address3/>
|
||||
<city>Düsseldorf</city>
|
||||
<zip>40229</zip>
|
||||
<stateOrRegion>-</stateOrRegion>
|
||||
<country>DE</country>
|
||||
</address>
|
||||
</customer>
|
||||
<items>
|
||||
<item>
|
||||
<itemId>FNJR085sw_FNJR085sw-obvod 58 mm</itemId>
|
||||
<externalId/>
|
||||
<itemPrice>54.9</itemPrice>
|
||||
<itemQuantity>1</itemQuantity>
|
||||
<itemName>SILVEGO - FNJR085sw - Verlobungsring aus 925 Sterling Silber mit Swarovski Zirconia</itemName>
|
||||
<orderItemId>42369721088562</orderItemId>
|
||||
<itemTax>8.77</itemTax>
|
||||
<shippingDiscount>0</shippingDiscount>
|
||||
<shippingDiscountTax>0</shippingDiscountTax>
|
||||
<promotionDiscount>0</promotionDiscount>
|
||||
<promotionDiscountTax>0</promotionDiscountTax>
|
||||
<shippingPrice>0</shippingPrice>
|
||||
<shippingTax>0</shippingTax>
|
||||
<lineItemPrice>
|
||||
<tax>8.77</tax>
|
||||
<taxRatePercent>19</taxRatePercent>
|
||||
<withoutTax/>
|
||||
<withTax>54.9</withTax>
|
||||
</lineItemPrice>
|
||||
<lineItemDiscount>
|
||||
<tax/>
|
||||
<taxRatePercent/>
|
||||
<withoutTax/>
|
||||
<withTax/>
|
||||
</lineItemDiscount>
|
||||
<deliveryPrice>
|
||||
<tax/>
|
||||
<taxRatePercent/>
|
||||
<withoutTax/>
|
||||
<withTax/>
|
||||
</deliveryPrice>
|
||||
<deliveryDiscount>
|
||||
<tax/>
|
||||
<taxRatePercent/>
|
||||
<withoutTax/>
|
||||
<withTax/>
|
||||
</deliveryDiscount>
|
||||
<marketplaceCommission>
|
||||
<tax/>
|
||||
<taxRatePercent/>
|
||||
<withoutTax/>
|
||||
<withTax/>
|
||||
</marketplaceCommission>
|
||||
</item>
|
||||
</items>
|
||||
<price>
|
||||
<delivery>
|
||||
<tax/>
|
||||
<taxRatePercent/>
|
||||
<withoutTax/>
|
||||
<withTax/>
|
||||
</delivery>
|
||||
<items>
|
||||
<tax>8.77</tax>
|
||||
<taxRatePercent/>
|
||||
<withoutTax/>
|
||||
<withTax>54.9</withTax>
|
||||
</items>
|
||||
<payment>
|
||||
<tax/>
|
||||
<taxRatePercent/>
|
||||
<withoutTax/>
|
||||
<withTax/>
|
||||
</payment>
|
||||
<total>
|
||||
<tax/>
|
||||
<taxRatePercent/>
|
||||
<withoutTax/>
|
||||
<withTax>54.9</withTax>
|
||||
</total>
|
||||
<totalDiscount>
|
||||
<tax/>
|
||||
<taxRatePercent/>
|
||||
<withoutTax/>
|
||||
<withTax/>
|
||||
</totalDiscount>
|
||||
</price>
|
||||
<payment>
|
||||
<paymentMethod>CreditCard</paymentMethod>
|
||||
<cashOnDelivery>
|
||||
<toPay/>
|
||||
<servicePrice/>
|
||||
</cashOnDelivery>
|
||||
</payment>
|
||||
<delivery>
|
||||
<shippingCarrier/>
|
||||
<shippingCarrierService/>
|
||||
</delivery>
|
||||
<cancelRequest>
|
||||
<cancelReason/>
|
||||
<marketplaceCancelReason/>
|
||||
</cancelRequest>
|
||||
<returnLabels>
|
||||
</returnLabels>
|
||||
<parcelShop>
|
||||
<parcelShopIdentification/>
|
||||
<parcelShopBranchCode/>
|
||||
</parcelShop>
|
||||
</order>
|
||||
</orders>
|
||||
73
bundles/KupShop/DropshipBundle/Tests/files/xml_order_1.xml
Normal file
73
bundles/KupShop/DropshipBundle/Tests/files/xml_order_1.xml
Normal file
@@ -0,0 +1,73 @@
|
||||
<order>
|
||||
<orderId>404-8297607-3401137</orderId>
|
||||
<orderStatus>Canceled</orderStatus>
|
||||
<purchaseDate>2024-02-15 10:54:35</purchaseDate>
|
||||
<marketplace>AMAZON ES</marketplace>
|
||||
<venue>AMAZON</venue>
|
||||
<fulfillmentChannel>FBA</fulfillmentChannel>
|
||||
<businessOrder>false</businessOrder>
|
||||
<totalPrice>0</totalPrice>
|
||||
<totalItemTax>0</totalItemTax>
|
||||
<currencyCode/>
|
||||
<language>ES</language>
|
||||
<paymentMethod>Other</paymentMethod>
|
||||
<shippingMethod>Standard</shippingMethod>
|
||||
<shipServiceLevel>Standard</shipServiceLevel>
|
||||
<deliveryBranchId/>
|
||||
<shippingPrice>0</shippingPrice>
|
||||
<latestShipDate>2024-02-23 22:59:59</latestShipDate>
|
||||
<latestDeliveryDate/>
|
||||
<isPremiumOrder>false</isPremiumOrder>
|
||||
<isPrime>false</isPrime>
|
||||
<isComplete>false</isComplete>
|
||||
<isRefunded>false</isRefunded>
|
||||
<invoiceUrls> </invoiceUrls>
|
||||
<invoices> </invoices>
|
||||
<billingAddress>
|
||||
<companyName/>
|
||||
<name/>
|
||||
<email/>
|
||||
<phone/>
|
||||
<address1/>
|
||||
<address2/>
|
||||
<address3/>
|
||||
<city/>
|
||||
<province/>
|
||||
<zip/>
|
||||
<countryCode/>
|
||||
</billingAddress>
|
||||
<customer>
|
||||
<companyName>-</companyName>
|
||||
<firstname>-</firstname>
|
||||
<surname/>
|
||||
<email>-</email>
|
||||
<phone>-</phone>
|
||||
<taxId/>
|
||||
<taxCountry/>
|
||||
<address>
|
||||
<address1/>
|
||||
<address2/>
|
||||
<address3/>
|
||||
<city>-</city>
|
||||
<zip>-</zip>
|
||||
<stateOrRegion>-</stateOrRegion>
|
||||
<country>-</country>
|
||||
</address>
|
||||
</customer>
|
||||
<items>
|
||||
...
|
||||
</items>
|
||||
<price>
|
||||
...
|
||||
</price>
|
||||
<payment>
|
||||
...
|
||||
</payment>
|
||||
<delivery>
|
||||
...
|
||||
</delivery>
|
||||
<cancelRequest>
|
||||
...
|
||||
</cancelRequest>
|
||||
<returnLabels> </returnLabels>
|
||||
</order>
|
||||
433
bundles/KupShop/DropshipBundle/Transfer/AbstractTransfer.php
Normal file
433
bundles/KupShop/DropshipBundle/Transfer/AbstractTransfer.php
Normal file
@@ -0,0 +1,433 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace KupShop\DropshipBundle\Transfer;
|
||||
|
||||
use KupShop\AdminBundle\Util\ActivityLog;
|
||||
use KupShop\DropshipBundle\Entity\CurrencyInfo;
|
||||
use KupShop\DropshipBundle\Event\DropshipOrderCreatedEvent;
|
||||
use KupShop\DropshipBundle\Exception\TransferException;
|
||||
use KupShop\DropshipBundle\TransferInterface;
|
||||
use KupShop\KupShopBundle\Context\ContextManager;
|
||||
use KupShop\KupShopBundle\Context\CountryContext;
|
||||
use KupShop\KupShopBundle\Context\CurrencyContext;
|
||||
use KupShop\KupShopBundle\Util\Contexts;
|
||||
use KupShop\OrderingBundle\Event\OrderEvent;
|
||||
use KupShop\OrderingBundle\Event\OrderItemEvent;
|
||||
use KupShop\OrderingBundle\Util\Order\OrderInfo;
|
||||
use Query\Operator;
|
||||
use Query\QueryBuilder;
|
||||
use Symfony\Component\ErrorHandler\ErrorHandler;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
abstract class AbstractTransfer implements TransferInterface
|
||||
{
|
||||
protected static string $type;
|
||||
protected static string $name;
|
||||
|
||||
protected bool|array|null $tempRestrictions = null;
|
||||
|
||||
public array $dropshipment = [];
|
||||
public array $configuration = [];
|
||||
|
||||
/** @required */
|
||||
public OrderInfo $orderInfo;
|
||||
|
||||
/** @required */
|
||||
public EventDispatcherInterface $eventDispatcher;
|
||||
|
||||
/** @required */
|
||||
public ContextManager $contextManager;
|
||||
|
||||
public static function getType(): string
|
||||
{
|
||||
return static::$type;
|
||||
}
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return static::$name;
|
||||
}
|
||||
|
||||
public function isRunnable(): bool
|
||||
{
|
||||
if (!$this->dropshipment) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->dropshipment['active'] == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup transfer before run.
|
||||
*/
|
||||
public function setup(array $dropshipment): void
|
||||
{
|
||||
$this->tempRestrictions = null;
|
||||
$this->dropshipment = $dropshipment;
|
||||
$this->configuration = $dropshipment['configuration'];
|
||||
}
|
||||
|
||||
public function isValidRestrictionByTag(\SimpleXMLElement $order): bool
|
||||
{
|
||||
if ($this->tempRestrictions === null) {
|
||||
$drop = $this->dropshipment;
|
||||
if (
|
||||
($restrictions = $drop['data']['restrictions'] ?? false)
|
||||
&& !empty($restrictions['values'])
|
||||
&& !empty($restrictions['tagName'])
|
||||
) {
|
||||
$values = array_map(fn ($v) => strtolower(trim($v)), explode(',', $restrictions['values']));
|
||||
|
||||
$this->tempRestrictions = [$restrictions['tagName'], $values];
|
||||
} else {
|
||||
$this->tempRestrictions = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_array($this->tempRestrictions)) {
|
||||
[$tagName, $values] = $this->tempRestrictions;
|
||||
$tag = (string) $order->$tagName ?? '';
|
||||
|
||||
return !in_array(strtolower($tag), $values, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function process(): void
|
||||
{
|
||||
$config = $this->getConfiguration();
|
||||
|
||||
// zpracovani objednavek z feedu do e-shopu
|
||||
$this->in($config);
|
||||
|
||||
// zpracovani objednavek z e-shopu k externi sluzbe
|
||||
if ('Y' == ($config['out'] ?? 'N')) {
|
||||
$this->out($config);
|
||||
}
|
||||
}
|
||||
|
||||
public function getConfiguration(): array
|
||||
{
|
||||
return $this->configuration;
|
||||
}
|
||||
|
||||
public function getConfigurationVariables(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function payDropshipOrder(\Order $order): void
|
||||
{
|
||||
$pay_method = \Payment::METHOD_UNKNOWN;
|
||||
$delivery_type = $order->getDeliveryType();
|
||||
if ($delivery_type && !empty($delivery_type->payment_class)) {
|
||||
$pay_method = $delivery_type->payment_class->getPayMethod();
|
||||
}
|
||||
$order->insertPayment(
|
||||
$order->getTotalPrice()->getPriceWithVat(),
|
||||
'Zaplaceno přes modul dropshipment',
|
||||
null, false, $pay_method
|
||||
);
|
||||
}
|
||||
|
||||
protected function findDeliveryType(?int $deliveryId, ?int $paymentId): ?\DeliveryType
|
||||
{
|
||||
$deliveryTypeId = sqlQueryBuilder()
|
||||
->select('id')
|
||||
->from('delivery_type')
|
||||
->where(Operator::equals(['id_delivery' => $deliveryId, 'id_payment' => $paymentId]))
|
||||
->execute()->fetchOne();
|
||||
|
||||
if (!$deliveryTypeId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return \DeliveryType::get($deliveryTypeId, true);
|
||||
}
|
||||
|
||||
protected function loadXML(): ?\SimpleXMLElement
|
||||
{
|
||||
if (!($this->dropshipment['source_url'] ?? null)) {
|
||||
$this->addActivityLog(
|
||||
'V nastavení dropshipmentu chybí URL pro zdrojový XML soubor',
|
||||
$this->configuration
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
$xml = ErrorHandler::call(fn () => simplexml_load_file($this->dropshipment['source_url']));
|
||||
} catch (\Throwable $e) {
|
||||
if (isLocalDevelopment()) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->addActivityLog(
|
||||
'Nelze stáhnout objednávkový XML feed.',
|
||||
['error' => $e->getMessage()]
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return $xml ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* General method for creating an order. Order without items.
|
||||
*/
|
||||
protected function createDropshipOrder(\SimpleXMLElement $xml, array $data): \Order
|
||||
{
|
||||
[$externalId, $externalData] = $this->getExternalData($xml);
|
||||
|
||||
if ($data['delivery_country'] != '') {
|
||||
$data['delivery_country'] = trim($data['delivery_country']);
|
||||
}
|
||||
|
||||
if ($data['invoice_country'] != '') {
|
||||
$data['invoice_country'] = trim($data['invoice_country']);
|
||||
}
|
||||
|
||||
/** @var \Order $order */
|
||||
$order = sqlGetConnection()->transactional(function () use ($data, $externalId, $externalData) {
|
||||
sqlQueryBuilder()
|
||||
->insert('orders')
|
||||
->directValues(array_merge(
|
||||
[
|
||||
'source' => OrderInfo::ORDER_SOURCE_DROPSHIP,
|
||||
'date_updated' => (new \DateTime())->format('Y-m-d H:i:s'),
|
||||
],
|
||||
$data
|
||||
))
|
||||
->execute();
|
||||
|
||||
$orderId = (int) sqlInsertId();
|
||||
|
||||
// vytvorim mapovani na dropshipment
|
||||
sqlQueryBuilder()
|
||||
->insert('order_dropshipment')
|
||||
->directValues(
|
||||
[
|
||||
'id_order' => $orderId,
|
||||
'id_dropshipment' => $this->dropshipment['id'],
|
||||
'id_external' => $externalId,
|
||||
'data' => json_encode($externalData),
|
||||
]
|
||||
)->execute();
|
||||
|
||||
$order = \Order::get($orderId);
|
||||
|
||||
// dispatch order created event - order no is generated in event subscriber
|
||||
$this->eventDispatcher->dispatch(new OrderEvent($order), OrderEvent::ORDER_CREATED);
|
||||
|
||||
$countryContext = Contexts::get(CountryContext::class);
|
||||
|
||||
if (!empty($data['delivery_country']) && !isset($countryContext->getAll()[$data['delivery_country']])) {
|
||||
addActivityLog(ActivityLog::SEVERITY_WARNING, ActivityLog::TYPE_SYNC, 'Objednávka s č. '.$order->order_no.' z dropshipmentu byla vytvořena s neznámou zemí.', ['country' => $data['delivery_country']]);
|
||||
}
|
||||
|
||||
return $order;
|
||||
});
|
||||
|
||||
// zalogovat informaci o vytvoreni objednavky
|
||||
$order->logHistory(
|
||||
sprintf('[Dropshipment] <a href="javascript:nw(\'Dropshipment\', %s);">%s</a>: %s', $this->dropshipment['id'], $this->dropshipment['name'], $externalId)
|
||||
);
|
||||
|
||||
$this->eventDispatcher->dispatch(new DropshipOrderCreatedEvent($order, $this->dropshipment, $xml));
|
||||
|
||||
return $order;
|
||||
}
|
||||
|
||||
/**
|
||||
* General method for updating an order.
|
||||
*/
|
||||
protected function updateDropshipOrder(\Order $order, \SimpleXMLElement $xml): void
|
||||
{
|
||||
throw new \RuntimeException('Order update not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for checks if order should be imported or not.
|
||||
*/
|
||||
protected function isDropshipOrderValidToImport(\SimpleXMLElement $xml): bool
|
||||
{
|
||||
return $this->isValidRestrictionByTag($xml);
|
||||
}
|
||||
|
||||
protected function getProductByItem(\SimpleXMLElement $item): array
|
||||
{
|
||||
$code = (string) $item->CODE;
|
||||
$ean = (string) $item->EAN;
|
||||
|
||||
return $this->getProductByCode($code, $ean);
|
||||
}
|
||||
|
||||
protected function getProductByCode(?string $code, ?string $ean): array
|
||||
{
|
||||
$notFoundResult = [null, null];
|
||||
|
||||
$search = [];
|
||||
|
||||
if (!empty($code)) {
|
||||
$search['code'] = $code;
|
||||
}
|
||||
|
||||
if (!empty($ean)) {
|
||||
$search['ean'] = $ean;
|
||||
}
|
||||
|
||||
if (empty($search)) {
|
||||
return $notFoundResult;
|
||||
}
|
||||
|
||||
$foundItem = sqlQueryBuilder()
|
||||
->select('id as id_variation, id_product')
|
||||
->from('products_variations')
|
||||
->where(Operator::equals($search, 'OR'))
|
||||
->execute()->fetchAssociative();
|
||||
|
||||
if (!$foundItem) {
|
||||
$foundItem = sqlQueryBuilder()
|
||||
->select('id as id_product, null as id_variation')
|
||||
->from('products')
|
||||
->where(Operator::equals($search, 'OR'))
|
||||
->execute()->fetchAssociative();
|
||||
}
|
||||
|
||||
if ($foundItem) {
|
||||
return [$foundItem['id_product'], $foundItem['id_variation']];
|
||||
}
|
||||
|
||||
return $notFoundResult;
|
||||
}
|
||||
|
||||
protected function getCurrencyInfo(string $currency): CurrencyInfo
|
||||
{
|
||||
$currencyContext = Contexts::get(CurrencyContext::class);
|
||||
|
||||
// pokud nemam nastaveno konvertovani cen do defaultni meny, tak provadim validaci meny
|
||||
if (!$this->isPriceConvertionEnabled() && !($currencyObject = ($currencyContext->getAll()[$currency] ?? null))) {
|
||||
throw new TransferException(
|
||||
sprintf('Nepodařilo se naimportovat objednávku: měna "%s" není založena v e-shopu', $currency)
|
||||
);
|
||||
}
|
||||
|
||||
// pokud nemam currency rate, tak ho zkusim ziskat
|
||||
try {
|
||||
$currencyRate = isset($currencyObject) ? $currencyObject->getRate() : \CNB::getCurrency($currency);
|
||||
} catch (\Exception $e) {
|
||||
throw new TransferException(
|
||||
'Nepodařilo se naimportovat objednávku: nepodařilo se získat kurz vůči výchozí měně',
|
||||
['currencyRateError' => $e->getMessage()]
|
||||
);
|
||||
}
|
||||
|
||||
if (!isset($currencyObject)) {
|
||||
$currencyObject = $currencyContext->getDefault();
|
||||
}
|
||||
|
||||
return new CurrencyInfo($currencyObject, toDecimal($currencyRate));
|
||||
}
|
||||
|
||||
protected function convertPrice(\Decimal $price, CurrencyInfo $currencyInfo): \Decimal
|
||||
{
|
||||
// pokud mam zapnut prevod do vychozi meny
|
||||
if ($this->isPriceConvertionEnabled()) {
|
||||
$price = $price->mul($currencyInfo->rate);
|
||||
}
|
||||
|
||||
return $price;
|
||||
}
|
||||
|
||||
protected function modifyInsertedOrder(\Order $order, \SimpleXMLElement $orderXml): void
|
||||
{
|
||||
}
|
||||
|
||||
protected function modifyItem(array $item, \SimpleXMLElement $xmlItem): array
|
||||
{
|
||||
return $item;
|
||||
}
|
||||
|
||||
protected function getOrderByExternalId(string $externalId): ?\Order
|
||||
{
|
||||
$orderId = sqlQueryBuilder()
|
||||
->select('id_order')
|
||||
->from('order_dropshipment')
|
||||
->where(Operator::equals(['id_external' => $externalId, 'id_dropshipment' => $this->dropshipment['id']]))
|
||||
->execute()->fetchOne();
|
||||
|
||||
if (!$orderId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return \Order::get((int) $orderId);
|
||||
}
|
||||
|
||||
protected function isPriceConvertionEnabled(): bool
|
||||
{
|
||||
return ($this->configuration['prices_to_default_currency'] ?? 'N') === 'Y';
|
||||
}
|
||||
|
||||
protected function addActivityLog(string $message, array $data = [], string $severity = ActivityLog::SEVERITY_ERROR): void
|
||||
{
|
||||
addActivityLog(
|
||||
$severity,
|
||||
ActivityLog::TYPE_SYNC,
|
||||
sprintf('[Dropshipment] "%s" (ID: %s): ', $this->dropshipment['name'], $this->dropshipment['id']).$message,
|
||||
$data
|
||||
);
|
||||
}
|
||||
|
||||
protected function getLastSyncTime(): ?string
|
||||
{
|
||||
return sqlQueryBuilder()
|
||||
->select('last_sync')
|
||||
->from('dropshipment')
|
||||
->where(Operator::equals(['id' => $this->dropshipment['id']]))
|
||||
->execute()
|
||||
->fetchOne();
|
||||
}
|
||||
|
||||
protected function getOrdersForUpdate(): QueryBuilder
|
||||
{
|
||||
$qb = sqlQueryBuilder()
|
||||
->select('o.id as id, od.id_external as id_external')
|
||||
->from('orders', 'o')
|
||||
->leftJoin('o', 'order_dropshipment', 'od', 'o.id = od.id_order AND od.id_dropshipment = :dropshipment_id')
|
||||
->andWhere('o.status_storno = 0')
|
||||
->andWhere(Operator::not(Operator::equalsNullable(['o.package_id' => null])))
|
||||
->setParameter('dropshipment_id', $this->dropshipment['id'])
|
||||
->groupBy('o.id');
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
protected function itemCreatedEvent(?\ProductBase $product, int $idVariation, \Decimal $piecePrice, int $pieces, ?array $data, \Order $order): void
|
||||
{
|
||||
if (empty($product)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->eventDispatcher->dispatch(new OrderItemEvent($product, $idVariation, $piecePrice, $pieces, $data, $order), OrderItemEvent::ITEM_CREATED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares configuration data during save in admin.
|
||||
*/
|
||||
public function prepareConfigurationData(array $data): array
|
||||
{
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns array in format: [externalId, data].
|
||||
*/
|
||||
abstract protected function getExternalData(\SimpleXMLElement $xml): array;
|
||||
|
||||
abstract protected function getDeliveryTypeByConfiguration(\SimpleXMLElement $order): ?\DeliveryType;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace KupShop\DropshipBundle\Transfer;
|
||||
|
||||
class BaseLinkerTransfer extends GenericTransfer
|
||||
{
|
||||
protected static string $type = 'baselinker';
|
||||
protected static string $name = 'BaseLinker';
|
||||
|
||||
public function isRunnable(): bool
|
||||
{
|
||||
if (($this->configuration['use_baselinker_integration'] ?? 'N') === 'Y') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parent::isRunnable();
|
||||
}
|
||||
}
|
||||
720
bundles/KupShop/DropshipBundle/Transfer/ChannableTransfer.php
Normal file
720
bundles/KupShop/DropshipBundle/Transfer/ChannableTransfer.php
Normal file
@@ -0,0 +1,720 @@
|
||||
<?php
|
||||
|
||||
namespace KupShop\DropshipBundle\Transfer;
|
||||
|
||||
use KupShop\AdminBundle\Util\ActivityLog;
|
||||
use KupShop\DropshipBundle\TransferInterface;
|
||||
use KupShop\DropshipBundle\Util\TransferWorker;
|
||||
use KupShop\KupShopBundle\Context\ContextManager;
|
||||
use KupShop\KupShopBundle\Context\CountryContext;
|
||||
use KupShop\KupShopBundle\Context\CurrencyContext;
|
||||
use KupShop\KupShopBundle\Context\LanguageContext;
|
||||
use KupShop\KupShopBundle\Query\JsonOperator;
|
||||
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
|
||||
use KupShop\KupShopBundle\Util\Contexts;
|
||||
use KupShop\KupShopBundle\Util\StringUtil;
|
||||
use KupShop\OrderingBundle\Event\OrderEvent;
|
||||
use KupShop\OrderingBundle\Util\Order\OrderInfo;
|
||||
use KupShop\OrderingBundle\Util\Order\OrderItemInfo;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Query\Operator;
|
||||
use Query\QueryBuilder;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use Symfony\Contracts\Service\Attribute\Required;
|
||||
|
||||
class ChannableTransfer extends AbstractTransfer implements TransferInterface
|
||||
{
|
||||
protected static string $type = 'channable';
|
||||
protected static string $name = 'Channable';
|
||||
|
||||
public const API_URL_START = 'https://api.channable.com/v1/companies/';
|
||||
|
||||
public ContextManager $contextManager;
|
||||
|
||||
private HttpClientInterface $httpClient;
|
||||
/** @required */
|
||||
public TransferWorker $transferWorker;
|
||||
|
||||
protected LoggerInterface $logger;
|
||||
|
||||
public function __construct(HttpClientInterface $httpClient, LoggerInterface $logger)
|
||||
{
|
||||
$this->httpClient = $httpClient;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
#[Required]
|
||||
final public function setContextManager(ContextManager $contextManager)
|
||||
{
|
||||
$this->contextManager = $contextManager;
|
||||
}
|
||||
|
||||
public function sendTrackingInfoToChannable(): void
|
||||
{
|
||||
$qb = $this->getOrdersForUpdate();
|
||||
|
||||
foreach ($qb->execute() as $item) {
|
||||
$order = new \Order();
|
||||
$order->createFromDB($item['id']);
|
||||
|
||||
$connectionData = $this->getConnectionData();
|
||||
|
||||
if (!$connectionData) {
|
||||
$this->addActivityLog('V nastavení dropshipmentu chybí potřebné údaje pro komunikaci s Channable',
|
||||
severity: ActivityLog::SEVERITY_WARNING);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$orderInfo = ServiceContainer::getService(OrderInfo::class);
|
||||
$delivery = $order->getDeliveryType()->getDelivery()->class;
|
||||
$packages = array_keys($orderInfo->getPackages($order));
|
||||
|
||||
$connectionData['url'] .= '/'.$item['channable_id'].'/shipment';
|
||||
|
||||
// load order items
|
||||
$connectionData['body']['products'] = json_decode($order->note_admin, true)['channable']['channable_item_ids'] ?? [];
|
||||
$connectionData['body']['packages'] = $packages[0];
|
||||
$connectionData['body']['delivery'] = $delivery;
|
||||
|
||||
if ($this->sendTrackingInfo($connectionData)) {
|
||||
$order->logHistory('Informace o balíku byly odeslány do Channable');
|
||||
$this->addActivityLog('Informace o balíku byly odeslány do Channable - objednávka: '.$order->order_no, $connectionData['body'], severity: ActivityLog::SEVERITY_SUCCESS);
|
||||
|
||||
sqlQuery('UPDATE order_dropshipment SET data = JSON_OBJECT("trackingSent", "Y")
|
||||
WHERE id_order = :order_id AND id_dropshipment = :id_dropshipment',
|
||||
['order_id' => $item['id'], 'id_dropshipment' => $this->dropshipment['id']]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->addActivityLog('Nepodařilo se odestal informace o balíku do Channable u objednávky č. '.$order->order_no);
|
||||
$order->logHistory('Nepodařilo se odestal informace o balíku do Channable');
|
||||
}
|
||||
}
|
||||
|
||||
protected function getExternalData(\SimpleXMLElement $xml): array
|
||||
{
|
||||
// TODO: Implement getExternalData() method.
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function getDeliveryTypeByConfiguration(\SimpleXMLElement $order): ?\DeliveryType
|
||||
{
|
||||
// TODO: Implement getDeliveryTypeByConfiguration() method.
|
||||
return null;
|
||||
}
|
||||
|
||||
public function in(array $config): void
|
||||
{
|
||||
$ordersJson = $this->getOrders();
|
||||
$orders = json_decode($ordersJson, true)['orders'] ?? [];
|
||||
|
||||
$countryContext = Contexts::get(CountryContext::class);
|
||||
|
||||
foreach ($orders as $orderData) {
|
||||
$this->contextManager->activateContexts([CountryContext::class => $orderData['data']['billing']['country_code'] ?? $countryContext->getDefaultId()],
|
||||
function () use ($orderData) {
|
||||
// log incoming order to Kibana
|
||||
$this->logger->notice(self::$name.' IN', $orderData);
|
||||
|
||||
$externalId = $orderData['channel_id'];
|
||||
if (!$externalId) {
|
||||
$this->addActivityLog(
|
||||
'Nepodařilo se naimportovat objednávku do e-shopu, protože nemá externí ID. V odpovědi z Channable chybí ID',
|
||||
$orderData
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// hledame i podle platform_id, protoze nektere objednavky to maji nastavene jako id_external
|
||||
$order = $this->getOrderByExternalId($externalId) ?? $this->getOrderByExternalId($orderData['platform_id'] ?? '');
|
||||
|
||||
if ($order) {
|
||||
$this->updateChannableOrder($order, $orderData);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$order = sqlGetConnection()->transactional(function () use ($externalId, $orderData) {
|
||||
$currencyInfo = $this->getCurrencyInfo($orderData['data']['price']['currency']);
|
||||
|
||||
$data = $this->getOrderBaseData($orderData);
|
||||
$data['currency_rate'] = $currencyInfo->rate->asFloat();
|
||||
|
||||
$order = $this->createChannableOrder($data, $externalId);
|
||||
|
||||
$orderItems = [];
|
||||
foreach ($orderData['data']['products'] as $item) {
|
||||
[$code, $ean] = $this->getProductFromItem($item);
|
||||
[$productId, $variationId] = $this->getProductByCode($code, $ean ?? null);
|
||||
|
||||
$pieces = toDecimal($item['quantity']);
|
||||
$piecePrice = $this->convertPrice(toDecimal($item['price']), $currencyInfo);
|
||||
$totalPrice = $this->convertPrice(toDecimal($item['price'])->mul($pieces), $currencyInfo);
|
||||
|
||||
if ($productId) {
|
||||
$product = \Variation::createProductOrVariation($productId, $variationId);
|
||||
$product->createFromDB();
|
||||
$product->sell($variationId, $pieces->asFloat());
|
||||
|
||||
$piecePrice = $piecePrice->removeVat($product->vat);
|
||||
$totalPrice = $totalPrice->removeVat($product->vat);
|
||||
}
|
||||
|
||||
$productTitle = empty($product->title) ? $item['title'] : $product->title;
|
||||
|
||||
$itemData = [
|
||||
'id_order' => $order->id,
|
||||
'id_product' => $product->id ?? null,
|
||||
'id_variation' => $variationId,
|
||||
'pieces' => $pieces->asFloat(),
|
||||
'piece_price' => $piecePrice->asFloat(),
|
||||
'total_price' => $totalPrice->asFloat(),
|
||||
'descr' => $productTitle,
|
||||
'tax' => $item['tax'] ?? $product->vat ?? '',
|
||||
];
|
||||
|
||||
sqlQueryBuilder()
|
||||
->insert('order_items')
|
||||
->directValues($itemData)
|
||||
->execute();
|
||||
$itemData['id'] = sqlInsertId();
|
||||
|
||||
$orderItems[] = $itemData;
|
||||
|
||||
$this->itemCreatedEvent(
|
||||
product: $product ?? null,
|
||||
idVariation: (int) $variationId,
|
||||
piecePrice: $piecePrice,
|
||||
pieces: $pieces->asInteger(),
|
||||
data: [
|
||||
'row' => $itemData,
|
||||
'items_table' => 'order_items',
|
||||
],
|
||||
order: $order
|
||||
);
|
||||
|
||||
unset($product);
|
||||
}
|
||||
|
||||
$this->insertDeliveryItem($order, $orderData, $orderItems ?? []);
|
||||
$order->recalculate(round: false);
|
||||
|
||||
if (!$order->isPaid() && $data['status_payed'] == 1) {
|
||||
$this->payDropshipOrder($order);
|
||||
}
|
||||
|
||||
return $order;
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
$this->transferWorker->logException($e, $this);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function out(array $config): void
|
||||
{
|
||||
if (isDevelopment()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->sendTrackingInfoToChannable();
|
||||
$this->sendCancelledOrders();
|
||||
}
|
||||
|
||||
protected function sendCancelledOrders()
|
||||
{
|
||||
$configuration = $this->getConfiguration();
|
||||
|
||||
if ($configuration['do_not_storno_orders'] == 'Y' ?? false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$qb = $this->getCancelledOrders();
|
||||
|
||||
foreach ($qb->execute() as $cancelledOrder) {
|
||||
$order = new \Order();
|
||||
$order->createFromDB($cancelledOrder['id']);
|
||||
|
||||
$connectionData = $this->getConnectionData();
|
||||
|
||||
if (!$connectionData) {
|
||||
$this->addActivityLog('V nastavení dropshipmentu chybí potřebné údaje pro komunikaci s Channable',
|
||||
severity: ActivityLog::SEVERITY_WARNING);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$connectionData['url'] .= '/'.$cancelledOrder['channable_id'].'/cancel';
|
||||
|
||||
$connectionData['body']['products'] = json_decode($order->note_admin, true)['channable']['channable_item_ids'] ?? [];
|
||||
|
||||
if ($this->sendCancelledOrder($connectionData)) {
|
||||
$order->logHistory('Informace o zrušení objednávky byly odeslány do Channable');
|
||||
sqlQuery('UPDATE order_dropshipment SET data = JSON_OBJECT("cancelSent", "Y")
|
||||
WHERE id_order = :order_id AND id_dropshipment = :id_dropshipment',
|
||||
['order_id' => $cancelledOrder['id'], 'id_dropshipment' => $this->dropshipment['id']]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->addActivityLog('Nepodařilo se odeslat informace o zrušení objednávky do Channable u objednávky č. '.$order->order_no);
|
||||
|
||||
$order->logHistory('Nepodařilo se odeslat informace o zrušení objednávky do Channable');
|
||||
}
|
||||
}
|
||||
|
||||
protected function getOrders(): ?string
|
||||
{
|
||||
$connectionData = $this->getConnectionData();
|
||||
|
||||
if (!$connectionData) {
|
||||
$this->addActivityLog('V nastavení dropshipmentu chybí potřebné údaje pro komunikaci s Channable',
|
||||
severity: ActivityLog::SEVERITY_WARNING);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$lastSyncTime = $this->getLastSyncTime();
|
||||
|
||||
// potrebuju prevest $lastSyncTime na UTC cas, jinak se stavalo, ze se objednavky vubec na shop nedostavaly
|
||||
$time = new \DateTime($lastSyncTime);
|
||||
$time->setTimezone(new \DateTimeZone('UTC'));
|
||||
|
||||
$lastSyncTime = $time->format('Y-m-d H:i:s');
|
||||
|
||||
$response = $this->httpClient->request(
|
||||
'GET',
|
||||
$connectionData['url'],
|
||||
[
|
||||
'headers' => [
|
||||
'Authorization' => $connectionData['api_key'],
|
||||
],
|
||||
'query' => [
|
||||
'last_modified_after' => $lastSyncTime ?? date('Y-m-d H:i:s', strtotime('-1 day')),
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
if ($response->getStatusCode() != 200) {
|
||||
$this->addActivityLog('Nepodařilo se načíst objednávky z Channable', [$response->getContent(false)]);
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
$logData = ['last_sync_time' => $lastSyncTime];
|
||||
$this->addActivityLog('Načtení objednávek z Channable proběhlo úspěšně', data: $logData, severity: ActivityLog::SEVERITY_SUCCESS);
|
||||
|
||||
return $response->getContent(false);
|
||||
}
|
||||
|
||||
protected function sendTrackingInfo(array $data): bool
|
||||
{
|
||||
$response = $this->httpClient->request(
|
||||
'POST',
|
||||
$data['url'],
|
||||
[
|
||||
'headers' => [
|
||||
'Authorization' => $data['api_key'],
|
||||
'Content-Type' => 'application/json',
|
||||
],
|
||||
'body' => json_encode([
|
||||
'order_item_ids' => $data['body']['products'],
|
||||
'tracking_code' => (string) $data['body']['packages'],
|
||||
]),
|
||||
]
|
||||
);
|
||||
|
||||
if ($response->getStatusCode() != 200) {
|
||||
$this->addActivityLog('[Channable] Odesílání informací o doručení - chybná response', [$response->getContent(false)]);
|
||||
$this->addActivityLog('Nepodařilo se odeslat informace o balíku do Channable', [$response->getContent(false)]);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->addActivityLog('[Channable] Odesílání informací o doručení - response', [$response->getContent(false)], severity: ActivityLog::SEVERITY_SUCCESS);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function sendCancelledOrder(array $data): bool
|
||||
{
|
||||
$response = $this->httpClient->request(
|
||||
'POST',
|
||||
$data['url'],
|
||||
[
|
||||
'headers' => [
|
||||
'Authorization' => $data['api_key'],
|
||||
'Content-Type' => 'application/json',
|
||||
],
|
||||
'body' => json_encode([
|
||||
'order_item_ids' => $data['body']['products'],
|
||||
]),
|
||||
]
|
||||
);
|
||||
|
||||
if ($response->getStatusCode() != 200) {
|
||||
$this->addActivityLog('Nepodařilo se odeslat informace o zrušení objednávky do Channable', [$response->getContent(false)]);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function updateChannableOrder(\Order $order, array $orderData): void
|
||||
{
|
||||
$data = $this->getOrderBaseData($orderData);
|
||||
|
||||
if (!$order->isPaid() && $data['status_payed'] == 1) {
|
||||
sqlQueryBuilder()
|
||||
->update('orders')
|
||||
->directValues(['status_payed' => 1])
|
||||
->where(Operator::equals(['id' => $order->id]))
|
||||
->execute();
|
||||
|
||||
$this->payDropshipOrder($order);
|
||||
}
|
||||
}
|
||||
|
||||
protected function getOrderBaseData(array $order): array
|
||||
{
|
||||
$configuration = $this->getConfiguration();
|
||||
|
||||
$data = [];
|
||||
|
||||
$useMarketplaceOrderNo = $configuration['use_marketplace_order_no'] ?? 'N';
|
||||
if ($useMarketplaceOrderNo === 'Y') {
|
||||
$data['order_no'] = $order['channel_id'];
|
||||
}
|
||||
$data['note_invoice'] = $order['channel_id'];
|
||||
|
||||
$data['date_created'] = $this->dateTimeConvertor($order['created']);
|
||||
$data['date_updated'] = $this->dateTimeConvertor($order['modified']);
|
||||
$data['currency'] = $order['data']['price']['currency'];
|
||||
$data['status'] = 0;
|
||||
$data['status_payed'] = $order['status_paid'] === 'paid' ? 1 : 0;
|
||||
|
||||
$data['total_price'] = 0.0000; // 0 => RECALCULATE
|
||||
|
||||
// INVOICE
|
||||
$data['invoice_name'] = $order['data']['billing']['first_name'].' '.$order['data']['billing']['middle_name'];
|
||||
$data['invoice_surname'] = $order['data']['billing']['last_name'] ?? '';
|
||||
$data['invoice_firm'] = $order['data']['billing']['company'] ?? '';
|
||||
$data['invoice_dic'] = $order['data']['billing']['vat_number'] ?? '';
|
||||
$data['invoice_street'] = $order['data']['billing']['address1'] ?? '';
|
||||
$data['invoice_city'] = $order['data']['billing']['city'] ?? '';
|
||||
$data['invoice_zip'] = $order['data']['billing']['zip_code'] ?? '';
|
||||
$data['invoice_country'] = $order['data']['billing']['country_code'] ?? '';
|
||||
$data['invoice_email'] = $order['data']['billing']['email'] ?? '';
|
||||
$data['invoice_phone'] = $order['data']['customer']['phone'] ?? '';
|
||||
|
||||
// DELIVERY
|
||||
$data['delivery_name'] = $order['data']['shipping']['first_name'].' '.$order['data']['shipping']['middle_name'];
|
||||
$data['delivery_surname'] = $order['data']['shipping']['last_name'] ?? '';
|
||||
$data['delivery_firm'] = $order['data']['shipping']['company'] ?? '';
|
||||
$data['delivery_street'] = $order['data']['shipping']['address1'] ?? '';
|
||||
$data['delivery_city'] = $order['data']['shipping']['city'] ?? '';
|
||||
$data['delivery_zip'] = $order['data']['shipping']['zip_code'] ?? '';
|
||||
$data['delivery_country'] = $order['data']['shipping']['country_code'] ?? '';
|
||||
$data['delivery_phone'] = $order['data']['customer']['phone'] ?? '';
|
||||
|
||||
$deliveryType = $this->getDelivery($order);
|
||||
$data['delivery_type'] = $deliveryType->name ?? '';
|
||||
$data['id_delivery'] = $deliveryType->id ?? null;
|
||||
|
||||
$data['id_language'] = $this->getOrderLanguage($order, $configuration);
|
||||
|
||||
$channableOrderItemIds = [];
|
||||
foreach ($order['data']['products'] as $item) {
|
||||
$channableOrderItemIds[] = $item['channable_order_item_id'] ?? null;
|
||||
}
|
||||
|
||||
$data['note_admin'] = json_encode(
|
||||
[
|
||||
'channable' => [
|
||||
'id' => $order['id'],
|
||||
'channel_name' => $order['channel_name'],
|
||||
'platform_name' => $order['platform_name'],
|
||||
'project_id' => $order['project_id'],
|
||||
'order_config_id' => $order['order_config_id'],
|
||||
'platform_id' => $order['platform_id'],
|
||||
'channel_id' => $order['channel_id'],
|
||||
'channable_item_ids' => $channableOrderItemIds,
|
||||
],
|
||||
],
|
||||
);
|
||||
|
||||
$flags = ['DSC'];
|
||||
$data['flags'] = implode(',', $flags);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function createChannableOrder(array $data, int|string $externalId)
|
||||
{
|
||||
/** @var \Order $order */
|
||||
$order = sqlGetConnection()->transactional(function () use ($data) {
|
||||
sqlQueryBuilder()
|
||||
->insert('orders')
|
||||
->directValues(array_merge(['source' => OrderInfo::ORDER_SOURCE_DROPSHIP], $data))
|
||||
->execute();
|
||||
|
||||
$order = \Order::get((int) sqlInsertId());
|
||||
|
||||
// dispatch order created event - order no is generated in event subscriber
|
||||
$this->eventDispatcher->dispatch(new OrderEvent($order), OrderEvent::ORDER_CREATED);
|
||||
|
||||
return $order;
|
||||
});
|
||||
|
||||
// vytvorim mapovani na dropshipment
|
||||
sqlQueryBuilder()
|
||||
->insert('order_dropshipment')
|
||||
->directValues(
|
||||
[
|
||||
'id_order' => $order->id,
|
||||
'id_dropshipment' => $this->dropshipment['id'],
|
||||
'id_external' => $externalId,
|
||||
]
|
||||
)->execute();
|
||||
|
||||
// zalogovat informaci o vytvoreni objednavky
|
||||
$order->logHistory(
|
||||
sprintf('[Dropshipment] <a href="javascript:nw(\'Dropshipment\', %s);">%s</a>: %s',
|
||||
$this->dropshipment['id'],
|
||||
'channable',
|
||||
$externalId)
|
||||
);
|
||||
|
||||
return $order;
|
||||
}
|
||||
|
||||
protected function getDelivery(array $order): ?\DeliveryType
|
||||
{
|
||||
$channelConfiguration = $this->getChannelConfiguration($order);
|
||||
|
||||
$deliveryId = null;
|
||||
$country = $order['data']['shipping']['country_code'];
|
||||
foreach ($channelConfiguration['deliveries'] ?? [] as $delivery) {
|
||||
if (empty($delivery['country']) || $delivery['country'] === $country) {
|
||||
$deliveryId = (int) $delivery['id_delivery'];
|
||||
}
|
||||
}
|
||||
|
||||
$paymentId = empty($channelConfiguration['payments']['default']) ? null : (int) $channelConfiguration['payments']['default'];
|
||||
|
||||
return $this->findDeliveryType($deliveryId, $paymentId);
|
||||
}
|
||||
|
||||
protected function getChannelConfiguration(array $order): array
|
||||
{
|
||||
$configuration = $this->getConfiguration();
|
||||
$channelName = $order['channel_name'];
|
||||
|
||||
$channelConfiguration = null;
|
||||
$default = null;
|
||||
|
||||
foreach ($configuration['marketplaces'] ?? [] as $marketplace) {
|
||||
if (StringUtil::slugify($marketplace['name']) === StringUtil::slugify($channelName)) {
|
||||
$channelConfiguration = $marketplace;
|
||||
break;
|
||||
}
|
||||
|
||||
if (empty($marketplace['name'])) {
|
||||
$default = $marketplace;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$channelConfiguration && $default) {
|
||||
$channelConfiguration = $default;
|
||||
}
|
||||
|
||||
return $channelConfiguration ?: [];
|
||||
}
|
||||
|
||||
protected function insertDeliveryItem(\Order $order, array $data, array $orderItems): void
|
||||
{
|
||||
$dbcfg = \Settings::getDefault();
|
||||
|
||||
$deliveryPrice = $this->getDeliveryPrice($order);
|
||||
|
||||
$tax = ($dbcfg->delivery_config['from_products'] ?? 'N') == 'Y' ?
|
||||
$this->getDeliveryVatFromProducts($orderItems)
|
||||
: $deliveryPrice->getVat() ?? \DecimalConstants::zero();
|
||||
|
||||
$shippingPrice = toDecimal($data['data']['price']['shipping']) ?? $deliveryPrice->getPriceWithVat() ?? \DecimalConstants::zero();
|
||||
$shippingPrice = $shippingPrice->removeVat($tax);
|
||||
|
||||
$insertData = [
|
||||
'id_order' => $order->id,
|
||||
'id_product' => null,
|
||||
'id_variation' => null,
|
||||
'pieces' => 1,
|
||||
'pieces_reserved' => 1,
|
||||
'piece_price' => $shippingPrice ?? $deliveryPrice->getPriceWithoutVat(),
|
||||
'total_price' => $shippingPrice ?? $deliveryPrice->getPriceWithoutVat(),
|
||||
'tax' => $tax ?? 0,
|
||||
'descr' => 'Doprava a platba',
|
||||
'note' => json_encode(['item_type' => OrderItemInfo::TYPE_DELIVERY]),
|
||||
];
|
||||
|
||||
sqlQueryBuilder()
|
||||
->insert('order_items')
|
||||
->directValues($insertData)
|
||||
->execute();
|
||||
}
|
||||
|
||||
protected function getDeliveryPrice(\Order $order)
|
||||
{
|
||||
$deliveryType = $order->getDeliveryType();
|
||||
|
||||
$deliveryPrice = $this->contextManager->activateContexts(
|
||||
[
|
||||
CurrencyContext::class => $order->currency,
|
||||
],
|
||||
function () use ($deliveryType) {
|
||||
return $deliveryType->getPrice();
|
||||
}
|
||||
);
|
||||
|
||||
return $deliveryPrice;
|
||||
}
|
||||
|
||||
protected function getOrdersForUpdate(): QueryBuilder
|
||||
{
|
||||
$qb = parent::getOrdersForUpdate();
|
||||
$qb->addSelect('JSON_VALUE(o.note_admin, "$.channable.id") as channable_id');
|
||||
$qb->andWhere(
|
||||
Operator::equalsNullable([JsonOperator::value('od.data', 'trackingSent') => null])
|
||||
)
|
||||
->andWhere(
|
||||
Operator::not(Operator::equalsNullable([JsonOperator::value('o.note_admin', 'channable.*') => null]))
|
||||
)
|
||||
->andWhere(Operator::inIntArray([(int) str_replace('"', '', $this->getConfigurationStatus())], 'o.status'));
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
protected function getConfigurationStatus(): string
|
||||
{
|
||||
return sqlQueryBuilder()
|
||||
->select('JSON_EXTRACT(configuration, "$.statuses[0]") as statuses')
|
||||
->from('dropshipment')
|
||||
->where(Operator::equals(['id' => $this->dropshipment['id']]))
|
||||
->execute()->fetchOne();
|
||||
}
|
||||
|
||||
protected function getCancelledOrders(): QueryBuilder
|
||||
{
|
||||
return sqlQueryBuilder()
|
||||
->select('o.id as id, od.id_external as id_external, JSON_VALUE(o.note_admin, "$.channable.id") as channable_id')
|
||||
->from('orders', 'o')
|
||||
->join('o', 'order_dropshipment', 'od', 'o.id = od.id_order AND od.id_dropshipment = :dropshipment_id')
|
||||
->where('o.status_storno = 1')
|
||||
->andWhere(Operator::equalsNullable([JsonOperator::value('od.data', 'cancelSent') => null]))
|
||||
->andWhere(Operator::equalsNullable([JsonOperator::value('od.data', 'trackingSent') => null]))
|
||||
->setParameter('dropshipment_id', $this->dropshipment['id']);
|
||||
}
|
||||
|
||||
public function prepareConfigurationData(array $data): array
|
||||
{
|
||||
foreach ($data['marketplaces'] ?? [] as $key => $marketplace) {
|
||||
// zpracovani mapovani doprav
|
||||
foreach ($marketplace['deliveries'] ?? [] as $dKey => $delivery) {
|
||||
$delivery = array_filter($delivery);
|
||||
|
||||
if (!empty($delivery['delete'])) {
|
||||
unset($data['marketplaces'][$key]['deliveries'][$dKey]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($dKey <= 0) {
|
||||
if (!empty($delivery['id_delivery'])) {
|
||||
$data['marketplaces'][$key]['deliveries'][] = $delivery;
|
||||
}
|
||||
|
||||
unset($data['marketplaces'][$key]['deliveries'][$dKey]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$data['marketplaces'] = array_values($data['marketplaces'] ?? []);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function getConnectionData(): ?array
|
||||
{
|
||||
$url = self::API_URL_START;
|
||||
$data = $this->getChannableData();
|
||||
|
||||
$companyId = str_replace('"', '', $data['company_id']);
|
||||
$project_id = str_replace('"', '', $data['project_id']);
|
||||
|
||||
if (!$data['company_id'] || !$data['project_id'] || !$data['api_key']) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'url' => $url.(int) $companyId.'/projects/'.(int) $project_id.'/orders',
|
||||
'api_key' => 'Bearer '.$data['api_key'],
|
||||
];
|
||||
}
|
||||
|
||||
protected function getChannableData()
|
||||
{
|
||||
return sqlQueryBuilder()
|
||||
->select('JSON_EXTRACT(data, \'$.company_id\') as company_id, JSON_EXTRACT(data, \'$.project_id\') as project_id, source_url as api_key')
|
||||
->from('dropshipment')
|
||||
->where(Operator::equals(['id' => $this->dropshipment['id']]))
|
||||
->execute()
|
||||
->fetchAssociative();
|
||||
}
|
||||
|
||||
protected function getOrderLanguage(array $order, array $configuration): string
|
||||
{
|
||||
$languageContext = Contexts::get(LanguageContext::class);
|
||||
$marketplace = array_filter($configuration['marketplaces'], fn ($marketplace) => strtolower($marketplace['name']) === strtolower($order['channel_name'])) ?? [];
|
||||
$marketplace = reset($marketplace);
|
||||
|
||||
return $marketplace['settings']['id_language'] ?? $languageContext->getDefaultId();
|
||||
}
|
||||
|
||||
protected function getProductFromItem(array $item): array
|
||||
{
|
||||
$return = [null, null];
|
||||
|
||||
if ($item['id']) {
|
||||
$return[0] = $item['id'];
|
||||
}
|
||||
|
||||
if ($item['ean']) {
|
||||
$return[1] = $item['ean'];
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
protected function getDeliveryVatFromProducts(array $orderItems): int
|
||||
{
|
||||
return array_reduce($orderItems, function ($maxVat, $product) {
|
||||
$vat = (int) $product['tax'] ?? 0;
|
||||
|
||||
return ($vat > $maxVat) ? $vat : $maxVat;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts datetime to specific timezone.
|
||||
*/
|
||||
private function dateTimeConvertor(string $datetime, string $timezone = 'Europe/Prague')
|
||||
{
|
||||
$date = new \DateTime($datetime);
|
||||
$date->setTimezone(new \DateTimeZone($timezone));
|
||||
|
||||
return $date->format('Y-m-d H:i:s');
|
||||
}
|
||||
}
|
||||
810
bundles/KupShop/DropshipBundle/Transfer/ExpandoTransfer.php
Normal file
810
bundles/KupShop/DropshipBundle/Transfer/ExpandoTransfer.php
Normal file
@@ -0,0 +1,810 @@
|
||||
<?php
|
||||
|
||||
namespace KupShop\DropshipBundle\Transfer;
|
||||
|
||||
use KupShop\AdminBundle\Util\ActivityLog;
|
||||
use KupShop\DropshipBundle\Exception\TransferException;
|
||||
use KupShop\DropshipBundle\TransferInterface;
|
||||
use KupShop\KupShopBundle\Config;
|
||||
use KupShop\KupShopBundle\Context\CountryContext;
|
||||
use KupShop\KupShopBundle\Context\CurrencyContext;
|
||||
use KupShop\KupShopBundle\Context\LanguageContext;
|
||||
use KupShop\KupShopBundle\Context\VatContext;
|
||||
use KupShop\KupShopBundle\Query\JsonOperator;
|
||||
use KupShop\KupShopBundle\Util\Contexts;
|
||||
use KupShop\KupShopBundle\Util\Functional\Mapping;
|
||||
use KupShop\KupShopBundle\Util\Logging\SentryLogger;
|
||||
use KupShop\KupShopBundle\Util\StringUtil;
|
||||
use KupShop\OrderingBundle\Util\Order\OrderInfo;
|
||||
use KupShop\OrderingBundle\Util\Order\OrderItemInfo;
|
||||
use KupShop\OrderingBundle\Util\Order\OrderUtil;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Query\Operator;
|
||||
use Query\QueryBuilder;
|
||||
|
||||
class ExpandoTransfer extends AbstractTransfer implements TransferInterface
|
||||
{
|
||||
use \DatabaseCommunication;
|
||||
|
||||
protected static string $type = 'expando';
|
||||
protected static string $name = 'Expando';
|
||||
|
||||
public const URL_FULFILLMENT = 'https://app.expan.do/api/v2/fulfillment';
|
||||
|
||||
protected array $existsOrdersCache = [];
|
||||
|
||||
protected OrderUtil $orderUtil;
|
||||
|
||||
protected LoggerInterface $logger;
|
||||
|
||||
private $sentryLogger;
|
||||
private $currencyContext;
|
||||
private $countryContext;
|
||||
private $vatContext;
|
||||
|
||||
public function __construct(
|
||||
SentryLogger $sentryLogger,
|
||||
CurrencyContext $currencyContext,
|
||||
CountryContext $countryContext,
|
||||
VatContext $vatContext,
|
||||
LoggerInterface $logger,
|
||||
) {
|
||||
$this->sentryLogger = $sentryLogger;
|
||||
$this->currencyContext = $currencyContext;
|
||||
$this->countryContext = $countryContext;
|
||||
$this->vatContext = $vatContext;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* @required
|
||||
*/
|
||||
public function setOrderUtil(OrderUtil $orderUtil): void
|
||||
{
|
||||
$this->orderUtil = $orderUtil;
|
||||
}
|
||||
|
||||
public function in(array $config): void
|
||||
{
|
||||
$cfg = Config::get();
|
||||
|
||||
if (!($xml = $this->loadXML())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$currencies = $this->currencyContext->getAll();
|
||||
$update = $config['update'] ?? null;
|
||||
|
||||
$orders = [];
|
||||
foreach ($xml->order as $order) {
|
||||
$orders[] = $order;
|
||||
}
|
||||
|
||||
$this->loadExistsOrdersCache($orders);
|
||||
|
||||
foreach (array_reverse($orders) as $order) {
|
||||
if (!$this->isDropshipOrderValidToImport($order)) {
|
||||
// objednavka uz existuje
|
||||
if ($update && ($orderUpdateObj = $this->getOrderByExternalId((string) $order->orderId))) {
|
||||
$this->updateDropshipOrder($orderUpdateObj, $order);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$currencyInfo = $this->getCurrencyInfo(
|
||||
$this->getCurrency($order)
|
||||
);
|
||||
|
||||
$currencyCode = $this->isPriceConvertionEnabled() ? $this->currencyContext->getDefaultId() : $currencyInfo->getCurrencyCode();
|
||||
|
||||
$status = 0;
|
||||
$statusStorno = 0;
|
||||
|
||||
if (!$this->isOrderStatusValidToImport($order)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$statusXML = strtolower((string) $order->orderStatus);
|
||||
// import cancelled order
|
||||
if ($statusXML === 'canceled') {
|
||||
$statuses = array_filter(array_keys($cfg['Order']['Status']['global']), fn ($x) => $x < 100);
|
||||
$status = end($statuses);
|
||||
$statusStorno = 1;
|
||||
}
|
||||
|
||||
$totalPrice = (string) $order->totalPrice;
|
||||
$totalPrice = toDecimal($totalPrice);
|
||||
|
||||
$deliveryType = $this->getDeliveryType($order);
|
||||
$delivery_type = $order->shippingMethod.' - '.$order->paymentMethod;
|
||||
$id_delivery = null;
|
||||
if ($deliveryType) {
|
||||
$delivery_type = $deliveryType->name;
|
||||
$id_delivery = $deliveryType->id;
|
||||
}
|
||||
|
||||
$customer = $order->customer;
|
||||
|
||||
$userData = $this->getUserData($order);
|
||||
|
||||
$country = $userData['invoice_country'];
|
||||
|
||||
// Dropship Expando flag
|
||||
$flags = ['DSE'];
|
||||
// pokud je OSS aktivni, tak nastavit flag
|
||||
if ($this->vatContext->isCountryOssActive($country) && empty($userData['invoice_dic'])) {
|
||||
$flags[] = 'OSS';
|
||||
}
|
||||
|
||||
$data = [
|
||||
'currency' => $currencyCode,
|
||||
'currency_rate' => $currencies[$currencyCode]->getRate(),
|
||||
'date_created' => $this->getOrderDateCreated($order)->format('Y-m-d H:i:s'),
|
||||
'status' => $status,
|
||||
'status_storno' => $statusStorno,
|
||||
'total_price' => $totalPrice,
|
||||
|
||||
'id_delivery' => $id_delivery,
|
||||
'delivery_type' => $delivery_type,
|
||||
|
||||
'flags' => implode(',', $flags),
|
||||
|
||||
'note_invoice' => (string) $order->orderId,
|
||||
|
||||
'note_admin' => json_encode(
|
||||
[
|
||||
'expando' => [
|
||||
'orderId' => (string) $order->orderId,
|
||||
'marketplace' => (string) $order->marketplace,
|
||||
'country' => $customer->address->country->__toString(),
|
||||
],
|
||||
]
|
||||
),
|
||||
|
||||
'source' => OrderInfo::ORDER_SOURCE_DROPSHIP,
|
||||
];
|
||||
|
||||
// nastavim jazyk objednavky
|
||||
if (findModule(\Modules::TRANSLATIONS)) {
|
||||
$marketplaceSettings = $this->getMarketplaceConfiguration($order)['settings'] ?? [];
|
||||
$data['id_language'] = !empty($marketplaceSettings['id_language']) ? $marketplaceSettings['id_language'] : Contexts::get(LanguageContext::class)->getDefaultId();
|
||||
}
|
||||
|
||||
$data = array_merge(
|
||||
$data,
|
||||
$userData
|
||||
);
|
||||
|
||||
$orderObj = sqlGetConnection()->transactional(function () use ($order, $data, $currencyInfo) {
|
||||
$orderObj = $this->createDropshipOrder($order, $data);
|
||||
$orderObj->logHistory('[Dropshipment] Marketplace: '.$order->marketplace);
|
||||
|
||||
// Vytvorit itemy objednavky
|
||||
$lastItemTax = null;
|
||||
foreach ($order->items->item as $item) {
|
||||
if (!$this->isOrderItemValidToImport($item)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[$productID, $variationID] = $this->getProductByItem($item);
|
||||
|
||||
$itemTax = $this->getItemTax($item);
|
||||
// pokud je zapnuta konverze do defaultni meny, tak nastavim i defaultni DPH
|
||||
if ($this->isPriceConvertionEnabled()) {
|
||||
$itemTax = getAdminVat()['value'];
|
||||
}
|
||||
|
||||
$itemPrice = $this->convertPrice($this->getItemPiecePrice($item), $currencyInfo)->removeVat(toDecimal($itemTax));
|
||||
$itemQuantity = toDecimal((string) $item->itemQuantity);
|
||||
|
||||
if ($variationID) {
|
||||
$product = new \Variation($productID, $variationID);
|
||||
} else {
|
||||
$product = new \Product($productID);
|
||||
}
|
||||
|
||||
// sell product
|
||||
if ($productID) {
|
||||
$product->createFromDB($productID);
|
||||
$product->sell($variationID, $itemQuantity->asInteger());
|
||||
}
|
||||
|
||||
$title = $product->title;
|
||||
if (!empty($title) && $variationID) {
|
||||
$title .= ' ('.$product->variationTitle.')';
|
||||
}
|
||||
|
||||
$modifiedItem = $this->modifyItem([
|
||||
'id_order' => $orderObj->id,
|
||||
'id_product' => $productID,
|
||||
'id_variation' => $variationID,
|
||||
'pieces' => $itemQuantity,
|
||||
'pieces_reserved' => $itemQuantity,
|
||||
'piece_price' => $itemPrice,
|
||||
'total_price' => $itemPrice->mul($itemQuantity),
|
||||
'descr' => (!empty($title)) ? $title : (string) $item->itemName,
|
||||
'tax' => $itemTax,
|
||||
'note' => json_encode(['item_type' => OrderItemInfo::TYPE_PRODUCT]),
|
||||
], $item);
|
||||
|
||||
$this->insertSQL('order_items', $modifiedItem);
|
||||
$modifiedItem['id'] = sqlInsertId();
|
||||
|
||||
$this->itemCreatedEvent(
|
||||
product: $product,
|
||||
idVariation: (int) $variationID,
|
||||
piecePrice: $itemPrice,
|
||||
pieces: $itemQuantity->asInteger(),
|
||||
data: [
|
||||
'row' => $modifiedItem,
|
||||
'items_table' => 'order_items',
|
||||
],
|
||||
order: $orderObj
|
||||
);
|
||||
|
||||
$lastItemTax = $itemTax;
|
||||
}
|
||||
|
||||
// Doprava a platba
|
||||
$paymentPrice = toDecimal((string) $order->paymentPrice);
|
||||
$shippingPrice = toDecimal((string) $order->shippingPrice);
|
||||
$shippingTaxValue = toDecimal((string) $order->price->delivery->tax);
|
||||
|
||||
$shippingTax = $this->calculateItemTax($shippingPrice, $shippingTaxValue)->printFloatValue(-2);
|
||||
if (!$shippingTax && $lastItemTax) {
|
||||
$shippingTax = $lastItemTax;
|
||||
}
|
||||
|
||||
// pokud je zapnuta konverze do defaultni meny, tak nastavim i defaultni DPH
|
||||
if ($this->isPriceConvertionEnabled()) {
|
||||
$shippingTax = toDecimal(getAdminVat()['value']);
|
||||
}
|
||||
|
||||
$deliveryItemPrice = $shippingPrice->add($paymentPrice);
|
||||
$deliveryItemPrice = $this->convertPrice($deliveryItemPrice, $currencyInfo)->removeVat($shippingTax);
|
||||
|
||||
$this->insertDeliveryItem(
|
||||
[
|
||||
'id_order' => $orderObj->id,
|
||||
'id_product' => null,
|
||||
'id_variation' => null,
|
||||
'pieces' => 1,
|
||||
'pieces_reserved' => 1,
|
||||
'piece_price' => $deliveryItemPrice,
|
||||
'total_price' => $deliveryItemPrice,
|
||||
'descr' => 'Doprava a platba',
|
||||
'tax' => $shippingTax,
|
||||
'note' => json_encode(['item_type' => OrderItemInfo::TYPE_DELIVERY]),
|
||||
]
|
||||
);
|
||||
|
||||
$this->contextManager->activateOrder($orderObj, function () use ($orderObj) {
|
||||
$orderObj->recalculate(round: false);
|
||||
});
|
||||
|
||||
return $orderObj;
|
||||
});
|
||||
|
||||
$this->modifyInsertedOrder($orderObj, $order);
|
||||
}
|
||||
}
|
||||
|
||||
public function out(array $config): void
|
||||
{
|
||||
if (isDevelopment()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!($accessToken = $config['api_key'] ?? null)) {
|
||||
$this->addActivityLog(
|
||||
'V nastavení dropshipmentu chybí API klíč!',
|
||||
$config
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$qb = $this->getBaseOutQueryBuilder();
|
||||
|
||||
foreach ($qb->execute() as $item) {
|
||||
$order = new \Order();
|
||||
$order->createFromDB($item['id']);
|
||||
|
||||
$balikobotData = json_decode($item['balikobot_data'] ?? '', true) ?? [];
|
||||
|
||||
$expandoData = $order->getData('expando');
|
||||
|
||||
[$carrier, $carrierName] = $this->getOutCarrier($order);
|
||||
|
||||
$params = [
|
||||
'marketplaceOrderId' => $expandoData['orderId'],
|
||||
'marketplace' => $this->getOutMarketplace($expandoData['marketplace'] ?? ''),
|
||||
'shipDate' => $order->date_handle ? $order->date_handle->format('c') : (new \DateTime())->format('c'),
|
||||
'status' => 'Shipped',
|
||||
'carrier' => $carrier,
|
||||
'carrierName' => $carrierName,
|
||||
];
|
||||
|
||||
if ($order->package_id) {
|
||||
$params['trackingNumber'] = $order->package_id;
|
||||
$params['trackingUrl'] = $this->getTrackingUrl($order, $balikobotData);
|
||||
}
|
||||
|
||||
try {
|
||||
$this->sendFulfillment($accessToken, $params);
|
||||
|
||||
$expandoData['fulfillmentSent'] = true;
|
||||
|
||||
$order->setData('expando', $expandoData);
|
||||
$order->logHistory('Objednávka v Expandu byla aktualizována na vyřízenou');
|
||||
} catch (TransferException $e) {
|
||||
addActivityLog(ActivityLog::SEVERITY_ERROR, ActivityLog::TYPE_SYNC, $e->getMessage(), $params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function loadExistsOrdersCache(array $orders): void
|
||||
{
|
||||
$ids = array_map(function ($x) {
|
||||
return (string) $x->orderId;
|
||||
}, $orders);
|
||||
|
||||
$qb = sqlQueryBuilder()
|
||||
->select('id_order AS id, id_external AS expandoId')
|
||||
->from('order_dropshipment')
|
||||
->sendToMaster()
|
||||
->where(Operator::andX(
|
||||
Operator::inStringArray($ids, 'id_external'),
|
||||
Operator::equals([
|
||||
'id_dropshipment' => $this->dropshipment['id'],
|
||||
])
|
||||
));
|
||||
|
||||
$this->existsOrdersCache = Mapping::mapKeys($qb->execute()->fetchAllAssociative(), function ($k, $v) {
|
||||
return [$v['expandoId'], $v['id']];
|
||||
});
|
||||
}
|
||||
|
||||
protected function isDropshipOrderValidToImport(\SimpleXMLElement $xml): bool
|
||||
{
|
||||
return parent::isDropshipOrderValidToImport($xml) && $this->isValidToImport($xml);
|
||||
}
|
||||
|
||||
protected function isOrderItemValidToImport(\SimpleXMLElement $item): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function isValidToImport(\SimpleXMLElement $order): bool
|
||||
{
|
||||
// pokud objednavka uz existuje, tak vracim false, protoze ji nechci importovat znovu
|
||||
if ($this->existsOrdersCache[(string) $order->orderId] ?? false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function getExternalData(\SimpleXMLElement $xml): array
|
||||
{
|
||||
return [
|
||||
(string) $xml->orderId,
|
||||
[
|
||||
'marketplace' => (string) $xml->marketplace,
|
||||
'country' => $xml->customer->address->country->__toString(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
protected function getOrderDateCreated(\SimpleXMLElement $order): \DateTimeInterface
|
||||
{
|
||||
try {
|
||||
$date = new \DateTime((string) $order->purchaseDate, new \DateTimeZone('UTC'));
|
||||
$date->setTimezone(new \DateTimeZone('Europe/Prague'));
|
||||
} catch (\Exception) {
|
||||
$date = new \DateTime();
|
||||
}
|
||||
|
||||
return $date;
|
||||
}
|
||||
|
||||
protected function getCurrency(\SimpleXMLElement $order): ?string
|
||||
{
|
||||
$currencyCode = (string) $order->currencyCode;
|
||||
if (empty($currencyCode)) {
|
||||
$currencyCode = 'EUR';
|
||||
}
|
||||
|
||||
return $currencyCode;
|
||||
}
|
||||
|
||||
protected function getDeliveryType(\SimpleXMLElement $orderItem): ?\DeliveryType
|
||||
{
|
||||
return $this->getDeliveryTypeByConfiguration($orderItem);
|
||||
}
|
||||
|
||||
protected function isOrderStatusValidToImport(\SimpleXMLElement $order): bool
|
||||
{
|
||||
$statusXML = strtolower((string) $order->orderStatus);
|
||||
|
||||
$importStatuses = $this->getConfiguration()['import_statuses'] ?? [];
|
||||
|
||||
// default behaviour if config is not set
|
||||
if (empty($importStatuses)) {
|
||||
return !in_array($statusXML, $this->getIgnoredStatuses());
|
||||
}
|
||||
|
||||
return in_array($statusXML, $importStatuses);
|
||||
}
|
||||
|
||||
protected function getIgnoredStatuses(): array
|
||||
{
|
||||
return [
|
||||
'canceled',
|
||||
'pending',
|
||||
'shipped',
|
||||
];
|
||||
}
|
||||
|
||||
protected function getItemPiecePrice(\SimpleXMLElement $item): \Decimal
|
||||
{
|
||||
return toDecimal((string) $item->itemPrice);
|
||||
}
|
||||
|
||||
protected function getItemTax(\SimpleXMLElement $item): float
|
||||
{
|
||||
$line = $item->lineItemPrice;
|
||||
|
||||
$priceWithTax = toDecimal((string) $line->withTax);
|
||||
$taxValue = toDecimal((string) $line->tax);
|
||||
|
||||
return $this->calculateItemTax($priceWithTax, $taxValue)->printFloatValue(-2);
|
||||
}
|
||||
|
||||
protected function calculateItemTax(\Decimal $priceWithTax, \Decimal $taxValue): \Decimal
|
||||
{
|
||||
if ($taxValue->isZero()) {
|
||||
return toDecimal(0);
|
||||
}
|
||||
|
||||
$priceWithoutTax = $priceWithTax->sub($taxValue);
|
||||
|
||||
return $priceWithTax->div($priceWithoutTax)->mul(\DecimalConstants::hundred())->sub(\DecimalConstants::hundred())->round();
|
||||
}
|
||||
|
||||
protected function getProductByItem(\SimpleXMLElement $item): array
|
||||
{
|
||||
[$productId, $variationId] = $this->findProduct((string) $item->itemId);
|
||||
|
||||
if (empty($productId) && empty($variationId)) {
|
||||
return $this->getProductByCode((string) $item->itemId, null);
|
||||
}
|
||||
|
||||
return [$productId, $variationId];
|
||||
}
|
||||
|
||||
protected function getUserData(\SimpleXMLElement $order): array
|
||||
{
|
||||
$customer = $order->customer;
|
||||
|
||||
$country = $customer->address->country->__toString();
|
||||
// validate country
|
||||
if (!isset($this->countryContext->getAll()[$country])) {
|
||||
$country = '';
|
||||
}
|
||||
|
||||
return [
|
||||
'invoice_name' => (string) $customer->firstname,
|
||||
'invoice_surname' => (string) $customer->surname,
|
||||
'invoice_email' => (string) $customer->email,
|
||||
'invoice_phone' => (string) $customer->phone,
|
||||
'invoice_street' => $this->prepareAddressStreet($customer->address),
|
||||
'invoice_city' => (string) $customer->address->city,
|
||||
'invoice_zip' => (string) $customer->address->zip,
|
||||
'invoice_country' => $country,
|
||||
'invoice_custom_address' => (string) $customer->address->address3,
|
||||
'invoice_state' => (string) $customer->address->stateOrRegion,
|
||||
'invoice_dic' => (string) $customer->taxId,
|
||||
|
||||
'delivery_name' => (string) $customer->firstname,
|
||||
'delivery_surname' => (string) $customer->surname,
|
||||
'delivery_street' => $this->prepareAddressStreet($customer->address),
|
||||
'delivery_city' => (string) $customer->address->city,
|
||||
'delivery_zip' => (string) $customer->address->zip,
|
||||
'delivery_country' => $country,
|
||||
'delivery_custom_address' => (string) $customer->address->address3,
|
||||
'delivery_state' => (string) $customer->address->stateOrRegion,
|
||||
];
|
||||
}
|
||||
|
||||
protected function findProduct(string $itemId): array
|
||||
{
|
||||
$codes = explode('_', $itemId);
|
||||
$productCode = $codes[0] ?? null;
|
||||
$variationCode = $codes[1] ?? null;
|
||||
|
||||
if ($variationCode) {
|
||||
$variation = sqlQueryBuilder()
|
||||
->select('id, id_product')
|
||||
->from('products_variations')
|
||||
->where(Operator::equals(['code' => $variationCode]))
|
||||
->execute()->fetch();
|
||||
|
||||
if ($variation) {
|
||||
return [(int) $variation['id_product'], (int) $variation['id']];
|
||||
}
|
||||
|
||||
$variation = sqlQueryBuilder()
|
||||
->select('id, id_product')
|
||||
->from('products_variations')
|
||||
->where(Operator::equals(['id' => $variationCode]))
|
||||
->execute()->fetch();
|
||||
|
||||
if ($variation) {
|
||||
return [(int) $variation['id_product'], (int) $variation['id']];
|
||||
}
|
||||
}
|
||||
|
||||
if ($productCode) {
|
||||
$productId = sqlQueryBuilder()
|
||||
->select('id')
|
||||
->from('products')
|
||||
->where(Operator::equals(['code' => $productCode]))
|
||||
->execute()->fetchColumn();
|
||||
|
||||
if ($productId) {
|
||||
return [(int) $productId, null];
|
||||
}
|
||||
|
||||
$productId = sqlQueryBuilder()
|
||||
->select('id')
|
||||
->from('products')
|
||||
->where(Operator::equals(['id' => $productCode]))
|
||||
->execute()->fetchColumn();
|
||||
|
||||
if ($productId) {
|
||||
return [(int) $productId, null];
|
||||
}
|
||||
}
|
||||
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
protected function insertDeliveryItem(array $item): void
|
||||
{
|
||||
$this->insertSQL('order_items', $item);
|
||||
}
|
||||
|
||||
protected function getTrackingUrl(\Order $order, array $balikobotData): ?string
|
||||
{
|
||||
if (!($trackingUrl = ($balikobotData['response'][0]['track_url'] ?? false))) {
|
||||
if ($deliveryType = $order->getDeliveryType()) {
|
||||
if ($delivery = $deliveryType->getDelivery()) {
|
||||
$trackingUrl = $delivery->getTrackAndTraceLink($order->package_id, $order);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$trackingUrl) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $trackingUrl;
|
||||
}
|
||||
|
||||
protected function getOutCarrier(\Order $order): array
|
||||
{
|
||||
$marketplace = $order->getData('expando')['marketplace'] ?? null;
|
||||
|
||||
if ($deliveryType = $order->getDeliveryType()) {
|
||||
if ($delivery = $deliveryType->getDelivery()) {
|
||||
if ($delivery instanceof \DHL) {
|
||||
return ['DHL', null];
|
||||
} elseif ($delivery instanceof \DPD) {
|
||||
return ['DPD', null];
|
||||
} elseif ($delivery instanceof \GLS) {
|
||||
return ['GLS', null];
|
||||
} elseif ($delivery instanceof \PPL) {
|
||||
// Amazon nezna PPL, takze to musim poslat jako "Other"
|
||||
if (strpos(mb_strtolower($marketplace), 'amazon') !== false) {
|
||||
return ['Other', 'PPL'];
|
||||
}
|
||||
|
||||
return ['PPL', null];
|
||||
} elseif ($delivery instanceof \Fedex) {
|
||||
return ['FedEx', null];
|
||||
} elseif ($delivery instanceof \UPS) {
|
||||
return ['UPS', 'Standard'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ['Other', $order->getDeliveryType()->delivery ?? ''];
|
||||
}
|
||||
|
||||
private function prepareAddressStreet(\SimpleXMLElement $address): string
|
||||
{
|
||||
$street = [(string) $address->address1, (string) $address->address2];
|
||||
$street = implode(', ', array_filter($street));
|
||||
|
||||
return $street;
|
||||
}
|
||||
|
||||
private function getOutMarketplace(string $marketplace): string
|
||||
{
|
||||
return preg_replace('/\s+/', '_', mb_strtolower($marketplace));
|
||||
}
|
||||
|
||||
private function sendFulfillment(string $accessToken, array $params): void
|
||||
{
|
||||
$ch = curl_init();
|
||||
$this->logger->notice(sprintf('EXPANDO REQUEST: ', static::getType()),
|
||||
[
|
||||
'Data' => $params,
|
||||
'Type' => static::getType(),
|
||||
]);
|
||||
curl_setopt($ch, CURLOPT_URL, self::URL_FULFILLMENT);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type:application/json', 'Authorization: Bearer '.$accessToken]);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($params));
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
$result = curl_exec($ch);
|
||||
$this->logger->notice(sprintf('EXPANDO RESPONSE: ', static::getType()),
|
||||
[
|
||||
'data' => (array) $result,
|
||||
'Type' => static::getType(),
|
||||
]);
|
||||
curl_close($ch);
|
||||
|
||||
$result = json_decode($result, true);
|
||||
|
||||
if ($result['message'] ?? null) {
|
||||
if (StringUtil::startsWith($result['message'], 'ValidationError')) {
|
||||
throw new TransferException(
|
||||
sprintf('Send fulfillment failed with message "%s"', $result['message'])
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function getMarketplaceConfiguration(\SimpleXMLElement $order): array
|
||||
{
|
||||
// konfigurace dropshipmentu
|
||||
$configuration = $this->getConfiguration();
|
||||
|
||||
// budu hledat konfiguraci podle marketplacu
|
||||
$orderMarketplace = $order->marketplace->__toString();
|
||||
|
||||
$marketplaceConfiguration = null;
|
||||
$default = null;
|
||||
foreach ($configuration['marketplaces'] ?? [] as $marketplace) {
|
||||
// podle nazvu zkusim najit konfiguraci pro dany marketplace
|
||||
if (StringUtil::slugify($marketplace['name']) === StringUtil::slugify($orderMarketplace)) {
|
||||
$marketplaceConfiguration = $marketplace;
|
||||
break;
|
||||
}
|
||||
|
||||
if (empty($marketplace['name'])) {
|
||||
$default = $marketplace;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$marketplaceConfiguration && $default) {
|
||||
$marketplaceConfiguration = $default;
|
||||
}
|
||||
|
||||
return $marketplaceConfiguration ?: [];
|
||||
}
|
||||
|
||||
protected function getDeliveryTypeByConfiguration(\SimpleXMLElement $order): ?\DeliveryType
|
||||
{
|
||||
$config = $this->getMarketplaceConfiguration($order);
|
||||
|
||||
$deliveryId = null;
|
||||
// najdu ID dopravy
|
||||
$country = $order->customer->address->country->__toString();
|
||||
foreach ($config['deliveries'] ?? [] as $item) {
|
||||
if (empty($item['country']) || $item['country'] == $country) {
|
||||
$deliveryId = (int) $item['id_delivery'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// nactu ID platby
|
||||
$paymentId = empty($config['payments']['default']) ? null : (int) $config['payments']['default'];
|
||||
|
||||
return $this->findDeliveryType($deliveryId, $paymentId);
|
||||
}
|
||||
|
||||
protected function updateDropshipOrder(\Order $order, \SimpleXMLElement $xml): void
|
||||
{
|
||||
$updateStatuses = $this->configuration['update'] ?? [];
|
||||
|
||||
$currentStatusXml = strtolower((string) $xml->orderStatus);
|
||||
// pokud je povoleny update stavu
|
||||
if (!in_array($currentStatusXml, $updateStatuses)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// storno objednavky
|
||||
if ($currentStatusXml === 'canceled') {
|
||||
if (!$order->status_storno) {
|
||||
$order->storno(false, '[Expando] Storno objednávky z Expanda', false);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($currentStatusXml !== 'shipped') {
|
||||
return;
|
||||
}
|
||||
|
||||
// update stavu po expedici
|
||||
$dic = (string) $xml->customer->taxId;
|
||||
if ($dic != $order->invoice_dic) {
|
||||
$this->updateSQL('orders', ['invoice_dic' => $dic], ['id' => $order->id]);
|
||||
if ($this->vatContext->isCountryOssActive($order->invoice_country)) {
|
||||
if (empty($dic)) {
|
||||
$this->orderUtil->addFlag($order, 'OSS');
|
||||
} else {
|
||||
$this->orderUtil->removeFlag($order, 'OSS');
|
||||
$this->orderUtil->removeVat($order, 0, false, true); // $keepFinalPrice = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function getBaseOutQueryBuilder(): QueryBuilder
|
||||
{
|
||||
$qb = sqlQueryBuilder()
|
||||
->select('o.id')
|
||||
->from('orders', 'o')
|
||||
->where(
|
||||
Operator::not(
|
||||
Operator::equalsNullable([JsonOperator::value('o.note_admin', 'expando.orderId') => null])
|
||||
)
|
||||
)
|
||||
->andWhere(
|
||||
Operator::equalsNullable([JsonOperator::value('o.note_admin', 'expando.fulfillmentSent') => null])
|
||||
)
|
||||
->andWhere('o.status_storno = 0')
|
||||
->andWhere(Operator::inIntArray(getStatuses('handled'), 'o.status'))
|
||||
->andWhere('DATEDIFF(NOW(), o.date_handle) <= 30') // ne starsi nez 30 dni
|
||||
->groupBy('o.id');
|
||||
|
||||
if (findModule(\Modules::BALIKONOS, 'provider') === 'balikobot') {
|
||||
$qb->addSelect('b.data as balikobot_data')
|
||||
->leftJoin('o', 'balikonos', 'b', 'b.id_order = o.id');
|
||||
}
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
public function prepareConfigurationData(array $data): array
|
||||
{
|
||||
foreach ($data['marketplaces'] ?? [] as $key => $marketplace) {
|
||||
// zpracovani mapovani doprav
|
||||
foreach ($marketplace['deliveries'] ?? [] as $dKey => $delivery) {
|
||||
$delivery = array_filter($delivery);
|
||||
|
||||
if (!empty($delivery['delete'])) {
|
||||
unset($data['marketplaces'][$key]['deliveries'][$dKey]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($dKey <= 0) {
|
||||
if (!empty($delivery['id_delivery'])) {
|
||||
$data['marketplaces'][$key]['deliveries'][] = $delivery;
|
||||
}
|
||||
|
||||
unset($data['marketplaces'][$key]['deliveries'][$dKey]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$data['marketplaces'] = array_values($data['marketplaces'] ?? []);
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
441
bundles/KupShop/DropshipBundle/Transfer/GenericTransfer.php
Normal file
441
bundles/KupShop/DropshipBundle/Transfer/GenericTransfer.php
Normal file
@@ -0,0 +1,441 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
40
bundles/KupShop/DropshipBundle/Transfer/HeurekaTransfer.php
Normal file
40
bundles/KupShop/DropshipBundle/Transfer/HeurekaTransfer.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace KupShop\DropshipBundle\Transfer;
|
||||
|
||||
use KupShop\DropshipBundle\TransferInterface;
|
||||
use Query\Operator;
|
||||
|
||||
class HeurekaTransfer extends AbstractTransfer implements TransferInterface
|
||||
{
|
||||
protected static string $type = 'heureka';
|
||||
protected static string $name = 'Heureka';
|
||||
|
||||
protected function getExternalData(\SimpleXMLElement $xml): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function getDeliveryTypeByConfiguration(\SimpleXMLElement $order): ?\DeliveryType
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function in(array $config): void
|
||||
{
|
||||
}
|
||||
|
||||
public function out(array $config): void
|
||||
{
|
||||
}
|
||||
|
||||
public function getConfigurationVariables(): array
|
||||
{
|
||||
$vars['deliveryTypes'] = sqlQueryBuilder()->select('id,name')
|
||||
->from('delivery_type_delivery')
|
||||
->andWhere(Operator::equals(['class' => 'OsobniOdber']))
|
||||
->execute()->fetchAllKeyValue();
|
||||
|
||||
return $vars;
|
||||
}
|
||||
}
|
||||
465
bundles/KupShop/DropshipBundle/Transfer/MallTransfer.php
Normal file
465
bundles/KupShop/DropshipBundle/Transfer/MallTransfer.php
Normal file
@@ -0,0 +1,465 @@
|
||||
<?php
|
||||
|
||||
namespace KupShop\DropshipBundle\Transfer;
|
||||
|
||||
use KupShop\AdminBundle\Util\ActivityLog;
|
||||
use KupShop\DropshipBundle\TransferInterface;
|
||||
use KupShop\KupShopBundle\Context\CountryContext;
|
||||
use KupShop\KupShopBundle\Context\CurrencyContext;
|
||||
use KupShop\KupShopBundle\Query\JsonOperator;
|
||||
use KupShop\KupShopBundle\Util\Contexts;
|
||||
use KupShop\OrderingBundle\Util\Order\OrderInfo;
|
||||
use KupShop\OrderingBundle\Util\Order\OrderItemInfo;
|
||||
use Query\Operator;
|
||||
|
||||
class MallTransfer extends AbstractTransfer implements TransferInterface
|
||||
{
|
||||
use \DatabaseCommunication;
|
||||
|
||||
protected static string $type = 'mall';
|
||||
protected static string $name = 'MALL';
|
||||
|
||||
public const URL_API = 'https://mpapi.mallgroup.com/v1/orders/';
|
||||
|
||||
public function in(array $config): void
|
||||
{
|
||||
if (!($xml = $this->loadXML())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$countryContext = Contexts::get(CountryContext::class);
|
||||
|
||||
$countries = $countryContext->getAll();
|
||||
$defaultCurrency = Contexts::get(CurrencyContext::class)->getDefaultId();
|
||||
|
||||
foreach ($xml->ORDER as $order) {
|
||||
if (!$this->isDropshipOrderValidToImport($order)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$deliveryType = $this->getDeliveryType($order);
|
||||
$delivery_type = $order->DELIVERY_METHOD.' - '.$order->PAYMENT_TYPE;
|
||||
$id_delivery = null;
|
||||
if ($deliveryType) {
|
||||
$delivery_type = $deliveryType->name;
|
||||
$id_delivery = $deliveryType->id;
|
||||
}
|
||||
|
||||
$deliveryPrice = $order->DELIVERY_PRICE + $order->COD_PRICE;
|
||||
$customer = $order->ADDRESS;
|
||||
|
||||
$country = (string) $customer->COUNTRY;
|
||||
if (!($countries[$country] ?? false)) {
|
||||
$countries = array_keys($countries);
|
||||
$country = reset($countries);
|
||||
}
|
||||
|
||||
$customer_name = explode(' ', (string) $customer->NAME);
|
||||
$name = array_shift($customer_name);
|
||||
$surname = implode(' ', $customer_name);
|
||||
|
||||
$data = [
|
||||
'date_created' => (new \DateTime())->format('Y-m-d H:i:s'),
|
||||
'status' => 0,
|
||||
|
||||
'id_delivery' => $id_delivery,
|
||||
'delivery_type' => $delivery_type,
|
||||
|
||||
'flags' => 'DSM',
|
||||
|
||||
'note_invoice' => (string) $order->ID,
|
||||
'note_admin' => json_encode([
|
||||
'mall' => [
|
||||
'order_id' => (string) $order->ID,
|
||||
'cash_on_delivery' => (string) $order->COD,
|
||||
'delivery_method_id' => (string) $order->DELIVERY_METHOD_ID,
|
||||
],
|
||||
]),
|
||||
|
||||
'source' => OrderInfo::ORDER_SOURCE_DROPSHIP,
|
||||
|
||||
'invoice_name' => $name ?? '',
|
||||
'invoice_surname' => $surname ?? '',
|
||||
'invoice_email' => (string) $customer->EMAIL,
|
||||
'invoice_phone' => (string) $customer->PHONE,
|
||||
'invoice_street' => (string) $customer->STREET,
|
||||
'invoice_city' => (string) $customer->CITY,
|
||||
'invoice_zip' => (string) $customer->ZIP,
|
||||
'invoice_country' => $country,
|
||||
|
||||
'delivery_name' => $name ?? '',
|
||||
'delivery_surname' => $surname ?? '',
|
||||
'delivery_street' => (string) $customer->STREET,
|
||||
'delivery_city' => (string) $customer->CITY,
|
||||
'delivery_zip' => (string) $customer->ZIP,
|
||||
'delivery_country' => $country,
|
||||
'currency' => $defaultCurrency,
|
||||
];
|
||||
|
||||
$orderObj = sqlGetConnection()->transactional(function () use ($order, $data, $deliveryPrice) {
|
||||
$orderObj = $this->createOrder($order, $data);
|
||||
// zaloguju dalsi informace o objednavce
|
||||
$orderObj->logHistory(implode('<br>', [
|
||||
'Číslo Mall objednávky: '.(string) $order->ID,
|
||||
'ID výdejního místa: '.(string) $order->DELIVERY_METHOD_ID,
|
||||
'SHIP_DATE: '.(string) $order->SHIP_DATE,
|
||||
]));
|
||||
|
||||
$orderID = $orderObj->id;
|
||||
|
||||
// prepare order items
|
||||
$items = $this->prepareItems($order->ITEMS);
|
||||
// add delivery item
|
||||
$items[] = $this->getDeliveryItem($deliveryPrice);
|
||||
if ($order->DISCOUNT > 0) {
|
||||
// add discount item
|
||||
$items[] = $this->getDiscountItem(-1 * $order->DISCOUNT);
|
||||
}
|
||||
|
||||
// insert order items
|
||||
foreach ($items as $item) {
|
||||
if ($item['id_product']) {
|
||||
$product = new \Product();
|
||||
$product->createFromDB($item['id_product']);
|
||||
$product->sell($item['id_variation'], toDecimal($item['pieces'])->asInteger());
|
||||
}
|
||||
|
||||
$item['id_order'] = $orderID;
|
||||
|
||||
$this->insertSQL('order_items', $item);
|
||||
$item['id'] = sqlInsertId();
|
||||
|
||||
$this->itemCreatedEvent(
|
||||
product: $product ?? null,
|
||||
idVariation: (int) $item['id_variation'],
|
||||
piecePrice: toDecimal($item['piece_price']),
|
||||
pieces: toDecimal($item['pieces'])->asInteger(),
|
||||
data: [
|
||||
'row' => $item,
|
||||
'items_table' => 'order_items',
|
||||
],
|
||||
order: $orderObj
|
||||
);
|
||||
|
||||
unset($product);
|
||||
}
|
||||
|
||||
$orderObj->recalculate(round: false);
|
||||
|
||||
return $orderObj;
|
||||
});
|
||||
|
||||
$this->modifyInsertedOrder($orderObj, $order);
|
||||
}
|
||||
}
|
||||
|
||||
protected function getDeliveryItem($deliveryPrice): array
|
||||
{
|
||||
$deliveryPrice = toDecimal($deliveryPrice);
|
||||
$deliveryPrice = $deliveryPrice->removeVat(getVat());
|
||||
|
||||
return [
|
||||
'id_product' => null,
|
||||
'id_variation' => null,
|
||||
'pieces' => 1,
|
||||
'pieces_reserved' => 1,
|
||||
'piece_price' => $deliveryPrice,
|
||||
'total_price' => $deliveryPrice,
|
||||
'descr' => 'Doprava a platba',
|
||||
'tax' => getVat(),
|
||||
'note' => '{"item_type":"delivery"}',
|
||||
];
|
||||
}
|
||||
|
||||
protected function getDiscountItem($discountPrice): array
|
||||
{
|
||||
$discountPrice = toDecimal($discountPrice);
|
||||
$discountPrice = $discountPrice->removeVat(getVat());
|
||||
|
||||
return [
|
||||
'id_product' => null,
|
||||
'id_variation' => null,
|
||||
'pieces' => 1,
|
||||
'pieces_reserved' => 1,
|
||||
'piece_price' => $discountPrice,
|
||||
'total_price' => $discountPrice,
|
||||
'descr' => 'Celková sleva',
|
||||
'tax' => getVat(),
|
||||
'note' => '{"item_type":"discount"}',
|
||||
];
|
||||
}
|
||||
|
||||
protected function getExternalData(\SimpleXMLElement $xml): array
|
||||
{
|
||||
return [
|
||||
(string) $xml->ID,
|
||||
[
|
||||
'cash_on_delivery' => (string) $xml->COD,
|
||||
'delivery_method_id' => (string) $xml->DELIVERY_METHOD_ID,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function out(array $config): void
|
||||
{
|
||||
if (isDevelopment()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!($clientId = $config['api_key'] ?? null)) {
|
||||
$this->addActivityLog(
|
||||
'V nastavení chybí API klíč',
|
||||
$config
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->sendCancelledOrders($clientId);
|
||||
$this->sendShippedOrders($clientId);
|
||||
}
|
||||
|
||||
protected function sendCancelledOrders($clientId)
|
||||
{
|
||||
$qb = sqlQueryBuilder()->select('o.id')->from('orders', 'o')
|
||||
->where(Operator::isNotNull(JsonOperator::value('o.note_admin', 'mall.order_id')))
|
||||
->andWhere('o.status_storno = 1')
|
||||
->andWhere(Operator::isNull(JsonOperator::value('o.note_admin', 'mall.cancelledSent')))
|
||||
->groupBy('o.id');
|
||||
|
||||
foreach ($qb->execute() as $item) {
|
||||
$order = new \Order();
|
||||
$order->createFromDB($item['id']);
|
||||
$mallData = $order->getData('mall');
|
||||
$params = [
|
||||
'confirmed' => true,
|
||||
'status' => 'cancelled',
|
||||
];
|
||||
|
||||
if ($this->sendOrderUpdate($clientId, $mallData['order_id'], $params)) {
|
||||
$mallData['cancelledSent'] = true;
|
||||
|
||||
$order->setData('mall', $mallData);
|
||||
$order->logHistory('Objednávka v MALL byla aktualizována na status "cancelled"');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function sendShippedOrders($clientId)
|
||||
{
|
||||
$qb = sqlQueryBuilder()->select('o.id')->from('orders', 'o')
|
||||
->where(Operator::isNotNull(JsonOperator::value('o.note_admin', 'mall.order_id')))
|
||||
->andWhere('o.status_storno = 0 AND o.package_id IS NOT NULL')
|
||||
->andWhere(Operator::inIntArray($this->getShippedStatuses(), 'o.status'))
|
||||
->andWhere(Operator::isNull(JsonOperator::value('o.note_admin', 'mall.shippedSent')))
|
||||
->groupBy('o.id');
|
||||
|
||||
foreach ($qb->execute() as $item) {
|
||||
$order = new \Order();
|
||||
$order->createFromDB($item['id']);
|
||||
$mallData = $order->getData('mall');
|
||||
$packages = $this->orderInfo->getPackages($order);
|
||||
$package = $packages[$order->package_id] ?? end($packages);
|
||||
if (!$package) {
|
||||
continue;
|
||||
}
|
||||
$params = [
|
||||
'confirmed' => true,
|
||||
'status' => 'shipped',
|
||||
'tracking_number' => $package['package_id'],
|
||||
'tracking_url' => $package['track_url'] ?? '',
|
||||
];
|
||||
|
||||
if ($this->sendOrderUpdate($clientId, $mallData['order_id'], $params)) {
|
||||
$mallData['shippedSent'] = true;
|
||||
|
||||
$order->setData('mall', $mallData);
|
||||
$order->logHistory('Objednávka v MALL byla aktualizována na status "shipped"');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function getShippedStatuses(): ?array
|
||||
{
|
||||
return getStatuses('handled');
|
||||
}
|
||||
|
||||
protected function sendOrderUpdate(string $clientId, $mall_order_id, array $params): bool
|
||||
{
|
||||
$ch = curl_init();
|
||||
|
||||
$url = self::URL_API.$mall_order_id.'?client_id='.$clientId;
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type:application/json']);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($params));
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
|
||||
$result = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
$result = json_decode($result, true);
|
||||
|
||||
if ('OK' != $result['result']['status'] ?? null) {
|
||||
$message = $result['result']['message'] ?? '';
|
||||
$data = [
|
||||
'mall_order_id' => $mall_order_id,
|
||||
'params' => $params,
|
||||
'result' => $result['result'] ?? null,
|
||||
];
|
||||
|
||||
addActivityLog(ActivityLog::SEVERITY_ERROR, ActivityLog::TYPE_SYNC,
|
||||
sprintf('Dropshipment MALL: chyba při odeslání aktualizace stavů objednávky "%s"', $message), $data);
|
||||
|
||||
if (str_starts_with($message, 'Final status') && str_ends_with($message, 'cannot be changed')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function isDropshipOrderValidToImport(\SimpleXMLElement $xml): bool
|
||||
{
|
||||
return parent::isDropshipOrderValidToImport($xml) && $this->isValidToImport($xml);
|
||||
}
|
||||
|
||||
protected function isValidToImport(\SimpleXMLElement $order): bool
|
||||
{
|
||||
$found = sqlQueryBuilder()
|
||||
->select('id')
|
||||
->from('orders')
|
||||
->where(
|
||||
Operator::equals(
|
||||
[
|
||||
JsonOperator::value('note_admin', 'mall.order_id') => (string) $order->ID,
|
||||
]
|
||||
)
|
||||
)->execute()->fetchColumn();
|
||||
|
||||
// objednavka uz je vytvorena
|
||||
if ($found) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function createOrder(\SimpleXMLElement $order, array $data): \Order
|
||||
{
|
||||
return $this->createDropshipOrder($order, $data);
|
||||
}
|
||||
|
||||
protected function getDeliveryType(\SimpleXMLElement $orderItem): ?\DeliveryType
|
||||
{
|
||||
return $this->getDeliveryTypeByConfiguration($orderItem);
|
||||
}
|
||||
|
||||
private function prepareItems(\SimpleXMLElement $items): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($items as $item) {
|
||||
$itemCode = (string) $item->ID;
|
||||
$parsed = explode('_', $itemCode);
|
||||
|
||||
$productID = $parsed[0] ?? $itemCode;
|
||||
$variationID = $parsed[1] ?? null;
|
||||
|
||||
// check that productID exists
|
||||
if (!$this->selectSQL('products', ['id' => $productID], ['id'])->fetch()) {
|
||||
$productID = null;
|
||||
}
|
||||
|
||||
// check that variationID exists
|
||||
if ($variationID && !$this->selectSQL('products_variations', ['id' => $variationID], ['id'])->fetch()) {
|
||||
$variationID = null;
|
||||
}
|
||||
|
||||
// create name of item
|
||||
$descr = 'Položka kód: '.$itemCode;
|
||||
if ($productID) {
|
||||
$title = $this->selectSQL('products', ['id' => $productID], ['title'])->fetchColumn();
|
||||
if (!empty($title)) {
|
||||
$descr = $title;
|
||||
}
|
||||
if ($variationID) {
|
||||
$title = $this->selectSQL('products_variations', ['id' => $variationID], ['title'])->fetchColumn();
|
||||
if (!empty($title)) {
|
||||
$descr .= ' ('.$title.')';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$price = toDecimal((string) $item->PRICE);
|
||||
$vat = (string) $item->VAT;
|
||||
$pieces = toDecimal((string) $item->QUANTITY);
|
||||
|
||||
$price = $price->removeVat($vat);
|
||||
|
||||
$itemTotalPrice = $price->mul($pieces);
|
||||
|
||||
$result[] = $this->modifyItem([
|
||||
'id_product' => $productID,
|
||||
'id_variation' => $variationID,
|
||||
'pieces' => $pieces,
|
||||
'pieces_reserved' => (string) $item->QUANTITY,
|
||||
'piece_price' => $price,
|
||||
'total_price' => $itemTotalPrice,
|
||||
'tax' => (string) $item->VAT,
|
||||
'descr' => $descr,
|
||||
'note' => json_encode(['item_type' => OrderItemInfo::TYPE_PRODUCT]),
|
||||
], $item);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function getDeliveryTypeByConfiguration(\SimpleXMLElement $order): ?\DeliveryType
|
||||
{
|
||||
$deliveryMethod = (string) $order->DELIVERY_METHOD;
|
||||
$country = (string) $order->ADDRESS->COUNTRY;
|
||||
$cod = (string) $order->COD;
|
||||
|
||||
$config = $this->getConfiguration();
|
||||
|
||||
$deliveryId = null;
|
||||
foreach ($config['deliveries'] ?? [] as $item) {
|
||||
if ($deliveryMethod == $item['id_external'] && (empty($item['country']) || $item['country'] == $country)) {
|
||||
$deliveryId = (int) $item['id_delivery'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($cod > 0) {
|
||||
$paymentId = empty($config['payments']['cod']) ? null : (int) $config['payments']['cod'];
|
||||
} else {
|
||||
$paymentId = empty($config['payments']['paid']) ? null : (int) $config['payments']['paid'];
|
||||
}
|
||||
|
||||
return $this->findDeliveryType($deliveryId, $paymentId);
|
||||
}
|
||||
|
||||
public function prepareConfigurationData(array $data): array
|
||||
{
|
||||
foreach ($data['deliveries'] ?? [] as $key => $item) {
|
||||
$item = array_filter($item);
|
||||
|
||||
if (!empty($item['delete'])) {
|
||||
unset($data['deliveries'][$key]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($key <= 0) {
|
||||
if (!empty($item['id_external']) && !empty($item['id_delivery'])) {
|
||||
$data['deliveries'][] = $item;
|
||||
}
|
||||
|
||||
unset($data['deliveries'][$key]);
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
24
bundles/KupShop/DropshipBundle/TransferInterface.php
Normal file
24
bundles/KupShop/DropshipBundle/TransferInterface.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace KupShop\DropshipBundle;
|
||||
|
||||
interface TransferInterface
|
||||
{
|
||||
public static function getType(): string;
|
||||
|
||||
public static function getName(): string;
|
||||
|
||||
public function isRunnable(): bool;
|
||||
|
||||
public function setup(array $dropshipment): void;
|
||||
|
||||
public function process(): void;
|
||||
|
||||
public function in(array $config): void;
|
||||
|
||||
public function out(array $config): void;
|
||||
|
||||
public function prepareConfigurationData(array $data): array;
|
||||
|
||||
public function getConfigurationVariables(): array;
|
||||
}
|
||||
60
bundles/KupShop/DropshipBundle/Util/DropshipmentUtil.php
Normal file
60
bundles/KupShop/DropshipBundle/Util/DropshipmentUtil.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace KupShop\DropshipBundle\Util;
|
||||
|
||||
use Query\Operator;
|
||||
|
||||
class DropshipmentUtil
|
||||
{
|
||||
private ?array $dropshipmentsCache = null;
|
||||
|
||||
public function __construct(private TransferLocator $transferLocator)
|
||||
{
|
||||
}
|
||||
|
||||
public function getDropshipmentTypes(): array
|
||||
{
|
||||
return $this->transferLocator->getTypes();
|
||||
}
|
||||
|
||||
public function getDropshipment(int $id, bool $force = false): ?array
|
||||
{
|
||||
return $this->getDropshipments($force ? Operator::andX('1 = 1') : null)[$id] ?? null;
|
||||
}
|
||||
|
||||
public function getDropshipments(?callable $spec = null, ?int &$totalCount = null): array
|
||||
{
|
||||
if ($spec === null && $this->dropshipmentsCache !== null) {
|
||||
return $this->dropshipmentsCache;
|
||||
}
|
||||
|
||||
$useTotalCount = count(func_get_args()) > 1;
|
||||
|
||||
$qb = sqlQueryBuilder()
|
||||
->select('*')
|
||||
->from('dropshipment');
|
||||
|
||||
if ($spec !== null) {
|
||||
$qb->andWhere($spec);
|
||||
}
|
||||
|
||||
if ($useTotalCount) {
|
||||
$qb->addCalcRows();
|
||||
}
|
||||
|
||||
$result = [];
|
||||
foreach ($qb->execute() as $item) {
|
||||
$item['configuration'] = json_decode($item['configuration'] ?: '', true) ?: [];
|
||||
$item['data'] = json_decode($item['data'] ?: '', true) ?: [];
|
||||
$result[$item['id']] = $item;
|
||||
}
|
||||
|
||||
if ($useTotalCount) {
|
||||
$totalCount = (int) sqlFetchAssoc(sqlQuery('SELECT FOUND_ROWS() as total_count'))['total_count'];
|
||||
}
|
||||
|
||||
return $this->dropshipmentsCache = $result;
|
||||
}
|
||||
}
|
||||
44
bundles/KupShop/DropshipBundle/Util/TransferLocator.php
Normal file
44
bundles/KupShop/DropshipBundle/Util/TransferLocator.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace KupShop\DropshipBundle\Util;
|
||||
|
||||
use KupShop\DropshipBundle\Exception\TransferNotFoundException;
|
||||
use KupShop\DropshipBundle\TransferInterface;
|
||||
|
||||
class TransferLocator
|
||||
{
|
||||
private $transfers;
|
||||
|
||||
public function __construct(iterable $transfers)
|
||||
{
|
||||
$this->transfers = $transfers;
|
||||
}
|
||||
|
||||
public function getTypes(): array
|
||||
{
|
||||
return array_keys($this->getTransfers());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TransferInterface[]
|
||||
*/
|
||||
public function getTransfers(): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($this->transfers as $transfer) {
|
||||
$result[$transfer::getType()] = $transfer;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getTransfer(string $type): TransferInterface
|
||||
{
|
||||
if ($transfer = $this->getTransfers()[$type] ?? null) {
|
||||
return $transfer;
|
||||
}
|
||||
|
||||
throw new TransferNotFoundException($type);
|
||||
}
|
||||
}
|
||||
82
bundles/KupShop/DropshipBundle/Util/TransferWorker.php
Normal file
82
bundles/KupShop/DropshipBundle/Util/TransferWorker.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace KupShop\DropshipBundle\Util;
|
||||
|
||||
use KupShop\AdminBundle\Util\ActivityLog;
|
||||
use KupShop\DropshipBundle\Exception\TransferException;
|
||||
use KupShop\DropshipBundle\TransferInterface;
|
||||
use KupShop\KupShopBundle\Util\Logging\SentryLogger;
|
||||
use Query\Operator;
|
||||
|
||||
class TransferWorker
|
||||
{
|
||||
protected TransferLocator $transferLocator;
|
||||
protected DropshipmentUtil $dropshipmentUtil;
|
||||
|
||||
private SentryLogger $sentryLogger;
|
||||
|
||||
public function __construct(DropshipmentUtil $dropshipmentUtil, TransferLocator $transferLocator, SentryLogger $sentryLogger)
|
||||
{
|
||||
$this->dropshipmentUtil = $dropshipmentUtil;
|
||||
$this->transferLocator = $transferLocator;
|
||||
$this->sentryLogger = $sentryLogger;
|
||||
}
|
||||
|
||||
public function run(?int $id = null): void
|
||||
{
|
||||
$dropshipments = $id ? [$this->dropshipmentUtil->getDropshipment($id)] : $this->dropshipmentUtil->getDropshipments();
|
||||
|
||||
foreach ($dropshipments as $dropshipment) {
|
||||
$transfer = $this->transferLocator->getTransfer($dropshipment['type']);
|
||||
$transfer->setup($dropshipment);
|
||||
|
||||
// preskocim dropshipmenty, ktere se nemaji spoustet
|
||||
if (!$transfer->isRunnable()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$newLastSyncTime = date('Y-m-d H:i:s');
|
||||
$transfer->process();
|
||||
$this->updateLastSyncTime($newLastSyncTime, $transfer);
|
||||
} catch (\Throwable $e) {
|
||||
$this->logException($e, $transfer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function logException(\Throwable $e, TransferInterface $transfer): void
|
||||
{
|
||||
if (isLocalDevelopment()) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$message = sprintf('[Dropshipment] Chyba během zpracování: %s (ID: %s)', $transfer->dropshipment['name'], $transfer->dropshipment['id']);
|
||||
|
||||
$data = ['error' => $e->getMessage()];
|
||||
if (!($e instanceof TransferException)) {
|
||||
$this->sentryLogger->captureException($e);
|
||||
} else {
|
||||
$message = $e->getMessage();
|
||||
$data = array_merge($data, $e->getData());
|
||||
}
|
||||
|
||||
addActivityLog(
|
||||
ActivityLog::SEVERITY_ERROR,
|
||||
ActivityLog::TYPE_IMPORT,
|
||||
$message,
|
||||
$data
|
||||
);
|
||||
}
|
||||
|
||||
protected function updateLastSyncTime(string $lastSyncTime, TransferInterface $transfer): void
|
||||
{
|
||||
sqlQueryBuilder()
|
||||
->update('dropshipment')
|
||||
->directValues(['last_sync' => $lastSyncTime])
|
||||
->where(Operator::equals(['id' => $transfer->dropshipment['id']]))
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user