diff --git a/.claude/skills/meta-edit/scripts/meta-edit.ps1 b/.claude/skills/meta-edit/scripts/meta-edit.ps1 index 1ae0d636..6ee9117a 100644 --- a/.claude/skills/meta-edit/scripts/meta-edit.ps1 +++ b/.claude/skills/meta-edit/scripts/meta-edit.ps1 @@ -1,4 +1,4 @@ -# meta-edit v1.9 — Edit existing 1C metadata object XML (inline mode + complex properties + TS attribute ops + modify-ts) +# meta-edit v1.10 — Edit existing 1C metadata object XML (inline mode + complex properties + TS attribute ops + modify-ts) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [string]$DefinitionFile, @@ -890,20 +890,33 @@ $script:reservedAttrNames = @{ "Account"="Счет"; "ValueType"="ТипЗначения"; "ActionPeriodIsBasic"="ПериодДействияБазовый" } +# Стандартные реквизиты по типу объекта (ключи из reservedAttrNames). Имя реквизита, совпадающее +# с ними (англ. ИЛИ рус.), платформа не позволит — жёсткий отказ. Контексты вне карты → предупреждение. +$script:reservedByContext = @{ + "catalog" = @("Ref","DeletionMark","Predefined","PredefinedDataName","Code","Description","Owner","Parent","IsFolder") + "document" = @("Ref","DeletionMark","Date","Number","Posted") +} + function Build-AttributeFragment { param($parsed, [string]$context, [string]$indent) if (-not $context) { $context = Get-AttributeContext } - # Check reserved attribute names + # Check reserved attribute names (типозависимо: catalog/document — жёсткий отказ; прочее — предупреждение) $attrName = $parsed.name - if ($script:reservedAttrNames.ContainsKey($attrName)) { + $ctxReserved = $script:reservedByContext[$context] + if ($ctxReserved) { + foreach ($en in $ctxReserved) { + $ru = $script:reservedAttrNames[$en] + if (($attrName -ieq $en) -or ($ru -and $attrName -ieq $ru)) { + Write-Error "Имя реквизита '$attrName' зарезервировано стандартным реквизитом ($en/$ru) объекта '$context'. Выберите другое имя." + exit 1 + } + } + } elseif ($context -notin @("tabular", "processor-tabular") -and + ($script:reservedAttrNames.ContainsKey($attrName) -or ($script:reservedAttrNames.Values -contains $attrName))) { Write-Warning "Attribute '$attrName' conflicts with a standard attribute name. This may cause errors when loading into 1C." } - $ruValues = $script:reservedAttrNames.Values - if ($ruValues -contains $attrName) { - Write-Warning "Attribute '$attrName' conflicts with a standard attribute name (Russian). This may cause errors when loading into 1C." - } $uuid = New-Guid-String $sb = New-Object System.Text.StringBuilder diff --git a/.claude/skills/meta-edit/scripts/meta-edit.py b/.claude/skills/meta-edit/scripts/meta-edit.py index c15f73a7..032406b0 100644 --- a/.claude/skills/meta-edit/scripts/meta-edit.py +++ b/.claude/skills/meta-edit/scripts/meta-edit.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# meta-edit v1.9 — Edit existing 1C metadata object XML (inline mode + complex properties + TS attribute ops + modify-ts) +# meta-edit v1.10 — Edit existing 1C metadata object XML (inline mode + complex properties + TS attribute ops + modify-ts) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -861,14 +861,43 @@ RESERVED_ATTR_NAMES_RU = { } +# Стандартные реквизиты по типу объекта (EN + RU). Совпадение имени реквизита с ними платформа +# не позволит — жёсткий отказ. Контексты вне карты → предупреждение по плоскому списку. +RESERVED_BY_CONTEXT = { + 'catalog': { + 'ref', 'ссылка', + 'deletionmark', 'пометкаудаления', + 'predefined', 'предопределенный', + 'predefineddataname', 'имяпредопределенныхданных', + 'code', 'код', + 'description', 'наименование', + 'owner', 'владелец', + 'parent', 'родитель', + 'isfolder', 'этогруппа', + }, + 'document': { + 'ref', 'ссылка', + 'deletionmark', 'пометкаудаления', + 'date', 'дата', + 'number', 'номер', + 'posted', 'проведен', + }, +} + + def build_attribute_fragment(parsed, context, indent): """Build XML fragment string for an Attribute element.""" if not context: context = get_attribute_context() - # Check reserved attribute names + # Check reserved attribute names (типозависимо: catalog/document — отказ; прочее — предупреждение) attr_name = parsed['name'] - if attr_name in RESERVED_ATTR_NAMES or attr_name in RESERVED_ATTR_NAMES_RU: + ctx_reserved = RESERVED_BY_CONTEXT.get(context) + if ctx_reserved is not None: + if attr_name.lower() in ctx_reserved: + print(f"meta-edit: имя реквизита '{attr_name}' зарезервировано стандартным реквизитом объекта '{context}'. Выберите другое имя.", file=sys.stderr) + sys.exit(1) + elif context not in ('tabular', 'processor-tabular') and (attr_name in RESERVED_ATTR_NAMES or attr_name in RESERVED_ATTR_NAMES_RU): print(f"WARNING: Attribute '{attr_name}' conflicts with a standard attribute name. This may cause errors when loading into 1C.", file=sys.stderr) uid = new_uuid() diff --git a/.claude/skills/meta-validate/scripts/meta-validate.ps1 b/.claude/skills/meta-validate/scripts/meta-validate.ps1 index 2881af28..e8b4cd87 100644 --- a/.claude/skills/meta-validate/scripts/meta-validate.ps1 +++ b/.claude/skills/meta-validate/scripts/meta-validate.ps1 @@ -1,4 +1,4 @@ -# meta-validate v1.4 — Validate 1C metadata object structure +# meta-validate v1.5 — Validate 1C metadata object structure # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -699,17 +699,31 @@ if ($childObjNode) { if ($script:stopped) { & $finalize; exit 1 } -# --- Check 7b: Reserved attribute names --- +# --- Check 7b: Reserved attribute names (типозависимо: стандартные реквизиты ДАННОГО типа, EN+RU) --- +# Совпадение имени собственного реквизита со стандартным (англ. или рус.) платформа не примет → ошибка. -$reservedAttrNames = @( - "Ref","DeletionMark","Code","Description","Date","Number","Posted","Parent","Owner", - "IsFolder","Predefined","PredefinedDataName","Recorder","Period","LineNumber","Active", - "Order","Type","OffBalance","Started","Completed","HeadTask","Executed","RoutePoint", - "BusinessProcess","ThisNode","SentNo","ReceivedNo","CalculationType","RegistrationPeriod", - "ReversingEntry","Account","ValueType","ActionPeriodIsBasic" -) +$reservedEnRu = @{ + "Ref"="Ссылка"; "DeletionMark"="ПометкаУдаления"; "Code"="Код"; "Description"="Наименование" + "Date"="Дата"; "Number"="Номер"; "Posted"="Проведен"; "Parent"="Родитель"; "Owner"="Владелец" + "IsFolder"="ЭтоГруппа"; "Predefined"="Предопределенный"; "PredefinedDataName"="ИмяПредопределенныхДанных" + "Recorder"="Регистратор"; "Period"="Период"; "LineNumber"="НомерСтроки"; "Active"="Активность" + "Order"="Порядок"; "Type"="Тип"; "OffBalance"="Забалансовый"; "RecordType"="ВидДвижения" + "Started"="Стартован"; "Completed"="Завершен"; "HeadTask"="ВедущаяЗадача" + "Executed"="Выполнена"; "RoutePoint"="ТочкаМаршрута"; "BusinessProcess"="БизнесПроцесс" + "ThisNode"="ЭтотУзел"; "SentNo"="НомерОтправленного"; "ReceivedNo"="НомерПринятого" + "CalculationType"="ВидРасчета"; "RegistrationPeriod"="ПериодРегистрации"; "ReversingEntry"="СторноЗапись" + "Account"="Счет"; "ValueType"="ТипЗначения"; "ActionPeriodIsBasic"="ПериодДействияБазовый" +} -if ($childObjNode) { +$stdForType = $standardAttributesByType[$mdType] +if ($childObjNode -and $stdForType) { + # Множество зарезервированных имён (EN + RU) в нижнем регистре. + $reservedSet = @{} + foreach ($en in $stdForType) { + $reservedSet[$en.ToLower()] = $true + $ru = $reservedEnRu[$en] + if ($ru) { $reservedSet[$ru.ToLower()] = $true } + } $check7bOk = $true $attrNodes = $childObjNode.SelectNodes("md:Attribute", $ns) foreach ($attrNode in $attrNodes) { @@ -718,8 +732,8 @@ if ($childObjNode) { $attrNameNode = $attrProps.SelectSingleNode("md:Name", $ns) if ($attrNameNode -and $attrNameNode.InnerText) { $an = $attrNameNode.InnerText - if ($reservedAttrNames -contains $an) { - Report-Warn "7b. Attribute '$an' conflicts with a standard attribute name" + if ($reservedSet.ContainsKey($an.ToLower())) { + Report-Error "7b. Attribute '$an' conflicts with a standard attribute of $mdType" $check7bOk = $false } } @@ -728,6 +742,8 @@ if ($childObjNode) { if ($check7bOk) { Report-OK "7b. Reserved attribute names: no conflicts" } +} elseif ($childObjNode) { + Report-OK "7b. Reserved attribute names: no conflicts (no standard set for $mdType)" } if ($script:stopped) { & $finalize; exit 1 } diff --git a/.claude/skills/meta-validate/scripts/meta-validate.py b/.claude/skills/meta-validate/scripts/meta-validate.py index 3b9eeb96..f306d520 100644 --- a/.claude/skills/meta-validate/scripts/meta-validate.py +++ b/.claude/skills/meta-validate/scripts/meta-validate.py @@ -1,4 +1,4 @@ -# meta-validate v1.4 — Validate 1C metadata object structure (Python port) +# meta-validate v1.5 — Validate 1C metadata object structure (Python port) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import os @@ -676,18 +676,30 @@ if stopped: finalize() sys.exit(1) -# ── Check 7b: Reserved attribute names ─────────────────────── +# ── Check 7b: Reserved attribute names (типозависимо: стандартные ДАННОГО типа, EN+RU) ─────── +# Совпадение имени собственного реквизита со стандартным (англ. или рус.) платформа не примет → ошибка. -RESERVED_ATTR_NAMES = { - 'Ref', 'DeletionMark', 'Code', 'Description', 'Date', 'Number', 'Posted', - 'Parent', 'Owner', 'IsFolder', 'Predefined', 'PredefinedDataName', - 'Recorder', 'Period', 'LineNumber', 'Active', 'Order', 'Type', 'OffBalance', - 'Started', 'Completed', 'HeadTask', 'Executed', 'RoutePoint', 'BusinessProcess', - 'ThisNode', 'SentNo', 'ReceivedNo', 'CalculationType', 'RegistrationPeriod', - 'ReversingEntry', 'Account', 'ValueType', 'ActionPeriodIsBasic', +RESERVED_EN_RU = { + 'Ref': 'Ссылка', 'DeletionMark': 'ПометкаУдаления', 'Code': 'Код', 'Description': 'Наименование', + 'Date': 'Дата', 'Number': 'Номер', 'Posted': 'Проведен', 'Parent': 'Родитель', 'Owner': 'Владелец', + 'IsFolder': 'ЭтоГруппа', 'Predefined': 'Предопределенный', 'PredefinedDataName': 'ИмяПредопределенныхДанных', + 'Recorder': 'Регистратор', 'Period': 'Период', 'LineNumber': 'НомерСтроки', 'Active': 'Активность', + 'Order': 'Порядок', 'Type': 'Тип', 'OffBalance': 'Забалансовый', 'RecordType': 'ВидДвижения', + 'Started': 'Стартован', 'Completed': 'Завершен', 'HeadTask': 'ВедущаяЗадача', + 'Executed': 'Выполнена', 'RoutePoint': 'ТочкаМаршрута', 'BusinessProcess': 'БизнесПроцесс', + 'ThisNode': 'ЭтотУзел', 'SentNo': 'НомерОтправленного', 'ReceivedNo': 'НомерПринятого', + 'CalculationType': 'ВидРасчета', 'RegistrationPeriod': 'ПериодРегистрации', 'ReversingEntry': 'СторноЗапись', + 'Account': 'Счет', 'ValueType': 'ТипЗначения', 'ActionPeriodIsBasic': 'ПериодДействияБазовый', } -if child_obj_node is not None: +std_for_type = standard_attributes_by_type.get(md_type) +if child_obj_node is not None and std_for_type: + reserved_set = set() + for en in std_for_type: + reserved_set.add(en.lower()) + ru = RESERVED_EN_RU.get(en) + if ru: + reserved_set.add(ru.lower()) check7b_ok = True for attr_node in find_all(child_obj_node, 'md:Attribute'): attr_props = find(attr_node, 'md:Properties') @@ -695,11 +707,13 @@ if child_obj_node is not None: attr_name_node = find(attr_props, 'md:Name') if attr_name_node is not None and inner_text(attr_name_node): an = inner_text(attr_name_node) - if an in RESERVED_ATTR_NAMES: - report_warn(f"7b. Attribute '{an}' conflicts with a standard attribute name") + if an.lower() in reserved_set: + report_error(f"7b. Attribute '{an}' conflicts with a standard attribute of {md_type}") check7b_ok = False if check7b_ok: report_ok("7b. Reserved attribute names: no conflicts") +elif child_obj_node is not None: + report_ok(f"7b. Reserved attribute names: no conflicts (no standard set for {md_type})") if stopped: finalize() diff --git a/tests/skills/cases/meta-edit/error-reserved-attr.json b/tests/skills/cases/meta-edit/error-reserved-attr.json new file mode 100644 index 00000000..c0d37494 --- /dev/null +++ b/tests/skills/cases/meta-edit/error-reserved-attr.json @@ -0,0 +1,15 @@ +{ + "name": "Ошибка: добавление реквизита с зарезервированным именем", + "preRun": [ + { + "script": "meta-compile/scripts/meta-compile", + "input": { "type": "Catalog", "name": "Контрагенты" }, + "args": { "-JsonPath": "{inputFile}", "-OutputDir": "{workDir}" } + } + ], + "params": { "objectPath": "Catalogs/Контрагенты" }, + "input": { + "add": { "attributes": ["Наименование: String(50)"] } + }, + "expectError": "зарезервировано стандартным реквизитом" +} diff --git a/tests/skills/cases/meta-validate/error-reserved-attr.json b/tests/skills/cases/meta-validate/error-reserved-attr.json new file mode 100644 index 00000000..84807f5c --- /dev/null +++ b/tests/skills/cases/meta-validate/error-reserved-attr.json @@ -0,0 +1,6 @@ +{ + "name": "Валидатор находит ошибку: имя реквизита совпадает со стандартным", + "setup": "fixture:catalog-reserved-attr", + "params": { "objectPath": "Catalogs/РезервТест.xml" }, + "expectError": true +} diff --git a/tests/skills/cases/meta-validate/fixtures/catalog-reserved-attr/Catalogs/РезервТест.xml b/tests/skills/cases/meta-validate/fixtures/catalog-reserved-attr/Catalogs/РезервТест.xml new file mode 100644 index 00000000..3f10ef8b --- /dev/null +++ b/tests/skills/cases/meta-validate/fixtures/catalog-reserved-attr/Catalogs/РезервТест.xml @@ -0,0 +1,137 @@ + + + + + + 50222869-fa45-4e19-b54c-f85738e99fe3 + f047dcb9-ac0b-481d-b3dd-08b79d14ac1e + + + d51e0c65-0896-4595-8b99-3fd6bf35f714 + 4df96eb8-e5ed-4223-897b-c83e5b780fe8 + + + abda7247-b3d1-4e54-982c-c6c5f2182724 + bb5893da-55cb-42c8-8f8f-d563768dcc39 + + + 62af546e-4901-4bc5-9744-212a0c6b8a19 + 246cf98e-057d-4f40-baf0-3f48581b8202 + + + 4b7564c8-c0e0-46ac-a01e-3508311c59b7 + 097659cc-1974-4110-b0b3-d8ddf03718c0 + + + + РезервТест + + + ru + Резерв тест + + + + false + HierarchyFoldersAndItems + false + 2 + true + true + + ToItems + 9 + 25 + String + Variable + WholeCatalog + false + true + AsDescription + + Auto + InDialog + false + BothWays + + Catalog.РезервТест.StandardAttribute.Description + Catalog.РезервТест.StandardAttribute.Code + + Begin + DontUse + Directly + + + + + + + + + + + false + + + Automatic + Use + + + + + + Use + Auto + DontUse + false + false + + + + + Код + + + ru + Приоритет + + + + + xs:decimal + + 5 + 0 + Any + + + false + + + + false + + false + false + + + false + 0 + DontCheck + Items + + + Auto + Auto + + + Auto + ForItem + DontIndex + Use + Use + + + + +