From: fomichev Date: Thu, 3 Jul 2025 06:04:17 +0000 (+0300) Subject: Merge branch 'refs/heads/develop' into feature_fomichev_erp-362_export_to_excel_autop... X-Git-Url: https://gitweb.erp-flowers.ru/?a=commitdiff_plain;h=23af4cac2fcfaf13ae26fd0a810a7c8389fa0340;p=erp24_rep%2Fyii-erp24%2F.git Merge branch 'refs/heads/develop' into feature_fomichev_erp-362_export_to_excel_autoplannogram # Conflicts: # erp24/controllers/AutoPlannogrammaController.php --- 23af4cac2fcfaf13ae26fd0a810a7c8389fa0340 diff --cc erp24/controllers/AutoPlannogrammaController.php index 23e7ddaa,2c9904b2..2df1b04c --- a/erp24/controllers/AutoPlannogrammaController.php +++ b/erp24/controllers/AutoPlannogrammaController.php @@@ -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, + ]; + + + } }