mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-09-03 00:30:52 +03:00
feat(cfe-borrow,cfe-patch-method,cfe-validate): модули заимствованных объектов и пометка расширенного свойства
Объект заимствуют, чтобы дописать в него модуль, — теперь пустой файл модуля создаётся навыком, а не руками. Тип с единственным модулем (CommonModule, HTTPService, WebService) получает его без указаний, остальные — по -Module; -Module None отменяет. Существующий файл не перезаписывается никогда. Вместе с файлом проставляется <xr:PropertyState> со State=Extended. Замер по лестнице платформ 8.3.20…8.5.1: элемент появился в формате 2.19 (8.3.26), ниже платформа молча выбрасывает его при загрузке — отсюда гейт по версии формата. Имя свойства равно базовому имени файла модуля, у заимствованной формы — Form. Правило владения одно: пометку ставит тот, кто создал файл, поэтому её ставит и cfe-patch-method. Заодно закрыта перезапись при повторном заимствовании: прямая ветка писала XML уже заимствованного объекта начисто, унося собственные реквизиты расширения и регистрацию формы, — молча, с успешным отчётом. Повторный вызов теперь безопасен и служит способом дозаимствовать модуль. cfe-validate сверяет «файл модуля ↔ пометка» в обе стороны (предупреждение: перекос платформа принимает, но выгрузка Конфигуратора так не выглядит). Проверено: полный регресс 827/0/8 (ps1) и 824/0/11 (py), гарды 5/5, сквозной раундтрип на 8.3.26 и 8.3.27 — InternalInfo совпадает с выгрузкой платформы. Закрывает #70. Разбор и эталоны выгрузки — Romandredan. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
89f65ca6a6
commit
8fe727e32f
@@ -1,10 +1,11 @@
|
||||
# cfe-borrow v1.31 — Borrow objects from configuration into extension (CFE)
|
||||
# cfe-borrow v1.32 — Borrow objects from configuration into extension (CFE)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$ExtensionPath,
|
||||
[Parameter(Mandatory)][string]$ConfigPath,
|
||||
[Parameter(Mandatory)][string]$Object,
|
||||
[string]$BorrowMainAttribute
|
||||
[string]$BorrowMainAttribute,
|
||||
[string]$Module
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
@@ -261,6 +262,32 @@ $childTypeDirMap = @{
|
||||
"CommonAttribute"="CommonAttributes"; "Style"="Styles"; "Bot"="Bots"; "Language"="Languages"
|
||||
}
|
||||
|
||||
# --- 4a. Модули заимствованных объектов ---
|
||||
# Порядок внутри значения — порядок выгрузки Конфигуратора: сначала «объектный» модуль
|
||||
# (ObjectModule / RecordSetModule / ValueManagerModule), затем ManagerModule.
|
||||
$script:moduleKindsByType = @{
|
||||
"CommonModule"=@("Module"); "HTTPService"=@("Module"); "WebService"=@("Module")
|
||||
"Catalog"=@("ObjectModule","ManagerModule"); "Document"=@("ObjectModule","ManagerModule")
|
||||
"Report"=@("ObjectModule","ManagerModule"); "DataProcessor"=@("ObjectModule","ManagerModule")
|
||||
"ExchangePlan"=@("ObjectModule","ManagerModule")
|
||||
"ChartOfCharacteristicTypes"=@("ObjectModule","ManagerModule")
|
||||
"ChartOfAccounts"=@("ObjectModule","ManagerModule")
|
||||
"ChartOfCalculationTypes"=@("ObjectModule","ManagerModule")
|
||||
"BusinessProcess"=@("ObjectModule","ManagerModule"); "Task"=@("ObjectModule","ManagerModule")
|
||||
"InformationRegister"=@("RecordSetModule","ManagerModule")
|
||||
"AccumulationRegister"=@("RecordSetModule","ManagerModule")
|
||||
"AccountingRegister"=@("RecordSetModule","ManagerModule")
|
||||
"CalculationRegister"=@("RecordSetModule","ManagerModule")
|
||||
"Sequence"=@("RecordSetModule","ManagerModule")
|
||||
"Constant"=@("ValueManagerModule","ManagerModule")
|
||||
"Enum"=@("ManagerModule"); "DocumentJournal"=@("ManagerModule")
|
||||
"FilterCriterion"=@("ManagerModule")
|
||||
}
|
||||
# Типы с ЕДИНСТВЕННЫМ модулем: ради него объект и заимствуют, поэтому файл создаётся молча.
|
||||
# Отказ — `-Module None`.
|
||||
$script:autoModuleTypes = @("CommonModule", "HTTPService", "WebService")
|
||||
$script:moduleKindNames = @("Module", "ObjectModule", "ManagerModule", "RecordSetModule", "ValueManagerModule")
|
||||
|
||||
# --- 4b. Russian synonym → English type ---
|
||||
$synonymMap = @{
|
||||
"Справочник"="Catalog"; "Документ"="Document"; "Перечисление"="Enum"
|
||||
@@ -591,6 +618,50 @@ if ($BorrowMainAttribute) {
|
||||
}
|
||||
}
|
||||
|
||||
# --- 9c. Validate -Module ---
|
||||
$script:requestedModules = @()
|
||||
$script:noModule = $false
|
||||
if ($Module) {
|
||||
foreach ($raw in ($Module -split '[,;]')) {
|
||||
$kind = $raw.Trim()
|
||||
if (-not $kind) { continue }
|
||||
# Сравнение РЕГИСТРОНЕЗАВИСИМОЕ явно (-ieq): в py-порте это отдельная ветка, и молчаливое
|
||||
# расхождение портов на «none» ловится только глазами.
|
||||
if ($kind -ieq "None") { $script:noModule = $true; continue }
|
||||
$canon = @($script:moduleKindNames | Where-Object { $_ -ieq $kind })
|
||||
if ($canon.Count -eq 0) {
|
||||
Write-Error "Неизвестный вид модуля '$kind'. Допустимо: $($script:moduleKindNames -join ', '), None"
|
||||
exit 1
|
||||
}
|
||||
$script:requestedModules += $canon[0]
|
||||
}
|
||||
if ($script:noModule -and $script:requestedModules.Count -gt 0) {
|
||||
Write-Error "-Module None нельзя сочетать с видами модулей"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# Какие модули создать для объекта. Тип с единственным модулем получает его всегда — уточнять
|
||||
# там нечего; -Module разбирает только неоднозначные типы. Иначе батч смешанных типов
|
||||
# (`CommonModule.X ;; Catalog.Y`) не выражался бы одним вызовом.
|
||||
function Resolve-ModuleKinds {
|
||||
param([string]$typeName)
|
||||
|
||||
if ($script:noModule) { return @() }
|
||||
$allowed = @($script:moduleKindsByType[$typeName])
|
||||
if ($allowed.Count -eq 0) { return @() }
|
||||
|
||||
if ($script:autoModuleTypes -contains $typeName) { return @($allowed[0]) }
|
||||
if ($script:requestedModules.Count -eq 0) { return @() }
|
||||
|
||||
# Порядок берём из таблицы типа, а не из порядка ключей в -Module.
|
||||
$selected = @($allowed | Where-Object { $script:requestedModules -contains $_ })
|
||||
if ($selected.Count -eq 0) {
|
||||
Warn " Тип $typeName не имеет запрошенных модулей — пропущено. Допустимо: $($allowed -join ', ')"
|
||||
}
|
||||
return $selected
|
||||
}
|
||||
|
||||
# --- 10. Helper: read source object XML ---
|
||||
# Имена реквизитов исходного объекта → uuid. Нужны для непрозрачной формы пути в ссылках
|
||||
# параметров выбора (см. Rewrite-ChoiceParameterLinks).
|
||||
@@ -1313,6 +1384,81 @@ function Test-ObjectBorrowed {
|
||||
return (Test-Path $objFile)
|
||||
}
|
||||
|
||||
# --- 10f. Helper: пометка расширенного свойства (<xr:PropertyState>) ---
|
||||
# Свойство появилось в формате 2.19 (8.3.26): на 2.18 и ниже платформа молча выбрасывает элемент
|
||||
# при загрузке. С 2.19 Конфигуратор ставит его сам при выгрузке — эмитим, чтобы исходники навыка
|
||||
# совпадали с эталоном. Имя свойства = базовое имя файла модуля (Module / ObjectModule / …),
|
||||
# у заимствованной формы — Form. Ставит тот, кто создал файл модуля (или форму).
|
||||
function Build-PropertyStateXml {
|
||||
param([string]$propertyName, [string]$indent)
|
||||
|
||||
$sb = New-Object System.Text.StringBuilder
|
||||
$sb.AppendLine("${indent}<xr:PropertyState>") | Out-Null
|
||||
$sb.AppendLine("${indent}`t<xr:Property>${propertyName}</xr:Property>") | Out-Null
|
||||
$sb.AppendLine("${indent}`t<xr:State>Extended</xr:State>") | Out-Null
|
||||
$sb.Append("${indent}</xr:PropertyState>") | Out-Null
|
||||
return $sb.ToString()
|
||||
}
|
||||
|
||||
function Set-PropertyStateFlag {
|
||||
param([string]$objFile, [string]$propertyName, [string]$formatVersion)
|
||||
|
||||
if ((Get-FormatRank $formatVersion) -lt 219) { return }
|
||||
if (-not (Test-Path $objFile)) { return }
|
||||
|
||||
$enc = New-Object System.Text.UTF8Encoding($true)
|
||||
$text = [System.IO.File]::ReadAllText($objFile, $enc)
|
||||
$nl = if ($text -match "`r`n") { "`r`n" } else { "`n" }
|
||||
|
||||
# ПЕРВЫЙ <InternalInfo> в файле — собственный у объекта: у реквизитов и подобъектов свои,
|
||||
# но они лежат ниже, внутри <ChildObjects>.
|
||||
$empty = [regex]::Match($text, '([ \t]*)<InternalInfo\s*/>')
|
||||
$open = [regex]::Match($text, '(?s)([ \t]*)<InternalInfo>(.*?)</InternalInfo>')
|
||||
|
||||
if ($empty.Success -and (-not $open.Success -or $empty.Index -lt $open.Index)) {
|
||||
$ind = $empty.Groups[1].Value
|
||||
$block = Build-PropertyStateXml $propertyName ($ind + "`t")
|
||||
$replacement = "${ind}<InternalInfo>${nl}${block}${nl}${ind}</InternalInfo>"
|
||||
$text = $text.Remove($empty.Index, $empty.Length).Insert($empty.Index, $replacement)
|
||||
} elseif ($open.Success) {
|
||||
if ($open.Groups[2].Value -match "<xr:Property>$([regex]::Escape($propertyName))</xr:Property>") { return }
|
||||
$ind = $open.Groups[1].Value
|
||||
$block = Build-PropertyStateXml $propertyName ($ind + "`t")
|
||||
# Дописываем в КОНЕЦ InternalInfo: у Конфигуратора PropertyState идёт после GeneratedType.
|
||||
$closeAt = $open.Index + $open.Length - "</InternalInfo>".Length - $ind.Length
|
||||
$text = $text.Insert($closeAt, "${block}${nl}")
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
[System.IO.File]::WriteAllText($objFile, $text, $enc)
|
||||
}
|
||||
|
||||
# --- 10g. Helper: пустой модуль заимствованного объекта ---
|
||||
function New-BorrowedModuleFile {
|
||||
param([string]$typeName, [string]$objName, [string]$moduleKind)
|
||||
|
||||
$dirName = $childTypeDirMap[$typeName]
|
||||
$objDir = Join-Path (Join-Path $extDir $dirName) $objName
|
||||
$moduleDir = Join-Path $objDir "Ext"
|
||||
if (-not (Test-Path $moduleDir)) { New-Item -ItemType Directory -Path $moduleDir -Force | Out-Null }
|
||||
|
||||
# NEVER overwrite an existing one: повторное заимствование не должно затирать дописанный код
|
||||
# (то же правило, что у модуля формы).
|
||||
$moduleFile = Join-Path $moduleDir "${moduleKind}.bsl"
|
||||
if (Test-Path $moduleFile) {
|
||||
Info " Preserved existing ${moduleKind}.bsl"
|
||||
} else {
|
||||
$enc = New-Object System.Text.UTF8Encoding($true)
|
||||
[System.IO.File]::WriteAllText($moduleFile, "", $enc)
|
||||
Info " Created: $moduleFile"
|
||||
}
|
||||
|
||||
# Флаг ставим и для уже существовавшего файла: состояние объекта должно отражать факт модуля.
|
||||
Set-PropertyStateFlag (Join-Path (Join-Path $extDir $dirName) "${objName}.xml") $moduleKind $script:formatVersion
|
||||
return $moduleFile
|
||||
}
|
||||
|
||||
# --- 11. Helper: generate InternalInfo XML ---
|
||||
function Build-InternalInfoXml {
|
||||
param([string]$typeName, [string]$objName, [string]$indent)
|
||||
@@ -2204,6 +2350,9 @@ foreach ($item in $items) {
|
||||
$hasBMA = [bool]$BorrowMainAttribute
|
||||
$formFiles = Borrow-Form $typeName $objName $formName -BorrowMainAttr:$hasBMA
|
||||
$script:borrowedFiles += $formFiles
|
||||
# Замер на 8.3.26: платформа помечает форму расширенной сразу при заимствовании,
|
||||
# даже если элементы не менялись. Флаг живёт в метаданных формы, не у владельца.
|
||||
Set-PropertyStateFlag $formFiles[0] "Form" $script:formatVersion
|
||||
$borrowedCount++
|
||||
|
||||
# Borrow main attribute if requested
|
||||
@@ -2212,26 +2361,37 @@ foreach ($item in $items) {
|
||||
}
|
||||
} else {
|
||||
# --- Object borrowing (existing logic) ---
|
||||
Info "Borrowing ${typeName}.${objName}..."
|
||||
|
||||
$src = Read-SourceObject $typeName $objName
|
||||
Info " Source UUID: $($src.Uuid)"
|
||||
|
||||
$borrowedXml = Build-BorrowedObjectXml $typeName $objName $src.Uuid $src.Properties
|
||||
|
||||
$targetDir = Join-Path $extDir $dirName
|
||||
if (-not (Test-Path $targetDir)) {
|
||||
New-Item -ItemType Directory -Path $targetDir -Force | Out-Null
|
||||
}
|
||||
|
||||
$targetFile = Join-Path $targetDir "${objName}.xml"
|
||||
$enc = New-Object System.Text.UTF8Encoding($true)
|
||||
[System.IO.File]::WriteAllText($targetFile, $borrowedXml, $enc)
|
||||
Info " Created: $targetFile"
|
||||
|
||||
# Уже заимствованный объект НЕ переписываем: в его XML лежат собственные реквизиты
|
||||
# расширения, заимствованные подобъекты и состояния, которые из источника не выводятся.
|
||||
# Повторный вызов — законный способ доделать модуль (-Module), а не переиздать заготовку.
|
||||
if (Test-ObjectBorrowed $typeName $objName) {
|
||||
Info "Already borrowed: ${typeName}.${objName} — XML сохранён без изменений"
|
||||
} else {
|
||||
Info "Borrowing ${typeName}.${objName}..."
|
||||
|
||||
$src = Read-SourceObject $typeName $objName
|
||||
Info " Source UUID: $($src.Uuid)"
|
||||
|
||||
$borrowedXml = Build-BorrowedObjectXml $typeName $objName $src.Uuid $src.Properties
|
||||
|
||||
if (-not (Test-Path $targetDir)) {
|
||||
New-Item -ItemType Directory -Path $targetDir -Force | Out-Null
|
||||
}
|
||||
|
||||
$enc = New-Object System.Text.UTF8Encoding($true)
|
||||
[System.IO.File]::WriteAllText($targetFile, $borrowedXml, $enc)
|
||||
Info " Created: $targetFile"
|
||||
}
|
||||
|
||||
Add-ToChildObjects $typeName $objName
|
||||
|
||||
$script:borrowedFiles += $targetFile
|
||||
foreach ($kind in (Resolve-ModuleKinds $typeName)) {
|
||||
$script:borrowedFiles += (New-BorrowedModuleFile $typeName $objName $kind)
|
||||
}
|
||||
$borrowedCount++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# cfe-borrow v1.31 — Borrow objects from configuration into extension (CFE)
|
||||
# cfe-borrow v1.32 — Borrow objects from configuration into extension (CFE)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -240,6 +240,32 @@ CHILD_TYPE_DIR_MAP = {
|
||||
"Bot": "Bots", "Language": "Languages",
|
||||
}
|
||||
|
||||
# --- Модули заимствованных объектов ---
|
||||
# Порядок внутри значения — порядок выгрузки Конфигуратора: сначала «объектный» модуль
|
||||
# (ObjectModule / RecordSetModule / ValueManagerModule), затем ManagerModule.
|
||||
MODULE_KINDS_BY_TYPE = {
|
||||
"CommonModule": ["Module"], "HTTPService": ["Module"], "WebService": ["Module"],
|
||||
"Catalog": ["ObjectModule", "ManagerModule"], "Document": ["ObjectModule", "ManagerModule"],
|
||||
"Report": ["ObjectModule", "ManagerModule"], "DataProcessor": ["ObjectModule", "ManagerModule"],
|
||||
"ExchangePlan": ["ObjectModule", "ManagerModule"],
|
||||
"ChartOfCharacteristicTypes": ["ObjectModule", "ManagerModule"],
|
||||
"ChartOfAccounts": ["ObjectModule", "ManagerModule"],
|
||||
"ChartOfCalculationTypes": ["ObjectModule", "ManagerModule"],
|
||||
"BusinessProcess": ["ObjectModule", "ManagerModule"], "Task": ["ObjectModule", "ManagerModule"],
|
||||
"InformationRegister": ["RecordSetModule", "ManagerModule"],
|
||||
"AccumulationRegister": ["RecordSetModule", "ManagerModule"],
|
||||
"AccountingRegister": ["RecordSetModule", "ManagerModule"],
|
||||
"CalculationRegister": ["RecordSetModule", "ManagerModule"],
|
||||
"Sequence": ["RecordSetModule", "ManagerModule"],
|
||||
"Constant": ["ValueManagerModule", "ManagerModule"],
|
||||
"Enum": ["ManagerModule"], "DocumentJournal": ["ManagerModule"],
|
||||
"FilterCriterion": ["ManagerModule"],
|
||||
}
|
||||
# Типы с ЕДИНСТВЕННЫМ модулем: ради него объект и заимствуют, поэтому файл создаётся молча.
|
||||
# Отказ — `-Module None`.
|
||||
AUTO_MODULE_TYPES = ["CommonModule", "HTTPService", "WebService"]
|
||||
MODULE_KIND_NAMES = ["Module", "ObjectModule", "ManagerModule", "RecordSetModule", "ValueManagerModule"]
|
||||
|
||||
SYNONYM_MAP = {
|
||||
"\u0421\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a": "Catalog",
|
||||
"\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442": "Document",
|
||||
@@ -516,6 +542,55 @@ def format_rank(ver):
|
||||
return int(m.group(1)) * 100 + int(m.group(2)) if m else 0
|
||||
|
||||
|
||||
# --- Пометка расширенного свойства (<xr:PropertyState>) ---
|
||||
# Свойство появилось в формате 2.19 (8.3.26): на 2.18 и ниже платформа молча выбрасывает элемент
|
||||
# при загрузке. С 2.19 Конфигуратор ставит его сам при выгрузке. Правило: флаг ставит тот, кто
|
||||
# создал файл модуля, — здесь это мы. Имя свойства = базовое имя файла модуля.
|
||||
# Копии этих функций есть в cfe-patch-method (навыки автономны); держать их одинаковыми — сознательно.
|
||||
def build_property_state_xml(property_name, indent):
|
||||
return "\n".join([
|
||||
f"{indent}<xr:PropertyState>",
|
||||
f"{indent}\t<xr:Property>{property_name}</xr:Property>",
|
||||
f"{indent}\t<xr:State>Extended</xr:State>",
|
||||
f"{indent}</xr:PropertyState>",
|
||||
])
|
||||
|
||||
|
||||
def set_property_state_flag(obj_file, property_name, format_version):
|
||||
if format_rank(format_version) < 219:
|
||||
return
|
||||
if not os.path.isfile(obj_file):
|
||||
return
|
||||
|
||||
with open(obj_file, "r", encoding="utf-8-sig", newline="") as fh:
|
||||
text = fh.read()
|
||||
nl = "\r\n" if "\r\n" in text else "\n"
|
||||
|
||||
# ПЕРВЫЙ <InternalInfo> в файле — собственный у объекта: у реквизитов и подобъектов свои,
|
||||
# но они лежат ниже, внутри <ChildObjects>.
|
||||
empty = re.search(r"([ \t]*)<InternalInfo\s*/>", text)
|
||||
opened = re.search(r"([ \t]*)<InternalInfo>(.*?)</InternalInfo>", text, re.S)
|
||||
|
||||
if empty and (not opened or empty.start() < opened.start()):
|
||||
ind = empty.group(1)
|
||||
block = build_property_state_xml(property_name, ind + "\t")
|
||||
replacement = f"{ind}<InternalInfo>{nl}{block}{nl}{ind}</InternalInfo>"
|
||||
text = text[:empty.start()] + replacement + text[empty.end():]
|
||||
elif opened:
|
||||
if re.search(rf"<xr:Property>{re.escape(property_name)}</xr:Property>", opened.group(2)):
|
||||
return
|
||||
ind = opened.group(1)
|
||||
block = build_property_state_xml(property_name, ind + "\t")
|
||||
# Дописываем в КОНЕЦ InternalInfo: у Конфигуратора PropertyState идёт после GeneratedType.
|
||||
close_at = opened.end() - len("</InternalInfo>") - len(ind)
|
||||
text = text[:close_at] + block + nl + text[close_at:]
|
||||
else:
|
||||
return
|
||||
|
||||
with open(obj_file, "w", encoding="utf-8-sig", newline="") as fh:
|
||||
fh.write(text)
|
||||
|
||||
|
||||
def apply_pal_ns(format_version):
|
||||
"""2.21 (8.5) добавила в шапку пространство палитры — ради <Color> у значений перечисления.
|
||||
Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту,
|
||||
@@ -655,6 +730,7 @@ def main():
|
||||
parser.add_argument("-ConfigPath", required=True)
|
||||
parser.add_argument("-Object", required=True)
|
||||
parser.add_argument("-BorrowMainAttribute", nargs="?", const="Form", default=None)
|
||||
parser.add_argument("-Module", default=None)
|
||||
args = ci_parse_args(parser)
|
||||
|
||||
# --- 1. Resolve paths ---
|
||||
@@ -860,6 +936,25 @@ def main():
|
||||
sys.exit(1)
|
||||
return src_uuid
|
||||
|
||||
# --- Пустой модуль заимствованного объекта ---
|
||||
def new_borrowed_module_file(type_name, obj_name, module_kind):
|
||||
dir_name = CHILD_TYPE_DIR_MAP[type_name]
|
||||
module_dir = os.path.join(ext_dir, dir_name, obj_name, "Ext")
|
||||
os.makedirs(module_dir, exist_ok=True)
|
||||
|
||||
# NEVER overwrite an existing one: повторное заимствование не должно затирать дописанный
|
||||
# код (то же правило, что у модуля формы).
|
||||
module_file = os.path.join(module_dir, f"{module_kind}.bsl")
|
||||
if os.path.isfile(module_file):
|
||||
info(f" Preserved existing {module_kind}.bsl")
|
||||
else:
|
||||
write_utf8_bom(module_file, "")
|
||||
info(f" Created: {module_file}")
|
||||
|
||||
# Флаг ставим и для уже существовавшего файла: состояние объекта должно отражать факт модуля.
|
||||
set_property_state_flag(os.path.join(ext_dir, dir_name, f"{obj_name}.xml"), module_kind, format_version)
|
||||
return module_file
|
||||
|
||||
def build_internal_info_xml(type_name, obj_name, indent):
|
||||
types = GENERATED_TYPES.get(type_name)
|
||||
if not types:
|
||||
@@ -2019,6 +2114,47 @@ def main():
|
||||
print("-BorrowMainAttribute requires a form in -Object (e.g. 'Catalog.X.Form.Y')", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# --- 9c. Validate -Module ---
|
||||
requested_modules = []
|
||||
no_module = False
|
||||
if args.Module:
|
||||
for raw in re.split(r"[,;]", args.Module):
|
||||
kind = raw.strip()
|
||||
if not kind:
|
||||
continue
|
||||
# Сравнение РЕГИСТРОНЕЗАВИСИМОЕ явно: в ps1-порте `-ieq`, и молчаливое расхождение
|
||||
# портов на «none» ловится только глазами.
|
||||
if kind.lower() == "none":
|
||||
no_module = True
|
||||
continue
|
||||
canon = [k for k in MODULE_KIND_NAMES if k.lower() == kind.lower()]
|
||||
if not canon:
|
||||
print(f"Неизвестный вид модуля '{kind}'. Допустимо: {', '.join(MODULE_KIND_NAMES)}, None", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
requested_modules.append(canon[0])
|
||||
if no_module and requested_modules:
|
||||
print("-Module None нельзя сочетать с видами модулей", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Какие модули создать для объекта. Тип с единственным модулем получает его всегда — уточнять
|
||||
# там нечего; -Module разбирает только неоднозначные типы. Иначе батч смешанных типов
|
||||
# (`CommonModule.X ;; Catalog.Y`) не выражался бы одним вызовом.
|
||||
def resolve_module_kinds(type_name):
|
||||
if no_module:
|
||||
return []
|
||||
allowed = MODULE_KINDS_BY_TYPE.get(type_name, [])
|
||||
if not allowed:
|
||||
return []
|
||||
if type_name in AUTO_MODULE_TYPES:
|
||||
return [allowed[0]]
|
||||
if not requested_modules:
|
||||
return []
|
||||
# Порядок берём из таблицы типа, а не из порядка ключей в -Module.
|
||||
selected = [k for k in allowed if k in requested_modules]
|
||||
if not selected:
|
||||
warn(f" Тип {type_name} не имеет запрошенных модулей — пропущено. Допустимо: {', '.join(allowed)}")
|
||||
return selected
|
||||
|
||||
# --- 10. Process each item ---
|
||||
borrowed_count = 0
|
||||
|
||||
@@ -2070,6 +2206,9 @@ def main():
|
||||
has_bma = borrow_main_attribute_mode is not None
|
||||
form_files = borrow_form(type_name, obj_name, form_name, borrow_main_attr=has_bma)
|
||||
borrowed_files.extend(form_files)
|
||||
# Замер на 8.3.26: платформа помечает форму расширенной сразу при заимствовании,
|
||||
# даже если элементы не менялись. Флаг живёт в метаданных формы, не у владельца.
|
||||
set_property_state_flag(form_files[0], "Form", format_version)
|
||||
borrowed_count += 1
|
||||
|
||||
# Borrow main attribute if requested
|
||||
@@ -2077,23 +2216,32 @@ def main():
|
||||
borrow_main_attribute(type_name, obj_name, form_name, borrow_main_attribute_mode)
|
||||
else:
|
||||
# --- Object borrowing ---
|
||||
info(f"Borrowing {type_name}.{obj_name}...")
|
||||
|
||||
src = read_source_object(type_name, obj_name)
|
||||
info(f" Source UUID: {src['Uuid']}")
|
||||
|
||||
borrowed_xml = build_borrowed_object_xml(type_name, obj_name, src["Uuid"], src["Properties"])
|
||||
|
||||
target_dir = os.path.join(ext_dir, dir_name)
|
||||
os.makedirs(target_dir, exist_ok=True)
|
||||
|
||||
target_file = os.path.join(target_dir, f"{obj_name}.xml")
|
||||
write_xml_file(target_file, borrowed_xml)
|
||||
info(f" Created: {target_file}")
|
||||
|
||||
# Уже заимствованный объект НЕ переписываем: в его XML лежат собственные реквизиты
|
||||
# расширения, заимствованные подобъекты и состояния, которые из источника не
|
||||
# выводятся. Повторный вызов — законный способ доделать модуль (-Module), а не
|
||||
# переиздать заготовку.
|
||||
if test_object_borrowed(type_name, obj_name):
|
||||
info(f"Already borrowed: {type_name}.{obj_name} — XML сохранён без изменений")
|
||||
else:
|
||||
info(f"Borrowing {type_name}.{obj_name}...")
|
||||
|
||||
src = read_source_object(type_name, obj_name)
|
||||
info(f" Source UUID: {src['Uuid']}")
|
||||
|
||||
borrowed_xml = build_borrowed_object_xml(type_name, obj_name, src["Uuid"], src["Properties"])
|
||||
|
||||
os.makedirs(target_dir, exist_ok=True)
|
||||
write_xml_file(target_file, borrowed_xml)
|
||||
info(f" Created: {target_file}")
|
||||
|
||||
add_to_child_objects(type_name, obj_name)
|
||||
|
||||
borrowed_files.append(target_file)
|
||||
for kind in resolve_module_kinds(type_name):
|
||||
borrowed_files.append(new_borrowed_module_file(type_name, obj_name, kind))
|
||||
borrowed_count += 1
|
||||
|
||||
# --- Владельцы заимствованных справочников ---
|
||||
|
||||
Reference in New Issue
Block a user