]> gitweb.erp-flowers.ru Git - erp24_rep/yii-erp24/.git/commitdiff
ERP-360 Сборка страницы автопм
authormarina <m.zozirova@gmail.com>
Wed, 18 Jun 2025 12:19:23 +0000 (15:19 +0300)
committermarina <m.zozirova@gmail.com>
Wed, 18 Jun 2025 12:19:23 +0000 (15:19 +0300)
erp24/commands/CronController.php
erp24/records/Autoplannogramma.php
erp24/services/AutoPlannogrammaService.php

index 2424c57fba693d1df6e1e031950bad761dede2ea..bdc52b333971016c5817dd3ee9af8268afbab372 100644 (file)
@@ -7,6 +7,7 @@ use app\jobs\SendTelegramMessageJob;
 use app\jobs\SendWhatsappMessageJob;
 use DateTime;
 use DateTimeZone;
+use Throwable;
 use Yii;
 use yii\console\Controller;
 use yii\console\ExitCode;
@@ -1562,42 +1563,24 @@ class CronController extends Controller
         return ExitCode::OK;
     }
 
-    public function actionAutoplannogrammaCalculate()
+    public function actionAutoplannogrammaCalculate(): void
     {
-        $date = new \DateTime();
+        $date = new DateTime();
         $date->modify('+2 months');
-
         $planDate = $date->format('Y-m-01');
-        $month = $date->format('m');
-        $year = $date->format('Y');
+        $month = (int)$date->format('m');
+        $year = (int)$date->format('Y');
 
         $service = new AutoPlannogrammaService();
-        $writeOffsForecast = $service->getWeeklyProductsWriteoffsForecast($month, $year, 2);
-        $forecast = $service->getWeeklyBouquetProductsForecast($month, $year, 2);
-
-        foreach ($forecast as $item) {
-
-            $this->stdout('прогноз', BaseConsole::FG_GREEN);
-            $this->stdout(print_r($item, true), BaseConsole::FG_GREEN);
-            break;
-        }
-
-        foreach ($writeOffsForecast as $item) {
-            $this->stdout('списание', BaseConsole::FG_GREEN);
-            $this->stdout(print_r($item, true), BaseConsole::FG_GREEN);
-            break;
-        }
+        $stores = CityStore::find()->where(['visible' => CityStore::IS_VISIBLE])->all();
 
         $this->stdout("Начало расчетов автопланограммы для $planDate\n", BaseConsole::FG_GREEN);
 
-        $service = new AutoPlannogrammaService();
-        $stores = CityStore::findAll(['visible' => CityStore::IS_VISIBLE]);
-
         foreach ($stores as $store) {
             $this->stdout("Начало расчетов автопланограммы для магазина ID: {$store->id} ({$store->name})\n", BaseConsole::FG_YELLOW);
 
             try {
-                $forecast = $service->calculateFullForecastForWeek([
+                $forecastParams = [
                     'month' => $month,
                     'year' => $year,
                     'type' => AutoPlannogrammaService::TYPE_SALES,
@@ -1606,83 +1589,101 @@ class CronController extends Controller
                     'subcategory' => null,
                     'species' => null,
                     'plan_date' => $planDate
-                ]);
+                ];
 
+                $forecast = $service->calculateFullForecastForWeek($forecastParams);
                 $writeOffsForecast = $service->getWeeklyProductsWriteoffsForecast($month, $year, $store->id);
-                $forecast = $service->getWeeklyBouquetProductsForecast($month, $year, $store->id);
-
+                $salesForecast = $service->getWeeklyBouquetProductsSalesForecast($month, $year, $store->id);
 
                 $this->stdout("Рассчитана автопланограмма для магазина {$store->name}\n", BaseConsole::FG_GREEN);
-            } catch (\Throwable $e) {
-                $this->stderr("Ошибка при расчёте прогноза: {$e->getMessage()}\n", BaseConsole::FG_RED);
-                Yii::error("Ошибка при расчёте прогноза: " . $e->getMessage(), __METHOD__);
-                continue;
-            }
-            $writeOffsForecastMap = [];
-            foreach ($writeOffsForecast as $itemForecast) {
-                $itemWeek = $itemForecast['week'];
-                $itemGuid = $itemForecast['product_id'];
-                $writeoffsForecast = $itemForecast['forecast'];
-                $writeOffsForecastMap[$itemWeek][$itemGuid] = $writeoffsForecast;
-
-            }
-
-            $existingRecords = [];
-            $records = Autoplannogramma::find()
-                ->where([
-                    'month' => $month,
-                    'year' => $year,
-                    'store_id' => $store->id,
-                ])
-                ->andWhere(['week' => array_unique(array_column($forecast, 'week'))])
-                ->all();
 
-            foreach ($records as $record) {
-                $existingRecords[$record->week . '_' . $record->product_id] = $record;
-            }
-            foreach ($forecast as $item) {
-                $key = $item['week'] . '_' . $item['product_id'];
-                $model = $existingRecords[$key] ?? null;
-                $this->stderr(implode("\n", $item), BaseConsole::FG_GREEN);
-                if (!$model) {
-                    $model = new Autoplannogramma();
-                    $model->month = $month;
-                    $model->year = $year;
-                    $model->week = $item['week'];
-                    $model->product_id = $item['product_id'];
-                    $model->store_id = $item['store_id'];
-                    $model->is_archive = false;
-                    $model->capacity_type = 1;
-                    $model->quantity = $item['forecast_week_pieces'];
-                    if (isset($writeOffsForecastMap[$item['week']]) && isset($writeOffsForecastMap[$item['week']][$item['product_id']])) {
-                        $model->writeoffs_forecast = $writeOffsForecastMap[$item['week']][$item['product_id']];
-                    } else {
-                        $model->writeoffs_forecast = 0;
+                $existingRecords = Autoplannogramma::find()
+                    ->where([
+                        'month' => $month,
+                        'year' => $year,
+                        'store_id' => $store->id,
+                        'week' => array_unique(array_column($forecast, 'week'))
+                    ])
+                    ->indexBy(fn($record) => $record->week . '_' . $record->product_id)
+                    ->all();
+
+                foreach ($forecast as $item) {
+                    $key = $item['week'] . '_' . $item['product_id'];
+                    $model = $existingRecords[$key] ?? new Autoplannogramma();
+                    $productId = $item['product_id'];
+                    $week = $item['week'];
+                    $quantity = (float)($item['forecast_week_pieces'] ?? 0);
+
+                    $this->stderr(implode("\n", $item), BaseConsole::FG_GREEN);
+
+                    $details = [];
+                    $total = $quantity;
+
+                    if (!empty($writeOffsForecast[$productId][$week]['writeOffs'])) {
+                        $writeOffs = $writeOffsForecast[$productId][$week]['writeOffs'];
+                        $details['writeOffs']['groups'] = $writeOffs;
+                        $total += is_array($writeOffs) ? array_sum($writeOffs) : (float)$writeOffs;
                     }
-                }
-
-
 
-                $needsUpdate = $model->quantity_forecast != $item['forecast_week_pieces'];
+                    foreach (['offline', 'online', 'marketplace'] as $type) {
+                        $data = $salesForecast[$productId][$week][$type] ?? null;
+                        if ($data) {
+                            $details[$type]['groups'] = [];
+                            if (isset($data['share'])) {
+                                $share = (float)$data['share'];
+                                $details[$type]['groups']['share'] = $share;
+                                $details[$type]['groups']['quantity'] = round($quantity * $share, 2);
+                                $total += $details[$type]['groups']['quantity'];
+                                unset($data['share']);
+                            }
+                            if (!empty($data)) {
+                                $details[$type]['groups'] = array_merge($details[$type]['groups'], $data);
+                                $total += is_array($data) ? array_sum($data) : (float)$data;
+                            }
+                        }
+                    }
 
-                if ($needsUpdate) {
-                    $model->quantity_forecast = $item['forecast_week_pieces'];
-                }
+                    $needsUpdate = !$model->isNewRecord && (
+                            $model->calculate !== $quantity ||
+                            round((float)$model->total, 2) !== round($total, 2) ||
+                            json_encode($model->details, JSON_UNESCAPED_UNICODE) !== json_encode($details, JSON_UNESCAPED_UNICODE)
+                        );
 
-                if (!$model->save()) {
-                    $errors = [];
-                    foreach ($model->getErrors() as $attr => $attrErrors) {
-                        foreach ($attrErrors as $error) {
-                            $errors[] = "$attr: $error";
+                    if ($model->isNewRecord || $needsUpdate) {
+                        $model->setAttributes([
+                            'month' => $month,
+                            'year' => $year,
+                            'week' => $week,
+                            'product_id' => $productId,
+                            'store_id' => $store->id,
+                            'is_archive' => false,
+                            'capacity_type' => 1,
+                            'details' => json_encode($details, JSON_UNESCAPED_UNICODE),
+                            'calculate' => $quantity,
+                            'modify' => $quantity,
+                            'total' => round($total, 2)
+                        ]);
+
+                        if (!$model->save()) {
+                            $errors = implode('; ', array_map(
+                                fn($attr, $attrErrors) => "$attr: " . implode(', ', $attrErrors),
+                                array_keys($model->getErrors()),
+                                $model->getErrors()
+                            ));
+                            $this->stderr("Ошибка при сохранении модели: $errors\n", BaseConsole::FG_RED);
+                            Yii::error("Ошибка сохранения Autoplannogramma: $errors", __METHOD__);
                         }
                     }
-                    $errorMessage = implode('; ', $errors);
-                    $this->stderr("Ошибка при сохранении модели: $errorMessage\n", BaseConsole::FG_RED);
-                    Yii::error("Ошибка сохранения Autoplannogramma: $errorMessage", __METHOD__);
                 }
+
+                $this->stdout("Сохранена автопланограмма для магазина {$store->name}\n", BaseConsole::FG_GREEN);
+            } catch (Throwable $e) {
+                $this->stderr("Ошибка при расчёте прогноза: {$e->getMessage()}\n", BaseConsole::FG_RED);
+                Yii::error("Ошибка при расчёте прогноза: " . $e->getMessage(), __METHOD__);
+                continue;
             }
-            $this->stdout("Сохранена автопланограмма для магазина {$store->name}\n", BaseConsole::FG_GREEN);
         }
+
         $this->stdout("Расчет и сохранение автопланограммы завершены\n", BaseConsole::FG_GREEN);
     }
 }
index 63c8c87d76f73947400e62466c2937d07b43247d..210fef2bc5e6b21abd3af606e220f0207b6dd1da 100644 (file)
@@ -2,7 +2,6 @@
 
 namespace yii_app\records;
 
-use Product;
 use Yii;
 use yii\behaviors\BlameableBehavior;
 use yii\behaviors\TimestampBehavior;
@@ -18,9 +17,10 @@ use yii\db\Expression;
  * @property string|null $product_id GUID продукта
  * @property int|null $store_id ID магазина
  * @property int|null $capacity_type Тип планограммы
- * @property int|null $quantity Количество
- * @property int|null $quantity_forecast Количество рассчитанное
- * @property int|null $writeoffs_forecast Количество списано
+ * @property float|null $calculate Суммарное расчетное значение
+ * @property float|null $modify Значение проставленное закупщиком
+ * @property float|null $total Расчетное значение продаж
+ * @property array|null $details Детализация итоговой суммы (JSON)
  * @property bool|null $is_archive Архивная ли запись?
  * @property bool|null $auto_forecast Значение спрогнозировано?
  * @property string|null $created_at Дата создания
@@ -30,23 +30,18 @@ use yii\db\Expression;
  */
 class Autoplannogramma extends \yii\db\ActiveRecord
 {
-    /**
-     * {@inheritdoc}
-     */
     public static function tableName()
     {
         return 'autoplannogramma';
     }
 
-    /**
-     * {@inheritdoc}
-     */
     public function rules()
     {
         return [
             [['week', 'month', 'year', 'store_id', 'capacity_type', 'created_by', 'updated_by'], 'integer'],
             [['is_archive', 'auto_forecast'], 'boolean'],
-            [[ 'quantity', 'quantity_forecast', 'writeoffs_forecast'], 'number'],
+            [['calculate', 'modify', 'total'], 'number'],
+            [['details'], 'safe'],
             [['created_at', 'updated_at'], 'safe'],
             [['auto_forecast'], 'default', 'value' => true],
             [['product_id'], 'string', 'max' => 255],
@@ -60,7 +55,7 @@ class Autoplannogramma extends \yii\db\ActiveRecord
                 'class' => TimestampBehavior::class,
                 'createdAtAttribute' => 'created_at',
                 'updatedAtAttribute' => 'updated_at',
-                'value' => new Expression('NOW()')
+                'value' => new Expression('NOW()'),
             ],
             [
                 'class' => BlameableBehavior::class,
@@ -70,9 +65,6 @@ class Autoplannogramma extends \yii\db\ActiveRecord
         ];
     }
 
-    /**
-     * {@inheritdoc}
-     */
     public function attributeLabels()
     {
         return [
@@ -83,9 +75,10 @@ class Autoplannogramma extends \yii\db\ActiveRecord
             'product_id' => 'GUID продукта',
             'store_id' => 'ID магазина',
             'capacity_type' => 'Тип планограммы',
-            'quantity' => 'Количество',
-            'quantity_forecast' => 'Количество рассчитанное',
-            'writeoffs_forecast' => 'Количество списано',
+            'calculate' => 'Суммарное расчетное значение',
+            'modify' => 'Значение проставленное закупщиком',
+            'total' => 'Расчетное значение продаж',
+            'details' => 'Детализация итоговой суммы',
             'is_archive' => 'Архивная ли запись?',
             'auto_forecast' => 'Значение спрогнозировано?',
             'created_at' => 'Дата создания',
@@ -95,7 +88,8 @@ class Autoplannogramma extends \yii\db\ActiveRecord
         ];
     }
 
-    public function getProducts() {
+    public function getProducts()
+    {
         return $this->hasMany(Products1cNomenclature::class, ['id' => 'product_id']);
     }
 }
index 101ce93ec871f3f4499482207fdda128abf118f0..518a4357e636eef5f2e070755fc33e9ea72081d4 100644 (file)
@@ -2795,7 +2795,8 @@ class AutoPlannogrammaService
         return $pricesMap;
     }
 
-    public function getWeeklyBouquetProductsForecast($month, $year, $storeId = null, $weekNumber = null)
+
+    public function getWeeklyBouquetProductsSalesForecast($month, $year, $storeId = null, $weekNumber = null)
     {
         $matrixGroups = ArrayHelper::map(
             MatrixBouquetForecast::find()->select(['group'])->distinct()->asArray()->all(),
@@ -2856,7 +2857,6 @@ class AutoPlannogrammaService
             }
         }
 
-        $grouped = [];
         $grouped = [];
         $salesShares = [];
 
@@ -2865,6 +2865,7 @@ class AutoPlannogrammaService
             ->indexBy('store_id')
             ->asArray()
             ->all();
+
         if ($plans) {
             foreach ($plans as $storeId => $plan) {
                 $total = $plan['total_sales_plan'];
@@ -2882,11 +2883,12 @@ class AutoPlannogrammaService
             $guid = (string)$item['product_guid'];
             $group = (string)$item['matrix_group'];
             $type = (string)$item['type'];
+            $week = (string)$item['week'];
             $forecastValue = (float)$item['week_forecast'];
             if (isset($salesShares[$storeItem]) && isset($salesShares[$storeItem][$type])) {
-                $grouped[$storeItem][$type]['share'] = $salesShares[$storeItem][$type];
+                $grouped[$week][$type]['share'] = $salesShares[$storeItem][$type];
             }
-            $grouped[$storeItem][$guid][$type][$group] = $forecastValue;
+            $grouped[$guid][$week][$type][$group] = $forecastValue;
         }
 
         return $grouped;
@@ -2923,7 +2925,7 @@ class AutoPlannogrammaService
                     'category' => $forecast['category'] ?? '',
                     'subcategory' => $forecast['subcategory'] ?? '',
                     'species' => $forecast['species'] ?? '',
-                    'product_id' => $forecast['product_id'] ?? '',
+                    'product_guid' => $forecast['product_id'] ?? '',
                     'name' => $forecast['name'] ?? '',
                     'price' => $forecast['price'] ?? '',
                     'goal' => $forecast['goal'] ?? 0,
@@ -2933,7 +2935,17 @@ class AutoPlannogrammaService
             }
         }
 
-        return $weeksProductForecast;
+        $grouped = [];
+        foreach ($weeksProductForecast as $item) {
+            $productId = $item['product_guid'];
+            $week = $item['week'];
+            $type = 'writeOffs';
+            $forecastValue = $item['forecast'];
+            $grouped[$productId][$week][$type] = $forecastValue;
+
+        }
+
+        return $grouped;
     }
 
 }
\ No newline at end of file