From a08fcc1bcca0cbc7f057ba680f86f35ea8f8d4aa Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Sun, 5 Jul 2026 20:25:21 +0300 Subject: [PATCH] =?UTF-8?q?feat(meta-compile,meta-decompile):=20=D0=BA?= =?UTF-8?q?=D0=BE=D1=80=D0=BE=D1=82=D0=BA=D0=B0=D1=8F=20=D0=B7=D0=B0=D0=BF?= =?UTF-8?q?=D0=B8=D1=81=D1=8C=20=D1=81=D1=83=D0=B1=D0=BA=D0=BE=D0=BD=D1=82?= =?UTF-8?q?=D0=BE=20=D0=9F=D0=A1=20+=20=D1=84=D0=B8=D0=BA=D1=81=20=D1=80?= =?UTF-8?q?=D0=B5=D0=B3=D0=B8=D1=81=D1=82=D1=80=D0=B0=20=D0=BE=D0=BF=D0=B8?= =?UTF-8?q?=D1=81=D0=B0=D0=BD=D0=B8=D1=8F=20=D1=81=D1=87=D1=91=D1=82=D0=B0?= =?UTF-8?q?=20(v1.38/v0.27)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Субконто предопределённого счёта — компактная строка "Вид | Признак1, Признак2": type — голое имя значения ПВХ (компилятор разворачивает через extDimensionTypes плана), признаки после '|' — только TRUE (разворот по def-порядку extDimensionAccountingFlags). Объектная форма {type, turnover?, flags?} остаётся для turnover=true («Только обороты», синоним `толькоОбороты`). Декомпилятор пишет строку при turnover=false, объект — при true. Хелпер Resolve-TypePrefixSyn (общий с extDimensionTypes). Фикс (нашёлся на реальных счетах): PS `-ne` регистронезависим → description предопределённого счёта с хвостовой аббревиатурой (ОС/НМА) ошибочно опускался (auto==actual регистронезависимо), компилятор регенерил lowercase через Split-CamelCase. Декомпилятор теперь сравнивает регистрочувствительно (-cne). Harness маскировал (его семантический дифф регистронезависим) — реальная 1С регистр сохраняет. Роундтрип 3/3 match. Проверены обе формы субконто (строка/объект, смешанный массив, turnover=true). Регресс 47/47 ps1+py, form-compile-from-object 12/12. spec §7.2c. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../meta-compile/scripts/meta-compile.ps1 | 53 +++++++++------- .../meta-compile/scripts/meta-compile.py | 60 ++++++++++++------- .../meta-decompile/scripts/meta-decompile.ps1 | 25 +++++--- docs/meta-dsl-spec.md | 10 +++- 4 files changed, 95 insertions(+), 53 deletions(-) diff --git a/.claude/skills/meta-compile/scripts/meta-compile.ps1 b/.claude/skills/meta-compile/scripts/meta-compile.ps1 index 109aa352..0771ebb1 100644 --- a/.claude/skills/meta-compile/scripts/meta-compile.ps1 +++ b/.claude/skills/meta-compile/scripts/meta-compile.ps1 @@ -1,4 +1,4 @@ -# meta-compile v1.37 — Compile 1C metadata object from JSON +# meta-compile v1.38 — Compile 1C metadata object from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -2735,6 +2735,17 @@ function Emit-DocumentJournalProperties { # --- 13d. Wave 4: ChartOfAccounts, AccountingRegister, ChartOfCalculationTypes, CalculationRegister --- +# Ссылка на объект метаданных: русский префикс типа → английский (ПланВидовХарактеристик.X → ChartOfCharacteristicTypes.X). +function Resolve-TypePrefixSyn { + param([string]$ref) + if ($ref -and $ref.Contains('.')) { + $d = $ref.IndexOf('.'); $p = $ref.Substring(0, $d); $s = $ref.Substring($d + 1) + if ($script:objectTypeSynonyms.ContainsKey($p)) { $p = $script:objectTypeSynonyms[$p] } + return "$p.$s" + } + return $ref +} + function Emit-ChartOfAccountsProperties { param([string]$indent) $i = $indent @@ -2749,15 +2760,7 @@ function Emit-ChartOfAccountsProperties { Emit-BasedOn $i $def.basedOn # ExtDimensionTypes — ссылка на ПВХ видов субконто (прощающий ввод: ПланВидовХарактеристик.X → ChartOfCharacteristicTypes.X). - $extDimTypes = "" - if ($def.extDimensionTypes) { - $extDimTypes = "$($def.extDimensionTypes)" - if ($extDimTypes.Contains('.')) { - $edtDot = $extDimTypes.IndexOf('.'); $edtPfx = $extDimTypes.Substring(0, $edtDot); $edtSfx = $extDimTypes.Substring($edtDot + 1) - if ($script:objectTypeSynonyms.ContainsKey($edtPfx)) { $edtPfx = $script:objectTypeSynonyms[$edtPfx] } - $extDimTypes = "$edtPfx.$edtSfx" - } - } + $extDimTypes = if ($def.extDimensionTypes) { Resolve-TypePrefixSyn "$($def.extDimensionTypes)" } else { "" } if ($extDimTypes) { X "$i$(Esc-Xml $extDimTypes)" } else { X "$i" } $maxExtDim = if ($null -ne $def.maxExtDimensionCount) { "$($def.maxExtDimensionCount)" } else { "3" } @@ -3818,7 +3821,7 @@ function Emit-PredefAccountFlags { [void]$sb.Append("$indent`n") } function Emit-PredefAccount { - param($sb, $val, [string]$indent, [string]$objName, [string[]]$acctFlagNames, [string[]]$extDimFlagNames) + param($sb, $val, [string]$indent, [string]$objName, [string[]]$acctFlagNames, [string[]]$extDimFlagNames, [string]$extDimTypesRef = '') $gv = $script:predefAccGet $name = "$(& $gv $val @('name','имя'))" $codeV = & $gv $val @('code','код'); $code = if ($null -ne $codeV) { "$codeV" } else { '' } @@ -3846,14 +3849,19 @@ function Emit-PredefAccount { else { [void]$sb.Append("$indent`t`n") foreach ($sc in $subArr) { - $scType = "$(& $gv $sc @('type','тип'))" - if ($scType.Contains('.')) { - $scDot = $scType.IndexOf('.'); $scPfx = $scType.Substring(0, $scDot); $scSfx = $scType.Substring($scDot + 1) - if ($script:objectTypeSynonyms.ContainsKey($scPfx)) { $scPfx = $script:objectTypeSynonyms[$scPfx] } - $scType = "$scPfx.$scSfx" + # Строковая форма "Тип | Признак1, Признак2" (флаги после |, turnover=false). Объектная — {type, turnover?, flags?}. + if ($sc -is [string]) { + $scTurnV = $null; $scFlags = $null; $scStr = "$sc" + if ($scStr.Contains('|')) { + $scParts = $scStr.Split('|', 2); $scType = $scParts[0].Trim() + $scFlags = @($scParts[1].Split(',') | ForEach-Object { $_.Trim() } | Where-Object { $_ }) + } else { $scType = $scStr.Trim() } } - $scTurnV = & $gv $sc @('turnover','оборотный'); $scTurn = if ($scTurnV -eq $true) { 'true' } else { 'false' } - $scFlags = & $gv $sc @('flags','признаки') + else { $scType = "$(& $gv $sc @('type','тип'))"; $scTurnV = & $gv $sc @('turnover','толькоОбороты','оборотный'); $scFlags = & $gv $sc @('flags','признаки') } + # Короткая запись: голое имя значения → префикс ПВХ видов субконто плана (extDimensionTypes); иначе резолв синонима. + if (-not $scType.Contains('.')) { if ($extDimTypesRef) { $scType = "$extDimTypesRef.$scType" } } + else { $scType = Resolve-TypePrefixSyn $scType } + $scTurn = if ($scTurnV -eq $true) { 'true' } else { 'false' } [void]$sb.Append("$indent`t`t`n") [void]$sb.Append("$indent`t`t`t$scTurn`n") Emit-PredefAccountFlags $sb "$indent`t`t`t" 'AccountingFlags' 'ExtDimensionAccountingFlag' $objName $extDimFlagNames $scFlags @@ -3864,17 +3872,17 @@ function Emit-PredefAccount { $childArr = @(); if ($children) { $childArr = @($children) } if ($childArr.Count -gt 0) { [void]$sb.Append("$indent`t`n") - foreach ($c in $childArr) { Emit-PredefAccount $sb $c "$indent`t`t" $objName $acctFlagNames $extDimFlagNames } + foreach ($c in $childArr) { Emit-PredefAccount $sb $c "$indent`t`t" $objName $acctFlagNames $extDimFlagNames $extDimTypesRef } [void]$sb.Append("$indent`t`n") } [void]$sb.Append("$indent`n") } function Build-PredefinedAccountXml { - param($items, [string]$objName, [string[]]$acctFlagNames, [string[]]$extDimFlagNames) + param($items, [string]$objName, [string[]]$acctFlagNames, [string[]]$extDimFlagNames, [string]$extDimTypesRef = '') $sb = New-Object System.Text.StringBuilder [void]$sb.Append("`n") [void]$sb.Append("`n") - foreach ($it in $items) { Emit-PredefAccount $sb $it "`t" $objName $acctFlagNames $extDimFlagNames } + foreach ($it in $items) { Emit-PredefAccount $sb $it "`t" $objName $acctFlagNames $extDimFlagNames $extDimTypesRef } [void]$sb.Append("`n") return $sb.ToString() } @@ -3984,7 +3992,8 @@ if ($objType -eq 'ChartOfAccounts' -and $def.predefined -and @($def.predefined). Ensure-ExtDir $afNames = @(); if ($def.accountingFlags) { foreach ($af in $def.accountingFlags) { $afNames += (Parse-AttributeShorthand $af).name } } $edfNames = @(); if ($def.extDimensionAccountingFlags) { foreach ($edf in $def.extDimensionAccountingFlags) { $edfNames += (Parse-AttributeShorthand $edf).name } } - $predefXml = Build-PredefinedAccountXml @($def.predefined) $objName $afNames $edfNames + $edtRef = if ($def.extDimensionTypes) { Resolve-TypePrefixSyn "$($def.extDimensionTypes)" } else { '' } + $predefXml = Build-PredefinedAccountXml @($def.predefined) $objName $afNames $edfNames $edtRef $predefPath = Join-Path $extDir "Predefined.xml" [System.IO.File]::WriteAllText($predefPath, $predefXml, $enc) $modulesCreated += $predefPath diff --git a/.claude/skills/meta-compile/scripts/meta-compile.py b/.claude/skills/meta-compile/scripts/meta-compile.py index 884b1be9..91ad98b1 100644 --- a/.claude/skills/meta-compile/scripts/meta-compile.py +++ b/.claude/skills/meta-compile/scripts/meta-compile.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# meta-compile v1.37 — Compile 1C metadata object from JSON +# meta-compile v1.38 — Compile 1C metadata object from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -2630,6 +2630,15 @@ def emit_document_journal_properties(indent): X(f'{i}') X(f'{i}') +def resolve_type_prefix_syn(ref): + """Ссылка на объект: русский префикс типа → английский (ПланВидовХарактеристик.X → ChartOfCharacteristicTypes.X).""" + if ref and '.' in ref: + p, s = ref.split('.', 1) + if p in object_type_synonyms: + p = object_type_synonyms[p] + return f'{p}.{s}' + return ref + def emit_chart_of_accounts_properties(indent): i = indent X(f'{i}{esc_xml(obj_name)}') @@ -2642,14 +2651,7 @@ def emit_chart_of_accounts_properties(indent): X(f'{i}{"true" if get_bool_prop("includeHelpInContents", False) else "false"}') emit_based_on(i, defn.get('basedOn')) # ExtDimensionTypes — ссылка на ПВХ видов субконто (прощающий ввод: ПланВидовХарактеристик.X → ChartOfCharacteristicTypes.X). - ext_dim_types = '' - if defn.get('extDimensionTypes'): - ext_dim_types = str(defn['extDimensionTypes']) - if '.' in ext_dim_types: - edt_pfx, edt_sfx = ext_dim_types.split('.', 1) - if edt_pfx in object_type_synonyms: - edt_pfx = object_type_synonyms[edt_pfx] - ext_dim_types = f'{edt_pfx}.{edt_sfx}' + ext_dim_types = resolve_type_prefix_syn(str(defn['extDimensionTypes'])) if defn.get('extDimensionTypes') else '' if ext_dim_types: X(f'{i}{esc_xml(ext_dim_types)}') else: @@ -3668,7 +3670,7 @@ def emit_predef_account_flags(out, indent, tag, ref_kind, obj_nm, flag_names, tr out.append(f'{indent}\t{v}') out.append(f'{indent}') -def emit_predef_account(out, val, indent, obj_nm, acct_flag_names, ext_dim_flag_names): +def emit_predef_account(out, val, indent, obj_nm, acct_flag_names, ext_dim_flag_names, ext_dim_types_ref=''): name = str(_predef_acc_get(val, ('name', 'имя')) or '') code_v = _predef_acc_get(val, ('code', 'код')) code = str(code_v) if code_v is not None else '' @@ -3696,14 +3698,27 @@ def emit_predef_account(out, val, indent, obj_nm, acct_flag_names, ext_dim_flag_ else: out.append(f'{indent}\t') for sc in sub_arr: - sc_type = str(_predef_acc_get(sc, ('type', 'тип')) or '') - if '.' in sc_type: - sc_pfx, sc_sfx = sc_type.split('.', 1) - if sc_pfx in object_type_synonyms: - sc_pfx = object_type_synonyms[sc_pfx] - sc_type = f'{sc_pfx}.{sc_sfx}' - sc_turn = 'true' if _predef_acc_get(sc, ('turnover', 'оборотный')) is True else 'false' - sc_flags = _predef_acc_get(sc, ('flags', 'признаки')) + # Строковая форма "Тип | Признак1, Признак2" (флаги после |, turnover=false). Объектная — {type, turnover?, flags?}. + if isinstance(sc, str): + sc_turn_v = None + if '|' in sc: + tpart, fpart = sc.split('|', 1) + sc_type = tpart.strip() + sc_flags = [x.strip() for x in fpart.split(',') if x.strip()] + else: + sc_type = sc.strip() + sc_flags = None + else: + sc_type = str(_predef_acc_get(sc, ('type', 'тип')) or '') + sc_turn_v = _predef_acc_get(sc, ('turnover', 'толькоОбороты', 'оборотный')) + sc_flags = _predef_acc_get(sc, ('flags', 'признаки')) + # Короткая запись: голое имя значения → префикс ПВХ видов субконто плана (extDimensionTypes); иначе резолв синонима. + if '.' not in sc_type: + if ext_dim_types_ref: + sc_type = f'{ext_dim_types_ref}.{sc_type}' + else: + sc_type = resolve_type_prefix_syn(sc_type) + sc_turn = 'true' if sc_turn_v is True else 'false' out.append(f'{indent}\t\t') out.append(f'{indent}\t\t\t{sc_turn}') emit_predef_account_flags(out, f'{indent}\t\t\t', 'AccountingFlags', 'ExtDimensionAccountingFlag', obj_nm, ext_dim_flag_names, sc_flags) @@ -3713,15 +3728,15 @@ def emit_predef_account(out, val, indent, obj_nm, acct_flag_names, ext_dim_flag_ if child_arr: out.append(f'{indent}\t') for c in child_arr: - emit_predef_account(out, c, f'{indent}\t\t', obj_nm, acct_flag_names, ext_dim_flag_names) + emit_predef_account(out, c, f'{indent}\t\t', obj_nm, acct_flag_names, ext_dim_flag_names, ext_dim_types_ref) out.append(f'{indent}\t') out.append(f'{indent}') -def build_predefined_account_xml(items, obj_nm, acct_flag_names, ext_dim_flag_names): +def build_predefined_account_xml(items, obj_nm, acct_flag_names, ext_dim_flag_names, ext_dim_types_ref=''): out = [''] out.append(f'') for it in items: - emit_predef_account(out, it, '\t', obj_nm, acct_flag_names, ext_dim_flag_names) + emit_predef_account(out, it, '\t', obj_nm, acct_flag_names, ext_dim_flag_names, ext_dim_types_ref) out.append('') return '\n'.join(out) + '\n' @@ -3749,7 +3764,8 @@ if obj_type == 'ChartOfAccounts' and defn.get('predefined'): ensure_ext_dir() af_names = [parse_attribute_shorthand(af)['name'] for af in _as_list(defn['accountingFlags'])] if defn.get('accountingFlags') else [] edf_names = [parse_attribute_shorthand(edf)['name'] for edf in _as_list(defn['extDimensionAccountingFlags'])] if defn.get('extDimensionAccountingFlags') else [] - predef_xml = build_predefined_account_xml(defn['predefined'], obj_name, af_names, edf_names) + edt_ref = resolve_type_prefix_syn(str(defn['extDimensionTypes'])) if defn.get('extDimensionTypes') else '' + predef_xml = build_predefined_account_xml(defn['predefined'], obj_name, af_names, edf_names, edt_ref) predef_path = os.path.join(ext_dir, 'Predefined.xml') write_utf8_bom(predef_path, predef_xml) modules_created.append(predef_path) diff --git a/.claude/skills/meta-decompile/scripts/meta-decompile.ps1 b/.claude/skills/meta-decompile/scripts/meta-decompile.ps1 index a91e205b..e457d89b 100644 --- a/.claude/skills/meta-decompile/scripts/meta-decompile.ps1 +++ b/.claude/skills/meta-decompile/scripts/meta-decompile.ps1 @@ -1,4 +1,4 @@ -# meta-decompile v0.26 — XML объекта метаданных 1С → JSON-черновик формата meta-compile +# meta-decompile v0.27 — XML объекта метаданных 1С → JSON-черновик формата meta-compile # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # # Поддержаны: Catalog, ExchangePlan, ChartOfCharacteristicTypes, ChartOfAccounts. Инверс meta-compile (omit-on-default: ключ эмитим только @@ -808,25 +808,36 @@ if (Test-Path -LiteralPath $predefPath) { foreach ($fl in @($itemEl.SelectNodes("*[local-name()='AccountingFlags']/*[local-name()='Flag']"))) { if ($fl.InnerText -eq 'true') { $r = $fl.GetAttribute('ref'); $trueFlags += ($r -split '\.')[-1] } } - # ExtDimensionTypes → subconto [{type, turnover?, flags?}]. + # ExtDimensionTypes → subconto. Короткая запись: срезаем префикс ПВХ видов субконто плана (extDimensionTypes). + # turnover=false → строка "Тип | Признак1, Признак2" (или "Тип" без признаков); turnover=true → объект. + $edtPfx = if ($dsl['extDimensionTypes']) { "$($dsl['extDimensionTypes'])." } else { $null } $subconto = [System.Collections.ArrayList]@() foreach ($edt in @($itemEl.SelectNodes("*[local-name()='ExtDimensionTypes']/*[local-name()='ExtDimensionType']"))) { - $so = [ordered]@{ type = $edt.GetAttribute('name') } + $scT = $edt.GetAttribute('name') + if ($edtPfx -and $scT.StartsWith($edtPfx)) { $scT = $scT.Substring($edtPfx.Length) } $tEl = $edt.SelectSingleNode("*[local-name()='Turnover']") - if ($tEl -and $tEl.InnerText -eq 'true') { $so['turnover'] = $true } + $scTurn = ($tEl -and $tEl.InnerText -eq 'true') $stf = @() foreach ($fl in @($edt.SelectNodes("*[local-name()='AccountingFlags']/*[local-name()='Flag']"))) { if ($fl.InnerText -eq 'true') { $r = $fl.GetAttribute('ref'); $stf += ($r -split '\.')[-1] } } - if ($stf.Count -gt 0) { $so['flags'] = [System.Collections.ArrayList]@($stf) } - [void]$subconto.Add($so) + if (-not $scTurn) { + if ($stf.Count -gt 0) { [void]$subconto.Add("$scT | " + ($stf -join ', ')) } + else { [void]$subconto.Add($scT) } + } else { + $so = [ordered]@{ type = $scT; turnover = $true } + if ($stf.Count -gt 0) { $so['flags'] = [System.Collections.ArrayList]@($stf) } + [void]$subconto.Add($so) + } } $childContainer = $itemEl.SelectSingleNode("*[local-name()='ChildItems']") $kids = @(if ($childContainer) { $childContainer.SelectNodes("*[local-name()='Item']") } else { @() }) $o = [ordered]@{ name = $name } if ($code) { $o['code'] = $code } - if ($desc -ne $auto) { $o['description'] = $desc } + # -cne (регистрочувствительно!): PS `-ne` регистронезависим → «…ОС» == «…ос» и description ошибочно + # опускался, а компилятор регенерил lowercase (Split-CamelCase). Хвостовые аббревиатуры (ОС/НМА) теряли регистр. + if ($desc -cne $auto) { $o['description'] = $desc } $o['accountType'] = $acctType if ($off) { $o['offBalance'] = $true } $o['order'] = $order diff --git a/docs/meta-dsl-spec.md b/docs/meta-dsl-spec.md index bbf74879..93141c3d 100644 --- a/docs/meta-dsl-spec.md +++ b/docs/meta-dsl-spec.md @@ -695,14 +695,20 @@ Split-CamelCase имени. | `offBalance` | `false` | OffBalance | | `order` | — | Order (строка сортировки, вербатим) | | `flags` | `[]` | AccountingFlags → `` по каждому признаку плана | -| `subconto` | `[]` | ExtDimensionTypes → `` `{type, turnover?, flags?}` | +| `subconto` | `[]` | ExtDimensionTypes → `` (вид субконто) | | `childItems` | `[]` | ChildItems (иерархия счетов) | +**Субконто** (`subconto`) — короткая строка **`"Вид | Признак1, Признак2"`** (после `|` — включённые признаки учёта +субконто): `type` — голое имя предопределённого значения ПВХ, компилятор разворачивает через `extDimensionTypes` плана +(`Номенклатура` → `ChartOfCharacteristicTypes.ВидыСубконто.Номенклатура`); признаки — только TRUE, разворот по def-порядку +`extDimensionAccountingFlags`. Объектная форма `{type, turnover?, flags?}` — когда нужен `turnover: true` +(`` = «Только обороты»: субконто ведётся только по оборотам, без остатков; синоним `толькоОбороты`). + ```json "predefined": [ { "name": "ОсновныеСредства", "code": "01", "accountType": "Active", "order": " 01", "flags": ["Количественный"], - "subconto": [{ "type": "ChartOfCharacteristicTypes.ВидыСубконто.ОсновныеСредства", "flags": ["Суммовой"] }], + "subconto": ["Номенклатура | Суммовой, Валютный"], "childItems": [ { "name": "ОС в организации", "code": "01.01", "accountType": "Active", "order": " 01.01" } ] }