}
}
- //var_dump($matrixGroups); die();
+
$flatData = array_filter($bouquetSpeciesForecast, function ($row) use ($filters) {
foreach ($filters as $key => $value) {
if (empty($value)) continue;
// Обработка даты на год и месяц
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); die();
+
$service = new AutoPlannogrammaService();
$data = $service->calculateSpeciesForecastForProductsWithoutHistory($filters['plan_date'], $filters);
- //var_dump($data); die();
$flatData = array_filter($data, function ($row) use ($filters) {
foreach ($filters as $key => $value) {
if (empty($value)) continue;
// Обработка даты на год и месяц
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); die();
+
$service = new AutoPlannogrammaService();
//$goals = $service->calculateFullGoalChain($filters);
// $productForecastSpecies = $service->calculateProductSalesBySpecies($salesProductForecastShare, $cleanedSpeciesGoals);
- // var_dump($salesProductForecastShare); die();
-
-
-
-
-
$flatData = array_filter($salesProductForecastShare, function ($row) use ($filters) {
foreach ($filters as $key => $value) {
if (empty($value)) continue;
// Обработка даты на год и месяц
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); die();
+
$service = new AutoPlannogrammaService();
//$goals = $service->calculateFullGoalChain($filters);
$productForecastSpecies = $service->calculateProductSalesBySpecies($salesProductForecastShare, $cleanedSpeciesGoals);
-
-
- //var_dump($productForecastSpecies); die();
-
-
-
-
-
$flatData = array_filter($productForecastSpecies, function ($row) use ($filters) {
foreach ($filters as $key => $value) {
if (empty($value)) continue;
}
}
- //var_dump($bouquetSpeciesForecast); die();
+
$noHistoryProductData = $service->calculateSpeciesForecastForProductsWithoutHistory($filters['plan_date'], $filters);
- // var_dump($noHistoryProductData); die();
+
$cleanedSpeciesGoals = $service->subtractSpeciesGoals($data, $bouquetSpeciesForecast, $noHistoryProductData);
- //var_dump($cleanedSpeciesGoals); die();
$flatData = array_filter($cleanedSpeciesGoals, function ($row) use ($filters) {
foreach ($filters as $key => $value) {
// Обработка даты на год и месяц
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();
// Обработка даты на год и месяц
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);
$monthCategoryShareResult[$cats['store_id']][$cats['category']]['goal'] = $cats['goal'];
}
- //var_dump($monthCategoryShareResult); die();
+
$monthSubcategoryShare = $service->getMonthSubcategoryShareOrWriteOffWeighted($datePlan, $filters, null, $filters['type']);
$monthSubcategoryGoal = $service->getMonthSubcategoryGoal($monthSubcategoryShare, $monthCategoryGoal);
unset($row);
$monthSubcategoryGoal = $service->getMonthSubcategoryGoal($monthSubcategoryShare, $monthCategoryGoal);
}
- // var_dump($monthSubcategoryShare); die();
+
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'];
});
}
-//var_dump($weeksProductForecast); die();
+
return $this->render('control-species-old', [
'model' => $model,
'result' => $monthResult,
$monthSubcategoryShare = $service->getMonthSubcategoryShareOrWriteOff($datePlan, $filters, $filters['type']);
$monthSubcategoryGoal = $service->getMonthSubcategoryGoal($monthSubcategoryShare, $monthCategoryGoal, $filters['type'], $monthSubcategorySalesGoal);
-
-
- // var_dump($monthSubcategoryShare); die();
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'];
$monthCategoryShareResult[$row['store_id']][$row['category']][$row['subcategory']][$row['species']][$row['week']]['sumWeek'] = $row['sumWeek'];
}
- //var_dump($monthCategoryShareResult); die();
foreach ($weeksData as $r) {
$forecasts = $service->calculateWeekForecastSpeciesProducts($r['category'], $r['subcategory'], $r['species'], $r['store_id'], $r['weekly_goal']);
});
}
-//var_dump($weeksProductForecast); die();
+
return $this->render('control-species', [
'model' => $model,
'result' => $monthResult,
->andWhere(['nom.category' => null])
->asArray()
->all();
- // var_dump( $salesProducts); die();
$components = [];
$rows = [];
// Недельные расчеты
- /**
- * Получает суммы продаж или списаний по видам (species) для каждой недели указанного месяца.
- *
- * @param string $monthYear месяц-год в формате MM-YYYY, например '03-2025'
- * @param array|null $filters опциональные фильтры ['store_id'=>...]
- * @param array|null $productFilter опциональный фильтр по product_id
- * @param string $type 'sales' или 'writeOffs'
- * @return array<int, array> массив строк: [
- * ['week'=>1,'store_id'=>...,'category'=>...,'subcategory'=>...,'species'=>...,'sum'=>...],
- * ...
- * ]
- */
- public function getWeeklySpeciesDataForMonth(
- string $monthYear,
- ?array $filters = null,
- ?array $productFilter = null,
- string $type = 'sales'
- ): array {
- [$monthStr, $yearStr] = explode('-', $monthYear);
- $month = (int)$monthStr;
- $year = (int)$yearStr;
-
- $dateFrom = strtotime(sprintf('%04d-%02d-01 00:00:00', $year, $month));
- $dateTo = strtotime('+1 month -1 second', $dateFrom);
-
- $stores = $this->getVisibleStores();
- $storeIds = array_map(fn($s)=>$s->id, $stores);
- if (!empty($filters['store_id'])) {
- $storeIds = array_intersect($storeIds, [(int)$filters['store_id']]);
- }
- if (empty($storeIds)) {
- return [];
- }
-
- $dayOfWeek = (int)date('N', $dateFrom);
- $firstMonday = $dayOfWeek === 1
- ? $dateFrom
- : strtotime('next monday', $dateFrom);
-
-
- $weekRanges = [];
- for ($wkStart = $firstMonday; $wkStart <= $dateTo; $wkStart += 7 * 86400) {
- $wkEnd = $wkStart + 6 * 86400;
- if ($wkEnd > $dateTo) {
- $wkEnd = $dateTo;
- }
- $periodStart = max($wkStart, $dateFrom);
- $periodEnd = min($wkEnd, $dateTo);
- $daysInMonth = floor(($periodEnd - $periodStart) / 86400) + 1;
- if ($daysInMonth >= 4) {
- $weekRanges[] = [
- 'index' => (int)date('W', $wkStart),
- 'start' => date('Y-m-d H:i:s', $wkStart),
- 'end' => date('Y-m-d 23:59:59', $wkEnd),
- ];
- }
- }
-
- $result = [];
-
- foreach ($weekRanges as $range) {
- $exprWeek = new Expression((string)$range['index']);
- $query = (new Query())->select([
- 'week' => $exprWeek,
- 'store_id' => 'ex.entity_id',
- 'category' => 'p1c.category',
- 'subcategory' => 'p1c.subcategory',
- 'species' => 'p1c.species',
- 'total_sum' => new Expression(
- $type === 'writeOffs'
- ? 'SUM(CAST(wop.summ AS NUMERIC))'
- : 'SUM(sp.summ)'
- ),
- ]);
-
- if ($type === 'writeOffs') {
- $query->from(['w' => 'write_offs'])
- ->leftJoin(['ex' => 'export_import_table'], 'ex.export_val = w.store_id')
- ->leftJoin(['wop'=> 'write_offs_products'], 'wop.write_offs_id = w.id')
- ->leftJoin(['p1c'=> 'products_1c_nomenclature'], 'p1c.id = wop.product_id')
- ->andWhere(['>=', 'w.date', $range['start']])
- ->andWhere(['<=', 'w.date', $range['end']]);
- if ($productFilter !== null) {
- $query->andWhere(['wop.product_id' => $productFilter]);
- }
- } else {
- $query->from(['s' => 'sales'])
- ->leftJoin(['sp' => 'sales_products'], 'sp.check_id = s.id')
- ->leftJoin(['ex' => 'export_import_table'], 'ex.export_val = s.store_id_1c')
- ->leftJoin(['p1c'=> 'products_1c_nomenclature'], 'p1c.id = sp.product_id')
- ->andWhere(['>=', 's.date', $range['start']])
- ->andWhere(['<=', 's.date', $range['end']]);
- if ($productFilter !== null) {
- $query->andWhere(['sp.product_id' => $productFilter]);
- }
- }
-
- $query->andWhere(['ex.entity_id' => $storeIds])
- ->andWhere(['<>', 'p1c.species', ''])
- ->groupBy(['week','ex.entity_id','p1c.category','p1c.subcategory','p1c.species']);
-
- $rows = $query->all();
- foreach ($rows as $row) {
- $result[] = [
- 'week' => $row['week'],
- 'store_id' => $row['store_id'],
- 'category' => $row['category'],
- 'subcategory' => $row['subcategory'],
- 'species' => $row['species'],
- 'sum' => (float)$row['total_sum'],
- ];
- }
- }
-
- return $result;
- }
-
- /**
- * Исторический недельный отчёт и доли по видам с учётом store_id.
- *
- * @param string $monthYear месяц-год в формате MM-YYYY
- * @param array|null $filters
- * @param array|null $productFilter
- * @param string $type
- * @return array{ 'weeksData': array<int, array> }
- * возвращает плоский список строк:
- * [
- * ['week'=>1, 'store_id'=>2, 'category'=>'...', 'subcategory'=>'...', 'species'=>'...', 'percent'=>0.32],
- * ...
- * ]
- */
- public function getHistoricalWeeklySpeciesShare(
- string $monthYear,
- ?array $filters = null,
- ?array $productFilter = null,
- string $type = 'sales'
- ): array {
- [$monthStr, $yearStr] = explode('-', $monthYear);
- $month = (int)$monthStr;
- $year = (int)$yearStr;
-
- $historical = [];
- for ($yr = $year - 2; $yr < $year; $yr++) {
- $mYear = sprintf('%02d-%d', $month, $yr);
- $weeklyData = $this->getWeeklySpeciesDataForMonth(
- $mYear, $filters, $productFilter, $type
- );
-
- foreach ($weeklyData as $row) {
- $week = $row['week'];
- $sid = $row['store_id'];
- $cat = $row['category'];
- $sub = $row['subcategory'];
- $spec = $row['species'];
- $sumWeek = $row['sum'];
-
- $historical[$week] ??= [];
- $historical[$week][$sid] ??= [];
- $historical[$week][$sid][$cat] ??= [];
- $historical[$week][$sid][$cat][$sub] ??= [];
- $historical[$week][$sid][$cat][$sub][$spec] =
- ($historical[$week][$sid][$cat][$sub][$spec] ?? 0) + $sumWeek;
- }
- }
-
- $dateFrom = sprintf('%04d-%02d-01 00:00:00', $year, $month);
- $dateTo = date('Y-m-d H:i:s', strtotime("$dateFrom +1 month -1 second"));
- $monthWeighted = $this->getMonthSpeciesShareOrWriteOffWeighted(
- $dateFrom, $dateTo, $filters, $productFilter, $type
- );
- $monthMap = [];
- foreach ($monthWeighted as $m) {
- $sid = $m['store_id'];
- $cat = $m['category'];
- $sub = $m['subcategory'];
- $spec = $m['species'];
- $sumMonth = $m['total_sum'];
-
- $monthMap[$sid] ??= [];
- $monthMap[$sid][$cat] ??= [];
- $monthMap[$sid][$cat][$sub]??= [];
- $monthMap[$sid][$cat][$sub][$spec] =
- ($monthMap[$sid][$cat][$sub][$spec] ?? 0) + $sumMonth;
- }
-
-
- $weeksList = array_keys($historical);
- sort($weeksList, SORT_NUMERIC);
-
- $speciesList = [];
- foreach ($monthMap as $sid => $byCat) {
- foreach ($byCat as $cat => $bySub) {
- foreach ($bySub as $sub => $bySpec) {
- foreach ($bySpec as $spec => $_) {
- $speciesList[] = compact('sid','cat','sub','spec');
- }
- }
- }
- }
-
-
- $rows = [];
- foreach ($speciesList as $comb) {
- $sid = $comb['sid'];
- $cat = $comb['cat'];
- $sub = $comb['sub'];
- $spec = $comb['spec'];
-
- $sumMonth = $monthMap[$sid][$cat][$sub][$spec] ?? 0;
- if ($sumMonth <= 0) {
- continue; // нет месячного итога
- }
-
- foreach ($weeksList as $week) {
- $sumWeek = $historical[$week][$sid][$cat][$sub][$spec] ?? 0;
- $percent = $sumWeek > 0 ? round($sumWeek / $sumMonth, 4) : null;
- $rows[] = [
- 'week' => $week,
- 'store_id' => $sid,
- 'category' => $cat,
- 'subcategory' => $sub,
- 'species' => $spec,
- 'sumWeek' => $sumWeek,
- 'percent' => $percent,
- ];
- }
- }
-
- $grouped = [];
- foreach ($rows as $idx => $row) {
- $key = "{$row['store_id']}|{$row['category']}|{$row['subcategory']}|{$row['species']}";
- $grouped[$key][] = $idx;
- }
- foreach ($grouped as $key => $indices) {
- $sumPercent = 0.0;
- foreach ($indices as $i) {
- $sumPercent += $rows[$i]['percent'];
- }
- if ($sumPercent < 1.0) {
- $diff = 1.0 - $sumPercent;
- $count = count($indices);
- $add = $diff / $count;
- foreach ($indices as $i) {
- $rows[$i]['percent'] = round($rows[$i]['percent'] + $add, 4);
- }
- }
- }
-
- return ['weeksData' => $rows];
- }
-
-
- /**
- * Рассчитывает недельную цель для каждого вида (species) по данным недельных долей
- * и целям месяца.
- * @param array $weeksShareData
- * @param array $monthSpeciesGoals
- * @return array
- * Плоский массив строк с полями: week, store_id, category, subcategory,
- * species, percent, monthly_goal, weekly_goal
- */
- public function calculateWeeklySpeciesGoals(
- array $weeksShareData,
- array $monthSpeciesGoals
- ): array {
- $monthSpeciesGoalsMap = [];
- foreach ($monthSpeciesGoals as $monthSpeciesGoal) {
- $monthSpeciesGoalsMap[$monthSpeciesGoal['store_id']]
- [$monthSpeciesGoal['category']]
- [$monthSpeciesGoal['subcategory']]
- [$monthSpeciesGoal['species']] = $monthSpeciesGoal['goal'] ;
- }
- $result = [];
- foreach ($weeksShareData as $row) {
- $week = $row['week'];
- $sid = $row['store_id'];
- $cat = $row['category'];
- $sub = $row['subcategory'];
- $spec = $row['species'];
- $percent = $row['percent'];
-
- $monthlyGoal = $monthSpeciesGoalsMap[$sid][$cat][$sub][$spec] ?? null;
-
- $weeklyGoal = 0;
- if ($monthlyGoal !== null && $percent !== null) {
- $weeklyGoal = round($percent * $monthlyGoal, 4);
- }
-
- $result[] = [
- 'week' => $week,
- 'store_id' => $sid,
- 'category' => $cat,
- 'subcategory' => $sub,
- 'species' => $spec,
- 'percent' => $percent,
- 'monthly_goal' => $monthlyGoal,
- 'weekly_goal' => $weeklyGoal,
- ];
- }
- return $result;
- }
-
- /**
- * Возвращает дату понедельника ISO-недели в формате YYYY-MM-DD
- *
- * @param int $year ISO-год (может отличаться от календарного в границах года)
- * @param int $week номер ISO-недели (1–53)
- * @return string дата понедельника, например '2025-03-10'
- */
- public static function getIsoWeekStart(int $year, int $week): string
- {
- $iso = $year . 'W' . str_pad($week, 2, '0', STR_PAD_LEFT) . '1';
- return date('Y-m-d', strtotime($iso));
- }
-
-
- public static function calculateWeekForecastSpeciesProducts($category, $subcategory, $species, $storeId, $goal)
- {
- $speciesProductForecast = [];
- $products = Products1cNomenclature::find()
- ->select(['id', 'name'])
- ->where(['category' => $category])
- ->andWhere(['subcategory' => $subcategory])
- ->andWhere(['species' => $species])
- ->indexBy('id')
- ->asArray()
- ->all();
-
- $productsIds = ArrayHelper::getColumn($products, 'id');
- if (CityStore::find()->where(['id' => $storeId])->one()->city_id == 1342) {
- $region = 52;
- } elseif (CityStore::find()->where(['id' => $storeId])->one()->city_id == 1) {
- $region = 77;
- } else {
- $region = null;
- }
- $priceRecords = PricesDynamic::find()
- ->select(['product_id', 'price'])
- ->where(['product_id' => $productsIds])
- ->andWhere(['active' => 1])
- ->andWhere(['or', ['region_id' => $region], ['region_id' => null]])
- ->indexBy('product_id')
- ->asArray()
- ->all();
-
- foreach ($priceRecords as $id => $record) {
- if ($goal == 0 || (int)$record['price'] == 0) {
- $forecast = 0;
- } else {
- $forecast = round(max($goal / (float)$record['price'], 1), 0);
- }
- $speciesProductForecast[] = [
- 'category' => $category,
- 'subcategory' => $subcategory,
- 'species' => $species,
- 'product_id' => $record['product_id'],
- 'name' => $products[$id]['name'],
- 'price' => $record['price'] ?? $goal ?? 1,
- 'goal' => $goal ?? 0,
- 'forecast' => $forecast
-
- ];
-
- }
-
- return $speciesProductForecast;
-
- }
// альтернативные методы расчета списаний