mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-09-18 15:45:52 +03:00
feat(role-compile): порядок прав и узлов как у платформы
Платформа нормализует и то и другое: права внутри <object> идут в фиксированном для типа порядке, сами <object> — по uuid объекта метаданных. Навык писал в порядке ввода, поэтому первая же выгрузка из Конфигуратора давала диф, которого никто не делал. Замеры (debug/role-edit/FINDINGS.md): канон прав снят с платформы и сошёлся с топологической сортировкой по корпусу (~290k узлов <right>); порядок узлов по uuid подтверждён на 363 ролях ACC из 363 и отдельно на 44 ролях с вложенными объектами, где ключ — uuid самого реквизита. Порядок дерева конфигурации тут ни при чём — прямое сравнение с ChildObjects даёт скачущие позиции. Проверка: выход обоих портов совпал с выгрузкой 1С байт в байт. Сортировка узлов строго ordinal: Sort-Object сравнивает по культуре и игнорирует дефис, из-за чего порядок разошёлся бы и с платформой, и с py-портом. Объект, которого нет в выгрузке (uuid неизвестен), уходит в конец с предупреждением в stderr — платформа переставит его сама. Кейсы с несколькими объектами переведены с preRun на фикстуры: uuid, который meta-compile генерирует заново на каждом прогоне, сделал бы порядок узлов плавающим. Фикстуры собраны теми же навыками. Двум фикстурам добавлен документ-регистратор: регистр расчёта без него платформа не принимает, и verify-snapshots это поймал. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FGkXwoXTuafcu1SXMsauFq
This commit is contained in:
co-authored by
Claude Opus 5
parent
c23cae39d8
commit
bbc3be55a5
@@ -1,4 +1,4 @@
|
||||
# role-compile v1.37 — Compile 1C role from JSON
|
||||
# role-compile v1.38 — Compile 1C role from JSON
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
[CmdletBinding(PositionalBinding=$false)]
|
||||
param(
|
||||
@@ -620,6 +620,219 @@ $script:presets = @{
|
||||
}
|
||||
}
|
||||
|
||||
# --- 4a. Канонический порядок прав и узлов (замерено на платформе) ---
|
||||
# Платформа нормализует порядок <right> внутри <object> и порядок самих <object>:
|
||||
# права идут в фиксированном для типа порядке, узлы — по uuid объекта метаданных.
|
||||
# Пишем сразу так же, иначе первая же выгрузка из Конфигуратора даст диф на ровном месте.
|
||||
$script:rightOrder = @{
|
||||
"AccountingRegister" = @("Read","Update","View","Edit","TotalsControl")
|
||||
"AccumulationRegister" = @("Read","Update","View","Edit","TotalsControl")
|
||||
"BusinessProcess" = @(
|
||||
"Read","Insert","Update","Delete",
|
||||
"View","InteractiveInsert","Edit","InteractiveDelete",
|
||||
"InteractiveSetDeletionMark","InteractiveClearDeletionMark","InteractiveDeleteMarked","InputByString",
|
||||
"InteractiveActivate","Start","InteractiveStart","ReadDataHistory",
|
||||
"ReadDataHistoryOfMissingData","UpdateDataHistory","UpdateDataHistoryOfMissingData","UpdateDataHistorySettings",
|
||||
"UpdateDataHistoryVersionComment","ViewDataHistory","EditDataHistoryVersionComment","SwitchToDataHistoryVersion"
|
||||
)
|
||||
"CalculationRegister" = @("Read","Update","View","Edit")
|
||||
"Catalog" = @(
|
||||
"Read","Insert","Update","Delete",
|
||||
"View","InteractiveInsert","Edit","InteractiveDelete",
|
||||
"InteractiveSetDeletionMark","InteractiveClearDeletionMark","InteractiveDeleteMarked","InputByString",
|
||||
"InteractiveDeletePredefinedData","InteractiveSetDeletionMarkPredefinedData","InteractiveClearDeletionMarkPredefinedData","InteractiveDeleteMarkedPredefinedData",
|
||||
"ReadDataHistory","ReadDataHistoryOfMissingData","UpdateDataHistory","UpdateDataHistoryOfMissingData",
|
||||
"UpdateDataHistorySettings","UpdateDataHistoryVersionComment","ViewDataHistory","EditDataHistoryVersionComment",
|
||||
"SwitchToDataHistoryVersion"
|
||||
)
|
||||
"ChartOfAccounts" = @(
|
||||
"Read","Insert","Update","Delete",
|
||||
"View","InteractiveInsert","Edit","InteractiveDelete",
|
||||
"InteractiveSetDeletionMark","InteractiveClearDeletionMark","InteractiveDeleteMarked","InputByString",
|
||||
"InteractiveDeletePredefinedData","InteractiveSetDeletionMarkPredefinedData","InteractiveClearDeletionMarkPredefinedData","InteractiveDeleteMarkedPredefinedData",
|
||||
"ReadDataHistory","ReadDataHistoryOfMissingData","UpdateDataHistory","UpdateDataHistoryOfMissingData",
|
||||
"UpdateDataHistorySettings","UpdateDataHistoryVersionComment","ViewDataHistory","EditDataHistoryVersionComment",
|
||||
"SwitchToDataHistoryVersion"
|
||||
)
|
||||
"ChartOfCalculationTypes" = @(
|
||||
"Read","Insert","Update","Delete",
|
||||
"View","InteractiveInsert","Edit","InteractiveDelete",
|
||||
"InteractiveSetDeletionMark","InteractiveClearDeletionMark","InteractiveDeleteMarked","InputByString",
|
||||
"InteractiveDeletePredefinedData","InteractiveSetDeletionMarkPredefinedData","InteractiveClearDeletionMarkPredefinedData","InteractiveDeleteMarkedPredefinedData",
|
||||
"ReadDataHistory","ReadDataHistoryOfMissingData","UpdateDataHistory","UpdateDataHistoryOfMissingData",
|
||||
"UpdateDataHistorySettings","UpdateDataHistoryVersionComment","ViewDataHistory","EditDataHistoryVersionComment",
|
||||
"SwitchToDataHistoryVersion"
|
||||
)
|
||||
"ChartOfCharacteristicTypes" = @(
|
||||
"Read","Insert","Update","Delete",
|
||||
"View","InteractiveInsert","Edit","InteractiveDelete",
|
||||
"InteractiveSetDeletionMark","InteractiveClearDeletionMark","InteractiveDeleteMarked","InputByString",
|
||||
"InteractiveDeletePredefinedData","InteractiveSetDeletionMarkPredefinedData","InteractiveClearDeletionMarkPredefinedData","InteractiveDeleteMarkedPredefinedData",
|
||||
"ReadDataHistory","ReadDataHistoryOfMissingData","UpdateDataHistory","UpdateDataHistoryOfMissingData",
|
||||
"UpdateDataHistorySettings","UpdateDataHistoryVersionComment","ViewDataHistory","EditDataHistoryVersionComment",
|
||||
"SwitchToDataHistoryVersion"
|
||||
)
|
||||
"CommonAttribute" = @("View","Edit")
|
||||
"CommonCommand" = @("View")
|
||||
"CommonForm" = @("View")
|
||||
"Configuration" = @(
|
||||
"Administration","DataAdministration","UpdateDataBaseConfiguration","ExclusiveMode",
|
||||
"ActiveUsers","EventLog","ThinClient","WebClient",
|
||||
"MobileClient","ThickClient","ExternalConnection","Automation",
|
||||
"TechnicalSpecialistMode","CollaborationSystemInfoBaseRegistration","MainWindowModeNormal","MainWindowModeWorkplace",
|
||||
"MainWindowModeEmbeddedWorkplace","MainWindowModeFullscreenWorkplace","MainWindowModeKiosk","AnalyticsSystemClient",
|
||||
"SaveUserData","ConfigurationExtensionsAdministration","InteractiveOpenExtDataProcessors","InteractiveOpenExtReports",
|
||||
"Output"
|
||||
)
|
||||
"Constant" = @(
|
||||
"Read","Update","View","Edit",
|
||||
"ReadDataHistory","UpdateDataHistory","UpdateDataHistorySettings","UpdateDataHistoryVersionComment",
|
||||
"ViewDataHistory","EditDataHistoryVersionComment","SwitchToDataHistoryVersion"
|
||||
)
|
||||
"DataProcessor" = @("Use","View")
|
||||
"Document" = @(
|
||||
"Read","Insert","Update","Delete",
|
||||
"Posting","UndoPosting","View","InteractiveInsert",
|
||||
"Edit","InteractiveDelete","InteractiveSetDeletionMark","InteractiveClearDeletionMark",
|
||||
"InteractiveDeleteMarked","InteractivePosting","InteractivePostingRegular","InteractiveUndoPosting",
|
||||
"InteractiveChangeOfPosted","InputByString","ReadDataHistory","ReadDataHistoryOfMissingData",
|
||||
"UpdateDataHistory","UpdateDataHistoryOfMissingData","UpdateDataHistorySettings","UpdateDataHistoryVersionComment",
|
||||
"ViewDataHistory","EditDataHistoryVersionComment","SwitchToDataHistoryVersion"
|
||||
)
|
||||
"DocumentJournal" = @("Read","View")
|
||||
"ExchangePlan" = @(
|
||||
"Read","Insert","Update","Delete",
|
||||
"View","InteractiveInsert","Edit","InteractiveDelete",
|
||||
"InteractiveSetDeletionMark","InteractiveClearDeletionMark","InteractiveDeleteMarked","InputByString",
|
||||
"ReadDataHistory","ReadDataHistoryOfMissingData","UpdateDataHistory","UpdateDataHistoryOfMissingData",
|
||||
"UpdateDataHistorySettings","UpdateDataHistoryVersionComment","ViewDataHistory","EditDataHistoryVersionComment",
|
||||
"SwitchToDataHistoryVersion"
|
||||
)
|
||||
"FilterCriterion" = @("View")
|
||||
"HTTPService" = @("Use")
|
||||
"InformationRegister" = @(
|
||||
"Read","Update","View","Edit",
|
||||
"TotalsControl","ReadDataHistory","ReadDataHistoryOfMissingData","UpdateDataHistory",
|
||||
"UpdateDataHistoryOfMissingData","UpdateDataHistorySettings","UpdateDataHistoryVersionComment","ViewDataHistory",
|
||||
"EditDataHistoryVersionComment","SwitchToDataHistoryVersion"
|
||||
)
|
||||
"IntegrationService" = @("Use")
|
||||
"Report" = @("Use","View")
|
||||
"Sequence" = @("Read","Update")
|
||||
"SessionParameter" = @("Get","Set")
|
||||
"Subsystem" = @("View")
|
||||
"Task" = @(
|
||||
"Read","Insert","Update","Delete",
|
||||
"View","InteractiveInsert","Edit","InteractiveDelete",
|
||||
"InteractiveSetDeletionMark","InteractiveClearDeletionMark","InteractiveDeleteMarked","InputByString",
|
||||
"InteractiveActivate","Execute","InteractiveExecute","ReadDataHistory",
|
||||
"ReadDataHistoryOfMissingData","UpdateDataHistory","UpdateDataHistoryOfMissingData","UpdateDataHistorySettings",
|
||||
"UpdateDataHistoryVersionComment","ViewDataHistory","EditDataHistoryVersionComment","SwitchToDataHistoryVersion"
|
||||
)
|
||||
"WebService" = @("Use")
|
||||
}
|
||||
|
||||
$script:nestedRightOrder = @{
|
||||
"AccountingFlag" = @("View","Edit")
|
||||
"AddressingAttribute" = @("View","Edit")
|
||||
"Attribute" = @("View","Edit")
|
||||
"Command" = @("View")
|
||||
"Dimension" = @("View","Edit")
|
||||
"ExtDimensionAccountingFlag" = @("View","Edit")
|
||||
"IntegrationServiceChannel" = @("Use")
|
||||
"Method" = @("Use")
|
||||
"Operation" = @("Use")
|
||||
"Recalculation" = @("Read","Update")
|
||||
"Resource" = @("View","Edit")
|
||||
"StandardAttribute" = @("View","Edit")
|
||||
"StandardTabularSection" = @("View","Edit")
|
||||
"Subsystem" = @("View")
|
||||
"TabularSection" = @("View","Edit")
|
||||
}
|
||||
|
||||
# Каталоги объектов метаданных — нужны, чтобы прочитать uuid и расставить <object>.
|
||||
$script:typeDirs = @{
|
||||
"Catalog"="Catalogs"; "Document"="Documents"; "DocumentJournal"="DocumentJournals"
|
||||
"Sequence"="Sequences"; "Constant"="Constants"; "Report"="Reports"; "DataProcessor"="DataProcessors"
|
||||
"InformationRegister"="InformationRegisters"; "AccumulationRegister"="AccumulationRegisters"
|
||||
"AccountingRegister"="AccountingRegisters"; "CalculationRegister"="CalculationRegisters"
|
||||
"ChartOfAccounts"="ChartsOfAccounts"; "ChartOfCharacteristicTypes"="ChartsOfCharacteristicTypes"
|
||||
"ChartOfCalculationTypes"="ChartsOfCalculationTypes"; "ExchangePlan"="ExchangePlans"
|
||||
"BusinessProcess"="BusinessProcesses"; "Task"="Tasks"; "Subsystem"="Subsystems"
|
||||
"CommonForm"="CommonForms"; "CommonCommand"="CommonCommands"; "CommonAttribute"="CommonAttributes"
|
||||
"FilterCriterion"="FilterCriteria"; "SessionParameter"="SessionParameters"
|
||||
"WebService"="WebServices"; "HTTPService"="HTTPServices"; "IntegrationService"="IntegrationServices"
|
||||
"ExternalDataSource"="ExternalDataSources"
|
||||
}
|
||||
|
||||
# Порядок прав объекта: известные — по таблице, незнакомые — следом, в порядке ввода.
|
||||
function Sort-RightsCanonical {
|
||||
param([string]$objName, $rights)
|
||||
$parts = $objName -split '\.'
|
||||
$order = if ($parts.Count -ge 3) { $script:nestedRightOrder[$parts[$parts.Count-2]] }
|
||||
else { $script:rightOrder[$parts[0]] }
|
||||
if (-not $order) { return $rights }
|
||||
$byName = @{}
|
||||
foreach ($r in $rights) { if (-not $byName.ContainsKey($r.Name)) { $byName[$r.Name] = $r } }
|
||||
$sorted = @()
|
||||
foreach ($name in $order) { if ($byName.ContainsKey($name)) { $sorted += ,$byName[$name]; $byName.Remove($name) } }
|
||||
foreach ($r in $rights) { if ($byName.ContainsKey($r.Name)) { $sorted += ,$r; $byName.Remove($r.Name) } }
|
||||
return $sorted
|
||||
}
|
||||
|
||||
# uuid объекта прав: у верхнего уровня — из файла объекта, у вложенного — из его узла.
|
||||
function Get-RightsObjectUuid {
|
||||
param([string]$objName, [string]$configRoot)
|
||||
$parts = $objName -split '\.'
|
||||
if ($parts[0] -eq 'Configuration') {
|
||||
$cfgPath = Join-Path $configRoot "Configuration.xml"
|
||||
if (-not (Test-Path $cfgPath)) { return $null }
|
||||
$head = [System.IO.File]::ReadAllText($cfgPath)
|
||||
if ($head -match '<Configuration uuid="([0-9a-fA-F-]+)"') { return $Matches[1] }
|
||||
return $null
|
||||
}
|
||||
$dir = $script:typeDirs[$parts[0]]
|
||||
if (-not $dir -or $parts.Count -lt 2) { return $null }
|
||||
$ownerPath = Join-Path (Join-Path $configRoot $dir) "$($parts[1]).xml"
|
||||
if (-not (Test-Path $ownerPath)) { return $null }
|
||||
$text = [System.IO.File]::ReadAllText($ownerPath)
|
||||
if ($parts.Count -eq 2) {
|
||||
if ($text -match "<$($parts[0]) uuid=`"([0-9a-fA-F-]+)`"") { return $Matches[1] }
|
||||
return $null
|
||||
}
|
||||
# Вложенный: вид — предпоследний сегмент, имя — последний.
|
||||
$kind = [regex]::Escape($parts[$parts.Count-2])
|
||||
$name = [regex]::Escape($parts[$parts.Count-1])
|
||||
$rx = "<$kind uuid=`"([0-9a-fA-F-]+)`"[^>]*>\s*<Properties>\s*<Name>$name</Name>"
|
||||
if ($text -match $rx) { return $Matches[1] }
|
||||
return $null
|
||||
}
|
||||
|
||||
# Порядок узлов: по uuid объекта; неразрешённые — в конец, в порядке ввода.
|
||||
function Sort-ObjectsByUuid {
|
||||
param($objects, [string]$configRoot)
|
||||
$known = @()
|
||||
$unknown = @()
|
||||
foreach ($o in $objects) {
|
||||
$uuid = Get-RightsObjectUuid -objName $o.Name -configRoot $configRoot
|
||||
if ($uuid) { $known += ,[pscustomobject]@{ Uuid = $uuid; Obj = $o } }
|
||||
else {
|
||||
[Console]::Error.WriteLine("[role-compile] $($o.Name): объект не найден в выгрузке, uuid неизвестен — узел записан в конец (платформа переставит его при первой выгрузке)")
|
||||
$unknown += ,$o
|
||||
}
|
||||
}
|
||||
# Сортировка строго ordinal: Sort-Object сравнивает по культуре и игнорирует дефис,
|
||||
# из-за чего порядок разошёлся бы и с платформой, и с py-портом.
|
||||
$arr = [object[]]$known
|
||||
if ($arr.Count -gt 1) {
|
||||
[Array]::Sort($arr, [System.Comparison[object]]{ param($x, $y) [string]::CompareOrdinal($x.Uuid, $y.Uuid) })
|
||||
}
|
||||
$result = @()
|
||||
foreach ($k in $arr) { $result += ,$k.Obj }
|
||||
foreach ($u in $unknown) { $result += ,$u }
|
||||
return $result
|
||||
}
|
||||
|
||||
# --- 5. Helpers ---
|
||||
|
||||
function Get-ObjectType {
|
||||
@@ -1119,6 +1332,11 @@ X "`t<setForNewObjects>$sfno</setForNewObjects>"
|
||||
X "`t<setForAttributesByDefault>$sfab</setForAttributesByDefault>"
|
||||
X "`t<independentRightsOfChildObjects>$irco</independentRightsOfChildObjects>"
|
||||
|
||||
# Порядок как у платформы: узлы по uuid объекта, права — по канону типа. Иначе первая же
|
||||
# выгрузка из Конфигуратора переставит их и даст диф, которого никто не делал.
|
||||
$parsedObjects = @(Sort-ObjectsByUuid -objects $parsedObjects -configRoot $resolvedOutputDir)
|
||||
foreach ($o in $parsedObjects) { $o.Rights = @(Sort-RightsCanonical -objName $o.Name -rights $o.Rights) }
|
||||
|
||||
# Object blocks
|
||||
$totalRights = 0
|
||||
foreach ($obj in $parsedObjects) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# role-compile v1.37 — Compile 1C role from JSON
|
||||
# role-compile v1.38 — Compile 1C role from JSON
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import argparse
|
||||
import json
|
||||
@@ -771,6 +771,217 @@ def get_nested_rights(object_type, kind):
|
||||
return NESTED_KIND_RIGHTS.get(kind)
|
||||
|
||||
|
||||
# --- Канонический порядок прав и узлов (замерено на платформе) ---
|
||||
# Платформа нормализует порядок <right> внутри <object> и порядок самих <object>:
|
||||
# права идут в фиксированном для типа порядке, узлы — по uuid объекта метаданных.
|
||||
# Пишем сразу так же, иначе первая же выгрузка из Конфигуратора даст диф на ровном месте.
|
||||
RIGHT_ORDER = {
|
||||
"AccountingRegister": ["Read", "Update", "View", "Edit", "TotalsControl"],
|
||||
"AccumulationRegister": ["Read", "Update", "View", "Edit", "TotalsControl"],
|
||||
"BusinessProcess": [
|
||||
"Read", "Insert", "Update", "Delete",
|
||||
"View", "InteractiveInsert", "Edit", "InteractiveDelete",
|
||||
"InteractiveSetDeletionMark", "InteractiveClearDeletionMark", "InteractiveDeleteMarked", "InputByString",
|
||||
"InteractiveActivate", "Start", "InteractiveStart", "ReadDataHistory",
|
||||
"ReadDataHistoryOfMissingData", "UpdateDataHistory", "UpdateDataHistoryOfMissingData", "UpdateDataHistorySettings",
|
||||
"UpdateDataHistoryVersionComment", "ViewDataHistory", "EditDataHistoryVersionComment", "SwitchToDataHistoryVersion",
|
||||
],
|
||||
"CalculationRegister": ["Read", "Update", "View", "Edit"],
|
||||
"Catalog": [
|
||||
"Read", "Insert", "Update", "Delete",
|
||||
"View", "InteractiveInsert", "Edit", "InteractiveDelete",
|
||||
"InteractiveSetDeletionMark", "InteractiveClearDeletionMark", "InteractiveDeleteMarked", "InputByString",
|
||||
"InteractiveDeletePredefinedData", "InteractiveSetDeletionMarkPredefinedData", "InteractiveClearDeletionMarkPredefinedData", "InteractiveDeleteMarkedPredefinedData",
|
||||
"ReadDataHistory", "ReadDataHistoryOfMissingData", "UpdateDataHistory", "UpdateDataHistoryOfMissingData",
|
||||
"UpdateDataHistorySettings", "UpdateDataHistoryVersionComment", "ViewDataHistory", "EditDataHistoryVersionComment",
|
||||
"SwitchToDataHistoryVersion",
|
||||
],
|
||||
"ChartOfAccounts": [
|
||||
"Read", "Insert", "Update", "Delete",
|
||||
"View", "InteractiveInsert", "Edit", "InteractiveDelete",
|
||||
"InteractiveSetDeletionMark", "InteractiveClearDeletionMark", "InteractiveDeleteMarked", "InputByString",
|
||||
"InteractiveDeletePredefinedData", "InteractiveSetDeletionMarkPredefinedData", "InteractiveClearDeletionMarkPredefinedData", "InteractiveDeleteMarkedPredefinedData",
|
||||
"ReadDataHistory", "ReadDataHistoryOfMissingData", "UpdateDataHistory", "UpdateDataHistoryOfMissingData",
|
||||
"UpdateDataHistorySettings", "UpdateDataHistoryVersionComment", "ViewDataHistory", "EditDataHistoryVersionComment",
|
||||
"SwitchToDataHistoryVersion",
|
||||
],
|
||||
"ChartOfCalculationTypes": [
|
||||
"Read", "Insert", "Update", "Delete",
|
||||
"View", "InteractiveInsert", "Edit", "InteractiveDelete",
|
||||
"InteractiveSetDeletionMark", "InteractiveClearDeletionMark", "InteractiveDeleteMarked", "InputByString",
|
||||
"InteractiveDeletePredefinedData", "InteractiveSetDeletionMarkPredefinedData", "InteractiveClearDeletionMarkPredefinedData", "InteractiveDeleteMarkedPredefinedData",
|
||||
"ReadDataHistory", "ReadDataHistoryOfMissingData", "UpdateDataHistory", "UpdateDataHistoryOfMissingData",
|
||||
"UpdateDataHistorySettings", "UpdateDataHistoryVersionComment", "ViewDataHistory", "EditDataHistoryVersionComment",
|
||||
"SwitchToDataHistoryVersion",
|
||||
],
|
||||
"ChartOfCharacteristicTypes": [
|
||||
"Read", "Insert", "Update", "Delete",
|
||||
"View", "InteractiveInsert", "Edit", "InteractiveDelete",
|
||||
"InteractiveSetDeletionMark", "InteractiveClearDeletionMark", "InteractiveDeleteMarked", "InputByString",
|
||||
"InteractiveDeletePredefinedData", "InteractiveSetDeletionMarkPredefinedData", "InteractiveClearDeletionMarkPredefinedData", "InteractiveDeleteMarkedPredefinedData",
|
||||
"ReadDataHistory", "ReadDataHistoryOfMissingData", "UpdateDataHistory", "UpdateDataHistoryOfMissingData",
|
||||
"UpdateDataHistorySettings", "UpdateDataHistoryVersionComment", "ViewDataHistory", "EditDataHistoryVersionComment",
|
||||
"SwitchToDataHistoryVersion",
|
||||
],
|
||||
"CommonAttribute": ["View", "Edit"],
|
||||
"CommonCommand": ["View"],
|
||||
"CommonForm": ["View"],
|
||||
"Configuration": [
|
||||
"Administration", "DataAdministration", "UpdateDataBaseConfiguration", "ExclusiveMode",
|
||||
"ActiveUsers", "EventLog", "ThinClient", "WebClient",
|
||||
"MobileClient", "ThickClient", "ExternalConnection", "Automation",
|
||||
"TechnicalSpecialistMode", "CollaborationSystemInfoBaseRegistration", "MainWindowModeNormal", "MainWindowModeWorkplace",
|
||||
"MainWindowModeEmbeddedWorkplace", "MainWindowModeFullscreenWorkplace", "MainWindowModeKiosk", "AnalyticsSystemClient",
|
||||
"SaveUserData", "ConfigurationExtensionsAdministration", "InteractiveOpenExtDataProcessors", "InteractiveOpenExtReports",
|
||||
"Output",
|
||||
],
|
||||
"Constant": [
|
||||
"Read", "Update", "View", "Edit",
|
||||
"ReadDataHistory", "UpdateDataHistory", "UpdateDataHistorySettings", "UpdateDataHistoryVersionComment",
|
||||
"ViewDataHistory", "EditDataHistoryVersionComment", "SwitchToDataHistoryVersion",
|
||||
],
|
||||
"DataProcessor": ["Use", "View"],
|
||||
"Document": [
|
||||
"Read", "Insert", "Update", "Delete",
|
||||
"Posting", "UndoPosting", "View", "InteractiveInsert",
|
||||
"Edit", "InteractiveDelete", "InteractiveSetDeletionMark", "InteractiveClearDeletionMark",
|
||||
"InteractiveDeleteMarked", "InteractivePosting", "InteractivePostingRegular", "InteractiveUndoPosting",
|
||||
"InteractiveChangeOfPosted", "InputByString", "ReadDataHistory", "ReadDataHistoryOfMissingData",
|
||||
"UpdateDataHistory", "UpdateDataHistoryOfMissingData", "UpdateDataHistorySettings", "UpdateDataHistoryVersionComment",
|
||||
"ViewDataHistory", "EditDataHistoryVersionComment", "SwitchToDataHistoryVersion",
|
||||
],
|
||||
"DocumentJournal": ["Read", "View"],
|
||||
"ExchangePlan": [
|
||||
"Read", "Insert", "Update", "Delete",
|
||||
"View", "InteractiveInsert", "Edit", "InteractiveDelete",
|
||||
"InteractiveSetDeletionMark", "InteractiveClearDeletionMark", "InteractiveDeleteMarked", "InputByString",
|
||||
"ReadDataHistory", "ReadDataHistoryOfMissingData", "UpdateDataHistory", "UpdateDataHistoryOfMissingData",
|
||||
"UpdateDataHistorySettings", "UpdateDataHistoryVersionComment", "ViewDataHistory", "EditDataHistoryVersionComment",
|
||||
"SwitchToDataHistoryVersion",
|
||||
],
|
||||
"FilterCriterion": ["View"],
|
||||
"HTTPService": ["Use"],
|
||||
"InformationRegister": [
|
||||
"Read", "Update", "View", "Edit",
|
||||
"TotalsControl", "ReadDataHistory", "ReadDataHistoryOfMissingData", "UpdateDataHistory",
|
||||
"UpdateDataHistoryOfMissingData", "UpdateDataHistorySettings", "UpdateDataHistoryVersionComment", "ViewDataHistory",
|
||||
"EditDataHistoryVersionComment", "SwitchToDataHistoryVersion",
|
||||
],
|
||||
"IntegrationService": ["Use"],
|
||||
"Report": ["Use", "View"],
|
||||
"Sequence": ["Read", "Update"],
|
||||
"SessionParameter": ["Get", "Set"],
|
||||
"Subsystem": ["View"],
|
||||
"Task": [
|
||||
"Read", "Insert", "Update", "Delete",
|
||||
"View", "InteractiveInsert", "Edit", "InteractiveDelete",
|
||||
"InteractiveSetDeletionMark", "InteractiveClearDeletionMark", "InteractiveDeleteMarked", "InputByString",
|
||||
"InteractiveActivate", "Execute", "InteractiveExecute", "ReadDataHistory",
|
||||
"ReadDataHistoryOfMissingData", "UpdateDataHistory", "UpdateDataHistoryOfMissingData", "UpdateDataHistorySettings",
|
||||
"UpdateDataHistoryVersionComment", "ViewDataHistory", "EditDataHistoryVersionComment", "SwitchToDataHistoryVersion",
|
||||
],
|
||||
"WebService": ["Use"],
|
||||
}
|
||||
|
||||
NESTED_RIGHT_ORDER = {
|
||||
"AccountingFlag": ["View", "Edit"],
|
||||
"AddressingAttribute": ["View", "Edit"],
|
||||
"Attribute": ["View", "Edit"],
|
||||
"Command": ["View"],
|
||||
"Dimension": ["View", "Edit"],
|
||||
"ExtDimensionAccountingFlag": ["View", "Edit"],
|
||||
"IntegrationServiceChannel": ["Use"],
|
||||
"Method": ["Use"],
|
||||
"Operation": ["Use"],
|
||||
"Recalculation": ["Read", "Update"],
|
||||
"Resource": ["View", "Edit"],
|
||||
"StandardAttribute": ["View", "Edit"],
|
||||
"StandardTabularSection": ["View", "Edit"],
|
||||
"Subsystem": ["View"],
|
||||
"TabularSection": ["View", "Edit"],
|
||||
}
|
||||
|
||||
# Каталоги объектов метаданных — нужны, чтобы прочитать uuid и расставить <object>.
|
||||
TYPE_DIRS = {
|
||||
"Catalog": "Catalogs", "Document": "Documents", "DocumentJournal": "DocumentJournals",
|
||||
"Sequence": "Sequences", "Constant": "Constants", "Report": "Reports",
|
||||
"DataProcessor": "DataProcessors", "InformationRegister": "InformationRegisters",
|
||||
"AccumulationRegister": "AccumulationRegisters", "AccountingRegister": "AccountingRegisters",
|
||||
"CalculationRegister": "CalculationRegisters", "ChartOfAccounts": "ChartsOfAccounts",
|
||||
"ChartOfCharacteristicTypes": "ChartsOfCharacteristicTypes",
|
||||
"ChartOfCalculationTypes": "ChartsOfCalculationTypes", "ExchangePlan": "ExchangePlans",
|
||||
"BusinessProcess": "BusinessProcesses", "Task": "Tasks", "Subsystem": "Subsystems",
|
||||
"CommonForm": "CommonForms", "CommonCommand": "CommonCommands",
|
||||
"CommonAttribute": "CommonAttributes", "FilterCriterion": "FilterCriteria",
|
||||
"SessionParameter": "SessionParameters", "WebService": "WebServices",
|
||||
"HTTPService": "HTTPServices", "IntegrationService": "IntegrationServices",
|
||||
"ExternalDataSource": "ExternalDataSources",
|
||||
}
|
||||
|
||||
|
||||
def sort_rights_canonical(object_name, rights):
|
||||
"""Порядок прав объекта: известные — по таблице, незнакомые — следом, в порядке ввода."""
|
||||
parts = object_name.split('.')
|
||||
order = NESTED_RIGHT_ORDER.get(parts[-2]) if len(parts) >= 3 else RIGHT_ORDER.get(parts[0])
|
||||
if not order:
|
||||
return rights
|
||||
by_name = {}
|
||||
for r in rights:
|
||||
by_name.setdefault(r['Name'], r)
|
||||
sorted_rights = []
|
||||
for name in order:
|
||||
if name in by_name:
|
||||
sorted_rights.append(by_name.pop(name))
|
||||
for r in rights:
|
||||
if r['Name'] in by_name:
|
||||
sorted_rights.append(by_name.pop(r['Name']))
|
||||
return sorted_rights
|
||||
|
||||
|
||||
def get_rights_object_uuid(object_name, config_root):
|
||||
"""uuid объекта прав: у верхнего уровня — из файла объекта, у вложенного — из его узла."""
|
||||
parts = object_name.split('.')
|
||||
if parts[0] == 'Configuration':
|
||||
cfg_path = os.path.join(config_root, 'Configuration.xml')
|
||||
if not os.path.isfile(cfg_path):
|
||||
return None
|
||||
with open(cfg_path, 'r', encoding='utf-8-sig') as f:
|
||||
m = re.search(r'<Configuration uuid="([0-9a-fA-F-]+)"', f.read())
|
||||
return m.group(1) if m else None
|
||||
directory = TYPE_DIRS.get(parts[0])
|
||||
if not directory or len(parts) < 2:
|
||||
return None
|
||||
owner_path = os.path.join(config_root, directory, parts[1] + '.xml')
|
||||
if not os.path.isfile(owner_path):
|
||||
return None
|
||||
with open(owner_path, 'r', encoding='utf-8-sig') as f:
|
||||
text = f.read()
|
||||
if len(parts) == 2:
|
||||
m = re.search(r'<%s uuid="([0-9a-fA-F-]+)"' % re.escape(parts[0]), text)
|
||||
return m.group(1) if m else None
|
||||
# Вложенный: вид — предпоследний сегмент, имя — последний.
|
||||
rx = r'<%s uuid="([0-9a-fA-F-]+)"[^>]*>\s*<Properties>\s*<Name>%s</Name>' % (
|
||||
re.escape(parts[-2]), re.escape(parts[-1]))
|
||||
m = re.search(rx, text)
|
||||
return m.group(1) if m else None
|
||||
|
||||
|
||||
def sort_objects_by_uuid(objects, config_root):
|
||||
"""Порядок узлов: по uuid объекта; неразрешённые — в конец, в порядке ввода."""
|
||||
known, unknown = [], []
|
||||
for o in objects:
|
||||
uuid_value = get_rights_object_uuid(o['Name'], config_root)
|
||||
if uuid_value:
|
||||
known.append((uuid_value, o))
|
||||
else:
|
||||
print(f"[role-compile] {o['Name']}: объект не найден в выгрузке, uuid неизвестен — "
|
||||
f"узел записан в конец (платформа переставит его при первой выгрузке)",
|
||||
file=sys.stderr)
|
||||
unknown.append(o)
|
||||
known.sort(key=lambda pair: pair[0])
|
||||
return [o for _, o in known] + unknown
|
||||
|
||||
|
||||
# Отказ копится, а не печатается сразу: роль пишется целиком, поэтому единственный
|
||||
# безопасный момент отказа — до первой записи, и показать надо все причины сразу.
|
||||
VALIDATION_ERRORS = []
|
||||
@@ -1372,6 +1583,12 @@ def main():
|
||||
lines.append(f'\t<setForAttributesByDefault>{sfab}</setForAttributesByDefault>')
|
||||
lines.append(f'\t<independentRightsOfChildObjects>{irco}</independentRightsOfChildObjects>')
|
||||
|
||||
# Порядок как у платформы: узлы по uuid объекта, права — по канону типа. Иначе первая же
|
||||
# выгрузка из Конфигуратора переставит их и даст диф, которого никто не делал.
|
||||
parsed_objects = sort_objects_by_uuid(parsed_objects, out_dir_resolved)
|
||||
for o in parsed_objects:
|
||||
o['Rights'] = sort_rights_canonical(o['Name'], o['Rights'])
|
||||
|
||||
# Object blocks
|
||||
total_rights = 0
|
||||
for obj in parsed_objects:
|
||||
|
||||
Reference in New Issue
Block a user