]> gitweb.erp-flowers.ru Git - erp24_rep/yii-erp24/.git/commitdiff
Управление порядком
authorfomichev <vladimir.fomichev@erp-flowers.ru>
Thu, 5 Jun 2025 09:57:14 +0000 (12:57 +0300)
committerfomichev <vladimir.fomichev@erp-flowers.ru>
Thu, 5 Jun 2025 09:57:14 +0000 (12:57 +0300)
erp24/controllers/crud/MarketplaceOrder1cStatusesController.php
erp24/migrations/m250604_141110_create_marketplace_order_1c_statuses_relations_table.php
erp24/records/MarketplaceOrder1cStatusesRelations.php
erp24/views/crud/marketplace-order-1c-statuses/_form.php
erp24/web/js/crud/marketplace-order1c-statuses/index.js

index 8696b4b08f92dea85d7539dcdb5591d394231b88..e6908e4e0201557333caf460ae91ed76efa862c5 100644 (file)
@@ -95,18 +95,22 @@ class MarketplaceOrder1cStatusesController extends Controller
         $model = new MarketplaceOrder1cStatuses();
 
         $relationModels = [new MarketplaceOrder1cStatusesRelations()];
-
-        $statusOptions = MarketplaceOrder1cStatuses::find()
-            ->select(['status'])
-            ->indexBy('id')
-            ->column();
+        $allStatuses = MarketplaceOrder1cStatuses::find()
+            ->select(['id','status','marketplace_id'])
+            ->asArray()
+            ->all();
+        $statusOptionsByMarketplace = [];
+        foreach ($allStatuses as $row) {
+            $mp = (int)$row['marketplace_id'];
+            $statusOptionsByMarketplace[$mp][$row['id']] = $row['status'];
+        }
 
         if (Yii::$app->request->isPost) {
             if ($model->load(Yii::$app->request->post())) {
                 $transaction = Yii::$app->db->beginTransaction(Transaction::SERIALIZABLE);
                 try {
                     if (!$model->save()) {
-                        throw new \Exception('Не удалось сохранить MarketplaceOrder1cStatuses');
+                        throw new \Exception('Не удалось сохранить основной статус');
                     }
 
                     $postRelations = Yii::$app->request->post('Relations', []);
@@ -118,12 +122,15 @@ class MarketplaceOrder1cStatusesController extends Controller
 
                         $rel = new MarketplaceOrder1cStatusesRelations();
                         $rel->status_id_from = $model->id;
-                        $rel->status_id_to = $relData['status_id_to'];
-                        $rel->description   = $relData['description'];
-                        $rel->button_text   = $relData['button_text'];
+                        $rel->status_id_to   = $relData['status_id_to'];
+                        $rel->description    = $relData['description'];
+                        $rel->button_text    = $relData['button_text'];
+                        $rel->order = (int)$relData['order'];
 
                         if (!$rel->save()) {
-                            throw new \Exception('Не удалось сохранить Relation #' . $i);
+                            $errors = $rel->getFirstErrors();
+                            $message = implode("; ", $errors);
+                            throw new \Exception("Ошибка при сохранении связи #".($i+1).": ".$message);
                         }
                     }
 
@@ -137,11 +144,10 @@ class MarketplaceOrder1cStatusesController extends Controller
             }
         }
 
-
         return $this->render('create', [
             'model'           => $model,
             'relationModels'  => $relationModels,
-            'statusOptions'   => $statusOptions,
+            'statusOptions'   => $statusOptionsByMarketplace,
         ]);
     }
 
@@ -159,49 +165,73 @@ class MarketplaceOrder1cStatusesController extends Controller
         $existingRelations = $model->getRelationsFrom()->all();
         $relationModels = count($existingRelations) ? $existingRelations : [new MarketplaceOrder1cStatusesRelations()];
 
-        $statusOptions = MarketplaceOrder1cStatuses::find()
-            ->select(['status'])
-            ->indexBy('id')
-            ->column();
 
-        if (Yii::$app->request->isPost) {
-            if ($model->load(Yii::$app->request->post())) {
-                $transaction = Yii::$app->db->beginTransaction(Transaction::SERIALIZABLE);
-                try {
-                    if (!$model->save()) {
-                        throw new \Exception('Не удалось сохранить основной статус');
-                    }
+        $allStatuses = MarketplaceOrder1cStatuses::find()
+            ->select(['id','status','marketplace_id'])
+            ->asArray()
+            ->all();
 
-                    MarketplaceOrder1cStatusesRelations::deleteAll(['status_id_from' => $model->id]);
+        $statusOptionsByMarketplace = [];
+        foreach ($allStatuses as $row) {
+            $mp = (int)$row['marketplace_id'];
+            $statusOptionsByMarketplace[$mp][$row['id']] = $row['status'];
+        }
 
-                    $postRelations = Yii::$app->request->post('Relations', []);
-                    foreach ($postRelations as $i => $relData) {
-                        if (empty($relData['status_id_to'])) {
-                            continue;
-                        }
-                        $rel = new MarketplaceOrder1cStatusesRelations();
-                        $rel->status_id_from = $model->id;
-                        $rel->status_id_to   = $relData['status_id_to'];
-                        $rel->description    = $relData['description'];
-                        $rel->button_text    = $relData['button_text'];
-                        if (!$rel->save()) {
-                            throw new \Exception('Не удалось сохранить Relation #' . $i);
-                        }
-                    }
+        $currentId = $model->id;
+        if ($currentId) {
+            foreach ($statusOptionsByMarketplace as $mp => &$arr) {
+                if (isset($arr[$currentId])) {
+                    unset($arr[$currentId]);
+                }
+            }
+            unset($arr);
+        }
 
-                    $transaction->commit();
-                    return $this->redirect(['view', 'id' => $model->id]);
-                } catch (\Exception $e) {
-                    $transaction->rollBack();
-                    Yii::$app->session->setFlash('error', 'Ошибка при сохранении отношений: ' . $e->getMessage());
+        $existingRelations = $model->getRelationsFrom()
+            ->orderBy(['order' => SORT_ASC])
+            ->all();
+
+        if (Yii::$app->request->isPost && $model->load(Yii::$app->request->post())) {
+            $transaction = Yii::$app->db->beginTransaction(Transaction::SERIALIZABLE);
+            try {
+                if (!$model->save()) {
+                    throw new \Exception('Не удалось сохранить основной статус');
+                }
+
+                MarketplaceOrder1cStatusesRelations::deleteAll(['status_id_from' => $model->id]);
+
+                $postRelations = Yii::$app->request->post('Relations', []);
+                foreach ($postRelations as $i => $relData) {
+                    if (empty($relData['status_id_to'])) {
+                        continue;
+                    }
+                    $rel = new MarketplaceOrder1cStatusesRelations();
+                    $rel->status_id_from = $model->id;
+                    $rel->status_id_to   = $relData['status_id_to'];
+                    $rel->description    = $relData['description'];
+                    $rel->button_text    = $relData['button_text'];
+                    $rel->order = (int)$relData['order'];
+
+                    if (!$rel->save()) {
+                        $errors = $rel->getFirstErrors();
+                        $message = implode("; ", $errors);
+                        throw new \Exception("Ошибка при сохранении связи #".($i+1).": ".$message);
+                    }
                 }
+
+                $transaction->commit();
+                return $this->redirect(['view', 'id' => $model->id]);
+
+            } catch (\Exception $e) {
+                $transaction->rollBack();
+                Yii::$app->session->setFlash('error', 'Ошибка при сохранении отношений: ' . $e->getMessage());
             }
         }
 
         return $this->render('update', [
             'model'           => $model,
             'relationModels'  => $relationModels,
-            'statusOptions'   => $statusOptions,
+            'statusOptions'   => $statusOptionsByMarketplace,
         ]);
     }
 
index 8833354efc057b69fa6429e319a3a5ccd4e3aba0..34e90f7e19dec59dfa58b511d9a36ca4f8c834ef 100644 (file)
@@ -23,6 +23,7 @@ class m250604_141110_create_marketplace_order_1c_statuses_relations_table extend
                 'status_id_to' => $this->integer()->comment('Cтатус для перехода - ссылка на id из таблицы marketplace_order_1c_statuses'),
                 'description' => $this->text()->null()->comment('Описание для 1С'),
                 'button_text' => $this->string()->null()->comment('Текст кнопки'),
+                'order' => $this->integer()->comment('Порядок статусов перехода'),
                 'created_at' => $this->dateTime()->comment('Дата создания'),
                 'updated_at' => $this->dateTime()->comment('Дата обновления'),
 
index c035c6e1647cc6a4545a0ae9f3aae93365913f51..45a1392374af140d164c69a2e37bae1b06f2baa3 100644 (file)
@@ -12,6 +12,7 @@ use Yii;
  * @property int|null $status_id_to Cтатус для перехода - ссылка на id из таблицы marketplace_order_1c_statuses
  * @property string|null $description Описание для 1С
  * @property string|null $button_text Текст кнопки
+ * @property int $order Порядок статусов перехода
  * @property string|null $created_at Дата создания
  * @property string|null $updated_at Дата обновления
  */
@@ -33,29 +34,52 @@ class MarketplaceOrder1cStatusesRelations extends \yii\db\ActiveRecord
     public function rules()
     {
         return [
-            [['status_id_from', 'status_id_to', 'description', 'button_text', 'created_at', 'updated_at'], 'default', 'value' => null],
-            [['status_id_from', 'status_id_to'], 'default', 'value' => null],
-            [['status_id_from', 'status_id_to'], 'integer'],
+            [['status_id_from', 'status_id_to', 'order'], 'integer'],
             [['description'], 'string'],
-            [['created_at', 'updated_at'], 'safe'],
             [['button_text'], 'string', 'max' => 255],
+            [['created_at', 'updated_at'], 'safe'],
+            [['status_id_from', 'status_id_to'], 'unique',
+                'targetAttribute' => ['status_id_from', 'status_id_to'],
+                'message' => 'Для данного статуса уже существует связь с таким же status_id_to.'
+            ],
+            [['status_id_from', 'order'], 'unique',
+                'targetAttribute' => ['status_id_from', 'order'],
+                'message' => 'Для данного статуса уже существует связь с таким же порядком.'
+            ],
         ];
     }
 
+
     /**
      * {@inheritdoc}
      */
     public function attributeLabels()
     {
         return [
-            'id' => 'ID',
-            'status_id_from' => 'Status Id From',
-            'status_id_to' => 'Status Id To',
-            'description' => 'Description',
-            'button_text' => 'Button Text',
-            'created_at' => 'Created At',
-            'updated_at' => 'Updated At',
+            'id'             => 'ID',
+            'status_id_from' => 'Статус (откуда)',
+            'status_id_to'   => 'Статус (куда)',
+            'description'    => 'Описание',
+            'button_text'    => 'Текст кнопки',
+            'order'          => 'Порядок перехода',
+            'created_at'     => 'Дата создания',
+            'updated_at'     => 'Дата обновления',
         ];
     }
 
+    public function beforeValidate()
+    {
+        if (parent::beforeValidate()) {
+            if ($this->status_id_from !== null && $this->order === null) {
+                // Получаем из БД максимальный order для этого status_id_from
+                $maxOrder = (int) self::find()
+                    ->where(['status_id_from' => $this->status_id_from])
+                    ->max('`order`');
+                $this->order = $maxOrder + 1;
+            }
+            return true;
+        }
+        return false;
+    }
+
 }
index ed00551790915e958289ff5ddb93bae94362040f..98a26a0691b3f71e3fd7096ec157bb08adc3afc3 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 use yii\helpers\Html;
+use yii\helpers\Json;
+use yii\web\View;
 use yii\widgets\ActiveForm;
 
 /** @var yii\web\View $this */
@@ -7,31 +9,43 @@ use yii\widgets\ActiveForm;
 /** @var yii_app\records\MarketplaceOrder1cStatusesRelations[] $relationModels */
 /** @var array $statusOptions  (ключ = id, значение = статус) */
 
-$js = <<<JS
-$(document).ready(function() {
+$jsonStatuses = Json::encode($statusOptions);
 
-    $('#add-relation-btn').on('click', function() {
-        var template = $('#relation-template .relation-item.template');
-        var clone = template.clone().removeClass('template');
-        $('#relations-container').append(clone);
-    });
 
-    $('#relations-container').on('click', '.remove-relation-btn', function() {
-        $(this).closest('.relation-item').remove();
-    });
-});
-JS;
-$this->registerJs($js);
+$this->registerJs(
+    "window.statusesByMarketplace = $jsonStatuses;",
+    View::POS_HEAD
+);
+
+$this->registerJsFile(
+    '@web/js/Sortable.js',
+    ['position' => \yii\web\View::POS_END]
+);
+$this->registerJsFile(
+    '@web/js/crud/marketplace-order1c-statuses/index.js',
+    ['depends' => [\yii\web\JqueryAsset::class], 'position' => \yii\web\View::POS_END]
+);
+
 
 ?>
 
 <div class="marketplace-order1c-statuses-form">
+    <?php if (Yii::$app->session->hasFlash('error')): ?>
+        <div class="alert alert-danger" role="alert">
+            <?= Yii::$app->session->getFlash('error') ?>
+        </div>
+    <?php endif; ?>
 
     <?php $form = ActiveForm::begin(); ?>
 
-    <!-- Поля самого статуса -->
     <?= $form->field($model, 'marketplace_id')
-        ->dropDownList([1 => 'ФлауВау', 2 => 'ЯндексМаркет'], ['prompt' => 'Выберите маркетплейс']) ?>
+        ->dropDownList(
+            [1 => 'ФлауВау', 2 => 'ЯндексМаркет'],
+            [
+                'prompt' => 'Выберите маркетплейс',
+                'id' => 'marketplace-order1cstatuses-marketplace_id',
+            ]
+        ) ?>
 
     <?= $form->field($model, 'status')->textInput(['maxlength' => true]) ?>
 
@@ -39,40 +53,53 @@ $this->registerJs($js);
 
     <hr>
 
-
     <h4>Связанные статусы (relations)</h4>
     <div id="relations-container">
-        <?php foreach ($relationModels as $i => $rel): ?>
-            <div class="relation-item" style="border:1px solid #ddd; padding:10px; margin-bottom:10px; position: relative;">
-                <!-- Поле «Статус-назначения» -->
-                <?= Html::label('Статус-назначения', null, ['class' => 'control-label']) ?>
-                <?= Html::dropDownList(
-                    "Relations[{$i}][status_id_to]",
-                    $rel->status_id_to,
-                    $statusOptions,
-                    ['prompt' => '— выберите статус —', 'class' => 'form-control']
-                ); ?>
-
-
-                <?= Html::label('Описание (description)', null, ['class' => 'control-label', 'style' => 'margin-top:8px;']) ?>
-                <?= Html::textInput(
-                    "Relations[{$i}][description]",
-                    $rel->description,
-                    ['class' => 'form-control']
-                ); ?>
-
-
-                <?= Html::label('Текст кнопки (button_text)', null, ['class' => 'control-label', 'style' => 'margin-top:8px;']) ?>
-                <?= Html::textInput(
-                    "Relations[{$i}][button_text]",
-                    $rel->button_text,
-                    ['class' => 'form-control']
-                ); ?>
-
+        <?php foreach ($relationModels as $i => $rel):
+            $initialOrder = $rel->order ?: ($i + 1);
+            ?>
+            <div class="relation-item" style="border:1px solid #ddd; padding:10px; margin-bottom:10px; position: relative; background:#f9f9f9;">
+                <span class="drag-handle" style="cursor:move; font-size:18px; margin-right:8px;" title="Перетащите, чтобы изменить порядок">☰</span>
+                <strong>Порядок: <span class="order-label"><?= $initialOrder ?></span></strong>
+
+                <?= Html::hiddenInput("Relations[{$i}][order]", $initialOrder, ['class' => 'rel-order-input']) ?>
+
+                <div style="margin-top:8px;">
+                    <?= Html::label('Статус-назначения', null, ['class' => 'control-label']) ?>
+                    <?= Html::dropDownList(
+                        "Relations[{$i}][status_id_to]",
+                        $rel->status_id_to,
+                        [],
+                        [
+                            'prompt'       => '— выберите статус —',
+                            'class'        => 'form-control status-to-select',
+
+                            'data-current' => $rel->status_id_to,
+                        ]
+                    ); ?>
+                </div>
+
+                <div style="margin-top:8px;">
+                    <?= Html::label('Описание (description)', null, ['class' => 'control-label']) ?>
+                    <?= Html::textInput(
+                        "Relations[{$i}][description]",
+                        $rel->description,
+                        ['class' => 'form-control']
+                    ); ?>
+                </div>
+
+                <div style="margin-top:8px;">
+                    <?= Html::label('Текст кнопки (button_text)', null, ['class' => 'control-label']) ?>
+                    <?= Html::textInput(
+                        "Relations[{$i}][button_text]",
+                        $rel->button_text,
+                        ['class' => 'form-control']
+                    ); ?>
+                </div>
 
                 <button type="button" class="btn btn-danger btn-sm remove-relation-btn"
                         style="position:absolute; top:10px; right:10px;">
-                    &minus; Удалить
+                   Удалить
                 </button>
             </div>
         <?php endforeach; ?>
@@ -89,26 +116,40 @@ $this->registerJs($js);
     <?php ActiveForm::end(); ?>
 
 
-
     <div id="relation-template" style="display:none;">
-        <div class="relation-item template" style="border:1px solid #ddd; padding:10px; margin-bottom:10px; position: relative;">
-            <?= Html::label('Статус-назначения', null, ['class' => 'control-label']) ?>
-            <?= Html::dropDownList(
-                "Relations[__index__][status_id_to]",
-                null,
-                $statusOptions,
-                ['prompt' => '— выберите статус —', 'class' => 'form-control']
-            ); ?>
+        <div class="relation-item template" style="border:1px solid #ddd; padding:10px; margin-bottom:10px; position: relative; background:#f9f9f9;">
+            <span class="drag-handle" style="cursor:move; font-size:18px; margin-right:8px;" title="Перетащите, чтобы изменить порядок">☰</span>
+            <strong>Порядок: <span class="order-label">__order__</span></strong>
 
-            <?= Html::label('Описание (description)', null, ['class' => 'control-label', 'style' => 'margin-top:8px;']) ?>
-            <?= Html::textInput("Relations[__index__][description]", null, ['class' => 'form-control']) ?>
+            <?= Html::hiddenInput("Relations[__index__][order]", "__order__", ['class' => 'rel-order-input']) ?>
 
-            <?= Html::label('Текст кнопки (button_text)', null, ['class' => 'control-label', 'style' => 'margin-top:8px;']) ?>
-            <?= Html::textInput("Relations[__index__][button_text]", null, ['class' => 'form-control']) ?>
+            <div style="margin-top:8px;">
+                <?= Html::label('Статус-назначения', null, ['class' => 'control-label']) ?>
+                <?= Html::dropDownList(
+                    "Relations[__index__][status_id_to]",
+                    null,
+                    [],
+                    [
+                        'prompt'       => '— выберите статус —',
+                        'class'        => 'form-control status-to-select',
+                        'data-current' => '',
+                    ]
+                ); ?>
+            </div>
+
+            <div style="margin-top:8px;">
+                <?= Html::label('Описание (description)', null, ['class' => 'control-label']) ?>
+                <?= Html::textInput("Relations[__index__][description]", null, ['class' => 'form-control']) ?>
+            </div>
+
+            <div style="margin-top:8px;">
+                <?= Html::label('Текст кнопки (button_text)', null, ['class' => 'control-label']) ?>
+                <?= Html::textInput("Relations[__index__][button_text]", null, ['class' => 'form-control']) ?>
+            </div>
 
             <button type="button" class="btn btn-danger btn-sm remove-relation-btn"
                     style="position:absolute; top:10px; right:10px;">
-                &minus; Удалить
+                Удалить
             </button>
         </div>
     </div>
index e4bd4bb527f2e3c52131d76694de1054588e6244..819b36d3aadf4356bd8f25a8d3360b44bf4e5337 100644 (file)
@@ -62,4 +62,125 @@ function showSortingDialog(marketplaceId) {
 
         }
     });
-}
\ No newline at end of file
+}
+
+document.addEventListener('DOMContentLoaded', function() {
+
+    var statusesByMarketplace = window.statusesByMarketplace || {};
+console.log(statusesByMarketplace);
+    function updateAllRelationSelects() {
+        var mpSelect = document.getElementById('marketplace-order1cstatuses-marketplace_id');
+        if (!mpSelect) return;
+        var mp = mpSelect.value;
+
+        var options = statusesByMarketplace[mp] || {};
+
+
+        document.querySelectorAll('.status-to-select').forEach(function(select) {
+
+            var currentValue = select.getAttribute('data-current') || select.value;
+            select.innerHTML = ''; // очистили все опции
+
+
+            var opt = document.createElement('option');
+            opt.value = '';
+            opt.textContent = '— выберите статус —';
+            select.appendChild(opt);
+
+
+            Object.keys(options).forEach(function(id) {
+                var o = document.createElement('option');
+                o.value = id;
+                o.textContent = options[id];
+                if (String(id) === String(currentValue)) {
+                    o.selected = true;
+                }
+                select.appendChild(o);
+            });
+        });
+    }
+
+
+    updateAllRelationSelects();
+
+
+    var mpElem = document.getElementById('marketplace-order1cstatuses-marketplace_id');
+    if (mpElem) {
+        mpElem.addEventListener('change', updateAllRelationSelects);
+    }
+
+
+    var el = document.getElementById('relations-container');
+    if (el) {
+        Sortable.create(el, {
+            animation: 150,
+            handle: '.drag-handle',
+            onEnd: function (evt) {
+
+                var items = el.querySelectorAll('.relation-item');
+                items.forEach(function(item, idx) {
+                    var newOrder = idx + 1;
+                    var input = item.querySelector('.rel-order-input');
+                    var label = item.querySelector('.order-label');
+                    if (input) {
+                        input.value = newOrder;
+                    }
+                    if (label) {
+                        label.textContent = newOrder;
+                    }
+                });
+            }
+        });
+    }
+
+
+    document.getElementById('add-relation-btn').addEventListener('click', function() {
+        var template = document.querySelector('#relation-template .relation-item.template');
+        if (!template) return;
+        var clone = template.cloneNode(true);
+        clone.classList.remove('template');
+        clone.style.display = 'block';
+
+        var currentCount = document.querySelectorAll('#relations-container .relation-item:not(.template)').length;
+        var newIdx = currentCount;
+
+        clone.querySelectorAll('[name]').forEach(function(node) {
+            var name = node.getAttribute('name');
+
+            var newName = name.replace(/__index__/, newIdx);
+            node.setAttribute('name', newName);
+        });
+        var hiddenInput = clone.querySelector('.rel-order-input');
+        if (hiddenInput) {
+            hiddenInput.value = currentCount + 1;
+        }
+
+        var label = clone.querySelector('.order-label');
+        if (label) {
+            label.textContent = currentCount + 1;
+        }
+        document.getElementById('relations-container').appendChild(clone);
+    });
+
+
+    document.getElementById('relations-container').addEventListener('click', function(e) {
+        if (e.target && e.target.matches('.remove-relation-btn')) {
+            var item = e.target.closest('.relation-item');
+            if (item) {
+                item.remove();
+                var items = document.querySelectorAll('#relations-container .relation-item');
+                items.forEach(function(remItem, idx) {
+                    var newOrder = idx + 1;
+                    var input = remItem.querySelector('.rel-order-input');
+                    var label = remItem.querySelector('.order-label');
+                    if (input) {
+                        input.value = newOrder;
+                    }
+                    if (label) {
+                        label.textContent = newOrder;
+                    }
+                });
+            }
+        }
+    });
+});
\ No newline at end of file