diff --git a/.claude/skills/form-compile/SKILL.md b/.claude/skills/form-compile/SKILL.md index c4a991eb..e5aaa5ea 100644 --- a/.claude/skills/form-compile/SKILL.md +++ b/.claude/skills/form-compile/SKILL.md @@ -241,6 +241,8 @@ powershell.exe -NoProfile -File .claude/skills/form-compile/scripts/form-compile ### Система типов +**Примитивные:** + | DSL | XML | |------------------------|----------------------------------------| | `"string"` / `"string(100)"` | `xs:string` + StringQualifiers | @@ -248,11 +250,38 @@ powershell.exe -NoProfile -File .claude/skills/form-compile/scripts/form-compile | `"decimal(10,0,nonneg)"` | с AllowedSign=Nonnegative | | `"boolean"` | `xs:boolean` | | `"date"` / `"dateTime"` / `"time"` | `xs:dateTime` + DateFractions | -| `"CatalogRef.XXX"` | `cfg:CatalogRef.XXX` | -| `"DocumentRef.XXX"` | `cfg:DocumentRef.XXX` | -| `"ValueTable"` | `v8:ValueTable` | -| `"ValueList"` | `v8:ValueListType` | -| `"Type1 \| Type2"` | составной тип | + +**Ссылочные и объектные (`cfg:Prefix.Name`):** + +| DSL | Описание | +|-----|----------| +| `"CatalogRef.XXX"` / `"CatalogObject.XXX"` | Справочник | +| `"DocumentRef.XXX"` / `"DocumentObject.XXX"` | Документ | +| `"EnumRef.XXX"` | Перечисление | +| `"DataProcessorObject.XXX"` / `"ReportObject.XXX"` | Обработка / Отчёт | +| `"InformationRegisterRecordSet.XXX"` | Набор записей регистра сведений | +| `"AccumulationRegisterRecordSet.XXX"` | Набор записей регистра накопления | +| `"DynamicList"` | Динамический список | + +Также допустимы: `ChartOfAccountsRef/Object`, `ChartOfCharacteristicTypesRef/Object`, `ChartOfCalculationTypesRef/Object`, `ExchangePlanRef/Object`, `BusinessProcessRef/Object`, `TaskRef/Object`, `AccountingRegisterRecordSet`, `InformationRegisterRecordManager`, `ConstantsSet`. + +**Платформенные:** + +| DSL | XML | +|-----|-----| +| `"ValueTable"` | `v8:ValueTable` | +| `"ValueTree"` | `v8:ValueTree` | +| `"ValueList"` | `v8:ValueListType` | +| `"TypeDescription"` | `v8:TypeDescription` | +| `"UUID"` | `v8:UUID` | +| `"FormattedString"` | `v8ui:FormattedString` | +| `"Picture"` / `"Color"` / `"Font"` | `v8ui:*` | +| `"DataCompositionSettings"` | `dcsset:DataCompositionSettings` | +| `"Type1 \| Type2"` | составной тип (несколько ``) | + +**Недопустимые типы (XDTO-ошибка при загрузке):** + +> `FormDataStructure`, `FormDataCollection`, `FormDataTree` — runtime-типы 1С, не существуют в XML-схеме. Вместо них используйте `CatalogObject.XXX`, `DocumentObject.XXX`, `DataProcessorObject.XXX`, `ValueTable`, `ValueTree`. ## Связки: элемент + реквизит diff --git a/.claude/skills/form-compile/scripts/form-compile.ps1 b/.claude/skills/form-compile/scripts/form-compile.ps1 index cdc13b79..5b6151f3 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.0 — Compile 1C managed form from JSON +# form-compile v1.1 — Compile 1C managed form from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -80,6 +80,20 @@ $script:formTypeSynonyms["бизнеспроцессссылка"] = " $script:formTypeSynonyms["задачассылка"] = "TaskRef" $script:formTypeSynonyms["определяемыйтип"] = "DefinedType" +# Known invalid types (runtime/UI types that don't exist in XDTO schema) +$script:knownInvalidTypes = @{ + "FormDataStructure" = "Runtime type. Use cfg:*Object.XXX (e.g. CatalogObject.XXX)" + "FormDataCollection" = "Runtime type. Use ValueTable" + "FormDataTree" = "Runtime type. Use ValueTree" + "FormDataTreeItem" = "Runtime type, not valid in XML" + "FormDataCollectionItem"= "Runtime type, not valid in XML" + "FormGroup" = "UI element type, not a data type" + "FormField" = "UI element type, not a data type" + "FormButton" = "UI element type, not a data type" + "FormDecoration" = "UI element type, not a data type" + "FormTable" = "UI element type, not a data type" +} + function Resolve-TypeStr { param([string]$typeStr) if (-not $typeStr) { return $typeStr } @@ -219,15 +233,21 @@ function Emit-SingleType { } # cfg: references (CatalogRef.XXX, DocumentObject.XXX, etc.) - if ($typeStr -match '^(CatalogRef|CatalogObject|DocumentRef|DocumentObject|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef|ChartOfCalculationTypesRef|ExchangePlanRef|BusinessProcessRef|TaskRef|InformationRegisterRecordSet|AccumulationRegisterRecordSet|DataProcessorObject)\.') { + if ($typeStr -match '^(CatalogRef|CatalogObject|DocumentRef|DocumentObject|EnumRef|ChartOfAccountsRef|ChartOfAccountsObject|ChartOfCharacteristicTypesRef|ChartOfCharacteristicTypesObject|ChartOfCalculationTypesRef|ChartOfCalculationTypesObject|ExchangePlanRef|ExchangePlanObject|BusinessProcessRef|BusinessProcessObject|TaskRef|TaskObject|InformationRegisterRecordSet|InformationRegisterRecordManager|AccumulationRegisterRecordSet|AccountingRegisterRecordSet|ConstantsSet|DataProcessorObject|ReportObject)\.') { X "$indentcfg:$typeStr" return } - # Fallback: emit as-is with cfg: prefix if contains dot, otherwise v8: + # Fallback with validation + if ($script:knownInvalidTypes.ContainsKey($typeStr)) { + Write-Warning "Type '$typeStr': $($script:knownInvalidTypes[$typeStr])" + } if ($typeStr.Contains('.')) { X "$indentcfg:$typeStr" } else { + if (-not $script:knownInvalidTypes.ContainsKey($typeStr)) { + Write-Warning "Unrecognized bare type '$typeStr' — will be emitted without namespace prefix" + } X "$indent$typeStr" } } diff --git a/.claude/skills/form-compile/scripts/form-compile.py b/.claude/skills/form-compile/scripts/form-compile.py index 108fe66e..37be10e6 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.0 — Compile 1C managed form from JSON +# form-compile v1.1 — Compile 1C managed form from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import json @@ -202,11 +202,27 @@ DCS_MAP = { CFG_REF_PATTERN = re.compile( r'^(CatalogRef|CatalogObject|DocumentRef|DocumentObject|EnumRef|' - r'ChartOfAccountsRef|ChartOfCharacteristicTypesRef|ChartOfCalculationTypesRef|' - r'ExchangePlanRef|BusinessProcessRef|TaskRef|' - r'InformationRegisterRecordSet|AccumulationRegisterRecordSet|DataProcessorObject)\.' + r'ChartOfAccountsRef|ChartOfAccountsObject|ChartOfCharacteristicTypesRef|ChartOfCharacteristicTypesObject|' + r'ChartOfCalculationTypesRef|ChartOfCalculationTypesObject|' + r'ExchangePlanRef|ExchangePlanObject|BusinessProcessRef|BusinessProcessObject|TaskRef|TaskObject|' + r'InformationRegisterRecordSet|InformationRegisterRecordManager|' + r'AccumulationRegisterRecordSet|AccountingRegisterRecordSet|' + r'ConstantsSet|DataProcessorObject|ReportObject)\.' ) +KNOWN_INVALID_TYPES = { + 'FormDataStructure': 'Runtime type. Use cfg:*Object.XXX (e.g. CatalogObject.XXX)', + 'FormDataCollection': 'Runtime type. Use ValueTable', + 'FormDataTree': 'Runtime type. Use ValueTree', + 'FormDataTreeItem': 'Runtime type, not valid in XML', + 'FormDataCollectionItem': 'Runtime type, not valid in XML', + 'FormGroup': 'UI element type, not a data type', + 'FormField': 'UI element type, not a data type', + 'FormButton': 'UI element type, not a data type', + 'FormDecoration': 'UI element type, not a data type', + 'FormTable': 'UI element type, not a data type', +} + _FORM_TYPE_SYNONYMS = { "строка": "string", "число": "decimal", "булево": "boolean", @@ -312,10 +328,14 @@ def emit_single_type(lines, type_str, indent): lines.append(f'{indent}cfg:{type_str}') return - # Fallback + # Fallback with validation + if type_str in KNOWN_INVALID_TYPES: + print(f"WARNING: Type '{type_str}': {KNOWN_INVALID_TYPES[type_str]}", file=sys.stderr) if '.' in type_str: lines.append(f'{indent}cfg:{type_str}') else: + if type_str not in KNOWN_INVALID_TYPES: + print(f"WARNING: Unrecognized bare type '{type_str}' — will be emitted without namespace prefix", file=sys.stderr) lines.append(f'{indent}{type_str}') diff --git a/.claude/skills/form-validate/scripts/form-validate.ps1 b/.claude/skills/form-validate/scripts/form-validate.ps1 index 6f572b98..268266a4 100644 --- a/.claude/skills/form-validate/scripts/form-validate.ps1 +++ b/.claude/skills/form-validate/scripts/form-validate.ps1 @@ -1,4 +1,4 @@ -# form-validate v1.1 — Validate 1C managed form +# form-validate v1.2 — Validate 1C managed form # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -667,6 +667,75 @@ if (-not $stopped -and -not $isExtension) { } } +# --- Check 12: Type values validation --- + +$knownInvalidTypes = @( + "FormDataStructure","FormDataCollection","FormDataTree","FormDataTreeItem","FormDataCollectionItem" + "FormGroup","FormField","FormButton","FormDecoration","FormTable" +) +$validClosedTypes = @( + "xs:boolean","xs:string","xs:decimal","xs:dateTime","xs:binary" + "v8:FillChecking","v8:Null","v8:StandardPeriod","v8:StandardBeginningDate","v8:Type" + "v8:TypeDescription","v8:UUID","v8:ValueListType","v8:ValueTable","v8:ValueTree" + "v8:Universal","v8:FixedArray","v8:FixedStructure" + "v8ui:Color","v8ui:Font","v8ui:FormattedString","v8ui:HorizontalAlign" + "v8ui:Picture","v8ui:SizeChangeMode","v8ui:VerticalAlign" + "dcsset:DataCompositionComparisonType","dcsset:DataCompositionFieldPlacement" + "dcsset:Filter","dcsset:SettingsComposer","dcsset:DataCompositionSettings" + "dcssch:DataCompositionSchema" + "dcscor:DataCompositionComparisonType","dcscor:DataCompositionGroupType" + "dcscor:DataCompositionPeriodAdditionType","dcscor:DataCompositionSortDirection","dcscor:Field" + "ent:AccountType","ent:AccumulationRecordType","ent:AccountingRecordType" +) +$validCfgPrefixes = @( + "AccountingRegisterRecordSet","AccumulationRegisterRecordSet" + "BusinessProcessObject","BusinessProcessRef" + "CatalogObject","CatalogRef" + "ChartOfAccountsObject","ChartOfAccountsRef" + "ChartOfCalculationTypesObject","ChartOfCalculationTypesRef" + "ChartOfCharacteristicTypesObject","ChartOfCharacteristicTypesRef" + "ConstantsSet","DataProcessorObject","DocumentObject","DocumentRef" + "DynamicList","EnumRef","ExchangePlanObject","ExchangePlanRef" + "InformationRegisterRecordManager","InformationRegisterRecordSet" + "ReportObject","TaskObject","TaskRef" +) + +if (-not $stopped) { + $typeNodes = $root.SelectNodes("//v8:Type", $nsMgr) + $typeOk = $true + $typeChecked = 0 + $typeInvalid = 0 + foreach ($tn in $typeNodes) { + $tv = $tn.InnerText.Trim() + if (-not $tv) { continue } + $typeChecked++ + if ($tv -in $knownInvalidTypes) { + Report-Error "12. Type '$tv': invalid runtime/UI type (not valid in XDTO schema)" + $typeOk = $false; $typeInvalid++ + continue + } + if ($tv -in $validClosedTypes) { continue } + if ($tv -match '^cfg:(.+)$') { + $cfgVal = $Matches[1] + if ($cfgVal -eq "DynamicList") { continue } + if ($cfgVal -match '^([^.]+)\.') { + if ($Matches[1] -in $validCfgPrefixes) { continue } + } + Report-Warn "12. Type '$tv': unrecognized cfg prefix" + $typeOk = $false + continue + } + if ($tv -match ':') { continue } + Report-Warn "12. Type '$tv': bare type without namespace prefix" + $typeOk = $false + } + if ($typeChecked -eq 0) { + Report-OK "12. Types: no type values to check" + } elseif ($typeOk) { + Report-OK "12. Types: $typeChecked values, all valid" + } +} + # --- Summary --- $checks = $script:okCount + $errors + $warnings diff --git a/.claude/skills/form-validate/scripts/form-validate.py b/.claude/skills/form-validate/scripts/form-validate.py index ff44ed1e..b0a753c6 100644 --- a/.claude/skills/form-validate/scripts/form-validate.py +++ b/.claude/skills/form-validate/scripts/form-validate.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# form-validate v1.1 — Validate 1C managed form +# form-validate v1.2 — Validate 1C managed form # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -13,6 +13,40 @@ V8_NS = "http://v8.1c.ru/8.1/data/core" NSMAP = {"f": F_NS, "v8": V8_NS} +KNOWN_INVALID_TYPES = { + 'FormDataStructure', 'FormDataCollection', 'FormDataTree', + 'FormDataTreeItem', 'FormDataCollectionItem', + 'FormGroup', 'FormField', 'FormButton', 'FormDecoration', 'FormTable', +} + +VALID_CLOSED_TYPES = { + 'xs:boolean', 'xs:string', 'xs:decimal', 'xs:dateTime', 'xs:binary', + 'v8:FillChecking', 'v8:Null', 'v8:StandardPeriod', 'v8:StandardBeginningDate', 'v8:Type', + 'v8:TypeDescription', 'v8:UUID', 'v8:ValueListType', 'v8:ValueTable', 'v8:ValueTree', + 'v8:Universal', 'v8:FixedArray', 'v8:FixedStructure', + 'v8ui:Color', 'v8ui:Font', 'v8ui:FormattedString', 'v8ui:HorizontalAlign', + 'v8ui:Picture', 'v8ui:SizeChangeMode', 'v8ui:VerticalAlign', + 'dcsset:DataCompositionComparisonType', 'dcsset:DataCompositionFieldPlacement', + 'dcsset:Filter', 'dcsset:SettingsComposer', 'dcsset:DataCompositionSettings', + 'dcssch:DataCompositionSchema', + 'dcscor:DataCompositionComparisonType', 'dcscor:DataCompositionGroupType', + 'dcscor:DataCompositionPeriodAdditionType', 'dcscor:DataCompositionSortDirection', 'dcscor:Field', + 'ent:AccountType', 'ent:AccumulationRecordType', 'ent:AccountingRecordType', +} + +VALID_CFG_PREFIXES = { + 'AccountingRegisterRecordSet', 'AccumulationRegisterRecordSet', + 'BusinessProcessObject', 'BusinessProcessRef', + 'CatalogObject', 'CatalogRef', + 'ChartOfAccountsObject', 'ChartOfAccountsRef', + 'ChartOfCalculationTypesObject', 'ChartOfCalculationTypesRef', + 'ChartOfCharacteristicTypesObject', 'ChartOfCharacteristicTypesRef', + 'ConstantsSet', 'DataProcessorObject', 'DocumentObject', 'DocumentRef', + 'DynamicList', 'EnumRef', 'ExchangePlanObject', 'ExchangePlanRef', + 'InformationRegisterRecordManager', 'InformationRegisterRecordSet', + 'ReportObject', 'TaskObject', 'TaskRef', +} + def localname(el): return etree.QName(el.tag).localname @@ -588,6 +622,45 @@ def main(): if call_type_without_base: report_warn("callType attributes found but no BaseForm \u2014 possible incorrect structure") + # --- Check 12: Type validation --- + if not stopped: + type_nodes = root.xpath('//v8:Type', namespaces={'v8': V8_NS}) + type_error_count = 0 + type_warn_count = 0 + type_count = len(type_nodes) + + for tn in type_nodes: + if stopped: + break + tv = (tn.text or "").strip() + if not tv: + continue + + if tv in KNOWN_INVALID_TYPES: + report_error(f'12. Type "{tv}": invalid runtime/UI type (not valid in XDTO schema)') + type_error_count += 1 + elif tv in VALID_CLOSED_TYPES: + pass # OK + elif tv.startswith("cfg:"): + suffix = tv[4:] # after "cfg:" + prefix = suffix.split(".")[0] + if prefix in VALID_CFG_PREFIXES or suffix == "DynamicList": + pass # OK + else: + report_warn(f'12. Type "{tv}": unrecognized cfg prefix') + type_warn_count += 1 + elif ":" in tv: + pass # unknown namespace, pass through + else: + report_warn(f'12. Type "{tv}": bare type without namespace prefix') + type_warn_count += 1 + + if type_error_count == 0 and type_warn_count == 0: + if type_count > 0: + report_ok(f'12. Types: {type_count} values, all valid') + else: + report_ok('12. Types: no type values to check') + # --- Finalize --- checks = ok_count + errors + warnings if errors == 0 and warnings == 0 and not detailed: diff --git a/docs/1c-form-spec.md b/docs/1c-form-spec.md index 0d811804..df8724bf 100644 --- a/docs/1c-form-spec.md +++ b/docs/1c-form-spec.md @@ -958,7 +958,19 @@ ChildItems | `cfg:BusinessProcessRef.<Имя>` | — | Ссылка на бизнес-процесс | | `cfg:TaskRef.<Имя>` | — | Ссылка на задачу | | `cfg:InformationRegisterRecordSet.<Имя>` | — | Набор записей регистра сведений | +| `cfg:InformationRegisterRecordManager.<Имя>` | — | Менеджер записи регистра сведений | | `cfg:AccumulationRegisterRecordSet.<Имя>` | — | Набор записей регистра накопления | +| `cfg:AccountingRegisterRecordSet.<Имя>` | — | Набор записей регистра бухгалтерии | +| `cfg:ChartOfAccountsObject.<Имя>` | — | Объект плана счетов | +| `cfg:ChartOfCharacteristicTypesObject.<Имя>` | — | Объект ПВХ | +| `cfg:ChartOfCalculationTypesObject.<Имя>` | — | Объект плана видов расчёта | +| `cfg:ExchangePlanObject.<Имя>` | — | Объект плана обмена | +| `cfg:BusinessProcessObject.<Имя>` | — | Объект бизнес-процесса | +| `cfg:TaskObject.<Имя>` | — | Объект задачи | +| `cfg:ConstantsSet` | — | Набор констант | +| `cfg:DataProcessorObject.<Имя>` | — | Объект обработки | +| `cfg:ReportObject.<Имя>` | — | Объект отчёта | +| `cfg:DynamicList` | — | Динамический список | #### Платформенные типы (v8:*) @@ -971,6 +983,12 @@ ChildItems | `v8:Universal` | Произвольный тип | | `v8:FixedArray` | Фиксированный массив | | `v8:FixedStructure` | Фиксированная структура | +| `v8:FillChecking` | Проверка заполнения | +| `v8:Null` | Null | +| `v8:StandardPeriod` | Стандартный период | +| `v8:StandardBeginningDate` | Стандартная начальная дата | +| `v8:Type` | Тип | +| `v8:UUID` | Уникальный идентификатор | #### UI-типы (v8ui:*) @@ -980,6 +998,9 @@ ChildItems | `v8ui:Picture` | Картинка | | `v8ui:Color` | Цвет | | `v8ui:Font` | Шрифт | +| `v8ui:SizeChangeMode` | Режим изменения размера | +| `v8ui:VerticalAlign` | Вертикальное выравнивание | +| `v8ui:HorizontalAlign` | Горизонтальное выравнивание | #### Типы СКД (dcs*:*) @@ -988,6 +1009,21 @@ ChildItems | `dcsset:DataCompositionSettings` | Настройки СКД | | `dcssch:DataCompositionSchema` | Схема СКД | | `dcscor:DataCompositionComparisonType` | Тип сравнения СКД | +| `dcsset:Filter` | Отбор СКД | +| `dcsset:SettingsComposer` | Компоновщик настроек | +| `dcsset:DataCompositionFieldPlacement` | Размещение поля СКД | +| `dcscor:DataCompositionGroupType` | Тип группировки | +| `dcscor:DataCompositionPeriodAdditionType` | Тип дополнения периода | +| `dcscor:DataCompositionSortDirection` | Направление сортировки | +| `dcscor:Field` | Поле СКД | + +#### Типы предприятия (ent:*) + +| Тип | Описание | +|-----|----------| +| `ent:AccountType` | Тип счёта (Активный/Пассивный/АктивноПассивный) | +| `ent:AccumulationRecordType` | Тип движения регистра накопления (Приход/Расход) | +| `ent:AccountingRecordType` | Тип бухгалтерской записи | #### Пустой тип diff --git a/tests/skills/cases/form-validate/snapshots/valid-form/Catalogs/Товары/Forms/Форма/Ext/Form.xml b/tests/skills/cases/form-validate/snapshots/valid-form/Catalogs/Товары/Forms/Форма/Ext/Form.xml index 57d6247e..2472f091 100644 --- a/tests/skills/cases/form-validate/snapshots/valid-form/Catalogs/Товары/Forms/Форма/Ext/Form.xml +++ b/tests/skills/cases/form-validate/snapshots/valid-form/Catalogs/Товары/Forms/Форма/Ext/Form.xml @@ -15,7 +15,7 @@ - FormDataStructure + cfg:CatalogObject.Товары true diff --git a/tests/skills/cases/form-validate/valid-form.json b/tests/skills/cases/form-validate/valid-form.json index 394229ab..6b371328 100644 --- a/tests/skills/cases/form-validate/valid-form.json +++ b/tests/skills/cases/form-validate/valid-form.json @@ -12,7 +12,7 @@ }, { "script": "form-compile/scripts/form-compile", - "input": { "title": "Тест", "attributes": [{ "name": "Объект", "type": "FormDataStructure", "main": true }], "elements": [{ "type": "InputField", "dataPath": "Объект.Наименование" }] }, + "input": { "title": "Тест", "attributes": [{ "name": "Объект", "type": "CatalogObject.Товары", "main": true }], "elements": [{ "type": "InputField", "dataPath": "Объект.Наименование" }] }, "args": { "-JsonPath": "{inputFile}", "-OutputPath": "{workDir}/Catalogs/Товары/Forms/Форма/Ext/Form.xml" } } ],