feat(meta-info): блок стандартных реквизитов

Ишью #58: сводка не показывала владельца подчинённого справочника, и код,
написанный по ней, падал на Записать(). Владелец оказался частным случаем:
шапка «Код(9) | Наименование(100)» смешивала свойства объекта с полями и не
давала по полям ни типа, ни обязательности. По acc_8.3.24 из 690 справочников
23 имеют числовой код, 122 — фиксированной длины, 426 — вовсе без кода; всё
это было невидимо. У ПВХ, планов счетов и обмена, бизнес-процессов и задач
шапки не было вообще — включая ТипЗначения у ПВХ.

Поля вынесены в блок «Стандартные реквизиты» на языке блока «Реквизиты»
(имя — тип — [флаги]), в шапке остались свойства объекта. В overview
показываются те, что нельзя вывести из остального вывода: Владелец, Код,
Наименование, Дата, Номер, ТипЗначения. Ссылка, ПометкаУдаления, Родитель,
ЭтоГруппа следуют из типа объекта и строки «Иерархический» — они в full.

Обязательность берётся из StandardAttributes/FillChecking. Блок в XML
опционален (meta-dsl-spec §7.1.1), поэтому при его отсутствии действует
профиль платформенных дефолтов, синхронный с meta-compile — иначе объекты,
созданные meta-compile, теряли бы флаг, то есть ровно кейс из ишью.

Составной ТипЗначения ПВХ доходит до 151 типа и сворачивается в счётчик
«Составной (110)»; полный состав даёт drill-down -Name ТипЗначения, о чём
сообщает единственная строка в конце вывода. brief получил строку
«Подчинён: …» — факт структуры без обещаний про обязательность, которых
brief выполнить не может.

Проверено сравнением с эталонным прогоном по 4768 объектам acc/erp/ut/unf:
все расхождения классифицированы, необъяснённых нет, ненулевых кодов возврата
нет. Владелец появился у 242 объектов, числовой код — у 56.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-08-04 11:57:32 +03:00
co-authored by Claude Opus 5
parent c687860535
commit 50f8b497b6
42 changed files with 2960 additions and 40 deletions
+277 -19
View File
@@ -1,4 +1,4 @@
# meta-info v1.6 — Compact summary of 1C metadata object
# meta-info v1.7 — Compact summary of 1C metadata object
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory=$true)][Alias('Path')][string]$ObjectPath,
@@ -296,6 +296,202 @@ function Format-Flags($propsNode, [bool]$isDimension = $false) {
return " [$($flags -join ', ')]"
}
# Ссылка на объект метаданных (`Catalog.Валюты` в Owners и подобных) — не тип значения,
# но в выводе показываем именно тип, который присваивается: СправочникСсылка.Валюты.
function Format-MDObjectRef([string]$raw) {
if ($raw -match '^(\w+)\.(.+)$') {
$key = "$($Matches[1])Ref"
if ($refTypeMap.ContainsKey($key)) { return "$($refTypeMap[$key]).$($Matches[2])" }
}
return $raw
}
# --- Стандартные реквизиты ---
# Сколько типов состава печатать списком, прежде чем свернуть в счётчик. По корпусу
# acc/erp/ut/unf состав ПВХ доходит до 151 типа, при этом 20 из 42 ПВХ укладываются в 5.
$script:composedTypeThreshold = 5
# Имена полей, состав которых свернули в счётчик, — по ним в конце вывода печатается
# единственная подсказка, чем состав развернуть.
$script:collapsedNames = @()
# Блок StandardAttributes в XML опционален: платформа материализует его только когда хотя бы
# один стандартный реквизит кастомизирован (docs/meta-dsl-spec.md §7.1.1). Когда блока нет,
# действуют платформенные дефолты — профиль ниже совпадает с $stdAttrProfile в meta-compile
# (выведен из корпуса acc+erp). Правки держать синхронными, иначе навыки разъедутся молча.
$stdAttrRequiredDefault = @{
"Catalog" = @{ "Owner" = $true; "Description" = $true }
"Document" = @{ "Date" = $true }
"ExchangePlan" = @{ "Description" = $true; "Code" = $true }
"ChartOfAccounts" = @{ "Description" = $true; "Code" = $true }
"ChartOfCharacteristicTypes" = @{ "Description" = $true }
"ChartOfCalculationTypes" = @{ "Description" = $true }
}
function Test-StdAttrRequired($propsNode, [string]$attrName, [string]$objType) {
$fc = $propsNode.SelectSingleNode("md:StandardAttributes/xr:StandardAttribute[@name='$attrName']/xr:FillChecking", $ns)
if ($fc) { return ($fc.InnerText -eq "ShowError") }
if ($stdAttrRequiredDefault.ContainsKey($objType) -and $stdAttrRequiredDefault[$objType].ContainsKey($attrName)) {
return $stdAttrRequiredDefault[$objType][$attrName]
}
return $false
}
function New-StdAttr([string]$name, [string]$type, $flags) {
$f = @($flags | Where-Object { $_ })
$flagStr = if ($f.Count -gt 0) { " [$($f -join ', ')]" } else { "" }
return @{ Name = $name; Type = $type; Flags = $flagStr }
}
# Стандартные реквизиты, наличие и характеристики которых задаются настройками объекта, —
# их нельзя вывести из остального вывода, поэтому они попадают и в overview. Ссылка,
# ПометкаУдаления, Родитель и прочее следуют из типа объекта и строки «Иерархический» —
# они только в full (см. Get-StandardAttributesFull).
function Get-StandardAttributes($propsNode, [string]$objType, [string]$mode) {
$result = @()
# Владелец — только у подчинённого справочника; состав типов ниоткуда не выводится
if ($objType -eq "Catalog") {
$ownersNode = $propsNode.SelectSingleNode("md:Owners", $ns)
$ownerTypes = @()
if ($ownersNode) {
foreach ($it in $ownersNode.SelectNodes("xr:Item", $ns)) {
$ownerTypes += Format-MDObjectRef $it.InnerText
}
}
if ($ownerTypes.Count -gt 0) {
$req = if (Test-StdAttrRequired $propsNode "Owner" $objType) { "обязательный" } else { $null }
$result += New-StdAttr "Владелец" ($ownerTypes -join ", ") @($req)
}
}
# ТипЗначения ПВХ — до полутора сотен типов, полный список раздул бы сводку в обоих
# режимах, поэтому сворачиваем в счётчик. Состав смотреть через -Name ТипЗначения.
if ($objType -eq "ChartOfCharacteristicTypes") {
$vt = $propsNode.SelectSingleNode("md:Type", $ns)
if ($vt) {
$cnt = $vt.SelectNodes("v8:Type", $ns).Count + $vt.SelectNodes("v8:TypeSet", $ns).Count
$typeStr = if ($cnt -gt $script:composedTypeThreshold) {
$script:collapsedNames += "ТипЗначения"
"Составной ($cnt)"
} else { Format-Type $vt }
if ($typeStr) { $result += New-StdAttr "ТипЗначения" $typeStr @() }
}
}
# Дата — есть всегда, ценна флагом обязательности
if ($objType -in @("Document", "BusinessProcess", "Task")) {
$req = if (Test-StdAttrRequired $propsNode "Date" $objType) { "обязательный" } else { $null }
$result += New-StdAttr "Дата" "Дата" @($req)
}
# Номер — тип и длина задаются объектом, при NumberLength=0 номера нет
if ($objType -in @("Document", "BusinessProcess", "Task")) {
$numLen = $propsNode.SelectSingleNode("md:NumberLength", $ns)
if ($numLen -and [int]$numLen.InnerText -gt 0) {
$numType = $propsNode.SelectSingleNode("md:NumberType", $ns)
$ntRu = if ($numType -and $numType.InnerText -eq "Number") { "Число" } else { "Строка" }
$flags = @()
if (Test-StdAttrRequired $propsNode "Number" $objType) { $flags += "обязательный" }
$numPer = $propsNode.SelectSingleNode("md:NumberPeriodicity", $ns)
if ($numPer -and $numberPeriodMap.ContainsKey($numPer.InnerText)) { $flags += $numberPeriodMap[$numPer.InnerText] }
$numAllowed = $propsNode.SelectSingleNode("md:NumberAllowedLength", $ns)
if ($numAllowed -and $numAllowed.InnerText -eq "Fixed") { $flags += "фикс. длина" }
$autoNum = $propsNode.SelectSingleNode("md:Autonumbering", $ns)
if ($autoNum -and $autoNum.InnerText -eq "true") { $flags += "авто" }
$result += New-StdAttr "Номер" "$ntRu($($numLen.InnerText))" $flags
}
}
# Код — при CodeLength=0 кода у объекта нет вовсе, строку не печатаем
$codeLen = $propsNode.SelectSingleNode("md:CodeLength", $ns)
if ($codeLen -and [int]$codeLen.InnerText -gt 0) {
$codeType = $propsNode.SelectSingleNode("md:CodeType", $ns)
$ctRu = if ($codeType -and $codeType.InnerText -eq "Number") { "Число" } else { "Строка" }
$flags = @()
if (Test-StdAttrRequired $propsNode "Code" $objType) { $flags += "обязательный" }
$codeAllowed = $propsNode.SelectSingleNode("md:CodeAllowedLength", $ns)
if ($codeAllowed -and $codeAllowed.InnerText -eq "Fixed") { $flags += "фикс. длина" }
$result += New-StdAttr "Код" "$ctRu($($codeLen.InnerText))" $flags
}
# Наименование — при DescriptionLength=0 наименования нет
$descLen = $propsNode.SelectSingleNode("md:DescriptionLength", $ns)
if ($descLen -and [int]$descLen.InnerText -gt 0) {
$req = if (Test-StdAttrRequired $propsNode "Description" $objType) { "обязательный" } else { $null }
$result += New-StdAttr "Наименование" "Строка($($descLen.InnerText))" @($req)
}
return $result
}
# Остальные стандартные реквизиты — предсказуемы по типу объекта, но в full полезны
# как перечень доступных полей для запроса.
function Get-StandardAttributesFull($propsNode, [string]$objType, [string]$objName) {
$selfRef = switch ($objType) {
"Catalog" { "СправочникСсылка.$objName" }
"ChartOfCharacteristicTypes" { "ПВХСсылка.$objName" }
"ChartOfAccounts" { "ПланСчетовСсылка.$objName" }
"ChartOfCalculationTypes" { "ПВРСсылка.$objName" }
"ExchangePlan" { "ПланОбменаСсылка.$objName" }
"Document" { "ДокументСсылка.$objName" }
"BusinessProcess" { "БизнесПроцессСсылка.$objName" }
"Task" { "ЗадачаСсылка.$objName" }
default { "" }
}
$result = @()
if ($selfRef) { $result += New-StdAttr "Ссылка" $selfRef @() }
$result += New-StdAttr "ПометкаУдаления" "Булево" @()
# Родитель и ЭтоГруппа существуют только у иерархических объектов
$hier = $propsNode.SelectSingleNode("md:Hierarchical", $ns)
$isHier = ($hier -and $hier.InnerText -eq "true") -or $objType -eq "ChartOfAccounts"
if ($isHier -and $selfRef) {
$result += New-StdAttr "Родитель" $selfRef @()
$ht = $propsNode.SelectSingleNode("md:HierarchyType", $ns)
if ($objType -eq "Catalog" -and (-not $ht -or $ht.InnerText -eq "HierarchyFoldersAndItems")) {
$result += New-StdAttr "ЭтоГруппа" "Булево" @()
}
}
switch ($objType) {
"Document" {
$result += New-StdAttr "Проведен" "Булево" @()
}
"ExchangePlan" {
$result += New-StdAttr "ЭтотУзел" "Булево" @()
$result += New-StdAttr "НомерОтправленного" "Число" @()
$result += New-StdAttr "НомерПринятого" "Число" @()
}
"BusinessProcess" {
$result += New-StdAttr "Стартован" "Булево" @()
$result += New-StdAttr "Завершен" "Булево" @()
$result += New-StdAttr "ВедущаяЗадача" "ЗадачаСсылка" @()
}
"Task" {
$result += New-StdAttr "Выполнена" "Булево" @()
$result += New-StdAttr "БизнесПроцесс" "БизнесПроцессСсылка" @()
$result += New-StdAttr "ТочкаМаршрута" "БизнесПроцессТочкаМаршрутаСсылка" @()
}
"ChartOfAccounts" {
$result += New-StdAttr "Вид" "ВидСчета" @()
$result += New-StdAttr "Забалансовый" "Булево" @()
$result += New-StdAttr "Порядок" "Число" @()
}
"ChartOfCalculationTypes" {
$result += New-StdAttr "ПериодДействияБазовый" "Булево" @()
}
}
# Предопределённые данные есть у всех перечисленных типов, кроме документов и задач
if ($objType -in @("Catalog", "ChartOfCharacteristicTypes", "ChartOfAccounts", "ChartOfCalculationTypes")) {
$result += New-StdAttr "Предопределенный" "Булево" @()
$result += New-StdAttr "ИмяПредопределенныхДанных" "Строка" @()
}
return $result
}
function Get-Attributes($parentNode, [string]$childTag = "Attribute", [bool]$isDimension = $false) {
$result = @()
foreach ($attr in $parentNode.SelectNodes("md:$childTag", $ns)) {
@@ -662,6 +858,43 @@ if ($Name -and $childObjs) {
}
}
# Не нашли среди дочерних объектов — пробуем стандартные реквизиты ниже
}
# Drill-down по стандартному реквизиту — единственный способ увидеть состав типов целиком,
# когда в сводке он свёрнут в счётчик. Живёт в Properties, а не в ChildObjects, поэтому идёт
# отдельной веткой и работает даже у объектов без ChildObjects.
if ($Name -and -not $drillDone) {
$stdAll = @(Get-StandardAttributes $props $mdType "full") + @(Get-StandardAttributesFull $props $mdType $objName)
foreach ($s in $stdAll) {
if ($s.Name -ne $Name) { continue }
Out "Стандартный реквизит: $($s.Name)"
# Состав типов разворачиваем списком: ради этого drill-down и нужен
$typeSrc = $null
if ($Name -eq "ТипЗначения") { $typeSrc = $props.SelectSingleNode("md:Type", $ns) }
$typeList = @()
if ($typeSrc) {
foreach ($t in $typeSrc.SelectNodes("v8:Type", $ns)) { $typeList += Format-SingleType $t.InnerText $typeSrc }
foreach ($t in $typeSrc.SelectNodes("v8:TypeSet", $ns)) { $typeList += Format-SingleTypeSet $t.InnerText }
} elseif ($Name -eq "Владелец") {
$ownersNode = $props.SelectSingleNode("md:Owners", $ns)
if ($ownersNode) {
foreach ($it in $ownersNode.SelectNodes("xr:Item", $ns)) { $typeList += Format-MDObjectRef $it.InnerText }
}
}
if ($typeList.Count -gt 0) {
Out " Типы ($($typeList.Count)):"
foreach ($t in $typeList) { Out " $t" }
} else {
Out " Тип: $($s.Type)"
}
$flagText = $s.Flags.Trim()
if ($flagText) { Out " Свойства: $($flagText.Trim('[', ']'))" }
$drillDone = $true
break
}
if (-not $drillDone) {
Write-Host "[ERROR] '$Name' not found in $objName"
exit 1
@@ -692,6 +925,20 @@ if (-not $drillDone) {
# --- Mode: brief ---
if ($Mode -eq "brief") {
# Подчинённость — единственный факт структуры, который из brief не выводится никак,
# а без него код записи элемента падает. Про обязательность здесь намеренно молчим:
# обязательными бывают и обычные реквизиты, а их brief не разбирает.
if ($mdType -eq "Catalog") {
$ownersNode = $props.SelectSingleNode("md:Owners", $ns)
$ownerNames = @()
if ($ownersNode) {
foreach ($it in $ownersNode.SelectNodes("xr:Item", $ns)) {
$ownerNames += ($it.InnerText -replace '^\w+\.', '')
}
}
if ($ownerNames.Count -gt 0) { Out "Подчинён: $($ownerNames -join ', ')" }
}
# Attributes
$attrs = @()
if ($childObjs) { $attrs = @(Get-Attributes $childObjs) }
@@ -840,23 +1087,10 @@ if (-not $drillDone) {
# Document-specific header properties
if ($mdType -eq "Document") {
$numType = $props.SelectSingleNode("md:NumberType", $ns)
$numLen = $props.SelectSingleNode("md:NumberLength", $ns)
$numPer = $props.SelectSingleNode("md:NumberPeriodicity", $ns)
$autoNum = $props.SelectSingleNode("md:Autonumbering", $ns)
$posting = $props.SelectSingleNode("md:Posting", $ns)
$parts = @()
if ($numType -and $numLen) {
$nt = if ($numType.InnerText -eq "String") { "Строка" } else { "Число" }
$piece = "Номер: $nt($($numLen.InnerText))"
if ($numPer) {
$perRu = if ($numberPeriodMap.ContainsKey($numPer.InnerText)) { $numberPeriodMap[$numPer.InnerText] } else { $numPer.InnerText }
$piece += ", $perRu"
}
if ($autoNum -and $autoNum.InnerText -eq "true") { $piece += ", авто" }
$parts += $piece
}
# Номер уехал в блок стандартных реквизитов — здесь остаются свойства объекта
if ($posting) {
$parts += "Проведение: $(if ($posting.InnerText -eq 'Allow') { 'да' } else { 'нет' })"
}
@@ -879,10 +1113,17 @@ if (-not $drillDone) {
}
$parts += "Иерархический: $htText"
}
$codeLen = $props.SelectSingleNode("md:CodeLength", $ns)
$descLen = $props.SelectSingleNode("md:DescriptionLength", $ns)
if ($codeLen -and [int]$codeLen.InnerText -gt 0) { $parts += "Код($($codeLen.InnerText))" }
if ($descLen -and [int]$descLen.InnerText -gt 0) { $parts += "Наименование($($descLen.InnerText))" }
# Код и Наименование уехали в блок стандартных реквизитов — здесь только свойства объекта.
# Подчинение печатаем лишь когда оно отличается от дефолта ToItems.
$sub = $props.SelectSingleNode("md:SubordinationUse", $ns)
if ($sub -and $sub.InnerText -ne "ToItems") {
$subRu = switch ($sub.InnerText) {
"ToFolders" { "группам" }
"ToFoldersAndItems" { "группам и элементам" }
default { $sub.InnerText }
}
$parts += "Подчинение: $subRu"
}
if ($parts.Count -gt 0) { Out ($parts -join " | ") }
}
@@ -1071,6 +1312,16 @@ if (-not $drillDone) {
}
}
# --- Standard attributes ---
$stdAttrs = @(Get-StandardAttributes $props $mdType $Mode)
if ($Mode -eq "full") { $stdAttrs += @(Get-StandardAttributesFull $props $mdType $objName) }
if ($stdAttrs.Count -gt 0) {
Out ""
Out "Стандартные реквизиты:"
$ml = Get-MaxNameLen $stdAttrs
foreach ($s in $stdAttrs) { Out (Format-AttrLine $s $ml) }
}
# --- Dimensions (registers) ---
if ($mdType -match "Register$" -and $childObjs) {
$dims = @(Get-Attributes $childObjs "Dimension" $true)
@@ -1189,6 +1440,13 @@ if (-not $drillDone) {
}
}
}
# Единственная подсказка на весь вывод — чем развернуть свёрнутый в счётчик состав
if ($script:collapsedNames.Count -gt 0) {
$hints = ($script:collapsedNames | ForEach-Object { "-Name $_" }) -join ", "
Out ""
Out "Полный состав типов: $hints"
}
}
# --- Pagination and output ---
+260 -20
View File
@@ -1,4 +1,4 @@
# meta-info v1.6 — Compact summary of 1C metadata object (Python port)
# meta-info v1.7 — Compact summary of 1C metadata object (Python port)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import os
@@ -356,6 +356,190 @@ def format_flags(a_props, is_dimension=False):
return f" [{', '.join(flags)}]"
# Ссылка на объект метаданных (`Catalog.Валюты` в Owners и подобных) — не тип значения,
# но в выводе показываем именно тип, который присваивается: СправочникСсылка.Валюты.
def format_md_object_ref(raw):
m = re.match(r'^(\w+)\.(.+)$', raw)
if m:
key = f"{m.group(1)}Ref"
if key in ref_type_map:
return f"{ref_type_map[key]}.{m.group(2)}"
return raw
# ── Стандартные реквизиты ────────────────────────────────────
# Сколько типов состава печатать списком, прежде чем свернуть в счётчик. По корпусу
# acc/erp/ut/unf состав ПВХ доходит до 151 типа, при этом 20 из 42 ПВХ укладываются в 5.
COMPOSED_TYPE_THRESHOLD = 5
# Имена полей, состав которых свернули в счётчик, — по ним в конце вывода печатается
# единственная подсказка, чем состав развернуть.
collapsed_names = []
# Блок StandardAttributes в XML опционален: платформа материализует его только когда хотя бы
# один стандартный реквизит кастомизирован (docs/meta-dsl-spec.md §7.1.1). Когда блока нет,
# действуют платформенные дефолты — профиль ниже совпадает с $stdAttrProfile в meta-compile
# (выведен из корпуса acc+erp). Правки держать синхронными, иначе навыки разъедутся молча.
std_attr_required_default = {
"Catalog": {"Owner": True, "Description": True},
"Document": {"Date": True},
"ExchangePlan": {"Description": True, "Code": True},
"ChartOfAccounts": {"Description": True, "Code": True},
"ChartOfCharacteristicTypes": {"Description": True},
"ChartOfCalculationTypes": {"Description": True},
}
def test_std_attr_required(props_node, attr_name, obj_type):
fc = find(props_node, f"md:StandardAttributes/xr:StandardAttribute[@name='{attr_name}']/xr:FillChecking")
if fc is not None:
return inner_text(fc) == "ShowError"
return std_attr_required_default.get(obj_type, {}).get(attr_name, False)
def new_std_attr(name, type_str, flags):
f = [x for x in flags if x]
flag_str = f" [{', '.join(f)}]" if f else ""
return {"Name": name, "Type": type_str, "Flags": flag_str}
# Стандартные реквизиты, наличие и характеристики которых задаются настройками объекта, —
# их нельзя вывести из остального вывода, поэтому они попадают и в overview. Ссылка,
# ПометкаУдаления, Родитель и прочее следуют из типа объекта и строки «Иерархический» —
# они только в full (см. get_standard_attributes_full).
def get_standard_attributes(props_node, obj_type, mode_name):
result = []
# Владелец — только у подчинённого справочника; состав типов ниоткуда не выводится
if obj_type == "Catalog":
owners_node = find(props_node, "md:Owners")
owner_types = []
if owners_node is not None:
for it in find_all(owners_node, "xr:Item"):
owner_types.append(format_md_object_ref(inner_text(it)))
if owner_types:
req = "обязательный" if test_std_attr_required(props_node, "Owner", obj_type) else None
result.append(new_std_attr("Владелец", ", ".join(owner_types), [req]))
# ТипЗначения ПВХ — до полутора сотен типов, полный список раздул бы сводку в обоих
# режимах, поэтому сворачиваем в счётчик. Состав смотреть через -Name ТипЗначения.
if obj_type == "ChartOfCharacteristicTypes":
vt = find(props_node, "md:Type")
if vt is not None:
cnt = len(find_all(vt, "v8:Type")) + len(find_all(vt, "v8:TypeSet"))
if cnt > COMPOSED_TYPE_THRESHOLD:
type_str = f"Составной ({cnt})"
collapsed_names.append("ТипЗначения")
else:
type_str = format_type(vt)
if type_str:
result.append(new_std_attr("ТипЗначения", type_str, []))
# Дата — есть всегда, ценна флагом обязательности
if obj_type in ("Document", "BusinessProcess", "Task"):
req = "обязательный" if test_std_attr_required(props_node, "Date", obj_type) else None
result.append(new_std_attr("Дата", "Дата", [req]))
# Номер — тип и длина задаются объектом, при NumberLength=0 номера нет
if obj_type in ("Document", "BusinessProcess", "Task"):
num_len = find(props_node, "md:NumberLength")
if num_len is not None and inner_text(num_len).isdigit() and int(inner_text(num_len)) > 0:
num_type = find(props_node, "md:NumberType")
nt_ru = "Число" if num_type is not None and inner_text(num_type) == "Number" else "Строка"
flags = []
if test_std_attr_required(props_node, "Number", obj_type):
flags.append("обязательный")
num_per = find(props_node, "md:NumberPeriodicity")
if num_per is not None and inner_text(num_per) in number_period_map:
flags.append(number_period_map[inner_text(num_per)])
num_allowed = find(props_node, "md:NumberAllowedLength")
if num_allowed is not None and inner_text(num_allowed) == "Fixed":
flags.append("фикс. длина")
auto_num = find(props_node, "md:Autonumbering")
if auto_num is not None and inner_text(auto_num) == "true":
flags.append("авто")
result.append(new_std_attr("Номер", f"{nt_ru}({inner_text(num_len)})", flags))
# Код — при CodeLength=0 кода у объекта нет вовсе, строку не печатаем
code_len = find(props_node, "md:CodeLength")
if code_len is not None and inner_text(code_len).isdigit() and int(inner_text(code_len)) > 0:
code_type = find(props_node, "md:CodeType")
ct_ru = "Число" if code_type is not None and inner_text(code_type) == "Number" else "Строка"
flags = []
if test_std_attr_required(props_node, "Code", obj_type):
flags.append("обязательный")
code_allowed = find(props_node, "md:CodeAllowedLength")
if code_allowed is not None and inner_text(code_allowed) == "Fixed":
flags.append("фикс. длина")
result.append(new_std_attr("Код", f"{ct_ru}({inner_text(code_len)})", flags))
# Наименование — при DescriptionLength=0 наименования нет
desc_len = find(props_node, "md:DescriptionLength")
if desc_len is not None and inner_text(desc_len).isdigit() and int(inner_text(desc_len)) > 0:
req = "обязательный" if test_std_attr_required(props_node, "Description", obj_type) else None
result.append(new_std_attr("Наименование", f"Строка({inner_text(desc_len)})", [req]))
return result
# Остальные стандартные реквизиты — предсказуемы по типу объекта, но в full полезны
# как перечень доступных полей для запроса.
def get_standard_attributes_full(props_node, obj_type, obj_name):
self_ref_map = {
"Catalog": "СправочникСсылка",
"ChartOfCharacteristicTypes": "ПВХСсылка",
"ChartOfAccounts": "ПланСчетовСсылка",
"ChartOfCalculationTypes": "ПВРСсылка",
"ExchangePlan": "ПланОбменаСсылка",
"Document": "ДокументСсылка",
"BusinessProcess": "БизнесПроцессСсылка",
"Task": "ЗадачаСсылка",
}
self_ref = f"{self_ref_map[obj_type]}.{obj_name}" if obj_type in self_ref_map else ""
result = []
if self_ref:
result.append(new_std_attr("Ссылка", self_ref, []))
result.append(new_std_attr("ПометкаУдаления", "Булево", []))
# Родитель и ЭтоГруппа существуют только у иерархических объектов
hier = find(props_node, "md:Hierarchical")
is_hier = (hier is not None and inner_text(hier) == "true") or obj_type == "ChartOfAccounts"
if is_hier and self_ref:
result.append(new_std_attr("Родитель", self_ref, []))
ht = find(props_node, "md:HierarchyType")
if obj_type == "Catalog" and (ht is None or inner_text(ht) == "HierarchyFoldersAndItems"):
result.append(new_std_attr("ЭтоГруппа", "Булево", []))
if obj_type == "Document":
result.append(new_std_attr("Проведен", "Булево", []))
elif obj_type == "ExchangePlan":
result.append(new_std_attr("ЭтотУзел", "Булево", []))
result.append(new_std_attr("НомерОтправленного", "Число", []))
result.append(new_std_attr("НомерПринятого", "Число", []))
elif obj_type == "BusinessProcess":
result.append(new_std_attr("Стартован", "Булево", []))
result.append(new_std_attr("Завершен", "Булево", []))
result.append(new_std_attr("ВедущаяЗадача", "ЗадачаСсылка", []))
elif obj_type == "Task":
result.append(new_std_attr("Выполнена", "Булево", []))
result.append(new_std_attr("БизнесПроцесс", "БизнесПроцессСсылка", []))
result.append(new_std_attr("ТочкаМаршрута", "БизнесПроцессТочкаМаршрутаСсылка", []))
elif obj_type == "ChartOfAccounts":
result.append(new_std_attr("Вид", "ВидСчета", []))
result.append(new_std_attr("Забалансовый", "Булево", []))
result.append(new_std_attr("Порядок", "Число", []))
elif obj_type == "ChartOfCalculationTypes":
result.append(new_std_attr("ПериодДействияБазовый", "Булево", []))
# Предопределённые данные есть у всех перечисленных типов, кроме документов и задач
if obj_type in ("Catalog", "ChartOfCharacteristicTypes", "ChartOfAccounts", "ChartOfCalculationTypes"):
result.append(new_std_attr("Предопределенный", "Булево", []))
result.append(new_std_attr("ИмяПредопределенныхДанных", "Строка", []))
return result
def get_attributes(parent_node, child_tag="Attribute", is_dimension=False):
result = []
for attr in find_all(parent_node, f"md:{child_tag}"):
@@ -723,6 +907,44 @@ if drill_name and child_objs is not None:
drill_done = True
break
# Не нашли среди дочерних объектов — пробуем стандартные реквизиты ниже
# Drill-down по стандартному реквизиту — единственный способ увидеть состав типов целиком,
# когда в сводке он свёрнут в счётчик. Живёт в Properties, а не в ChildObjects, поэтому идёт
# отдельной веткой и работает даже у объектов без ChildObjects.
if drill_name and not drill_done:
std_all = get_standard_attributes(props, md_type, "full") + get_standard_attributes_full(props, md_type, obj_name)
for s in std_all:
if s["Name"] != drill_name:
continue
out(f"Стандартный реквизит: {s['Name']}")
# Состав типов разворачиваем списком: ради этого drill-down и нужен
type_list = []
type_src = find(props, "md:Type") if drill_name == "ТипЗначения" else None
if type_src is not None:
for t in find_all(type_src, "v8:Type"):
type_list.append(format_single_type(inner_text(t), type_src))
for t in find_all(type_src, "v8:TypeSet"):
type_list.append(format_single_type_set(inner_text(t)))
elif drill_name == "Владелец":
owners_node = find(props, "md:Owners")
if owners_node is not None:
for it in find_all(owners_node, "xr:Item"):
type_list.append(format_md_object_ref(inner_text(it)))
if type_list:
out(f" Типы ({len(type_list)}):")
for t in type_list:
out(f" {t}")
else:
out(f" Тип: {s['Type']}")
flag_text = s["Flags"].strip()
if flag_text:
out(f" Свойства: {flag_text.strip('[]')}")
drill_done = True
break
if not drill_done:
print(f"[ERROR] '{drill_name}' not found in {obj_name}")
sys.exit(1)
@@ -754,6 +976,18 @@ if not drill_done:
out(f"Расширенное представление списка: {ext_list_presentation}")
if mode == "brief":
# Подчинённость — единственный факт структуры, который из brief не выводится никак,
# а без него код записи элемента падает. Про обязательность здесь намеренно молчим:
# обязательными бывают и обычные реквизиты, а их brief не разбирает.
if md_type == "Catalog":
owners_node = find(props, "md:Owners")
owner_names = []
if owners_node is not None:
for it in find_all(owners_node, "xr:Item"):
owner_names.append(re.sub(r'^\w+\.', '', inner_text(it)))
if owner_names:
out(f"Подчинён: {', '.join(owner_names)}")
# Attributes
attrs = get_attributes(child_objs) if child_objs is not None else []
if attrs:
@@ -883,21 +1117,9 @@ if not drill_done:
# Document-specific header
if md_type == "Document":
num_type = find(props, "md:NumberType")
num_len = find(props, "md:NumberLength")
num_per = find(props, "md:NumberPeriodicity")
auto_num = find(props, "md:Autonumbering")
posting = find(props, "md:Posting")
parts = []
if num_type is not None and num_len is not None:
nt = "Строка" if inner_text(num_type) == "String" else "Число"
piece = f"Номер: {nt}({inner_text(num_len)})"
if num_per is not None:
per_ru = number_period_map.get(inner_text(num_per), inner_text(num_per))
piece += f", {per_ru}"
if auto_num is not None and inner_text(auto_num) == "true":
piece += ", авто"
parts.append(piece)
# Номер уехал в блок стандартных реквизитов — здесь остаются свойства объекта
if posting is not None:
parts.append(f"Проведение: {'да' if inner_text(posting) == 'Allow' else 'нет'}")
if parts:
@@ -917,12 +1139,13 @@ if not drill_done:
else:
ht_text += ", без ограничения уровней"
parts.append(f"Иерархический: {ht_text}")
code_len = find(props, "md:CodeLength")
desc_len = find(props, "md:DescriptionLength")
if code_len is not None and inner_text(code_len).isdigit() and int(inner_text(code_len)) > 0:
parts.append(f"Код({inner_text(code_len)})")
if desc_len is not None and inner_text(desc_len).isdigit() and int(inner_text(desc_len)) > 0:
parts.append(f"Наименование({inner_text(desc_len)})")
# Код и Наименование уехали в блок стандартных реквизитов — здесь только свойства объекта.
# Подчинение печатаем лишь когда оно отличается от дефолта ToItems.
sub = find(props, "md:SubordinationUse")
if sub is not None and inner_text(sub) != "ToItems":
sub_ru = {"ToFolders": "группам", "ToFoldersAndItems": "группам и элементам"}.get(
inner_text(sub), inner_text(sub))
parts.append(f"Подчинение: {sub_ru}")
if parts:
out(" | ".join(parts))
@@ -1085,6 +1308,17 @@ if not drill_done:
syn_text = f'"{v["Synonym"]}"' if v["Synonym"] and v["Synonym"] != v["Name"] else ""
out(f" {padded} {syn_text}")
# Standard attributes
std_attrs = get_standard_attributes(props, md_type, mode)
if mode == "full":
std_attrs += get_standard_attributes_full(props, md_type, obj_name)
if std_attrs:
out("")
out("Стандартные реквизиты:")
ml = get_max_name_len(std_attrs)
for s in std_attrs:
out(format_attr_line(s, ml))
# Dimensions (registers)
if md_type.endswith("Register") and child_objs is not None:
dims = get_attributes(child_objs, "Dimension", True)
@@ -1192,6 +1426,12 @@ if not drill_done:
if commands:
out(f"Команды: {', '.join(commands)}")
# Единственная подсказка на весь вывод — чем развернуть свёрнутый в счётчик состав
if collapsed_names:
hints = ", ".join(f"-Name {n}" for n in collapsed_names)
out("")
out(f"Полный состав типов: {hints}")
# ── Pagination and output ────────────────────────────────────
total_lines = len(lines)