]> gitweb.erp-flowers.ru Git - erp24_rep/yii-erp24/.git/commitdiff
fix(ERP-292): critical fixes — транзакции, типы, FK drop, use imports, XSS
authorfomichev <vladimir.fomichev@erp-flowers.ru>
Fri, 17 Apr 2026 14:28:33 +0000 (17:28 +0300)
committerfomichev <vladimir.fomichev@erp-flowers.ru>
Fri, 17 Apr 2026 14:28:33 +0000 (17:28 +0300)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
erp24/controllers/AutoMarkController.php
erp24/jobs/AutoMarkPredictionJob.php
erp24/migrations/m260417_000001_create_products_1c_automark_predictions.php
erp24/records/Products1cAutomarkPrediction.php
erp24/services/AutoMarkService.php
erp24/services/automark/RuleBasedParser.php
erp24/services/automark/SimilarityMatcher.php
erp24/views/auto-mark/index.php

index c5d62713aef8e29339016311b233899d807ea779..779a1be668729da824e466672b12ab0d58caad37 100644 (file)
@@ -43,12 +43,20 @@ class AutoMarkController extends Controller
             $service = new AutoMarkService();
 
             if ($action === 'approve') {
-                $prediction->status      = Products1cAutomarkPrediction::STATUS_APPROVED;
-                $prediction->approved_by = Yii::$app->user->id;
-                $prediction->updated_at  = date('Y-m-d H:i:s');
-                $prediction->save();
-                $service->applyApprovedPrediction($prediction->id);
-                Yii::$app->session->setFlash('success', 'Разметка применена.');
+                $transaction = \Yii::$app->db->beginTransaction();
+                try {
+                    $prediction->status      = Products1cAutomarkPrediction::STATUS_APPROVED;
+                    $prediction->approved_by = Yii::$app->user->id;
+                    $prediction->updated_at  = date('Y-m-d H:i:s');
+                    if ($prediction->save()) {
+                        $service->applyApprovedPrediction($prediction->id);
+                    }
+                    $transaction->commit();
+                    Yii::$app->session->setFlash('success', 'Разметка применена.');
+                } catch (\Exception $e) {
+                    $transaction->rollBack();
+                    Yii::$app->session->setFlash('error', 'Ошибка при применении разметки.');
+                }
             } elseif ($action === 'reject') {
                 $prediction->status     = Products1cAutomarkPrediction::STATUS_REJECTED;
                 $prediction->updated_at = date('Y-m-d H:i:s');
index ab21a1c9bd75f449c34888f8a599c8d1d2eb3ffe..4bbd17416e0915a63a6b6a6f691361c199fb63eb 100644 (file)
@@ -4,27 +4,15 @@ declare(strict_types=1);
 
 namespace yii_app\jobs;
 
-use Yii;
 use yii\queue\JobInterface;
 use yii_app\services\AutoMarkService;
 
 class AutoMarkPredictionJob extends \yii\base\BaseObject implements JobInterface
 {
-    public $productId;
+    public string $productId;
 
     public function execute($queue): void
     {
-        $productId = $this->productId;
-
-        try {
-            $service = new AutoMarkService();
-            $service->predictForProduct($productId);
-            Yii::info("AutoMark прогноз успешно обработан для продукта ID {$productId}", 'automark');
-        } catch (\Exception $e) {
-            Yii::error(
-                "Ошибка при обработке AutoMark прогноза для продукта ID {$productId}: " . $e->getMessage(),
-                'automark'
-            );
-        }
+        (new AutoMarkService())->predictForProduct($this->productId);
     }
 }
index c261788aa5986a4c4b4e319310118defafdcb973..376a83b30a6ad97159b02c00bc09883d83ece81a 100644 (file)
@@ -38,6 +38,7 @@ class m260417_000001_create_products_1c_automark_predictions extends Migration
 
     public function safeDown(): void
     {
+        $this->dropForeignKey('fk_automark_product', 'products_1c_automark_predictions');
         $this->dropTable('products_1c_automark_predictions');
     }
 }
index 201c3ac206e4b6a525540e397ca23724a1046b94..8ed27579c8ac209e2e901e629747c9e04ca5fb23 100644 (file)
@@ -5,6 +5,7 @@ declare(strict_types=1);
 namespace yii_app\records;
 
 use yii\db\ActiveQuery;
+use yii_app\records\Products1c;
 
 /**
  * Предсказание авторазметки товара из 1С.
@@ -63,6 +64,11 @@ class Products1cAutomarkPrediction extends \yii\db\ActiveRecord
         return $this->status === self::STATUS_APPROVED;
     }
 
+    public function isRejected(): bool
+    {
+        return $this->status === self::STATUS_REJECTED;
+    }
+
     public function getProduct(): ActiveQuery
     {
         return $this->hasOne(Products1c::class, ['id' => 'product_id']);
index b524372b0cabf5b54db95c2e4a781b31d084fbe7..fe5985dce4e8a75bfe2742033fa600d6832df180 100644 (file)
@@ -4,6 +4,7 @@ declare(strict_types=1);
 
 namespace yii_app\services;
 
+use Yii;
 use yii_app\records\Products1c;
 use yii_app\records\Products1cAutomarkPrediction;
 use yii_app\records\Products1cNomenclature;
@@ -62,28 +63,41 @@ class AutoMarkService
             return false;
         }
 
-        $nomenclature = Products1cNomenclature::findOne($prediction->product_id);
-        if ($nomenclature === null) {
-            $product = Products1c::findOne($prediction->product_id);
-            if ($product === null) {
-                return false;
+        $transaction = Yii::$app->db->beginTransaction();
+        try {
+            $nomenclature = Products1cNomenclature::findOne($prediction->product_id);
+            if ($nomenclature === null) {
+                $product = Products1c::findOne($prediction->product_id);
+                if ($product === null) {
+                    $transaction->rollBack();
+                    return false;
+                }
+                $nomenclature          = new Products1cNomenclature();
+                $nomenclature->id      = $prediction->product_id;
+                $nomenclature->name    = $product->name;
+                $nomenclature->location = '';
+                $nomenclature->type_num = '';
             }
-            $nomenclature          = new Products1cNomenclature();
-            $nomenclature->id      = $prediction->product_id;
-            $nomenclature->name    = $product->name;
-            $nomenclature->location = '';
-            $nomenclature->type_num = '';
-        }
 
-        $nomenclature->category    = $prediction->category    ?? $nomenclature->category;
-        $nomenclature->subcategory = $prediction->subcategory ?? $nomenclature->subcategory;
-        $nomenclature->species     = $prediction->species     ?? $nomenclature->species;
-        $nomenclature->sort        = $prediction->sort        ?? $nomenclature->sort;
-        $nomenclature->type        = $prediction->type        ?? $nomenclature->type;
-        $nomenclature->size        = $prediction->size        ?? $nomenclature->size;
-        $nomenclature->color       = $prediction->color       ?? $nomenclature->color;
+            $nomenclature->category    = $prediction->category    ?? $nomenclature->category;
+            $nomenclature->subcategory = $prediction->subcategory ?? $nomenclature->subcategory;
+            $nomenclature->species     = $prediction->species     ?? $nomenclature->species;
+            $nomenclature->sort        = $prediction->sort        ?? $nomenclature->sort;
+            $nomenclature->type        = $prediction->type        ?? $nomenclature->type;
+            $nomenclature->size        = $prediction->size        ?? $nomenclature->size;
+            $nomenclature->color       = $prediction->color       ?? $nomenclature->color;
+
+            if (!$nomenclature->save()) {
+                $transaction->rollBack();
+                return false;
+            }
 
-        return $nomenclature->save();
+            $transaction->commit();
+            return true;
+        } catch (\Exception $e) {
+            $transaction->rollBack();
+            throw $e;
+        }
     }
 
     /**
index 2b4d08f2fcfc1d927ee957bf9bff0968dfb8af2e..1b104d2d0446bdae9fd7b5d74d74df2064d14f8d 100644 (file)
@@ -4,6 +4,8 @@ declare(strict_types=1);
 
 namespace yii_app\services\automark;
 
+use yii_app\records\Products1cAutomarkPrediction;
+
 class RuleBasedParser
 {
     private const SPECIES_SREZY = [
@@ -66,7 +68,7 @@ class RuleBasedParser
             size:        $size,
             color:       $color,
             confidence:  $confidence,
-            method:      'rule',
+            method:      Products1cAutomarkPrediction::METHOD_RULE,
         );
     }
 
index 4de5e2c50e4aeb7ca387649081c545af2b85bc45..45623d1263ec89f2b4a34e3567145494ba368687 100644 (file)
@@ -4,6 +4,8 @@ declare(strict_types=1);
 
 namespace yii_app\services\automark;
 
+use yii_app\records\Products1cAutomarkPrediction;
+
 class SimilarityMatcher
 {
     private const STOP_WORDS = ['и', 'в', 'на', 'с', 'по', 'для', 'из', 'от', 'до', 'за', 'при', 'под'];
@@ -50,7 +52,7 @@ class SimilarityMatcher
             size:        isset($bestItem['size']) ? (int) $bestItem['size'] : null,
             color:       $bestItem['color'] ?? null,
             confidence:  round($bestScore, 4),
-            method:      'similarity',
+            method:      Products1cAutomarkPrediction::METHOD_SIMILARITY,
         );
     }
 
index 1bf8a9d0e21de1c28f89fa7c3db22ca44e0d258e..4366b8724e6f71f593f1dbb1388f508decba0160 100644 (file)
@@ -21,7 +21,7 @@ $this->title = 'Авторазметка товаров';
             'id',
             [
                 'label' => 'Товар',
-                'value' => fn($m) => $m->product?->name ?? $m->product_id,
+                'value' => fn($m) => Html::encode($m->product?->name ?? $m->product_id),
             ],
             'category',
             'species',