diff --git a/CHANGELOG.md b/CHANGELOG.md index fcf16a52..c4327f3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,11 @@ - Сохранение настроек доставки и оплаты использует общий нормализатор доп. стоимости, чтобы одинаково обрабатывать `-10`, `-10%`, запятые и лишние символы. - Подсказки Vue-админки уточняют, что доп. стоимость может быть отрицательной и процентной. +**Редактор правил доставки — поля из расширенной конфигурации (#215):** +- `ValidationRulesEditor` подгружает поля `msOrder` и `msOrderAddress` из API «поля модели» и активные поля Object Extension для этих классов. +- В списке выбора поля для `validation_rules` доставки доступны кастомные колонки и подписи из админки; служебные поля (id, стоимости, delivery_id и т.д.) отфильтрованы. +- Те же исключения применяются и к ключам Object Extension, чтобы не предлагать служебные имена. + --- ## Апрель 2026 diff --git a/vueManager/src/components/ValidationRulesEditor.vue b/vueManager/src/components/ValidationRulesEditor.vue index e892bbb2..ca4c9312 100644 --- a/vueManager/src/components/ValidationRulesEditor.vue +++ b/vueManager/src/components/ValidationRulesEditor.vue @@ -7,10 +7,42 @@ import InputText from 'primevue/inputtext' import Select from 'primevue/select' import Textarea from 'primevue/textarea' import ToggleSwitch from 'primevue/toggleswitch' -import { computed, ref, watch } from 'vue' +import { computed, onMounted, ref, watch } from 'vue' + +import request from '../request.js' const { _ } = useLexicon() +/** Technical msOrder columns — not offered as checkout fields for delivery rules */ +const ORDER_FIELD_BLOCKLIST = new Set([ + 'id', + 'user_id', + 'customer_id', + 'token', + 'uuid', + 'createdon', + 'updatedon', + 'cost', + 'cart_cost', + 'delivery_cost', + 'weight', + 'status_id', + 'delivery_id', + 'payment_id', + 'context', + 'properties', + 'num', +]) + +/** Technical msOrderAddress columns */ +const ADDRESS_FIELD_BLOCKLIST = new Set(['id', 'order_id', 'createdon', 'updatedon', 'properties']) + +const FIELD_GROUP_SORT = { order: 0, address: 1 } + +function blocklistForGroup(group) { + return group === 'order' ? ORDER_FIELD_BLOCKLIST : ADDRESS_FIELD_BLOCKLIST +} + // Editor mode: visual or json const isJsonMode = ref(false) const jsonText = ref('') @@ -49,6 +81,9 @@ const fieldDefinitions = [ { name: 'text_address', group: 'address' }, ] +/** Extra definitions from model fields + Object Extension (filled on mount) */ +const extensionFieldDefinitions = ref([]) + // Available validation rules from rakit/validation const ruleDefinitions = [ // Simple rules (no parameters) @@ -99,11 +134,107 @@ const ruleDefinitions = [ { name: 'required_without_all', hasParam: true, paramType: 'text' }, ] +const mergedFieldDefinitions = computed(() => [...fieldDefinitions, ...extensionFieldDefinitions.value]) + +async function loadExtensionFieldDefinitions() { + const staticNames = new Set(fieldDefinitions.map(f => f.name)) + const collected = [] + + const pushModelRows = (rows, model, blocklist) => { + const group = model === 'msOrder' ? 'order' : 'address' + for (const row of rows || []) { + const name = row.name + if (!name || blocklist.has(name) || staticNames.has(name)) { + continue + } + collected.push({ + name, + group, + labelHint: row.label_translated || row.label || name, + sortRank: [FIELD_GROUP_SORT[group], row.sort_order ?? 0, name], + }) + } + } + + try { + const [orderRes, addressRes] = await Promise.all([ + request.get('/api/mgr/model-fields', { model: 'msOrder', limit: 500, start: 0 }), + request.get('/api/mgr/model-fields', { model: 'msOrderAddress', limit: 500, start: 0 }), + ]) + pushModelRows(orderRes.results, 'msOrder', ORDER_FIELD_BLOCKLIST) + pushModelRows(addressRes.results, 'msOrderAddress', ADDRESS_FIELD_BLOCKLIST) + } catch (e) { + console.warn('[ValidationRulesEditor] Failed to load model-fields:', e) + } + + try { + const pushExtraRows = (fields, group) => { + const blocklist = blocklistForGroup(group) + for (const f of fields || []) { + if (!f.active) { + continue + } + const name = f.key + if (!name || staticNames.has(name) || blocklist.has(name)) { + continue + } + collected.push({ + name, + group, + labelHint: f.label || name, + sortRank: [FIELD_GROUP_SORT[group], 1e6, name], + }) + } + } + const [orderExtra, addressExtra] = await Promise.all([ + request.get('/api/mgr/extra-fields', { class: 'msOrder' }), + request.get('/api/mgr/extra-fields', { class: 'msOrderAddress' }), + ]) + pushExtraRows(orderExtra.fields, 'order') + pushExtraRows(addressExtra.fields, 'address') + } catch (e) { + console.warn('[ValidationRulesEditor] Failed to load extra-fields:', e) + } + + // validation_rules JSON keys are a single flat namespace (field name → rules) + const byName = new Map() + for (const c of collected) { + if (byName.has(c.name)) { + continue + } + byName.set(c.name, c) + } + + const sorted = [...byName.values()].sort((a, b) => { + for (let i = 0; i < 3; i++) { + const av = a.sortRank[i] + const bv = b.sortRank[i] + if (av < bv) { + return -1 + } + if (av > bv) { + return 1 + } + } + return 0 + }) + + extensionFieldDefinitions.value = sorted.map(c => ({ + name: c.name, + group: c.group, + labelHint: c.labelHint, + })) +} + +onMounted(() => { + void loadExtensionFieldDefinitions() +}) + // Build available fields with localized labels (flat list) const availableFields = computed(() => { - return fieldDefinitions.map(field => ({ + return mergedFieldDefinitions.value.map(field => ({ ...field, - label: _(`validation_field_${field.name}`), + label: field.labelHint || _(`validation_field_${field.name}`), groupLabel: _(`validation_field_group_${field.group}`), })) })