From: fomichev Date: Tue, 1 Jul 2025 07:32:48 +0000 (+0300) Subject: Настройка в кроне X-Git-Url: https://gitweb.erp-flowers.ru/?a=commitdiff_plain;h=5918e94cff6d2207dcd0f4f558f802bc7195961f;p=erp24_rep%2Fyii-erp24%2F.git Настройка в кроне --- diff --git a/erp24/commands/CronController.php b/erp24/commands/CronController.php index 562b4b79..e1eb5e25 100644 --- a/erp24/commands/CronController.php +++ b/erp24/commands/CronController.php @@ -1746,176 +1746,192 @@ class CronController extends Controller $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; } diff --git a/erp24/controllers/CategoryPlanController.php b/erp24/controllers/CategoryPlanController.php index 2dbdb248..ef87bdc5 100644 --- a/erp24/controllers/CategoryPlanController.php +++ b/erp24/controllers/CategoryPlanController.php @@ -445,8 +445,8 @@ class CategoryPlanController extends Controller { $service = new AutoPlannogrammaService(); $isEditable = date($model->year . '-' . $model->month . '-d') > date('Y-m-d') && ( - (date('d') < 31) || (date('Y-m-d', strtotime('-1 month', strtotime(date($model->year . '-' . $model->month . '-d')))) > date('Y-m-d'))); - + (date('d') < 27) || (date('Y-m-d', strtotime('-1 month', strtotime(date($model->year . '-' . $model->month . '-d')))) > date('Y-m-d'))); + $isEditable = true; $categoryPlan = CategoryPlan::find()->where(['year' => $model->year, 'month' => $model->month, 'store_id' => $model->store_id])->indexBy('category')->asArray()->all(); $types = []; $table = []; diff --git a/erp24/views/category-plan/new.php b/erp24/views/category-plan/new.php index 884f54ef..434ed740 100644 --- a/erp24/views/category-plan/new.php +++ b/erp24/views/category-plan/new.php @@ -171,17 +171,7 @@ input[readonly] { ], ]) ?> - $model->year, - 'month' => $model->month, - 'store_id' => $model->store_id, - ], - [ - 'class'=>'btn btn-success', - 'data-confirm'=>'Запустить пересчет?' - ] - ) */?> + 'btn btn-success ms-2', 'disabled' => true, diff --git a/erp24/web/js/category-plan/index.js b/erp24/web/js/category-plan/index.js index 9b71df24..5c58bb1e 100644 --- a/erp24/web/js/category-plan/index.js +++ b/erp24/web/js/category-plan/index.js @@ -88,11 +88,12 @@ function editProcent(zis) { if (!changes[store_id]) { changes[store_id] = {}; } - changes[store_id][type] = { - offline: offlineVal, - internet_shop: internetVal, - write_offs: writeoffsVal - }; + if (!changes[store_id][type]) { + changes[store_id][type] = {}; + } + + changes[store_id][type][map.inputName] = currentVal; + localStorage.setItem('planChanges', JSON.stringify(changes, null, 2)); updateChangesLog(store_id); $('#rebuild').prop('disabled', false); @@ -125,7 +126,7 @@ function updateChangesLog(store_id) { }); changesBox.html(``).hide(); - changesCount.html(`Изменения - ${count} `); + changesCount.html(`Были внесены изменения (число изменений) - ${count} `); $('#show-changes').on('click', (event) => { event.preventDefault(); @@ -148,7 +149,8 @@ $(document).ready(() => { updateChangesLog(store_id); $('#rebuild').prop('disabled', false); $('#changes-hint').remove(); - $('#changes').after('
Пересчитайте планограмму после внесения изменений
'); + $('#changes').after('
После внесения всех изменений нажмите на кнопку\n' + + '"Пересчитать автопланограмму"
'); } $('#delete').on('click', function () {