]> gitweb.erp-flowers.ru Git - erp24_rep/yii-erp24/.git/commitdiff
Вывод долей и прогнозов
authorfomichev <vladimir.fomichev@erp-flowers.ru>
Tue, 27 May 2025 15:53:26 +0000 (18:53 +0300)
committerfomichev <vladimir.fomichev@erp-flowers.ru>
Tue, 27 May 2025 15:53:26 +0000 (18:53 +0300)
erp24/controllers/AutoPlannogrammaController.php
erp24/services/AutoPlannogrammaService.php
erp24/services/StorePlanService.php
erp24/views/auto-plannogramma/81.php
erp24/views/auto-plannogramma/82.php [new file with mode: 0644]

index d0dfa07fabf42775e2e4281e3ca6a26ee3807da1..3a9d226561e01d82b968729f4648b824ee3a2497 100644 (file)
@@ -679,13 +679,13 @@ class AutoPlannogrammaController extends BaseController
             '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); die();
             $service = new AutoPlannogrammaService();
-            $data = $service->calculateSpeciesForecastForProductsWithoutHistory($filters['plan_date'], $filters);
+
             //$goals = $service->calculateFullGoalChain($filters);
             $monthCategoryShare = $service->getMonthCategoryShareOrWriteOff($filters['plan_date'], $filters);
             $monthCategoryGoal = $service->getMonthCategoryGoal($monthCategoryShare, $filters['plan_date']);
@@ -713,12 +713,7 @@ class AutoPlannogrammaController extends BaseController
                 $filters['species']
             );
 
-            $withoutHistoryResults = StorePlanService::calculateMedianSalesForProductsWithoutHistoryExtended(
-                $filters['store_id'],
-                $filters['month'],
-                $filters['year'],
-                $result['without_history']
-            );
+            $noHistoryProductData = $service->calculateSpeciesForecastForProductsWithoutHistory($filters['plan_date'], $filters);
 
             $productSalesShare = StorePlanService::calculateProductSalesShareProductsWithHistory(
                 $filters['store_id'],
@@ -736,13 +731,43 @@ class AutoPlannogrammaController extends BaseController
                 $goals
             );
 
-            var_dump($productSalesForecast); die();
+            $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);
 
+            //var_dump($salesProductForecastShare); die();
 
-            $flatData = array_filter($data, function ($row) use ($filters) {
+
+
+
+
+            $flatData = array_filter($salesProductForecastShare, function ($row) use ($filters) {
                 foreach ($filters as $key => $value) {
                     if (empty($value)) continue;
                     if (!isset($row[$key])) continue;
@@ -832,7 +857,7 @@ class AutoPlannogrammaController extends BaseController
             }
             //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();
 
index 34d3de8991bfdbbbe4cbf15b1db356406ba94672..2731cbd064f3755a8025e5960be170711c828557 100644 (file)
@@ -904,4 +904,138 @@ class AutoPlannogrammaService
         return $result;
     }
 
+    /**
+     * Рассчитывает долю каждого товара в общем прогнозе по штукам.
+     *
+     * Объединяет прогнозы без истории и с историей для полного списка товаров.
+     *
+     * @param array $pieciesForecastProductsNoHistyory  Результат calculateSpeciesForecastForProductsWithoutHistory
+     * @param array $pieciesForecastProductWithHistory Результат calculateProductForecastInPiecesProductsWithHistory
+     * @return array
+     */
+    public function calculateProductForecastShare(
+        array $pieciesForecastProductsNoHistyory,
+        array $pieciesForecastProductWithHistory
+    ): array {
+        $shareResult = [];
+
+        $info = $pieciesForecastProductsNoHistyory[0] ?? null;
+        if (!$info) {
+            return [];
+        }
+
+        $noHistoryMap = $info['forecasts'] ?? [];
+
+        $piecesMap = [];
+        foreach ($pieciesForecastProductWithHistory as $item) {
+            if (isset($item['product_id'], $item['forecast_pieces'])) {
+                $piecesMap[$item['product_id']] = $item['forecast_pieces'];
+            }
+        }
+
+        $allProductIds = array_merge(
+                array_keys($noHistoryMap),
+                array_keys($piecesMap)
+        );
+
+        $quantityMap = [];
+        foreach ($allProductIds as $pid) {
+            if (isset($piecesMap[$pid])) {
+                $quantityMap[$pid] = $piecesMap[$pid];
+            } elseif (isset($noHistoryMap[$pid])) {
+                $quantityMap[$pid] = (float)$noHistoryMap[$pid];
+            } else {
+                $quantityMap[$pid] = 0;
+            }
+        }
+
+        $totalPieces = array_sum($quantityMap);
+        if ($totalPieces <= 0) {
+            return [];
+        }
+
+        $storeId     = $info['store_id'];
+        $month       = $info['month'];
+        $year        = $info['year'];
+        $category    = $info['category'];
+        $subcategory = $info['subcategory'];
+        $species     = $info['species'];
+
+        foreach ($quantityMap as $pid => $count) {
+            $share = $count / $totalPieces;
+
+            $shareResult[] = [
+                'store_id'       => $storeId,
+                'month'          => $month,
+                'year'           => $year,
+                'category'       => $category,
+                'subcategory'    => $subcategory,
+                'species'        => $species,
+                'product_id'     => $pid,
+                'forecast_pieces'=> $count,
+                'share'          => round($share, 4),
+                 'history_status'  => in_array($pid,  array_keys($noHistoryMap)) ? 'No history' : 'With history'
+            ];
+        }
+
+        return $shareResult;
+    }
+
+    /**
+     * Рассчитывает продажи по каждому товару внутри вида на основе долей и очищенной цели вида.
+     *
+     * @param array $productShares  Результат calculateProductForecastShare
+     * @param array $speciesGoals   Массив целей по видам с ключом 'goal'
+     * @return array
+     */
+    public static function calculateProductSalesBySpecies(
+        array $productShares,
+        array $speciesGoals
+    ): array {
+        $result = [];
+        $goalsMap = [];
+        foreach ($speciesGoals as $item) {
+            if (isset($item['store_id'], $item['category'], $item['subcategory'], $item['species'], $item['goal'])) {
+                $key = implode('|', [
+                    $item['store_id'],
+                    $item['category'],
+                    $item['subcategory'],
+                    $item['species']
+                ]);
+                $goalsMap[$key] = $item['goal'];
+            }
+        }
+
+        foreach ($productShares as $shareItem) {
+            $key = implode('|', [
+                $shareItem['store_id'],
+                $shareItem['category'],
+                $shareItem['subcategory'],
+                $shareItem['species']
+            ]);
+            if (!isset($goalsMap[$key])) {
+                continue;
+            }
+            $cleanGoal     = $goalsMap[$key];
+            $productSales = $shareItem['share'] * $cleanGoal;
+
+            $result[] = [
+                'store_id'      => $shareItem['store_id'],
+                'month'         => $shareItem['month'] ?? null,
+                'year'          => $shareItem['year'] ?? null,
+                'category'      => $shareItem['category'],
+                'subcategory'   => $shareItem['subcategory'],
+                'species'       => $shareItem['species'],
+                'product_id'    => $shareItem['product_id'],
+                'forecast_pieces' => $shareItem['forecast_pieces'],
+                'share'          => round($shareItem['share'], 4),
+                'history_status' => $shareItem['history_status'],
+                'cleanGoal' => $cleanGoal,
+                'product_sales' => round($productSales, 2),
+            ];
+        }
+
+        return $result;
+    }
+
 }
\ No newline at end of file
index f24f6dac144e0afb1cad0dd8df8fdfc128fc3d42..ccf53f78d5260f46a648d5243f802eac58bad513 100755 (executable)
@@ -478,7 +478,6 @@ class StorePlanService
         $initTime = (hrtime(true) - $t0) / 1e6; // миллисекунды
         Yii::warning( "Init (periods): {$initTime} ms\n");
         foreach ($productsWithoutHistory as $product) {
-            var_dump($product);die();
             $guid = $product['guid'];
             $t1 = hrtime(true);
             $similarProductIds = self::getSimilarProductIDs($guid);
@@ -715,14 +714,12 @@ class StorePlanService
             $accumulator[$groupKey]['goal'] += $q * $price;
         }
 
-        foreach ($medianProductsWithoutHistory as $guid => $_) {
+        foreach ($medianProductsWithoutHistory as $guid => $qty) {
             $groupKey = $guidToGroup[$guid];
             $goal     = $accumulator[$groupKey]['goal'];
             $price    = $prices[$guid] ?? 0.0;
-            $accumulator[$groupKey]['forecasts'][$guid] =
-                $price > 0.0
-                    ? ($goal / $price)
-                    : 0.0;
+            $accumulator[$groupKey]['forecasts'][$guid] = $qty['weightedValue'];
+
         }
 
         return array_values($accumulator);
index 885a80ed45d238d6173b7fe8de5d26c02c222c17..36934c01c8e57fa3b94f5964894d5d798cee60c4 100644 (file)
@@ -10,7 +10,7 @@
         use yii_app\records\CityStore;
         use yii_app\records\Products1cNomenclature;
         ?>
-        <h1 class="ms-3 mb-4"><?= Html::encode("РаÑ\81Ñ\87еÑ\82 Ð´Ð¾Ð»Ð¸ Ñ\82оваÑ\80а Ð±ÐµÐ· Ð¸Ñ\81Ñ\82оÑ\80ии (month_nohistory_sale_frcst)") ?></h1>
+        <h1 class="ms-3 mb-4"><?= Html::encode("РаÑ\81Ñ\87еÑ\82 Ð´Ð¾Ð»Ð¸ Ñ\82оваÑ\80а Ð¾Ñ\82 Ð¾Ð±Ñ\89его ÐºÐ¾Ð»Ð¸Ñ\87еÑ\81Ñ\82ва Ð²Ð½Ñ\83Ñ\82Ñ\80и Ð²Ð¸Ð´Ð° (month_goods_sales_share)") ?></h1>
         <?php
         $form = ActiveForm::begin(['method' => 'get']); ?>
         <div class="row p-3">
@@ -102,7 +102,18 @@ $columns = [
     ['attribute' => 'category', 'label' => 'Категория'],
     ['attribute' => 'subcategory', 'label' => 'Подкатегория'],
     ['attribute' => 'species', 'label' => 'Тип'],
-    ['attribute' => 'goal', 'label' => 'Сумма', 'format' => ['decimal', 2]],
+    ['attribute' => 'product_id', 'label' => 'GUID Товара', ],
+    ['attribute' => 'product_id', 'label' => 'Имя Товара',
+        'value' => function ($data) {
+            return \yii_app\records\Products1c::findOne($data['product_id'])->name ?? null;
+        },
+        ],
+    ['attribute' => 'forecast_pieces', 'label' => 'Прогноз в шт', 'format' => ['decimal', 2]],
+    ['attribute' => 'share', 'label' => 'Доля',
+
+        'format' => ['percent', 2]],
+
+    ['attribute' => 'history_status', 'label' => 'Статус товара', ],
 ];
 
 
diff --git a/erp24/views/auto-plannogramma/82.php b/erp24/views/auto-plannogramma/82.php
new file mode 100644 (file)
index 0000000..6ccb744
--- /dev/null
@@ -0,0 +1,126 @@
+<?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' => 'store_id', 'label' => 'Магазин', 'value' => function ($data) {
+        return CityStore::findOne($data['store_id'])->name ?? null;
+    }],
+    ['attribute' => 'category', 'label' => 'Категория'],
+    ['attribute' => 'subcategory', 'label' => 'Подкатегория'],
+    ['attribute' => 'species', 'label' => 'Тип'],
+    ['attribute' => 'product_id', 'label' => 'GUID Товара', ],
+    ['attribute' => 'product_id', 'label' => 'Имя Товара',
+        'value' => function ($data) {
+            return \yii_app\records\Products1c::findOne($data['product_id'])->name ?? null;
+        },
+        ],
+    ['attribute' => 'forecast_pieces', 'label' => 'Прогноз в шт', 'format' => ['decimal', 2]],
+    ['attribute' => 'share', 'label' => 'Доля',
+
+        'format' => ['percent', 2]],
+    ['attribute' => 'cleanGoal', 'label' => 'Цель вида очищенная', 'format' => ['decimal', 2]],
+    ['attribute' => 'product_sales', 'label' => 'Прогноз в стоимости внутри вида', 'format' => ['decimal', 2]],
+    ['attribute' => 'history_status', 'label' => 'Статус товара', ],
+];
+
+
+?>
+<?= GridView::widget([
+    'dataProvider' => $dataProvider,
+    'columns' => $columns,
+]); ?>
+<?php