]> gitweb.erp-flowers.ru Git - erp24_rep/yii-erp24/.git/commitdiff
Рефакторинг цен
authorfomichev <vladimir.fomichev@erp-flowers.ru>
Tue, 10 Jun 2025 15:30:26 +0000 (18:30 +0300)
committerfomichev <vladimir.fomichev@erp-flowers.ru>
Tue, 10 Jun 2025 15:30:26 +0000 (18:30 +0300)
erp24/controllers/AutoPlannogrammaController.php
erp24/services/AutoPlannogrammaService.php

index 791d01966927c531f1dd3ba0bd0a68161f40d913..23e6fdbdb3cdc1cb8ed807330f0940d0ef6dcf58 100644 (file)
@@ -866,7 +866,35 @@ class AutoPlannogrammaController extends BaseController
         // Обработка даты на год и месяц
         if (!empty($filters['year']) && !empty($filters['month'])) {
             $filters['plan_date'] = $filters['year'] . '-' . str_pad($filters['month'], 2, '0', STR_PAD_LEFT) . '-01';
+            $periodEnd   = (new \DateTime())->format('Y-m-d H:i:s');          // «сейчас»
+            $periodStart = (new \DateTime())->modify('-20 days')->format('Y-m-d H:i:s'); // «сейчас – 2 дня»
 
+            $prices = PricesDynamic::find()
+
+                ->where(['product_id' => ['2b5a5fcd-03d4-11ef-84e6-ac1f6b1b7573', '2b72702a-792f-11e8-9edd-1c6f659fb563']])
+
+                ->andWhere(['<=', 'date_from', $periodEnd])
+                ->andWhere(['>=', 'date_to',   $periodStart])
+                ->orderBy(['date_from' => SORT_ASC])
+                ->asArray()
+                ->all();
+
+            $pricesMap =  [];
+            foreach ($prices as $price) {
+                if ($price['product_id'] == '2b72702a-792f-11e8-9edd-1c6f659fb563') {
+                    if ( empty($pricesMap[$price['product_id']][$price['region_id']])) {
+                        $pricesMap[$price['product_id']][$price['region_id']][] = $price['price'];
+                    } else {
+                        $pricesMap[$price['product_id']][$price['region_id']][0] = min($price['price'], $pricesMap[$price['product_id']][$price['region_id']][0]);
+                    }
+                } else {
+                    if ($price['active'] !== 1) {
+                        continue;
+                    }
+                    $pricesMap[$price['product_id']][$price['region_id']][] = $price['price'];
+                }
+            }
+            var_dump($prices);die();
             $service = new AutoPlannogrammaService();
 
             //$goals = $service->calculateFullGoalChain($filters);
@@ -912,6 +940,8 @@ class AutoPlannogrammaController extends BaseController
                 $productSalesShare
             );
 
+
+
 /*            $matrixForecast = MatrixBouquetForecast::find()
                 ->where(['year' => $filters['year'], 'month' => $filters['month']])
                 ->asArray()
@@ -1345,7 +1375,6 @@ class AutoPlannogrammaController extends BaseController
 
             $productForecastSpecies = $service->calculateProductSalesBySpecies($salesProductForecastShare, $cleanedSpeciesGoals);
 
-
             $weeklySales = $service->getHistoricalSpeciesShareByWeek($filters['plan_date'], $filters);
 
             $weeklySalesForecast = $service->calculateWeeklyProductForecastPieces($productForecastSpecies, $weeklySales);
@@ -1703,6 +1732,7 @@ class AutoPlannogrammaController extends BaseController
 
             foreach ($weeksData as $r) {
                 $forecasts = $service->calculateWeekForecastSpeciesProducts($r['category'], $r['subcategory'], $r['species'], $r['store_id'], $r['weekly_goal']);
+
                 foreach ($forecasts as $forecast) {
                     $weeksProductForecast[] = [
                         'category'    => $forecast['category']    ?? '',
index cd70178fb8b745a89567f7d70dd2ea0d164baf17..6095c7731cc24e68d7a6328137d18bbb4a09bd88 100644 (file)
@@ -29,6 +29,7 @@ class AutoPlannogrammaService
     public const TYPE_WRITE_OFFS = 'writeOffs'; // Тип операции: списания
     private const CATEGORY_LOOKBACK_MONTHS = 3; // Период для анализа категорий (месяцы)
     private const LOOKBACK_MONTHS = 2; // Отступаемый шаг от плановой даты перед расчетами
+    const SPECIAL_PRODUCT = '2b72702a-792f-11e8-9edd-1c6f659fb563';
 
     /**
      * Получение списка видимых магазинов
@@ -1508,28 +1509,22 @@ class AutoPlannogrammaService
                 ? BouquetComposition::REGION_MSK
                 : BouquetComposition::REGION_NN;
         }
-        $priceRecords = PricesDynamic::find()
-            ->select(['product_id', 'price'])
-            ->where(['product_id' => $productsIds])
-            ->andWhere(['active' => 1])
-            ->andWhere(['region_id' => $region])
-            ->indexBy('product_id')
-            ->asArray()
-            ->all();
 
-        foreach ($priceRecords as $id => $record) {
-            if ($goal == 0 || (int)$record['price'] == 0) {
+        $pricesMap =  self::buildPricesMap($productsIds, self::SPECIAL_PRODUCT, $region);
+
+        foreach ($pricesMap as $id => $price) {
+            if ($goal == 0 || (float)$price == 0) {
                 $forecast = 0;
             } else {
-                $forecast = round(max($goal / (float)$record['price'], 1), 0);
+                $forecast = round(max($goal / (float)$price, 1), 0);
             }
             $speciesProductForecast[] = [
                 'category' => $category,
                 'subcategory' => $subcategory,
                 'species' => $species,
-                'product_id' => $record['product_id'],
+                'product_id' => $id,
                 'name' => $products[$id]['name'],
-                'price' => $record['price'] ?? $goal ?? 1,
+                'price' => $price ?? $goal ?? 1,
                 'goal' => $goal ?? 0,
                 'forecast' => $forecast
 
@@ -1670,7 +1665,6 @@ class AutoPlannogrammaService
                 $year,
                 $goals,
                 $productSalesShare
-
             );
 
 
@@ -1723,142 +1717,6 @@ class AutoPlannogrammaService
         return $result;
     }
 
-    /**
-     * @param array|int $storeIds
-     * @param int $month
-     * @param int $year
-     * @param int       $regionId
-     * @return array   список строк с полями:
-     *                 sale_id, sale_date, product_id, product_name,
-     *                 component_guid, component_name, component_category,
-     *                 quantity, price, cost
-     */
-    public function getUnmarkedProductsComponents(int $storeId, string $month, string $year, int $regionId = null, $typeFilter = null): array
-    {
-        $storeParams = CityStoreParams::find()
-            ->where(['store_id' => $storeId])
-            ->one();
-
-        $region = $storeParams?->address_region;
-
-        if (!$region && !$regionId) {
-            $cityId = CityStore::find()->select('city_id')->where(['id' => $storeId])->scalar();
-            $region = $cityId == 1
-                ? BouquetComposition::REGION_MSK
-                : BouquetComposition::REGION_NN;
-        }
-
-        $monthStart = sprintf('%04d-%02d-01 00:00:00', $year, $month);
-        $daysInMonth = cal_days_in_month(CAL_GREGORIAN, $month, $year);
-        $monthEnd   = sprintf('%04d-%02d-%02d 23:59:59', $year, $month, $daysInMonth);
-        $salesProducts = Sales::find()
-            ->alias('s')
-            ->select(['s.id', 's.date', 'sp.product_id', 'p1c.type', 'p1c.components' , 'p1c.name'])
-            ->innerJoin(
-                ['sp' => SalesProducts::tableName()],
-                's.id = sp.check_id'
-            )
-            ->innerJoin(
-                ['p1c' => Products1c::tableName()],
-                'p1c.id = sp.product_id'
-            )
-            ->leftJoin(
-                ['nom' => Products1cNomenclature::tableName()],
-                'nom.id = sp.product_id'
-            )
-            ->andWhere(['s.store_id'   => $storeId])
-            ->andWhere(['between', 's.date', $monthStart, $monthEnd])
-            ->andWhere(['not', ['p1c.components' => '']])
-            ->andWhere(['nom.category' => null])
-            ->asArray()
-            ->all();
-
-        $components = [];
-        $rows       = [];
-        foreach ($salesProducts as $sp) {
-            /** @var SalesProducts $sp */
-            $js      = trim($sp['components']);
-            if ($js === '' || $js[0] !== '{') {
-                continue;
-            }
-            $data = @json_decode($js, true);
-            if (!is_array($data)) {
-                continue;
-            }
-            foreach ($data as $guid => $qty) {
-                $qty = (int)$qty;
-                if ($qty <= 0) {
-                    continue;
-                }
-
-                $components[$guid] = true;
-
-                $rows[] = [
-                    'sale_id'     => $sp['id'],
-                    'sale_date'   => $sp['date'],
-                    'product_id'  => $sp['product_id'],
-                    'product_name'=> $sp['name'],
-                    'component_guid' => $guid,
-                    'quantity'    => $qty,
-                ];
-            }
-        }
-
-        if (empty($rows)) {
-            return [];
-        }
-        $guids = array_keys($components);
-
-        $nomenclatures = Products1cNomenclature::find()
-            ->andWhere(['id' => $guids])
-            ->indexBy('id')
-            ->all();
-
-        $priceDynamics = PricesDynamic::find()
-            ->andWhere(['region_id' => $region])
-            ->andWhere(['product_id' => array_values( ArrayHelper::getColumn($nomenclatures, 'id') )])
-            ->orderBy(['date_from' => SORT_DESC])
-            ->all();
-
-        $pricesByProduct = [];
-        foreach ($priceDynamics as $pd) {
-            /** @var PricesDynamic $pd */
-            $pid = $pd->product_id;
-            $pricesByProduct[$pid][] = $pd;
-        }
-
-        $result = [];
-        foreach ($rows as $r) {
-            $guid = $r['component_guid'];
-            $n   = $nomenclatures[$guid] ?? null;
-            $pid = $n?->id;
-            $price = 0;
-            if ($pid && isset($pricesByProduct[$pid])) {
-                foreach ($pricesByProduct[$pid] as $pd) {
-                    if ($pd->date_from <= $r['sale_date'] && $pd->date_to >= $r['sale_date']) {
-                        $price = $pd->price;
-                        break;
-                    }
-                }
-            }
-            $cost = $r['quantity'] * $price;
-
-            $result[] = [
-                'sale_id'            => $r['sale_id'],
-                'sale_date'          => $r['sale_date'],
-                'product_id'         => $r['product_id'],
-                'product_name'       => $r['product_name'],
-                'component_guid'     => $guid,
-                'component_name'     => $n?->name,
-                'component_category' => $n?->category,
-                'quantity'           => $r['quantity'],
-                'price'              => $price,
-                'cost'               => $cost,
-            ];
-        }
-
-        return $result;
-    }
 
     public function calculateProductForecastInPiecesProductsWithHistory(
         int    $storeId,
@@ -1887,13 +1745,8 @@ class AutoPlannogrammaService
                 ? BouquetComposition::REGION_MSK
                 : BouquetComposition::REGION_NN;
         }
-        $priceRecords = PricesDynamic::find()
-            ->where([
-                'product_id' => array_keys($productSalesShare),
-                'region_id' => $region,
-                'active' => 1,
-            ])
-            ->indexBy('product_id')->asArray()->all();
+
+        $pricesMap =  self::buildPricesMap(array_keys($productSalesShare), self::SPECIAL_PRODUCT, $region);
 
         foreach ($productSalesShare as $productId => $data) {
             $share = $data['share'] ?? 0.0;
@@ -1902,7 +1755,7 @@ class AutoPlannogrammaService
                 continue;
             }
 
-            if (!$priceRecords[$productId] || $priceRecords[$productId]['price'] <= 0) {
+            if (!$pricesMap[$productId] || $pricesMap[$productId] <= 0) {
                 continue;
             }
 
@@ -1910,7 +1763,7 @@ class AutoPlannogrammaService
             $cat     = $data['category'];
             $sub     = $data['subcategory'];
             $spec    = $data['species'];
-            $price   = $priceRecords[$productId]['price'];
+            $price   = $pricesMap[$productId];
             if (
                 ! isset(
                     $goalsMap[$storeId],
@@ -2066,28 +1919,18 @@ class AutoPlannogrammaService
             ->all();
         $products = ArrayHelper::getColumn($productShares, 'product_id');
 
-        $prices = PricesDynamic::find()
-            ->select(['product_id', 'price', 'region_id'])
-            ->where(['product_id' => $products])
-            ->andWhere(['active' => 1])
-            ->asArray()
-            ->all();
+        $pricesMap =  self::buildPricesMap($products, self::SPECIAL_PRODUCT);
 
-        $pricesMap =  [];
-        foreach ($prices as $price) {
-            $pricesMap[$price['product_id']][$price['region_id']][] = $price['price'];
-        }
         foreach ($productShares as $shareItem) {
             $storeId = $shareItem['store_id'];
 
             $region = $regions[$storeId]['address_region']
                 ?? BouquetComposition::REGION_NN;
 
-            $priceList = $pricesMap[$shareItem['product_id']][$region] ?? null;
-            $price = is_array($priceList) && count($priceList) > 0
-                ? $priceList[0] ?? 1
-                : 1;
-
+            if (!$pricesMap[$shareItem['product_id']] || $pricesMap[$shareItem['product_id']][$region] <= 0) {
+                continue;
+            }
+            $price = $pricesMap[$shareItem['product_id']][$region];
 
             $key = implode('|', [
                 $shareItem['store_id'],
@@ -2283,7 +2126,7 @@ class AutoPlannogrammaService
             $price = 0;
             $dailyPrices = [];
             foreach ($pricesByProduct[$productId] ?? [] as $priceRecordForProduct) {
-                if ($productId == '2b72702a-792f-11e8-9edd-1c6f659fb563') {
+                if ($productId == self::SPECIAL_PRODUCT) {
                     $saleDay = (new \DateTime($componentDataRecord['sale_date']))->format('Y-m-d');
                     $fromDay = (new \DateTime($priceRecordForProduct->date_from))->modify('-1 day')->format('Y-m-d');
                     $toDay   = (new \DateTime($priceRecordForProduct->date_to  ))->modify('+1 day')->format('Y-m-d');
@@ -2863,4 +2706,82 @@ class AutoPlannogrammaService
         return array_values($filtered);
     }
 
+
+    /**
+     * Строит карту цен за последние 20 дней.
+     *
+     * @param array      $productIds        Список product_id для выборки
+     * @param string     $specialProductId  GUID товара, для которого нужен min
+     * @param int|null   $regionId          Если указан — один регион, иначе — несколько
+     * @return array
+     */
+    public static function buildPricesMap(
+        array $productIds,
+        string $specialProductId,
+        ?int $regionId = null
+    ): array {
+        $periodEnd   = (new \DateTime())->format('Y-m-d H:i:s');
+        $periodStart = (new \DateTime())->modify('-20 days')->format('Y-m-d H:i:s');
+
+
+        $query = PricesDynamic::find()
+            ->select(['product_id', 'region_id', 'price', 'active', 'date_from', 'date_to'])
+            ->where(['product_id' => $productIds])
+            ->andWhere(['<=', 'date_from', $periodEnd])
+            ->andWhere(['>=', 'date_to',   $periodStart])
+            ->orderBy(['date_from' => SORT_ASC]);
+
+        if ($regionId !== null) {
+            $query->andWhere(['region_id' => $regionId]);
+        }
+
+        $priceRecords = $query->asArray()->all();
+        $pricesMap = [];
+        $multiRegion = $regionId === null;
+
+        foreach ($priceRecords as $priceRecord) {
+            $productId   = $priceRecord['product_id'];
+            $price = (float)$priceRecord['price'];
+
+            $regionId   = $multiRegion
+                ? $priceRecord['region_id']
+                : null;
+
+            if ($productId === $specialProductId) {
+                if ($multiRegion) {
+                    if (!isset($pricesMap[$productId][$regionId])) {
+                        $pricesMap[$productId][$regionId] = [$price];
+                    } else {
+                        $pricesMap[$productId][$regionId] = min(
+                            $pricesMap[$productId][$regionId],
+                            $price
+                        );
+                    }
+                } else {
+                    if (!isset($pricesMap[$productId])) {
+                        $pricesMap[$productId] = [$price];
+                    } else {
+                        $pricesMap[$productId] = min(
+                            $pricesMap[$productId],
+                            $price
+                        );
+                    }
+                }
+                continue;
+            }
+
+            if (isset($priceRecord['active']) && $priceRecord['active'] !== 1) {
+                continue;
+            }
+
+            if ($multiRegion) {
+                $pricesMap[$productId][$regionId] = $price;
+            } else {
+                $pricesMap[$productId] = $price;
+            }
+        }
+
+        return $pricesMap;
+    }
+
 }
\ No newline at end of file