Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
storestocksale.php
1<?php
2
4
5\Bitrix\Main\Loader::includeModule('sale');
6
26use Bitrix\Main\Entity\Base;
27
29final class StoreStockSale
30{
31 protected const DEFAULT_DATE_INTERVAL = '-30D';
32
33 protected static $defaultCurrency;
34
35 protected static $productPrice;
36
37 public static function getProductsSoldAmountForStores($filter = []): array
38 {
39 $soldProductsDbResult = self::getProductsSoldAmountFromShipmentsList($filter);
40 $result = [];
41 while ($soldProduct = $soldProductsDbResult->fetch())
42 {
43 $storeId = (int)$soldProduct['STORE_ID'];
44 if (!isset($result[$storeId]))
45 {
46 $result[$storeId] = [];
47 }
48
49 $measureId = (int)$soldProduct['MEASURE_ID'] ?: \CCatalogMeasure::getDefaultMeasure(true)['ID'];
50 if (!isset($result[$storeId][$measureId]))
51 {
52 $result[$storeId][$measureId] = 0.0;
53 }
54
55 $result[$storeId][$measureId] += (float)$soldProduct['QUANTITY_SUM'];
56 }
57
58 return $result;
59 }
60
61 public static function getProductsSoldAmountForProducts($filter = []): array
62 {
63 $shipmentsDbResult = self::getProductsSoldAmountFromShipmentsList($filter);
64 $result = [];
65
66 while ($row = $shipmentsDbResult->fetch())
67 {
68 $result[$row['PRODUCT_ID']] ??= 0;
69 $result[$row['PRODUCT_ID']] += (float)$row['QUANTITY_SUM'];
70 }
71
72 return $result;
73 }
74 public static function getProductsSoldAmountForProductsOnStore(int $storeId, $filter = []): array
75 {
76 $filter['STORES'] = $storeId;
77
79 }
80
81 public static function getProductsSoldPricesForStores(array $filter = []): array
82 {
83 $soldProductsDbResult = self::getProductsSoldPricesFromShipmentsList($filter);
84 $result = [];
85 while ($soldProduct = $soldProductsDbResult->fetch())
86 {
87 $storeId = (int)$soldProduct['STORE_ID'];
88 $result[$storeId] ??= [];
89 $currencyId = $soldProduct['CURRENCY'];
90 $result[$storeId][$currencyId] ??= [];
91 $result[$storeId][$currencyId]['COST_PRICE'] ??= 0.0;
92 $result[$storeId][$currencyId]['COST_PRICE'] += (float)$soldProduct['COST_PRICE_SUM'];
93 $result[$storeId][$currencyId]['TOTAL_SOLD'] ??= 0.0;
94 $result[$storeId][$currencyId]['TOTAL_SOLD'] += (float)$soldProduct['TOTAL_SOLD'];
95 }
96
97 return $result;
98 }
99
100 public static function getProductsSoldPricesForProducts($filter = []): array
101 {
102 $shipmentsDbResult = self::getProductsSoldPricesFromShipmentsList($filter);
103 $result = [];
104
105 while ($row = $shipmentsDbResult->fetch())
106 {
107 $currencyId = $row['CURRENCY'];
108 $result[$row['PRODUCT_ID']][$currencyId] ??= [];
109 $result[$row['PRODUCT_ID']][$currencyId]['COST_PRICE'] ??= 0.0;
110 $result[$row['PRODUCT_ID']][$currencyId]['COST_PRICE'] += (float)$row['COST_PRICE_SUM'];
111 $result[$row['PRODUCT_ID']][$currencyId]['TOTAL_SOLD'] ??= 0.0;
112 $result[$row['PRODUCT_ID']][$currencyId]['TOTAL_SOLD'] += (float)$row['TOTAL_SOLD'];
113 }
114
115 return $result;
116 }
117
118 public static function getProductsSoldPricesForProductsOnStore(int $storeId, $filter = []): array
119 {
120 $filter['=STORES'] = $storeId;
121
123 }
124
125 public static function getProductsSoldPricesForDeductedPeriod(array $filter = []): array
126 {
127 $getListParameters = self::getShippedDataListParameters($filter);
128
129 $getListParameters['select']['CURRENCY'] = 'BASKET.CURRENCY';
130 $getListParameters['select']['BASKET_PRICE'] = 'BASKET.PRICE';
131 $getListParameters['select']['DATE_DEDUCTED'] = 'DELIVERY.DATE_DEDUCTED';
132
133 $getListParameters['runtime'][] = new Reference(
134 'S_PRODUCT_BATCH_SHIPMENT',
135 StoreBatchDocumentElementTable::class,
136 Join::on('this.S_BARCODE.ID', 'ref.SHIPMENT_ITEM_STORE_ID')
137 );
138
139 $getListParameters['select']['COST_PRICE'] = 'S_PRODUCT_BATCH_SHIPMENT.BATCH_PRICE';
140 $getListParameters['select']['BASKET_QUANTITY'] = 'S_PRODUCT_BATCH_SHIPMENT.AMOUNT';
141
142 return ShipmentItemTable::getList($getListParameters)->fetchAll();
143 }
144
149 private static function getProductsSoldAmountFromShipmentsList(array $filter = []): \Bitrix\Main\ORM\Query\Result
150 {
151 $getListParameters = self::getShippedDataListParameters($filter);
152
153 $getListParameters['select']['MEASURE_ID'] = 'BASKET.PRODUCT.MEASURE';
154 $getListParameters['select'][] = 'QUANTITY_SUM';
155 $getListParameters['group'] = ['BASKET.PRODUCT_ID', 'S_BARCODE.STORE_ID'];
156 $getListParameters['runtime'][] = new ExpressionField(
157 'QUANTITY_SUM',
158 'SUM(%s)',
159 ['QUANTITY']
160 );
161
162 return ShipmentItemTable::getList($getListParameters);
163 }
164
169 private static function getProductsSoldPricesFromShipmentsList(array $filter = []): \Bitrix\Main\ORM\Query\Result
170 {
171 $getListParameters = self::getShippedDataListParameters($filter);
172
173 $storeBatchQuery = StoreBatchDocumentElementTable::query();
174 $storeBatchQuery->registerRuntimeField(
175 new ExpressionField(
176 'COST_PRICE_SUM',
177 'SUM(%s * %s * -1)',
178 ['AMOUNT', 'BATCH_PRICE']
179 )
180 );
181 $storeBatchQuery->registerRuntimeField(
182 new ExpressionField(
183 'SUM_AMOUNT',
184 'SUM(%s * -1)',
185 ['AMOUNT']
186 )
187 );
188 $storeBatchQuery->setSelect([
189 'SHIPMENT_ITEM_STORE_ID',
190 'COST_PRICE_SUM',
191 'BATCH_CURRENCY',
192 'SUM_AMOUNT'
193 ]);
194 $storeBatchQuery->setGroup(['SHIPMENT_ITEM_STORE_ID', 'BATCH_CURRENCY']);
195
196
197 $getListParameters['runtime'][] = new Reference(
198 'SUBQUERY',
199 Base::getInstanceByQuery($storeBatchQuery),
200 Join::on('this.S_BARCODE.ID', 'ref.SHIPMENT_ITEM_STORE_ID')
201 );
202
203 $getListParameters['select']['COST_PRICE_SUM'] = 'SUBQUERY.COST_PRICE_SUM';
204 $getListParameters['select']['CURRENCY'] = 'SUBQUERY.BATCH_CURRENCY';
205 $getListParameters['runtime'][] = new ExpressionField(
206 'TOTAL_SOLD',
207 'SUM(%s * %s)',
208 ['BASKET.PRICE', 'SUBQUERY.SUM_AMOUNT']
209 );
210
211 $getListParameters['select'][] = 'TOTAL_SOLD';
212 $getListParameters['group'] = ['BASKET.PRODUCT_ID', 'S_BARCODE.STORE_ID', 'CURRENCY'];
213
214 return ShipmentItemTable::getList($getListParameters);
215 }
216
217 public static function getStoreStockSaleData(bool $isOneField, array $filter): array
218 {
219 $filter = self::prepareFilter($filter);
220 $reservedData = self::getReservedData($filter);
221
222 $productIds = array_column($reservedData, 'PRODUCT_ID');
223 self::initProductPrice($productIds);
224
225 $storeIds =
226 $filter['STORES']
227 ?? array_column($reservedData, 'STORE_ID')
228 ;
229
230 $storesData = [];
231 if ($isOneField)
232 {
233 $storesData = self::formField($reservedData);
234 $storesData['STORE_IDS'] = $storeIds;
235 }
236 else
237 {
238 $storesPositionData = array_fill_keys(
239 $storeIds,
240 [
241 'reservedData' => [],
242 ]
243 );
244
245 foreach ($reservedData as $reservedPosition)
246 {
247 $storesPositionData[$reservedPosition['STORE_ID']]['reservedData'][] = $reservedPosition;
248 }
249
250 foreach ($storesPositionData as $storeId => $fieldData)
251 {
252 $storesData[] = self::formField($fieldData['reservedData'], $storeId);
253 }
254 }
255
256 return $storesData;
257 }
258
259 public static function getDefaultReportInterval(): array
260 {
261 $currentDate = new DateTime();
262 $intervalStartDate = new DateTime();
263 $intervalStartDate->add(self::DEFAULT_DATE_INTERVAL);
264
265 return [
266 'FROM' => $intervalStartDate->toString(),
267 'TO' => $currentDate->toString(),
268 ];
269 }
270
271 private static function getProductPrice(int $productId): float
272 {
273 if (!isset(self::$productPrice[$productId]))
274 {
275 self::initProductPrice([$productId]);
276 self::$productPrice[$productId] ??= 0;
277 }
278
279 return self::$productPrice[$productId];
280 }
281
282 private static function prepareFilter(array $filter): array
283 {
284 if (isset($filter['REPORT_INTERVAL_from']) && isset($filter['REPORT_INTERVAL_to']))
285 {
286 $filter['REPORT_INTERVAL'] = [
287 'FROM' => $filter['REPORT_INTERVAL_from'],
288 'TO' => $filter['REPORT_INTERVAL_to'],
289 ];
290 }
291
292 $accessController = AccessController::getCurrent();
293 if (!$accessController->checkCompleteRight(ActionDictionary::ACTION_STORE_VIEW))
294 {
295 $availableStores = $accessController->getPermissionValue(ActionDictionary::ACTION_STORE_VIEW) ?? [];
296
297 if (isset($filter['STORES']) && is_array($filter['STORES']))
298 {
299 $filter['STORES'] = array_values(array_intersect($availableStores, $filter['STORES']));
300 }
301 else
302 {
303 $filter['STORES'] = $availableStores;
304 }
305 }
306
307 if
308 (
309 !isset($filter['REPORT_INTERVAL'])
310 || !isset($filter['REPORT_INTERVAL']['FROM'])
311 || !isset($filter['REPORT_INTERVAL']['TO'])
312 )
313 {
314 $filter['REPORT_INTERVAL'] = self::getDefaultReportInterval();
315 }
316
317 $filter['INNER_MOVEMENT'] = (bool)($filter['INNER_MOVEMENT'] ?? true);
318
319 return $filter;
320 }
321
322
331 public static function getShippedData(array $filter): array
332 {
333 $getListParameters = self::getShippedDataListParameters($filter);
334 $getListParameters['select']['QUANTITY'] = 'QUANTITY';
335
336 return self::formStoresListFromStoresData($filter, ShipmentItemTable::getList($getListParameters)->fetchAll());
337 }
338
339 protected static function formStoresListFromStoresData(array $filter, array $storesData): array
340 {
341 ProductInfo::initBasePrice(...array_column($storesData, 'PRODUCT_ID'));
342 StoreInfo::loadStoreName(...array_column($storesData, 'STORE_ID'));
343
344 $storesInfo = [];
345
346 if (isset($filter['STORES']))
347 {
348 $storesInfo = array_fill_keys($filter['STORES'], []);
349 }
350 foreach ($storesData as $shipmentItem)
351 {
352 $storeId = $shipmentItem['STORE_ID'];
353 $productId = $shipmentItem['PRODUCT_ID'];
354 if (!isset($storesInfo[$storeId]))
355 {
356 $storesInfo[$storeId] = [];
357 }
358
359 if (!isset($storesInfo[$storeId][$productId]))
360 {
361 $storesInfo[$storeId][$productId] = (float)$shipmentItem['QUANTITY'];
362 }
363 else
364 {
365 $storesInfo[$storeId][$productId] += (float)$shipmentItem['QUANTITY'];
366 }
367 }
368
369 $stores = [];
370 foreach ($storesInfo as $storeId => $storeInfo)
371 {
372 $store = new StoreWithProductsInfo($storeId);
373 foreach ($storeInfo as $productId => $quantity)
374 {
375 $store->addProduct(new ProductInfo($productId, $quantity));
376 }
377 $stores[] = $store;
378 }
379
380 return $stores;
381 }
382
383 protected static function getShippedDataListParameters(array $filter): array
384 {
385 $filter = self::prepareFilter($filter);
386
387 return [
388 'select' => [
389 'STORE_ID' => 'S_BARCODE.STORE_ID',
390 'PRODUCT_ID' => 'BASKET.PRODUCT_ID',
391 ],
392
393 'filter' => self::formShipmentDataFilter($filter),
394
395 'runtime' => [
396 (new Reference(
397 'S_BARCODE',
398 ShipmentItemStoreTable::class,
399 Join::on('this.ID', 'ref.ORDER_DELIVERY_BASKET_ID')
400 ))->configureJoinType(Join::TYPE_LEFT),
401 ],
402 ];
403 }
404
405 private static function formShipmentDataFilter(array $filter): array
406 {
407 $formedFilter = [
408 '=DELIVERY.DEDUCTED' => 'Y',
409 '>S_BARCODE.STORE_ID' => 0,
410 ];
411
412 if (isset($filter['STORES']))
413 {
414 $formedFilter['=S_BARCODE.STORE_ID'] = $filter['STORES'];
415 }
416
417 if (isset($filter['=STORES']))
418 {
419 $formedFilter['=S_BARCODE.STORE_ID'] = $filter['=STORES'];
420 }
421
422 if (isset($filter['PRODUCTS']))
423 {
424 $formedFilter['=BASKET.PRODUCT_ID'] = $filter['PRODUCTS'];
425 }
426
427 if (isset($filter['REPORT_INTERVAL']))
428 {
429 $formedFilter['>=DELIVERY.DATE_DEDUCTED'] = new DateTime($filter['REPORT_INTERVAL']['FROM']);
430 $formedFilter['<=DELIVERY.DATE_DEDUCTED'] = new DateTime($filter['REPORT_INTERVAL']['TO']);
431 }
432
433 return $formedFilter;
434 }
435
442 public static function getArrivedData(array $filter): array
443 {
444 $getListParameters = self::getArrivedDataListParameters($filter);
445 return self::formStoresListFromStoresData($filter, StoreDocumentTable::getList($getListParameters)->fetchAll());
446 }
447
456 public static function computeSoldPercent(float $shippedSum, float $arrivedSum, int $precision = 2): float
457 {
458
459 if ($shippedSum === 0.0)
460 {
461 $soldPercent = 0;
462 }
463 elseif ($arrivedSum === 0.0)
464 {
465 $soldPercent = 100;
466 }
467 else
468 {
469 $soldPercent = ($shippedSum / $arrivedSum) * 100;
470 }
471
472 return round($soldPercent, $precision);
473 }
474
475 protected static function getArrivedDataListParameters(array $filter): array
476 {
477 $filter = self::prepareFilter($filter);
478
479 return [
480 'select' => [
481 'PRODUCT_ID' => 'ELEMENTS.ELEMENT_ID',
482 'QUANTITY' => 'ELEMENTS.AMOUNT',
483 'STORE_ID' => 'ELEMENTS.STORE_TO',
484 ],
485
486 'filter' => self::formArrivedDataFilter($filter),
487 ];
488 }
489
490 private static function formArrivedDataFilter(array $filter): array
491 {
493 if ($filter['INNER_MOVEMENT'])
494 {
496 }
497 $formedFilter = [
498 '=DOC_TYPE' => $docTypes,
499 '=STATUS' => 'Y',
500 '>ELEMENTS.ELEMENT_ID' => 0,
501 ];
502
503 if (isset($filter['STORES']))
504 {
505 $formedFilter['=ELEMENTS.STORE_TO'] = $filter['STORES'];
506 }
507
508 if (isset($filter['PRODUCTS']))
509 {
510 $formedFilter['=ELEMENTS.ELEMENT_ID'] = $filter['PRODUCTS'];
511 }
512
513 if (isset($filter['REPORT_INTERVAL']))
514 {
515 $formedFilter['>=DATE_STATUS'] = new DateTime($filter['REPORT_INTERVAL']['FROM']);
516 $formedFilter['<=DATE_STATUS'] = new DateTime($filter['REPORT_INTERVAL']['TO']);
517 }
518
519 return $formedFilter;
520 }
521
522 public static function getReservedData(array $filter): array
523 {
524 $reservedData = StoreProductTable::getList([
525 'select' => [
526 'PRODUCT_ID',
527 'STORE_ID',
528 'QUANTITY' => 'AMOUNT',
529 ],
530 'filter' => self::formReservedDataFilter($filter),
531 ])->fetchAll();
532
533 return self::formStoresListFromStoresData($filter, $reservedData);
534 }
535
536 private static function formReservedDataFilter(array $filter): array
537 {
538 $formedFilter = [
539 '>STORE_ID' => 0,
540 ];
541
542 $filter = self::prepareFilter($filter);
543 if (isset($filter['STORES']))
544 {
545 $formedFilter['=STORE_ID'] = $filter['STORES'];
546 }
547 else
548 {
549 $formedFilter['!=QUANTITY'] = 0;
550 }
551
552 if (isset($filter['PRODUCTS']))
553 {
554 $formedFilter['=PRODUCT_ID'] = $filter['PRODUCTS'];
555 }
556
557 return $formedFilter;
558 }
559
560 private static function combineUniqueColumnElements(array $arraysList, string $columnKey): array
561 {
562 $combineColumnElements = [];
563 foreach ($arraysList as $item)
564 {
565 $columnElements = array_column($item, $columnKey);
566 array_push($combineColumnElements, ...$columnElements);
567 }
568
569 return array_unique($combineColumnElements);
570 }
571
572 protected static function formField(array $storeReservedData, int $storeId = null): array
573 {
574 $storedSum = 0.0;
575 foreach ($storeReservedData as $storePosition)
576 {
577 $storedSum += self::getPositionPrice($storePosition['PRODUCT_ID'], $storePosition['QUANTITY']);
578 }
579
580 $result = [
581 'SUM_STORED' => $storedSum,
582 ];
583
584 if ($storeId !== null)
585 {
586 $result['STORE_ID'] = $storeId;
587 }
588
589 return $result;
590 }
591
592 protected static function getPositionPrice(int $productId, float $productCount): float
593 {
594 return self::getProductPrice($productId) * $productCount;
595 }
596
597 protected static function initProductPrice(array $productIds): void
598 {
599 $defaultCurrency = CurrencyManager::getBaseCurrency();
600 $productsData = ProductTable::getList([
601 'select' => [
602 'ID',
603 'PURCHASING_PRICE',
604 'PURCHASING_CURRENCY',
605 'PURCHASING_CURRENCY_AMOUNT' => 'CURRENCY_TABLE.CURRENT_BASE_RATE',
606 ],
607 'filter' => [
608 '=ID' => $productIds,
609 ],
610 'runtime' => [
611 (new Reference(
612 'CURRENCY_TABLE',
613 CurrencyTable::class,
614 Join::on('this.PURCHASING_CURRENCY', 'ref.CURRENCY')
615 ))->configureJoinType(Join::TYPE_LEFT),
616 ],
617 ])->fetchAll();
618
619 foreach ($productsData as $product)
620 {
621 self::$productPrice[$product['ID']] = (float)$product['PURCHASING_PRICE'];
622 if ($product['PURCHASING_CURRENCY'] !== $defaultCurrency)
623 {
624 $defaultCurrencyAmount = (float)\CCurrency::getCurrency($defaultCurrency)['CURRENT_BASE_RATE'];
625 $currentCurrencyAmount = (float)$product['PURCHASING_CURRENCY_AMOUNT'];
626
627 self::$productPrice[$product['ID']] *= $currentCurrencyAmount;
628 self::$productPrice[$product['ID']] /= $defaultCurrencyAmount;
629 }
630 }
631 }
632}
static getPositionPrice(int $productId, float $productCount)
static computeSoldPercent(float $shippedSum, float $arrivedSum, int $precision=2)
static formField(array $storeReservedData, int $storeId=null)
static getStoreStockSaleData(bool $isOneField, array $filter)
static getProductsSoldPricesForProductsOnStore(int $storeId, $filter=[])
static getProductsSoldAmountForProductsOnStore(int $storeId, $filter=[])
static formStoresListFromStoresData(array $filter, array $storesData)
static getList(array $parameters=array())