mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-09-03 08:40:52 +03:00
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:
co-authored by
Claude Opus 5
parent
c687860535
commit
50f8b497b6
@@ -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 ---
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user