diff --git a/.claude/skills/form-compile/scripts/form-compile.ps1 b/.claude/skills/form-compile/scripts/form-compile.ps1
index 1bc5b172..d2047561 100644
--- a/.claude/skills/form-compile/scripts/form-compile.ps1
+++ b/.claude/skills/form-compile/scripts/form-compile.ps1
@@ -1,4 +1,4 @@
-# form-compile v1.123 — Compile 1C managed form from JSON or object metadata
+# form-compile v1.124 — Compile 1C managed form from JSON or object metadata
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[string]$JsonPath,
@@ -3895,7 +3895,15 @@ function Emit-ChoiceList {
elseif ($item.PSObject.Properties["title"]) { $presRaw = $item.title; $hasPres = $true }
}
- $norm = Normalize-ChoiceValue -value $valRaw
+ # valueType: явный xsi:type значения (системное перечисление ent:*, иной не-примитив) —
+ # переопределяет авто-детект (Normalize-ChoiceValue вывела бы xs:string).
+ $vtRaw = $null
+ if ($item -is [hashtable] -or $item -is [System.Collections.IDictionary]) {
+ if ($item.Contains("valueType")) { $vtRaw = "$($item["valueType"])" }
+ } elseif ($item.PSObject.Properties["valueType"]) { $vtRaw = "$($item.valueType)" }
+
+ if ($vtRaw) { $norm = @{ XsiType = $vtRaw; Text = "$valRaw" } }
+ else { $norm = Normalize-ChoiceValue -value $valRaw }
# авто-вывод presentation, если не задан
if (-not $hasPres) {
@@ -4092,6 +4100,7 @@ function Emit-Radio {
Emit-Title -el $el -name $name -indent $inner -auto:(-not $el.path)
Emit-CommonFlags -el $el -indent $inner
+ if ($el.editMode) { X "$inner$($el.editMode)" }
Emit-TitleLocation -el $el -indent $inner -smartDefault "None"
# RadioButtonType: Auto | RadioButtons | Tumbler. Accept synonyms.
@@ -4181,6 +4190,8 @@ function Emit-LabelField {
if ($el.titleLocation) { X "$inner$(Map-TitleLoc "$($el.titleLocation)")" }
if ($el.editMode) { X "$inner$($el.editMode)" }
+ # FooterDataPath — путь данных подвала колонки (общий cell-prop, как у input); после EditMode
+ if ($el.footerDataPath) { X "$inner$(Esc-Xml "$($el.footerDataPath)")" }
# PasswordMode на LabelField — платформа эмитит явный false (редко); факт. значение
if ($null -ne $el.passwordMode) { X "$inner$(if ($el.passwordMode){'true'}else{'false'})" }
Emit-ColumnPics -el $el -indent $inner
@@ -4189,6 +4200,7 @@ function Emit-LabelField {
Emit-Layout -el $el -indent $inner
if ($null -ne $el.warningOnEdit) { Emit-MLText -tag "WarningOnEdit" -text $el.warningOnEdit -indent $inner }
+ if ($null -ne $el.footerText) { Emit-MLText -tag "FooterText" -text $el.footerText -indent $inner }
# Формат / формат редактирования (LocalStringType — строка или {ru,en})
if ($el.format) { Emit-MLText -tag "Format" -text $el.format -indent $inner }
@@ -5227,9 +5239,15 @@ function Emit-Attributes {
}
if ($hasAddCols) {
foreach ($ac in @($attr.additionalColumns)) {
+ $acCols = @($ac.columns)
+ if ($acCols.Count -eq 0) {
+ # Пустая группа доп.колонок (table-ref без колонок) → self-closing (как платформа)
+ X "$inner`t"
+ continue
+ }
X "$inner`t"
$seenAcCols = @{} # уникальность в пределах группы AdditionalColumns
- foreach ($col in @($ac.columns)) {
+ foreach ($col in $acCols) {
Assert-UniqueName -name "$($col.name)" -seen $seenAcCols -kind "column of '$attrName'"
Emit-AttrColumn -col $col -indent "$inner`t`t"
}
diff --git a/.claude/skills/form-compile/scripts/form-compile.py b/.claude/skills/form-compile/scripts/form-compile.py
index 585391aa..f452f0a2 100644
--- a/.claude/skills/form-compile/scripts/form-compile.py
+++ b/.claude/skills/form-compile/scripts/form-compile.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
-# form-compile v1.123 — Compile 1C managed form from JSON or object metadata
+# form-compile v1.124 — Compile 1C managed form from JSON or object metadata
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import copy
@@ -2120,7 +2120,13 @@ def emit_choice_list(lines, el, indent):
has_pres = any(k in item for k in ('presentation', 'представление', 'title'))
pres_raw = item.get('presentation', item.get('представление', item.get('title')))
- norm = normalize_choice_value(val_raw)
+ # valueType: явный xsi:type значения (системное перечисление ent:*, иной не-примитив) —
+ # переопределяет авто-детект (normalize_choice_value вывела бы xs:string).
+ vt_raw = item.get('valueType')
+ if vt_raw:
+ norm = {'xsi_type': str(vt_raw), 'text': '' if val_raw is None else str(val_raw)}
+ else:
+ norm = normalize_choice_value(val_raw)
if not has_pres:
if norm['xsi_type'] == 'xr:DesignTimeRef':
@@ -3817,6 +3823,8 @@ def emit_radio_button_field(lines, el, name, eid, indent):
emit_title(lines, el, name, inner, auto=not el.get('path'))
emit_common_flags(lines, el, inner)
+ if el.get('editMode'):
+ lines.append(f'{inner}{el["editMode"]}')
emit_title_location(lines, el, inner, 'None')
rbt = normalize_radio_button_type(el.get('radioButtonType'))
@@ -3898,6 +3906,9 @@ def emit_label_field(lines, el, name, eid, indent):
lines.append(f'{inner}{map_title_loc(el["titleLocation"])}')
if el.get('editMode'):
lines.append(f'{inner}{el["editMode"]}')
+ # FooterDataPath — путь данных подвала колонки (общий cell-prop, как у input); после EditMode
+ if el.get('footerDataPath'):
+ lines.append(f'{inner}{esc_xml(str(el["footerDataPath"]))}')
# PasswordMode на LabelField — платформа эмитит явный false (редко); факт. значение
if el.get('passwordMode') is not None:
lines.append(f'{inner}{"true" if el["passwordMode"] else "false"}')
@@ -3909,6 +3920,8 @@ def emit_label_field(lines, el, name, eid, indent):
if el.get('warningOnEdit') is not None:
emit_mltext(lines, inner, 'WarningOnEdit', el['warningOnEdit'])
+ if el.get('footerText') is not None:
+ emit_mltext(lines, inner, 'FooterText', el['footerText'])
# Формат / формат редактирования (LocalStringType — строка или {ru,en})
if el.get('format'):
@@ -4953,9 +4966,14 @@ def emit_attributes(lines, attrs, indent, conditional_appearance=None):
emit_attr_column(lines, col, f'{inner}\t')
if has_add_cols:
for ac in attr['additionalColumns']:
+ ac_cols = ac.get('columns') or []
+ if not ac_cols:
+ # Пустая группа доп.колонок (table-ref без колонок) → self-closing (как платформа)
+ lines.append(f'{inner}\t')
+ continue
lines.append(f'{inner}\t')
seen_ac_cols = set() # уникальность в пределах группы AdditionalColumns
- for col in (ac.get('columns') or []):
+ for col in ac_cols:
_ensure_unique(str(col['name']), seen_ac_cols, f"column of '{attr_name}'")
emit_attr_column(lines, col, f'{inner}\t\t')
lines.append(f'{inner}\t')
diff --git a/.claude/skills/form-decompile/scripts/form-decompile.ps1 b/.claude/skills/form-decompile/scripts/form-decompile.ps1
index e048ae9c..5f91da08 100644
--- a/.claude/skills/form-decompile/scripts/form-decompile.ps1
+++ b/.claude/skills/form-decompile/scripts/form-decompile.ps1
@@ -1,4 +1,4 @@
-# form-decompile v0.97 — Decompile 1C managed Form.xml to JSON DSL (draft)
+# form-decompile v0.98 — Decompile 1C managed Form.xml to JSON DSL (draft)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
# ВНИМАНИЕ: раундтрип не гарантируется. Навык исключён из авто-использования моделью.
param(
@@ -1416,6 +1416,11 @@ function Decompile-ChoiceList {
if ($valNode) {
$xsiType = $valNode.GetAttribute("type", $NS_XSI)
$ci['value'] = Convert-TypedValue $valNode.InnerText $xsiType
+ # Системное перечисление (ent:*) / иной не-примитивный, не-DesignTimeRef тип → сохраняем
+ # valueType (Normalize-ChoiceValue в компиляторе вывела бы xs:string и потеряла тип).
+ if ($xsiType -and $xsiType -notmatch '^xs:(string|decimal|boolean|dateTime)$' -and $xsiType -ne 'xr:DesignTimeRef') {
+ $ci['valueType'] = $xsiType
+ }
}
# Presentation: непустой → текст/мультиязык; пустой → "" — суппресс-маркер,
# подавляет авто-вывод компилятора (иначе компилятор додумает presentation из значения).
@@ -1666,6 +1671,7 @@ function Decompile-Element {
$dp = Get-Child $node 'DataPath'; if ($dp) { $obj['path'] = $dp }
Add-CommonProps $obj $node $name
Add-TitleLocation $obj $node 'None'
+ $em = Get-Child $node 'EditMode'; if ($em) { $obj['editMode'] = $em }
$rbt = Get-Child $node 'RadioButtonType'; if ($rbt) { $obj['radioButtonType'] = $rbt }
$cc = Get-Child $node 'ColumnsCount'; if ($cc) { $obj['columnsCount'] = [int]$cc }
$woe = $node.SelectSingleNode("lf:WarningOnEdit", $ns); if ($woe) { $t = Get-LangText $woe; if ($null -ne $t) { $obj['warningOnEdit'] = $t } }
@@ -1690,6 +1696,9 @@ function Decompile-Element {
# PasswordMode на LabelField — платформа эмитит явный false (редко); захват факт. значения
$pm = Get-Child $node 'PasswordMode'; if ($null -ne $pm) { $obj['passwordMode'] = ($pm -eq 'true') }
$woe = $node.SelectSingleNode("lf:WarningOnEdit", $ns); if ($woe) { $t = Get-LangText $woe; if ($null -ne $t) { $obj['warningOnEdit'] = $t } }
+ # FooterDataPath / FooterText — общие cell-свойства колонки (как у input), не только input
+ $fdp = Get-Child $node 'FooterDataPath'; if ($fdp) { $obj['footerDataPath'] = $fdp }
+ $ftxt = $node.SelectSingleNode("lf:FooterText", $ns); if ($ftxt) { $t = Get-LangText $ftxt; if ($null -ne $t) { $obj['footerText'] = $t } }
Add-FormatProps $obj $node
}
'PictureDecoration' {
diff --git a/docs/form-dsl-spec.md b/docs/form-dsl-spec.md
index 100c37fe..922a9fb4 100644
--- a/docs/form-dsl-spec.md
+++ b/docs/form-dsl-spec.md
@@ -489,6 +489,7 @@ companion-панели с собственным контентом. Оба не
| Свойство | Тип | Описание |
|----------|-----|----------|
| `value` | string/number/bool | Значение варианта. Для перечисления — `"Enum.ИмяТипа.EnumValue.ИмяЗначения"` (xsi:type автоматически: `xr:DesignTimeRef` / `xs:string` / `xs:decimal` / `xs:boolean`) |
+| `valueType` | string | Явный xsi:type значения, переопределяет авто-детект. Нужен для **системных перечислений** (`ent:` namespace: `ent:AccountType`=ВидСчёта, `ent:AccumulationRecordType`, `ent:HorizontalAlignment`, … — см. «Системные перечисления» в палитре типов) и иных не-примитивных типов. Напр. `{ "value": "Active", "valueType": "ent:AccountType", "presentation": "Активный" }` |
| `presentation` | string или object | Текст рядом с переключателем. Строка → ru; объект `{ru, en, ...}` → мультиязык. Если не задано — выводится из имени значения |
#### label — LabelDecoration
diff --git a/tests/skills/cases/form-compile/additional-columns.json b/tests/skills/cases/form-compile/additional-columns.json
index 29a028d7..3fc04c64 100644
--- a/tests/skills/cases/form-compile/additional-columns.json
+++ b/tests/skills/cases/form-compile/additional-columns.json
@@ -7,7 +7,8 @@
"type": "DataProcessor",
"name": "ДопКолонки",
"tabularSections": [
- { "name": "Прочее", "attributes": [ { "name": "Значение", "type": "String", "length": 50 } ] }
+ { "name": "Прочее", "attributes": [ { "name": "Значение", "type": "String", "length": 50 } ] },
+ { "name": "ЕщёТаблица", "attributes": [ { "name": "Поле", "type": "String", "length": 10 } ] }
]
},
"args": { "-JsonPath": "{inputFile}", "-OutputDir": "{workDir}" }
@@ -30,7 +31,8 @@
{ "table": "Объект.Прочее", "columns": [
{ "name": "Доступность", "type": "boolean" },
{ "name": "Служебная", "type": "string" }
- ]}
+ ]},
+ { "table": "Объект.ЕщёТаблица", "columns": [] }
]
}
]
diff --git a/tests/skills/cases/form-compile/picture-field.json b/tests/skills/cases/form-compile/picture-field.json
index 95977fb8..6ccece30 100644
--- a/tests/skills/cases/form-compile/picture-field.json
+++ b/tests/skills/cases/form-compile/picture-field.json
@@ -21,7 +21,8 @@
"columns": [
{ "input": "ТаблицаДанныхНоменклатура", "path": "ТаблицаДанных.Номенклатура" },
{ "picField": "ТаблицаДанныхКартинка", "path": "ТаблицаДанных.Картинка", "titleLocation": "none", "editMode": "EnterOnInput", "hyperlink": true, "shortcut": "Ctrl+S", "headerPicture": "StdPicture.ExecuteTask", "valuesPicture": { "src": "StdPicture.FilterCriterion", "loadTransparent": true, "transparentPixel": { "x": 7, "y": 3 } } },
- { "check": "ТаблицаДанныхКартинкаФлаг", "path": "ТаблицаДанных.Картинка", "title": "Флаг", "headerPicture": { "src": "StdPicture.ClearFilter", "loadTransparent": true } }
+ { "check": "ТаблицаДанныхКартинкаФлаг", "path": "ТаблицаДанных.Картинка", "title": "Флаг", "headerPicture": { "src": "StdPicture.ClearFilter", "loadTransparent": true } },
+ { "labelField": "ТаблицаДанныхИтог", "path": "ТаблицаДанных.Номенклатура", "editMode": "EnterOnInput", "footerDataPath": "ТаблицаДанных.Номенклатура", "footerText": "Итого" }
]}
],
"attributes": [
diff --git a/tests/skills/cases/form-compile/radio-tumbler-strings.json b/tests/skills/cases/form-compile/radio-tumbler-strings.json
index 39005215..829e3557 100644
--- a/tests/skills/cases/form-compile/radio-tumbler-strings.json
+++ b/tests/skills/cases/form-compile/radio-tumbler-strings.json
@@ -26,11 +26,20 @@
"radio": "ОтборСтрок",
"path": "ОтборСтрок",
"radioButtonType": "Tumbler",
+ "editMode": "EnterOnInput",
"warningOnEdit": "Смена отбора перезагрузит список",
"choiceList": [
{ "value": "Рекомендуемые" },
{ "value": "Все" }
]
+ },
+ {
+ "radio": "ВидСчета",
+ "choiceList": [
+ { "value": "Active", "valueType": "ent:AccountType", "presentation": "Активный" },
+ { "value": "Passive", "valueType": "ent:AccountType", "presentation": "Пассивный" },
+ { "value": "ActivePassive", "valueType": "ent:AccountType", "presentation": "Активный/Пассивный" }
+ ]
}
]
}
diff --git a/tests/skills/cases/form-compile/snapshots/additional-columns/DataProcessors/ДопКолонки.xml b/tests/skills/cases/form-compile/snapshots/additional-columns/DataProcessors/ДопКолонки.xml
index a3be3c64..157a88fa 100644
--- a/tests/skills/cases/form-compile/snapshots/additional-columns/DataProcessors/ДопКолонки.xml
+++ b/tests/skills/cases/form-compile/snapshots/additional-columns/DataProcessors/ДопКолонки.xml
@@ -123,6 +123,100 @@
+
+
+
+ UUID-013
+ UUID-014
+
+
+ UUID-015
+ UUID-016
+
+
+
+ ЕщёТаблица
+
+
+ ru
+ Ещё таблица
+
+
+
+
+ DontCheck
+
+
+
+ DontCheck
+ false
+ false
+ Auto
+
+
+ false
+
+
+ Auto
+ Auto
+
+ false
+ Use
+ false
+
+
+
+ Use
+
+
+
+
+
+
+
+
+
+
+ Поле
+
+
+ ru
+ Поле
+
+
+
+
+ xs:string
+
+ 10
+ Variable
+
+
+ false
+
+
+
+ false
+
+ false
+ false
+
+
+ false
+
+ DontCheck
+ Items
+
+
+ Auto
+ Auto
+
+
+ Auto
+
+
+
+
diff --git a/tests/skills/cases/form-compile/snapshots/additional-columns/DataProcessors/ДопКолонки/Forms/Форма/Ext/Form.xml b/tests/skills/cases/form-compile/snapshots/additional-columns/DataProcessors/ДопКолонки/Forms/Форма/Ext/Form.xml
index 5916fefb..6e283a9a 100644
--- a/tests/skills/cases/form-compile/snapshots/additional-columns/DataProcessors/ДопКолонки/Forms/Форма/Ext/Form.xml
+++ b/tests/skills/cases/form-compile/snapshots/additional-columns/DataProcessors/ДопКолонки/Forms/Форма/Ext/Form.xml
@@ -43,6 +43,7 @@
+
diff --git a/tests/skills/cases/form-compile/snapshots/picture-field/DataProcessors/КартинкаВСтроке/Forms/Форма/Ext/Form.xml b/tests/skills/cases/form-compile/snapshots/picture-field/DataProcessors/КартинкаВСтроке/Forms/Форма/Ext/Form.xml
index dc3ebb82..fec6e2a6 100644
--- a/tests/skills/cases/form-compile/snapshots/picture-field/DataProcessors/КартинкаВСтроке/Forms/Форма/Ext/Form.xml
+++ b/tests/skills/cases/form-compile/snapshots/picture-field/DataProcessors/КартинкаВСтроке/Forms/Форма/Ext/Form.xml
@@ -79,6 +79,19 @@
+
+ ТаблицаДанных.Номенклатура
+ EnterOnInput
+ ТаблицаДанных.Номенклатура
+
+
+ ru
+ Итого
+
+
+
+
+
ТаблицаДанныхВыбор
@@ -86,13 +99,13 @@
-
+
cfg:DataProcessorObject.КартинкаВСтроке
true
-
+
ru
@@ -103,7 +116,7 @@
v8:ValueTable
-
+
xs:string
@@ -112,7 +125,7 @@
-
+
xs:boolean
diff --git a/tests/skills/cases/form-compile/snapshots/radio-tumbler-strings/DataProcessors/ТестТумблер/Forms/Форма/Ext/Form.xml b/tests/skills/cases/form-compile/snapshots/radio-tumbler-strings/DataProcessors/ТестТумблер/Forms/Форма/Ext/Form.xml
index 7b9c53a3..d6ae242f 100644
--- a/tests/skills/cases/form-compile/snapshots/radio-tumbler-strings/DataProcessors/ТестТумблер/Forms/Форма/Ext/Form.xml
+++ b/tests/skills/cases/form-compile/snapshots/radio-tumbler-strings/DataProcessors/ТестТумблер/Forms/Форма/Ext/Form.xml
@@ -11,6 +11,7 @@
ОтборСтрок
+ EnterOnInput
None
Tumbler
@@ -50,15 +51,68 @@
+
+
+
+ ru
+ Вид счета
+
+
+ None
+ Auto
+
+
+
+ 0
+
+
+
+ ru
+ Активный
+
+
+ Active
+
+
+
+
+ 0
+
+
+
+ ru
+ Пассивный
+
+
+ Passive
+
+
+
+
+ 0
+
+
+
+ ru
+ Активный/Пассивный
+
+
+ ActivePassive
+
+
+
+
+
+
-
+
cfg:DataProcessorObject.ТестТумблер
true
-
+
ru