]> gitweb.erp-flowers.ru Git - erp24_rep/yii-erp24/.git/commitdiff
Доля продаж недельи для вида
authorfomichev <vladimir.fomichev@erp-flowers.ru>
Mon, 2 Jun 2025 15:39:01 +0000 (18:39 +0300)
committerfomichev <vladimir.fomichev@erp-flowers.ru>
Mon, 2 Jun 2025 15:39:01 +0000 (18:39 +0300)
erp24/controllers/AutoPlannogrammaController.php
erp24/services/AutoPlannogrammaService.php
erp24/views/auto-plannogramma/month-products-species-forecast.php
erp24/views/auto-plannogramma/week-sales-products_forecast.php [new file with mode: 0644]

index 24215970dc90a319621da7b1b8a71e5e1ad3b6a9..c362cba8c6e97a1d62a36ed29ad08643cf74523b 100644 (file)
@@ -1075,8 +1075,8 @@ class AutoPlannogrammaController extends BaseController
              $productForecastSpecies = $service->calculateProductSalesBySpecies($salesProductForecastShare, $cleanedSpeciesGoals);
 
 
-            $weeklySales = $service->getHistoricalSpeciesShareByWeek($filters['plan_date'], $filters);
-            var_dump($weeklySales); die();
+
+            //var_dump($productForecastSpecies); die();
 
 
 
@@ -1260,4 +1260,136 @@ class AutoPlannogrammaController extends BaseController
     }
 
 
+    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,
+        ]);
+    }
+
+
 }
index c94aedb376aa159a1fb0ee3b733c5b5014c4b47f..74ea14aba1c91ace443eae85cb54cfa68892b8c3 100644 (file)
@@ -18,6 +18,7 @@ use yii_app\records\Products1cNomenclature;
 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;
 
@@ -1169,6 +1170,67 @@ class AutoPlannogrammaService
 
         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.
      *
@@ -1874,20 +1936,48 @@ class AutoPlannogrammaService
                 $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,
@@ -1901,6 +1991,7 @@ class AutoPlannogrammaService
                 'history_status' => $shareItem['history_status'],
                 'cleanGoal' => $cleanGoal,
                 'product_sales' => round($productSales, 2),
+                'product_sales_pieces' => $productSalesPieces
             ];
         }
 
index 7385b4525d23fff90fcb2dbdae8e448babd65ab8..f9a0db4b3887b8b9708b5c11028c7969df62a930 100644 (file)
@@ -112,6 +112,7 @@ $columns = [
     ['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' => 'Статус товара', ],
 ];
 
diff --git a/erp24/views/auto-plannogramma/week-sales-products_forecast.php b/erp24/views/auto-plannogramma/week-sales-products_forecast.php
new file mode 100644 (file)
index 0000000..321c84f
--- /dev/null
@@ -0,0 +1,136 @@
+<?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,
+]);
+