]> gitweb.erp-flowers.ru Git - erp24_rep/yii-erp24/.git/commitdiff
Настройка в кроне
authorfomichev <vladimir.fomichev@erp-flowers.ru>
Tue, 1 Jul 2025 07:32:48 +0000 (10:32 +0300)
committerfomichev <vladimir.fomichev@erp-flowers.ru>
Tue, 1 Jul 2025 07:32:48 +0000 (10:32 +0300)
erp24/commands/CronController.php
erp24/controllers/CategoryPlanController.php
erp24/views/category-plan/new.php
erp24/web/js/category-plan/index.js

index 562b4b79f04f126a5be9230ad8bab4159c7cb72d..e1eb5e25b76e196a95928a8dfc773b5b9ee7a270 100644 (file)
@@ -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;
     }
 
 
index 2dbdb248fc214bf196f0239787eb49818c41c3ea..ef87bdc5615054f597c5528dee7f9a0e8ff7e971 100644 (file)
@@ -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 = [];
index 884f54ef7ac88b4362ea8e0c92d73d581d5ab595..434ed740bf231e13a3ac1aec15bbaeb5b0e6a7bd 100644 (file)
@@ -171,17 +171,7 @@ input[readonly] {
                         ],
                     ])
                     ?>
-                       <!-- --><?php /*= Html::a('Пересчитать автопланограмму', [
-                            'rebuild',
-                            'year'     => $model->year,
-                            'month'    => $model->month,
-                            'store_id' => $model->store_id,
-                        ],
-                            [
-                                'class'=>'btn btn-success',
-                                'data-confirm'=>'Запустить пересчет?'
-                            ]
-                        ) */?>
+
                     <?= Html::submitButton('Пересчитать автопланограмму', [
                         'class' => 'btn btn-success ms-2',
                         'disabled' => true,
index 9b71df241e8f2482c6947fcd52aad1837621ccef..5c58bb1e3e5cdd05fcfcbf388ba1c9ae52424fa5 100644 (file)
@@ -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(`<ul>${listItems}</ul>`).hide();
-    changesCount.html(`Ð\98зменениÑ\8f - ${count} <button id="show-changes">Подробнее</button>`);
+    changesCount.html(`Ð\91Ñ\8bли Ð²Ð½ÐµÑ\81енÑ\8b Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ\8f (Ñ\87иÑ\81ло Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ð¹) - ${count} <button id="show-changes" class="btn btn-link">Подробнее</button>`);
 
     $('#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('<div id="changes-hint" style="color:red; margin-top:10px;">Пересчитайте планограмму после внесения изменений</div>');
+        $('#changes').after('<div id="changes-hint" style="color:red; margin-top:10px;">После внесения всех изменений нажмите на кнопку\n' +
+            '"Пересчитать автопланограмму"</div>');
     }
 
     $('#delete').on('click', function () {