feat(meta-compile): пары субконто бухрегистра из плана счетов

Компилятор эмитил ExtDimensionN/ExtDimensionTypeN только по ключам DSL,
поэтому регистр, созданный по неполному описанию, не совпадал с тем, что
материализует платформа: пары дописывались лишь при загрузке.

Теперь их число выводится из MaxExtDimensionCount плана счетов, на который
ссылается регистр, — файл читается из выгрузки, как версия формата из
Configuration.xml. ExtDimensionN получает LinkByType на Account с LinkItem
по номеру. Выведенный хвост дополняет DSL, а не заменяет: ключи из описания
остаются, недостающее добавляется.

План счетов не найден в выгрузке (ещё не создан, лежит вне каталога) — пары
не генерируются, в вывод идёт [HINT]: платформа допишет их сама при загрузке.

Проверка: матрица из трёх случаев (план с 3 субконто, план с 0, план
отсутствует) на обоих портах; синтетика загружена в 1С и выгружена обратно —
состав и порядок совпали; корпусный роундтрип 739 регистров без расхождений.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-08-09 18:42:00 +03:00
co-authored by Claude Opus 5
parent 16199deb92
commit 4622234b52
13 changed files with 1101 additions and 2 deletions
@@ -1465,6 +1465,34 @@ $script:stdAttrConditions = @{
$script:stdAttrTailPattern = @{
"AccountingRegister" = '^ExtDimension(Type)?\d+$'
}
# Состав хвоста задаёт не DSL, а объект, на который регистр ссылается: пар субконто столько,
# сколько у плана счетов MaxExtDimensionCount. Читаем его из выгрузки — как версию формата из
# Configuration.xml, — чтобы регистр, описанный неполным DSL, совпал с тем, что материализует
# платформа. План не найден → хвост не генерируем и говорим об этом в выводе.
$script:stdAttrTailHint = $null
$script:stdAttrTailDerived = @{
"AccountingRegister" = {
param($d, $objectName, $outDir)
$ref = "$($d.chartOfAccounts)"
if (-not $ref) { return @() }
$chartName = $ref -replace '^.*\.', '' # ссылка вида ChartOfAccounts.X (имя объекта точек не содержит)
$path = Join-Path (Join-Path $outDir "ChartsOfAccounts") "$chartName.xml"
if (-not (Test-Path -LiteralPath $path)) {
$script:stdAttrTailHint = "ChartOfAccounts '$chartName' not found in dump — ExtDimension pairs not generated (platform will add them on load)"
return @()
}
$n = 0
if ([System.IO.File]::ReadAllText($path) -match '<MaxExtDimensionCount>(\d+)</MaxExtDimensionCount>') { $n = [int]$matches[1] }
$out = @()
for ($i = 1; $i -le $n; $i++) {
# ExtDimensionN связан с Account через LinkByType (LinkItem = номер), ExtDimensionTypeN — нет.
$out += @{ name = "ExtDimension$i"; ov = @{ LinkByType = @{ dataPath = "AccountingRegister.$objectName.StandardAttribute.Account"; linkItem = $i } } }
$out += @{ name = "ExtDimensionType$i"; ov = @{} }
}
return $out
}
}
function Emit-StandardAttributes {
param([string]$indent, [string]$objectType)
$attrs = $script:standardAttributesByType[$objectType]
@@ -1491,6 +1519,16 @@ function Emit-StandardAttributes {
if ($tailRe -and $k -match $tailRe) { $tail += $k } else { $extra += $k }
}
}
# Хвост, выведенный из связанного объекта: дополняет DSL, а не заменяет его — лишнее из DSL
# остаётся (прощаем), недостающее добавляется вместе со своими значениями по умолчанию.
$derivedOv = @{}
$gen = $script:stdAttrTailDerived[$objectType]
if ($gen) {
foreach ($e in @(& $gen $def $objName $OutputDir)) {
$derivedOv[$e.name] = $e.ov
if ($tail -notcontains $e.name) { $tail += $e.name }
}
}
$tail = @($tail | Sort-Object @{e={[int]([regex]::Match($_, '\d+').Value)}}, @{e={ if ($_ -match 'Type\d+$') { 1 } else { 0 } }})
X "$indent<StandardAttributes>"
foreach ($a in ($extra + $attrs + $tail)) {
@@ -1502,6 +1540,7 @@ function Emit-StandardAttributes {
}
$ov = @{}
if ($profile.ContainsKey($a)) { foreach ($k in $profile[$a].Keys) { $ov[$k] = $profile[$a][$k] } }
if ($derivedOv.ContainsKey($a)) { foreach ($k in $derivedOv[$a].Keys) { $ov[$k] = $derivedOv[$a][$k] } }
if ($sa) { # DSL-override применяем всегда при наличии ключа (для не-условных типов тоже, напр. ExchangePlan)
$d = $sa.$a
if ($d) {
@@ -5261,6 +5300,9 @@ switch ($regResult) {
}
# Cross-reference hints
if ($script:stdAttrTailHint) {
Write-Host "[HINT] $($script:stdAttrTailHint)"
}
if ($objType -eq "AccountingRegister" -and -not $def.chartOfAccounts) {
Write-Host "[HINT] AccountingRegister requires ChartOfAccounts reference:"
Write-Host " /meta-edit -Operation modify-property -Value `"ChartOfAccounts=ChartOfAccounts.XXX`""
@@ -1512,6 +1512,39 @@ std_attr_conditions = {
std_attr_tail_pattern = {
'AccountingRegister': r'^ExtDimension(Type)?\d+$',
}
# Состав хвоста задаёт не DSL, а объект, на который регистр ссылается: пар субконто столько,
# сколько у плана счетов MaxExtDimensionCount. Читаем его из выгрузки — как версию формата из
# Configuration.xml, — чтобы регистр, описанный неполным DSL, совпал с тем, что материализует
# платформа. План не найден → хвост не генерируем и говорим об этом в выводе.
std_attr_tail_hint = None
def _acc_register_ext_dimension_tail(d, object_name, out_dir):
global std_attr_tail_hint
ref = str(d.get('chartOfAccounts') or '')
if not ref:
return []
chart_name = re.sub(r'^.*\.', '', ref) # ссылка вида ChartOfAccounts.X (имя объекта точек не содержит)
path = os.path.join(out_dir, 'ChartsOfAccounts', chart_name + '.xml')
if not os.path.isfile(path):
std_attr_tail_hint = ("ChartOfAccounts '%s' not found in dump — ExtDimension pairs not generated "
"(platform will add them on load)" % chart_name)
return []
with open(path, encoding='utf-8-sig') as f:
m = re.search(r'<MaxExtDimensionCount>(\d+)</MaxExtDimensionCount>', f.read())
n = int(m.group(1)) if m else 0
out = []
for i in range(1, n + 1):
# ExtDimensionN связан с Account через LinkByType (LinkItem = номер), ExtDimensionTypeN — нет.
out.append(('ExtDimension%d' % i,
{'LinkByType': {'dataPath': 'AccountingRegister.%s.StandardAttribute.Account' % object_name,
'linkItem': i}}))
out.append(('ExtDimensionType%d' % i, {}))
return out
std_attr_tail_derived = {
'AccountingRegister': _acc_register_ext_dimension_tail,
}
def emit_standard_attributes(indent, object_type):
attrs = standard_attributes_by_type.get(object_type)
if not attrs:
@@ -1542,6 +1575,15 @@ def emit_standard_attributes(indent, object_type):
tail.append(k)
else:
extra.append(k)
# Хвост, выведенный из связанного объекта: дополняет DSL, а не заменяет его — лишнее из DSL
# остаётся (прощаем), недостающее добавляется вместе со своими значениями по умолчанию.
derived_ov = {}
gen = std_attr_tail_derived.get(object_type)
if gen:
for name, dov in gen(defn, obj_name, output_dir):
derived_ov[name] = dov
if name not in tail:
tail.append(name)
tail.sort(key=lambda k: (int(re.search(r'\d+', k).group()), 1 if re.search(r'Type\d+$', k) else 0))
X(f'{indent}<StandardAttributes>')
for a in extra + list(attrs) + tail:
@@ -1552,6 +1594,7 @@ def emit_standard_attributes(indent, object_type):
if not present:
continue
ov = dict(profile.get(a, {}))
ov.update(derived_ov.get(a, {}))
if isinstance(sa, dict):
d = sa.get(a)
if d:
@@ -5159,6 +5202,8 @@ elif reg_result == 'no-config':
print(f' Configuration.xml: not found at {config_xml_path} (register manually)')
# Cross-reference hints
if std_attr_tail_hint:
print(f'[HINT] {std_attr_tail_hint}')
if obj_type == 'AccountingRegister' and not defn.get('chartOfAccounts'):
print('[HINT] AccountingRegister requires ChartOfAccounts reference:')
print(' /meta-edit -Operation modify-property -Value "ChartOfAccounts=ChartOfAccounts.XXX"')