ProductStockPropelDataSetWriter   A
last analyzed

Complexity

Total Complexity 33

Size/Duplication

Total Lines 337
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 33
eloc 158
dl 0
loc 337
rs 9.76
c 0
b 0
f 0

22 Methods

Rating   Name   Duplication   Size   Complexity  
A write() 0 12 2
A triggerAvailabilityPublishEvents() 0 6 2
A getReservationQuantityForStore() 0 22 2
A persistAvailabilityData() 0 12 1
A createAvailabilityAbstract() 0 9 1
A createOrUpdateStock() 0 10 1
A getAvailabilityAbstract() 0 18 3
A updateAvailabilityForStore() 0 17 1
A getProductAvailabilityForStore() 0 7 2
A getIdStore() 0 10 2
A updateAbstractAvailabilityQuantity() 0 16 1
A getReservationsFromOtherStores() 0 16 3
A getStockProductQuantityByIdProductAndStockNames() 0 14 1
A createOrUpdateProductStock() 0 10 1
A collectProductAbstractSku() 0 4 1
A calculateProductStockForSkuAndStore() 0 6 1
A __construct() 0 10 1
A getStoreIds() 0 9 2
A getAvailabilityAbstractIdsForCollectedAbstractSkus() 0 15 1
A updateAvailability() 0 4 2
A flush() 0 4 1
A getStoreWarehouses() 0 3 1
1
<?php
2
3
/**
4
 * This file is part of the Spryker Commerce OS.
5
 * For full license information, please view the LICENSE file that was distributed with this source code.
6
 */
7
8
declare(strict_types = 1);
9
10
namespace Pyz\Zed\DataImport\Business\Model\ProductStock\Writer;
11
12
use Generated\Shared\Transfer\StoreTransfer;
0 ignored issues
show
Bug introduced by
The type Generated\Shared\Transfer\StoreTransfer was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
13
use Orm\Zed\Availability\Persistence\Map\SpyAvailabilityAbstractTableMap;
0 ignored issues
show
Bug introduced by
The type Orm\Zed\Availability\Per...abilityAbstractTableMap was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
14
use Orm\Zed\Availability\Persistence\Map\SpyAvailabilityTableMap;
0 ignored issues
show
Bug introduced by
The type Orm\Zed\Availability\Per...SpyAvailabilityTableMap was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
15
use Orm\Zed\Availability\Persistence\SpyAvailabilityAbstract;
16
use Orm\Zed\Availability\Persistence\SpyAvailabilityAbstractQuery;
17
use Orm\Zed\Availability\Persistence\SpyAvailabilityQuery;
18
use Orm\Zed\Oms\Persistence\Map\SpyOmsProductReservationTableMap;
0 ignored issues
show
Bug introduced by
The type Orm\Zed\Oms\Persistence\...ductReservationTableMap was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
19
use Orm\Zed\Oms\Persistence\SpyOmsProductReservationQuery;
20
use Orm\Zed\Oms\Persistence\SpyOmsProductReservationStoreQuery;
21
use Orm\Zed\Stock\Persistence\Map\SpyStockProductTableMap;
0 ignored issues
show
Bug introduced by
The type Orm\Zed\Stock\Persistenc...SpyStockProductTableMap was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
22
use Orm\Zed\Stock\Persistence\SpyStock;
23
use Orm\Zed\Stock\Persistence\SpyStockProductQuery;
24
use Orm\Zed\Stock\Persistence\SpyStockQuery;
25
use Pyz\Zed\DataImport\Business\Model\Product\Repository\ProductRepositoryInterface;
26
use Pyz\Zed\DataImport\Business\Model\ProductStock\ProductStockHydratorStep;
27
use Spryker\DecimalObject\Decimal;
28
use Spryker\Zed\Availability\Dependency\AvailabilityEvents;
29
use Spryker\Zed\DataImport\Business\Model\DataSet\DataSetInterface;
30
use Spryker\Zed\DataImport\Business\Model\DataSet\DataSetWriterInterface;
31
use Spryker\Zed\DataImport\Business\Model\Publisher\DataImporterPublisher;
32
use Spryker\Zed\ProductBundle\Business\ProductBundleFacadeInterface;
33
use Spryker\Zed\PropelOrm\Business\Runtime\ActiveQuery\Criteria;
34
use Spryker\Zed\Stock\Business\StockFacadeInterface;
35
use Spryker\Zed\Store\Business\StoreFacadeInterface;
36
37
class ProductStockPropelDataSetWriter implements DataSetWriterInterface
38
{
39
    protected const COLUMN_CONCRETE_SKU = ProductStockHydratorStep::COLUMN_CONCRETE_SKU;
40
41
    protected const COLUMN_IS_BUNDLE = ProductStockHydratorStep::COLUMN_IS_BUNDLE;
42
43
    protected const COLUMN_IS_NEVER_OUT_OF_STOCK = ProductStockHydratorStep::COLUMN_IS_NEVER_OUT_OF_STOCK;
44
45
    protected const KEY_AVAILABILITY_SKU = 'KEY_AVAILABILITY_SKU';
46
47
    protected const KEY_AVAILABILITY_QUANTITY = 'KEY_AVAILABILITY_QUANTITY';
48
49
    protected const KEY_AVAILABILITY_ID_STORE = 'KEY_AVAILABILITY_ID_STORE';
50
51
    protected const KEY_AVAILABILITY_IS_NEVER_OUT_OF_STOCK = 'KEY_AVAILABILITY_IS_NEVER_OUT_OF_STOCK';
52
53
    protected const KEY_AVAILABILITY_ID_AVAILABILITY_ABSTRACT = 'KEY_AVAILABILITY_ID_AVAILABILITY_ABSTRACT';
54
55
    protected const COL_AVAILABILITY_TOTAL_QUANTITY = 'availabilityTotalQuantity';
56
57
    protected const COL_STOCK_PRODUCT_TOTAL_QUANTITY = 'stockProductTotalQuantity';
58
59
    /**
60
     * @var array<string, array<int, \Orm\Zed\Availability\Persistence\SpyAvailabilityAbstract>>
61
     */
62
    protected static array $availabilityAbstractEntitiesIndexedByAbstractSkuAndIdStore = [];
63
64
    /**
65
     * @var array<string>
66
     */
67
    protected static array $productAbstractSkus = [];
68
69
    protected ProductRepositoryInterface $productRepository;
70
71
    protected ProductBundleFacadeInterface $productBundleFacade;
72
73
    protected StoreFacadeInterface $storeFacade;
74
75
    protected StockFacadeInterface $stockFacade;
76
77
    public function __construct(
78
        ProductBundleFacadeInterface $productBundleFacade,
79
        ProductRepositoryInterface $productRepository,
80
        StoreFacadeInterface $storeFacade,
81
        StockFacadeInterface $stockFacade,
82
    ) {
83
        $this->productBundleFacade = $productBundleFacade;
84
        $this->productRepository = $productRepository;
85
        $this->storeFacade = $storeFacade;
86
        $this->stockFacade = $stockFacade;
87
    }
88
89
    public function write(DataSetInterface $dataSet): void
90
    {
91
        $stockEntity = $this->createOrUpdateStock($dataSet);
92
        $this->createOrUpdateProductStock($dataSet, $stockEntity);
93
        $this->collectProductAbstractSku($dataSet);
94
        $this->updateAvailability($dataSet);
95
96
        if ($dataSet[static::COLUMN_IS_BUNDLE]) {
97
            $this->productBundleFacade->updateBundleAvailability($dataSet[static::COLUMN_CONCRETE_SKU]);
98
        } else {
99
            $this->productBundleFacade->updateAffectedBundlesAvailability($dataSet[static::COLUMN_CONCRETE_SKU]);
100
            $this->productBundleFacade->updateAffectedBundlesStock($dataSet[static::COLUMN_CONCRETE_SKU]);
101
        }
102
    }
103
104
    public function flush(): void
105
    {
106
        $this->triggerAvailabilityPublishEvents();
107
        $this->productRepository->flush();
108
    }
109
110
    protected function createOrUpdateStock(DataSetInterface $dataSet): SpyStock
111
    {
112
        $stockTransfer = $dataSet[ProductStockHydratorStep::STOCK_ENTITY_TRANSFER];
113
        $stockEntity = SpyStockQuery::create()
114
            ->filterByName($stockTransfer->getName())
115
            ->findOneOrCreate();
116
        $stockEntity->fromArray($stockTransfer->modifiedToArray());
117
        $stockEntity->save();
118
119
        return $stockEntity;
120
    }
121
122
    protected function createOrUpdateProductStock(DataSetInterface $dataSet, SpyStock $stockEntity): void
123
    {
124
        $stockProductEntityTransfer = $dataSet[ProductStockHydratorStep::STOCK_PRODUCT_ENTITY_TRANSFER];
125
        $idProductConcrete = $this->productRepository->getIdProductByConcreteSku($dataSet[static::COLUMN_CONCRETE_SKU]);
126
        $stockProductEntity = SpyStockProductQuery::create()
127
            ->filterByFkProduct($idProductConcrete)
128
            ->filterByFkStock($stockEntity->getIdStock())
129
            ->findOneOrCreate();
130
        $stockProductEntity->fromArray($stockProductEntityTransfer->modifiedToArray());
131
        $stockProductEntity->save();
132
    }
133
134
    protected function collectProductAbstractSku(DataSetInterface $dataSet): void
135
    {
136
        $productConcreteSku = $dataSet[static::COLUMN_CONCRETE_SKU];
137
        static::$productAbstractSkus[] = $this->productRepository->getAbstractSkuByConcreteSku($productConcreteSku);
138
    }
139
140
    protected function triggerAvailabilityPublishEvents(): void
141
    {
142
        $availabilityAbstractIds = $this->getAvailabilityAbstractIdsForCollectedAbstractSkus();
143
144
        foreach ($availabilityAbstractIds as $availabilityAbstractId) {
145
            DataImporterPublisher::addEvent(AvailabilityEvents::AVAILABILITY_ABSTRACT_PUBLISH, $availabilityAbstractId);
146
        }
147
    }
148
149
    /**
150
     * @return array<int>
151
     */
152
    protected function getAvailabilityAbstractIdsForCollectedAbstractSkus(): array
153
    {
154
        $storeIds = $this->getStoreIds();
155
156
        return SpyAvailabilityAbstractQuery::create()
157
            ->joinWithSpyAvailability()
158
            ->filterByAbstractSku_In(static::$productAbstractSkus)
159
            ->useSpyAvailabilityQuery()
160
                ->filterByFkStore_In($storeIds)
161
            ->endUse()
162
            ->select([
163
                SpyAvailabilityAbstractTableMap::COL_ID_AVAILABILITY_ABSTRACT,
164
            ])
165
            ->find()
166
            ->getData();
167
    }
168
169
    /**
170
     * @return array<int>
171
     */
172
    protected function getStoreIds(): array
173
    {
174
        $storeIds = [];
175
176
        foreach ($this->storeFacade->getAllStores() as $storeTransfer) {
177
            $storeIds[] = $storeTransfer->getIdStoreOrFail();
178
        }
179
180
        return $storeIds;
181
    }
182
183
    protected function updateAvailability(DataSetInterface $dataSet): void
184
    {
185
        foreach ($this->storeFacade->getAllStores() as $storeTransfer) {
186
            $this->updateAvailabilityForStore($dataSet, $storeTransfer);
187
        }
188
    }
189
190
    protected function updateAvailabilityForStore(DataSetInterface $dataSet, StoreTransfer $storeTransfer): void
191
    {
192
        $concreteSku = $dataSet[static::COLUMN_CONCRETE_SKU];
193
        $abstractSku = $this->productRepository->getAbstractSkuByConcreteSku($concreteSku);
194
        $idStore = $this->getIdStore($storeTransfer);
195
196
        $availabilityQuantity = $this->getProductAvailabilityForStore($concreteSku, $storeTransfer);
197
        $availabilityAbstractEntity = $this->getAvailabilityAbstract($abstractSku, $idStore);
198
        $this->persistAvailabilityData([
199
            static::KEY_AVAILABILITY_SKU => $concreteSku,
200
            static::KEY_AVAILABILITY_QUANTITY => $availabilityQuantity,
201
            static::KEY_AVAILABILITY_ID_AVAILABILITY_ABSTRACT => $availabilityAbstractEntity->getIdAvailabilityAbstract(),
202
            static::KEY_AVAILABILITY_ID_STORE => $idStore,
203
            static::KEY_AVAILABILITY_IS_NEVER_OUT_OF_STOCK => $dataSet[static::COLUMN_IS_NEVER_OUT_OF_STOCK],
204
        ]);
205
206
        $this->updateAbstractAvailabilityQuantity($availabilityAbstractEntity, $idStore);
207
    }
208
209
    protected function getProductAvailabilityForStore(string $concreteSku, StoreTransfer $storeTransfer): Decimal
210
    {
211
        $physicalItems = $this->calculateProductStockForSkuAndStore($concreteSku, $storeTransfer);
212
        $reservedItems = $this->getReservationQuantityForStore($concreteSku, $storeTransfer);
213
        $stockProductQuantity = $physicalItems->subtract($reservedItems);
214
215
        return $stockProductQuantity->greatherThanOrEquals(0) ? $stockProductQuantity : new Decimal(0);
0 ignored issues
show
Deprecated Code introduced by
The function Spryker\DecimalObject\De...:greatherThanOrEquals() has been deprecated: Use {@link greaterThanOrEquals()} instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

215
        return /** @scrutinizer ignore-deprecated */ $stockProductQuantity->greatherThanOrEquals(0) ? $stockProductQuantity : new Decimal(0);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
216
    }
217
218
    protected function calculateProductStockForSkuAndStore(string $concreteSku, StoreTransfer $storeTransfer): Decimal
219
    {
220
        $idProductConcrete = $this->productRepository->getIdProductByConcreteSku($concreteSku);
221
        $stockNames = $this->getStoreWarehouses($storeTransfer->getName());
222
223
        return $this->getStockProductQuantityByIdProductAndStockNames($idProductConcrete, $stockNames);
224
    }
225
226
    /**
227
     * @param string $storeName
228
     *
229
     * @return array<string>
230
     */
231
    protected function getStoreWarehouses(string $storeName): array
232
    {
233
        return $this->stockFacade->getStoreToWarehouseMapping()[$storeName] ?? [];
234
    }
235
236
    /**
237
     * @param int $idProductConcrete
238
     * @param array<string> $stockNames
239
     */
240
    protected function getStockProductQuantityByIdProductAndStockNames(
241
        int $idProductConcrete,
242
        array $stockNames,
243
    ): Decimal {
244
        $stockProductTotalQuantity = SpyStockProductQuery::create()
245
            ->filterByFkProduct($idProductConcrete)
246
            ->useStockQuery()
247
                ->filterByName($stockNames, Criteria::IN)
248
            ->endUse()
249
            ->withColumn(sprintf('SUM(%s)', SpyStockProductTableMap::COL_QUANTITY), static::COL_STOCK_PRODUCT_TOTAL_QUANTITY)
250
            ->select([static::COL_STOCK_PRODUCT_TOTAL_QUANTITY])
251
            ->findOne();
252
253
        return new Decimal($stockProductTotalQuantity ?? 0);
254
    }
255
256
    protected function getReservationQuantityForStore(string $sku, StoreTransfer $storeTransfer): Decimal
257
    {
258
        $idStore = $this->getIdStore($storeTransfer);
259
260
        /** @var \Propel\Runtime\Collection\ArrayCollection $productReservations */
261
        $productReservations = SpyOmsProductReservationQuery::create()
262
            ->filterBySku($sku)
263
            ->filterByFkStore($idStore)
264
            ->select([
265
                SpyOmsProductReservationTableMap::COL_RESERVATION_QUANTITY,
266
            ])
267
            ->find();
268
269
        $reservationQuantity = new Decimal(0);
270
271
        foreach ($productReservations->toArray() as $productReservationQuantity) {
272
            $reservationQuantity = $reservationQuantity->add($productReservationQuantity);
273
        }
274
275
        $reservationQuantity = $reservationQuantity->add($this->getReservationsFromOtherStores($sku, $storeTransfer));
276
277
        return $reservationQuantity;
278
    }
279
280
    protected function getReservationsFromOtherStores(string $sku, StoreTransfer $currentStoreTransfer): Decimal
281
    {
282
        $reservationQuantity = new Decimal(0);
283
        $reservationStores = SpyOmsProductReservationStoreQuery::create()
284
            ->filterBySku($sku)
285
            ->find();
286
287
        foreach ($reservationStores as $omsProductReservationStoreEntity) {
288
            if ($omsProductReservationStoreEntity->getStore() === $currentStoreTransfer->getName()) {
289
                continue;
290
            }
291
292
            $reservationQuantity = $reservationQuantity->add($omsProductReservationStoreEntity->getReservationQuantity());
293
        }
294
295
        return $reservationQuantity;
296
    }
297
298
    protected function getIdStore(StoreTransfer $storeTransfer): int
299
    {
300
        if (!$storeTransfer->getIdStore()) {
301
            $idStore = $this->storeFacade
302
                ->getStoreByName($storeTransfer->getName())
303
                ->getIdStore();
304
            $storeTransfer->setIdStore($idStore);
305
        }
306
307
        return $storeTransfer->getIdStore();
308
    }
309
310
    /**
311
     * @param array<string, mixed> $availabilityData
312
     */
313
    protected function persistAvailabilityData(array $availabilityData): void
314
    {
315
        $spyAvailabilityEntity = SpyAvailabilityQuery::create()
316
            ->filterByFkStore($availabilityData[static::KEY_AVAILABILITY_ID_STORE])
317
            ->filterBySku($availabilityData[static::KEY_AVAILABILITY_SKU])
318
            ->findOneOrCreate();
319
320
        $spyAvailabilityEntity->setFkAvailabilityAbstract($availabilityData[static::KEY_AVAILABILITY_ID_AVAILABILITY_ABSTRACT]);
321
        $spyAvailabilityEntity->setQuantity($availabilityData[static::KEY_AVAILABILITY_QUANTITY]);
322
        $spyAvailabilityEntity->setIsNeverOutOfStock($availabilityData[static::KEY_AVAILABILITY_IS_NEVER_OUT_OF_STOCK]);
323
324
        $spyAvailabilityEntity->save();
325
    }
326
327
    protected function getAvailabilityAbstract(string $abstractSku, int $idStore): SpyAvailabilityAbstract
328
    {
329
        if (!empty(static::$availabilityAbstractEntitiesIndexedByAbstractSkuAndIdStore[$abstractSku][$idStore])) {
330
            return static::$availabilityAbstractEntitiesIndexedByAbstractSkuAndIdStore[$abstractSku][$idStore];
331
        }
332
333
        $availabilityAbstractEntity = SpyAvailabilityAbstractQuery::create()
334
            ->filterByAbstractSku($abstractSku)
335
            ->filterByFkStore($idStore)
336
            ->findOne();
337
338
        if (!$availabilityAbstractEntity) {
339
            $availabilityAbstractEntity = $this->createAvailabilityAbstract($abstractSku, $idStore);
340
        }
341
342
        static::$availabilityAbstractEntitiesIndexedByAbstractSkuAndIdStore[$abstractSku][$idStore] = $availabilityAbstractEntity;
343
344
        return $availabilityAbstractEntity;
345
    }
346
347
    protected function createAvailabilityAbstract(string $abstractSku, int $idStore): SpyAvailabilityAbstract
348
    {
349
        $availableAbstractEntity = (new SpyAvailabilityAbstract())
350
            ->setAbstractSku($abstractSku)
351
            ->setFkStore($idStore);
352
353
        $availableAbstractEntity->save();
354
355
        return $availableAbstractEntity;
356
    }
357
358
    protected function updateAbstractAvailabilityQuantity(
359
        SpyAvailabilityAbstract $availabilityAbstractEntity,
360
        int $idStore,
361
    ): SpyAvailabilityAbstract {
362
        $sumQuantity = SpyAvailabilityQuery::create()
363
            ->filterByFkAvailabilityAbstract($availabilityAbstractEntity->getIdAvailabilityAbstract())
364
            ->filterByFkStore($idStore)
365
            ->withColumn(sprintf('SUM(%s)', SpyAvailabilityTableMap::COL_QUANTITY), static::COL_AVAILABILITY_TOTAL_QUANTITY)
366
            ->select([static::COL_AVAILABILITY_TOTAL_QUANTITY])
367
            ->findOne();
368
369
        $availabilityAbstractEntity->setFkStore($idStore);
370
        $availabilityAbstractEntity->setQuantity((string)$sumQuantity);
371
        $availabilityAbstractEntity->save();
372
373
        return $availabilityAbstractEntity;
374
    }
375
}
376