first commit
This commit is contained in:
549
bundles/External/HannahBundle/SAP/Synchronizer/OrderSynchronizer.php
vendored
Normal file
549
bundles/External/HannahBundle/SAP/Synchronizer/OrderSynchronizer.php
vendored
Normal file
@@ -0,0 +1,549 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace External\HannahBundle\SAP\Synchronizer;
|
||||
|
||||
use External\HannahBundle\SAP\Exception\SAPException;
|
||||
use External\HannahBundle\SAP\Exception\SAPManyException;
|
||||
use External\HannahBundle\SAP\MappingType;
|
||||
use External\HannahBundle\SAP\Util\SAPApi;
|
||||
use External\HannahBundle\SAP\Util\SAPOrderUpdater;
|
||||
use External\HannahBundle\Util\OrderUtil as SAPOrderUtil;
|
||||
use KupShop\AdminBundle\Util\ActivityLog;
|
||||
use KupShop\OrderingBundle\Util\Order\OrderInfo;
|
||||
use KupShop\OrderingBundle\Util\Order\OrderUtil;
|
||||
use KupShop\ReturnsBundle\Util\ReturnsUtil;
|
||||
use Query\Operator;
|
||||
use Query\Order;
|
||||
use Symfony\Component\Routing\Router;
|
||||
use Symfony\Contracts\Service\Attribute\Required;
|
||||
|
||||
class OrderSynchronizer extends BaseSynchronizer
|
||||
{
|
||||
protected static $type = 'order';
|
||||
|
||||
#[Required]
|
||||
public SAPApi $api;
|
||||
|
||||
#[Required]
|
||||
public OrderUtil $orderUtil;
|
||||
|
||||
#[Required]
|
||||
public SAPOrderUtil $sapOrderUtil;
|
||||
|
||||
#[Required]
|
||||
public OrderInfo $orderInfo;
|
||||
|
||||
#[Required]
|
||||
public SAPOrderUpdater $sapOrderUpdater;
|
||||
|
||||
#[Required]
|
||||
public ReturnSynchronizer $returnSynchronizer;
|
||||
|
||||
public ?ReturnsUtil $returnsUtil = null;
|
||||
|
||||
#[Required]
|
||||
final public function setReturnsUtil(?ReturnsUtil $returnsUtil = null): void
|
||||
{
|
||||
$this->returnsUtil = $returnsUtil;
|
||||
}
|
||||
|
||||
public function process(array $data): void
|
||||
{
|
||||
$this->log($data);
|
||||
|
||||
if (empty($data['OrderId'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// jedna se o vratku, takze zpracovavam jinak
|
||||
if ($sapReturnId = $this->sapUtil->getAdditionalFieldValue($data['Additionals'] ?? [], 'SAP_RETURN_ID')) {
|
||||
$this->returnSynchronizer->processReturn($sapReturnId, $data);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$order = \Order::createFromDbOrderNo($data['OrderId']);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->checkOrderStorno($order, $data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// nacteni novych stavu a mailu, ktery ma poslat
|
||||
[$newStatus, $orderMessage, $isStorno] = $this->getOrderNewStatus($order, $data);
|
||||
|
||||
// tohle delam kvuli tomu, aby vsechny logHistory byly zalogovany uz pod tim nastavajicim stavem viz. tiket #16162
|
||||
$previousStatus = $order->status;
|
||||
$order->status = $newStatus;
|
||||
|
||||
try {
|
||||
// update order by SAP
|
||||
$this->sapOrderUpdater->updateOrderBySAP($order);
|
||||
} catch (\Throwable $e) {
|
||||
if (isLocalDevelopment()) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
if ($e instanceof SAPException) {
|
||||
$order->logHistory('[SAP] Při aktualizaci objednávky podle SAPu došlo k chybě: '.$e->getMessage());
|
||||
} else {
|
||||
$this->sentryLogger->captureException($e);
|
||||
}
|
||||
}
|
||||
|
||||
// ulozit package number - deprecated, package number by se mel ukladat uz v ramci volani `updateOrderBySAP`
|
||||
$this->updateOrderTracking($order, $data);
|
||||
|
||||
// ulozit si invoice number
|
||||
if ($invoiceNumber = $this->getInvoiceNumber($data['Additionals'] ?? [])) {
|
||||
// pole s cislama faktur, protoze se muze stat, ze jich v SAPu bylo vytvoreno vic
|
||||
if (!($invoiceNumbers = $order->getData('sapInvoiceNumbers'))) {
|
||||
$invoiceNumbers = [];
|
||||
}
|
||||
|
||||
// pokud faktura jeste nebyla odeslana, tak ji posleme
|
||||
if (!in_array($invoiceNumber, $invoiceNumbers)) {
|
||||
$invoiceNumbers[] = $invoiceNumber;
|
||||
|
||||
// ulozim si cislo faktury
|
||||
$order->setData('sapInvoiceNumber', $invoiceNumber);
|
||||
// aktualizuju seznam s cislama faktur
|
||||
$order->setData('sapInvoiceNumbers', array_unique($invoiceNumbers));
|
||||
|
||||
// zalogovat si číslo faktury
|
||||
$order->logHistory(sprintf('[SAP] Číslo faktury: %s', $invoiceNumber));
|
||||
// prerazim odkaz na fakturu odkazem, kde je specifikovane i cislo faktury
|
||||
$order->setPlaceholder('ODKAZ_FAKTURA',
|
||||
path(
|
||||
'kupshop_orderingbundle_pdfinvoice',
|
||||
[
|
||||
'id_order' => $order->id,
|
||||
'cf' => $order->getSecurityCode(),
|
||||
'invoiceNumber' => $invoiceNumber,
|
||||
],
|
||||
Router::ABSOLUTE_URL
|
||||
)
|
||||
);
|
||||
|
||||
if (!$order->getData('sapDateInvoice')) {
|
||||
$order->setData('sapDateInvoice', time());
|
||||
}
|
||||
|
||||
if (!$this->isDropshipmentOrder($order)) {
|
||||
// odeslat email s fakturou
|
||||
$order->sendEmail(null, 'faktura');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($newStatus !== null && $newStatus != $previousStatus) {
|
||||
$order->logHistory(
|
||||
sprintf('[SAP] Změna stavu %s: %s', $order->source === OrderInfo::ORDER_SOURCE_RESERVATION ? 'rezervace' : 'objednávky', $data['OrderState'])
|
||||
);
|
||||
// hack aby prosla zmena stavu
|
||||
$order->status = $previousStatus;
|
||||
$order->changeStatus($newStatus, null, $orderMessage === null ? null : true, $orderMessage);
|
||||
$this->updateStatusCustomDate($order, $newStatus);
|
||||
}
|
||||
|
||||
if ($isStorno) {
|
||||
$order->storno(false, '[SAP] Stornování objednávky', false);
|
||||
}
|
||||
}
|
||||
|
||||
public function processToSAP(): void
|
||||
{
|
||||
$this->processOrdersToSAP();
|
||||
$this->processUpdatePaidOrdersToSAP();
|
||||
}
|
||||
|
||||
protected function getHandledFields(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
private function processUpdatePaidOrdersToSAP(): void
|
||||
{
|
||||
if (isLocalDevelopment()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$qb = sqlQueryBuilder()
|
||||
->select('o.id, o.order_no, so.id_sap')
|
||||
->from('orders', 'o')
|
||||
->join('o', 'sap_orders', 'so', 'so.id_order = o.id')
|
||||
->andWhere(
|
||||
Order::byPaymentMethods([\Payment::METHOD_TRANSFER, \Payment::METHOD_ONLINE])
|
||||
)
|
||||
->andWhere(Operator::equals(['o.status_payed' => 1]))
|
||||
->andWhere('JSON_VALUE(so.data, "$.paidSent") IS NULL');
|
||||
|
||||
foreach ($qb->execute() as $item) {
|
||||
try {
|
||||
$order = \Order::get((int) $item['id']);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
$this->sentryLogger->captureException($e);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->api->updateOrderPayment($order)) {
|
||||
$order->logHistory('[SAP] Informace o zaplacení byla odeslána');
|
||||
}
|
||||
|
||||
// po zaplaceni jsou vygenerovany kupony, ktere musime v SAPu zaktivovat
|
||||
if ($coupons = $this->sapUtil->getOrderCreatedCoupon($order)) {
|
||||
try {
|
||||
$this->api->activateCoupons($item['id_sap'], $coupons, true);
|
||||
$order->logHistory('[SAP] Byly zaktivovány kupóny: '.implode(', ', $coupons).'; CausingDocument: '.$item['id_sap']);
|
||||
} catch (SAPException $e) {
|
||||
$order->logHistory('[SAP] Nepodařilo se aktivovat kupóny: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function processOrdersToSAP(): void
|
||||
{
|
||||
if (isLocalDevelopment()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$qb = sqlQueryBuilder()
|
||||
->select('o.id')
|
||||
->from('orders', 'o')
|
||||
->leftJoin('o', 'sap_orders', 'so', 'so.id_order = o.id')
|
||||
// objednavka neni stornovana
|
||||
->andWhere(Operator::equals(['o.status_storno' => 0]))
|
||||
// a neni vyrizena
|
||||
->andWhere(
|
||||
Operator::not(
|
||||
Operator::inIntArray(getStatuses('handled'), 'o.status')
|
||||
)
|
||||
)
|
||||
// objednavka nesmi byt ve stavu "chyba" - pokud je ve stavu chyba, tak ji uz nezkousim odeslat do SAPu
|
||||
->andWhere(
|
||||
Operator::not(
|
||||
Operator::equals(['o.status' => $this->configuration->getOrderErrorStatus()])
|
||||
)
|
||||
)
|
||||
// objednavka neni v SAPu
|
||||
->andWhere('so.id_sap IS NULL')
|
||||
// objednavka nesmi mit flag "OLD"
|
||||
->andWhere(
|
||||
Operator::not(
|
||||
Operator::findInSet(['OLD'], 'o.flags')
|
||||
)
|
||||
)
|
||||
->groupBy('o.id');
|
||||
|
||||
if (findModule(\Modules::DROPSHIP)) {
|
||||
$dropshipOrdersDisabled = $this->configuration->getOrderConfig()['disable_dropshipment_orders'] ?? 'N';
|
||||
// vypnuli odesilani drosphipment objednavek do SAPu
|
||||
if ($dropshipOrdersDisabled === 'Y') {
|
||||
$qb->leftJoin('o', 'order_dropshipment', 'od', 'od.id_order = o.id')
|
||||
->andWhere('od.id_external IS NULL');
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($qb->sendToMaster()->execute() as $item) {
|
||||
$order = new \Order();
|
||||
$order->createFromDB($item['id']);
|
||||
|
||||
// pocet pokusu, kolikrat se objednavka zkusila zapsat do SAPu
|
||||
if (!($try = $order->getData('sapCreateTry'))) {
|
||||
$try = 1;
|
||||
}
|
||||
|
||||
try {
|
||||
if ($sapOrderId = $this->api->createOrder($order, true)) {
|
||||
$order->logHistory(
|
||||
sprintf('[SAP] Objednávka byla zapsána do SAPu; SAPID: %s', $sapOrderId)
|
||||
);
|
||||
}
|
||||
} catch (SAPException $e) {
|
||||
if ($e instanceof SAPManyException) {
|
||||
// Duplicita pro zakazku
|
||||
if ($e->hasExceptionWithCode(['007'])) {
|
||||
// vytahnu si správný proces a základě jazyku objednávky
|
||||
$process = $this->sapOrderUtil->getOrderProcess($order);
|
||||
// zkusim si fetchnout objednavku z sapu podle kodu objednavky a to ID kdyztak doplnim
|
||||
if ($sapOrder = $this->api->getClient()->orderInfo($order->order_no, $process)) {
|
||||
if (!empty($sapOrder->SapOrderId)) {
|
||||
$this->sapUtil->createMapping(
|
||||
MappingType::ORDERS,
|
||||
$sapOrder->SapOrderId,
|
||||
(int) $order->id
|
||||
);
|
||||
|
||||
$order->logHistory(
|
||||
sprintf('[SAP] Objednávka byla zapsána do SAPu; SAPID: %s', $sapOrder->SapOrderId)
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// pokud proste nenajdu objednavku a sap rika, ze tam uz existuje, tak taky udelam, ze neexistuje
|
||||
$order->logHistory(
|
||||
'[SAP] Objednávku se nepodařilo zapsat, protože v SAPu už existuje'
|
||||
);
|
||||
|
||||
// vytvorim mapping, abych to uz nezkousel znovu
|
||||
sqlGetConnection()->transactional(function () use ($order) {
|
||||
$this->sapUtil->createMapping(
|
||||
MappingType::ORDERS,
|
||||
'DUP_'.$order->id,
|
||||
(int) $order->id
|
||||
);
|
||||
$this->sapUtil->setMappingData(
|
||||
MappingType::ORDERS,
|
||||
(int) $order->id,
|
||||
['paidSent' => true]
|
||||
);
|
||||
});
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$this->activityLog->addActivityLog(
|
||||
ActivityLog::SEVERITY_ERROR,
|
||||
ActivityLog::TYPE_SYNC,
|
||||
sprintf('Objednávku "%s" se nepodařilo zapsat do SAP, pokus: %s! Chyba: %s', $order->order_no, $try, $e->getMessage()),
|
||||
[
|
||||
'try' => $try,
|
||||
'message' => $e->getMessage(),
|
||||
]
|
||||
);
|
||||
|
||||
$order->setData('sapCreateTry', ++$try);
|
||||
if ($try > 4) {
|
||||
$order->changeStatus(
|
||||
$this->configuration->getOrderErrorStatus(),
|
||||
sprintf('[SAP] Objednávku se nepodařilo zapsat do SAPu. Chyba: %s', $e->getMessage()),
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getPackageNumber(array $data): ?string
|
||||
{
|
||||
return $this->sapUtil->getAdditionalFieldValue($data, 'PACKAGENR');
|
||||
}
|
||||
|
||||
private function getInvoiceNumber(array $data): ?string
|
||||
{
|
||||
return $this->sapUtil->getAdditionalFieldValue($data, 'INVOICE_NR');
|
||||
}
|
||||
|
||||
/**
|
||||
* Vratí čislo dobropisu.
|
||||
*/
|
||||
private function getCreditNoteNumber(array $data): ?string
|
||||
{
|
||||
return $this->sapUtil->getAdditionalFieldValue($data, 'CRMEMO_NR');
|
||||
}
|
||||
|
||||
private function checkOrderStorno(\Order $order, array $data): bool
|
||||
{
|
||||
if ($data['OrderState'] === 'STORNO') {
|
||||
$order->storno(false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// storno rezervacnich objednavek
|
||||
if (findModule(\Modules::RESERVATIONS) && $order->source === OrderInfo::ORDER_SOURCE_RESERVATION) {
|
||||
$this->removeFlags($order, ['SNS', 'SNP', 'SNK', 'SZJ']);
|
||||
switch ($data['OrderState']) {
|
||||
case 'storno_nemame_skladem':
|
||||
$order->storno(false);
|
||||
$this->orderUtil->addFlag($order, 'SNS');
|
||||
|
||||
return true;
|
||||
case 'storno_neprisel':
|
||||
$order->storno(false);
|
||||
$this->orderUtil->addFlag($order, 'SNP');
|
||||
|
||||
return true;
|
||||
case 'storno_nic_nekoupil':
|
||||
$order->storno(false);
|
||||
$this->orderUtil->addFlag($order, 'SNK');
|
||||
|
||||
return true;
|
||||
case 'storno_zakoupil_jine_zbozi':
|
||||
$order->storno(false);
|
||||
$this->orderUtil->addFlag($order, 'SZJ');
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function getOrderNewStatus(\Order $order, array $data): array
|
||||
{
|
||||
$newStatus = null;
|
||||
$orderMessage = null;
|
||||
$isStorno = false;
|
||||
|
||||
if (findModule(\Modules::RESERVATIONS) && $order->source === OrderInfo::ORDER_SOURCE_RESERVATION) {
|
||||
return $this->getReservationOrderNewStatus($order, $data);
|
||||
}
|
||||
|
||||
switch ($data['OrderState'] ?? false) {
|
||||
case 'INCOMPLETE':
|
||||
// nekompletni
|
||||
$newStatus = 1;
|
||||
break;
|
||||
case 'PICKHU':
|
||||
// priprava k expedici
|
||||
$newStatus = 5;
|
||||
break;
|
||||
case 'PICKING':
|
||||
// zahájeno vychystávání
|
||||
$newStatus = 2;
|
||||
break;
|
||||
case 'ORDERED':
|
||||
// poptano na prodejne #19014 - nastavit stav "Nová"
|
||||
$newStatus = 0;
|
||||
break;
|
||||
case 'PREPARED':
|
||||
case 'READYTOPIC':
|
||||
// připraveno k převzetí
|
||||
$newStatus = 4;
|
||||
// delaji to autopilotem, viz #16100
|
||||
// $orderMessage = 'osobni_prevzeti';
|
||||
break;
|
||||
case 'EXPED':
|
||||
case 'SENT':
|
||||
case 'INTRANSIT':
|
||||
// expedovano
|
||||
$newStatus = 6;
|
||||
// predano prepravci
|
||||
$orderMessage = 'predano_prepravci';
|
||||
break;
|
||||
case 'RETHU':
|
||||
// expedovano
|
||||
$newStatus = 6;
|
||||
break;
|
||||
case 'FINISHED':
|
||||
case 'DELIVERED':
|
||||
case 'INVOICED':
|
||||
// vyřízeno
|
||||
$newStatus = 7;
|
||||
break;
|
||||
case 'CANCEL':
|
||||
// expedovano
|
||||
$newStatus = 6;
|
||||
$isStorno = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// u dropshipment objednavek nechceme posilat maily
|
||||
if ($this->isDropshipmentOrder($order)) {
|
||||
$orderMessage = null;
|
||||
}
|
||||
|
||||
return [$newStatus, $orderMessage, $isStorno];
|
||||
}
|
||||
|
||||
private function getReservationOrderNewStatus(\Order $order, array $data): array
|
||||
{
|
||||
$newStatus = null;
|
||||
$orderMessage = null;
|
||||
|
||||
switch ($data['OrderState'] ?? false) {
|
||||
case 'reminder':
|
||||
$orderMessage = 'rezervace_upominka';
|
||||
break;
|
||||
case 'rezervovano':
|
||||
$newStatus = 4;
|
||||
$orderMessage = 'rezervace_pripravena';
|
||||
break;
|
||||
case 'zakoupeno':
|
||||
case 'zakoupeno_plus_jine_zbozi':
|
||||
$this->removeFlags($order, ['ZAK', 'ZJZ']);
|
||||
$this->orderUtil->addFlag($order, $data['OrderState'] === 'zakoupeno' ? 'ZAK' : 'ZJZ');
|
||||
$newStatus = 7;
|
||||
break;
|
||||
}
|
||||
|
||||
return [$newStatus, $orderMessage, false];
|
||||
}
|
||||
|
||||
private function updateOrderTracking(\Order $order, array $data): void
|
||||
{
|
||||
// pokud mam modul balikonos, tak se o update trackingu stara `External\HannahBundle\SAP\Util\SAPOrderUpdater::updateOrderTracking`
|
||||
if (findModule(\Modules::BALIKONOS)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!($packageNumber = $this->getPackageNumber($data['Additionals'] ?? []))) {
|
||||
return;
|
||||
}
|
||||
|
||||
sqlQueryBuilder()
|
||||
->update('orders')
|
||||
->directValues(
|
||||
[
|
||||
'package_id' => $packageNumber,
|
||||
]
|
||||
)
|
||||
->where(Operator::equals(['id' => $order->id]))
|
||||
->execute();
|
||||
|
||||
$order->package_id = $packageNumber;
|
||||
|
||||
$order->logHistory(
|
||||
sprintf('[SAP] Trackovací čislo balíku: %s', $packageNumber)
|
||||
);
|
||||
}
|
||||
|
||||
public function updateStatusCustomDate(\Order $order, int $status): void
|
||||
{
|
||||
if (!($this->getStatusesDateSaveDefinition()[$status] ?? false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$field = 'sapDate'.ucfirst($this->getStatusesDateSaveDefinition()[$status]);
|
||||
|
||||
$order->setData($field, time());
|
||||
}
|
||||
|
||||
private function getStatusesDateSaveDefinition(): array
|
||||
{
|
||||
return [
|
||||
2 => 'picking',
|
||||
4 => 'prepared',
|
||||
6 => 'sent',
|
||||
];
|
||||
}
|
||||
|
||||
private function removeFlags(\Order $order, array $flags): void
|
||||
{
|
||||
foreach ($flags as $flag) {
|
||||
if ($order->getFlags()[$flag] ?? false) {
|
||||
$this->orderUtil->removeFlag($order, $flag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function isDropshipmentOrder(\Order $order): bool
|
||||
{
|
||||
return $order->source === OrderInfo::ORDER_SOURCE_DROPSHIP;
|
||||
}
|
||||
|
||||
protected function isLoggingEnabled(): bool
|
||||
{
|
||||
// always log orders
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user