mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-09-12 21:20:52 +03:00
fix(form): уникальность имён во всех коллекциях форм + префикс колонок субконто ЧПС
Проверка уникальности имён элементов форм (основа — PR #21 от brake71), портированная на актуальную ветку и расширенная на все именованные коллекции. Корень проблемы: генератор формы счёта ПланаСчетов строил колонки таблицы субконто с «голыми» именами (Валютный, ТолькоОбороты, ВидСубконто), из-за чего флаг субконто сталкивался с одноимённым признаком учёта счёта → невалидный для 1С XML (форма не открывалась). Теперь имена колонок префиксуются именем таблицы (ВидыСубконтоВалютный) — как делает generic-путь табчастей и типовая 1С. - form-compile: fail-fast проверка уникальности в едином emit_element + по реквизитам, колонкам (в пределах реквизита), параметрам и командам. Хелпер вместо копипаста; проверка после нормализации синонимов. - form-validate: проверка имён симметрично существующим id-пулам (элементы, реквизиты, колонки, команды) + новый блок параметров. - form-edit: дедуп внутри JSON-определения и против существующих в форме — для элементов (рекурсивно), реквизитов (+колонки) и команд; WARN→ERROR. Каждая коллекция — свой неймспейс (имя реквизита и имя элемента могут совпадать легально). PS1 и PY — зеркальны. Версии: form-compile 1.74, form-validate 1.7, form-edit 1.1. Все тест-сеты зелёные на обоих рантаймах. Co-authored-by: brake71 <8448482+brake71@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
brake71
Claude Opus 4.8
parent
ad89929efd
commit
41e4714773
@@ -1,4 +1,4 @@
|
||||
# form-compile v1.73 — Compile 1C managed form from JSON or object metadata
|
||||
# form-compile v1.74 — Compile 1C managed form from JSON or object metadata
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[string]$JsonPath,
|
||||
@@ -1230,16 +1230,19 @@ function Generate-ChartOfAccountsItemDSL($meta, [hashtable]$p, [hashtable]$fd, [
|
||||
|
||||
# ExtDimensionTypes table
|
||||
if ($meta.MaxExtDimensionCount -gt 0) {
|
||||
# Имена колонок табчасти префиксуются именем таблицы (как generic-путь и типовая 1С),
|
||||
# иначе флаг субконто (напр. "Валютный") столкнётся с одноимённым признаком учёта счёта.
|
||||
$edTable = "ВидыСубконто"
|
||||
$edCols = @()
|
||||
$edCols += [ordered]@{ input = "ВидСубконто"; path = "Объект.ExtDimensionTypes.ExtDimensionType" }
|
||||
$edCols += [ordered]@{ check = "ТолькоОбороты"; path = "Объект.ExtDimensionTypes.TurnoversOnly" }
|
||||
$edCols += [ordered]@{ input = "${edTable}ВидСубконто"; path = "Объект.ExtDimensionTypes.ExtDimensionType" }
|
||||
$edCols += [ordered]@{ check = "${edTable}ТолькоОбороты"; path = "Объект.ExtDimensionTypes.TurnoversOnly" }
|
||||
if ($meta.ExtDimensionAccountingFlags) {
|
||||
foreach ($edFlag in $meta.ExtDimensionAccountingFlags) {
|
||||
$edCols += [ordered]@{ check = $edFlag.Name; path = "Объект.ExtDimensionTypes.$($edFlag.Name)" }
|
||||
$edCols += [ordered]@{ check = "${edTable}$($edFlag.Name)"; path = "Объект.ExtDimensionTypes.$($edFlag.Name)" }
|
||||
}
|
||||
}
|
||||
$elements += [ordered]@{
|
||||
table = "ВидыСубконто"
|
||||
table = $edTable
|
||||
path = "Объект.ExtDimensionTypes"
|
||||
columns = $edCols
|
||||
}
|
||||
@@ -1521,6 +1524,17 @@ function New-Id {
|
||||
return $id
|
||||
}
|
||||
|
||||
# Уникальность имён внутри коллекции (1С: элементы/реквизиты/команды/параметры/колонки — каждое своё
|
||||
# пространство имён). Дубль → битый XML, форма не открывается, поэтому fail-fast.
|
||||
function Assert-UniqueName {
|
||||
param([string]$name, [hashtable]$seen, [string]$kind)
|
||||
if ($seen.ContainsKey($name)) {
|
||||
Write-Error "Duplicate $kind name '$name' — names must be unique within their collection in a 1C form (set a unique 'name')"
|
||||
exit 1
|
||||
}
|
||||
$seen[$name] = $true
|
||||
}
|
||||
|
||||
# --- 3. XML helper ---
|
||||
|
||||
$script:xml = New-Object System.Text.StringBuilder 8192
|
||||
@@ -2483,6 +2497,7 @@ function Emit-Element {
|
||||
}
|
||||
|
||||
$name = Get-ElementName -el $el -typeKey $typeKey
|
||||
Assert-UniqueName -name $name -seen $script:seenElementNames -kind 'element'
|
||||
$id = New-Id
|
||||
|
||||
switch ($typeKey) {
|
||||
@@ -4259,9 +4274,11 @@ function Emit-Attributes {
|
||||
if (-not $attrs -or $attrs.Count -eq 0) { return }
|
||||
|
||||
X "$indent<Attributes>"
|
||||
$seenAttrs = @{}
|
||||
foreach ($attr in $attrs) {
|
||||
$attrId = New-Id
|
||||
$attrName = "$($attr.name)"
|
||||
Assert-UniqueName -name $attrName -seen $seenAttrs -kind 'attribute'
|
||||
|
||||
X "$indent`t<Attribute name=`"$attrName`" id=`"$attrId`">"
|
||||
$inner = "$indent`t`t"
|
||||
@@ -4333,12 +4350,20 @@ function Emit-Attributes {
|
||||
if ($hasDirectCols -or $hasAddCols) {
|
||||
X "$inner<Columns>"
|
||||
if ($hasDirectCols) {
|
||||
foreach ($col in $attr.columns) { Emit-AttrColumn -col $col -indent "$inner`t" }
|
||||
$seenCols = @{} # колонки уникальны в пределах своего реквизита
|
||||
foreach ($col in $attr.columns) {
|
||||
Assert-UniqueName -name "$($col.name)" -seen $seenCols -kind "column of '$attrName'"
|
||||
Emit-AttrColumn -col $col -indent "$inner`t"
|
||||
}
|
||||
}
|
||||
if ($hasAddCols) {
|
||||
foreach ($ac in @($attr.additionalColumns)) {
|
||||
X "$inner`t<AdditionalColumns table=`"$($ac.table)`">"
|
||||
foreach ($col in @($ac.columns)) { Emit-AttrColumn -col $col -indent "$inner`t`t" }
|
||||
$seenAcCols = @{} # уникальность в пределах группы AdditionalColumns
|
||||
foreach ($col in @($ac.columns)) {
|
||||
Assert-UniqueName -name "$($col.name)" -seen $seenAcCols -kind "column of '$attrName'"
|
||||
Emit-AttrColumn -col $col -indent "$inner`t`t"
|
||||
}
|
||||
X "$inner`t</AdditionalColumns>"
|
||||
}
|
||||
}
|
||||
@@ -4405,7 +4430,9 @@ function Emit-Parameters {
|
||||
if (-not $params -or $params.Count -eq 0) { return }
|
||||
|
||||
X "$indent<Parameters>"
|
||||
$seenParams = @{}
|
||||
foreach ($param in $params) {
|
||||
Assert-UniqueName -name "$($param.name)" -seen $seenParams -kind 'parameter'
|
||||
X "$indent`t<Parameter name=`"$($param.name)`">"
|
||||
$inner = "$indent`t`t"
|
||||
|
||||
@@ -4428,8 +4455,10 @@ function Emit-Commands {
|
||||
if (-not $cmds -or $cmds.Count -eq 0) { return }
|
||||
|
||||
X "$indent<Commands>"
|
||||
$seenCmds = @{}
|
||||
foreach ($cmd in $cmds) {
|
||||
$cmdId = New-Id
|
||||
Assert-UniqueName -name "$($cmd.name)" -seen $seenCmds -kind 'command'
|
||||
X "$indent`t<Command name=`"$($cmd.name)`" id=`"$cmdId`">"
|
||||
$inner = "$indent`t`t"
|
||||
|
||||
@@ -4746,6 +4775,7 @@ X "<Form xmlns=`"http://v8.1c.ru/8.3/xcf/logform`" xmlns:app=`"http://v8.1c.ru/8
|
||||
# Reset and rebuild properly
|
||||
$script:xml = New-Object System.Text.StringBuilder 8192
|
||||
$script:nextId = 1
|
||||
$script:seenElementNames = @{} # пул имён элементов (глобально по всей форме)
|
||||
|
||||
X '<?xml version="1.0" encoding="UTF-8"?>'
|
||||
X "<Form xmlns=`"http://v8.1c.ru/8.3/xcf/logform`" xmlns:app=`"http://v8.1c.ru/8.2/managed-application/core`" xmlns:cfg=`"http://v8.1c.ru/8.1/data/enterprise/current-config`" xmlns:dcscor=`"http://v8.1c.ru/8.1/data-composition-system/core`" xmlns:dcssch=`"http://v8.1c.ru/8.1/data-composition-system/schema`" xmlns:dcsset=`"http://v8.1c.ru/8.1/data-composition-system/settings`" xmlns:ent=`"http://v8.1c.ru/8.1/data/enterprise`" xmlns:lf=`"http://v8.1c.ru/8.2/managed-application/logform`" xmlns:style=`"http://v8.1c.ru/8.1/data/ui/style`" xmlns:sys=`"http://v8.1c.ru/8.1/data/ui/fonts/system`" xmlns:v8=`"http://v8.1c.ru/8.1/data/core`" xmlns:v8ui=`"http://v8.1c.ru/8.1/data/ui`" xmlns:web=`"http://v8.1c.ru/8.1/data/ui/colors/web`" xmlns:win=`"http://v8.1c.ru/8.1/data/ui/colors/windows`" xmlns:xr=`"http://v8.1c.ru/8.3/xcf/readable`" xmlns:xs=`"http://www.w3.org/2001/XMLSchema`" xmlns:xsi=`"http://www.w3.org/2001/XMLSchema-instance`" version=`"$($script:formatVersion)`">"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# form-compile v1.73 — Compile 1C managed form from JSON or object metadata
|
||||
# form-compile v1.74 — Compile 1C managed form from JSON or object metadata
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import argparse
|
||||
import copy
|
||||
@@ -1157,14 +1157,17 @@ def generate_chart_of_accounts_item_dsl(meta, p, fd, preset_data):
|
||||
|
||||
# ExtDimensionTypes table
|
||||
if meta.get('MaxExtDimensionCount', 0) > 0:
|
||||
# Column names are prefixed with the table name (like the generic TS path and stock 1C),
|
||||
# else a subconto flag column collides with a same-named account accounting-flag checkbox.
|
||||
ed_table = '\u0412\u0438\u0434\u044b\u0421\u0443\u0431\u043a\u043e\u043d\u0442\u043e'
|
||||
ed_cols = []
|
||||
ed_cols.append(OrderedDict([('input', '\u0412\u0438\u0434\u0421\u0443\u0431\u043a\u043e\u043d\u0442\u043e'), ('path', '\u041e\u0431\u044a\u0435\u043a\u0442.ExtDimensionTypes.ExtDimensionType')]))
|
||||
ed_cols.append(OrderedDict([('check', '\u0422\u043e\u043b\u044c\u043a\u043e\u041e\u0431\u043e\u0440\u043e\u0442\u044b'), ('path', '\u041e\u0431\u044a\u0435\u043a\u0442.ExtDimensionTypes.TurnoversOnly')]))
|
||||
ed_cols.append(OrderedDict([('input', f"{ed_table}\u0412\u0438\u0434\u0421\u0443\u0431\u043a\u043e\u043d\u0442\u043e"), ('path', '\u041e\u0431\u044a\u0435\u043a\u0442.ExtDimensionTypes.ExtDimensionType')]))
|
||||
ed_cols.append(OrderedDict([('check', f"{ed_table}\u0422\u043e\u043b\u044c\u043a\u043e\u041e\u0431\u043e\u0440\u043e\u0442\u044b"), ('path', '\u041e\u0431\u044a\u0435\u043a\u0442.ExtDimensionTypes.TurnoversOnly')]))
|
||||
if meta.get('ExtDimensionAccountingFlags'):
|
||||
for ed_flag in meta['ExtDimensionAccountingFlags']:
|
||||
ed_cols.append(OrderedDict([('check', ed_flag['Name']), ('path', f"\u041e\u0431\u044a\u0435\u043a\u0442.ExtDimensionTypes.{ed_flag['Name']}")]))
|
||||
ed_cols.append(OrderedDict([('check', f"{ed_table}{ed_flag['Name']}"), ('path', f"\u041e\u0431\u044a\u0435\u043a\u0442.ExtDimensionTypes.{ed_flag['Name']}")]))
|
||||
elements.append(OrderedDict([
|
||||
('table', '\u0412\u0438\u0434\u044b\u0421\u0443\u0431\u043a\u043e\u043d\u0442\u043e'),
|
||||
('table', ed_table),
|
||||
('path', '\u041e\u0431\u044a\u0435\u043a\u0442.ExtDimensionTypes'),
|
||||
('columns', ed_cols),
|
||||
]))
|
||||
@@ -1703,6 +1706,17 @@ def new_id():
|
||||
return _next_id
|
||||
|
||||
|
||||
# Уникальность имён внутри коллекции (1С: элементы/реквизиты/команды/параметры/колонки — каждое своё
|
||||
# пространство имён). Дубль → битый XML, форма не открывается, поэтому fail-fast.
|
||||
_seen_element_names = set() # пул имён элементов (глобально по всей форме)
|
||||
|
||||
def _ensure_unique(name, seen, kind):
|
||||
if name in seen:
|
||||
print(f"[ERROR] Duplicate {kind} name '{name}' — names must be unique within their collection in a 1C form (set a unique 'name')", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
seen.add(name)
|
||||
|
||||
|
||||
# --- Event handler name generator ---
|
||||
|
||||
EVENT_SUFFIX_MAP = {
|
||||
@@ -2831,6 +2845,7 @@ def emit_element(lines, el, indent, in_cmd_bar=False):
|
||||
print(f"WARNING: Element '{el.get(type_key, '')}': unknown key '{p_name}' -- ignored. Check SKILL.md for valid keys.", file=sys.stderr)
|
||||
|
||||
name = get_element_name(el, type_key)
|
||||
_ensure_unique(name, _seen_element_names, 'element')
|
||||
eid = new_id()
|
||||
|
||||
emitters = {
|
||||
@@ -3975,9 +3990,11 @@ def emit_attributes(lines, attrs, indent):
|
||||
return
|
||||
|
||||
lines.append(f'{indent}<Attributes>')
|
||||
seen_attrs = set()
|
||||
for attr in attrs:
|
||||
attr_id = new_id()
|
||||
attr_name = str(attr['name'])
|
||||
_ensure_unique(attr_name, seen_attrs, 'attribute')
|
||||
|
||||
lines.append(f'{indent}\t<Attribute name="{attr_name}" id="{attr_id}">')
|
||||
inner = f'{indent}\t\t'
|
||||
@@ -4041,12 +4058,16 @@ def emit_attributes(lines, attrs, indent):
|
||||
if has_direct_cols or has_add_cols:
|
||||
lines.append(f'{inner}<Columns>')
|
||||
if has_direct_cols:
|
||||
seen_cols = set() # колонки уникальны в пределах своего реквизита
|
||||
for col in attr['columns']:
|
||||
_ensure_unique(str(col['name']), seen_cols, f"column of '{attr_name}'")
|
||||
emit_attr_column(lines, col, f'{inner}\t')
|
||||
if has_add_cols:
|
||||
for ac in attr['additionalColumns']:
|
||||
lines.append(f'{inner}\t<AdditionalColumns table="{ac["table"]}">')
|
||||
seen_ac_cols = set() # уникальность в пределах группы AdditionalColumns
|
||||
for col in (ac.get('columns') or []):
|
||||
_ensure_unique(str(col['name']), seen_ac_cols, f"column of '{attr_name}'")
|
||||
emit_attr_column(lines, col, f'{inner}\t\t')
|
||||
lines.append(f'{inner}\t</AdditionalColumns>')
|
||||
lines.append(f'{inner}</Columns>')
|
||||
@@ -4105,7 +4126,9 @@ def emit_parameters(lines, params, indent):
|
||||
return
|
||||
|
||||
lines.append(f'{indent}<Parameters>')
|
||||
seen_params = set()
|
||||
for param in params:
|
||||
_ensure_unique(str(param['name']), seen_params, 'parameter')
|
||||
lines.append(f'{indent}\t<Parameter name="{param["name"]}">')
|
||||
inner = f'{indent}\t\t'
|
||||
|
||||
@@ -4125,8 +4148,10 @@ def emit_commands(lines, cmds, indent):
|
||||
return
|
||||
|
||||
lines.append(f'{indent}<Commands>')
|
||||
seen_cmds = set()
|
||||
for cmd in cmds:
|
||||
cmd_id = new_id()
|
||||
_ensure_unique(str(cmd['name']), seen_cmds, 'command')
|
||||
lines.append(f'{indent}\t<Command name="{cmd["name"]}" id="{cmd_id}">')
|
||||
inner = f'{indent}\t\t'
|
||||
|
||||
@@ -4607,6 +4632,7 @@ def main():
|
||||
|
||||
# --- 2. Main compilation ---
|
||||
_next_id = 0
|
||||
_seen_element_names.clear() # пул имён элементов (на случай повторного вызова в одном процессе)
|
||||
lines = []
|
||||
|
||||
lines.append('<?xml version="1.0" encoding="UTF-8"?>')
|
||||
|
||||
Reference in New Issue
Block a user