From: marina Date: Wed, 18 Jun 2025 12:19:23 +0000 (+0300) Subject: ERP-360 Сборка страницы автопм X-Git-Url: https://gitweb.erp-flowers.ru/?a=commitdiff_plain;h=dc4cf7417aa470c7501f9adca8a449821e04a812;p=erp24_rep%2Fyii-erp24%2F.git ERP-360 Сборка страницы автопм --- diff --git a/erp24/commands/CronController.php b/erp24/commands/CronController.php index 2424c57f..bdc52b33 100644 --- a/erp24/commands/CronController.php +++ b/erp24/commands/CronController.php @@ -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); } } diff --git a/erp24/records/Autoplannogramma.php b/erp24/records/Autoplannogramma.php index 63c8c87d..210fef2b 100644 --- a/erp24/records/Autoplannogramma.php +++ b/erp24/records/Autoplannogramma.php @@ -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']); } } diff --git a/erp24/services/AutoPlannogrammaService.php b/erp24/services/AutoPlannogrammaService.php index 101ce93e..518a4357 100644 --- a/erp24/services/AutoPlannogrammaService.php +++ b/erp24/services/AutoPlannogrammaService.php @@ -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