434 lines
14 KiB
PHP
434 lines
14 KiB
PHP
<?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;
|
|
}
|