Files
kupshop/bundles/KupShop/DropshipBundle/Transfer/MallTransfer.php
2025-08-02 16:30:27 +02:00

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;
}
}