diff --git a/.claude/skills/meta-edit/child-operations.md b/.claude/skills/meta-edit/child-operations.md index 62ad8e645..4be02f942 100644 --- a/.claude/skills/meta-edit/child-operations.md +++ b/.claude/skills/meta-edit/child-operations.md @@ -43,6 +43,22 @@ -Operation add-column -Value "Тип: EnumRef.ТипыДокументов" ``` +## add-field (таблица внешнего источника) + +Поле задаётся как обычный реквизит, плюс три своих ключа: `nameInDataSource` (умолчание — имя поля), +`readOnly`, `allowNull`. Флаги строковой формы — `readonly`, `nullable`. + +```json +{ "add": { "fields": [ + "barcode: String(20) | nullable", + { "name": "cost", "type": "Number(15,2)", "nameInDataSource": "cost_net", "readOnly": true } +] } } +``` + +Сам внешний источник точечно не правится: и таблица (отдельный файл), и функция (узел с полным +набором свойств) собираются `meta-compile` по описанию источника целиком. Удалить таблицу — +`meta-remove ExternalDataSource.<Источник>.Table.<Таблица>`. + ## add-ts Формат: `ИмяТЧ: Реквизит1: Тип1, Реквизит2: Тип2, ...` diff --git a/.claude/skills/meta-edit/scripts/meta-edit.ps1 b/.claude/skills/meta-edit/scripts/meta-edit.ps1 index 9d0e0b166..90511deb7 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.43 — Edit existing 1C metadata object XML +# meta-edit v1.44 — Edit existing 1C metadata object XML # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills [CmdletBinding(PositionalBinding=$false)] param( @@ -465,6 +465,7 @@ $script:childTypeSynonyms = @{ "templates" = "templates"; "макеты" = "templates" "commands" = "commands"; "команды" = "commands" "properties" = "properties"; "свойства" = "properties" + "fields" = "fields"; "поля" = "fields" } # Type synonyms (from meta-compile) @@ -955,6 +956,10 @@ function Parse-AttributeShorthand { indexing = if ($val.indexing) { "$($val.indexing)" } else { "" } after = if ($val.after) { "$($val.after)" } else { "" } before = if ($val.before) { "$($val.before)" } else { "" } + # Поле таблицы внешнего источника (контекст eds-field). + nameInDataSource = if ($val.nameInDataSource) { "$($val.nameInDataSource)" } else { "" } + readOnly = if ($val.readOnly -eq $true) { $true } else { $false } + allowNull = if ($val.allowNull -eq $true) { $true } else { $false } } # Map flags to properties if ($result.flags -contains "req" -and -not $result.fillChecking) { @@ -994,6 +999,7 @@ function Parse-EnumValueShorthand { function Get-AttributeContext { switch ($script:objType) { "Catalog" { return "catalog" } + "Table" { return "eds-field" } "Document" { return "document" } { $_ -in @("InformationRegister","AccumulationRegister","AccountingRegister","CalculationRegister") } { return "register" } { $_ -in @("DataProcessor","Report","ExternalDataProcessor","ExternalReport") } { return "processor" } @@ -1022,7 +1028,7 @@ $script:reservedByContext = @{ } function Build-AttributeFragment { - param($parsed, [string]$context, [string]$indent) + param($parsed, [string]$context, [string]$indent, [string]$elemTag = "Attribute") if (-not $context) { $context = Get-AttributeContext } @@ -1037,7 +1043,7 @@ function Build-AttributeFragment { exit 1 } } - } elseif ($context -notin @("tabular", "processor-tabular") -and + } elseif ($context -notin @("tabular", "processor-tabular", "eds-field") -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." } @@ -1045,7 +1051,7 @@ function Build-AttributeFragment { $uuid = New-Guid-String $sb = New-Object System.Text.StringBuilder - $sb.AppendLine("$indent") | Out-Null + $sb.AppendLine("$indent<$elemTag uuid=`"$uuid`">") | Out-Null $sb.AppendLine("$indent`t") | Out-Null $sb.AppendLine("$indent`t`t$(Esc-XmlText $parsed.name)") | Out-Null $sb.AppendLine($(Build-MLTextXml "$indent`t`t" "Synonym" $parsed.synonym)) | Out-Null @@ -1084,22 +1090,39 @@ function Build-AttributeFragment { if ($parsed.fillChecking) { $fillChecking = Normalize-EnumValue "FillChecking" $parsed.fillChecking } $sb.AppendLine("$indent`t`t$fillChecking") | Out-Null - $sb.AppendLine("$indent`t`tItems") | Out-Null + # Поле внешнего источника (eds-field) не имеет ChoiceFoldersAndItems и LinkByType, а ChoiceForm + # у него стоит ПОСЛЕ ChoiceHistoryOnInput — порядок снят с выгрузки платформы. + if ($context -ne "eds-field") { + $sb.AppendLine("$indent`t`tItems") | Out-Null + } $sb.AppendLine("$indent`t`t") | Out-Null $sb.AppendLine("$indent`t`t") | Out-Null $sb.AppendLine("$indent`t`tAuto") | Out-Null $sb.AppendLine("$indent`t`tAuto") | Out-Null - $sb.AppendLine("$indent`t`t") | Out-Null - $sb.AppendLine("$indent`t`t") | Out-Null + if ($context -ne "eds-field") { + $sb.AppendLine("$indent`t`t") | Out-Null + $sb.AppendLine("$indent`t`t") | Out-Null + } $sb.AppendLine("$indent`t`tAuto") | Out-Null + if ($context -eq "eds-field") { + $sb.AppendLine("$indent`t`t") | Out-Null + $nids = if ($parsed.nameInDataSource) { "$($parsed.nameInDataSource)" } else { $parsed.name } + $sb.AppendLine("$indent`t`t$(Esc-XmlText $nids)") | Out-Null + $ro = if ($parsed.readOnly -eq $true -or $parsed.flags -contains "readonly") { "true" } else { "false" } + $sb.AppendLine("$indent`t`t$ro") | Out-Null + $an = if ($parsed.allowNull -eq $true -or $parsed.flags -contains "nullable") { "true" } else { "false" } + $sb.AppendLine("$indent`t`t$an") | Out-Null + } + # Use — catalog only if ($context -eq "catalog") { $sb.AppendLine("$indent`t`tForItem") | Out-Null } # Indexing/FullTextSearch/DataHistory — not for non-stored objects (processor, processor-tabular) - if ($context -notin @("processor", "processor-tabular")) { + # Поля внешнего источника: индексами чужой таблицы 1С не владеет. + if ($context -notin @("processor", "processor-tabular", "eds-field")) { $indexing = "DontIndex" if ($parsed.flags -contains "index") { $indexing = "Index" } if ($parsed.flags -contains "indexadditional") { $indexing = "IndexWithAdditionalOrder" } @@ -1111,7 +1134,7 @@ function Build-AttributeFragment { } $sb.AppendLine("$indent`t") | Out-Null - $sb.Append("$indent") | Out-Null + $sb.Append("$indent") | Out-Null return $sb.ToString() } @@ -1538,11 +1561,15 @@ $script:validChildTypes = @{ "CalculationRegister" = @("dimensions","resources","attributes","forms","templates","commands") "DocumentJournal" = @("columns","forms","templates","commands") "Constant" = @("forms") + # Внешний источник данных правится целиком через meta-compile: и таблица (отдельный файл), + # и функция (узел с полным набором свойств) требуют эмиттера, который живёт там. + "ExternalDataSource" = @() + "Table" = @("fields","forms","templates","commands") } # Canonical child order in ChildObjects $script:childOrder = @( - "Resource", "Dimension", "Attribute", "TabularSection", + "Resource", "Dimension", "Attribute", "TabularSection", "Field", "Function", "AccountingFlag", "ExtDimensionAccountingFlag", "EnumValue", "Column", "AddressingAttribute", "Recalculation", "Form", "Template", "Command" @@ -1559,6 +1586,7 @@ $script:childTypeToXmlTag = @{ "forms" = "Form" "templates" = "Template" "commands" = "Command" + "fields" = "Field" } # ============================================================ @@ -1904,11 +1932,15 @@ function Process-Add($addDef) { return } - # Validate allowed - $allowed = $script:validChildTypes[$script:objType] - if ($allowed -and $childType -notin $allowed) { - Warn "$childType not allowed for $($script:objType), skipping" - return + # Validate allowed. Проверяем НАЛИЧИЕ ключа, а не истинность списка: пустой список + # ($script:objType без допустимых детей) трактовался как «ограничений нет», и чужой + # ребёнок молча записывался в объект. + if ($script:validChildTypes.ContainsKey($script:objType)) { + $allowed = $script:validChildTypes[$script:objType] + if ($childType -notin $allowed) { + Warn "$childType not allowed for $($script:objType), skipping" + return + } } $xmlTag = $script:childTypeToXmlTag[$childType] @@ -1941,6 +1973,25 @@ function Process-Add($addDef) { $existingNames[$parsed.name] = "Attribute" } } + "fields" { + # Поле таблицы внешнего источника: тот же парсер реквизита, свой тег и контекст. + foreach ($item in $items) { + $parsed = Parse-AttributeShorthand $item + if ($existingNames.ContainsKey($parsed.name)) { + Warn "Field '$($parsed.name)' already exists, skipping" + continue + } + $fragmentXml = Build-AttributeFragment $parsed "eds-field" $indent "Field" + $nodes = Import-Fragment $fragmentXml + $refNode = Find-InsertionPoint "Field" $parsed + foreach ($node in $nodes) { + Insert-BeforeElement $script:childObjectsEl $node $refNode $indent + } + Info "Added field: $($parsed.name)" + $script:addCount++ + $existingNames[$parsed.name] = "Field" + } + } "tabularSections" { foreach ($item in $items) { $tsName = if ($item -is [string]) { "$item" } else { "$($item.name)" } diff --git a/.claude/skills/meta-edit/scripts/meta-edit.py b/.claude/skills/meta-edit/scripts/meta-edit.py index 0ad834873..e3a4759c0 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.43 — Edit existing 1C metadata object XML +# meta-edit v1.44 — Edit existing 1C metadata object XML # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -503,6 +503,7 @@ child_type_synonyms = { "templates": "templates", "макеты": "templates", "commands": "commands", "команды": "commands", "properties": "properties", "свойства": "properties", + "fields": "fields", "поля": "fields", } type_synonyms = { @@ -967,6 +968,10 @@ def parse_attribute_shorthand(val): "indexing": normalize_enum_value("Indexing", str(val.get("indexing", ""))) if val.get("indexing") else "", "after": str(val.get("after", "")), "before": str(val.get("before", "")), + # Поле таблицы внешнего источника (контекст eds-field). + "nameInDataSource": str(val.get("nameInDataSource", "")), + "readOnly": val.get("readOnly") is True, + "allowNull": val.get("allowNull") is True, } # Map flags to properties if "req" in result["flags"] and not result["fillChecking"]: @@ -1002,6 +1007,8 @@ def get_attribute_context(): """Determine attribute context from object type.""" if obj_type == "Catalog": return "catalog" + if obj_type == "Table": + return "eds-field" if obj_type == "Document": return "document" if obj_type in ("InformationRegister", "AccumulationRegister", "AccountingRegister", "CalculationRegister"): @@ -1057,7 +1064,7 @@ RESERVED_BY_CONTEXT = { } -def build_attribute_fragment(parsed, context, indent): +def build_attribute_fragment(parsed, context, indent, elem_tag="Attribute"): """Build XML fragment string for an Attribute element.""" if not context: context = get_attribute_context() @@ -1069,13 +1076,13 @@ def build_attribute_fragment(parsed, context, indent): 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): + elif context not in ('tabular', 'processor-tabular', 'eds-field') 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() lines = [] - lines.append(f'{indent}') + lines.append(f'{indent}<{elem_tag} uuid="{uid}">') lines.append(f"{indent}\t") lines.append(f"{indent}\t\t{esc_xml_text(parsed['name'])}") lines.append(build_mltext_xml(f"{indent}\t\t", "Synonym", parsed["synonym"])) @@ -1114,21 +1121,35 @@ def build_attribute_fragment(parsed, context, indent): fill_checking = parsed["fillChecking"] lines.append(f"{indent}\t\t{fill_checking}") - lines.append(f"{indent}\t\tItems") + # Поле внешнего источника (eds-field) не имеет ChoiceFoldersAndItems и LinkByType, а ChoiceForm + # у него стоит ПОСЛЕ ChoiceHistoryOnInput — порядок снят с выгрузки платформы. + if context != "eds-field": + lines.append(f"{indent}\t\tItems") lines.append(f"{indent}\t\t") lines.append(f"{indent}\t\t") lines.append(f"{indent}\t\tAuto") lines.append(f"{indent}\t\tAuto") - lines.append(f"{indent}\t\t") - lines.append(f"{indent}\t\t") + if context != "eds-field": + lines.append(f"{indent}\t\t") + lines.append(f"{indent}\t\t") lines.append(f"{indent}\t\tAuto") + if context == "eds-field": + lines.append(f"{indent}\t\t") + nids = parsed.get("nameInDataSource") or parsed["name"] + lines.append(f"{indent}\t\t{esc_xml_text(str(nids))}") + ro = "true" if (parsed.get("readOnly") is True or "readonly" in parsed["flags"]) else "false" + lines.append(f"{indent}\t\t{ro}") + an = "true" if (parsed.get("allowNull") is True or "nullable" in parsed["flags"]) else "false" + lines.append(f"{indent}\t\t{an}") + # Use -- catalog only if context == "catalog": lines.append(f"{indent}\t\tForItem") # Indexing/FullTextSearch/DataHistory -- not for non-stored objects - if context not in ("processor", "processor-tabular"): + # Поля внешнего источника: индексами чужой таблицы 1С не владеет. + if context not in ("processor", "processor-tabular", "eds-field"): indexing = "DontIndex" if "index" in parsed["flags"]: indexing = "Index" @@ -1141,7 +1162,7 @@ def build_attribute_fragment(parsed, context, indent): lines.append(f"{indent}\t\tUse") lines.append(f"{indent}\t") - lines.append(f"{indent}") + lines.append(f"{indent}") return "\r\n".join(lines) @@ -1530,11 +1551,15 @@ valid_child_types = { "CalculationRegister": ["dimensions", "resources", "attributes", "forms", "templates", "commands"], "DocumentJournal": ["columns", "forms", "templates", "commands"], "Constant": ["forms"], + # Внешний источник данных правится целиком через meta-compile: и таблица (отдельный файл), + # и функция (узел с полным набором свойств) требуют эмиттера, который живёт там. + "ExternalDataSource": [], + "Table": ["fields", "forms", "templates", "commands"], } # Canonical child order in ChildObjects child_order = [ - "Resource", "Dimension", "Attribute", "TabularSection", + "Resource", "Dimension", "Attribute", "TabularSection", "Field", "Function", "AccountingFlag", "ExtDimensionAccountingFlag", "EnumValue", "Column", "AddressingAttribute", "Recalculation", "Form", "Template", "Command", @@ -1551,6 +1576,7 @@ child_type_to_xml_tag = { "forms": "Form", "templates": "Template", "commands": "Command", + "fields": "Field", } # ============================================================ @@ -1855,11 +1881,13 @@ def process_add(add_def): warn(f"Unknown add child type: {raw_key}") continue - # Validate allowed - allowed = valid_child_types.get(obj_type) - if allowed and child_type not in allowed: - warn(f"{child_type} not allowed for {obj_type}, skipping") - continue + # Validate allowed. Проверяем НАЛИЧИЕ ключа, а не истинность списка: пустой список + # (объект без допустимых детей) трактовался как «ограничений нет», и чужой ребёнок + # молча записывался в объект. + if obj_type in valid_child_types: + if child_type not in valid_child_types[obj_type]: + warn(f"{child_type} not allowed for {obj_type}, skipping") + continue xml_tag = child_type_to_xml_tag.get(child_type) if not xml_tag: @@ -1886,6 +1914,22 @@ def process_add(add_def): add_count += 1 existing_names[parsed["name"]] = "Attribute" + elif child_type == "fields": + # Поле таблицы внешнего источника: тот же парсер реквизита, свой тег и контекст. + for item in items: + parsed = parse_attribute_shorthand(item) + if parsed["name"] in existing_names: + warn(f"Field '{parsed['name']}' already exists, skipping") + continue + fragment_xml = build_attribute_fragment(parsed, "eds-field", indent, "Field") + nodes = import_fragment(fragment_xml) + ref_node = find_insertion_point("Field", parsed) + for node in nodes: + insert_before_element(child_objects_el, node, ref_node, indent) + info(f"Added field: {parsed['name']}") + add_count += 1 + existing_names[parsed["name"]] = "Field" + elif child_type == "tabularSections": for item in items: if isinstance(item, str): diff --git a/.claude/skills/meta-remove/SKILL.md b/.claude/skills/meta-remove/SKILL.md index 168352119..f82eb7077 100644 --- a/.claude/skills/meta-remove/SKILL.md +++ b/.claude/skills/meta-remove/SKILL.md @@ -24,7 +24,7 @@ allowed-tools: | Параметр | Обязательный | Описание | |------------|:------------:|-------------------------------------------------| | ConfigDir | да | Корневая директория выгрузки (где Configuration.xml) | -| Object | да | Тип и имя объекта: `Catalog.Товары`, `Document.Заказ` и т.д. | +| Object | да | Тип и имя объекта: `Catalog.Товары`, `Document.Заказ` и т.д. Таблица внешнего источника — четырьмя частями: `ExternalDataSource.PG.Table.products` | | DryRun | нет | Только показать что будет удалено, без изменений | | KeepFiles | нет | Не удалять файлы, только дерегистрировать | | Force | нет | Удалить несмотря на найденные ссылки; ссылки на формы объекта при этом очищаются | diff --git a/.claude/skills/meta-remove/scripts/meta-remove.ps1 b/.claude/skills/meta-remove/scripts/meta-remove.ps1 index 665a51c24..fae531979 100644 --- a/.claude/skills/meta-remove/scripts/meta-remove.ps1 +++ b/.claude/skills/meta-remove/scripts/meta-remove.ps1 @@ -1,4 +1,4 @@ -# meta-remove v1.11 — Remove metadata object from 1C configuration dump +# meta-remove v1.12 — Remove metadata object from 1C configuration dump # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills [CmdletBinding(PositionalBinding=$false)] param( @@ -60,6 +60,7 @@ $typePluralMap = @{ "WSReference" = "WSReferences" "StyleItem" = "StyleItems" "Language" = "Languages" + "ExternalDataSource" = "ExternalDataSources" } # --- Resolve paths --- @@ -216,21 +217,47 @@ function Assert-EditAllowed([string]$targetPath, [string]$require) { # --- Parse object spec --- -$parts = $Object -split "\.", 2 -if ($parts.Count -ne 2 -or -not $parts[0] -or -not $parts[1]) { - Write-Host "[ERROR] Invalid object format '$Object'. Expected: Type.Name (e.g. Catalog.Товары)" - exit 1 +# Таблица внешнего источника — единственный объект с четырёхчастным именем: она лежит не в +# каталоге вида, а внутри источника, и числится в ChildObjects файла источника, не конфигурации. +$edsSource = "" +if ($Object -match '^ExternalDataSource\.([^.]+)\.Table\.(.+)$') { + $edsSource = $Matches[1] + $objType = "Table" + $objName = $Matches[2] +} else { + $parts = $Object -split "\.", 2 + if ($parts.Count -ne 2 -or -not $parts[0] -or -not $parts[1]) { + Write-Host "[ERROR] Invalid object format '$Object'. Expected: Type.Name (e.g. Catalog.Товары) or ExternalDataSource.Источник.Table.Таблица" + exit 1 + } + $objType = $parts[0] + $objName = $parts[1] } -$objType = $parts[0] -$objName = $parts[1] - -if (-not $typePluralMap.ContainsKey($objType)) { - Write-Host "[ERROR] Unknown type '$objType'. Supported: $($typePluralMap.Keys -join ', ')" - exit 1 +if ($edsSource) { + $typePlural = Join-Path (Join-Path "ExternalDataSources" $edsSource) "Tables" +} else { + if (-not $typePluralMap.ContainsKey($objType)) { + Write-Host "[ERROR] Unknown type '$objType'. Supported: $($typePluralMap.Keys -join ', ')" + exit 1 + } + $typePlural = $typePluralMap[$objType] } -$typePlural = $typePluralMap[$objType] +# Реестр, где объект числится: обычно ChildObjects конфигурации, а для таблицы — файл источника. +if ($edsSource) { + $registryXml = Join-Path (Join-Path $ConfigDir "ExternalDataSources") "$edsSource.xml" + $registryRoot = "ExternalDataSource" + $registryLabel = "ExternalDataSources/$edsSource.xml" + if (-not (Test-Path $registryXml)) { + Write-Host "[ERROR] Внешний источник '$edsSource' не найден: $registryLabel" + exit 1 + } +} else { + $registryXml = $configXml + $registryRoot = "Configuration" + $registryLabel = "Configuration.xml" +} Write-Host "=== meta-remove: ${objType}.${objName} ===" Write-Host "" @@ -304,10 +331,10 @@ if (-not $hasXml -and -not $hasDir) { # Check if registered in Configuration.xml before proceeding $cfgCheckDoc = New-Object System.Xml.XmlDocument $cfgCheckDoc.PreserveWhitespace = $true - $cfgCheckDoc.Load($configXml) + $cfgCheckDoc.Load($registryXml) $cfgCheckNs = New-Object System.Xml.XmlNamespaceManager($cfgCheckDoc.NameTable) $cfgCheckNs.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses") - $cfgCheckNode = $cfgCheckDoc.DocumentElement.SelectSingleNode("md:Configuration/md:ChildObjects", $cfgCheckNs) + $cfgCheckNode = $cfgCheckDoc.DocumentElement.SelectSingleNode("md:$registryRoot/md:ChildObjects", $cfgCheckNs) $registeredInCfg = $false if ($cfgCheckNode) { foreach ($child in @($cfgCheckNode.ChildNodes)) { @@ -390,6 +417,18 @@ if ($ruMgr) { # English manager = plural directory name $searchPatterns += "$typePlural.$objName" +# 2а) Внешний источник данных: ссылки на сам источник и на его таблицы +if ($objType -eq "ExternalDataSource") { + $searchPatterns += "ExternalDataSource.$objName." + $searchPatterns += "ВнешниеИсточникиДанных.$objName" + $searchPatterns += "ExternalDataSources.$objName" +} +if ($edsSource) { + $searchPatterns += "ExternalDataSource.$edsSource.Table.$objName" + $searchPatterns += "ExternalDataSourceTableRef.$edsSource.$objName" + $searchPatterns += "ВнешниеИсточникиДанных.$edsSource.Таблицы.$objName" +} + # 3) CommonModule: method calls in BSL (ModuleName.) if ($objType -eq "CommonModule") { $searchPatterns += "$objName." @@ -504,22 +543,22 @@ if ($references.Count -gt 0) { Write-Host "[OK] No references found" } -# --- 3. Remove from Configuration.xml ChildObjects --- +# --- 3. Remove from registry ChildObjects (Configuration.xml или файл внешнего источника) --- Write-Host "" -Write-Host "--- Configuration.xml ---" +Write-Host "--- $registryLabel ---" $xmlDoc = New-Object System.Xml.XmlDocument $xmlDoc.PreserveWhitespace = $true -$xmlDoc.Load($configXml) +$xmlDoc.Load($registryXml) $ns = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable) $ns.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses") $ns.AddNamespace("v8", "http://v8.1c.ru/8.1/data/core") -$cfgNode = $xmlDoc.DocumentElement.SelectSingleNode("md:Configuration", $ns) +$cfgNode = $xmlDoc.DocumentElement.SelectSingleNode("md:$registryRoot", $ns) if (-not $cfgNode) { - Write-Host "[ERROR] Configuration element not found in Configuration.xml" + Write-Host "[ERROR] $registryRoot element not found in $registryLabel" $errors++ } else { $childObjects = $cfgNode.SelectSingleNode("md:ChildObjects", $ns) @@ -570,10 +609,10 @@ if (-not $cfgNode) { $xmlText = [regex]::Replace($xmlText, '(?s)||(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } }) # Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47), # новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту. - $targetEol = if ((Test-Path -LiteralPath $configXml) -and ([System.IO.File]::ReadAllText($configXml) -notmatch "`r`n")) { "`n" } else { "`r`n" } + $targetEol = if ((Test-Path -LiteralPath $registryXml) -and ([System.IO.File]::ReadAllText($registryXml) -notmatch "`r`n")) { "`n" } else { "`r`n" } $xmlText = ($xmlText -replace "`r`n", "`n") -replace "`n", $targetEol - [System.IO.File]::WriteAllText($configXml, $xmlText, $enc) - Write-Host "[OK] Configuration.xml saved" + [System.IO.File]::WriteAllText($registryXml, $xmlText, $enc) + Write-Host "[OK] $registryLabel saved" } } diff --git a/.claude/skills/meta-remove/scripts/meta-remove.py b/.claude/skills/meta-remove/scripts/meta-remove.py index ab7ec5b53..4316b3232 100644 --- a/.claude/skills/meta-remove/scripts/meta-remove.py +++ b/.claude/skills/meta-remove/scripts/meta-remove.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# meta-remove v1.11 — Remove metadata object from 1C configuration dump +# meta-remove v1.12 — Remove metadata object from 1C configuration dump # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -250,6 +250,7 @@ TYPE_PLURAL_MAP = { "WSReference": "WSReferences", "StyleItem": "StyleItems", "Language": "Languages", + "ExternalDataSource": "ExternalDataSources", } # Type -> reference type names (used in XML elements) @@ -388,19 +389,42 @@ def main(): sys.exit(1) # --- Parse object spec --- - parts = args.Object.split(".", 1) - if len(parts) != 2 or not parts[0] or not parts[1]: - print(f"[ERROR] Invalid object format '{args.Object}'. Expected: Type.Name (e.g. Catalog.\u0422\u043e\u0432\u0430\u0440\u044b)") - sys.exit(1) + # Таблица внешнего источника — единственный объект с четырёхчастным именем: она лежит не в + # каталоге вида, а внутри источника, и числится в ChildObjects файла источника, не конфигурации. + eds_source = "" + m_eds = re.match(r'^ExternalDataSource\.([^.]+)\.Table\.(.+)$', args.Object) + if m_eds: + eds_source = m_eds.group(1) + obj_type = "Table" + obj_name = m_eds.group(2) + else: + parts = args.Object.split(".", 1) + if len(parts) != 2 or not parts[0] or not parts[1]: + print(f"[ERROR] Invalid object format '{args.Object}'. Expected: Type.Name (e.g. Catalog.\u0422\u043e\u0432\u0430\u0440\u044b) or ExternalDataSource.\u0418\u0441\u0442\u043e\u0447\u043d\u0438\u043a.Table.\u0422\u0430\u0431\u043b\u0438\u0446\u0430") + sys.exit(1) + obj_type = parts[0] + obj_name = parts[1] - obj_type = parts[0] - obj_name = parts[1] + if eds_source: + type_plural = os.path.join("ExternalDataSources", eds_source, "Tables") + else: + if obj_type not in TYPE_PLURAL_MAP: + print(f"[ERROR] Unknown type '{obj_type}'. Supported: {', '.join(TYPE_PLURAL_MAP.keys())}") + sys.exit(1) + type_plural = TYPE_PLURAL_MAP[obj_type] - if obj_type not in TYPE_PLURAL_MAP: - print(f"[ERROR] Unknown type '{obj_type}'. Supported: {', '.join(TYPE_PLURAL_MAP.keys())}") - sys.exit(1) - - type_plural = TYPE_PLURAL_MAP[obj_type] + # Реестр, где объект числится: обычно ChildObjects конфигурации, а для таблицы — файл источника. + if eds_source: + registry_xml = os.path.join(config_dir, "ExternalDataSources", f"{eds_source}.xml") + registry_root = "ExternalDataSource" + registry_label = f"ExternalDataSources/{eds_source}.xml" + if not os.path.isfile(registry_xml): + print(f"[ERROR] \u0412\u043d\u0435\u0448\u043d\u0438\u0439 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a '{eds_source}' \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d: {registry_label}") + sys.exit(1) + else: + registry_xml = config_xml + registry_root = "Configuration" + registry_label = "Configuration.xml" print(f"=== meta-remove: {obj_type}.{obj_name} ===") print() @@ -425,7 +449,7 @@ def main(): if not has_xml and not has_dir: # Check if registered in Configuration.xml before proceeding - cfg_check_tree = etree.parse(config_xml, etree.XMLParser(remove_blank_text=False)) + cfg_check_tree = etree.parse(registry_xml, etree.XMLParser(remove_blank_text=False)) cfg_check_root = cfg_check_tree.getroot() child_objects = cfg_check_root.find(f"{{{MD_NS}}}Configuration/{{{MD_NS}}}ChildObjects") registered_in_cfg = False @@ -463,6 +487,16 @@ def main(): search_patterns.append(f"{ru_mgr}.{obj_name}") search_patterns.append(f"{type_plural}.{obj_name}") + # 2а) Внешний источник данных: ссылки на сам источник и на его таблицы + if obj_type == "ExternalDataSource": + search_patterns.append(f"ExternalDataSource.{obj_name}.") + search_patterns.append(f"\u0412\u043d\u0435\u0448\u043d\u0438\u0435\u0418\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0438\u0414\u0430\u043d\u043d\u044b\u0445.{obj_name}") + search_patterns.append(f"ExternalDataSources.{obj_name}") + if eds_source: + search_patterns.append(f"ExternalDataSource.{eds_source}.Table.{obj_name}") + search_patterns.append(f"ExternalDataSourceTableRef.{eds_source}.{obj_name}") + search_patterns.append(f"\u0412\u043d\u0435\u0448\u043d\u0438\u0435\u0418\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0438\u0414\u0430\u043d\u043d\u044b\u0445.{eds_source}.\u0422\u0430\u0431\u043b\u0438\u0446\u044b.{obj_name}") + # 3) CommonModule: method calls if obj_type == "CommonModule": search_patterns.append(f"{obj_name}.") @@ -578,17 +612,17 @@ def main(): else: print("[OK] No references found") - # --- 3. Remove from Configuration.xml ChildObjects --- + # --- 3. Remove from registry ChildObjects (Configuration.xml или файл внешнего источника) --- print() - print("--- Configuration.xml ---") + print(f"--- {registry_label} ---") xml_parser = etree.XMLParser(remove_blank_text=False) - tree = etree.parse(config_xml, xml_parser) + tree = etree.parse(registry_xml, xml_parser) xml_root = tree.getroot() - cfg_node = xml_root.find(f"{{{MD_NS}}}Configuration") + cfg_node = xml_root.find(f"{{{MD_NS}}}{registry_root}") if cfg_node is None: - print("[ERROR] Configuration element not found in Configuration.xml") + print(f"[ERROR] {registry_root} element not found in {registry_label}") errors += 1 else: child_objects = cfg_node.find(f"{{{MD_NS}}}ChildObjects") @@ -611,8 +645,8 @@ def main(): # Save Configuration.xml if actions > 0 and not args.DryRun: - save_xml_bom(tree, config_xml) - print("[OK] Configuration.xml saved") + save_xml_bom(tree, registry_xml) + print(f"[OK] {registry_label} saved") # --- 4. Remove from subsystem Content --- print() diff --git a/tests/skills/cases/meta-edit/eds-add-field.json b/tests/skills/cases/meta-edit/eds-add-field.json new file mode 100644 index 000000000..def7e8713 --- /dev/null +++ b/tests/skills/cases/meta-edit/eds-add-field.json @@ -0,0 +1,42 @@ +{ + "name": "Поле в таблицу внешнего источника; реквизит туда не пускают", + "setup": "empty-config", + "preRun": [ + { + "script": "meta-compile/scripts/meta-compile", + "input": { + "type": "ExternalDataSource", + "name": "PG", + "tables": { + "products": { + "keyFields": ["id"], + "fields": ["id: Number(10,0)", "name: String(150)"] + } + } + }, + "args": { "-JsonPath": "{inputFile}", "-OutputDir": "{workDir}" } + } + ], + "params": { "objectPath": "ExternalDataSources/PG/Tables/products.xml" }, + "input": { + "add": { + "fields": [ + "barcode: String(20) | nullable", + { "name": "cost", "type": "Number(15,2)", "nameInDataSource": "cost_net", "readOnly": true } + ], + "attributes": ["Лишний: String(10)"] + } + }, + "expect": { + "stdoutContains": ["Added field: barcode", "Added field: cost", "attributes not allowed for Table"], + "fileContains": [ + { + "file": "ExternalDataSources/PG/Tables/products.xml", + "text": ["cost_net", "true", ""] } + ] + } +} diff --git a/tests/skills/cases/meta-edit/snapshots/eds-add-field/Configuration.xml b/tests/skills/cases/meta-edit/snapshots/eds-add-field/Configuration.xml new file mode 100644 index 000000000..da0e784ee --- /dev/null +++ b/tests/skills/cases/meta-edit/snapshots/eds-add-field/Configuration.xml @@ -0,0 +1,252 @@ + + + + + + UUID-002 + UUID-003 + + + UUID-004 + UUID-005 + + + UUID-006 + UUID-007 + + + UUID-008 + UUID-009 + + + UUID-010 + UUID-011 + + + UUID-012 + UUID-013 + + + UUID-014 + UUID-015 + + + + TestConfig + + + ru + TestConfig + + + + + Version8_3_24 + ManagedApplication + + PlatformApplication + + Russian + + + + + false + false + false + + + + + + + + + + + + + + + + + + + + + + Biometrics + true + + + Location + false + + + BackgroundLocation + false + + + BluetoothPrinters + false + + + WiFiPrinters + false + + + Contacts + false + + + Calendars + false + + + PushNotifications + false + + + LocalNotifications + false + + + InAppPurchases + false + + + PersonalComputerFileExchange + false + + + Ads + false + + + NumberDialing + false + + + CallProcessing + false + + + CallLog + false + + + AutoSendSMS + false + + + ReceiveSMS + false + + + SMSLog + false + + + Camera + false + + + Microphone + false + + + MusicLibrary + false + + + PictureAndVideoLibraries + false + + + AudioPlaybackAndVibration + false + + + BackgroundAudioPlaybackAndVibration + false + + + InstallPackages + false + + + OSBackup + true + + + ApplicationUsageStatistics + false + + + BarcodeScanning + false + + + BackgroundAudioRecording + false + + + AllFilesAccess + false + + + Videoconferences + false + + + NFC + false + + + DocumentScanning + false + + + SpeechToText + false + + + Geofences + false + + + IncomingShareRequests + false + + + AllIncomingShareRequestsTypesProcessing + false + + + + + + Normal + + + Language.Русский + + + + + + Managed + NotAutoFree + DontUse + DontUse + TaxiEnableVersion8_2 + DontUse + Version8_3_24 + + + + Русский + PG + + + \ No newline at end of file diff --git a/tests/skills/cases/meta-edit/snapshots/eds-add-field/Ext/ClientApplicationInterface.xml b/tests/skills/cases/meta-edit/snapshots/eds-add-field/Ext/ClientApplicationInterface.xml new file mode 100644 index 000000000..3c1161b2d --- /dev/null +++ b/tests/skills/cases/meta-edit/snapshots/eds-add-field/Ext/ClientApplicationInterface.xml @@ -0,0 +1,18 @@ + + + + + UUID-002 + + + + + UUID-004 + + + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/meta-edit/snapshots/eds-add-field/ExternalDataSources/PG.xml b/tests/skills/cases/meta-edit/snapshots/eds-add-field/ExternalDataSources/PG.xml new file mode 100644 index 000000000..a1b89619b --- /dev/null +++ b/tests/skills/cases/meta-edit/snapshots/eds-add-field/ExternalDataSources/PG.xml @@ -0,0 +1,33 @@ + + + + + + UUID-002 + UUID-003 + + + UUID-004 + UUID-005 + + + UUID-006 + UUID-007 + + + + PG + + + ru + PG + + + + Automatic + + + products
+
+
+
\ No newline at end of file diff --git a/tests/skills/cases/meta-edit/snapshots/eds-add-field/ExternalDataSources/PG/Tables/products.xml b/tests/skills/cases/meta-edit/snapshots/eds-add-field/ExternalDataSources/PG/Tables/products.xml new file mode 100644 index 000000000..10024164c --- /dev/null +++ b/tests/skills/cases/meta-edit/snapshots/eds-add-field/ExternalDataSources/PG/Tables/products.xml @@ -0,0 +1,254 @@ + + + + + + UUID-002 + UUID-003 + + + UUID-004 + UUID-005 + + + UUID-006 + UUID-007 + + + UUID-008 + UUID-009 + + + UUID-010 + UUID-011 + + + UUID-012 + UUID-013 + + + UUID-014 + UUID-015 + + + UUID-016 + UUID-017 + + + + products + + + ru + products + + + + Table + products + + NonobjectData + + ExternalDataSource.PG.Table.products.Field.id + + + + + + true + false + + Auto + Begin + Directly + Auto + + + + + + + + + + + + false + false + Auto + + InDialog + + + Automatic + + + + + id + + + ru + id + + + + + xs:decimal + + 10 + 0 + Any + + + false + + + + false + + false + false + + + false + 0 + DontCheck + + + Auto + Auto + Auto + + id + false + false + + + + + name + + + ru + name + + + + + xs:string + + 150 + Variable + + + false + + + + false + + false + false + + + false + + DontCheck + + + Auto + Auto + Auto + + name + false + false + + + + + barcode + + + ru + barcode + + + + + xs:string + + 20 + Variable + + + false + + + + false + + false + false + + + false + + DontCheck + + + Auto + Auto + Auto + + barcode + false + true + + + + + cost + + + ru + cost + + + + + xs:decimal + + 15 + 2 + Any + + + false + + + + false + + false + false + + + false + 0 + DontCheck + + + Auto + Auto + Auto + + cost_net + true + false + + + +
+
\ No newline at end of file diff --git a/tests/skills/cases/meta-edit/snapshots/eds-add-field/Languages/Русский.xml b/tests/skills/cases/meta-edit/snapshots/eds-add-field/Languages/Русский.xml new file mode 100644 index 000000000..37c60d786 --- /dev/null +++ b/tests/skills/cases/meta-edit/snapshots/eds-add-field/Languages/Русский.xml @@ -0,0 +1,16 @@ + + + + + Русский + + + ru + Русский + + + + ru + + + \ No newline at end of file diff --git a/tests/skills/cases/meta-remove/eds-table.json b/tests/skills/cases/meta-remove/eds-table.json new file mode 100644 index 000000000..253ca3ac9 --- /dev/null +++ b/tests/skills/cases/meta-remove/eds-table.json @@ -0,0 +1,28 @@ +{ + "name": "Таблица внешнего источника: файл удалён, имя убрано из ChildObjects источника", + "preRun": [ + { + "script": "meta-compile/scripts/meta-compile", + "input": { + "type": "ExternalDataSource", + "name": "PG", + "tables": { + "products": { "keyFields": ["id"], "fields": ["id: Number(10,0)"] }, + "prices": ["product_id: Number(10,0)", "price: Number(15,2)"] + } + }, + "args": { "-JsonPath": "{inputFile}", "-OutputDir": "{workDir}" } + } + ], + "object": "ExternalDataSource.PG.Table.prices", + "expect": { + "files": ["ExternalDataSources/PG.xml", "ExternalDataSources/PG/Tables/products.xml"], + "filesAbsent": ["ExternalDataSources/PG/Tables/prices.xml"], + "fileContains": [ + { "file": "ExternalDataSources/PG.xml", "text": "products
" } + ], + "fileNotContains": [ + { "file": "ExternalDataSources/PG.xml", "text": "prices
" } + ] + } +} diff --git a/tests/skills/cases/meta-remove/snapshots/eds-table/Configuration.xml b/tests/skills/cases/meta-remove/snapshots/eds-table/Configuration.xml new file mode 100644 index 000000000..da0e784ee --- /dev/null +++ b/tests/skills/cases/meta-remove/snapshots/eds-table/Configuration.xml @@ -0,0 +1,252 @@ + + + + + + UUID-002 + UUID-003 + + + UUID-004 + UUID-005 + + + UUID-006 + UUID-007 + + + UUID-008 + UUID-009 + + + UUID-010 + UUID-011 + + + UUID-012 + UUID-013 + + + UUID-014 + UUID-015 + + + + TestConfig + + + ru + TestConfig + + + + + Version8_3_24 + ManagedApplication + + PlatformApplication + + Russian + + + + + false + false + false + + + + + + + + + + + + + + + + + + + + + + Biometrics + true + + + Location + false + + + BackgroundLocation + false + + + BluetoothPrinters + false + + + WiFiPrinters + false + + + Contacts + false + + + Calendars + false + + + PushNotifications + false + + + LocalNotifications + false + + + InAppPurchases + false + + + PersonalComputerFileExchange + false + + + Ads + false + + + NumberDialing + false + + + CallProcessing + false + + + CallLog + false + + + AutoSendSMS + false + + + ReceiveSMS + false + + + SMSLog + false + + + Camera + false + + + Microphone + false + + + MusicLibrary + false + + + PictureAndVideoLibraries + false + + + AudioPlaybackAndVibration + false + + + BackgroundAudioPlaybackAndVibration + false + + + InstallPackages + false + + + OSBackup + true + + + ApplicationUsageStatistics + false + + + BarcodeScanning + false + + + BackgroundAudioRecording + false + + + AllFilesAccess + false + + + Videoconferences + false + + + NFC + false + + + DocumentScanning + false + + + SpeechToText + false + + + Geofences + false + + + IncomingShareRequests + false + + + AllIncomingShareRequestsTypesProcessing + false + + + + + + Normal + + + Language.Русский + + + + + + Managed + NotAutoFree + DontUse + DontUse + TaxiEnableVersion8_2 + DontUse + Version8_3_24 + + + + Русский + PG + + + \ No newline at end of file diff --git a/tests/skills/cases/meta-remove/snapshots/eds-table/Ext/ClientApplicationInterface.xml b/tests/skills/cases/meta-remove/snapshots/eds-table/Ext/ClientApplicationInterface.xml new file mode 100644 index 000000000..3c1161b2d --- /dev/null +++ b/tests/skills/cases/meta-remove/snapshots/eds-table/Ext/ClientApplicationInterface.xml @@ -0,0 +1,18 @@ + + + + + UUID-002 + + + + + UUID-004 + + + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/meta-remove/snapshots/eds-table/ExternalDataSources/PG.xml b/tests/skills/cases/meta-remove/snapshots/eds-table/ExternalDataSources/PG.xml new file mode 100644 index 000000000..a1b89619b --- /dev/null +++ b/tests/skills/cases/meta-remove/snapshots/eds-table/ExternalDataSources/PG.xml @@ -0,0 +1,33 @@ + + + + + + UUID-002 + UUID-003 + + + UUID-004 + UUID-005 + + + UUID-006 + UUID-007 + + + + PG + + + ru + PG + + + + Automatic + + + products
+
+
+
\ No newline at end of file diff --git a/tests/skills/cases/meta-remove/snapshots/eds-table/ExternalDataSources/PG/Tables/products.xml b/tests/skills/cases/meta-remove/snapshots/eds-table/ExternalDataSources/PG/Tables/products.xml new file mode 100644 index 000000000..205ef9363 --- /dev/null +++ b/tests/skills/cases/meta-remove/snapshots/eds-table/ExternalDataSources/PG/Tables/products.xml @@ -0,0 +1,130 @@ + + + + + + UUID-002 + UUID-003 + + + UUID-004 + UUID-005 + + + UUID-006 + UUID-007 + + + UUID-008 + UUID-009 + + + UUID-010 + UUID-011 + + + UUID-012 + UUID-013 + + + UUID-014 + UUID-015 + + + UUID-016 + UUID-017 + + + + products + + + ru + products + + + + Table + products + + NonobjectData + + ExternalDataSource.PG.Table.products.Field.id + + + + + + true + false + + Auto + Begin + Directly + Auto + + + + + + + + + + + + false + false + Auto + + InDialog + + + Automatic + + + + + id + + + ru + id + + + + + xs:decimal + + 10 + 0 + Any + + + false + + + + false + + false + false + + + false + 0 + DontCheck + + + Auto + Auto + Auto + + id + false + false + + + +
+
\ No newline at end of file diff --git a/tests/skills/cases/meta-remove/snapshots/eds-table/Languages/Русский.xml b/tests/skills/cases/meta-remove/snapshots/eds-table/Languages/Русский.xml new file mode 100644 index 000000000..37c60d786 --- /dev/null +++ b/tests/skills/cases/meta-remove/snapshots/eds-table/Languages/Русский.xml @@ -0,0 +1,16 @@ + + + + + Русский + + + ru + Русский + + + + ru + + + \ No newline at end of file