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 @@
xs:boolean
-
+
+
+
+ ru
+ Период
+
+
+
+ v8:StandardPeriod
+
+
+ Период
+ Период.EndDate
+ Период.StartDate
+ Период.Variant
+
+
+
ru
@@ -107,7 +127,7 @@
v8:ValueListType
-
+
ru