]> gitweb.erp-flowers.ru Git - erp24_rep/yii-erp24/.git/commitdiff
ERP-359 Создать логику расчета на месяц - сумма продаж вида для автопм
authormarina <m.zozirova@gmail.com>
Fri, 18 Apr 2025 11:27:09 +0000 (14:27 +0300)
committermarina <m.zozirova@gmail.com>
Fri, 18 Apr 2025 11:27:09 +0000 (14:27 +0300)
erp24/controllers/AutoPlanogrammaController.php
erp24/services/AutoPlannogrammaService.php
erp24/views/auto-planogramma/calculating.php [new file with mode: 0644]
erp24/views/auto-planogramma/control.php [new file with mode: 0644]
erp24/views/auto-planogramma/sales.php [deleted file]
erp24/views/auto-planogramma/test-sales.php [deleted file]

index 4af47320ecf2a8b451dc8a9e685471cdd39eedce..bbbb297b9bfdf044702dbc86ea6f0f29ff986e07 100644 (file)
@@ -69,7 +69,7 @@ class AutoPlanogrammaController extends BaseController
         ]);
     }
 
-    public function actionTestSales()
+    public function actionCalculating()
     {
         $request = Yii::$app->request;
 
@@ -90,62 +90,50 @@ class AutoPlanogrammaController extends BaseController
         ]);
 
         if (!empty($filters['plan_date'])) {
+            $filters = [
+                'category' => $request->get('category'),
+                'subcategory' => $request->get('subcategory'),
+                'product_name' => $request->get('product_name'),
+                'store_id' => $request->get('store_id'),
+                'plan_date' => $request->get('plan_date'),
+                'type' => $request->get('type'),
+            ];
 
-            $datePlan = $filters['plan_date'];
-            $dateFromForCategory = date('Y-m-d', strtotime($datePlan . ' -12 months'));
-            $dateFrom = date('Y-m-d', strtotime($datePlan . ' -3 months'));
-
-            $autoPlannogrammaService = new AutoPlannogrammaService();
-
-            $monthCategoryShare = $autoPlannogrammaService->getMonthCategoryShare($dateFromForCategory);
-            $monthCategoryGoal = $autoPlannogrammaService->getMonthCategoryGoal($monthCategoryShare, $datePlan);
-            $monthSubcategoryShare = $autoPlannogrammaService->getMonthSubcategoryShare($dateFrom, $datePlan);
-            $monthSubcategoryGoal = $autoPlannogrammaService->getMonthSubcategoryGoal($monthSubcategoryShare, $monthCategoryGoal);
-            $monthSpeciesShare = $autoPlannogrammaService->getMonthSpeciesShare($dateFrom, $datePlan);
-            $monthSpeciesGoalDirtry = $autoPlannogrammaService->getMonthSpeciesGoalDirtry($monthSpeciesShare, $monthSubcategoryGoal);
-
-
-            $filtered = array_filter($monthSpeciesGoalDirtry, function ($row) use ($filters) {
-                foreach ($filters as $key => $value) {
-                    if ($value !== null && $value !== '' && isset($row[$key]) && stripos($row[$key], $value) === false) {
-                        return false;
-                    }
-                }
-                return true;
-            });
+            $service = new AutoPlannogrammaService();
 
             $dataProvider = new ArrayDataProvider([
-                'allModels' => array_values($filtered),
+                'allModels' => $service->calculateFullGoalChain($filters),
                 'pagination' => [
                     'pageSize' => 100
                 ],
             ]);
+
         }
 
-        return $this->render('test-sales', [
+        return $this->render('calculating', [
             'dataProvider' => $dataProvider,
             'filters' => $filters,
         ]);
     }
 
-    public function actionSales()
+    public function actionControl()
     {
         $filters = Yii::$app->request->get();
-
         $params = [];
+        $conditions = [];
 
         if (!empty($filters['product_name'])) {
-            $conditions[] = 'p1.name LIKE :product_name';
+            $conditions[] = 'p1n.name LIKE :product_name';
             $params[':product_name'] = '%' . $filters['product_name'] . '%';
         }
 
         if (!empty($filters['category'])) {
-            $conditions[] = 'p1.category = :category';
+            $conditions[] = 'p1n.category = :category';
             $params[':category'] = $filters['category'];
         }
 
         if (!empty($filters['subcategory'])) {
-            $conditions[] = 'p1.subcategory = :subcategory';
+            $conditions[] = 'p1n.subcategory = :subcategory';
             $params[':subcategory'] = $filters['subcategory'];
         }
 
@@ -155,33 +143,51 @@ class AutoPlanogrammaController extends BaseController
         }
 
         if (!empty($filters['plan_date'])) {
-            // Преобразуем дату в формат Y-m-d
             $date = \DateTime::createFromFormat('d-m-Y', $filters['plan_date']);
             if ($date) {
                 $startDate = $date->format('Y-m-d');
                 $endDate = $date->modify('+1 month')->format('Y-m-d');
 
-                $conditions[] = 'DATE(s.date) >= :start_date';
-                $conditions[] = 'DATE(s.date) < :end_date';
+                if ($filters['type'] === 'writeOffs') {
+                    $conditions[] = 'DATE(w.date) >= :start_date';
+                    $conditions[] = 'DATE(w.date) < :end_date';
+                } else {
+                    $conditions[] = 'DATE(s.date) >= :start_date';
+                    $conditions[] = 'DATE(s.date) < :end_date';
+                }
 
                 $params[':start_date'] = $startDate;
                 $params[':end_date'] = $endDate;
             }
         }
 
-        if (!empty($filters['type'])) {
-            $conditions[] = 's.type = :type';
-            $params[':type'] = $filters['type'];
-        }
-
-        $where = implode(' AND ', $conditions);
-
-        $query = Yii::$app->db->createCommand("
+        $where = !empty($conditions) ? 'WHERE ' . implode(' AND ', $conditions) : '';
+        if (isset($filters['type']) && $filters['type'] == 'writeOffs') {
+            $query = Yii::$app->db->createCommand("
+            SELECT 
+                c.name AS city_name,
+                p1n.category,
+                p1n.subcategory,
+                item ->> 'product_id' AS product_id,
+                p1n.name AS product_name,
+                SUM(CAST(item ->> 'quantity' AS NUMERIC)) AS count,
+                SUM(CAST(item ->> 'summ' AS NUMERIC)) AS sum
+            FROM write_offs w
+            JOIN export_import_table ex ON ex.export_val = w.store_id
+            JOIN city_store c ON c.id = ex.entity_id
+            JOIN LATERAL jsonb_array_elements(w.items::jsonb) AS item ON true
+            LEFT JOIN products_1c_nomenclature p1n ON p1n.id = item ->> 'product_id'
+            $where
+            GROUP BY w.store_id, c.name, p1n.category, p1n.subcategory, product_id, p1n.name
+            ORDER BY w.store_id, p1n.category, p1n.subcategory, p1n.name
+        ")->bindValues($params)->queryAll();
+        } else {
+            $query = Yii::$app->db->createCommand("
     SELECT 
         c.name AS city_name,
-        p1.name AS product_name,
-        p1.category, 
-        p1.subcategory,
+        p1n.name AS product_name,
+        p1n.category, 
+        p1n.subcategory,
         SUM(CASE 
                 WHEN s.operation = 'Продажа' THEN sp.quantity 
                 WHEN s.operation = 'Возврат' THEN -sp.quantity 
@@ -194,29 +200,22 @@ class AutoPlanogrammaController extends BaseController
             END) AS sum
     FROM sales s
     LEFT JOIN sales_products sp ON sp.check_id = s.id
-    LEFT JOIN products_1c_nomenclature p1 ON p1.id = sp.product_id
+    LEFT JOIN products_1c_nomenclature p1n ON p1n.id = sp.product_id
     LEFT JOIN export_import_table ex ON ex.export_val = store_id_1c
     LEFT JOIN city_store c ON c.id = entity_id
-    WHERE $where
-    GROUP BY p1.name, p1.category, p1.subcategory, c.name, c.id
-    ORDER BY c.id DESC, category, subcategory, p1.name
+    $where
+    GROUP BY p1n.name, p1n.category, p1n.subcategory, c.name, c.id
+    ORDER BY c.id DESC, category, subcategory, p1n.name
 ")->bindValues($params)->queryAll();
-
+        }
         $dataProvider = new ArrayDataProvider([
             'allModels' => $query,
             'pagination' => ['pageSize' => 20],
         ]);
 
-        return $this->render('sales', [
+        return $this->render('control', [
             'dataProvider' => $dataProvider,
             'filters' => $filters,
         ]);
-
     }
-
-    public function actionTestWriteOffs()
-    {
-        return $this->render('test-write-offs');
-    }
-
 }
\ No newline at end of file
index 6fd32f13403326546dcd0572666dedbd87e2f446..d40263d89223731e36c2b8a933ffd7c78707813d 100644 (file)
@@ -4,6 +4,7 @@ namespace yii_app\services;
 
 use yii\db\Expression;
 use yii\db\Query;
+use yii\helpers\ArrayHelper;
 use yii_app\records\CityStore;
 use yii_app\records\SalesWriteOffsPlan;
 
@@ -14,72 +15,101 @@ class AutoPlannogrammaService
         return CityStore::findAll(['visible' => CityStore::IS_VISIBLE]);
     }
 
-    private function getStoreSalesTotals(array $storeIds, string $dateFrom, $productFilter = null): array
+    public function getStoreTotals(array $storeIds, string $dateFrom, ?array $productFilter = null, string $type = 'sales', ?string $dateTo = null): array
     {
         $query = (new Query())
             ->select([
                 'store_id' => 'ex.entity_id',
-                'total_sum' => new Expression("
-                SUM(CASE 
-                        WHEN s.operation = 'Продажа' THEN sp.summ
-                        WHEN s.operation = 'Возврат' THEN -sp.summ
-                        ELSE 0
-                    END)
-            ")
+                'total_sum' => new Expression(
+                    $type === 'writeOffs'
+                        ? 'SUM(CAST(item ->> \'summ\' AS NUMERIC))'
+                        : 'SUM(sp.summ)'
+                )
             ])
-            ->from(['s' => 'sales'])
-            ->leftJoin('sales_products sp', 'sp.check_id = s.id')
-            ->leftJoin('export_import_table ex', 'ex.export_val = s.store_id_1c')
-            ->where(['>=', 's.date', $dateFrom])
-            ->andWhere(['ex.entity_id' => $storeIds])
-            ->groupBy('ex.entity_id');
+            ->from(['w' => $type === 'writeOffs' ? 'write_offs' : 'sales']);
+
+        if ($type === 'writeOffs') {
+            $query->leftJoin('export_import_table ex', 'ex.export_val = w.store_id')
+                ->join('JOIN', new Expression('LATERAL jsonb_array_elements(w.items::jsonb) AS item'), 'true');
+        } else {
+            $query->leftJoin('sales_products sp', 'sp.check_id = w.id')
+                ->leftJoin('export_import_table ex', 'ex.export_val = w.store_id_1c');
+        }
+
+        $query->where(['>=', 'w.date', $dateFrom])
+            ->andWhere(['ex.entity_id' => $storeIds]);
 
         if ($productFilter !== null) {
-            $query->andWhere(['sp.product_id' => $productFilter]);
+            if ($type === 'writeOffs') {
+                $query->andWhere(['item ->> \'product_id\'' => $productFilter]);
+            } else {
+                $query->andWhere(['sp.product_id' => $productFilter]);
+            }
         }
 
-        $rows = $query->all();
-        $totals = [];
-
-        foreach ($rows as $row) {
-            $totals[(int)$row['store_id']] = (float)$row['total_sum'];
+        if ($dateTo !== null) {
+            $query->andWhere(['<=', 'w.date', $dateTo]);
         }
 
-        return $totals;
+        $query->groupBy('ex.entity_id');
+
+        $rows = $query->all();
+
+        return ArrayHelper::map($rows, 'store_id', 'total_sum');
     }
 
-    public function getMonthCategoryShare($dateFrom, $storeFilter = null, $productFilter = null): array
+    public function getMonthCategoryShareOrWriteOff(string $dateFrom, ?array $filters = null, ?array $productFilter = null, string $type = 'sales'): array
     {
         $stores = $this->getVisibleStores();
         $storeIds = array_map(fn($s) => $s->id, $stores);
-        if ($storeFilter !== null) {
-            $storeIds = array_intersect($storeIds, (array)$storeFilter);
+
+        if (!empty($filters) && array_key_exists('store_id', $filters) && $filters['store_id'] !== '') {
+            $storeIds = array_intersect($storeIds, [(int)$filters['store_id']]);
         }
 
-        $totals = $this->getStoreSalesTotals($storeIds, $dateFrom, $productFilter);
+        $totals = $this->getStoreTotals($storeIds, $dateFrom, null, $type);
+
         if (empty($totals)) return [];
 
         $query = (new Query())
             ->select([
                 'store_id' => 'ex.entity_id',
                 'category' => 'p1c.category',
-                'total_sum' => new Expression('SUM(sp.summ)')
+                'total_sum' => new Expression(
+                    $type === 'writeOffs' ? 'SUM(CAST(item ->> \'summ\' AS NUMERIC))' : 'SUM(sp.summ)'
+                ),
             ])
-            ->from(['s' => 'sales'])
-            ->leftJoin('sales_products sp', 'sp.check_id = s.id')
-            ->leftJoin('products_1c_nomenclature p1c', 'p1c.id = sp.product_id')
-            ->leftJoin('export_import_table ex', 'ex.export_val = s.store_id_1c')
-            ->where(['>=', 's.date', $dateFrom])
-            ->andWhere(['ex.entity_id' => $storeIds])
-            ->andWhere(['<>', 'p1c.category', ''])
-            ->groupBy(['ex.entity_id', 'p1c.category']);
-
-        if ($productFilter !== null) {
-            $query->andWhere(['sp.product_id' => $productFilter]);
+            ->from($type === 'writeOffs' ? ['w' => 'write_offs'] : ['s' => 'sales']);
+
+        if ($type === 'writeOffs') {
+            $query->join('LEFT JOIN', 'export_import_table ex', 'ex.export_val = w.store_id')
+                ->join('JOIN', new Expression('LATERAL jsonb_array_elements(w.items::jsonb) AS item'), 'true')
+                ->leftJoin('products_1c_nomenclature p1c', 'p1c.id = item ->> \'product_id\'')
+                ->where(['>=', 'w.date', $dateFrom])
+                ->andWhere(['ex.entity_id' => $storeIds])
+                ->andWhere(['<>', 'p1c.category', ''])
+                ->groupBy(['ex.entity_id', 'p1c.category']);
+
+            if ($productFilter !== null) {
+                $query->andWhere(['item ->> \'product_id\'' => $productFilter]);
+            }
+        } else {
+            $query->leftJoin('sales_products sp', 'sp.check_id = s.id')
+                ->leftJoin('products_1c_nomenclature p1c', 'p1c.id = sp.product_id')
+                ->leftJoin('export_import_table ex', 'ex.export_val = s.store_id_1c')
+                ->where(['>=', 's.date', $dateFrom])
+                ->andWhere(['ex.entity_id' => $storeIds])
+                ->andWhere(['<>', 'p1c.category', ''])
+                ->groupBy(['ex.entity_id', 'p1c.category']);
+
+            if ($productFilter !== null) {
+                $query->andWhere(['sp.product_id' => $productFilter]);
+            }
         }
 
         $rows = $query->all();
         $result = [];
+
         foreach ($rows as $row) {
             $storeId = $row['store_id'];
             $total = $totals[$storeId] ?? 1;
@@ -93,7 +123,7 @@ class AutoPlannogrammaService
         return $result;
     }
 
-    public function getMonthCategoryGoal(array $categoryShare, $datePlan): array
+    public function getMonthCategoryGoal(array $categoryShare, $datePlan, $filters): array
     {
         $timestamp = strtotime($datePlan);
         $year = date('Y', $timestamp);
@@ -114,7 +144,7 @@ class AutoPlannogrammaService
                 $result[] = [
                     'category' => $item['category'],
                     'store_id' => $storeId,
-                    'goal' => round($item['share_of_total'] * $plan['total_sales_plan'], 2),
+                    'goal' => round($item['share_of_total'] * ($filters['type'] == 'writeOffs' ? $plan['write_offs_plan'] : $plan['total_sales_plan']), 2),
                 ];
             }
         }
@@ -122,56 +152,69 @@ class AutoPlannogrammaService
         return $result;
     }
 
-    public function getMonthSubcategoryShare($dateFrom, $datePlan, $storeFilter = null, $productFilter = null): array
+    public function getMonthSubcategoryShareOrWriteOff(string $dateFrom, ?array $filters = null, ?array $productFilter = null, string $type = 'sales'): array
     {
-        $month = (int)date('m', strtotime($datePlan));
-
         $stores = $this->getVisibleStores();
         $storeIds = array_map(fn($s) => $s->id, $stores);
-        if ($storeFilter !== null) {
-            $storeIds = array_intersect($storeIds, (array)$storeFilter);
+
+        if (!empty($filters) && array_key_exists('store_id', $filters) && $filters['store_id'] !== '') {
+            $storeIds = array_intersect($storeIds, [(int)$filters['store_id']]);
         }
 
+        $totals = $this->getStoreTotals($storeIds, $dateFrom, $productFilter, $type);
+        if (empty($totals)) return [];
+
         $query = (new Query())
             ->select([
                 'store_id' => 'ex.entity_id',
-                'category' => 'p1c.category',
                 'subcategory' => 'p1c.subcategory',
-                'month' => new Expression('EXTRACT(MONTH FROM s.date)'),
-                'total_sum' => new Expression('SUM(sp.summ)')
-            ])
-            ->from(['sp' => 'sales_products'])
-            ->leftJoin('products_1c_nomenclature p1c', 'p1c.id = sp.product_id')
-            ->leftJoin('sales s', 's.id = sp.check_id')
-            ->leftJoin('export_import_table ex', 'ex.export_val = s.store_id_1c')
-            ->where(['>=', 's.date', $dateFrom])
-            ->andWhere(['ex.entity_id' => $storeIds])
-            ->groupBy(['ex.entity_id', 'p1c.category', 'p1c.subcategory', new Expression('EXTRACT(MONTH FROM s.date)')]);
-
-        if ($productFilter !== null) {
-            $query->andWhere(['sp.product_id' => $productFilter]);
+                'category' => 'p1c.category',
+                'total_sum' => new Expression(
+                    $type === 'writeOffs' ? 'SUM(CAST(item ->> \'summ\' AS NUMERIC))' : 'SUM(sp.summ)'
+                ),
+            ]);
+
+        if ($type === 'writeOffs') {
+            $query->from(['w' => 'write_offs'])
+                ->join('LEFT JOIN', 'export_import_table ex', 'ex.export_val = w.store_id')
+                ->join('JOIN', new Expression('LATERAL jsonb_array_elements(w.items::jsonb) AS item'), 'true')
+                ->leftJoin('products_1c_nomenclature p1c', 'p1c.id = item ->> \'product_id\'')
+                ->where(['>=', 'w.date', $dateFrom])
+                ->andWhere(['ex.entity_id' => $storeIds])
+                ->andWhere(['<>', 'p1c.subcategory', ''])
+                ->groupBy(['ex.entity_id', 'p1c.subcategory', 'p1c.category']);
+
+            if ($productFilter !== null) {
+                $query->andWhere(['item ->> \'product_id\'' => $productFilter]);
+            }
+        } else {
+            $query->from(['s' => 'sales'])
+                ->leftJoin('sales_products sp', 'sp.check_id = s.id')
+                ->leftJoin('products_1c_nomenclature p1c', 'p1c.id = sp.product_id')
+                ->leftJoin('export_import_table ex', 'ex.export_val = s.store_id_1c')
+                ->where(['>=', 's.date', $dateFrom])
+                ->andWhere(['ex.entity_id' => $storeIds])
+                ->andWhere(['<>', 'p1c.subcategory', ''])
+                ->groupBy(['ex.entity_id', 'p1c.subcategory', 'p1c.category']);
+
+            if ($productFilter !== null) {
+                $query->andWhere(['sp.product_id' => $productFilter]);
+            }
         }
 
         $rows = $query->all();
-        $grouped = [];
+        $result = [];
 
         foreach ($rows as $row) {
-            if ((int)$row['month'] !== $month) continue;
-            $groupKey = "{$row['store_id']}_{$row['category']}_{$row['month']}";
-            $grouped[$groupKey][] = $row;
-        }
-
-        $result = [];
-        foreach ($grouped as $group) {
-            $total = array_sum(array_column($group, 'total_sum')) ?: 1;
-            foreach ($group as $item) {
-                $result[] = [
-                    'store_id' => $item['store_id'],
-                    'category' => $item['category'],
-                    'subcategory' => $item['subcategory'],
-                    'percent_of_month' => round($item['total_sum'] / $total, 4),
-                ];
-            }
+            $storeId = $row['store_id'];
+            $total = $totals[$storeId] ?? 1;
+            $result[] = [
+                'store_id' => $storeId,
+                'category' => $row['category'],
+                'subcategory' => $row['subcategory'],
+                'total_sum' => $row['total_sum'],
+                'percent_of_month' => round($row['total_sum'] / $total, 4),
+            ];
         }
 
         return $result;
@@ -200,65 +243,77 @@ class AutoPlannogrammaService
         return $result;
     }
 
-    public function getMonthSpeciesShare($dateFrom, $datePlan, $storeFilter = null, $productFilter = null): array
+    public function getMonthSpeciesShareOrWriteOff(string $dateFrom, ?array $filters = null, ?array $productFilter = null, string $type = 'sales'): array
     {
-        $month = (int)date('m', strtotime($datePlan));
-
         $stores = $this->getVisibleStores();
         $storeIds = array_map(fn($s) => $s->id, $stores);
-        if ($storeFilter !== null) {
-            $storeIds = array_intersect($storeIds, (array)$storeFilter);
+
+        if (!empty($filters) && array_key_exists('store_id', $filters) && $filters['store_id'] !== '') {
+            $storeIds = array_intersect($storeIds, [(int)$filters['store_id']]);
         }
 
+        $totals = $this->getStoreTotals($storeIds, $dateFrom, $productFilter, $type);
+        if (empty($totals)) return [];
+
         $query = (new Query())
             ->select([
                 'store_id' => 'ex.entity_id',
-                'category' => 'p1.category',
-                'subcategory' => 'p1.subcategory',
-                'species' => 'p1.name',
-                'month' => new Expression('EXTRACT(MONTH FROM s.date)'),
-                'total_sum' => new Expression('SUM(sp.summ)')
-            ])
-            ->from(['sp' => 'sales_products'])
-            ->leftJoin('products_1c_nomenclature p1', 'p1.id = sp.product_id')
-            ->leftJoin('sales s', 's.id = sp.check_id')
-            ->leftJoin('export_import_table ex', 'ex.export_val = s.store_id_1c')
-            ->where(['>=', 's.date', $dateFrom])
-            ->andWhere(['ex.entity_id' => $storeIds])
-            ->groupBy(['ex.entity_id', 'p1.category', 'p1.subcategory', 'p1.name', new Expression('EXTRACT(MONTH FROM s.date)')]);
-
-        if ($productFilter !== null) {
-            $query->andWhere(['sp.product_id' => $productFilter]);
+                'species' => 'p1c.species',
+                'category' => 'p1c.category',
+                'subcategory' => 'p1c.subcategory',
+                'total_sum' => new Expression(
+                    $type === 'writeOffs' ? 'SUM(CAST(item ->> \'summ\' AS NUMERIC))' : 'SUM(sp.summ)'
+                ),
+            ]);
+
+        if ($type === 'writeOffs') {
+            $query->from(['w' => 'write_offs'])
+                ->join('LEFT JOIN', 'export_import_table ex', 'ex.export_val = w.store_id')
+                ->join('JOIN', new Expression('LATERAL jsonb_array_elements(w.items::jsonb) AS item'), 'true')
+                ->leftJoin('products_1c_nomenclature p1c', 'p1c.id = item ->> \'product_id\'')
+                ->where(['>=', 'w.date', $dateFrom])
+                ->andWhere(['ex.entity_id' => $storeIds])
+                ->andWhere(['<>', 'p1c.species', ''])
+                ->groupBy(['ex.entity_id', 'p1c.species', 'p1c.category', 'p1c.subcategory']);
+
+            if ($productFilter !== null) {
+                $query->andWhere(['item ->> \'product_id\'' => $productFilter]);
+            }
+        } else {
+            $query->from(['s' => 'sales'])
+                ->leftJoin('sales_products sp', 'sp.check_id = s.id')
+                ->leftJoin('products_1c_nomenclature p1c', 'p1c.id = sp.product_id')
+                ->leftJoin('export_import_table ex', 'ex.export_val = s.store_id_1c')
+                ->where(['>=', 's.date', $dateFrom])
+                ->andWhere(['ex.entity_id' => $storeIds])
+                ->andWhere(['<>', 'p1c.species', ''])
+                ->groupBy(['ex.entity_id', 'p1c.species', 'p1c.category', 'p1c.subcategory']);
+
+            if ($productFilter !== null) {
+                $query->andWhere(['sp.product_id' => $productFilter]);
+            }
         }
 
         $rows = $query->all();
-        $grouped = [];
+        $result = [];
 
         foreach ($rows as $row) {
-            if ((int)$row['month'] !== $month) continue;
-            $key = "{$row['store_id']}_{$row['category']}_{$row['subcategory']}";
-            $grouped[$key][] = $row;
-        }
-
-        $result = [];
-        foreach ($grouped as $group) {
-            $total = array_sum(array_column($group, 'total_sum')) ?: 1;
-            foreach ($group as $item) {
-                $result[] = [
-                    'store_id' => $item['store_id'],
-                    'category' => $item['category'],
-                    'subcategory' => $item['subcategory'],
-                    'species' => $item['species'],
-                    'percent_of_month' => round($item['total_sum'] / $total, 6),
-                    'goal' => null,
-                ];
-            }
+            $storeId = $row['store_id'];
+            $total = $totals[$storeId] ?? 1;
+            $result[] = [
+                'store_id' => $storeId,
+                'category' => $row['category'],
+                'subcategory' => $row['subcategory'],
+                'species' => $row['species'],
+                'total_sum' => $row['total_sum'],
+                'percent_of_month' => round($row['total_sum'] / $total, 4),
+            ];
         }
 
         return $result;
     }
 
-    public function getMonthSpeciesGoalDirtry(array $speciesShare, array $subcategoryGoals): array
+    public function getMonthSpeciesGoalDirty(array $speciesShare, array $subcategoryGoals): array
     {
         $indexedGoals = [];
         foreach ($subcategoryGoals as $goal) {
@@ -281,4 +336,45 @@ class AutoPlannogrammaService
 
         return $result;
     }
+
+    public function calculateFullGoalChain(array $filters): array
+    {
+        $datePlan = $filters['plan_date'];
+        $dateFromForCategory = (new \DateTime($datePlan))->modify('-12 months')->format('Y-m-d');
+        $dateFrom = (new \DateTime($datePlan))->modify('-3 months')->format('Y-m-d');
+
+        $monthCategoryShare = $this->getMonthCategoryShareOrWriteOff($dateFromForCategory, $filters);
+        $monthCategoryGoal = $this->getMonthCategoryGoal($monthCategoryShare, $datePlan, $filters);
+
+        $monthSubcategoryShare = $this->getMonthSubcategoryShareOrWriteOff($dateFrom, $filters);
+        $monthSubcategoryGoal = $this->getMonthSubcategoryGoal($monthSubcategoryShare, $monthCategoryGoal);
+
+        $monthSpeciesShare = $this->getMonthSpeciesShareOrWriteOff($dateFrom, $filters);
+        $monthSpeciesGoal = $this->getMonthSpeciesGoalDirty($monthSpeciesShare, $monthSubcategoryGoal);
+
+        $filtered = array_filter($monthSpeciesGoal, function ($row) use ($filters) {
+            foreach ($filters as $key => $value) {
+                if ($value === null || $value === '') {
+                    continue;
+                }
+
+                if (!array_key_exists($key, $row)) {
+                    continue;
+                }
+
+                if (is_numeric($row[$key]) && is_numeric($value)) {
+                    if ((float)$row[$key] !== (float)$value) {
+                        return false;
+                    }
+                } else {
+                    if (stripos((string)$row[$key], (string)$value) === false) {
+                        return false;
+                    }
+                }
+            }
+            return true;
+        });
+
+        return array_values($filtered);
+    }
 }
diff --git a/erp24/views/auto-planogramma/calculating.php b/erp24/views/auto-planogramma/calculating.php
new file mode 100644 (file)
index 0000000..4139963
--- /dev/null
@@ -0,0 +1,108 @@
+<?php
+
+use kartik\date\DatePicker;
+use kartik\select2\Select2;
+use yii\helpers\ArrayHelper;
+use yii\helpers\Html;
+use yii\widgets\ActiveForm;
+use yii\grid\GridView;
+use yii_app\records\CityStore;
+use yii_app\records\Products1cNomenclature;
+
+/** @var array $filters */
+?>
+
+<div class="filter-form" style="margin-bottom: 20px;">
+    <?php $form = ActiveForm::begin(['method' => 'get']); ?>
+    <div class="row p-3">
+        <div class="col-md">
+            <?= $form->field(new \yii\base\DynamicModel(['category' => $filters['category'] ?? '']), 'category')->widget(Select2::class, [
+                'data' => ArrayHelper::map(
+                    Products1cNomenclature::find()->select('category')->distinct()->asArray()->all(),
+                    'category',
+                    'category'
+                ),
+                'options' => ['placeholder' => 'Категория', 'name' => 'category'],
+                'pluginOptions' => ['allowClear' => true],
+            ])->label('Категория') ?>
+        </div>
+        <div class="col-md">
+            <?= $form->field(new \yii\base\DynamicModel(['subcategory' => $filters['subcategory'] ?? '']), 'subcategory')->widget(Select2::class, [
+                'data' => ArrayHelper::map(
+                    Products1cNomenclature::find()->select('subcategory')->distinct()->asArray()->all(),
+                    'subcategory',
+                    'subcategory'
+                ),
+                'options' => ['placeholder' => 'Подкатегория', 'name' => 'subcategory'],
+                'pluginOptions' => ['allowClear' => true],
+            ])->label('Подкатегория') ?>
+        </div>
+        <div class="col-md">
+            <?= $form->field(new \yii\base\DynamicModel(['product_name' => $filters['product_name'] ?? '']), 'product_name')->widget(Select2::class, [
+                'data' => ArrayHelper::map(
+                    Products1cNomenclature::find()->select('name')->distinct()->asArray()->all(),
+                    'name',
+                    'name'
+                ),
+                'options' => ['placeholder' => 'Название товара', 'name' => 'product_name'],
+                'pluginOptions' => ['allowClear' => true],
+            ])->label('Товар') ?>
+        </div>
+        <div class="col-md">
+            <?= $form->field(new \yii\base\DynamicModel(['store_id' => $filters['store_id'] ?? '']), 'store_id')->widget(Select2::class, [
+                'data' => ArrayHelper::map(
+                    CityStore::findAll(['visible' => CityStore::IS_VISIBLE]),
+                    'id',
+                    'name'
+                ),
+                'options' => ['placeholder' => 'Магазин', 'name' => 'store_id'],
+                'pluginOptions' => ['allowClear' => true],
+            ])->label('Магазин') ?>
+        </div>
+        <div class="col-md">
+            <?= $form->field(new \yii\base\DynamicModel(['plan_date' => $filters['plan_date'] ?? '']), 'plan_date')->widget(DatePicker::class, [
+                'pluginOptions' => [
+                    'autoclose' => true,
+                    'format' => 'dd-mm-yyyy',
+                ],
+                'options' => [
+                    'class' => 'form-control',
+                    'placeholder' => 'Плановая дата',
+                    'name' => 'plan_date',
+                ],
+            ])->label('Плановая дата') ?>
+        </div>
+        <div class="col-md">
+            <?= $form->field(new \yii\base\DynamicModel(['type' => $filters['type'] ?? '']), 'type')->widget(Select2::class, [
+                'data' => [
+                    'writeOffs' => 'Списания',
+                    'sales' => 'Продажи'
+                ],
+                'options' => ['placeholder' => 'Тип', 'name' => 'type'],
+                'pluginOptions' => ['allowClear' => true],
+            ])->label('По дефолту продажи!') ?>
+        </div>
+        <div class="col-md">
+            <?= Html::submitButton('Фильтровать', ['class' => 'btn btn-primary']) ?>
+        </div>
+        <div class="col-md">
+            <?= Html::a('Сбросить', ['auto-planogramma/test-sales'], ['class' => 'btn btn-default']) ?>
+        </div>
+    </div>
+
+    <?php ActiveForm::end(); ?>
+</div>
+
+<?= GridView::widget([
+    'dataProvider' => $dataProvider,
+    'columns' => [
+        'category',
+        'subcategory',
+        'product_name',
+        'store_id',
+        [
+            'attribute' => 'goal',
+            'label' => 'План закупок (RUB)',
+        ],
+    ],
+]); ?>
diff --git a/erp24/views/auto-planogramma/control.php b/erp24/views/auto-planogramma/control.php
new file mode 100644 (file)
index 0000000..67e4647
--- /dev/null
@@ -0,0 +1,118 @@
+<div class="filter-form" style="margin-bottom: 20px;">
+    <?php use kartik\date\DatePicker;
+    use kartik\select2\Select2;
+    use yii\grid\GridView;
+    use yii\helpers\ArrayHelper;
+    use yii\helpers\Html;
+    use yii\widgets\ActiveForm;
+    use yii_app\records\CityStore;
+    use yii_app\records\Products1cNomenclature;
+
+    $form = ActiveForm::begin(['method' => 'get']); ?>
+    <div class="row">
+        <div class="col-md">
+            <?= Select2::widget([
+                'name' => 'category',
+                'value' => $filters['category'] ?? '',
+                'data' => ArrayHelper::map(
+                    Products1cNomenclature::find()->select('category')->distinct()->orderBy('category')->asArray()->all(),
+                    'category',
+                    'category'
+                ),
+                'options' => ['placeholder' => 'Категория'],
+                'pluginOptions' => ['allowClear' => true],
+            ]) ?>
+        </div>
+
+        <div class="col-md">
+            <?= Select2::widget([
+                'name' => 'subcategory',
+                'value' => $filters['subcategory'] ?? '',
+                'data' => ArrayHelper::map(
+                    Products1cNomenclature::find()->select('subcategory')->distinct()->orderBy('subcategory')->asArray()->all(),
+                    'subcategory',
+                    'subcategory'
+                ),
+                'options' => ['placeholder' => 'Подкатегория'],
+                'pluginOptions' => ['allowClear' => true],
+            ]) ?>
+        </div>
+        <div class="col-md">
+            <?= Select2::widget([
+                'name' => 'product_name',
+                'value' => $filters['product_name'] ?? '',
+                'data' => ArrayHelper::map(
+                    Products1cNomenclature::find()->select('name')->distinct()->orderBy('name')->asArray()->all(),
+                    'name',
+                    'name'
+                ),
+                'options' => ['placeholder' => 'Товар'],
+                'pluginOptions' => ['allowClear' => true],
+            ]) ?>
+        </div>
+
+
+        <div class="col-md">
+            <?= Select2::widget([
+                'name' => 'city_name',
+                'value' => $filters['city_name'] ?? '',
+                'data' => ArrayHelper::map(
+                    CityStore::find()->select(['name'])->distinct()->orderBy('name')->asArray()->all(),
+                    'name',
+                    'name'
+                ),
+                'options' => ['placeholder' => 'Магазин'],
+                'pluginOptions' => ['allowClear' => true],
+            ]) ?>
+        </div>
+
+        <div class="col-md">
+            <?= DatePicker::widget([
+                'name' => 'plan_date',
+                'value' => $filters['plan_date'] ?? '',
+                'pluginOptions' => [
+                    'autoclose' => true,
+                    'format' => 'dd-mm-yyyy',
+                ],
+                'options' => [
+                    'class' => 'form-control',
+                    'placeholder' => 'Плановая дата',
+                ],
+            ]) ?>
+        </div>
+
+        <div class="col-md">
+            <?= Select2::widget([
+                'name' => 'type',
+                'value' => $filters['type'] ?? '',
+                'data' => [
+                    'writeOffs' => 'Списания',
+                    'sales' => 'Продажи'
+                ],
+                'options' => ['placeholder' => 'Тип'],
+                'pluginOptions' => ['allowClear' => true],
+            ]) ?>
+        </div>
+
+
+        <div class="col-md">
+            <?= Html::submitButton('Фильтровать', ['class' => 'btn btn-primary']) ?>
+        </div>
+        <div class="col-md">
+            <?= Html::a('Сбросить', ['auto-planogramma/sales'], ['class' => 'btn btn-default']) ?>
+        </div>
+    </div>
+    <?php ActiveForm::end(); ?>
+</div>
+
+<?= GridView::widget([
+    'dataProvider' => $dataProvider,
+    'columns' => [
+        'category',
+        'subcategory',
+        ['attribute' => 'product_name', 'label' => 'Товар'],
+        ['attribute' => 'city_name', 'label' => 'Магазин'],
+        ['attribute' => 'count', 'label' => 'Кол-во'],
+        ['attribute' => 'sum', 'label' => 'Сумма'],
+    ],
+]) ?>
diff --git a/erp24/views/auto-planogramma/sales.php b/erp24/views/auto-planogramma/sales.php
deleted file mode 100644 (file)
index 67e4647..0000000
+++ /dev/null
@@ -1,118 +0,0 @@
-<div class="filter-form" style="margin-bottom: 20px;">
-    <?php use kartik\date\DatePicker;
-    use kartik\select2\Select2;
-    use yii\grid\GridView;
-    use yii\helpers\ArrayHelper;
-    use yii\helpers\Html;
-    use yii\widgets\ActiveForm;
-    use yii_app\records\CityStore;
-    use yii_app\records\Products1cNomenclature;
-
-    $form = ActiveForm::begin(['method' => 'get']); ?>
-    <div class="row">
-        <div class="col-md">
-            <?= Select2::widget([
-                'name' => 'category',
-                'value' => $filters['category'] ?? '',
-                'data' => ArrayHelper::map(
-                    Products1cNomenclature::find()->select('category')->distinct()->orderBy('category')->asArray()->all(),
-                    'category',
-                    'category'
-                ),
-                'options' => ['placeholder' => 'Категория'],
-                'pluginOptions' => ['allowClear' => true],
-            ]) ?>
-        </div>
-
-        <div class="col-md">
-            <?= Select2::widget([
-                'name' => 'subcategory',
-                'value' => $filters['subcategory'] ?? '',
-                'data' => ArrayHelper::map(
-                    Products1cNomenclature::find()->select('subcategory')->distinct()->orderBy('subcategory')->asArray()->all(),
-                    'subcategory',
-                    'subcategory'
-                ),
-                'options' => ['placeholder' => 'Подкатегория'],
-                'pluginOptions' => ['allowClear' => true],
-            ]) ?>
-        </div>
-        <div class="col-md">
-            <?= Select2::widget([
-                'name' => 'product_name',
-                'value' => $filters['product_name'] ?? '',
-                'data' => ArrayHelper::map(
-                    Products1cNomenclature::find()->select('name')->distinct()->orderBy('name')->asArray()->all(),
-                    'name',
-                    'name'
-                ),
-                'options' => ['placeholder' => 'Товар'],
-                'pluginOptions' => ['allowClear' => true],
-            ]) ?>
-        </div>
-
-
-        <div class="col-md">
-            <?= Select2::widget([
-                'name' => 'city_name',
-                'value' => $filters['city_name'] ?? '',
-                'data' => ArrayHelper::map(
-                    CityStore::find()->select(['name'])->distinct()->orderBy('name')->asArray()->all(),
-                    'name',
-                    'name'
-                ),
-                'options' => ['placeholder' => 'Магазин'],
-                'pluginOptions' => ['allowClear' => true],
-            ]) ?>
-        </div>
-
-        <div class="col-md">
-            <?= DatePicker::widget([
-                'name' => 'plan_date',
-                'value' => $filters['plan_date'] ?? '',
-                'pluginOptions' => [
-                    'autoclose' => true,
-                    'format' => 'dd-mm-yyyy',
-                ],
-                'options' => [
-                    'class' => 'form-control',
-                    'placeholder' => 'Плановая дата',
-                ],
-            ]) ?>
-        </div>
-
-        <div class="col-md">
-            <?= Select2::widget([
-                'name' => 'type',
-                'value' => $filters['type'] ?? '',
-                'data' => [
-                    'writeOffs' => 'Списания',
-                    'sales' => 'Продажи'
-                ],
-                'options' => ['placeholder' => 'Тип'],
-                'pluginOptions' => ['allowClear' => true],
-            ]) ?>
-        </div>
-
-
-        <div class="col-md">
-            <?= Html::submitButton('Фильтровать', ['class' => 'btn btn-primary']) ?>
-        </div>
-        <div class="col-md">
-            <?= Html::a('Сбросить', ['auto-planogramma/sales'], ['class' => 'btn btn-default']) ?>
-        </div>
-    </div>
-    <?php ActiveForm::end(); ?>
-</div>
-
-<?= GridView::widget([
-    'dataProvider' => $dataProvider,
-    'columns' => [
-        'category',
-        'subcategory',
-        ['attribute' => 'product_name', 'label' => 'Товар'],
-        ['attribute' => 'city_name', 'label' => 'Магазин'],
-        ['attribute' => 'count', 'label' => 'Кол-во'],
-        ['attribute' => 'sum', 'label' => 'Сумма'],
-    ],
-]) ?>
diff --git a/erp24/views/auto-planogramma/test-sales.php b/erp24/views/auto-planogramma/test-sales.php
deleted file mode 100644 (file)
index 4139963..0000000
+++ /dev/null
@@ -1,108 +0,0 @@
-<?php
-
-use kartik\date\DatePicker;
-use kartik\select2\Select2;
-use yii\helpers\ArrayHelper;
-use yii\helpers\Html;
-use yii\widgets\ActiveForm;
-use yii\grid\GridView;
-use yii_app\records\CityStore;
-use yii_app\records\Products1cNomenclature;
-
-/** @var array $filters */
-?>
-
-<div class="filter-form" style="margin-bottom: 20px;">
-    <?php $form = ActiveForm::begin(['method' => 'get']); ?>
-    <div class="row p-3">
-        <div class="col-md">
-            <?= $form->field(new \yii\base\DynamicModel(['category' => $filters['category'] ?? '']), 'category')->widget(Select2::class, [
-                'data' => ArrayHelper::map(
-                    Products1cNomenclature::find()->select('category')->distinct()->asArray()->all(),
-                    'category',
-                    'category'
-                ),
-                'options' => ['placeholder' => 'Категория', 'name' => 'category'],
-                'pluginOptions' => ['allowClear' => true],
-            ])->label('Категория') ?>
-        </div>
-        <div class="col-md">
-            <?= $form->field(new \yii\base\DynamicModel(['subcategory' => $filters['subcategory'] ?? '']), 'subcategory')->widget(Select2::class, [
-                'data' => ArrayHelper::map(
-                    Products1cNomenclature::find()->select('subcategory')->distinct()->asArray()->all(),
-                    'subcategory',
-                    'subcategory'
-                ),
-                'options' => ['placeholder' => 'Подкатегория', 'name' => 'subcategory'],
-                'pluginOptions' => ['allowClear' => true],
-            ])->label('Подкатегория') ?>
-        </div>
-        <div class="col-md">
-            <?= $form->field(new \yii\base\DynamicModel(['product_name' => $filters['product_name'] ?? '']), 'product_name')->widget(Select2::class, [
-                'data' => ArrayHelper::map(
-                    Products1cNomenclature::find()->select('name')->distinct()->asArray()->all(),
-                    'name',
-                    'name'
-                ),
-                'options' => ['placeholder' => 'Название товара', 'name' => 'product_name'],
-                'pluginOptions' => ['allowClear' => true],
-            ])->label('Товар') ?>
-        </div>
-        <div class="col-md">
-            <?= $form->field(new \yii\base\DynamicModel(['store_id' => $filters['store_id'] ?? '']), 'store_id')->widget(Select2::class, [
-                'data' => ArrayHelper::map(
-                    CityStore::findAll(['visible' => CityStore::IS_VISIBLE]),
-                    'id',
-                    'name'
-                ),
-                'options' => ['placeholder' => 'Магазин', 'name' => 'store_id'],
-                'pluginOptions' => ['allowClear' => true],
-            ])->label('Магазин') ?>
-        </div>
-        <div class="col-md">
-            <?= $form->field(new \yii\base\DynamicModel(['plan_date' => $filters['plan_date'] ?? '']), 'plan_date')->widget(DatePicker::class, [
-                'pluginOptions' => [
-                    'autoclose' => true,
-                    'format' => 'dd-mm-yyyy',
-                ],
-                'options' => [
-                    'class' => 'form-control',
-                    'placeholder' => 'Плановая дата',
-                    'name' => 'plan_date',
-                ],
-            ])->label('Плановая дата') ?>
-        </div>
-        <div class="col-md">
-            <?= $form->field(new \yii\base\DynamicModel(['type' => $filters['type'] ?? '']), 'type')->widget(Select2::class, [
-                'data' => [
-                    'writeOffs' => 'Списания',
-                    'sales' => 'Продажи'
-                ],
-                'options' => ['placeholder' => 'Тип', 'name' => 'type'],
-                'pluginOptions' => ['allowClear' => true],
-            ])->label('По дефолту продажи!') ?>
-        </div>
-        <div class="col-md">
-            <?= Html::submitButton('Фильтровать', ['class' => 'btn btn-primary']) ?>
-        </div>
-        <div class="col-md">
-            <?= Html::a('Сбросить', ['auto-planogramma/test-sales'], ['class' => 'btn btn-default']) ?>
-        </div>
-    </div>
-
-    <?php ActiveForm::end(); ?>
-</div>
-
-<?= GridView::widget([
-    'dataProvider' => $dataProvider,
-    'columns' => [
-        'category',
-        'subcategory',
-        'product_name',
-        'store_id',
-        [
-            'attribute' => 'goal',
-            'label' => 'План закупок (RUB)',
-        ],
-    ],
-]); ?>