]> gitweb.erp-flowers.ru Git - erp24_rep/yii-erp24/.git/commitdiff
Merge branch 'refs/heads/develop' into feature_fomichev_erp-362_export_to_excel_autop...
authorfomichev <vladimir.fomichev@erp-flowers.ru>
Thu, 3 Jul 2025 06:04:17 +0000 (09:04 +0300)
committerfomichev <vladimir.fomichev@erp-flowers.ru>
Thu, 3 Jul 2025 06:04:17 +0000 (09:04 +0300)
# Conflicts:
# erp24/controllers/AutoPlannogrammaController.php

1  2 
erp24/controllers/AutoPlannogrammaController.php

index 23e7ddaa7ac376938b77fb7b390501107d84ff6a,2c9904b286bd0560b4f381226e9ab5f5c1216111..2df1b04c1e34a1c0bda3c6c514523dd5eab48fed
@@@ -2,9 -2,8 +2,10 @@@
  
  namespace app\controllers;
  
 +use PhpOffice\PhpSpreadsheet\Spreadsheet;
 +use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
  use Yii;
+ use yii\base\DynamicModel;
  use yii\data\ArrayDataProvider;
  use yii\db\Expression;
  use yii\db\Query;
@@@ -1395,476 -1389,459 +1391,931 @@@ class AutoPlannogrammaController extend
      }
  
  
 +    public function actionWeekSalesSpeciesExcel()
 +    {
 +        $request = Yii::$app->request;
 +
 +        $filters = [
 +            'category' => $request->get('category'),
 +            'subcategory' => $request->get('subcategory') ?? null,
 +            'species' => $request->get('species') ?? null,
 +            'store_id' => $request->get('store_id') ?? [],
 +            'year' => $request->get('year'),
 +            'month' => $request->get('month'),
 +            'type' => $request->get('type'),
 +        ];
 +
 +        $dataProvider = new ArrayDataProvider([
 +            'allModels' => [],
 +            'pagination' => ['pageSize' => 100],
 +        ]);
 +
 +
 +        return $this->render('week-sales-species-excel', [
 +            'dataProvider' => $dataProvider,
 +            'filters' => $filters,
 +        ]);
 +    }
 +
 +
 +    public function actionExportExcel()
 +    {
 +        $rows = [];
 +       /* $rows[] = [
 +        'week' => '1',
 +        'type_pm' => 'max',
 +        'shop' => 'Ванеева',
 +       'category' => 'Срезка',
 +        'subcategory' => 'Розы',
 +        'species' => 'Роза',
 +        'product_name' => 'Роза Эквадор',
 +        'quantity'  => '3',
 +        'value_type' => 'offline',
 +        'group_name' => 'Оффлайн',
 +        ];*/
 +        $request = Yii::$app->request;
 +        $this->layout = false;
 +        Yii::$app->response->format = Response::FORMAT_RAW;
 +
 +
 +        while (ob_get_level() > 0) {
 +            ob_end_clean();
 +        }
 +
 +        $filters = [
 +            'category' => $request->get('category'),
 +            'subcategory' => $request->get('subcategory') ?? null,
 +            'species' => $request->get('species') ?? null,
 +            'store_id' => $request->get('store_id') ?? [],
 +            'year' => $request->get('year'),
 +            'month' => $request->get('month'),
 +            'type' => $request->get('type'),
 +        ];
 +
 +
 +
 +        $bouquetSpeciesForecast = [];
 +
 +
 +            $filters['plan_date'] = $filters['year'] . '-' . str_pad($filters['month'], 2, '0', STR_PAD_LEFT) . '-01';
 +            // var_dump( $filters['plan_date']); die();
 +            $service = new AutoPlannogrammaService();
 +
 +            $monthCategoryShare = $service->getMonthCategoryShareOrWriteOff($filters['plan_date'], $filters);
 +            $monthCategoryGoal = $service->getMonthCategoryGoal($monthCategoryShare, $filters['plan_date']);
 +            $monthSubcategoryShare = $service->getMonthSubcategoryShareOrWriteOff($filters['plan_date'], $filters);
 +            $monthSubcategoryGoal = $service->getMonthSubcategoryGoal($monthSubcategoryShare, $monthCategoryGoal);
 +            $monthSpeciesShare = $service->getMonthSpeciesShareOrWriteOff($filters['plan_date'], $filters);
 +            $goals = $service->getMonthSpeciesGoalDirty($monthSpeciesShare, $monthSubcategoryGoal);
 +
 +            if ($filters['type'] == AutoPlannogrammaService::TYPE_WRITE_OFFS) {
 +                $monthCategoryWriteOffsShare = $service->getMonthCategoryShareOrWriteOff($filters['plan_date'], $filters, $filters['type']);
 +                $monthCategoryWriteOffsGoal = $service->getMonthCategoryGoal($monthCategoryWriteOffsShare, $filters['plan_date'], $filters['type']);
 +                $monthSubcategoryWriteOffsShare = $service->getMonthSubcategoryShareOrWriteOff($filters['plan_date'], $filters, $filters['type']);
 +                $monthSubcategoryWriteOffsGoals = $service->getMonthSubcategoryGoal($monthSubcategoryWriteOffsShare, $monthCategoryWriteOffsGoal, $filters['type']);
 +                $monthSpeciesWriteOffShare = $service->getMonthSpeciesShareOrWriteOff($filters['plan_date'], $filters, $filters['type']);
 +                $goals = $service->getMonthSpeciesGoalDirty($monthSpeciesWriteOffShare, $monthSubcategoryWriteOffsGoals, $filters['type'], $data);
 +            }
 +
 +
 +            $result = StorePlanService::calculateHistoricalShare(
 +                $filters['store_id'],
 +                $filters['month'],
 +                $filters['year'],
 +                $filters['category'],
 +                $filters['subcategory'],
 +                $filters['species']
 +            );
 +
 +            $noHistoryProductData = $service->calculateSpeciesForecastForProductsWithoutHistory($filters['plan_date'], $filters);
 +
 +            $productSalesShare = StorePlanService::calculateProductSalesShareProductsWithHistory(
 +                $filters['store_id'],
 +                $filters['month'],
 +                $result['with_history']
 +            );
 +
 +            $productSalesForecast = $service->calculateProductForecastInPiecesProductsWithHistory(
 +                $filters['store_id'],
 +                $filters['month'],
 +                $productSalesShare,
 +                $goals,
 +                $filters['subcategory'],
 +                $filters['category'],
 +                $filters['species']
 +            );
 +
 +            $matrixForecast = MatrixBouquetForecast::find()
 +                ->where(['year' => $filters['year'], 'month' => $filters['month']])
 +                ->asArray()
 +                ->all();
 +            $matrixGroups = array_unique(ArrayHelper::getColumn($matrixForecast, 'group'));
 +            $bouquetForecast = StorePlanService::getBouquetSpiecesMonthGoalFromForecast($filters['month'], $filters['year'], $filters['store_id'], $matrixGroups);
 +            $speciesData = $bouquetForecast['final'];
 +            foreach ($speciesData as $store_id  => $categoryData) {
 +                foreach ($categoryData as $category => $subcategoryData) {
 +                    foreach ($subcategoryData as $subcategory => $species) {
 +                        foreach ($species as $speciesInd => $row) {
 +                            $bouquetSpeciesForecast[] = [
 +                                'category' => $category,
 +                                'subcategory' => $subcategory,
 +                                'store_id' => $store_id,
 +                                'species' => $speciesInd,
 +                                'goal' => $row
 +                            ];
 +                        }
 +                    }
 +                }
 +
 +            }
 +            $cleanedSpeciesGoals = $service->subtractSpeciesGoals($goals, $bouquetSpeciesForecast, $noHistoryProductData);
 +
 +
 +            $salesProductForecastShare = $service->calculateProductForecastShare($noHistoryProductData, $productSalesForecast);
 +
 +            $productForecastSpecies = $service->calculateProductSalesBySpecies($salesProductForecastShare, $cleanedSpeciesGoals);
 +
 +
 +            $weeklySales = $service->getHistoricalSpeciesShareByWeek($filters['plan_date'], $filters);
 +
 +            $weeklySalesForecast = $service->calculateWeeklyProductForecastPieces($productForecastSpecies, $weeklySales);
 +
 +
 +            foreach ($weeklySalesForecast as $fc ) {
 +                $rows[] = [
 +                    'week' => $fc['week'],
 +                    'type_pm' => 'max',
 +                    'shop' => $fc['store_id'],
 +                    'category' => $fc['category'],
 +                    'subcategory' => $fc['subcategory'],
 +                    'species' => $fc['species'],
 +                    'product_name' => $fc['product_id'],
 +                    'quantity'  => $fc['forecast_week_pieces'],
 +                    'value_type' => 'offline',
 +                    'group_name' => 'Оффлайн',
 +                ];
 +            }
 +
 +
 +
 +            $spreadsheet = new Spreadsheet();
 +        $sheet = $spreadsheet->getActiveSheet();
 +        $sheet->setTitle('Отчёт');
 +
 +        $sheet->setCellValue('A1', 'Неделя');
 +        $sheet->setCellValue('B1', 'Тип п-ма');
 +        $sheet->setCellValue('C1', 'Магазин');
 +        $sheet->setCellValue('D1', 'Категория');
 +        $sheet->setCellValue('E1', 'Подкатегория');
 +        $sheet->setCellValue('F1', 'Вид');
 +        $sheet->setCellValue('G1', 'Товар');
 +        $sheet->setCellValue('H1', 'Кол-во');
 +        $sheet->setCellValue('I1', 'Тип значения');
 +        $sheet->setCellValue('J1', 'Имя группы');
 +
 +        $rowNum = 2;
 +
 +        foreach ($rows as $row) {
 +            $sheet->setCellValue('A' . $rowNum, $row['week']);
 +            $sheet->setCellValue('B' . $rowNum, $row['type_pm']);
 +            $sheet->setCellValue('C' . $rowNum, $row['shop']);
 +            $sheet->setCellValue('D' . $rowNum, $row['category']);
 +            $sheet->setCellValue('E' . $rowNum, $row['subcategory']);
 +            $sheet->setCellValue('F' . $rowNum, $row['species']);
 +            $sheet->setCellValue('G' . $rowNum, $row['product_name']);
 +            $sheet->setCellValue('H' . $rowNum, $row['quantity']);
 +            $sheet->setCellValue('I' . $rowNum, $row['value_type']);
 +            $sheet->setCellValue('J' . $rowNum, $row['group_name']);
 +            $rowNum++;
 +        }
 +
 +        foreach (range('A', 'J') as $col) {
 +            $sheet->getColumnDimension($col)->setAutoSize(true);
 +        }
 +
 +
 +
 +        $tempFile = tempnam(sys_get_temp_dir(), 'xlsx');
 +        $writer   = new Xlsx($spreadsheet);
 +        $writer->save($tempFile);
 +
 +        return Yii::$app
 +            ->response
 +            ->sendStreamAsFile(
 +                fopen($tempFile, 'rb'),
 +                'report_' . date('Ymd_His') . '.xlsx',
 +                [
 +                    'mimeType'             => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
 +                    'inline'               => false,
 +                    'deleteFileAfterSend'  => true,
 +                ]
 +            );
 +    }
 +
 +
 +    /**
 +     * Получает JSON из тела запроса (массив строк) и экспортирует его в Excel.
 +     * Если JSON не передан или невалиден, подставляет тестовые данные.
 +     */
 +    public function actionExportJsonToExcel()
 +    {
 +        $this->layout = false;
 +        Yii::$app->response->format = Response::FORMAT_RAW;
 +
 +        while (ob_get_level() > 0) {
 +            ob_end_clean();
 +        }
 +
 +        $rawBody = Yii::$app->request->getRawBody();
 +        $decoded = json_decode($rawBody, true);
 +
 +        if (is_array($decoded) && !empty($decoded)) {
 +            $rows = $decoded;
 +        } else {
 +            $rows = [
 +                [
 +                    'week'         => '1',
 +                    'type_pm'      => 'max',
 +                    'shop'         => 'Ванеева',
 +                    'category'     => 'Срезка',
 +                    'subcategory'  => 'Розы',
 +                    'species'      => 'Роза',
 +                    'product_name' => 'Роза Эквадор',
 +                    'quantity'     => '3',
 +                    'value_type'   => 'offline',
 +                    'group_name'   => 'Оффлайн',
 +                ],
 +                [
 +                    'week'         => '2',
 +                    'type_pm'      => 'min',
 +                    'shop'         => 'Ленина',
 +                    'category'     => 'Срезка',
 +                    'subcategory'  => 'Гвоздики',
 +                    'species'      => 'Гвоздика',
 +                    'product_name' => 'Гвоздика Стандарт',
 +                    'quantity'     => '5',
 +                    'value_type'   => 'online',
 +                    'group_name'   => 'Онлайн',
 +                ],
 +            ];
 +        }
 +
 +        $spreadsheet = new Spreadsheet();
 +        $sheet = $spreadsheet->getActiveSheet();
 +        $sheet->setTitle('Отчёт из JSON');
 +
 +        $sheet->setCellValue('A1', 'Неделя');
 +        $sheet->setCellValue('B1', 'Тип п-ма');
 +        $sheet->setCellValue('C1', 'Магазин');
 +        $sheet->setCellValue('D1', 'Категория');
 +        $sheet->setCellValue('E1', 'Подкатегория');
 +        $sheet->setCellValue('F1', 'Вид');
 +        $sheet->setCellValue('G1', 'Товар');
 +        $sheet->setCellValue('H1', 'Кол-во');
 +        $sheet->setCellValue('I1', 'Тип значения');
 +        $sheet->setCellValue('J1', 'Имя группы');
 +
 +        $rowNum = 2;
 +        foreach ($rows as $row) {
 +            $week         = $row['week'] ?? '';
 +            $typePm       = $row['type_pm'] ?? '';
 +            $shop         = $row['shop'] ?? '';
 +            $category     = $row['category'] ?? '';
 +            $subcategory  = $row['subcategory'] ?? '';
 +            $species      = $row['species'] ?? '';
 +            $productName  = $row['product_name'] ?? '';
 +            $quantity     = $row['quantity'] ?? '';
 +            $valueType    = $row['value_type'] ?? '';
 +            $groupName    = $row['group_name'] ?? '';
 +
 +            $sheet->setCellValue("A{$rowNum}", $week);
 +            $sheet->setCellValue("B{$rowNum}", $typePm);
 +            $sheet->setCellValue("C{$rowNum}", $shop);
 +            $sheet->setCellValue("D{$rowNum}", $category);
 +            $sheet->setCellValue("E{$rowNum}", $subcategory);
 +            $sheet->setCellValue("F{$rowNum}", $species);
 +            $sheet->setCellValue("G{$rowNum}", $productName);
 +            $sheet->setCellValue("H{$rowNum}", $quantity);
 +            $sheet->setCellValue("I{$rowNum}", $valueType);
 +            $sheet->setCellValue("J{$rowNum}", $groupName);
 +
 +            $rowNum++;
 +        }
 +
 +        foreach (range('A', 'J') as $col) {
 +            $sheet->getColumnDimension($col)->setAutoSize(true);
 +        }
 +
 +        $tempFile = tempnam(sys_get_temp_dir(), 'xlsx');
 +        $writer   = new Xlsx($spreadsheet);
 +        $writer->save($tempFile);
 +
 +        return Yii::$app
 +            ->response
 +            ->sendStreamAsFile(
 +                fopen($tempFile, 'rb'),
 +                'report_' . date('Ymd_His') . '.xlsx',
 +                [
 +                    'mimeType'            => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
 +                    'inline'              => false,
 +                    'deleteFileAfterSend' => true,
 +                ]
 +            );
 +    }
 +
 +    public function actionTest()
 +    {
 +        $request = Yii::$app->request;
 +
 +        $filters = [
 +            'category' => 'Срезка',
 +            'subcategory' => 'Розы' ?? null,
 +            'species' => 'Роза' ?? null,
 +            'store_id' => 2 ?? [],
 +            'year' => 2025,
 +            'month' => 5,
 +            'type' => 'sales',
 +        ];
 +
 +        $stores = ArrayHelper::map(
 +            CityStore::findAll(['visible' => CityStore::IS_VISIBLE]),
 +            'id',
 +            'name'
 +        );
 +      //  var_dump($stores);die();
 +        $dataProvider = new ArrayDataProvider([
 +            'allModels' => [],
 +            'pagination' => ['pageSize' => 100],
 +        ]);
 +        $flatData =[];
 +        $bouquetSpeciesForecast = [];
 +        // Обработка даты на год и месяц
 +        if (!empty($filters['year']) && !empty($filters['month'])) {
 +            $filters['plan_date'] = $filters['year'] . '-' . str_pad($filters['month'], 2, '0', STR_PAD_LEFT) . '-01';
 +            // var_dump( $filters['plan_date']); die();
 +            $service = new AutoPlannogrammaService();
 +//$goals = $service->calculateFullGoalChain($filters);
 +            $monthCategoryShare = $service->getMonthCategoryShareOrWriteOff($filters['plan_date'], $filters);
 +            $monthCategoryGoal = $service->getMonthCategoryGoal($monthCategoryShare, $filters['plan_date']);
 +            $monthSubcategoryShare = $service->getMonthSubcategoryShareOrWriteOff($filters['plan_date'], $filters);
 +            $monthSubcategoryGoal = $service->getMonthSubcategoryGoal($monthSubcategoryShare, $monthCategoryGoal);
 +            $monthSpeciesShare = $service->getMonthSpeciesShareOrWriteOff($filters['plan_date'], $filters);
 +            $goals = $service->getMonthSpeciesGoalDirty($monthSpeciesShare, $monthSubcategoryGoal);
 +
 +            if ($filters['type'] == AutoPlannogrammaService::TYPE_WRITE_OFFS) {
 +                $monthCategoryWriteOffsShare = $service->getMonthCategoryShareOrWriteOff($filters['plan_date'], $filters, $filters['type']);
 +                $monthCategoryWriteOffsGoal = $service->getMonthCategoryGoal($monthCategoryWriteOffsShare, $filters['plan_date'], $filters['type']);
 +                $monthSubcategoryWriteOffsShare = $service->getMonthSubcategoryShareOrWriteOff($filters['plan_date'], $filters, $filters['type']);
 +                $monthSubcategoryWriteOffsGoals = $service->getMonthSubcategoryGoal($monthSubcategoryWriteOffsShare, $monthCategoryWriteOffsGoal, $filters['type']);
 +                $monthSpeciesWriteOffShare = $service->getMonthSpeciesShareOrWriteOff($filters['plan_date'], $filters, $filters['type']);
 +                $goals = $service->getMonthSpeciesGoalDirty($monthSpeciesWriteOffShare, $monthSubcategoryWriteOffsGoals, $filters['type'], $data);
 +            }
 +
 +
 +            $result = StorePlanService::calculateHistoricalShare(
 +                $filters['store_id'],
 +                $filters['month'],
 +                $filters['year'],
 +                $filters['category'],
 +                $filters['subcategory'],
 +                $filters['species']
 +            );
 +
 +            $noHistoryProductData = $service->calculateSpeciesForecastForProductsWithoutHistory($filters['plan_date'], $filters);
 +
 +            $productSalesShare = StorePlanService::calculateProductSalesShareProductsWithHistory(
 +                $filters['store_id'],
 +                $filters['month'],
 +                $result['with_history']
 +            );
 +
 +            $productSalesForecast = $service->calculateProductForecastInPiecesProductsWithHistory(
 +                $filters['store_id'],
 +                $filters['month'],
 +                $productSalesShare,
 +                $goals,
 +                $filters['subcategory'],
 +                $filters['category'],
 +                $filters['species']
 +            );
 +
 +            $matrixForecast = MatrixBouquetForecast::find()
 +                ->where(['year' => $filters['year'], 'month' => $filters['month']])
 +                ->asArray()
 +                ->all();
 +            $matrixGroups = array_unique(ArrayHelper::getColumn($matrixForecast, 'group'));
 +            $bouquetForecast = StorePlanService::getBouquetSpiecesMonthGoalFromForecast($filters['month'], $filters['year'], $filters['store_id'], $matrixGroups);
 +            $speciesData = $bouquetForecast['final'];
 +            foreach ($speciesData as $store_id  => $categoryData) {
 +                foreach ($categoryData as $category => $subcategoryData) {
 +                    foreach ($subcategoryData as $subcategory => $species) {
 +                        foreach ($species as $speciesInd => $row) {
 +                            $bouquetSpeciesForecast[] = [
 +                                'category' => $category,
 +                                'subcategory' => $subcategory,
 +                                'store_id' => $store_id,
 +                                'species' => $speciesInd,
 +                                'goal' => $row
 +                            ];
 +                        }
 +                    }
 +                }
 +
 +            }
 +            $cleanedSpeciesGoals = $service->subtractSpeciesGoals($goals, $bouquetSpeciesForecast, $noHistoryProductData);
 +
 +
 +            $salesProductForecastShare = $service->calculateProductForecastShare($noHistoryProductData, $productSalesForecast);
 +
 +            $productForecastSpecies = $service->calculateProductSalesBySpecies($salesProductForecastShare, $cleanedSpeciesGoals);
 +
 +
 +            $weeklySales = $service->getHistoricalSpeciesShareByWeek($filters['plan_date'], $filters);
 +
 +            $weeklySalesForecast = $service->calculateWeeklyProductForecastPieces($productForecastSpecies, $weeklySales);
 +
 +
 +
 +
 +            $flatData = array_filter($weeklySalesForecast, function ($row) use ($filters) {
 +                foreach ($filters as $key => $value) {
 +                    if (empty($value)) continue;
 +                    if (!isset($row[$key])) continue;
 +
 +                    if (stripos((string)$row[$key], (string)$value) === false) {
 +                        return false;
 +                    }
 +                }
 +                return true;
 +            });
 +
 +            $dataProvider = new ArrayDataProvider([
 +                'allModels' => $flatData,
 +                'pagination' => ['pageSize' => 100],
 +            ]);
 +        }
 +        return $this->render('test', [
 +            'dataProvider' => $dataProvider,
 +            'data' => $flatData,
 +            'filters' => $filters,
 +            'stores' => $stores
 +        ]);
 +    }
 +
 +
+     public function actionControlSpeciesOld()
+     {
+         $model = new DynamicModel([
+             'storeId', 'month', 'type',
+         ]);
+         $model->addRule(['month', 'type'], 'required')
+             ->addRule('storeId', 'integer');
+         $storeList = CityStore::find()
+             ->select(['name', 'id'])
+             ->where(['visible' => CityStore::IS_VISIBLE])
+             ->indexBy('id')
+             ->column();
+         $monthsList = [];
+         for ($i = 0; $i < 12; $i++) {
+             // получаем метку вида "03-2025"
+             $ts = strtotime("first day of -{$i} month");
+             $key = date('m-Y', $ts);
+             $monthsList[$key] = $key;
+         }
+         $monthResult = [];
+         $totals = [];
+         $weeksData = [];
+         $weeksShareResult = [];
+         $weeksGoalResult = [];
+         $monthCategoryShareResult = [];
+         $weeksProductForecast = [];
+         if ($model->load(Yii::$app->request->post()) && $model->validate()) {
+             $filters = [];
+             list($m, $y) = explode('-', $model->month);
+             $dateFrom = date("Y-m-d 00:00:00", strtotime(sprintf('%04d-%02d-01', $y, $m)));
+             $dateTo = date("Y-m-t 23:59:59", strtotime($dateFrom));
+             if ($model->storeId) {
+                 $filters['store_id'] = $model->storeId;
+                 $filters['type'] = $model->type;
+                 $filters['plan_date'] = $dateFrom;
+             }
+             $service = new AutoPlannogrammaService();
+             if ($model->storeId) {
+                 $totals = $service->getStoreTotals(
+                     [$model->storeId],
+                     $dateFrom,
+                     null,
+                     $model->type,
+                     $dateTo
+                 );
+             }
+             $monthSpeciesGoals = $service->calculateFullGoalChainWeighted($filters);
+             $monthSpeciesGoalsMap = [];
+             foreach ($monthSpeciesGoals as $monthSpeciesGoal) {
+                 $monthSpeciesGoalsMap[$monthSpeciesGoal['store_id']]
+                 [$monthSpeciesGoal['category']]
+                 [$monthSpeciesGoal['subcategory']]
+                 [$monthSpeciesGoal['species']] = $monthSpeciesGoal['goal'];
+             }
+             $weeksShareResult = $service->getHistoricalWeeklySpeciesShare($model->month, $filters, null, 'writeOffs');
+             $weeksData = $service->calculateWeeklySpeciesGoals($weeksShareResult['weeksData'], $monthSpeciesGoals);
+             $datePlan = $filters['plan_date'];
+             $monthCategoryShare = $service->getMonthCategoryShareOrWriteOffWeighted($datePlan, $filters, null, $filters['type']);
+             $monthCategoryGoal = $service->getMonthCategoryGoal($monthCategoryShare, $datePlan, $filters['type']);
+             foreach ($monthCategoryShare as $sid => $cats) {
+                 foreach ($cats as $cat) {
+                     $monthCategoryShareResult[$sid][$cat['category']]['total_sum_cat'] = $cat['total_sum_cat'];
+                     $monthCategoryShareResult[$sid][$cat['category']]['share_of_total'] = $cat['share_of_total'];
+                 }
+             }
+             foreach ($monthCategoryGoal as $cats) {
+                 $monthCategoryShareResult[$cats['store_id']][$cats['category']]['goal'] = $cats['goal'];
+             }
+             $monthSubcategoryShare = $service->getMonthSubcategoryShareOrWriteOffWeighted($datePlan, $filters, null, $filters['type']);
+             $monthSubcategoryGoal = $service->getMonthSubcategoryGoal($monthSubcategoryShare, $monthCategoryGoal);
+             if ($filters['type'] === 'writeOffs') {
+                 $salesSubShare = $service->getMonthSubcategoryShareOrWriteOffWeighted($datePlan, $filters, null, 'sales');
+                 $salesSubGoal = $service->getMonthSubcategoryGoal($salesSubShare, $monthCategoryGoal);
+                 $catGoalMap = [];
+                 foreach ($monthCategoryGoal as $row) {
+                     $catGoalMap[$row['category']] = $row['goal'];
+                 }
+                 $salesSubGoalMap = [];
+                 foreach ($salesSubGoal as $row) {
+                     $salesSubGoalMap[$row['category']][$row['subcategory']] = $row['goal'];
+                 }
+                 foreach ($monthSubcategoryShare as &$row) {
+                     $cat = $row['category'];
+                     $sub = $row['subcategory'];
+                     $writeShare = $row['percent_of_month'];
+                     $writeGoal = ($catGoalMap[$cat] ?? 0) * $writeShare;
+                     $saleGoal = $salesSubGoalMap[$cat][$sub] ?? 0;
+                     if ($saleGoal > 0 && $writeGoal > 0.1 * $saleGoal) {
+                         $row['share'] = 0.1;
+                     }
+                 }
+                 unset($row);
+                 $monthSubcategoryGoal = $service->getMonthSubcategoryGoal($monthSubcategoryShare, $monthCategoryGoal);
+             }
+             foreach ($monthSubcategoryShare as $subcat) {
+                 $monthCategoryShareResult[$subcat['store_id']][$subcat['category']][$subcat['subcategory']]['total_sum'] = $subcat['total_sum'];
+                 $monthCategoryShareResult[$subcat['store_id']][$subcat['category']][$subcat['subcategory']]['percent_of_month'] = $subcat['percent_of_month'];
+             }
+             foreach ($monthSubcategoryGoal as $cats) {
+                 $monthCategoryShareResult[$cats['store_id']][$cats['category']][$cats['subcategory']]['goal'] = $cats['goal'];
+             }
+             $monthSpeciesShare = $service->getMonthSpeciesShareOrWriteOffWeighted($datePlan, $datePlan, $filters, null, $filters['type']);
+             $monthSpeciesGoal = $service->getMonthSpeciesGoalDirty($monthSpeciesShare, $monthSubcategoryGoal);
+             if ($filters['type'] === 'writeOffs') {
+                 $salesSpecShare = $service->getMonthSpeciesShareOrWriteOffWeighted($datePlan, $datePlan, $filters, null, 'sales');
+                 $salesSpecGoal = $service->getMonthSpeciesGoalDirty($salesSpecShare, $monthSubcategoryGoal);
+                 $subGoalMap = [];
+                 foreach ($monthSubcategoryGoal as $row) {
+                     $subGoalMap[$row['category']][$row['subcategory']] = $row['goal'];
+                 }
+                 $salesSpecGoalMap = [];
+                 foreach ($salesSpecGoal as $row) {
+                     $salesSpecGoalMap[$row['category']][$row['subcategory']][$row['species']] = $row['goal'];
+                 }
+                 foreach ($monthSpeciesShare as &$row) {
+                     $cat = $row['category'];
+                     $sub = $row['subcategory'];
+                     $spec = $row['species'];
+                     $writeShare = $row['percent_of_month'];
+                     $writeGoal = ($subGoalMap[$cat][$sub] ?? 0) * $writeShare;
+                     $saleGoal = $salesSpecGoalMap[$cat][$sub][$spec] ?? 0;
+                     if ($saleGoal > 0 && $writeGoal > 0.1 * $saleGoal) {
+                         $row['share'] = 0.1;
+                     }
+                 }
+                 unset($row);
+                 $monthSpeciesGoal = $service->getMonthSpeciesGoalDirty($monthSpeciesShare, $monthSubcategoryGoal);
+             }
+             foreach ($monthSpeciesShare as $species) {
+                 $monthCategoryShareResult[$species['store_id']][$species['category']][$species['subcategory']][$species['species']]['total_sum'] = $species['total_sum'];
+                 $monthCategoryShareResult[$species['store_id']][$species['category']][$species['subcategory']][$species['species']]['percent_of_month'] = $species['percent_of_month'];
+             }
+             foreach ($weeksShareResult['weeksData'] as $row) {
+                 $monthCategoryShareResult[$row['store_id']][$row['category']][$row['subcategory']][$row['species']][$row['week']]['sumWeek'] = $row['sumWeek'];
+             }
+             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'] ?? '',
+                         'subcategory' => $forecast['subcategory'] ?? '',
+                         'species' => $forecast['species'] ?? '',
+                         'product_id' => $forecast['product_id'] ?? '',
+                         'name' => $forecast['name'] ?? '',
+                         'price' => $forecast['price'] ?? '',
+                         'goal' => $forecast['goal'] ?? 0,
+                         'forecast' => $forecast['forecast'] ?? 0,
+                         'week' => $r['week'],
+                     ];
+                 }
+             }
+             usort($weeksProductForecast, function ($a, $b) {
+                 foreach (['category', 'subcategory', 'species', 'name', 'week'] as $key) {
+                     $va = $a[$key];
+                     $vb = $b[$key];
+                     if ($va < $vb) return -1;
+                     if ($va > $vb) return 1;
+                 }
+                 return 0;
+             });
+         }
+         return $this->render('control-species-old', [
+             'model' => $model,
+             'result' => $monthResult,
+             'weeksData' => $weeksData,
+             'monthCategoryShare' => $monthCategoryShareResult,
+             'weeksProductForecast' => $weeksProductForecast,
+             'totals' => $totals,
+             'storeList' => $storeList,
+             'monthsList' => $monthsList,
+         ]);
+     }
+     public function actionControlSpecies()
+     {
+         $model = new DynamicModel([
+             'storeId', 'month', 'type',
+         ]);
+         $model->addRule(['month', 'type'], 'required')
+             ->addRule('storeId', 'integer');
+         $storeList = CityStore::find()
+             ->select(['name', 'id'])
+             ->where(['visible' => CityStore::IS_VISIBLE])
+             ->indexBy('id')
+             ->column();
+         $monthsList = [];
+         for ($i = 0; $i < 12; $i++) {
+             // получаем метку вида "03-2025"
+             $ts = strtotime("first day of -{$i} month");
+             $key = date('m-Y', $ts);
+             $monthsList[$key] = $key;
+         }
+         $monthResult = [];
+         $totals = [];
+         $weeksData = [];
+         $weeksShareResult = [];
+         $weeksGoalResult = [];
+         $monthCategoryShareResult = [];
+         $weeksProductForecast = [];
+         if ($model->load(Yii::$app->request->post()) && $model->validate()) {
+             $filters = [];
+             list($m, $y) = explode('-', $model->month);
+             $dateFrom = date("Y-m-d 00:00:00", strtotime(sprintf('%04d-%02d-01', $y, $m)));
+             $dateTo = date("Y-m-t 23:59:59", strtotime($dateFrom));
+             if ($model->storeId) {
+                 $filters['store_id'] = $model->storeId;
+                 $filters['type'] = $model->type;
+                 $filters['plan_date'] = $dateFrom;
+             }
+             $service = new AutoPlannogrammaService();
+             $monthSpeciesGoals = $service->calculateFullGoalChain($filters);
+             $monthSpeciesGoalsMap = [];
+             foreach ($monthSpeciesGoals as $monthSpeciesGoal) {
+                 $monthSpeciesGoalsMap[$monthSpeciesGoal['store_id']]
+                 [$monthSpeciesGoal['category']]
+                 [$monthSpeciesGoal['subcategory']]
+                 [$monthSpeciesGoal['species']] = $monthSpeciesGoal['goal'];
+             }
+             $weeksShareResult = $service->getHistoricalWeeklySpeciesShare($model->month, $filters, null, 'writeOffs');
+             $weeksData = $service->calculateWeeklySpeciesGoals($weeksShareResult, $monthSpeciesGoals);
+             $datePlan = $filters['plan_date'];
+             $monthCategoryShare = $service->getMonthCategoryShareOrWriteOff($datePlan, $filters, $filters['type']);
+             $monthCategoryGoal = $service->getMonthCategoryGoal($monthCategoryShare, $datePlan, $filters['type']);
+             $monthCategorySalesShare = $service->getMonthCategoryShareOrWriteOff($datePlan, $filters);
+             $monthCategorySalesGoal = $service->getMonthCategoryGoal($monthCategorySalesShare, $datePlan);
+             foreach ($monthCategoryShare as $sid => $cats) {
+                 foreach ($cats as $cat) {
+                     $monthCategoryShareResult[$sid][$cat['category']]['total_sum'] = $cat['total_sum'];
+                     $monthCategoryShareResult[$sid][$cat['category']]['percent'] = $cat['percent'];
+                 }
+             }
+             foreach ($monthCategoryGoal as $cats) {
+                 $monthCategoryShareResult[$cats['store_id']][$cats['category']]['goal'] = $cats['goal'];
+             }
+             $monthSubcategorySalesShare = $service->getMonthSubcategoryShareOrWriteOff($datePlan, $filters);
+             $monthSubcategorySalesGoal = $service->getMonthSubcategoryGoal($monthSubcategorySalesShare, $monthCategorySalesGoal);
+             $monthSubcategoryShare = $service->getMonthSubcategoryShareOrWriteOff($datePlan, $filters, $filters['type']);
+             $monthSubcategoryGoal = $service->getMonthSubcategoryGoal($monthSubcategoryShare, $monthCategoryGoal, $filters['type'], $monthSubcategorySalesGoal);
+             foreach ($monthSubcategoryShare as $subcat) {
+                 $monthCategoryShareResult[$subcat['store_id']][$subcat['category']][$subcat['subcategory']]['total_sum'] = $subcat['total_sum'];
+                 $monthCategoryShareResult[$subcat['store_id']][$subcat['category']][$subcat['subcategory']]['percent'] = $subcat['percent'];
+             }
+             foreach ($monthSubcategoryGoal as $cats) {
+                 $monthCategoryShareResult[$cats['store_id']][$cats['category']][$cats['subcategory']]['goal'] = $cats['goal'];
+             }
+             $monthSpeciesSalesShare = $service->getMonthSpeciesShareOrWriteOff($datePlan, $filters);
+             $monthSpeciesSalesGoal = $service->getMonthSpeciesGoalDirty($monthSpeciesSalesShare, $monthSubcategoryGoal);
+             $monthSpeciesShare = $service->getMonthSpeciesShareOrWriteOff($datePlan, $filters, $filters['type']);
+             $monthSpeciesGoal = $service->getMonthSpeciesGoalDirty($monthSpeciesShare, $monthSubcategoryGoal, $filters['type'], $monthSpeciesSalesGoal);
+             foreach ($monthSpeciesShare as $species) {
+                 $monthCategoryShareResult[$species['store_id']][$species['category']][$species['subcategory']][$species['species']]['total_sum'] = $species['total_sum'];
+                 $monthCategoryShareResult[$species['store_id']][$species['category']][$species['subcategory']][$species['species']]['percent'] = $species['percent'];
+             }
+             foreach ($monthSpeciesGoal as $cats) {
+                 $monthCategoryShareResult[$cats['store_id']][$cats['category']][$cats['subcategory']][$cats['species']]['goal'] = $cats['goal'];
+             }
+             foreach ($weeksShareResult as $row) {
+                 $monthCategoryShareResult[$row['store_id']][$row['category']][$row['subcategory']][$row['species']][$row['week']]['sumWeek'] = $row['sumWeek'];
+             }
+             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'] ?? '',
+                         'subcategory' => $forecast['subcategory'] ?? '',
+                         'species' => $forecast['species'] ?? '',
+                         'product_id' => $forecast['product_id'] ?? '',
+                         'name' => $forecast['name'] ?? '',
+                         'price' => $forecast['price'] ?? '',
+                         'goal' => $forecast['goal'] ?? 0,
+                         'forecast' => $forecast['forecast'] ?? 0,
+                         'week' => $r['week'],
+                     ];
+                 }
+             }
+             usort($weeksProductForecast, function ($a, $b) {
+                 foreach (['category', 'subcategory', 'species', 'name', 'week'] as $key) {
+                     $va = $a[$key];
+                     $vb = $b[$key];
+                     if ($va < $vb) return -1;
+                     if ($va > $vb) return 1;
+                 }
+                 return 0;
+             });
+         }
+         return $this->render('control-species', [
+             'model' => $model,
+             'result' => $monthResult,
+             'weeksData' => $weeksData,
+             'monthCategoryShare' => $monthCategoryShareResult,
+             'weeksProductForecast' => $weeksProductForecast,
+             'totals' => $totals,
+             'storeList' => $storeList,
+             'monthsList' => $monthsList,
+         ]);
+     }
+     public function actionGetSubcategories(string $category, int $year, int $week): array
+     {
+         Yii::$app->response->format = Response::FORMAT_JSON;
+         $data = Autoplannogramma::find()
+             ->alias('a')
+             ->leftJoin('products_1c_nomenclature p', 'a.product_id = p.id')
+             ->where(['p.category' => $category])
+             ->andWhere(['a.year' => $year])
+             ->andWhere(['a.week' => $week])
+             ->select([
+                 'p.subcategory as name',
+                 new \yii\db\Expression("CASE WHEN COUNT(a.id) > 0 THEN 1 ELSE 0 END AS hasData")
+             ])
+             ->groupBy('p.subcategory')
+             ->asArray()
+             ->all();
+         return $data;
+     }
+     public function actionWeeklyBouquetProductsForecast()
+     {
+         Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
+         $request = Yii::$app->request;
+         $storeIdRequest = $request->get('storeId', null);
+         $monthRequest   = $request->get('month');
+         $yearRequest    = $request->get('year');
+         $weekRequest    = $request->get('week');
+         if (!$monthRequest || !$yearRequest || $weekRequest === null) {
+             return ['success' => false, 'message' => 'Нет параметров'];
+         }
+         $service = new AutoPlannogrammaService();
+         $result = $service->getWeeklyBouquetProductsForecast($monthRequest, $yearRequest, $storeIdRequest);
+         if (!is_array($result)) {
+             return ['success' => false, 'message' => 'Ошибка структуры данных'];
+         }
+         $grouped = [];
+         $salesShares = [];
+         $plans = SalesWriteOffsPlan::find()
+             ->where(['month' => $monthRequest, 'year' => $yearRequest])
+             ->indexBy('store_id')
+             ->asArray()
+             ->all();
+         if ($plans) {
+             foreach ($plans as $storeId => $plan) {
+                 $total = $plan['total_sales_plan'];
+                 $offline = $plan['offline_sales_plan'];
+                 $online = $plan['online_sales_shop_plan'];
+                 $market = $plan['online_sales_marketplace_plan'];
+                 $salesShares[$storeId]['offline'] = round($offline / $total, 4);
+                 $salesShares[$storeId]['online'] = round($online / $total, 4);
+                 $salesShares[$storeId]['marketplace'] = round($market / $total, 4);
+             }
+         }
+         foreach ($result as $item) {
+             $weekItem = (int) $item['week'];
+             $storeItem = (int) $item['store_id'];
+             $guid = (string) $item['product_guid'];
+             $group = (string) $item['matrix_group'];
+             $type = (string) $item['type'];
+             $forecastValue = (float) $item['week_forecast'];
+             if (isset($salesShares[$storeItem]) && isset($salesShares[$storeItem][$type])) {
+                 $grouped[$weekItem][$storeItem][$type]['share'] = $salesShares[$storeItem][$type];
+             }
+             $grouped[$weekItem][$storeItem][$guid][$type][$group] = $forecastValue;
+         }
+         if ($weekRequest !== null) {
+             $week = (int) $weekRequest;
+             $grouped = isset($grouped[$week]) ? [$week => $grouped[$week]] : [];
+         }
+         return [
+             'success' => true,
+             'data' => $grouped,
+         ];
+     }
  }