diff --git a/.claude/skills/form-compile/scripts/form-compile.ps1 b/.claude/skills/form-compile/scripts/form-compile.ps1 index 05c9962e..76ae27c4 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.75 — Compile 1C managed form from JSON or object metadata +# form-compile v1.76 — Compile 1C managed form from JSON or object metadata # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [string]$JsonPath, @@ -4400,6 +4400,27 @@ function Emit-Attributes { if ($attr.savedData -eq $true -or $mainSaved) { X "$innertrue" } + # Save: сохранение значения реквизита в пользовательских настройках. true → имя; + # строка/массив → под-поля с авто-префиксом "имя." (путь с точкой / UUID / =имя — как есть). + # Нет ключа или false → не эмитим. + if ($null -ne $attr.PSObject.Properties['save'] -and $null -ne $attr.save) { + $saveFields = New-Object System.Collections.ArrayList + if ($attr.save -is [bool]) { + if ($attr.save) { [void]$saveFields.Add($attrName) } + } else { + foreach ($e in @($attr.save)) { + $fld = "$e" + if ([string]::IsNullOrEmpty($fld)) { continue } + if ($fld -ne $attrName -and $fld -notmatch '\.' -and $fld -notmatch '^\d+/\d+:') { $fld = "$attrName.$fld" } + if (-not $saveFields.Contains($fld)) { [void]$saveFields.Add($fld) } + } + } + if ($saveFields.Count -gt 0) { + X "$inner" + foreach ($f in $saveFields) { X "$inner`t$(Esc-Xml $f)" } + X "$inner" + } + } if ($attr.fillChecking) { X "$inner$($attr.fillChecking)" } diff --git a/.claude/skills/form-compile/scripts/form-compile.py b/.claude/skills/form-compile/scripts/form-compile.py index 8eaf6c6a..d4110e60 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.75 — Compile 1C managed form from JSON or object metadata +# form-compile v1.76 — Compile 1C managed form from JSON or object metadata # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import copy @@ -4124,6 +4124,29 @@ def emit_attributes(lines, attrs, indent): main_saved = bool(re.match(r'^(CatalogObject|DocumentObject|ChartOfAccountsObject|ChartOfCalculationTypesObject|ChartOfCharacteristicTypesObject|ExchangePlanObject|BusinessProcessObject|TaskObject)\.', t)) or ('RecordManager.' in t) if attr.get('savedData') is True or main_saved: lines.append(f'{inner}true') + # Save: сохранение значения реквизита в пользовательских настройках. true → имя; + # строка/массив → под-поля с авто-префиксом "имя." (путь с точкой / UUID / =имя — как есть). + # Нет ключа или false → не эмитим. + if 'save' in attr and attr['save'] is not None: + save_fields = [] + sv = attr['save'] + if isinstance(sv, bool): + if sv: + save_fields.append(attr_name) + else: + for e in (sv if isinstance(sv, (list, tuple)) else [sv]): + fld = str(e) + if not fld: + continue + if fld != attr_name and '.' not in fld and not re.match(r'^\d+/\d+:', fld): + fld = f'{attr_name}.{fld}' + if fld not in save_fields: + save_fields.append(fld) + if save_fields: + lines.append(f'{inner}') + for f in save_fields: + lines.append(f'{inner}\t{esc_xml(f)}') + lines.append(f'{inner}') if attr.get('fillChecking'): lines.append(f'{inner}{attr["fillChecking"]}') diff --git a/.claude/skills/form-decompile/scripts/form-decompile.ps1 b/.claude/skills/form-decompile/scripts/form-decompile.ps1 index 0df919db..1c03e0fe 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.52 — Decompile 1C managed Form.xml to JSON DSL (draft) +# form-decompile v0.53 — Decompile 1C managed Form.xml to JSON DSL (draft) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # ВНИМАНИЕ: раундтрип не гарантируется. Навык исключён из авто-использования моделью. param( @@ -1689,6 +1689,21 @@ if ($attrsNode) { $ao['title'] = '' } if ((Get-Child $a 'SavedData') -eq 'true') { $ao['savedData'] = $true } + # Save: сохранение значения реквизита в пользовательских настройках. Один Field=имя → save:true; + # иначе снимаем префикс "имя." (голое имя/UUID/прочее — как есть) → строка (1) или массив. + $saveNode = $a.SelectSingleNode("lf:Save", $ns) + if ($saveNode) { + $nm = "$($ao['name'])" + $flds = @($saveNode.SelectNodes("lf:Field", $ns) | ForEach-Object { $_.InnerText }) + if ($flds.Count -eq 1 -and $flds[0] -eq $nm) { + $ao['save'] = $true + } elseif ($flds.Count -gt 0) { + $stripped = @($flds | ForEach-Object { + if ($_ -match "^$([regex]::Escape($nm))\.(.+)$") { $matches[1] } else { $_ } + }) + if ($stripped.Count -eq 1) { $ao['save'] = $stripped[0] } else { $ao['save'] = $stripped } + } + } $fc = Get-Child $a 'FillChecking'; if ($fc) { $ao['fillChecking'] = $fc } $afo = Decompile-FunctionalOptions $a; if ($afo) { $ao['functionalOptions'] = $afo } $colsNode = $a.SelectSingleNode("lf:Columns", $ns) diff --git a/docs/form-dsl-spec.md b/docs/form-dsl-spec.md index 8e1377a1..1ed780e7 100644 --- a/docs/form-dsl-spec.md +++ b/docs/form-dsl-spec.md @@ -740,7 +740,8 @@ Pages поддерживает `pagesRepresentation`: `None`, `TabsOnTop`, `Tabs | `edit` | bool/object | Редактирование по ролям (``). См. §4.1c | | `functionalOptions` | array | Функциональные опции (`FunctionalOption.X…`). Массив имён; forgiving: `"X"`/`"FunctionalOption.X"`. Также у колонок (`columns[*]`) и команд (§7) | | `useAlways` | array | Поля, всегда читаемые (`Имя.Поле…`). Массив коротких имён полей (forgiving: с/без префикса `Имя.`). **Две формы**: этот массив на реквизите ИЛИ `useAlways: true` на колонке (`columns[*]`) — компилятор сливает. Для дин-списка — только массив (колонки не эмитятся, но формируют ``) | -| `savedData` | bool | Сохраняемые данные | +| `savedData` | bool | Сохраняемые данные (``) | +| `save` | bool/string/array | Сохранение значения в пользовательских настройках (`…`). `true` → `имя`; строка/массив строк → под-поля с авто-префиксом `имя.` (путь с точкой / UUID `1/0:…` / совпадающее с именем — берётся как есть). Нет ключа или `false` → не эмитится. Пример периода: `["Период","EndDate","StartDate","Variant"]` | | `fillChecking` | string | `Show`, `DontShow` | | `columns` | array | Колонки для ValueTable/ValueTree (`{ name, type, title?, functionalOptions?, useAlways? }`) | | `additionalColumns` | array | Доп. колонки табличных частей объекта: `[{ table: "Объект.ТабЧасть", columns: [] }]`. У главного реквизита-объекта; `` — та же грамматика, что у `columns`. Эмитятся в `` после прямых колонок | diff --git a/tests/skills/cases/form-compile/attributes-types.json b/tests/skills/cases/form-compile/attributes-types.json index 5c02b96f..5f8cc1a2 100644 --- a/tests/skills/cases/form-compile/attributes-types.json +++ b/tests/skills/cases/form-compile/attributes-types.json @@ -23,10 +23,11 @@ ], "attributes": [ { "name": "Объект", "type": "DataProcessorObject.Типы", "main": true }, - { "name": "Строка", "type": "string(200)", "view": false }, + { "name": "Строка", "type": "string(200)", "view": false, "save": true }, { "name": "Число", "type": "decimal(10,0,nonneg)", "edit": false }, { "name": "Дата", "type": "dateTime", "title": "" }, { "name": "Булево", "type": "boolean" }, + { "name": "Период", "type": "v8:StandardPeriod", "save": ["Период", "EndDate", "StartDate", "Variant"] }, { "name": "СписокЗначений", "type": "ValueList" }, { "name": "Идентификатор", "type": "v8:UUID" } ] diff --git a/tests/skills/cases/form-compile/snapshots/attributes-types/DataProcessors/Типы/Forms/Форма/Ext/Form.xml b/tests/skills/cases/form-compile/snapshots/attributes-types/DataProcessors/Типы/Forms/Форма/Ext/Form.xml index 77c25f32..7e1dd215 100644 --- a/tests/skills/cases/form-compile/snapshots/attributes-types/DataProcessors/Типы/Forms/Форма/Ext/Form.xml +++ b/tests/skills/cases/form-compile/snapshots/attributes-types/DataProcessors/Типы/Forms/Форма/Ext/Form.xml @@ -57,6 +57,9 @@ false + + Строка + @@ -96,7 +99,24 @@ <v8:Type>xs:boolean</v8:Type> </Type> </Attribute> - <Attribute name="СписокЗначений" id="18"> + <Attribute name="Период" id="18"> + <Title> + <v8:item> + <v8:lang>ru</v8:lang> + <v8:content>Период</v8:content> + </v8:item> + + + v8:StandardPeriod + + + Период + Период.EndDate + Период.StartDate + Период.Variant + + + <v8:item> <v8:lang>ru</v8:lang> @@ -107,7 +127,7 @@ <v8:Type>v8:ValueListType</v8:Type> </Type> </Attribute> - <Attribute name="Идентификатор" id="19"> + <Attribute name="Идентификатор" id="20"> <Title> <v8:item> <v8:lang>ru</v8:lang>