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 - + <v8:item> <v8:lang>ru</v8:lang> @@ -103,7 +116,7 @@ <v8:Type>v8:ValueTable</v8:Type> </Type> <Columns> - <Column name="Номенклатура" id="25"> + <Column name="Номенклатура" id="28"> <Type> <v8:Type>xs:string</v8:Type> <v8:StringQualifiers> @@ -112,7 +125,7 @@ </v8:StringQualifiers> </Type> </Column> - <Column name="Картинка" id="26"> + <Column name="Картинка" id="29"> <Type> <v8:Type>xs:boolean</v8:Type> </Type> 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 @@ <ChildItems> <RadioButtonField name="ОтборСтрок" id="1"> <DataPath>ОтборСтрок</DataPath> + <EditMode>EnterOnInput</EditMode> <TitleLocation>None</TitleLocation> <RadioButtonType>Tumbler</RadioButtonType> <ChoiceList> @@ -50,15 +51,68 @@ <ContextMenu name="ОтборСтрокКонтекстноеМеню" id="2"/> <ExtendedTooltip name="ОтборСтрокРасширеннаяПодсказка" id="3"/> </RadioButtonField> + <RadioButtonField name="ВидСчета" id="4"> + <Title> + <v8:item> + <v8:lang>ru</v8:lang> + <v8:content>Вид счета</v8:content> + </v8:item> + + None + Auto + + + + 0 + + + + ru + Активный + + + Active + + + + + 0 + + + + ru + Пассивный + + + Passive + + + + + 0 + + + + ru + Активный/Пассивный + + + ActivePassive + + + + + + - + cfg:DataProcessorObject.ТестТумблер true - + <v8:item> <v8:lang>ru</v8:lang>