238 lines
8.5 KiB
PHP
238 lines
8.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace External\HannahBundle\SAP\Synchronizer;
|
|
|
|
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
|
|
use External\HannahBundle\Enum\ProductAvailability;
|
|
use External\HannahBundle\SAP\Exception\SAPException;
|
|
use External\HannahBundle\SAP\MappingType;
|
|
use External\HannahBundle\Util\Product\ProductAvailabilityUpdater;
|
|
use KupShop\StoresBundle\Utils\StoresInStore;
|
|
use Query\Operator;
|
|
use Symfony\Contracts\Service\Attribute\Required;
|
|
|
|
class StockSynchronizer extends BaseSynchronizer
|
|
{
|
|
protected static $type = 'disponibility';
|
|
protected $logging = false;
|
|
|
|
#[Required]
|
|
public StoresInStore $storesInStore;
|
|
|
|
#[Required]
|
|
public ProductAvailabilityUpdater $productAvailabilityUpdater;
|
|
|
|
protected function processItem(array $item): void
|
|
{
|
|
if (empty($item['Matnr'])) {
|
|
return;
|
|
}
|
|
|
|
if (!($mapping = $this->sapUtil->getItemMapping($item['Matnr']))) {
|
|
// nemam produkt
|
|
return;
|
|
}
|
|
|
|
[$productId, $variationId] = $mapping;
|
|
|
|
$storeId = $this->getStoreId($item);
|
|
|
|
if (!($sapDateUpdated = \DateTime::createFromFormat('YmdHis', (string) $item['Lastupd']))) {
|
|
$sapDateUpdated = null;
|
|
}
|
|
|
|
try {
|
|
$this->updateStoreItem(
|
|
$storeId,
|
|
$productId,
|
|
$variationId,
|
|
(float) $item['Dispo'],
|
|
$sapDateUpdated
|
|
);
|
|
} catch (ForeignKeyConstraintViolationException $e) {
|
|
}
|
|
}
|
|
|
|
protected function getHandledFields(): array
|
|
{
|
|
return [
|
|
'Items' => 'item',
|
|
];
|
|
}
|
|
|
|
private function getStoreId(array $item): int
|
|
{
|
|
static $storeIdCache = [];
|
|
|
|
$shippingPoint = $item['ShippingPoint'] ?? null;
|
|
if (!$shippingPoint) {
|
|
throw new SAPException('Shipping point is empty');
|
|
}
|
|
|
|
if ($storeIdCache[$shippingPoint] ?? false) {
|
|
return $storeIdCache[$shippingPoint];
|
|
}
|
|
|
|
$storeId = sqlGetConnection()->transactional(function () use ($shippingPoint) {
|
|
return $this->sapUtil->getMapping(MappingType::STORES, $shippingPoint);
|
|
});
|
|
|
|
if (!$storeId) {
|
|
$storeId = sqlGetConnection()->transactional(function () use ($shippingPoint) {
|
|
sqlQueryBuilder()
|
|
->insert('stores')
|
|
->directValues(
|
|
[
|
|
'name' => 'Shipping point '.$shippingPoint,
|
|
'data' => json_encode(['shippingPoint' => $shippingPoint]),
|
|
]
|
|
)->execute();
|
|
|
|
return (int) sqlInsertId();
|
|
});
|
|
|
|
$this->sapUtil->createMapping(MappingType::STORES, $shippingPoint, $storeId);
|
|
}
|
|
|
|
return $storeIdCache[$shippingPoint] = $storeId;
|
|
}
|
|
|
|
public function updateStoreItem(int $storeId, int $productId, ?int $variationId, float $quantity, ?\DateTime $sapDateUpdated = null): void
|
|
{
|
|
if (empty($productId)) {
|
|
return;
|
|
}
|
|
|
|
$isRestocked = false;
|
|
|
|
if ($storeItem = sqlQueryBuilder()
|
|
->select('id, quantity, sap_date_updated')
|
|
->from('stores_items')
|
|
->andWhere(Operator::equalsNullable(
|
|
[
|
|
'id_product' => $productId,
|
|
'id_variation' => $variationId,
|
|
'id_store' => $storeId,
|
|
]))
|
|
->sendToMaster()
|
|
->execute()
|
|
->fetchAssociative()) {
|
|
if ($sapDateUpdated && ($storeItemSapDateUpdated = \DateTime::createFromFormat('Y-m-d H:i:s', $storeItem['sap_date_updated']))) {
|
|
// pokud mam ulozeny spravny casovy razitko ze sapu, ale nesedi mi sklad, tak je invalidStore a budu muset udelat update
|
|
$isInvalidStore = $sapDateUpdated == $storeItemSapDateUpdated && $quantity != $storeItem['quantity'];
|
|
// pokud mam v databazi ($storeItemSapDateUpdated) ulozenej novejsi zaznam, nez mi aktualne prisel, tak nebudu provat update, protoze
|
|
// je to neaktualni skladovost, ktera me nezajima
|
|
// zaroven nesmi byt invalidStore, protoze pokud je, tak ten update proste provedu, at se to fixne
|
|
if ($sapDateUpdated <= $storeItemSapDateUpdated && !$isInvalidStore) {
|
|
// date_updated aktualizuju jen pri plne synchronizaci, protoze jen tehdy me zajima. Jinym zpusobem ho nevyuzivam
|
|
if ($this->isFullFileImport()) {
|
|
// date_updated budu aktualizovat jen pokud se jedna o full update
|
|
sqlQueryBuilder()
|
|
->update('stores_items')
|
|
->set('date_updated', 'NOW()')
|
|
->where(Operator::equals(['id' => $storeItem['id']]))
|
|
->execute();
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ($quantity <= 0) {
|
|
sqlQueryBuilder()
|
|
->delete('stores_items')
|
|
->where(Operator::equals(['id' => $storeItem['id']]))
|
|
->execute();
|
|
|
|
$this->sapUtil->recalculateStores([$productId]);
|
|
|
|
// produkt se vyprodal, takze zmenime jeho dostupnost
|
|
$this->productAvailabilityUpdater->updateProductAvailability(
|
|
ProductAvailability::UNAVAILABLE,
|
|
$productId,
|
|
$variationId
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
// pokud doslo k znovu naskladneni, tak si to oznacim
|
|
if ($storeItem['quantity'] <= 0) {
|
|
$isRestocked = true;
|
|
}
|
|
|
|
$updateQb = sqlQueryBuilder()
|
|
->update('stores_items')
|
|
->where(Operator::equals(['id' => $storeItem['id']]));
|
|
|
|
$updateData = [
|
|
'quantity' => $quantity,
|
|
'sap_date_updated' => $sapDateUpdated ? $sapDateUpdated->format('Y-m-d H:i:s') : (new \DateTime())->format('Y-m-d H:i:s'),
|
|
];
|
|
|
|
// pokud se opravdu zmenila quantity, tak nastavim sap_updated na 1
|
|
if ($quantity != $storeItem['quantity']) {
|
|
$updateData['sap_updated'] = 1;
|
|
}
|
|
|
|
// date_updated aktualizuju jen pri plne synchronizaci, protoze jen tehdy me zajima. Jinym zpusobem ho nevyuzivam
|
|
// nebo se opravdu zmenila skladovost
|
|
if ($this->isFullFileImport() || $quantity != $storeItem['quantity']) {
|
|
$updateQb->set('date_updated', 'NOW()');
|
|
}
|
|
|
|
// nasetuju data a executnu update
|
|
$updateQb
|
|
->directValues($updateData)
|
|
->execute();
|
|
} elseif ($quantity > 0) {
|
|
sqlQueryBuilder()
|
|
->insert('stores_items')
|
|
->setValue('date_updated', 'NOW()')
|
|
->directValues(
|
|
[
|
|
'id_store' => $storeId,
|
|
'id_product' => $productId,
|
|
'id_variation' => $variationId,
|
|
'quantity' => $quantity,
|
|
'sap_date_updated' => $sapDateUpdated ? $sapDateUpdated->format('Y-m-d H:i:s') : (new \DateTime())->format('Y-m-d H:i:s'),
|
|
'sap_updated' => 1,
|
|
]
|
|
)
|
|
->execute();
|
|
|
|
$isRestocked = true;
|
|
}
|
|
|
|
if ($isRestocked) {
|
|
// produkt byl naskladnen, takze provedu update dostupnosti
|
|
$this->productAvailabilityUpdater->updateProductAvailability(
|
|
ProductAvailability::IN_STORE,
|
|
$productId,
|
|
$variationId
|
|
);
|
|
}
|
|
}
|
|
|
|
protected function postprocess(): void
|
|
{
|
|
// pokud je to full import a je to file import (import z JSON souboru), tak na konci importu spustim vynulovani
|
|
// zaznamu, ktere nebyly aktualizovany
|
|
if ($this->isFullFileImport()) {
|
|
// nastavim 0 vsem polozkam, ktere nebyly aktualizovant vice jak 12 hodin
|
|
sqlQueryBuilder()
|
|
->update('stores_items')
|
|
->directValues(['quantity' => 0])
|
|
->where('TIMESTAMPDIFF(HOUR, date_updated, NOW()) > 12')
|
|
->execute();
|
|
}
|
|
}
|
|
|
|
private function isFullFileImport(): bool
|
|
{
|
|
return $this->context['importType'] === 'FULL' && ($this->context['isFileImport'] ?? false);
|
|
}
|
|
}
|