$cache = Yii::$app->cache;
$task = $cache->get($cacheKey);
- $this->stdout(json_encode($task, JSON_UNESCAPED_UNICODE));
+ //$this->stdout(json_encode($task, JSON_UNESCAPED_UNICODE));
if (!is_array($task) || empty($task['taskName']) || $task['taskName'] !== 'apRecalculateTask') {
- $this->stdout("No pending apRecalculateTask\n");
+ $this->stdout("Нет задания apRecalculateTask\n");
return ExitCode::OK;
}
- if (!isset($task['status'])) {
- $task['status'] = 'pending';
- }
+
if ($task['status'] === 'running') {
- $this->stdout("Task already running since {$task['startTime']}\n");
+ $this->stdout("Задача уже запущена {$task['startTime']}\n");
return ExitCode::OK;
}
-
foreach (['year','month','storeId','startTime'] as $key) {
if (empty($task[$key])) {
- $this->stdout("Invalid task payload: missing {$key}\n");
+ $this->stdout("Недопустимые парамеры: отстутствует {$key}\n");
$task['status'] = 'error';
- $task['error'] = "Missing {$key}";
+ $task['error'] = "Отстутствует {$key}";
$cache->set($cacheKey, $task);
return ExitCode::UNSPECIFIED_ERROR;
}
}
-
- if (abs(time() - strtotime($task['startTime'])) > 86400) {
- $this->stdout("Task startTime is out of allowed window\n");
- $task['status'] = 'error';
- $task['error'] = 'Invalid startTime';
+ if (isset($task['status']) && $task['status'] === 'pending') {
+ $task['status'] = 'running';
$cache->set($cacheKey, $task);
- return ExitCode::UNSPECIFIED_ERROR;
- }
-
- $task['status'] = 'running';
- $cache->set($cacheKey, $task);
-
- // 4) Логируем старт
- $log = new ScriptLauncherLog();
- $log->source = 'CronController';
- $log->category = 'autoplannogramma';
- $log->prefix = 'actionAutoplannogrammaRecalculate';
- $log->name = 'taskApRecalculate';
- $log->context = json_encode($task, JSON_UNESCAPED_UNICODE);
- $log->year = (int)$task['year'];
- $log->month = (int)$task['month'];
- $log->active = 1;
- $log->date_start = date('Y-m-d H:i:s');
- $log->save(false);
- try {
+ $log = new ScriptLauncherLog();
+ $log->source = 'CronController';
+ $log->category = 'autoplannogramma';
+ $log->prefix = 'actionAutoplannogrammaRecalculate';
+ $log->name = 'taskApRecalculate';
+ $log->context = json_encode($task, JSON_UNESCAPED_UNICODE);
+ $log->year = (int)$task['year'];
+ $log->month = (int)$task['month'];
+ $log->active = 1;
+ $log->date_start = date('Y-m-d H:i:s');
+ $log->save(false);
- $year = (int)$task['year'];
- $month = (int)$task['month'];
+ $service = new AutoPlannogrammaService();
+ $year = (int)$task['year'];
+ $month = (int)$task['month'];
$storeId = (int)$task['storeId'];
+ $planDate = date('Y-m-01', strtotime($year . '-' . $month . '-01'));
+ $this->stdout("Начало расчетов автопланограммы для $month $year и магазина $storeId\n", BaseConsole::FG_GREEN);
+ try {
+ $forecastParams = [
+ 'month' => $month,
+ 'year' => $year,
+ 'type' => AutoPlannogrammaService::TYPE_SALES,
+ 'store_id' => $storeId,
+ 'category' => null,
+ 'subcategory' => null,
+ 'species' => null,
+ 'plan_date' => $planDate
+ ];
- $date = (new DateTime())->setDate($year, $month, 1);
- $planDate = $date->format('Y-m-01');
+ $forecast = $service->calculateFullForecastForWeek($forecastParams);
+ $writeOffsForecast = $service->getWeeklyProductsWriteoffsForecast($month, $year, $forecast, $storeId);
+ $salesForecast = $service->getWeeklyBouquetProductsSalesForecast($month, $year, $storeId);
- $service = new AutoPlannogrammaService();
+ $this->stdout("Рассчитана автопланограмма для магазина {$storeId}\n", BaseConsole::FG_GREEN);
+ $existingRecords = Autoplannogramma::find()
+ ->where([
+ 'month' => $month,
+ 'year' => $year,
+ 'store_id' => $storeId,
+ 'week' => array_unique(array_column($forecast, 'week'))
+ ])
+ ->indexBy(fn($record) => $record->week . '_' . $record->product_id)
+ ->all();
- $forecast = $service->calculateFullForecastForWeek([
- 'year' => $year,
- 'month' => $month,
- 'type' => AutoPlannogrammaService::TYPE_SALES,
- 'store_id' => $storeId,
- 'plan_date'=> $planDate,
- ]);
- $writeOffsF = $service->getWeeklyProductsWriteoffsForecast($month, $year, $forecast, $storeId);
- $salesForecast = $service->getWeeklyBouquetProductsSalesForecast($month, $year, $storeId);
+ 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;
- $existing = Autoplannogramma::find()
- ->where([
- 'store_id' => $storeId,
- 'year' => $year,
- 'month' => $month,
- 'week' => array_unique(ArrayHelper::getColumn($forecast, 'week')),
- ])
- ->indexBy(fn($m) => $m->week . '_' . $m->product_id)
- ->all();
+ 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[$storeId][$productId][$type])) {
+ $details[$type] = ['share' => 0, 'quantity' => 0, 'groups' => []];
+ continue;
+ }
+ $data = $salesForecast[$storeId][$productId][$type];
+ if (!is_array($data)) {
+ $details[$type] = ['share' => 0, 'quantity' => 0, 'groups' => []];
+ continue;
+ }
+ $block = [];
+ $groups = [];
+ $share = isset($salesForecast[$storeId][$type]['share']) ? (float)$salesForecast[$storeId][$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;
+ }
- foreach ($forecast as $item) {
- $key = $item['week'] . '_' . $item['product_id'];
- $model = $existing[$key] ?? new Autoplannogramma();
- $quantity = (float)($item['forecast_week_pieces'] ?? 0);
- $details = [];
- $total = $quantity;
+ $details['forecast'] = ['quantity' => $quantity];
+ $total = (float)sprintf('%.2f', $total);
- if (!empty($writeOffsF[$item['product_id']][$item['week']]['writeOffs'])) {
- $w = $writeOffsF[$item['product_id']][$item['week']]['writeOffs'];
- $details['writeOffs']['quantity'] = $w;
- $total += is_array($w) ? array_sum($w) : (float)$w;
- }
+ $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' => $storeId,
+ 'is_archive' => false,
+ 'capacity_type' => 1,
+ 'details' => json_encode($details, JSON_UNESCAPED_UNICODE),
+ 'calculate' => $quantity,
+ 'modify' => ceil($total),
+ 'total' => ceil($total)
+ ]);
- foreach (['offline','online','marketplace'] as $t) {
- $block = ['share'=>0,'quantity'=>0,'groups'=>[]];
- if (!empty($salesForecast[$storeId][$item['product_id']][$t])) {
- $share = $salesForecast[$storeId][$t]['share'] ?? 0;
- $block['share'] = $share;
- $block['quantity'] = round($quantity * $share, 2);
- $total += $block['quantity'];
- foreach ($salesForecast[$storeId][$item['product_id']][$t] as $k=>$v) {
- $block['groups'][$k] = (float)$v;
- $total += (float)$v;
+ 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__);
}
}
- $details[$t] = $block;
}
- $details['forecast'] = ['quantity' => $quantity];
- $total = (float) sprintf('%.2f', $total);
-
- $needsUpdate = $model->isNewRecord
- || $model->calculate != $quantity
- || ceil($model->total) != ceil($total)
- || json_encode($model->details, JSON_UNESCAPED_UNICODE)
- !== json_encode($details, JSON_UNESCAPED_UNICODE);
-
- if ($needsUpdate) {
- $model->setAttributes([
- 'year' => $year,
- 'month' => $month,
- 'week' => $item['week'],
- 'product_id' => $item['product_id'],
- 'store_id' => $storeId,
- 'is_archive' => false,
- 'capacity_type' => 1,
- 'details' => json_encode($details, JSON_UNESCAPED_UNICODE),
- 'calculate' => $quantity,
- 'modify' => ceil($total),
- 'total' => ceil($total),
- ]);
- if (!$model->save()) {
- throw new \RuntimeException('Save failed: ' . json_encode($model->getErrors(), JSON_UNESCAPED_UNICODE));
- }
- }
- }
+ $this->stdout("Сохранена автопланограмма для магазина {$storeId}\n", BaseConsole::FG_GREEN);
- $task['status'] = 'done';
- $task['progress'] = 100;
- $cache->set($cacheKey, $task);
- $log->message = 'Finished successfully';
- $log->progress = 100;
- $log->active = 0;
- $log->date_finish = date('Y-m-d H:i:s');
- $log->status = 1;
- $log->save(false);
+ $task['status'] = 'done';
+ $task['progress'] = 100;
+ $cache->set($cacheKey, $task);
- $this->stdout("Recalculate complete for store {$storeId}\n");
- return ExitCode::OK;
+ $log->message = 'Finished successfully';
+ $log->progress = 100;
+ $log->active = 0;
+ $log->date_finish = date('Y-m-d H:i:s');
+ $log->status = 1;
+ $log->save(false);
- } catch (\Throwable $e) {
- $task['status'] = 'error';
- $task['error'] = $e->getMessage();
- $cache->set($cacheKey, $task);
+ $this->stdout("Расчет для магазина {$storeId} закончен\n");
+ return ExitCode::OK;
- $log->message = 'Error: ' . $e->getMessage();
- $log->error_count = 1;
- $log->error_message= $e->getMessage();
- $log->active = 0;
- $log->date_finish = date('Y-m-d H:i:s');
- $log->status = 2;
- $log->save(false);
+ } catch (\Throwable $e) {
+
+ $task['status'] = 'error';
+ $task['error'] = $e->getMessage();
+ $cache->set($cacheKey, $task);
- $this->stderr("Error during recalc: {$e->getMessage()}\n");
- return ExitCode::UNSPECIFIED_ERROR;
+ $log->message = 'Error: ' . $e->getMessage();
+ $log->error_count = 1;
+ $log->error_message = $e->getMessage();
+ $log->active = 0;
+ $log->date_finish = date('Y-m-d H:i:s');
+ $log->status = 2;
+ $log->save(false);
+
+ $this->stderr("Ошибка в ходе расчета: {$e->getMessage()}\n");
+ return ExitCode::UNSPECIFIED_ERROR;
+ }
+ } else {
+ $this->stdout("Нет задач ожидающих выполнения\n", BaseConsole::FG_RED);
}
+ return ExitCode::OK;
}