$productForecastSpecies = $service->calculateProductSalesBySpecies($salesProductForecastShare, $cleanedSpeciesGoals);
- $weeklySales = $service->getHistoricalSpeciesShareByWeek($filters['plan_date'], $filters);
- var_dump($weeklySales); die();
+
+ //var_dump($productForecastSpecies); die();
}
+ public function actionWeekSalesProductsForecast()
+ {
+ $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],
+ ]);
+
+ $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);
+
+ //var_dump($weeklySalesForecast); die();
+
+
+ $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('week-sales-products_forecast', [
+ 'dataProvider' => $dataProvider,
+ 'filters' => $filters,
+ ]);
+ }
+
+
}
use yii_app\records\Sales;
use yii_app\records\SalesProducts;
use yii_app\records\SalesWriteOffsPlan;
+use yii_app\records\StorePlan;
use yii_app\records\WriteOffs;
use yii_app\records\WriteOffsProducts;
return $result;
}
+
+ public function calculateWeeklyProductForecastPieces(
+ array $productForecastSpecies,
+ array $weeklySales
+ ): array {
+
+ $forecastMap = [];
+ foreach ($productForecastSpecies as $item) {
+ $sid = $item['store_id'];
+ $cat = $item['category'];
+ $sub = $item['subcategory'];
+ $spec = $item['species'];
+ $pid = $item['product_id'];
+ $piecesMon = (float)$item['product_sales_pieces'];
+
+ $forecastMap[$sid][$cat][$sub][$spec][$pid] = $piecesMon;
+ }
+
+ $result = [];
+
+ foreach ($weeklySales as $w) {
+ $sid = $w['store_id'];
+ $cat = $w['category'];
+ $sub = $w['subcategory'];
+ $spec = $w['species'];
+ $week = $w['week'];
+ $wShare = (float)$w['share'];
+
+ if (
+ ! isset(
+ $forecastMap[$sid],
+ $forecastMap[$sid][$cat],
+ $forecastMap[$sid][$cat][$sub],
+ $forecastMap[$sid][$cat][$sub][$spec]
+ )
+ ) {
+ continue;
+ }
+
+ $productsInSpec = $forecastMap[$sid][$cat][$sub][$spec];
+
+ foreach ($productsInSpec as $pid => $piecesMon) {
+ $forecastWeekPieces = round($piecesMon * $wShare, 2);
+
+ $result[] = [
+ 'week' => $week,
+ 'store_id' => $sid,
+ 'category' => $cat,
+ 'subcategory' => $sub,
+ 'species' => $spec,
+ 'product_id' => $pid,
+ 'forecast_week_pieces' => $forecastWeekPieces,
+ ];
+ }
+ }
+
+ return $result;
+ }
+
+
+
/**
* Исторический недельный отчёт и доли по видам с учётом store_id.
*
$goalsMap[$key] = $item['goal'];
}
}
+ $regions = CityStoreParams::find()
+ ->select(['store_id', 'address_region'])
+ ->indexBy('store_id')
+ ->asArray()
+ ->all();
+ $products = ArrayHelper::getColumn($productShares, 'product_id');
+
+ $prices = PricesDynamic::find()
+ ->select(['product_id', 'price', 'region_id'])
+ ->where(['product_id' => $products])
+ ->andWhere(['active' => 1])
+ ->asArray()
+ ->all();
+ $pricesMap = [];
+ foreach ($prices as $price) {
+ $pricesMap[$price['product_id']][$price['region_id']][] = $price['price'];
+ }
foreach ($productShares as $shareItem) {
+ $storeId = $shareItem['store_id'];
+
+ $region = $regions[$storeId]['address_region']
+ ?? BouquetComposition::REGION_NN;
+
+ $priceList = $pricesMap[$shareItem['product_id']][$region] ?? null;
+ $price = is_array($priceList) && count($priceList) > 0
+ ? $priceList[0] ?? 1
+ : 1;
+
+
$key = implode('|', [
$shareItem['store_id'],
$shareItem['category'],
$shareItem['subcategory'],
$shareItem['species']
]);
- if (!isset($goalsMap[$key])) {
- continue;
- }
- $cleanGoal = $goalsMap[$key];
+
+ $cleanGoal = $goalsMap[$key] ?? 0;
$productSales = $shareItem['share'] * $cleanGoal;
+ $productSalesPieces = round($productSales / $price, 2);
+
$result[] = [
'store_id' => $shareItem['store_id'],
'month' => $shareItem['month'] ?? null,
'history_status' => $shareItem['history_status'],
'cleanGoal' => $cleanGoal,
'product_sales' => round($productSales, 2),
+ 'product_sales_pieces' => $productSalesPieces
];
}
['attribute' => 'share', 'label' => 'Доля', 'pageSummary' => true, 'format' => ['percent', 2]],
['attribute' => 'cleanGoal', 'label' => 'Цель вида очищенная', 'pageSummary' => true, 'format' => ['decimal', 2]],
['attribute' => 'product_sales', 'label' => 'Прогноз в стоимости внутри вида', 'pageSummary' => true, 'format' => ['decimal', 2]],
+ ['attribute' => 'product_sales_pieces', 'label' => 'Прогноз в стоимости внутри вида шт', 'pageSummary' => true, 'format' => ['decimal', 2]],
['attribute' => 'history_status', 'label' => 'Статус товара', ],
];
--- /dev/null
+<?php
+?>
+ <div class="filter-form" style="margin-bottom: 20px;">
+ <?php use kartik\date\DatePicker;
+ use kartik\grid\GridView;
+ use kartik\select2\Select2;
+ use yii\helpers\ArrayHelper;
+ use yii\helpers\Html;
+ use yii\widgets\ActiveForm;
+ use yii_app\records\CityStore;
+ use yii_app\records\Products1cNomenclature;
+ ?>
+ <h1 class="ms-3 mb-4"><?= Html::encode("Расчет прогноза товаров внутри вида (month_goods_sales_share)") ?></h1>
+ <?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(['species' => $filters['species'] ?? '']), 'species')->widget(Select2::class, [
+ 'data' => ArrayHelper::map(
+ Products1cNomenclature::find()->select('species')->distinct()->asArray()->all(),
+ 'species',
+ 'species'
+ ),
+ 'options' => ['placeholder' => 'Тип товара', 'name' => 'species'],
+ '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(['month' => $filters['month'] ?? '']), 'month')->dropDownList(\yii_app\helpers\DateHelper::MONTH_NUMBER_NAMES, [
+ 'prompt' => 'Месяц',
+ 'name' => 'month',
+ ])->label('Плановый месяц') ?>
+ </div>
+
+ <div class="col-md">
+ <?= $form->field(new \yii\base\DynamicModel(['year' => $filters['year'] ?? '']), 'year')->dropDownList(['2025' => 2025, '2026' => 2026], [
+ 'prompt' => 'Год',
+ 'name' => 'year',
+ ])->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-plannogramma/8'], ['class' => 'btn btn-default']) ?>
+ </div>
+ </div>
+
+ <?php ActiveForm::end(); ?>
+ </div>
+
+
+
+<?php
+$columns = [
+ [
+ 'attribute' => 'week',
+ 'label' => 'Неделя',
+ ],
+ [
+ 'attribute' => 'store_id',
+ 'label' => 'Магазин',
+ 'value' => function ($data) {
+ return CityStore::findOne($data['store_id'])->name ?? $data['store_id'];
+ },
+ ],
+ [
+ 'attribute' => 'category',
+ 'label' => 'Категория',
+ ],
+ [
+ 'attribute' => 'subcategory',
+ 'label' => 'Подкатегория',
+ ],
+ [
+ 'attribute' => 'species',
+ 'label' => 'Тип',
+ ],['attribute'=>'week','label'=>'Неделя'],
+ [
+ 'attribute'=>'store_id','label'=>'Магазин',
+ 'value'=>function($data){ return CityStore::findOne($data['store_id'])->name ?? $data['store_id']; }
+ ],
+
+ ['attribute'=>'product_id','label'=>'GUID Товара'],
+ ['attribute'=>'forecast_week_pieces','label'=>'Прогноз (шт.)','format'=>['decimal',2]],
+];
+
+echo GridView::widget([
+ 'dataProvider' => $dataProvider,
+ 'showPageSummary' => true,
+ 'columns' => $columns,
+]);
+