AvailabilityHandler::isNeverOutOfStock()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 6
c 0
b 0
f 0
nc 4
nop 2
dl 0
loc 13
rs 10
1
<?php
2
3
/**
4
 * Copyright © 2016-present Spryker Systems GmbH. All rights reserved.
5
 * Use of this software requires acceptance of the Evaluation License Agreement. See LICENSE file.
6
 */
7
8
namespace Spryker\Zed\Availability\Business\Model;
9
10
use Generated\Shared\Transfer\AvailabilityNotificationDataTransfer;
11
use Generated\Shared\Transfer\DynamicEntityPostEditRequestTransfer;
12
use Generated\Shared\Transfer\DynamicEntityPostEditResponseTransfer;
13
use Generated\Shared\Transfer\ProductAbstractAvailabilityTransfer;
14
use Generated\Shared\Transfer\ProductAvailabilityDataTransfer;
15
use Generated\Shared\Transfer\ProductConcreteAvailabilityTransfer;
16
use Generated\Shared\Transfer\StockStoreCriteriaTransfer;
17
use Generated\Shared\Transfer\StoreTransfer;
18
use Spryker\DecimalObject\Decimal;
19
use Spryker\Shared\Availability\AvailabilityConfig;
20
use Spryker\Zed\Availability\Business\Exception\ProductNotFoundException;
21
use Spryker\Zed\Availability\Dependency\AvailabilityEvents;
22
use Spryker\Zed\Availability\Dependency\Facade\AvailabilityToEventFacadeInterface;
23
use Spryker\Zed\Availability\Dependency\Facade\AvailabilityToProductFacadeInterface;
24
use Spryker\Zed\Availability\Dependency\Facade\AvailabilityToStockFacadeInterface;
25
use Spryker\Zed\Availability\Dependency\Facade\AvailabilityToStoreFacadeInterface;
26
use Spryker\Zed\Availability\Dependency\Facade\AvailabilityToTouchFacadeInterface;
27
use Spryker\Zed\Availability\Persistence\AvailabilityEntityManagerInterface;
28
use Spryker\Zed\Availability\Persistence\AvailabilityRepositoryInterface;
29
30
class AvailabilityHandler implements AvailabilityHandlerInterface
31
{
32
    /**
33
     * @var string
34
     */
35
    protected const PRODUCT_SKU_NOT_FOUND_EXCEPTION_MESSAGE_FORMAT = 'The product was not found with this SKU: %s';
36
37
    /**
38
     * @var string
39
     */
40
    protected const PRODUCT_ID_NOT_FOUND_EXCEPTION_MESSAGE_FORMAT = 'The product was not found with this ID: %d';
41
42
    /**
43
     * @var string
44
     */
45
    protected const TABLE_NAME = 'spy_stock_product';
46
47
    /**
48
     * @var string
49
     */
50
    protected const FK_PRODUCT = 'fk_product';
51
52
    /**
53
     * @var list<\Generated\Shared\Transfer\StoreTransfer>
54
     */
55
    protected static $allStoreTransfersCache = [];
56
57
    /**
58
     * @var \Spryker\Zed\Availability\Persistence\AvailabilityRepositoryInterface
59
     */
60
    protected $availabilityRepository;
61
62
    /**
63
     * @var \Spryker\Zed\Availability\Persistence\AvailabilityEntityManagerInterface
64
     */
65
    protected $availabilityEntityManager;
66
67
    /**
68
     * @var \Spryker\Zed\Availability\Business\Model\ProductAvailabilityCalculatorInterface
69
     */
70
    protected $availabilityCalculator;
71
72
    /**
73
     * @var \Spryker\Zed\Availability\Dependency\Facade\AvailabilityToTouchFacadeInterface
74
     */
75
    protected $touchFacade;
76
77
    /**
78
     * @var \Spryker\Zed\Availability\Dependency\Facade\AvailabilityToStockFacadeInterface
79
     */
80
    protected $stockFacade;
81
82
    /**
83
     * @var \Spryker\Zed\Availability\Dependency\Facade\AvailabilityToEventFacadeInterface
84
     */
85
    protected $eventFacade;
86
87
    /**
88
     * @var \Spryker\Zed\Availability\Dependency\Facade\AvailabilityToProductFacadeInterface
89
     */
90
    protected AvailabilityToProductFacadeInterface $productFacade;
91
92
    /**
93
     * @var \Spryker\Zed\Availability\Dependency\Facade\AvailabilityToStoreFacadeInterface $storeFacade
94
     */
95
    protected AvailabilityToStoreFacadeInterface $storeFacade;
96
97
    /**
98
     * @param \Spryker\Zed\Availability\Persistence\AvailabilityRepositoryInterface $availabilityRepository
99
     * @param \Spryker\Zed\Availability\Persistence\AvailabilityEntityManagerInterface $availabilityEntityManager
100
     * @param \Spryker\Zed\Availability\Business\Model\ProductAvailabilityCalculatorInterface $availabilityCalculator
101
     * @param \Spryker\Zed\Availability\Dependency\Facade\AvailabilityToTouchFacadeInterface $touchFacade
102
     * @param \Spryker\Zed\Availability\Dependency\Facade\AvailabilityToStockFacadeInterface $stockFacade
103
     * @param \Spryker\Zed\Availability\Dependency\Facade\AvailabilityToEventFacadeInterface $eventFacade
104
     * @param \Spryker\Zed\Availability\Dependency\Facade\AvailabilityToProductFacadeInterface $productFacade
105
     * @param \Spryker\Zed\Availability\Dependency\Facade\AvailabilityToStoreFacadeInterface $storeFacade
106
     */
107
    public function __construct(
108
        AvailabilityRepositoryInterface $availabilityRepository,
109
        AvailabilityEntityManagerInterface $availabilityEntityManager,
110
        ProductAvailabilityCalculatorInterface $availabilityCalculator,
111
        AvailabilityToTouchFacadeInterface $touchFacade,
112
        AvailabilityToStockFacadeInterface $stockFacade,
113
        AvailabilityToEventFacadeInterface $eventFacade,
114
        AvailabilityToProductFacadeInterface $productFacade,
115
        AvailabilityToStoreFacadeInterface $storeFacade
116
    ) {
117
        $this->availabilityCalculator = $availabilityCalculator;
118
        $this->availabilityRepository = $availabilityRepository;
119
        $this->availabilityEntityManager = $availabilityEntityManager;
120
        $this->touchFacade = $touchFacade;
121
        $this->stockFacade = $stockFacade;
122
        $this->eventFacade = $eventFacade;
123
        $this->productFacade = $productFacade;
124
        $this->storeFacade = $storeFacade;
125
    }
126
127
    /**
128
     * @param string $concreteSku
129
     *
130
     * @return void
131
     */
132
    public function updateAvailability(string $concreteSku): void
133
    {
134
        $productAvailabilityDataTransfer = $this->availabilityRepository->getProductConcreteWithAvailability($concreteSku);
135
136
        $this->saveAllAvailabilities($productAvailabilityDataTransfer);
137
    }
138
139
    /**
140
     * @param string $concreteSku
141
     * @param \Spryker\DecimalObject\Decimal $quantity
142
     * @param \Generated\Shared\Transfer\StoreTransfer $storeTransfer
143
     *
144
     * @return int
145
     */
146
    public function saveAndTouchAvailability(string $concreteSku, Decimal $quantity, StoreTransfer $storeTransfer): int
147
    {
148
        $wasProductConcreteAvailable = $this->isProductConcreteAvailable(
149
            $this->availabilityRepository->findProductConcreteAvailabilityBySkuAndStore($concreteSku, $storeTransfer),
150
        );
151
152
        /** @var string $productAbstractSku */
153
        $productAbstractSku = $this->availabilityRepository->getAbstractSkuFromProductConcrete($concreteSku);
154
        $productConcreteAvailabilityTransfer = (new ProductConcreteAvailabilityTransfer())
155
            ->setSku($concreteSku)
156
            ->setIsNeverOutOfStock($this->availabilityCalculator->isNeverOutOfStockForStore($concreteSku, $storeTransfer))
157
            ->setAvailability($quantity);
158
159
        $this->updateProductAbstractAvailabilityBySku($productAbstractSku, $storeTransfer);
160
        $idAvailabilityAbstract = $this->availabilityRepository
161
            ->findIdProductAbstractAvailabilityBySku($productAbstractSku, $storeTransfer);
162
163
        $isAvailabilityChanged = $this->availabilityEntityManager->saveProductConcreteAvailability(
164
            $productConcreteAvailabilityTransfer,
165
            $storeTransfer,
166
            $productAbstractSku,
167
        );
168
169
        if ($isAvailabilityChanged) {
170
            $this->touchAvailabilityAbstract($idAvailabilityAbstract);
171
        }
172
173
        if ($isAvailabilityChanged && !$wasProductConcreteAvailable && $this->isProductConcreteAvailable($productConcreteAvailabilityTransfer)) {
174
            $this->triggerProductIsAvailableAgainEvent($concreteSku, $storeTransfer);
175
        }
176
177
        return $idAvailabilityAbstract;
178
    }
179
180
    /**
181
     * @param int $idAvailabilityAbstract
182
     *
183
     * @return void
184
     */
185
    public function touchAvailabilityAbstract($idAvailabilityAbstract)
186
    {
187
        $this->touchFacade->touchActive(AvailabilityConfig::RESOURCE_TYPE_AVAILABILITY_ABSTRACT, $idAvailabilityAbstract);
188
    }
189
190
    /**
191
     * @param int $idProductConcrete
192
     * @param \Generated\Shared\Transfer\StoreTransfer $storeTransfer
193
     *
194
     * @return \Generated\Shared\Transfer\ProductConcreteAvailabilityTransfer
195
     */
196
    public function updateProductConcreteAvailabilityById(
197
        int $idProductConcrete,
198
        StoreTransfer $storeTransfer
199
    ): ProductConcreteAvailabilityTransfer {
200
        $concreteSku = $this->getProductConcreteSkuByConcreteId($idProductConcrete);
201
202
        return $this->updateProductConcreteAvailabilityBySku($concreteSku, $storeTransfer);
203
    }
204
205
    /**
206
     * @param string $concreteSku
207
     * @param \Generated\Shared\Transfer\StoreTransfer $storeTransfer
208
     *
209
     * @return \Generated\Shared\Transfer\ProductConcreteAvailabilityTransfer
210
     */
211
    public function updateProductConcreteAvailabilityBySku(
212
        string $concreteSku,
213
        StoreTransfer $storeTransfer
214
    ): ProductConcreteAvailabilityTransfer {
215
        $productAbstractSku = $this->getAbstractSkuFromProductConcrete($concreteSku);
216
217
        $wasProductConcreteAvailable = $this->isProductConcreteAvailable(
218
            $this->availabilityRepository->findProductConcreteAvailabilityBySkuAndStore($concreteSku, $storeTransfer),
219
        );
220
221
        $productConcreteAvailabilityTransfer = $this->availabilityCalculator
222
            ->getCalculatedProductConcreteAvailabilityTransfer($concreteSku, $storeTransfer);
223
224
        $this->updateProductAbstractAvailabilityBySku($productAbstractSku, $storeTransfer);
225
226
        $isAvailabilityChanged = $this->availabilityEntityManager->saveProductConcreteAvailability(
227
            $productConcreteAvailabilityTransfer,
228
            $storeTransfer,
229
            $productAbstractSku,
230
        );
231
232
        /** @var string $sku */
233
        $sku = $productConcreteAvailabilityTransfer->requireSku()->getSku();
234
        if ($isAvailabilityChanged && !$wasProductConcreteAvailable && $this->isProductConcreteAvailable($productConcreteAvailabilityTransfer)) {
235
            $this->triggerProductIsAvailableAgainEvent($sku, $storeTransfer);
236
        }
237
238
        return $productConcreteAvailabilityTransfer;
239
    }
240
241
    /**
242
     * @param string $productAbstractSku
243
     * @param \Generated\Shared\Transfer\StoreTransfer $storeTransfer
244
     *
245
     * @return \Generated\Shared\Transfer\ProductAbstractAvailabilityTransfer
246
     */
247
    public function updateProductAbstractAvailabilityBySku(
248
        string $productAbstractSku,
249
        StoreTransfer $storeTransfer
250
    ): ProductAbstractAvailabilityTransfer {
251
        $productAbstractAvailabilityTransfer = $this->availabilityCalculator
252
            ->getCalculatedProductAbstractAvailabilityTransfer($productAbstractSku, $storeTransfer);
253
254
        $this->availabilityEntityManager->saveProductAbstractAvailability(
255
            $productAbstractAvailabilityTransfer,
256
            $storeTransfer,
257
        );
258
259
        return $productAbstractAvailabilityTransfer;
260
    }
261
262
    /**
263
     * @param \Generated\Shared\Transfer\DynamicEntityPostEditRequestTransfer $dynamicEntityPostEditRequestTransfer
264
     *
265
     * @return \Generated\Shared\Transfer\DynamicEntityPostEditResponseTransfer
266
     */
267
    public function updateAvailabilityByDynamicEntityRequest(
268
        DynamicEntityPostEditRequestTransfer $dynamicEntityPostEditRequestTransfer
269
    ): DynamicEntityPostEditResponseTransfer {
270
        if ($dynamicEntityPostEditRequestTransfer->getTableNameOrFail() !== static::TABLE_NAME) {
271
            return $this->createDynamicEntityPostEditResponseTransfer();
272
        }
273
274
        $productIds = $this->getProductIdsFromDynamicEntityRequest($dynamicEntityPostEditRequestTransfer);
275
        $productConcreteSkus = $this->productFacade->getProductConcreteSkusByConcreteIds($productIds);
276
        foreach (array_keys($productConcreteSkus) as $sku) {
277
            $this->updateAvailability($sku);
278
        }
279
280
        return $this->createDynamicEntityPostEditResponseTransfer();
281
    }
282
283
    /**
284
     * @param \Generated\Shared\Transfer\DynamicEntityPostEditRequestTransfer $dynamicEntityPostEditRequestTransfer
285
     *
286
     * @return array<int>
287
     */
288
    protected function getProductIdsFromDynamicEntityRequest(DynamicEntityPostEditRequestTransfer $dynamicEntityPostEditRequestTransfer): array
289
    {
290
        $productIds = [];
291
        foreach ($dynamicEntityPostEditRequestTransfer->getRawDynamicEntities() as $rawDynamicEntity) {
292
            $productIds[] = $rawDynamicEntity->getFields()[static::FK_PRODUCT];
293
        }
294
295
        return $productIds;
296
    }
297
298
    /**
299
     * @param \Generated\Shared\Transfer\ProductConcreteAvailabilityTransfer|null $productConcreteAvailabilityTransfer
300
     *
301
     * @return bool
302
     */
303
    protected function isProductConcreteAvailable(?ProductConcreteAvailabilityTransfer $productConcreteAvailabilityTransfer): bool
304
    {
305
        if ($productConcreteAvailabilityTransfer === null) {
306
            return false;
307
        }
308
309
        /** @var \Spryker\DecimalObject\Decimal $availability */
310
        $availability = $productConcreteAvailabilityTransfer->requireAvailability()->getAvailability();
311
312
        return $availability->greaterThan(0) ||
313
            $productConcreteAvailabilityTransfer->getIsNeverOutOfStock() === true;
314
    }
315
316
    /**
317
     * @param string $concreteSku
318
     *
319
     * @throws \Spryker\Zed\Availability\Business\Exception\ProductNotFoundException
320
     *
321
     * @return string
322
     */
323
    protected function getAbstractSkuFromProductConcrete(string $concreteSku): string
324
    {
325
        $productAbstractSku = $this->availabilityRepository->getAbstractSkuFromProductConcrete($concreteSku);
326
327
        if ($productAbstractSku === null) {
328
            throw new ProductNotFoundException(
329
                sprintf(static::PRODUCT_SKU_NOT_FOUND_EXCEPTION_MESSAGE_FORMAT, $concreteSku),
330
            );
331
        }
332
333
        return $productAbstractSku;
334
    }
335
336
    /**
337
     * @param int $idProductConcrete
338
     *
339
     * @throws \Spryker\Zed\Availability\Business\Exception\ProductNotFoundException
340
     *
341
     * @return string
342
     */
343
    protected function getProductConcreteSkuByConcreteId(int $idProductConcrete): string
344
    {
345
        $concreteSku = $this->availabilityRepository->getProductConcreteSkuByConcreteId($idProductConcrete);
346
347
        if ($concreteSku === null) {
348
            throw new ProductNotFoundException(
349
                sprintf(static::PRODUCT_ID_NOT_FOUND_EXCEPTION_MESSAGE_FORMAT, $idProductConcrete),
350
            );
351
        }
352
353
        return $concreteSku;
354
    }
355
356
    /**
357
     * @param string $concreteSku
358
     * @param \Generated\Shared\Transfer\StoreTransfer $storeTransfer
359
     *
360
     * @return void
361
     */
362
    protected function triggerProductIsAvailableAgainEvent(string $concreteSku, StoreTransfer $storeTransfer): void
363
    {
364
        $availabilityNotificationDataTransfer = (new AvailabilityNotificationDataTransfer())
365
            ->setSku($concreteSku)
366
            ->setStore($storeTransfer);
367
368
        $this->eventFacade->trigger(
369
            AvailabilityEvents::AVAILABILITY_NOTIFICATION,
370
            $availabilityNotificationDataTransfer,
371
        );
372
    }
373
374
    /**
375
     * @return \Generated\Shared\Transfer\DynamicEntityPostEditResponseTransfer
376
     */
377
    protected function createDynamicEntityPostEditResponseTransfer(): DynamicEntityPostEditResponseTransfer
378
    {
379
        return new DynamicEntityPostEditResponseTransfer();
380
    }
381
382
    /**
383
     * @param \Generated\Shared\Transfer\ProductAvailabilityDataTransfer $productAvailabilityDataTransfer
384
     *
385
     * @return array<int, array<\Generated\Shared\Transfer\StockProductTransfer>>
386
     */
387
    protected function getStockProductTransfersIndexedByIdStore(ProductAvailabilityDataTransfer $productAvailabilityDataTransfer): array
388
    {
389
        $stockIdsGroupedByIdStore = $this->getStockIdsGroupedByIdStore();
390
        $stockProductsIndexedByStock = [];
391
        foreach ($productAvailabilityDataTransfer->getStockProducts() as $stockProductTransfer) {
392
            $stockProductsIndexedByStock[$stockProductTransfer->getFkStock()] = $stockProductTransfer;
393
        }
394
395
        $stockProductTransfersIndexedByIdStore = [];
396
        foreach ($productAvailabilityDataTransfer->getStocks() as $stockTransfer) {
397
            foreach ($stockTransfer->getStoreRelationOrFail()->getStores() as $storeTransfer) {
398
                if (!isset($stockProductsIndexedByStock[$stockTransfer->getIdStock()]) || !isset($stockIdsGroupedByIdStore[$storeTransfer->getIdStore()])) {
399
                    continue;
400
                }
401
402
                if (!in_array($stockTransfer->getIdStock(), $stockIdsGroupedByIdStore[$storeTransfer->getIdStore()])) {
403
                    continue;
404
                }
405
                $stockProductTransfersIndexedByIdStore[(int)$storeTransfer->getIdStore()][] = $stockProductsIndexedByStock[$stockTransfer->getIdStock()];
406
            }
407
        }
408
409
        return $stockProductTransfersIndexedByIdStore;
410
    }
411
412
    /**
413
     * @param \Generated\Shared\Transfer\ProductAvailabilityDataTransfer $productAvailabilityDataTransfer
414
     *
415
     * @return array<int, \Generated\Shared\Transfer\ProductConcreteAvailabilityTransfer>
416
     */
417
    protected function getProductConcreteAvailabilityTransfersIndexedByIdStore(ProductAvailabilityDataTransfer $productAvailabilityDataTransfer): array
418
    {
419
        $productConcreteAvailabilityTransfersIndexedByIdStore = [];
420
        foreach ($productAvailabilityDataTransfer->getProductConcreteAvailabilities() as $productConcreteAvailabilityTransfer) {
421
            $productConcreteAvailabilityTransfersIndexedByIdStore[(int)$productConcreteAvailabilityTransfer->getStoreOrFail()->getIdStore()] = $productConcreteAvailabilityTransfer;
422
        }
423
424
        return $productConcreteAvailabilityTransfersIndexedByIdStore;
425
    }
426
427
    /**
428
     * @param \Generated\Shared\Transfer\ProductAvailabilityDataTransfer $productAvailabilityDataTransfer
429
     *
430
     * @return array<int, \Generated\Shared\Transfer\ProductAbstractAvailabilityTransfer>
431
     */
432
    protected function getProductAbstractAvailabilityTransfersIndexedByIdStore(ProductAvailabilityDataTransfer $productAvailabilityDataTransfer): array
433
    {
434
        $productAbstractAvailabilityTransfersIndexedByIdStore = [];
435
        foreach ($productAvailabilityDataTransfer->getProductAbstractAvailabilities() as $productAbstractAvailabilityTransfer) {
436
            $productAbstractAvailabilityTransfersIndexedByIdStore[(int)$productAbstractAvailabilityTransfer->getIdStore()] = $productAbstractAvailabilityTransfer;
437
        }
438
439
        return $productAbstractAvailabilityTransfersIndexedByIdStore;
440
    }
441
442
    /**
443
     * @param string $productAbstractSku
444
     * @param \Generated\Shared\Transfer\StoreTransfer $storeTransfer
445
     * @param array<int, \Generated\Shared\Transfer\ProductAbstractAvailabilityTransfer> $productAbstractAvailabilityTransfersIndexedByIdStore
446
     *
447
     * @return void
448
     */
449
    protected function updateProductAbstractAvailability(
450
        string $productAbstractSku,
451
        StoreTransfer $storeTransfer,
452
        array $productAbstractAvailabilityTransfersIndexedByIdStore
453
    ): void {
454
        $calculatedProductAbstractAvailabilityTransfer = $this->availabilityCalculator
455
            ->getCalculatedProductAbstractAvailabilityTransfer($productAbstractSku, $storeTransfer);
456
457
        /** @var \Generated\Shared\Transfer\ProductAbstractAvailabilityTransfer|null $productAbstractAvailabilityTransfer */
458
        $productAbstractAvailabilityTransfer = $productAbstractAvailabilityTransfersIndexedByIdStore[$storeTransfer->getIdStore()] ?? null;
459
        $abstractAvailabilityQuantity = isset($productAbstractAvailabilityTransfersIndexedByIdStore[$storeTransfer->getIdStore()]) ?
460
            $productAbstractAvailabilityTransfer?->getAvailability() :
461
            null;
462
463
        if ($abstractAvailabilityQuantity === null || !$calculatedProductAbstractAvailabilityTransfer->getAvailabilityOrFail()->equals($abstractAvailabilityQuantity)) {
464
            $this->availabilityEntityManager->saveProductAbstractAvailability(
465
                $calculatedProductAbstractAvailabilityTransfer,
466
                $storeTransfer,
467
            );
468
        }
469
    }
470
471
    /**
472
     * @return list<\Generated\Shared\Transfer\StoreTransfer>
473
     */
474
    protected function getAllStoreTransfersCache(): array
475
    {
476
        if (static::$allStoreTransfersCache) {
477
            return static::$allStoreTransfersCache;
0 ignored issues
show
Bug Best Practice introduced by
The expression return static::allStoreTransfersCache returns the type Spryker\Zed\Availability\Business\Model\list which is incompatible with the type-hinted return array.
Loading history...
478
        }
479
        static::$allStoreTransfersCache = $this->storeFacade->getAllStores();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->storeFacade->getAllStores() of type array is incompatible with the declared type Spryker\Zed\Availability\Business\Model\list of property $allStoreTransfersCache.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
480
481
        return static::$allStoreTransfersCache;
482
    }
483
484
    /**
485
     * @param \Generated\Shared\Transfer\ProductAvailabilityDataTransfer $productAvailabilityDataTransfer
486
     *
487
     * @return void
488
     */
489
    protected function saveAllAvailabilities(ProductAvailabilityDataTransfer $productAvailabilityDataTransfer): void
490
    {
491
        if (!$productAvailabilityDataTransfer->getProductConcrete() && !$productAvailabilityDataTransfer->getProductAbstract()) {
492
            return;
493
        }
494
495
        $stockProductTransfersIndexedByIdStore = $this->getStockProductTransfersIndexedByIdStore($productAvailabilityDataTransfer);
496
        $productConcreteAvailabilityTransfersIndexedByIdStore = $this->getProductConcreteAvailabilityTransfersIndexedByIdStore($productAvailabilityDataTransfer);
497
        $productAbstractAvailabilityTransfersIndexedByIdStore = $this->getProductAbstractAvailabilityTransfersIndexedByIdStore($productAvailabilityDataTransfer);
498
        $productAbstractSku = $productAvailabilityDataTransfer->getProductAbstractOrFail()->getSkuOrFail();
499
        $productConcreteSku = $productAvailabilityDataTransfer->getProductConcreteOrFail()->getSkuOrFail();
500
501
        foreach ($this->getAllStoreTransfersCache() as $storeTransfer) {
502
            $this->executeSaveAllAvailabilitiesPerStore(
503
                $stockProductTransfersIndexedByIdStore,
504
                $productConcreteAvailabilityTransfersIndexedByIdStore,
505
                $productAbstractAvailabilityTransfersIndexedByIdStore,
506
                $productAbstractSku,
507
                $productConcreteSku,
508
                $storeTransfer,
509
            );
510
        }
511
    }
512
513
    /**
514
     * @param array<int, array<\Generated\Shared\Transfer\StockProductTransfer>> $stockProductTransfersIndexedByIdStore
515
     * @param array<int, \Generated\Shared\Transfer\ProductConcreteAvailabilityTransfer> $productConcreteAvailabilityTransfersIndexedByIdStore
516
     * @param array<int, \Generated\Shared\Transfer\ProductAbstractAvailabilityTransfer> $productAbstractAvailabilityTransfersIndexedByIdStore
517
     * @param string $productAbstractSku
518
     * @param string $productConcreteSku
519
     * @param \Generated\Shared\Transfer\StoreTransfer $storeTransfer
520
     *
521
     * @return void
522
     */
523
    protected function executeSaveAllAvailabilitiesPerStore(
524
        array $stockProductTransfersIndexedByIdStore,
525
        array $productConcreteAvailabilityTransfersIndexedByIdStore,
526
        array $productAbstractAvailabilityTransfersIndexedByIdStore,
527
        string $productAbstractSku,
528
        string $productConcreteSku,
529
        StoreTransfer $storeTransfer
530
    ): void {
531
        $wasProductConcreteAvailable = $this->isProductConcreteAvailable(
532
            $productConcreteAvailabilityTransfersIndexedByIdStore[$storeTransfer->getIdStore()] ?? null,
533
        );
534
535
        $this->updateProductAbstractAvailability($productAbstractSku, $storeTransfer, $productAbstractAvailabilityTransfersIndexedByIdStore);
536
537
        $quantity = new Decimal(0);
538
        if (isset($stockProductTransfersIndexedByIdStore[$storeTransfer->getIdStore()])) {
539
            $quantity = $this->availabilityCalculator->calculateAvailabilityForProductConcrete(
540
                $productConcreteSku,
541
                $storeTransfer,
542
                $stockProductTransfersIndexedByIdStore[$storeTransfer->getIdStore()],
543
            );
544
        }
545
546
        $isNeverOutOfStock = $this->isNeverOutOfStock($stockProductTransfersIndexedByIdStore, $storeTransfer);
547
        $productConcreteAvailabilityTransfer = (new ProductConcreteAvailabilityTransfer())
548
            ->setSku($productConcreteSku)
549
            ->setIsNeverOutOfStock($isNeverOutOfStock)
550
            ->setAvailability($quantity);
551
552
        if (isset($productConcreteAvailabilityTransfersIndexedByIdStore[$storeTransfer->getIdStore()])) {
553
            $productConcreteAvailabilityTransfer = $productConcreteAvailabilityTransfersIndexedByIdStore[$storeTransfer->getIdStore()];
554
        }
555
556
        $isAvailabilityChanged = false;
557
        if (
558
            !$quantity->equals($productConcreteAvailabilityTransfer->getAvailability() ?? 0) ||
559
            $productConcreteAvailabilityTransfer->getIsNeverOutOfStock() !== $isNeverOutOfStock ||
560
            !isset($productConcreteAvailabilityTransfersIndexedByIdStore[$storeTransfer->getIdStore()])
561
        ) {
562
            $productConcreteAvailabilityTransfer->setAvailability($quantity);
563
            $productConcreteAvailabilityTransfer->setIsNeverOutOfStock($isNeverOutOfStock);
564
            $isAvailabilityChanged = $this->availabilityEntityManager->saveProductConcreteAvailability(
565
                $productConcreteAvailabilityTransfer,
566
                $storeTransfer,
567
                $productAbstractSku,
568
            );
569
        }
570
571
        if ($isAvailabilityChanged && $this->touchFacade->isTouchEnabled()) {
572
            $idAvailabilityAbstract = $productConcreteAvailabilityTransfer->getFkAvailabilityAbstract();
573
            if ($idAvailabilityAbstract === null) {
574
                $idAvailabilityAbstract = $this->availabilityRepository
575
                    ->findIdProductAbstractAvailabilityBySku($productAbstractSku, $storeTransfer);
576
            }
577
            $this->touchAvailabilityAbstract($idAvailabilityAbstract);
578
        }
579
580
        if ($isAvailabilityChanged && !$wasProductConcreteAvailable && $this->isProductConcreteAvailable($productConcreteAvailabilityTransfer)) {
581
            $this->triggerProductIsAvailableAgainEvent($productConcreteSku, $storeTransfer);
582
        }
583
    }
584
585
    /**
586
     * @param array<int, array<\Generated\Shared\Transfer\StockProductTransfer>> $stockProductTransfersIndexedByIdStore
587
     * @param \Generated\Shared\Transfer\StoreTransfer $storeTransfer
588
     *
589
     * @return bool
590
     */
591
    protected function isNeverOutOfStock(array $stockProductTransfersIndexedByIdStore, StoreTransfer $storeTransfer): bool
592
    {
593
        if (!isset($stockProductTransfersIndexedByIdStore[$storeTransfer->getIdStore()])) {
594
            return false;
595
        }
596
597
        foreach ($stockProductTransfersIndexedByIdStore[$storeTransfer->getIdStore()] as $stockProductTransfer) {
598
            if ($stockProductTransfer->getIsNeverOutOfStock()) {
599
                return true;
600
            }
601
        }
602
603
        return false;
604
    }
605
606
    /**
607
     * @return array<int, array<int, int>>
608
     */
609
    protected function getStockIdsGroupedByIdStore(): array
610
    {
611
        $stockStoreCollectionTransfer = $this->stockFacade->getStockStoreCollection(new StockStoreCriteriaTransfer());
612
        $stockIdsGroupedByIdStore = [];
613
        /**
614
         * @var \ArrayObject<int, \Generated\Shared\Transfer\StockStoreTransfer>|null $stockStoreTransfers
615
         */
616
        $stockStoreTransfers = $stockStoreCollectionTransfer->getStockStores();
617
        if (!$stockStoreTransfers) {
0 ignored issues
show
introduced by
$stockStoreTransfers is of type ArrayObject, thus it always evaluated to true.
Loading history...
618
            return $stockIdsGroupedByIdStore;
619
        }
620
621
        foreach ($stockStoreTransfers as $stockStoreTransfer) {
622
            $stockIdsGroupedByIdStore[(int)$stockStoreTransfer->getFkStore()][] = (int)$stockStoreTransfer->getFkStock();
623
        }
624
625
        return $stockIdsGroupedByIdStore;
626
    }
627
}
628