$statusesData = [];
foreach ($statuses as $status) {
/* @var $status MarketplaceOrder1cStatuses */
- $statusesData []= [
+ $relftions = $status->relationsFrom;
+ $relationsToSend = [];
+ foreach ($relftions as $relation) {
+ $statusTo = MarketplaceOrder1cStatuses::find()->where(['id' => $relation['status_id_to']])->one();
+ if(!$statusTo) {
+ continue;
+ }
+ $relationsToSend[] =
+ $statusTo->status_id;
+ }
+ $statusesData [] = [
+ 'index_number' => $status->posit,
'status_name' => $status->status,
'hint' => $status->status_instruction,
'status_id' => $status->status_id,
return ExitCode::OK;
}
- $total = (float) sprintf('%.2f', $total);
+ public function actionAutoplannogrammaCalculate(): void
+ {
+ $date = new DateTime();
+ $date->modify('+2 months');
+ $planDate = $date->format('Y-m-01');
+ $month = (int)$date->format('m');
+ $year = (int)$date->format('Y');
+
+ $service = new AutoPlannogrammaService();
+ $stores = CityStore::find()->where(['visible' => CityStore::IS_VISIBLE])->all();
+
+ $this->stdout("Начало расчетов автопланограммы для $planDate\n", BaseConsole::FG_GREEN);
+
+ foreach ($stores as $store) {
+ $this->stdout("Начало расчетов автопланограммы для магазина ID: {$store->id} ({$store->name})\n", BaseConsole::FG_YELLOW);
+
+ try {
+ $forecastParams = [
+ 'month' => $month,
+ 'year' => $year,
+ 'type' => AutoPlannogrammaService::TYPE_SALES,
+ 'store_id' => $store->id,
+ 'category' => null,
+ 'subcategory' => null,
+ 'species' => null,
+ 'plan_date' => $planDate
+ ];
+
+ $forecast = $service->calculateFullForecastForWeek($forecastParams);
+ $writeOffsForecast = $service->getWeeklyProductsWriteoffsForecast($month, $year, $forecast, $store->id);
+ $salesForecast = $service->getWeeklyBouquetProductsSalesForecast($month, $year, $store->id);
+
+ $this->stdout("Рассчитана автопланограмма для магазина {$store->name}\n", BaseConsole::FG_GREEN);
+
+ $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);
+
+ $details = [];
+ $total = $quantity;
+
+ if (!empty($writeOffsForecast[$productId][$week]['writeOffs'])) {
+ $writeOffs = $writeOffsForecast[$productId][$week]['writeOffs'];
+ $details['writeOffs']['quantity'] = $writeOffs;
+ $total += is_array($writeOffs) ? array_sum($writeOffs) : (float)$writeOffs;
+ }
+
+ foreach (['offline', 'online', 'marketplace'] as $type) {
+ if (!isset($salesForecast[$store->id][$productId][$type])) {
+ $details[$type] = ['share' => 0, 'quantity' => 0, 'groups' => []];
+ continue;
+ }
+ $data = $salesForecast[$store->id][$productId][$type];
+ if (!is_array($data)) {
+ $details[$type] = ['share' => 0, 'quantity' => 0, 'groups' => []];
+ continue;
+ }
+ $block = [];
+ $groups = [];
+ $share = isset($salesForecast[$store->id][$type]['share']) ? (float)$salesForecast[$store->id][$type]['share'] : 0;
+ $block['share'] = $share;
+ $block['quantity'] = (float) sprintf('%.2f', round($quantity * $share, 2)); // Нормализует -0 до 0
+ $total += $block['quantity'];
+ foreach ($data as $k => $v) {
+ $val = (float)$v;
+ $groups[$k] = $val;
+ $total += $val;
+ }
+ $block['groups'] = !empty($groups) ? $groups : [];
+ $details[$type] = $block;
+ }
+
+ $details['forecast'] = ['quantity' => $quantity];
+
++ $total = (float) sprintf('%.2f', $total);
+
+ $needsUpdate = !$model->isNewRecord && (
+ $model->calculate !== $quantity ||
+ ceil((float)$model->total) !== ceil($total) ||
+ json_encode($model->details, JSON_UNESCAPED_UNICODE) !== json_encode($details, JSON_UNESCAPED_UNICODE)
+ );
+
+ 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' => ceil($total),
+ 'total' => ceil($total)
+ ]);
+
+ 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__);
+ }
+ }
+ }
+
+ $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("Расчет и сохранение автопланограммы завершены\n", BaseConsole::FG_GREEN);
+ }
}