$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', []);
$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);
}
}
}
}
-
return $this->render('create', [
'model' => $model,
'relationModels' => $relationModels,
- 'statusOptions' => $statusOptions,
+ 'statusOptions' => $statusOptionsByMarketplace,
]);
}
$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,
]);
}
'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('Дата обновления'),
* @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 Дата обновления
*/
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;
+ }
+
}
<?php
use yii\helpers\Html;
+use yii\helpers\Json;
+use yii\web\View;
use yii\widgets\ActiveForm;
/** @var yii\web\View $this */
/** @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]) ?>
<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;">
- − Удалить
+ Удалить
</button>
</div>
<?php endforeach; ?>
<?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;">
- − Удалить
+ Удалить
</button>
</div>
</div>
}
});
-}
\ 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