]> gitweb.erp-flowers.ru Git - erp24_rep/yii-erp24/.git/commitdiff
правки по ревью
authorfomichev <vladimir.fomichev@erp-flowers.ru>
Thu, 14 May 2026 10:28:01 +0000 (13:28 +0300)
committerfomichev <vladimir.fomichev@erp-flowers.ru>
Thu, 14 May 2026 10:28:01 +0000 (13:28 +0300)
erp24/commands/Products1cConceptController.php
erp24/controllers/AuthController.php
erp24/controllers/Products1cNomenclatureActualityController.php
erp24/controllers/Products1cNomenclatureMarkupController.php
erp24/views/products1c-nomenclature-actuality/add-activity.php
erp24/views/products1c-nomenclature-actuality/index.php
erp24/web/js/products1cNomenclatureActuality/index.js

index d90ad346195270a6181fe5482fa0c230ac34df09..9a025fdeef129fd6d378d532beede9a4220b6f10 100644 (file)
@@ -39,7 +39,7 @@ class Products1cConceptController extends Controller
 
         $transaction = $db->beginTransaction();
         try {
-            $db->createCommand('TRUNCATE TABLE ' . self::TARGET_TABLE)->execute();
+            $db->createCommand('TRUNCATE TABLE ' . $db->quoteTableName(self::TARGET_TABLE))->execute();
 
             $now = date('Y-m-d H:i:s');
             $inserted = $db->createCommand(<<<SQL
index 7ea8ac501885ba4250bec8e282ef20c1ceeee1ca..78d6591443de3e2cbcf3cb1f9066a5ce99ad2b60 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 
+declare(strict_types=1);
+
 namespace app\controllers;
 
 use Yii;
index 71d086591ad745f527e28aff206e3e34d24b23b9..bce3403ca4fd216869d9f23fd93af5ebd3d7308b 100644 (file)
@@ -59,6 +59,8 @@ class Products1cNomenclatureActualityController extends Controller
                     'class' => VerbFilter::class,
                     'actions' => [
                         'delete'                => ['POST'],
+                        'ajax-delete'           => ['POST'],
+                        'add-activity'          => ['POST'],
                         'ajax-save-interval'    => ['POST'],
                         'ajax-save-assortment'    => ['POST'],
                         'ajax-remove-label'       => ['POST'],
@@ -574,19 +576,25 @@ class Products1cNomenclatureActualityController extends Controller
     {
         $request = Yii::$app->request;
 
-        $historyDays = $request->get('historyDays');
-        $intervalMonths = $request->get('intervalMonths');
-        $startFrom = $request->get('startFrom', date('Y-m-d'));
+        $historyDays    = $request->post('historyDays');
+        $intervalMonths = $request->post('intervalMonths');
+        $startFrom      = $request->post('startFrom', date('Y-m-d'));
 
         if ($historyDays === null || $intervalMonths === null) {
             return $this->render('add-activity', [
-                'historyDays' => $historyDays ?? 14,
-                'intervalMonths' => $intervalMonths ?? 4,
-                'startFrom' => $startFrom,
+                'historyDays'    => 14,
+                'intervalMonths' => 4,
+                'startFrom'      => date('Y-m-d'),
             ]);
         }
 
-        $endDate = date('Y-m-d', strtotime($startFrom));
+        $historyDays    = max(1, min(365, (int)$historyDays));
+        $intervalMonths = max(1, min(24, (int)$intervalMonths));
+        $startFrom      = preg_match('/^\d{4}-\d{2}-\d{2}$/', (string)$startFrom)
+            ? (string)$startFrom
+            : date('Y-m-d');
+
+        $endDate   = $startFrom;
         $startDate = date('Y-m-d', strtotime("-{$historyDays} days", strtotime($endDate)));
 
         $productIds = (new Query())
@@ -1359,24 +1367,23 @@ class Products1cNomenclatureActualityController extends Controller
     {
         Yii::$app->response->format = Response::FORMAT_JSON;
 
-        $request = Yii::$app->request;
-        $id = $request->post('id') ?? $request->get('id');
+        $id = Yii::$app->request->post('id');
 
         if (empty($id)) {
             throw new BadRequestHttpException('Missing parameter: id');
         }
         try {
-            $model = $this->findModel($id);
-            $model->delete();
+            $this->findModel($id)->delete();
 
             return [
                 'success' => true,
                 'message' => 'Запись успешно удалена',
             ];
         } catch (\Throwable $e) {
+            Yii::error($e->getMessage(), __CLASS__);
             return [
                 'success' => false,
-                'message' => $e->getMessage(),
+                'message' => 'Ошибка удаления записи',
             ];
         }
     }
index 1b0b1796d3e5bb083188886b5fe99faa6b5503ad..289b5a0d06bbf9f5cc5bf7b0ef8f82c49ce12ade 100644 (file)
@@ -21,6 +21,14 @@ use yii_app\services\AutoMarkService;
 
 class Products1cNomenclatureMarkupController extends Controller
 {
+    private AutoMarkService $autoMarkService;
+
+    public function init(): void
+    {
+        parent::init();
+        $this->autoMarkService = new AutoMarkService();
+    }
+
     public const STATUS_NOT_NEEDED       = 'not_needed';
     public const STATUS_APPROVED         = 'approved';
     public const STATUS_AUTOMARK_PENDING = 'automark_pending';
@@ -228,9 +236,8 @@ class Products1cNomenclatureMarkupController extends Controller
             return ['success' => false, 'message' => 'Ошибка сохранения предсказания'];
         }
 
-        $service = new AutoMarkService();
         try {
-            if ($service->applyApprovedPrediction($prediction->id)) {
+            if ($this->autoMarkService->applyApprovedPrediction($prediction->id)) {
                 AuditLog::write(
                     Products1cAutomarkPrediction::tableName(),
                     (string)$prediction->id,
@@ -284,9 +291,8 @@ class Products1cNomenclatureMarkupController extends Controller
             return ['success' => false, 'message' => implode(', ', $prediction->getFirstErrors())];
         }
 
-        $service = new AutoMarkService();
         try {
-            if ($service->applyApprovedPrediction($prediction->id)) {
+            if ($this->autoMarkService->applyApprovedPrediction($prediction->id)) {
                 AuditLog::write(
                     Products1cAutomarkPrediction::tableName(),
                     (string)$prediction->id,
@@ -320,37 +326,45 @@ class Products1cNomenclatureMarkupController extends Controller
             ->where(['id' => $ids, 'status' => Products1cAutomarkPrediction::STATUS_PENDING])
             ->all();
 
-        $service = new AutoMarkService();
-        $now     = date('Y-m-d H:i:s');
-        $userId  = (int)Yii::$app->user->id;
+        $now    = date('Y-m-d H:i:s');
+        $userId = (int)Yii::$app->user->id;
         $applied = 0;
 
-        foreach ($predictions as $prediction) {
-            $prediction->status      = Products1cAutomarkPrediction::STATUS_APPROVED;
-            $prediction->approved_by = $userId;
-            $prediction->updated_at  = $now;
-
-            if (!$prediction->save()) {
-                continue;
-            }
+        $transaction = Yii::$app->db->beginTransaction();
+        try {
+            foreach ($predictions as $prediction) {
+                $prediction->status      = Products1cAutomarkPrediction::STATUS_APPROVED;
+                $prediction->approved_by = $userId;
+                $prediction->updated_at  = $now;
+
+                if (!$prediction->save()) {
+                    $transaction->rollBack();
+                    return ['success' => false, 'message' => 'Ошибка сохранения предсказания #' . $prediction->id];
+                }
 
-            try {
-                if ($service->applyApprovedPrediction($prediction->id)) {
+                if ($this->autoMarkService->applyApprovedPrediction($prediction->id)) {
                     $applied++;
+                } else {
+                    $transaction->rollBack();
+                    return ['success' => false, 'message' => 'Ошибка применения разметки для товара #' . $prediction->product_id];
                 }
-            } catch (\Exception $e) {
-                Yii::error($e->getMessage(), __CLASS__);
             }
-        }
 
-        if ($applied > 0) {
-            AuditLog::write(
-                Products1cAutomarkPrediction::tableName(),
-                null,
-                AuditLog::ACTION_BULK_UPDATE,
-                ['status' => Products1cAutomarkPrediction::STATUS_PENDING],
-                ['status' => Products1cAutomarkPrediction::STATUS_APPROVED, 'approved_by' => $userId, 'entity_ids' => $ids],
-            );
+            if ($applied > 0) {
+                AuditLog::write(
+                    Products1cAutomarkPrediction::tableName(),
+                    null,
+                    AuditLog::ACTION_BULK_UPDATE,
+                    ['status' => Products1cAutomarkPrediction::STATUS_PENDING],
+                    ['status' => Products1cAutomarkPrediction::STATUS_APPROVED, 'approved_by' => $userId, 'entity_ids' => $ids],
+                );
+            }
+
+            $transaction->commit();
+        } catch (\Exception $e) {
+            $transaction->rollBack();
+            Yii::error($e->getMessage(), __CLASS__);
+            return ['success' => false, 'message' => 'Ошибка пакетного подтверждения'];
         }
 
         return [
index a8e038f11fe95b96dbeccedeac55a7d4c4b6612b..a86ea244bf58a5b580258470db45621217e7362e 100644 (file)
@@ -5,7 +5,6 @@
 /** @var int $intervalMonths */
 /* @var string $startFrom  */
 use dosamigos\datepicker\DatePicker;
-use yii\base\DynamicModel;
 use yii\helpers\Html;
 use kartik\form\ActiveForm;
 
@@ -28,23 +27,22 @@ $this->title = 'Заполнить актуальность товаров по
     </ul>
 
     <?php $form = ActiveForm::begin([
-        'method'=>'get',
-        'action'=>['add-activity'],
+        'method' => 'post',
+        'action' => ['add-activity'],
     ]); ?>
     <?= Html::label('Дата отсчета для расчета актуальности', 'startFrom') ?>
     <?= DatePicker::widget([
         'name' => 'startFrom',
         'id' => 'startFrom',
-        'value' => date('d-m-Y'),
+        'value' => date('Y-m-d'),
         'template' => '{addon}{input}',
         'language' => 'ru',
         'clientOptions' => [
             'autoclose' => true,
-            'format' => 'dd-mm-yyyy',
-            'todayBtn' => true
-        ],
-        'clientEvents' => [
+            'format' => 'yyyy-mm-dd',
+            'todayBtn' => true,
         ],
+        'clientEvents' => [],
         'containerOptions' => ['class' => 'mb-4'],
     ]) ?>
 
index de6a81746cc991faf0a02b7f73bdc422bc12e823..91cd77b013e04592f48c533764d25599bfbaa1d7 100644 (file)
@@ -483,6 +483,7 @@ function actConceptChips(array $names): string
                             <div class="text-center py-3"><span class="spinner-border spinner-border-sm"></span> Загрузка...</div>
                         </div>
                         <div id="intervalForm" class="mt-3 p-3 border rounded bg-light" style="display:none">
+                            <div id="intervalAlert" class="alert d-none py-2 mb-2 small" role="alert"></div>
                             <input type="hidden" id="intervalId">
                             <div class="row g-2 align-items-end">
                                 <div class="col">
@@ -547,7 +548,7 @@ function actConceptChips(array $names): string
 </div>
 
 <!-- Toast container -->
-<div id="toastContainer" class="position-fixed top-0 end-0 p-3" style="z-index:9999"></div>
+<div id="toastContainer" class="position-fixed end-0 p-3" style="z-index:1100000;top:64px"></div>
 
 <script>
 window.productActualityConfig = {
index 67a8214a057db74e0a476899de53a2648a9d7918..4725f94f3e33f3c72351662d385788b7ee06173d 100644 (file)
@@ -1,5 +1,12 @@
 document.addEventListener('DOMContentLoaded', () => {
 
+    // Выносим контейнер тостов прямо в <body>, чтобы он не был ограничен
+    // stacking context Yii2-лейаута (transform/will-change на обёртках)
+    const toastEl = document.getElementById('toastContainer');
+    if (toastEl && toastEl.parentElement !== document.body) {
+        document.body.appendChild(toastEl);
+    }
+
     const esc = s => s == null ? '' : String(s)
         .replace(/&/g, '&amp;').replace(/</g, '&lt;')
         .replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
@@ -60,6 +67,19 @@ document.addEventListener('DOMContentLoaded', () => {
         el.addEventListener('hidden.bs.toast', () => el.remove());
     }
 
+    function showModalAlert(message, type = 'danger') {
+        const $a = $('#intervalAlert');
+        $a.removeClass('alert-danger alert-success')
+          .addClass('alert-' + type)
+          .text(message)
+          .removeClass('d-none');
+        $a[0]?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
+    }
+
+    function clearModalAlert() {
+        $('#intervalAlert').addClass('d-none').text('');
+    }
+
     // ─── Chips helpers ──────────────────────────────────────────────────────
 
     function renderIntervalChips(intervals) {
@@ -500,6 +520,7 @@ document.addEventListener('DOMContentLoaded', () => {
         const name  = $(this).data('name');
         $('#modalProductName').text(name);
         $intervalForm.hide();
+        clearModalAlert();
         $intervalId.val('');
         bootstrap.Tab.getOrCreateInstance(document.getElementById('tab-intervals-btn')).show();
         $('#addIntervalBtn').removeClass('d-none');
@@ -572,6 +593,7 @@ document.addEventListener('DOMContentLoaded', () => {
 
     $('#addIntervalBtn').on('click', function () {
         $intervalId.val('');
+        clearModalAlert();
         initMonthSelects();
         $intervalForm.show();
         $intervalForm[0].scrollIntoView({ behavior: 'smooth' });
@@ -582,6 +604,7 @@ document.addEventListener('DOMContentLoaded', () => {
         const from = $(this).data('from');
         const to   = $(this).data('to');
         $intervalId.val(id);
+        clearModalAlert();
         initMonthSelects(from, to);
         $intervalForm.show();
         $intervalForm[0].scrollIntoView({ behavior: 'smooth' });
@@ -589,6 +612,7 @@ document.addEventListener('DOMContentLoaded', () => {
 
     $('#cancelIntervalBtn').on('click', function () {
         $intervalForm.hide();
+        clearModalAlert();
         $intervalId.val('');
     });
 
@@ -598,7 +622,9 @@ document.addEventListener('DOMContentLoaded', () => {
         const to   = $toSel.val();
 
         if (!from || !to) {
-            showToast('Заполните начало и окончание интервала');
+            const msg = 'Заполните начало и окончание интервала';
+            showToast(msg);
+            showModalAlert(msg);
             return;
         }
 
@@ -611,18 +637,30 @@ document.addEventListener('DOMContentLoaded', () => {
         if (id) data.id = id;
 
         $(this).prop('disabled', true).text('Сохранение...');
+        clearModalAlert();
 
         $.post(urls.saveInterval, data, res => {
             if (res.success) {
-                showToast('Сохранено', 'success');
-                $intervalForm.hide();
+                showModalAlert('Интервал сохранён', 'success');
                 $intervalId.val('');
                 loadIntervals(currentGuid);
+                setTimeout(() => {
+                    $intervalForm.hide();
+                    clearModalAlert();
+                }, 1200);
             } else {
-                showToast(res.message || 'Ошибка сохранения');
+                const msg = res.message || 'Ошибка сохранения';
+                showToast(msg);
+                showModalAlert(msg);
             }
-        }).fail(() => {
-            showToast('Ошибка запроса');
+        }).fail((xhr) => {
+            let msg = 'Ошибка запроса';
+            try {
+                const r = JSON.parse(xhr.responseText);
+                if (r.message) msg = r.message;
+            } catch (e) {}
+            showToast(msg);
+            showModalAlert(msg);
         }).always(() => {
             $('#saveIntervalBtn').prop('disabled', false).text('Сохранить');
         });