466 lines
16 KiB
PHP
466 lines
16 KiB
PHP
<?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;
|
|
}
|
|
}
|