diff --git a/.claude/skills/form-add/scripts/form-add.ps1 b/.claude/skills/form-add/scripts/form-add.ps1 index 2e57542f..823d704b 100644 --- a/.claude/skills/form-add/scripts/form-add.ps1 +++ b/.claude/skills/form-add/scripts/form-add.ps1 @@ -73,7 +73,7 @@ if (-not $metaDataObject) { $supportedTypes = @( "Document", "Catalog", "DataProcessor", "Report", "ExternalDataProcessor", "ExternalReport", - "InformationRegister", "ChartOfAccounts", "ChartOfCharacteristicTypes", + "InformationRegister", "AccumulationRegister", "ChartOfAccounts", "ChartOfCharacteristicTypes", "ExchangePlan", "BusinessProcess", "Task" ) @@ -280,6 +280,7 @@ if ($Purpose -eq "List" -or $Purpose -eq "Choice") { "BusinessProcess" = "BusinessProcessObject" "Task" = "TaskObject" "InformationRegister" = "InformationRegisterRecordManager" + "AccumulationRegister" = "AccumulationRegisterRecordSet" } $mainAttrType = "$($attrTypeMap[$objectType]).$objectName" diff --git a/.claude/skills/form-add/scripts/form-add.py b/.claude/skills/form-add/scripts/form-add.py index 1b4d948c..ff894d5a 100644 --- a/.claude/skills/form-add/scripts/form-add.py +++ b/.claude/skills/form-add/scripts/form-add.py @@ -93,7 +93,7 @@ def main(): supported_types = [ "Document", "Catalog", "DataProcessor", "Report", "ExternalDataProcessor", "ExternalReport", - "InformationRegister", "ChartOfAccounts", "ChartOfCharacteristicTypes", + "InformationRegister", "AccumulationRegister", "ChartOfAccounts", "ChartOfCharacteristicTypes", "ExchangePlan", "BusinessProcess", "Task", ] @@ -310,6 +310,7 @@ def main(): "BusinessProcess": "BusinessProcessObject", "Task": "TaskObject", "InformationRegister": "InformationRegisterRecordManager", + "AccumulationRegister": "AccumulationRegisterRecordSet", } main_attr_type = f"{attr_type_map[object_type]}.{object_name}" diff --git a/.claude/skills/form-compile/presets/erp-standard.json b/.claude/skills/form-compile/presets/erp-standard.json index 0a9e4f9d..12c91c7d 100644 --- a/.claude/skills/form-compile/presets/erp-standard.json +++ b/.claude/skills/form-compile/presets/erp-standard.json @@ -40,5 +40,29 @@ "tabularSections": { "exclude": ["ДополнительныеРеквизиты", "Представления"] } + }, + + "informationRegister.record": { + "properties": { + "windowOpeningMode": "LockOwnerWindow" + } + }, + + "informationRegister.list": {}, + + "accumulationRegister.list": {}, + + "chartOfCharacteristicTypes.item": { + "basedOn": "catalog.item" + }, + + "exchangePlan.item": { + "basedOn": "catalog.item" + }, + + "chartOfAccounts.item": { + "parent": { + "title": "Подчинен счету" + } } } diff --git a/.claude/skills/form-compile/scripts/form-compile.ps1 b/.claude/skills/form-compile/scripts/form-compile.ps1 index fed79f71..c4be685a 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.5 — Compile 1C managed form from JSON or object metadata +# form-compile v1.6 — Compile 1C managed form from JSON or object metadata # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [string]$JsonPath, @@ -65,30 +65,30 @@ function Parse-ObjectMeta([string]$ObjectPath) { return ($t -match 'Ref\.' -or $t -match 'ссылка\.') } - # Helper: extract attribute list from ChildObjects - $extractAttrs = { - param($parentNode) + # Helper: extract field list from ChildObjects by tag name (Attribute, Dimension, Resource, AccountingFlag, ExtDimensionAccountingFlag) + $extractFields = { + param($parentNode, [string]$tagName) $result = @() if (-not $parentNode) { return $result } - foreach ($attrNode in $parentNode.SelectNodes("md:Attribute", $ns)) { - $ap = $attrNode.SelectSingleNode("md:Properties", $ns) - $aName = $ap.SelectSingleNode("md:Name", $ns).InnerText - $aSynNode = $ap.SelectSingleNode("md:Synonym/v8:item[v8:lang='ru']/v8:content", $ns) - $aSyn = if ($aSynNode) { $aSynNode.InnerText } else { $aName } - $aTypeNode = $ap.SelectSingleNode("md:Type", $ns) - $aType = & $extractType $aTypeNode + foreach ($fieldNode in $parentNode.SelectNodes("md:$tagName", $ns)) { + $fp = $fieldNode.SelectSingleNode("md:Properties", $ns) + $fName = $fp.SelectSingleNode("md:Name", $ns).InnerText + $fSynNode = $fp.SelectSingleNode("md:Synonym/v8:item[v8:lang='ru']/v8:content", $ns) + $fSyn = if ($fSynNode) { $fSynNode.InnerText } else { $fName } + $fTypeNode = $fp.SelectSingleNode("md:Type", $ns) + $fType = & $extractType $fTypeNode $result += @{ - Name = $aName - Synonym = $aSyn - Type = $aType - IsRef = (& $isRefType $aType) + Name = $fName + Synonym = $fSyn + Type = $fType + IsRef = (& $isRefType $fType) } } return $result } # Attributes - $attributes = @(& $extractAttrs $childObjs) + $attributes = @(& $extractFields $childObjs "Attribute") # Tabular sections $tabularSections = @() @@ -99,7 +99,7 @@ function Parse-ObjectMeta([string]$ObjectPath) { $tsSynNode = $tsp.SelectSingleNode("md:Synonym/v8:item[v8:lang='ru']/v8:content", $ns) $tsSyn = if ($tsSynNode) { $tsSynNode.InnerText } else { $tsName } $tsCo = $tsNode.SelectSingleNode("md:ChildObjects", $ns) - $tsCols = @(& $extractAttrs $tsCo) + $tsCols = @(& $extractFields $tsCo "Attribute") $tabularSections += @{ Name = $tsName Synonym = $tsSyn @@ -138,6 +138,59 @@ function Parse-ObjectMeta([string]$ObjectPath) { } $meta.Owners = $owners } + "InformationRegister" { + $meta.Dimensions = @(& $extractFields $childObjs "Dimension") + $meta.Resources = @(& $extractFields $childObjs "Resource") + $prdNode = $propsNode.SelectSingleNode("md:InformationRegisterPeriodicity", $ns) + $meta.Periodicity = if ($prdNode) { $prdNode.InnerText } else { "Nonperiodical" } + $wmNode = $propsNode.SelectSingleNode("md:WriteMode", $ns) + $meta.WriteMode = if ($wmNode) { $wmNode.InnerText } else { "Independent" } + } + "AccumulationRegister" { + $meta.Dimensions = @(& $extractFields $childObjs "Dimension") + $meta.Resources = @(& $extractFields $childObjs "Resource") + $rtNode = $propsNode.SelectSingleNode("md:RegisterType", $ns) + $meta.RegisterType = if ($rtNode) { $rtNode.InnerText } else { "Balances" } + } + "ChartOfCharacteristicTypes" { + $clNode = $propsNode.SelectSingleNode("md:CodeLength", $ns) + $meta.CodeLength = if ($clNode) { [int]$clNode.InnerText } else { 0 } + $dlNode = $propsNode.SelectSingleNode("md:DescriptionLength", $ns) + $meta.DescriptionLength = if ($dlNode) { [int]$dlNode.InnerText } else { 0 } + $hiNode = $propsNode.SelectSingleNode("md:Hierarchical", $ns) + $meta.Hierarchical = ($hiNode -and $hiNode.InnerText -eq "true") + $htNode = $propsNode.SelectSingleNode("md:HierarchyType", $ns) + $meta.HierarchyType = if ($htNode) { $htNode.InnerText } else { "HierarchyFoldersAndItems" } + $owners = @() + foreach ($ow in $propsNode.SelectNodes("md:Owners/xr:Item", $ns)) { + $owners += $ow.InnerText + } + $meta.Owners = $owners + $meta.HasValueType = $true + } + "ExchangePlan" { + $clNode = $propsNode.SelectSingleNode("md:CodeLength", $ns) + $meta.CodeLength = if ($clNode) { [int]$clNode.InnerText } else { 0 } + $dlNode = $propsNode.SelectSingleNode("md:DescriptionLength", $ns) + $meta.DescriptionLength = if ($dlNode) { [int]$dlNode.InnerText } else { 0 } + $meta.Hierarchical = $false + $meta.HierarchyType = $null + $meta.Owners = @() + } + "ChartOfAccounts" { + $clNode = $propsNode.SelectSingleNode("md:CodeLength", $ns) + $meta.CodeLength = if ($clNode) { [int]$clNode.InnerText } else { 0 } + $dlNode = $propsNode.SelectSingleNode("md:DescriptionLength", $ns) + $meta.DescriptionLength = if ($dlNode) { [int]$dlNode.InnerText } else { 0 } + $meta.Hierarchical = $true + $htNode = $propsNode.SelectSingleNode("md:HierarchyType", $ns) + $meta.HierarchyType = if ($htNode) { $htNode.InnerText } else { "HierarchyFoldersAndItems" } + $meta.Owners = @() + $maxEdNode = $propsNode.SelectSingleNode("md:MaxExtDimensionCount", $ns) + $meta.MaxExtDimensionCount = if ($maxEdNode) { [int]$maxEdNode.InnerText } else { 0 } + $meta.AccountingFlags = @(& $extractFields $childObjs "AccountingFlag") + $meta.ExtDimensionAccountingFlags = @(& $extractFields $childObjs "ExtDimensionAccountingFlag") + } } return $meta @@ -189,6 +242,41 @@ function Load-Preset([string]$PresetName, [string]$ScriptDir) { basedOn = "catalog.list"; choiceMode = $true properties = @{ windowOpeningMode = "LockOwnerWindow" } } + # ─── Register defaults ─── + "informationRegister.record" = @{ + fieldDefaults = @{ ref = @{ choiceButton = $true }; boolean = @{ element = "check" } } + properties = @{ windowOpeningMode = "LockOwnerWindow" } + } + "informationRegister.list" = @{ + columns = "all"; columnType = "labelField" + tableCommandBar = "none"; commandBar = "auto" + properties = @{} + } + "accumulationRegister.list" = @{ + columns = "all"; columnType = "labelField" + tableCommandBar = "none"; commandBar = "auto" + properties = @{} + } + # ─── Catalog-like type defaults ─── + "chartOfCharacteristicTypes.item" = @{ basedOn = "catalog.item" } + "chartOfCharacteristicTypes.folder" = @{ basedOn = "catalog.folder" } + "chartOfCharacteristicTypes.list" = @{ basedOn = "catalog.list" } + "chartOfCharacteristicTypes.choice" = @{ basedOn = "catalog.choice" } + "exchangePlan.item" = @{ basedOn = "catalog.item" } + "exchangePlan.list" = @{ basedOn = "catalog.list" } + "exchangePlan.choice" = @{ basedOn = "catalog.choice" } + # ─── ChartOfAccounts defaults ─── + "chartOfAccounts.item" = @{ + parent = @{ title = "Подчинен счету" } + fieldDefaults = @{ ref = @{ choiceButton = $true }; boolean = @{ element = "check" } } + properties = @{} + } + "chartOfAccounts.folder" = @{ + parent = @{ title = "Подчинен счету" } + properties = @{ windowOpeningMode = "LockOwnerWindow" } + } + "chartOfAccounts.list" = @{ basedOn = "catalog.list" } + "chartOfAccounts.choice" = @{ basedOn = "catalog.choice" } } # Deep merge helper @@ -826,6 +914,413 @@ function Generate-DocumentItemDSL($meta, [hashtable]$p, [hashtable]$fd) { } } +# ─── InformationRegister ────────────────────────────────────────────────── + +function Generate-InformationRegisterDSL { + param($meta, [hashtable]$presetData, [string]$purpose) + $pKey = "informationRegister.$($purpose.ToLower())" + $p = if ($presetData.ContainsKey($pKey)) { $presetData[$pKey] } else { @{} } + $fd = if ($p.fieldDefaults) { $p.fieldDefaults } else { @{ ref = @{ choiceButton = $true }; boolean = @{ element = "check" } } } + switch ($purpose) { + "Record" { return Generate-InformationRegisterRecordDSL $meta $p $fd } + "List" { return Generate-InformationRegisterListDSL $meta $p } + } +} + +function Generate-InformationRegisterRecordDSL($meta, [hashtable]$p, [hashtable]$fd) { + $elements = [ordered]@{} + $isPeriodic = $meta.Periodicity -and $meta.Periodicity -ne "Nonperiodical" + + # Period first (if periodic) + if ($isPeriodic) { + $elements["Период"] = @{ element = "input"; path = "Запись.Period" } + } + # Dimensions + foreach ($dim in $meta.Dimensions) { + if (-not (Test-DisplayableType $dim.Type)) { continue } + $elements[$dim.Name] = New-FieldElement $dim "Запись" $fd + } + # Resources + foreach ($res in $meta.Resources) { + if (-not (Test-DisplayableType $res.Type)) { continue } + $elements[$res.Name] = New-FieldElement $res "Запись" $fd + } + # Attributes + foreach ($attr in $meta.Attributes) { + if (-not (Test-DisplayableType $attr.Type)) { continue } + $elements[$attr.Name] = New-FieldElement $attr "Запись" $fd + } + + $props = [ordered]@{ windowOpeningMode = "LockOwnerWindow" } + if ($p.properties) { foreach ($k in $p.properties.Keys) { $props[$k] = $p.properties[$k] } } + + return [ordered]@{ + title = $meta.Synonym + properties = $props + elements = $elements + attributes = @( + @{ name = "Запись"; type = "InformationRegisterRecordManager.$($meta.Name)"; main = $true; savedData = $true } + ) + } +} + +function Generate-InformationRegisterListDSL($meta, [hashtable]$p) { + $isPeriodic = $meta.Periodicity -and $meta.Periodicity -ne "Nonperiodical" + $isRecorderSubordinate = $meta.WriteMode -eq "RecorderSubordinate" + + $columns = [ordered]@{} + # Period + if ($isPeriodic) { + $columns["Период"] = @{ element = "labelField"; path = "Список.Period" } + } + # Recorder/LineNumber for subordinate registers + if ($isRecorderSubordinate) { + $columns["Регистратор"] = @{ element = "labelField"; path = "Список.Recorder" } + $columns["НомерСтроки"] = @{ element = "labelField"; path = "Список.LineNumber" } + } + # Dimensions + foreach ($dim in $meta.Dimensions) { + if (-not (Test-DisplayableType $dim.Type)) { continue } + $columns[$dim.Name] = @{ element = "labelField"; path = "Список.$($dim.Name)" } + } + # Resources + foreach ($res in $meta.Resources) { + if (-not (Test-DisplayableType $res.Type)) { continue } + $el = "labelField" + if ($res.Type -match '^xs:boolean$|^Boolean$') { $el = "checkBox" } + $columns[$res.Name] = @{ element = $el; path = "Список.$($res.Name)" } + } + # Attributes + foreach ($attr in $meta.Attributes) { + if (-not (Test-DisplayableType $attr.Type)) { continue } + $el = "labelField" + if ($attr.Type -match '^xs:boolean$|^Boolean$') { $el = "checkBox" } + $columns[$attr.Name] = @{ element = $el; path = "Список.$($attr.Name)" } + } + + $tableEl = [ordered]@{ + element = "table" + path = "Список" + commandBarLocation = "none" + autoCommandBar = @{ autofill = $false } + columns = $columns + } + + $props = [ordered]@{} + if ($p.properties) { foreach ($k in $p.properties.Keys) { $props[$k] = $p.properties[$k] } } + + return [ordered]@{ + title = $meta.Synonym + properties = $props + elements = [ordered]@{ + "Список" = $tableEl + } + attributes = @( + @{ name = "Список"; type = "DynamicList"; main = $true; settings = @{ mainTable = "InformationRegister.$($meta.Name)"; dynamicDataRead = $true } } + ) + } +} + +# ─── AccumulationRegister ───────────────────────────────────────────────── + +function Generate-AccumulationRegisterDSL { + param($meta, [hashtable]$presetData, [string]$purpose) + $pKey = "accumulationRegister.$($purpose.ToLower())" + $p = if ($presetData.ContainsKey($pKey)) { $presetData[$pKey] } else { @{} } + switch ($purpose) { + "List" { return Generate-AccumulationRegisterListDSL $meta $p } + } +} + +function Generate-AccumulationRegisterListDSL($meta, [hashtable]$p) { + $columns = [ordered]@{} + # AccumulationRegisters always have Period, Recorder, LineNumber + $columns["Период"] = @{ element = "labelField"; path = "Список.Period" } + $columns["Регистратор"] = @{ element = "labelField"; path = "Список.Recorder" } + $columns["НомерСтроки"] = @{ element = "labelField"; path = "Список.LineNumber" } + # Dimensions + foreach ($dim in $meta.Dimensions) { + if (-not (Test-DisplayableType $dim.Type)) { continue } + $columns[$dim.Name] = @{ element = "labelField"; path = "Список.$($dim.Name)" } + } + # Resources + foreach ($res in $meta.Resources) { + if (-not (Test-DisplayableType $res.Type)) { continue } + $el = "labelField" + if ($res.Type -match '^xs:boolean$|^Boolean$') { $el = "checkBox" } + $columns[$res.Name] = @{ element = $el; path = "Список.$($res.Name)" } + } + # Attributes + foreach ($attr in $meta.Attributes) { + if (-not (Test-DisplayableType $attr.Type)) { continue } + $el = "labelField" + if ($attr.Type -match '^xs:boolean$|^Boolean$') { $el = "checkBox" } + $columns[$attr.Name] = @{ element = $el; path = "Список.$($attr.Name)" } + } + + $tableEl = [ordered]@{ + element = "table" + path = "Список" + commandBarLocation = "none" + autoCommandBar = @{ autofill = $false } + columns = $columns + } + + $props = [ordered]@{} + if ($p.properties) { foreach ($k in $p.properties.Keys) { $props[$k] = $p.properties[$k] } } + + return [ordered]@{ + title = $meta.Synonym + properties = $props + elements = [ordered]@{ + "Список" = $tableEl + } + attributes = @( + @{ name = "Список"; type = "DynamicList"; main = $true; settings = @{ mainTable = "AccumulationRegister.$($meta.Name)"; dynamicDataRead = $true } } + ) + } +} + +# ─── ChartOfCharacteristicTypes (delegates to Catalog) ──────────────────── + +function Generate-ChartOfCharacteristicTypesDSL { + param($meta, [hashtable]$presetData, [string]$purpose) + # Delegate to Catalog generators — meta already has CodeLength, DescriptionLength, etc. + $dsl = Generate-CatalogDSL -meta $meta -presetData $presetData -purpose $purpose + + # Post-patch: replace Catalog types with ChartOfCharacteristicTypes types + $catObjType = "CatalogObject.$($meta.Name)" + $ccoctObjType = "ChartOfCharacteristicTypesObject.$($meta.Name)" + $catListType = "Catalog.$($meta.Name)" + $ccoctListType = "ChartOfCharacteristicTypes.$($meta.Name)" + + foreach ($a in $dsl.attributes) { + if ($a.type -eq $catObjType) { $a.type = $ccoctObjType } + if ($a.type -eq "DynamicList" -and $a.settings -and $a.settings.mainTable -eq $catListType) { + $a.settings.mainTable = $ccoctListType + } + } + + # For Item forms: inject ValueType field after Code/Description + if ($purpose -eq "Item" -and $dsl.elements) { + $newElements = [ordered]@{} + $inserted = $false + foreach ($k in $dsl.elements.Keys) { + $newElements[$k] = $dsl.elements[$k] + # Insert after Description or after ГруппаКодНаименование + if (-not $inserted -and ($k -eq "Наименование" -or $k -eq "ГруппаКодНаименование")) { + $newElements["ТипЗначения"] = @{ element = "input"; path = "Объект.ValueType" } + $inserted = $true + } + } + if (-not $inserted) { + $newElements["ТипЗначения"] = @{ element = "input"; path = "Объект.ValueType" } + } + $dsl.elements = $newElements + } + + return $dsl +} + +# ─── ExchangePlan (delegates to Catalog) ────────────────────────────────── + +function Generate-ExchangePlanDSL { + param($meta, [hashtable]$presetData, [string]$purpose) + # ExchangePlans are not hierarchical and have no Folder form + $dsl = Generate-CatalogDSL -meta $meta -presetData $presetData -purpose $purpose + + # Post-patch: replace Catalog types with ExchangePlan types + $catObjType = "CatalogObject.$($meta.Name)" + $epObjType = "ExchangePlanObject.$($meta.Name)" + $catListType = "Catalog.$($meta.Name)" + $epListType = "ExchangePlan.$($meta.Name)" + + foreach ($a in $dsl.attributes) { + if ($a.type -eq $catObjType) { $a.type = $epObjType } + if ($a.type -eq "DynamicList" -and $a.settings -and $a.settings.mainTable -eq $catListType) { + $a.settings.mainTable = $epListType + } + } + + # For Item forms: inject SentNo, ReceivedNo after Code/Description + if ($purpose -eq "Item" -and $dsl.elements) { + $newElements = [ordered]@{} + $inserted = $false + foreach ($k in $dsl.elements.Keys) { + $newElements[$k] = $dsl.elements[$k] + if (-not $inserted -and ($k -eq "Наименование" -or $k -eq "ГруппаКодНаименование")) { + $newElements["НомерОтправленного"] = @{ element = "input"; path = "Объект.SentNo"; readOnly = $true } + $newElements["НомерПринятого"] = @{ element = "input"; path = "Объект.ReceivedNo"; readOnly = $true } + $inserted = $true + } + } + if (-not $inserted) { + $newElements["НомерОтправленного"] = @{ element = "input"; path = "Объект.SentNo"; readOnly = $true } + $newElements["НомерПринятого"] = @{ element = "input"; path = "Объект.ReceivedNo"; readOnly = $true } + } + $dsl.elements = $newElements + } + + return $dsl +} + +# ─── ChartOfAccounts ────────────────────────────────────────────────────── + +function Generate-ChartOfAccountsDSL { + param($meta, [hashtable]$presetData, [string]$purpose) + $pKey = "chartOfAccounts.$($purpose.ToLower())" + $p = if ($presetData.ContainsKey($pKey)) { $presetData[$pKey] } else { @{} } + $fd = if ($p.fieldDefaults) { $p.fieldDefaults } else { @{ ref = @{ choiceButton = $true }; boolean = @{ element = "check" } } } + switch ($purpose) { + "Item" { return Generate-ChartOfAccountsItemDSL $meta $p $fd $presetData } + "Folder" { return Generate-ChartOfAccountsFolderDSL $meta $p } + "List" { return Generate-ChartOfAccountsListDSL $meta $presetData } + "Choice" { return Generate-ChartOfAccountsChoiceDSL $meta $presetData } + } +} + +function Generate-ChartOfAccountsItemDSL($meta, [hashtable]$p, [hashtable]$fd, [hashtable]$presetData) { + $elements = [ordered]@{} + + # Header: Code + Parent + $headerLeft = [ordered]@{} + if ($meta.CodeLength -gt 0) { + $headerLeft["Код"] = @{ element = "input"; path = "Объект.Code" } + } + $headerRight = [ordered]@{} + if ($meta.Hierarchical) { + $parentTitle = if ($p.parent -and $p.parent.title) { $p.parent.title } else { "Подчинен счету" } + $headerRight["Родитель"] = @{ element = "input"; path = "Объект.Parent"; title = $parentTitle } + } + + if ($headerRight.Count -gt 0) { + $elements["ГруппаШапка"] = [ordered]@{ + element = "group"; groupType = "horizontal"; showTitle = $false; representation = "none" + elements = [ordered]@{ + "ГруппаШапкаЛево" = [ordered]@{ element = "group"; groupType = "vertical"; showTitle = $false; elements = $headerLeft } + "ГруппаШапкаПраво" = [ordered]@{ element = "group"; groupType = "vertical"; showTitle = $false; elements = $headerRight } + } + } + } elseif ($headerLeft.Count -gt 0) { + foreach ($k in $headerLeft.Keys) { $elements[$k] = $headerLeft[$k] } + } + + # Description + if ($meta.DescriptionLength -gt 0) { + $elements["Наименование"] = @{ element = "input"; path = "Объект.Description" } + } + + # OffBalance + $elements["Забалансовый"] = @{ element = "check"; path = "Объект.OffBalance" } + + # AccountingFlags as checkboxes + if ($meta.AccountingFlags -and $meta.AccountingFlags.Count -gt 0) { + $flagElements = [ordered]@{} + foreach ($flag in $meta.AccountingFlags) { + $flagElements[$flag.Name] = @{ element = "check"; path = "Объект.$($flag.Name)" } + } + $elements["ГруппаПризнакиУчета"] = [ordered]@{ + element = "group"; groupType = "vertical"; title = "Признаки учета" + elements = $flagElements + } + } + + # ExtDimensionTypes table + if ($meta.MaxExtDimensionCount -gt 0) { + $edCols = [ordered]@{} + $edCols["ВидСубконто"] = @{ element = "input"; path = "Объект.ExtDimensionTypes.ExtDimensionType" } + $edCols["ТолькоОбороты"] = @{ element = "check"; path = "Объект.ExtDimensionTypes.TurnoversOnly" } + if ($meta.ExtDimensionAccountingFlags) { + foreach ($edFlag in $meta.ExtDimensionAccountingFlags) { + $edCols[$edFlag.Name] = @{ element = "check"; path = "Объект.ExtDimensionTypes.$($edFlag.Name)" } + } + } + $elements["ВидыСубконто"] = [ordered]@{ + element = "table" + path = "Объект.ExtDimensionTypes" + columns = $edCols + } + } + + # Custom attributes + foreach ($attr in $meta.Attributes) { + if (-not (Test-DisplayableType $attr.Type)) { continue } + $elements[$attr.Name] = New-FieldElement $attr "Объект" $fd + } + + # Tabular sections + $tsExclude = @("ДополнительныеРеквизиты","Представления") + foreach ($ts in $meta.TabularSections) { + if ($tsExclude -contains $ts.Name) { continue } + $tsCols = [ordered]@{} + foreach ($col in $ts.Columns) { + if (-not (Test-DisplayableType $col.Type)) { continue } + $tsCols["$($ts.Name)$($col.Name)"] = New-FieldElement $col "Объект.$($ts.Name)" $fd + } + $elements[$ts.Name] = [ordered]@{ element = "table"; path = "Объект.$($ts.Name)"; columns = $tsCols } + } + + $props = [ordered]@{} + if ($p.properties) { foreach ($k in $p.properties.Keys) { $props[$k] = $p.properties[$k] } } + + return [ordered]@{ + title = $meta.Synonym + properties = $props + elements = $elements + attributes = @( + @{ name = "Объект"; type = "ChartOfAccountsObject.$($meta.Name)"; main = $true; savedData = $true } + ) + } +} + +function Generate-ChartOfAccountsFolderDSL($meta, [hashtable]$p) { + $elements = [ordered]@{} + if ($meta.CodeLength -gt 0) { + $elements["Код"] = @{ element = "input"; path = "Объект.Code" } + } + if ($meta.DescriptionLength -gt 0) { + $elements["Наименование"] = @{ element = "input"; path = "Объект.Description" } + } + if ($meta.Hierarchical) { + $parentTitle = if ($p.parent -and $p.parent.title) { $p.parent.title } else { "Подчинен счету" } + $elements["Родитель"] = @{ element = "input"; path = "Объект.Parent"; title = $parentTitle } + } + + $props = [ordered]@{ windowOpeningMode = "LockOwnerWindow" } + if ($p.properties) { foreach ($k in $p.properties.Keys) { $props[$k] = $p.properties[$k] } } + + return [ordered]@{ + title = $meta.Synonym + useForFoldersAndItems = "Folders" + properties = $props + elements = $elements + attributes = @( + @{ name = "Объект"; type = "ChartOfAccountsObject.$($meta.Name)"; main = $true; savedData = $true } + ) + } +} + +function Generate-ChartOfAccountsListDSL($meta, [hashtable]$presetData) { + # Delegate to Catalog List and patch types + $dsl = Generate-CatalogDSL -meta $meta -presetData $presetData -purpose "List" + foreach ($a in $dsl.attributes) { + if ($a.type -eq "DynamicList" -and $a.settings -and $a.settings.mainTable -eq "Catalog.$($meta.Name)") { + $a.settings.mainTable = "ChartOfAccounts.$($meta.Name)" + } + } + return $dsl +} + +function Generate-ChartOfAccountsChoiceDSL($meta, [hashtable]$presetData) { + $dsl = Generate-CatalogDSL -meta $meta -presetData $presetData -purpose "Choice" + foreach ($a in $dsl.attributes) { + if ($a.type -eq "DynamicList" -and $a.settings -and $a.settings.mainTable -eq "Catalog.$($meta.Name)") { + $a.settings.mainTable = "ChartOfAccounts.$($meta.Name)" + } + } + return $dsl +} + # ═══════════════════════════════════════════════════════════════════════════ # END OF FROM-OBJECT MODE FUNCTIONS # ═══════════════════════════════════════════════════════════════════════════ @@ -859,6 +1354,9 @@ $script:formNameToPurpose = @{ "ФормаСписка" = "List" "ФормаВыбора" = "Choice" "ФормаГруппы" = "Folder" + "ФормаЗаписи" = "Record" + "ФормаСчета" = "Item" + "ФормаУзла" = "Item" } if ($FromObject -and $JsonPath) { @@ -942,12 +1440,17 @@ if ($FromObject) { $presetData = Load-Preset -PresetName $Preset -ScriptDir $PSScriptRoot $supportedPurposes = switch ($meta.Type) { - "Document" { @("Item","List","Choice") } - "Catalog" { @("Item","Folder","List","Choice") } - default { @() } + "Document" { @("Item","List","Choice") } + "Catalog" { @("Item","Folder","List","Choice") } + "InformationRegister" { @("Record","List") } + "AccumulationRegister" { @("List") } + "ChartOfCharacteristicTypes" { @("Item","Folder","List","Choice") } + "ExchangePlan" { @("Item","List","Choice") } + "ChartOfAccounts" { @("Item","Folder","List","Choice") } + default { @() } } if ($supportedPurposes.Count -eq 0) { - Write-Error "Object type '$($meta.Type)' is not yet supported by --from-object. Supported: Document, Catalog." + Write-Error "Object type '$($meta.Type)' is not yet supported by --from-object. Supported: Document, Catalog, InformationRegister, AccumulationRegister, ChartOfCharacteristicTypes, ExchangePlan, ChartOfAccounts." exit 1 } if ($supportedPurposes -notcontains $effectivePurpose) { @@ -957,8 +1460,13 @@ if ($FromObject) { # Generate DSL $dsl = switch ($meta.Type) { - "Document" { Generate-DocumentDSL -meta $meta -presetData $presetData -purpose $effectivePurpose } - "Catalog" { Generate-CatalogDSL -meta $meta -presetData $presetData -purpose $effectivePurpose } + "Document" { Generate-DocumentDSL -meta $meta -presetData $presetData -purpose $effectivePurpose } + "Catalog" { Generate-CatalogDSL -meta $meta -presetData $presetData -purpose $effectivePurpose } + "InformationRegister" { Generate-InformationRegisterDSL -meta $meta -presetData $presetData -purpose $effectivePurpose } + "AccumulationRegister" { Generate-AccumulationRegisterDSL -meta $meta -presetData $presetData -purpose $effectivePurpose } + "ChartOfCharacteristicTypes" { Generate-ChartOfCharacteristicTypesDSL -meta $meta -presetData $presetData -purpose $effectivePurpose } + "ExchangePlan" { Generate-ExchangePlanDSL -meta $meta -presetData $presetData -purpose $effectivePurpose } + "ChartOfAccounts" { Generate-ChartOfAccountsDSL -meta $meta -presetData $presetData -purpose $effectivePurpose } } # Emit DSL if requested diff --git a/.claude/skills/form-compile/scripts/form-compile.py b/.claude/skills/form-compile/scripts/form-compile.py index a5dee7d0..70e4d532 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.5 — Compile 1C managed form from JSON or object metadata +# form-compile v1.6 — Compile 1C managed form from JSON or object metadata # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import copy @@ -84,28 +84,28 @@ def parse_object_meta(object_path): def is_ref_type(t): return bool(re.search(r'Ref\.', t) or re.search(r'\u0441\u0441\u044b\u043b\u043a\u0430\.', t)) - def extract_attrs(parent_node): - """Extract attribute list from ChildObjects.""" + def extract_fields(parent_node, tag_name='Attribute'): + """Extract field list from ChildObjects by tag name (Attribute, Dimension, Resource, AccountingFlag, ExtDimensionAccountingFlag).""" result = [] if parent_node is None: return result - for attr_node in _et_findall(parent_node, 'md:Attribute'): - ap = _et_find(attr_node, 'md:Properties') - a_name = _et_text(ap, 'md:Name') - a_syn_node = _et_find(ap, "md:Synonym/v8:item[v8:lang='ru']/v8:content") - a_syn = a_syn_node.text if a_syn_node is not None and a_syn_node.text else a_name - a_type_node = _et_find(ap, 'md:Type') - a_type = extract_type(a_type_node) + for field_node in _et_findall(parent_node, f'md:{tag_name}'): + fp = _et_find(field_node, 'md:Properties') + f_name = _et_text(fp, 'md:Name') + f_syn_node = _et_find(fp, "md:Synonym/v8:item[v8:lang='ru']/v8:content") + f_syn = f_syn_node.text if f_syn_node is not None and f_syn_node.text else f_name + f_type_node = _et_find(fp, 'md:Type') + f_type = extract_type(f_type_node) result.append({ - 'Name': a_name, - 'Synonym': a_syn, - 'Type': a_type, - 'IsRef': is_ref_type(a_type), + 'Name': f_name, + 'Synonym': f_syn, + 'Type': f_type, + 'IsRef': is_ref_type(f_type), }) return result # Attributes - attributes = extract_attrs(child_objs) + attributes = extract_fields(child_objs, 'Attribute') # Tabular sections tabular_sections = [] @@ -116,7 +116,7 @@ def parse_object_meta(object_path): ts_syn_node = _et_find(tsp, "md:Synonym/v8:item[v8:lang='ru']/v8:content") ts_syn = ts_syn_node.text if ts_syn_node is not None and ts_syn_node.text else ts_name ts_co = _et_find(ts_node, 'md:ChildObjects') - ts_cols = extract_attrs(ts_co) + ts_cols = extract_fields(ts_co, 'Attribute') tabular_sections.append({ 'Name': ts_name, 'Synonym': ts_syn, @@ -149,6 +149,54 @@ def parse_object_meta(object_path): if ow.text: owners.append(ow.text) meta['Owners'] = owners + elif obj_type == 'InformationRegister': + meta['Dimensions'] = extract_fields(child_objs, 'Dimension') + meta['Resources'] = extract_fields(child_objs, 'Resource') + prd_node = _et_find(props_node, 'md:InformationRegisterPeriodicity') + meta['Periodicity'] = prd_node.text if prd_node is not None and prd_node.text else 'Nonperiodical' + wm_node = _et_find(props_node, 'md:WriteMode') + meta['WriteMode'] = wm_node.text if wm_node is not None and wm_node.text else 'Independent' + elif obj_type == 'AccumulationRegister': + meta['Dimensions'] = extract_fields(child_objs, 'Dimension') + meta['Resources'] = extract_fields(child_objs, 'Resource') + rt_node = _et_find(props_node, 'md:RegisterType') + meta['RegisterType'] = rt_node.text if rt_node is not None and rt_node.text else 'Balances' + elif obj_type == 'ChartOfCharacteristicTypes': + cl_node = _et_find(props_node, 'md:CodeLength') + meta['CodeLength'] = int(cl_node.text) if cl_node is not None and cl_node.text else 0 + dl_node = _et_find(props_node, 'md:DescriptionLength') + meta['DescriptionLength'] = int(dl_node.text) if dl_node is not None and dl_node.text else 0 + hi_node = _et_find(props_node, 'md:Hierarchical') + meta['Hierarchical'] = (hi_node is not None and hi_node.text == 'true') + ht_node = _et_find(props_node, 'md:HierarchyType') + meta['HierarchyType'] = ht_node.text if ht_node is not None and ht_node.text else 'HierarchyFoldersAndItems' + owners = [] + for ow in _et_findall(props_node, 'md:Owners/xr:Item'): + if ow.text: + owners.append(ow.text) + meta['Owners'] = owners + meta['HasValueType'] = True + elif obj_type == 'ExchangePlan': + cl_node = _et_find(props_node, 'md:CodeLength') + meta['CodeLength'] = int(cl_node.text) if cl_node is not None and cl_node.text else 0 + dl_node = _et_find(props_node, 'md:DescriptionLength') + meta['DescriptionLength'] = int(dl_node.text) if dl_node is not None and dl_node.text else 0 + meta['Hierarchical'] = False + meta['HierarchyType'] = None + meta['Owners'] = [] + elif obj_type == 'ChartOfAccounts': + cl_node = _et_find(props_node, 'md:CodeLength') + meta['CodeLength'] = int(cl_node.text) if cl_node is not None and cl_node.text else 0 + dl_node = _et_find(props_node, 'md:DescriptionLength') + meta['DescriptionLength'] = int(dl_node.text) if dl_node is not None and dl_node.text else 0 + meta['Hierarchical'] = True + ht_node = _et_find(props_node, 'md:HierarchyType') + meta['HierarchyType'] = ht_node.text if ht_node is not None and ht_node.text else 'HierarchyFoldersAndItems' + meta['Owners'] = [] + max_ed_node = _et_find(props_node, 'md:MaxExtDimensionCount') + meta['MaxExtDimensionCount'] = int(max_ed_node.text) if max_ed_node is not None and max_ed_node.text else 0 + meta['AccountingFlags'] = extract_fields(child_objs, 'AccountingFlag') + meta['ExtDimensionAccountingFlags'] = extract_fields(child_objs, 'ExtDimensionAccountingFlag') return meta @@ -216,6 +264,41 @@ def load_preset(preset_name, script_dir, out_path_resolved): 'basedOn': 'catalog.list', 'choiceMode': True, 'properties': {'windowOpeningMode': 'LockOwnerWindow'}, }, + # --- Register defaults --- + 'informationRegister.record': { + 'fieldDefaults': {'ref': {'choiceButton': True}, 'boolean': {'element': 'check'}}, + 'properties': {'windowOpeningMode': 'LockOwnerWindow'}, + }, + 'informationRegister.list': { + 'columns': 'all', 'columnType': 'labelField', + 'tableCommandBar': 'none', 'commandBar': 'auto', + 'properties': {}, + }, + 'accumulationRegister.list': { + 'columns': 'all', 'columnType': 'labelField', + 'tableCommandBar': 'none', 'commandBar': 'auto', + 'properties': {}, + }, + # --- Catalog-like type defaults --- + 'chartOfCharacteristicTypes.item': {'basedOn': 'catalog.item'}, + 'chartOfCharacteristicTypes.folder': {'basedOn': 'catalog.folder'}, + 'chartOfCharacteristicTypes.list': {'basedOn': 'catalog.list'}, + 'chartOfCharacteristicTypes.choice': {'basedOn': 'catalog.choice'}, + 'exchangePlan.item': {'basedOn': 'catalog.item'}, + 'exchangePlan.list': {'basedOn': 'catalog.list'}, + 'exchangePlan.choice': {'basedOn': 'catalog.choice'}, + # --- ChartOfAccounts defaults --- + 'chartOfAccounts.item': { + 'parent': {'title': '\u041f\u043e\u0434\u0447\u0438\u043d\u0435\u043d \u0441\u0447\u0435\u0442\u0443'}, + 'fieldDefaults': {'ref': {'choiceButton': True}, 'boolean': {'element': 'check'}}, + 'properties': {}, + }, + 'chartOfAccounts.folder': { + 'parent': {'title': '\u041f\u043e\u0434\u0447\u0438\u043d\u0435\u043d \u0441\u0447\u0435\u0442\u0443'}, + 'properties': {'windowOpeningMode': 'LockOwnerWindow'}, + }, + 'chartOfAccounts.list': {'basedOn': 'catalog.list'}, + 'chartOfAccounts.choice': {'basedOn': 'catalog.choice'}, } # Try built-in preset @@ -768,6 +851,403 @@ def generate_document_item_dsl(meta, p, fd): ]) +# --- InformationRegister DSL generators --- + +def generate_information_register_dsl(meta, preset_data, purpose): + p_key = f"informationRegister.{purpose.lower()}" + p = preset_data.get(p_key, {}) + fd = p.get('fieldDefaults') or {'ref': {'choiceButton': True}, 'boolean': {'element': 'check'}} + dispatch = { + 'Record': lambda: generate_information_register_record_dsl(meta, p, fd), + 'List': lambda: generate_information_register_list_dsl(meta, p), + } + return dispatch[purpose]() + + +def generate_information_register_record_dsl(meta, p, fd): + elements = OrderedDict() + is_periodic = meta.get('Periodicity') and meta['Periodicity'] != 'Nonperiodical' + + # Period first (if periodic) + if is_periodic: + elements['\u041f\u0435\u0440\u0438\u043e\u0434'] = {'element': 'input', 'path': '\u0417\u0430\u043f\u0438\u0441\u044c.Period'} + # Dimensions + for dim in meta.get('Dimensions', []): + if not is_displayable_type(dim['Type']): + continue + elements[dim['Name']] = new_field_element(dim['Name'], f"\u0417\u0430\u043f\u0438\u0441\u044c.{dim['Name']}", dim['Type'], fd) + # Resources + for res in meta.get('Resources', []): + if not is_displayable_type(res['Type']): + continue + elements[res['Name']] = new_field_element(res['Name'], f"\u0417\u0430\u043f\u0438\u0441\u044c.{res['Name']}", res['Type'], fd) + # Attributes + for attr in meta['Attributes']: + if not is_displayable_type(attr['Type']): + continue + elements[attr['Name']] = new_field_element(attr['Name'], f"\u0417\u0430\u043f\u0438\u0441\u044c.{attr['Name']}", attr['Type'], fd) + + props = OrderedDict([('windowOpeningMode', 'LockOwnerWindow')]) + if p.get('properties'): + for k in p['properties']: + props[k] = p['properties'][k] + + return OrderedDict([ + ('title', meta['Synonym']), + ('properties', props), + ('elements', elements), + ('attributes', [ + {'name': '\u0417\u0430\u043f\u0438\u0441\u044c', 'type': f"InformationRegisterRecordManager.{meta['Name']}", 'main': True, 'savedData': True} + ]), + ]) + + +def generate_information_register_list_dsl(meta, p): + is_periodic = meta.get('Periodicity') and meta['Periodicity'] != 'Nonperiodical' + is_recorder_subordinate = meta.get('WriteMode') == 'RecorderSubordinate' + + columns = OrderedDict() + # Period + if is_periodic: + columns['\u041f\u0435\u0440\u0438\u043e\u0434'] = {'element': 'labelField', 'path': '\u0421\u043f\u0438\u0441\u043e\u043a.Period'} + # Recorder/LineNumber for subordinate registers + if is_recorder_subordinate: + columns['\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440'] = {'element': 'labelField', 'path': '\u0421\u043f\u0438\u0441\u043e\u043a.Recorder'} + columns['\u041d\u043e\u043c\u0435\u0440\u0421\u0442\u0440\u043e\u043a\u0438'] = {'element': 'labelField', 'path': '\u0421\u043f\u0438\u0441\u043e\u043a.LineNumber'} + # Dimensions + for dim in meta.get('Dimensions', []): + if not is_displayable_type(dim['Type']): + continue + columns[dim['Name']] = {'element': 'labelField', 'path': f"\u0421\u043f\u0438\u0441\u043e\u043a.{dim['Name']}"} + # Resources + for res in meta.get('Resources', []): + if not is_displayable_type(res['Type']): + continue + el = 'labelField' + if re.match(r'^xs:boolean$|^Boolean$', res['Type']): + el = 'checkBox' + columns[res['Name']] = {'element': el, 'path': f"\u0421\u043f\u0438\u0441\u043e\u043a.{res['Name']}"} + # Attributes + for attr in meta['Attributes']: + if not is_displayable_type(attr['Type']): + continue + el = 'labelField' + if re.match(r'^xs:boolean$|^Boolean$', attr['Type']): + el = 'checkBox' + columns[attr['Name']] = {'element': el, 'path': f"\u0421\u043f\u0438\u0441\u043e\u043a.{attr['Name']}"} + + table_el = OrderedDict([ + ('element', 'table'), + ('path', '\u0421\u043f\u0438\u0441\u043e\u043a'), + ('commandBarLocation', 'none'), + ('autoCommandBar', {'autofill': False}), + ('columns', columns), + ]) + + props = OrderedDict() + if p.get('properties'): + for k in p['properties']: + props[k] = p['properties'][k] + + return OrderedDict([ + ('title', meta['Synonym']), + ('properties', props), + ('elements', OrderedDict([('\u0421\u043f\u0438\u0441\u043e\u043a', table_el)])), + ('attributes', [ + {'name': '\u0421\u043f\u0438\u0441\u043e\u043a', 'type': 'DynamicList', 'main': True, 'settings': {'mainTable': f"InformationRegister.{meta['Name']}", 'dynamicDataRead': True}} + ]), + ]) + + +# --- AccumulationRegister DSL generators --- + +def generate_accumulation_register_dsl(meta, preset_data, purpose): + p_key = f"accumulationRegister.{purpose.lower()}" + p = preset_data.get(p_key, {}) + dispatch = { + 'List': lambda: generate_accumulation_register_list_dsl(meta, p), + } + return dispatch[purpose]() + + +def generate_accumulation_register_list_dsl(meta, p): + columns = OrderedDict() + # AccumulationRegisters always have Period, Recorder, LineNumber + columns['\u041f\u0435\u0440\u0438\u043e\u0434'] = {'element': 'labelField', 'path': '\u0421\u043f\u0438\u0441\u043e\u043a.Period'} + columns['\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440'] = {'element': 'labelField', 'path': '\u0421\u043f\u0438\u0441\u043e\u043a.Recorder'} + columns['\u041d\u043e\u043c\u0435\u0440\u0421\u0442\u0440\u043e\u043a\u0438'] = {'element': 'labelField', 'path': '\u0421\u043f\u0438\u0441\u043e\u043a.LineNumber'} + # Dimensions + for dim in meta.get('Dimensions', []): + if not is_displayable_type(dim['Type']): + continue + columns[dim['Name']] = {'element': 'labelField', 'path': f"\u0421\u043f\u0438\u0441\u043e\u043a.{dim['Name']}"} + # Resources + for res in meta.get('Resources', []): + if not is_displayable_type(res['Type']): + continue + el = 'labelField' + if re.match(r'^xs:boolean$|^Boolean$', res['Type']): + el = 'checkBox' + columns[res['Name']] = {'element': el, 'path': f"\u0421\u043f\u0438\u0441\u043e\u043a.{res['Name']}"} + # Attributes + for attr in meta['Attributes']: + if not is_displayable_type(attr['Type']): + continue + el = 'labelField' + if re.match(r'^xs:boolean$|^Boolean$', attr['Type']): + el = 'checkBox' + columns[attr['Name']] = {'element': el, 'path': f"\u0421\u043f\u0438\u0441\u043e\u043a.{attr['Name']}"} + + table_el = OrderedDict([ + ('element', 'table'), + ('path', '\u0421\u043f\u0438\u0441\u043e\u043a'), + ('commandBarLocation', 'none'), + ('autoCommandBar', {'autofill': False}), + ('columns', columns), + ]) + + props = OrderedDict() + if p.get('properties'): + for k in p['properties']: + props[k] = p['properties'][k] + + return OrderedDict([ + ('title', meta['Synonym']), + ('properties', props), + ('elements', OrderedDict([('\u0421\u043f\u0438\u0441\u043e\u043a', table_el)])), + ('attributes', [ + {'name': '\u0421\u043f\u0438\u0441\u043e\u043a', 'type': 'DynamicList', 'main': True, 'settings': {'mainTable': f"AccumulationRegister.{meta['Name']}", 'dynamicDataRead': True}} + ]), + ]) + + +# --- ChartOfCharacteristicTypes (delegates to Catalog) --- + +def generate_chart_of_characteristic_types_dsl(meta, preset_data, purpose): + # Delegate to Catalog generators -- meta already has CodeLength, DescriptionLength, etc. + dsl = generate_catalog_dsl(meta, preset_data, purpose) + + # Post-patch: replace Catalog types with ChartOfCharacteristicTypes types + cat_obj_type = f"CatalogObject.{meta['Name']}" + ccoct_obj_type = f"ChartOfCharacteristicTypesObject.{meta['Name']}" + cat_list_type = f"Catalog.{meta['Name']}" + ccoct_list_type = f"ChartOfCharacteristicTypes.{meta['Name']}" + + for a in dsl['attributes']: + if a.get('type') == cat_obj_type: + a['type'] = ccoct_obj_type + if a.get('type') == 'DynamicList' and a.get('settings') and a['settings'].get('mainTable') == cat_list_type: + a['settings']['mainTable'] = ccoct_list_type + + # For Item forms: inject ValueType field after Description/ГруппаКодНаименование + if purpose == 'Item' and dsl.get('elements'): + vt_el = OrderedDict([('input', '\u0422\u0438\u043f\u0417\u043d\u0430\u0447\u0435\u043d\u0438\u044f'), ('path', '\u041e\u0431\u044a\u0435\u043a\u0442.ValueType')]) + els = dsl['elements'] + if isinstance(els, list): + inserted = False + new_els = [] + for el in els: + new_els.append(el) + if not inserted and isinstance(el, dict): + name = el.get('input') or el.get('group') or '' + if name in ('\u041d\u0430\u0438\u043c\u0435\u043d\u043e\u0432\u0430\u043d\u0438\u0435', '\u0413\u0440\u0443\u043f\u043f\u0430\u041a\u043e\u0434\u041d\u0430\u0438\u043c\u0435\u043d\u043e\u0432\u0430\u043d\u0438\u0435'): + new_els.append(vt_el) + inserted = True + if not inserted: + new_els.append(vt_el) + dsl['elements'] = new_els + + return dsl + + +# --- ExchangePlan (delegates to Catalog) --- + +def generate_exchange_plan_dsl(meta, preset_data, purpose): + # ExchangePlans are not hierarchical and have no Folder form + dsl = generate_catalog_dsl(meta, preset_data, purpose) + + # Post-patch: replace Catalog types with ExchangePlan types + cat_obj_type = f"CatalogObject.{meta['Name']}" + ep_obj_type = f"ExchangePlanObject.{meta['Name']}" + cat_list_type = f"Catalog.{meta['Name']}" + ep_list_type = f"ExchangePlan.{meta['Name']}" + + for a in dsl['attributes']: + if a.get('type') == cat_obj_type: + a['type'] = ep_obj_type + if a.get('type') == 'DynamicList' and a.get('settings') and a['settings'].get('mainTable') == cat_list_type: + a['settings']['mainTable'] = ep_list_type + + # For Item forms: inject SentNo, ReceivedNo after Code/Description + if purpose == 'Item' and dsl.get('elements'): + sent_el = OrderedDict([('input', '\u041d\u043e\u043c\u0435\u0440\u041e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u043e\u0433\u043e'), ('path', '\u041e\u0431\u044a\u0435\u043a\u0442.SentNo'), ('readOnly', True)]) + recv_el = OrderedDict([('input', '\u041d\u043e\u043c\u0435\u0440\u041f\u0440\u0438\u043d\u044f\u0442\u043e\u0433\u043e'), ('path', '\u041e\u0431\u044a\u0435\u043a\u0442.ReceivedNo'), ('readOnly', True)]) + els = dsl['elements'] + if isinstance(els, list): + inserted = False + new_els = [] + for el in els: + new_els.append(el) + if not inserted and isinstance(el, dict): + name = el.get('input') or el.get('group') or '' + if name in ('\u041d\u0430\u0438\u043c\u0435\u043d\u043e\u0432\u0430\u043d\u0438\u0435', '\u0413\u0440\u0443\u043f\u043f\u0430\u041a\u043e\u0434\u041d\u0430\u0438\u043c\u0435\u043d\u043e\u0432\u0430\u043d\u0438\u0435'): + new_els.append(sent_el) + new_els.append(recv_el) + inserted = True + if not inserted: + new_els.append(sent_el) + new_els.append(recv_el) + dsl['elements'] = new_els + + return dsl + + +# --- ChartOfAccounts DSL generators --- + +def generate_chart_of_accounts_dsl(meta, preset_data, purpose): + p_key = f"chartOfAccounts.{purpose.lower()}" + p = preset_data.get(p_key, {}) + fd = p.get('fieldDefaults') or {'ref': {'choiceButton': True}, 'boolean': {'element': 'check'}} + dispatch = { + 'Item': lambda: generate_chart_of_accounts_item_dsl(meta, p, fd, preset_data), + 'Folder': lambda: generate_chart_of_accounts_folder_dsl(meta, p), + 'List': lambda: generate_chart_of_accounts_list_dsl(meta, preset_data), + 'Choice': lambda: generate_chart_of_accounts_choice_dsl(meta, preset_data), + } + return dispatch[purpose]() + + +def generate_chart_of_accounts_item_dsl(meta, p, fd, preset_data): + elements = OrderedDict() + + # Header: Code + Parent + header_left = OrderedDict() + if meta.get('CodeLength', 0) > 0: + header_left['\u041a\u043e\u0434'] = {'element': 'input', 'path': '\u041e\u0431\u044a\u0435\u043a\u0442.Code'} + header_right = OrderedDict() + if meta.get('Hierarchical'): + parent_title = (p.get('parent') or {}).get('title', '\u041f\u043e\u0434\u0447\u0438\u043d\u0435\u043d \u0441\u0447\u0435\u0442\u0443') + header_right['\u0420\u043e\u0434\u0438\u0442\u0435\u043b\u044c'] = {'element': 'input', 'path': '\u041e\u0431\u044a\u0435\u043a\u0442.Parent', 'title': parent_title} + + if len(header_right) > 0: + elements['\u0413\u0440\u0443\u043f\u043f\u0430\u0428\u0430\u043f\u043a\u0430'] = OrderedDict([ + ('element', 'group'), ('groupType', 'horizontal'), ('showTitle', False), ('representation', 'none'), + ('elements', OrderedDict([ + ('\u0413\u0440\u0443\u043f\u043f\u0430\u0428\u0430\u043f\u043a\u0430\u041b\u0435\u0432\u043e', OrderedDict([('element', 'group'), ('groupType', 'vertical'), ('showTitle', False), ('elements', header_left)])), + ('\u0413\u0440\u0443\u043f\u043f\u0430\u0428\u0430\u043f\u043a\u0430\u041f\u0440\u0430\u0432\u043e', OrderedDict([('element', 'group'), ('groupType', 'vertical'), ('showTitle', False), ('elements', header_right)])), + ])), + ]) + elif len(header_left) > 0: + for k, v in header_left.items(): + elements[k] = v + + # Description + if meta.get('DescriptionLength', 0) > 0: + elements['\u041d\u0430\u0438\u043c\u0435\u043d\u043e\u0432\u0430\u043d\u0438\u0435'] = {'element': 'input', 'path': '\u041e\u0431\u044a\u0435\u043a\u0442.Description'} + + # OffBalance + elements['\u0417\u0430\u0431\u0430\u043b\u0430\u043d\u0441\u043e\u0432\u044b\u0439'] = {'element': 'check', 'path': '\u041e\u0431\u044a\u0435\u043a\u0442.OffBalance'} + + # AccountingFlags as checkboxes + if meta.get('AccountingFlags') and len(meta['AccountingFlags']) > 0: + flag_elements = OrderedDict() + for flag in meta['AccountingFlags']: + flag_elements[flag['Name']] = {'element': 'check', 'path': f"\u041e\u0431\u044a\u0435\u043a\u0442.{flag['Name']}"} + elements['\u0413\u0440\u0443\u043f\u043f\u0430\u041f\u0440\u0438\u0437\u043d\u0430\u043a\u0438\u0423\u0447\u0435\u0442\u0430'] = OrderedDict([ + ('element', 'group'), ('groupType', 'vertical'), ('title', '\u041f\u0440\u0438\u0437\u043d\u0430\u043a\u0438 \u0443\u0447\u0435\u0442\u0430'), + ('elements', flag_elements), + ]) + + # ExtDimensionTypes table + if meta.get('MaxExtDimensionCount', 0) > 0: + ed_cols = OrderedDict() + ed_cols['\u0412\u0438\u0434\u0421\u0443\u0431\u043a\u043e\u043d\u0442\u043e'] = {'element': 'input', 'path': '\u041e\u0431\u044a\u0435\u043a\u0442.ExtDimensionTypes.ExtDimensionType'} + ed_cols['\u0422\u043e\u043b\u044c\u043a\u043e\u041e\u0431\u043e\u0440\u043e\u0442\u044b'] = {'element': 'check', 'path': '\u041e\u0431\u044a\u0435\u043a\u0442.ExtDimensionTypes.TurnoversOnly'} + if meta.get('ExtDimensionAccountingFlags'): + for ed_flag in meta['ExtDimensionAccountingFlags']: + ed_cols[ed_flag['Name']] = {'element': 'check', 'path': f"\u041e\u0431\u044a\u0435\u043a\u0442.ExtDimensionTypes.{ed_flag['Name']}"} + elements['\u0412\u0438\u0434\u044b\u0421\u0443\u0431\u043a\u043e\u043d\u0442\u043e'] = OrderedDict([ + ('element', 'table'), + ('path', '\u041e\u0431\u044a\u0435\u043a\u0442.ExtDimensionTypes'), + ('columns', ed_cols), + ]) + + # Custom attributes + for attr in meta['Attributes']: + if not is_displayable_type(attr['Type']): + continue + elements[attr['Name']] = new_field_element(attr['Name'], f"\u041e\u0431\u044a\u0435\u043a\u0442.{attr['Name']}", attr['Type'], fd) + + # Tabular sections + ts_exclude = ['\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435\u0420\u0435\u043a\u0432\u0438\u0437\u0438\u0442\u044b', '\u041f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f'] + for ts in meta['TabularSections']: + if ts['Name'] in ts_exclude: + continue + ts_cols = OrderedDict() + for col in ts['Columns']: + if not is_displayable_type(col['Type']): + continue + ts_cols[f"{ts['Name']}{col['Name']}"] = new_field_element(col['Name'], f"\u041e\u0431\u044a\u0435\u043a\u0442.{ts['Name']}.{col['Name']}", col['Type'], fd) + elements[ts['Name']] = OrderedDict([('element', 'table'), ('path', f"\u041e\u0431\u044a\u0435\u043a\u0442.{ts['Name']}"), ('columns', ts_cols)]) + + props = OrderedDict() + if p.get('properties'): + for k in p['properties']: + props[k] = p['properties'][k] + + return OrderedDict([ + ('title', meta['Synonym']), + ('properties', props), + ('elements', elements), + ('attributes', [ + {'name': '\u041e\u0431\u044a\u0435\u043a\u0442', 'type': f"ChartOfAccountsObject.{meta['Name']}", 'main': True, 'savedData': True} + ]), + ]) + + +def generate_chart_of_accounts_folder_dsl(meta, p): + elements = OrderedDict() + if meta.get('CodeLength', 0) > 0: + elements['\u041a\u043e\u0434'] = {'element': 'input', 'path': '\u041e\u0431\u044a\u0435\u043a\u0442.Code'} + if meta.get('DescriptionLength', 0) > 0: + elements['\u041d\u0430\u0438\u043c\u0435\u043d\u043e\u0432\u0430\u043d\u0438\u0435'] = {'element': 'input', 'path': '\u041e\u0431\u044a\u0435\u043a\u0442.Description'} + if meta.get('Hierarchical'): + parent_title = (p.get('parent') or {}).get('title', '\u041f\u043e\u0434\u0447\u0438\u043d\u0435\u043d \u0441\u0447\u0435\u0442\u0443') + elements['\u0420\u043e\u0434\u0438\u0442\u0435\u043b\u044c'] = {'element': 'input', 'path': '\u041e\u0431\u044a\u0435\u043a\u0442.Parent', 'title': parent_title} + + props = OrderedDict([('windowOpeningMode', 'LockOwnerWindow')]) + if p.get('properties'): + for k in p['properties']: + props[k] = p['properties'][k] + + return OrderedDict([ + ('title', meta['Synonym']), + ('useForFoldersAndItems', 'Folders'), + ('properties', props), + ('elements', elements), + ('attributes', [ + {'name': '\u041e\u0431\u044a\u0435\u043a\u0442', 'type': f"ChartOfAccountsObject.{meta['Name']}", 'main': True, 'savedData': True} + ]), + ]) + + +def generate_chart_of_accounts_list_dsl(meta, preset_data): + # Delegate to Catalog List and patch types + dsl = generate_catalog_dsl(meta, preset_data, 'List') + for a in dsl['attributes']: + if a.get('type') == 'DynamicList' and a.get('settings') and a['settings'].get('mainTable') == f"Catalog.{meta['Name']}": + a['settings']['mainTable'] = f"ChartOfAccounts.{meta['Name']}" + return dsl + + +def generate_chart_of_accounts_choice_dsl(meta, preset_data): + dsl = generate_catalog_dsl(meta, preset_data, 'Choice') + for a in dsl['attributes']: + if a.get('type') == 'DynamicList' and a.get('settings') and a['settings'].get('mainTable') == f"Catalog.{meta['Name']}": + a['settings']['mainTable'] = f"ChartOfAccounts.{meta['Name']}" + return dsl + + # ═══════════════════════════════════════════════════════════════════════════ # END OF FROM-OBJECT MODE FUNCTIONS # ═══════════════════════════════════════════════════════════════════════════ @@ -1817,6 +2297,95 @@ def detect_format_version(d): return "2.17" +def _normalize_elements(defn): + """Convert dict-style elements from --from-object generators to list-style expected by compiler. + Generator format: elements = {"ИмяЭлемента": {"element": "input", "path": "..."}, ...} + Compiler format: elements = [{"input": "ИмяЭлемента", "path": "..."}, ...] + Also handles nested 'elements' in groups and 'columns' in tables recursively. + """ + def convert_elements(els): + if isinstance(els, list): + # Already list format — but may have nested dicts inside groups + result = [] + for el in els: + if isinstance(el, dict): + el = dict(el) # copy + if 'elements' in el and isinstance(el['elements'], dict): + el['elements'] = convert_elements(el['elements']) + if 'columns' in el and isinstance(el['columns'], dict): + el['columns'] = convert_columns(el['columns']) + result.append(el) + return result + if isinstance(els, dict): + result = [] + for name, props in els.items(): + if not isinstance(props, dict): + continue + new_el = {} + el_type = props.get('element', 'input') + # Map element type to the key name used in JSON DSL + type_map = { + 'input': 'input', 'check': 'check', 'labelField': 'labelField', + 'table': 'table', 'group': 'group', 'pages': 'pages', + 'page': 'page', 'label': 'label', 'button': 'button', + 'checkBox': 'check', 'radioButton': 'radioButton', + 'pictureField': 'pictureField', + } + mapped_type = type_map.get(el_type, el_type) + new_el[mapped_type] = name + for k, v in props.items(): + if k == 'element': + continue + if k == 'elements' and isinstance(v, dict): + new_el['elements'] = convert_elements(v) + elif k == 'columns' and isinstance(v, dict): + new_el['columns'] = convert_columns(v) + elif k == 'groupType': + # groupType → group property in DSL + new_el['group'] = v + elif k == 'showTitle': + new_el['showTitle'] = v + elif k == 'representation': + new_el['representation'] = v + elif k == 'autoCommandBar': + new_el['autoCommandBar'] = v + elif k == 'commandBarLocation': + new_el['commandBarLocation'] = v + else: + new_el[k] = v + result.append(new_el) + return result + return els + + def convert_columns(cols): + if isinstance(cols, list): + return cols + if isinstance(cols, dict): + result = [] + for name, props in cols.items(): + if not isinstance(props, dict): + continue + new_col = {} + el_type = props.get('element', 'input') + type_map = { + 'input': 'input', 'check': 'check', 'labelField': 'labelField', + 'checkBox': 'check', + } + mapped_type = type_map.get(el_type, el_type) + new_col[mapped_type] = name + for k, v in props.items(): + if k == 'element': + continue + new_col[k] = v + result.append(new_col) + return result + return cols + + if 'elements' in defn: + defn['elements'] = convert_elements(defn['elements']) + return defn + + def main(): sys.stdout.reconfigure(encoding="utf-8") sys.stderr.reconfigure(encoding="utf-8") @@ -1839,6 +2408,9 @@ def main(): '\u0424\u043e\u0440\u043c\u0430\u0421\u043f\u0438\u0441\u043a\u0430': 'List', # ФормаСписка '\u0424\u043e\u0440\u043c\u0430\u0412\u044b\u0431\u043e\u0440\u0430': 'Choice', # ФормаВыбора '\u0424\u043e\u0440\u043c\u0430\u0413\u0440\u0443\u043f\u043f\u044b': 'Folder', # ФормаГруппы + '\u0424\u043e\u0440\u043c\u0430\u0417\u0430\u043f\u0438\u0441\u0438': 'Record', # ФормаЗаписи + '\u0424\u043e\u0440\u043c\u0430\u0421\u0447\u0435\u0442\u0430': 'Item', # ФормаСчета + '\u0424\u043e\u0440\u043c\u0430\u0423\u0437\u043b\u0430': 'Item', # ФормаУзла } # Mutual exclusion validation @@ -1916,18 +2488,32 @@ def main(): preset_data = load_preset(args.Preset, os.path.dirname(os.path.abspath(__file__)), out_path_resolved) - supported = {'Document': ['Item', 'List', 'Choice'], 'Catalog': ['Item', 'Folder', 'List', 'Choice']} + supported = { + 'Document': ['Item', 'List', 'Choice'], + 'Catalog': ['Item', 'Folder', 'List', 'Choice'], + 'InformationRegister': ['Record', 'List'], + 'AccumulationRegister': ['List'], + 'ChartOfCharacteristicTypes': ['Item', 'Folder', 'List', 'Choice'], + 'ExchangePlan': ['Item', 'List', 'Choice'], + 'ChartOfAccounts': ['Item', 'Folder', 'List', 'Choice'], + } if meta['Type'] not in supported: - print(f"Object type '{meta['Type']}' not supported. Supported: Document, Catalog.", file=sys.stderr) + print(f"Object type '{meta['Type']}' not supported. Supported: Document, Catalog, InformationRegister, AccumulationRegister, ChartOfCharacteristicTypes, ExchangePlan, ChartOfAccounts.", file=sys.stderr) sys.exit(1) if purpose not in supported[meta['Type']]: print(f"Purpose '{purpose}' not valid for {meta['Type']}. Valid: {', '.join(supported[meta['Type']])}", file=sys.stderr) sys.exit(1) - if meta['Type'] == 'Document': - dsl = generate_document_dsl(meta, preset_data, purpose) - else: - dsl = generate_catalog_dsl(meta, preset_data, purpose) + dsl_dispatch = { + 'Document': generate_document_dsl, + 'Catalog': generate_catalog_dsl, + 'InformationRegister': generate_information_register_dsl, + 'AccumulationRegister': generate_accumulation_register_dsl, + 'ChartOfCharacteristicTypes': generate_chart_of_characteristic_types_dsl, + 'ExchangePlan': generate_exchange_plan_dsl, + 'ChartOfAccounts': generate_chart_of_accounts_dsl, + } + dsl = dsl_dispatch[meta['Type']](meta, preset_data, purpose) if args.EmitDsl: dsl_path = args.EmitDsl if os.path.isabs(args.EmitDsl) else os.path.join(os.getcwd(), args.EmitDsl) @@ -1937,6 +2523,8 @@ def main(): print(f"[from-object] DSL saved: {dsl_path}") defn = json.loads(json.dumps(dsl)) # normalize OrderedDict to regular dict + # Convert dict-style elements (from generators) to list-style (expected by compiler) + defn = _normalize_elements(defn) else: # --- 1. Load and validate JSON --- json_path = args.JsonPath