mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-06-12 00:44:57 +03:00
Merge branch 'dev'
This commit is contained in:
@@ -38,7 +38,7 @@ allowed-tools:
|
||||
"user": "Admin",
|
||||
"password": "",
|
||||
"aliases": ["dev", "разработка"],
|
||||
"branches": ["dev", "develop"],
|
||||
"branches": ["dev", "develop", "feature/*"],
|
||||
"configSrc": "C:\\WS\\myapp\\cfsrc"
|
||||
},
|
||||
{
|
||||
@@ -77,22 +77,26 @@ allowed-tools:
|
||||
| `user` | string | нет | Имя пользователя 1С |
|
||||
| `password` | string | нет | Пароль |
|
||||
| `aliases` | string[] | нет | Альтернативные имена для быстрого доступа |
|
||||
| `branches` | string[] | нет | Git-ветки, привязанные к этой базе |
|
||||
| `branches` | string[] | нет | Git-ветки или glob-паттерны (`release/*`, `feature/*`), привязанные к этой базе |
|
||||
| `configSrc` | string | нет | Каталог XML-выгрузки конфигурации |
|
||||
|
||||
## Алгоритм разрешения базы данных
|
||||
|
||||
Этот алгоритм используется ВСЕМИ навыками `db-*` для определения целевой базы.
|
||||
Этот алгоритм используется ВСЕМИ навыками (`db-*`, `epf-build`, `epf-dump`, `erf-build`, `erf-dump`) для определения целевой базы.
|
||||
|
||||
1. Прочитай `.v8-project.json` (в корне проекта или ближайшем родительском каталоге)
|
||||
2. Если пользователь указал базу — ищи совпадение в таком порядке:
|
||||
1. Если пользователь указал **параметры подключения** (путь, сервер) — используй напрямую
|
||||
2. Если пользователь указал **базу по имени** — ищи совпадение в таком порядке:
|
||||
1. По `id` (точное совпадение)
|
||||
2. По `aliases` (совпадение в массиве с учётом морфологии: «тестовую» = «тестовая» = «тестовой»)
|
||||
3. По `branches` (совпадение с текущей Git-веткой)
|
||||
4. По `name` (нечёткое совпадение с учётом морфологии и регистра)
|
||||
3. Если пользователь не указал базу — используй `default`
|
||||
4. Если не найдено или неоднозначно — спроси пользователя
|
||||
5. Если файл `.v8-project.json` не найден — спроси параметры подключения и предложи создать файл
|
||||
3. По `name` (нечёткое совпадение с учётом морфологии и регистра)
|
||||
3. Если пользователь **не указал** базу — сопоставь текущую ветку Git с `databases[].branches`:
|
||||
- Точное совпадение: ветка `dev` → `"branches": ["dev"]`
|
||||
- Glob-паттерн: ветка `release/2.1` → `"branches": ["release/*"]`
|
||||
4. Если ветка не совпала — используй `default`
|
||||
5. Если не найдено или неоднозначно — спроси пользователя
|
||||
6. Если файл `.v8-project.json` не найден — спроси параметры подключения и предложи создать файл
|
||||
|
||||
После выполнения: если использованная база не зарегистрирована — предложи добавить через `/db-list add`.
|
||||
|
||||
### Автоопределение платформы
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
name: epf-validate
|
||||
description: Валидация внешней обработки 1С (EPF). Используй после создания или модификации обработки для проверки корректности
|
||||
argument-hint: <ObjectPath> [-MaxErrors 30]
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
- Glob
|
||||
---
|
||||
|
||||
# /epf-validate — валидация внешней обработки (EPF)
|
||||
|
||||
Проверяет структурную корректность XML-исходников внешней обработки: корневую структуру, InternalInfo, свойства, ChildObjects, реквизиты, табличные части, уникальность имён, наличие файлов форм и макетов.
|
||||
|
||||
Скрипт также работает для внешних отчётов (ERF) — автоопределение по типу элемента. См. `/erf-validate`.
|
||||
|
||||
## Использование
|
||||
|
||||
```
|
||||
/epf-validate <ObjectPath>
|
||||
```
|
||||
|
||||
## Параметры
|
||||
|
||||
| Параметр | Обязательный | По умолчанию | Описание |
|
||||
|------------|:------------:|--------------|-------------------------------------------------|
|
||||
| ObjectPath | да | — | Путь к корневому XML или каталогу обработки |
|
||||
| MaxErrors | нет | 30 | Остановиться после N ошибок |
|
||||
| OutFile | нет | — | Записать результат в файл (UTF-8 BOM) |
|
||||
|
||||
`ObjectPath` авторезолв: если указана директория — ищет `<dirName>/<dirName>.xml`.
|
||||
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude\skills\epf-validate\scripts\epf-validate.ps1 -ObjectPath "<путь>"
|
||||
```
|
||||
|
||||
## Выполняемые проверки
|
||||
|
||||
| # | Проверка | Серьёзность |
|
||||
|----|-------------------------------------------------------|--------------|
|
||||
| 1 | Root structure: MetaDataObject/ExternalDataProcessor | ERROR |
|
||||
| 2 | InternalInfo: ClassId, ContainedObject, GeneratedType | ERROR / WARN |
|
||||
| 3 | Properties: Name (identifier), Synonym | ERROR / WARN |
|
||||
| 4 | ChildObjects: допустимые типы, порядок | ERROR / WARN |
|
||||
| 5 | Cross-references: DefaultForm → Form, AuxiliaryForm | ERROR / WARN |
|
||||
| 6 | Attributes: UUID, Name, Type | ERROR |
|
||||
| 7 | TabularSections: UUID, Name, GeneratedType, Attributes | ERROR / WARN |
|
||||
| 8 | Уникальность имён (Attribute, TS, Form, Template, Command) | ERROR |
|
||||
| 9 | Файлы: формы (.xml + Ext/Form.xml), макеты | ERROR |
|
||||
| 10 | Дескрипторы форм: корневая структура, uuid, Name, FormType | ERROR / WARN |
|
||||
|
||||
## Вывод
|
||||
|
||||
```
|
||||
=== Validation: EPF.МояОбработка ===
|
||||
|
||||
[OK] 1. Root structure: MetaDataObject/ExternalDataProcessor, version 2.17
|
||||
[OK] 2. InternalInfo: ClassId correct, 1 GeneratedType
|
||||
[OK] 3. Properties: Name="МояОбработка", Synonym present, DefaultForm set
|
||||
[OK] 4. ChildObjects: Attribute(3), TabularSection(1), Form(1)
|
||||
[OK] 5. Cross-references: DefaultForm valid
|
||||
[OK] 6. Attributes: 3 checked (UUID, Name, Type)
|
||||
[OK] 7. TabularSections: 1 sections, 5 inner attributes
|
||||
[OK] 8. Name uniqueness: 6 names, all unique
|
||||
[OK] 9. File existence: 3 files verified
|
||||
[OK] 10. Form descriptors: 1 checked
|
||||
|
||||
=== Result: 0 errors, 0 warnings ===
|
||||
```
|
||||
|
||||
Код возврата: 0 = все проверки пройдены, 1 = есть ошибки.
|
||||
|
||||
## Верификация
|
||||
|
||||
```
|
||||
/epf-init <Name> — создать обработку
|
||||
/epf-validate src/<Name>.xml — проверить результат
|
||||
/epf-build <Name> — собрать EPF
|
||||
```
|
||||
|
||||
## Когда использовать
|
||||
|
||||
- **После `/epf-init`**: проверить scaffold
|
||||
- **После добавления формы/макета**: убедиться что ChildObjects, файлы и ссылки корректны
|
||||
- **После ручного редактирования XML**: выявить структурные ошибки до сборки
|
||||
- **При отладке сборки**: найти причину ошибки Designer
|
||||
@@ -0,0 +1,824 @@
|
||||
# epf-validate v1.0 — Validate 1C external data processor / report structure
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
# Works for both EPF (ExternalDataProcessor) and ERF (ExternalReport) — auto-detects
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$ObjectPath,
|
||||
|
||||
[int]$MaxErrors = 30,
|
||||
|
||||
[string]$OutFile
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
# --- Resolve path ---
|
||||
|
||||
if (-not [System.IO.Path]::IsPathRooted($ObjectPath)) {
|
||||
$ObjectPath = Join-Path (Get-Location).Path $ObjectPath
|
||||
}
|
||||
|
||||
if (Test-Path $ObjectPath -PathType Container) {
|
||||
$dirName = Split-Path $ObjectPath -Leaf
|
||||
$candidate = Join-Path $ObjectPath "$dirName.xml"
|
||||
if (Test-Path $candidate) {
|
||||
$ObjectPath = $candidate
|
||||
} else {
|
||||
$xmlFiles = @(Get-ChildItem $ObjectPath -Filter "*.xml" -File | Select-Object -First 1)
|
||||
if ($xmlFiles.Count -gt 0) {
|
||||
$ObjectPath = $xmlFiles[0].FullName
|
||||
} else {
|
||||
Write-Host "[ERROR] No XML file found in directory: $ObjectPath"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (-not (Test-Path $ObjectPath)) {
|
||||
Write-Host "[ERROR] File not found: $ObjectPath"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$resolvedPath = (Resolve-Path $ObjectPath).Path
|
||||
$srcDir = Split-Path $resolvedPath -Parent
|
||||
|
||||
# --- Output infrastructure ---
|
||||
|
||||
$script:errors = 0
|
||||
$script:warnings = 0
|
||||
$script:stopped = $false
|
||||
$script:output = New-Object System.Text.StringBuilder 8192
|
||||
|
||||
function Out-Line {
|
||||
param([string]$msg)
|
||||
$script:output.AppendLine($msg) | Out-Null
|
||||
}
|
||||
|
||||
function Report-OK {
|
||||
param([string]$msg)
|
||||
Out-Line "[OK] $msg"
|
||||
}
|
||||
|
||||
function Report-Error {
|
||||
param([string]$msg)
|
||||
$script:errors++
|
||||
Out-Line "[ERROR] $msg"
|
||||
if ($script:errors -ge $MaxErrors) {
|
||||
$script:stopped = $true
|
||||
}
|
||||
}
|
||||
|
||||
function Report-Warn {
|
||||
param([string]$msg)
|
||||
$script:warnings++
|
||||
Out-Line "[WARN] $msg"
|
||||
}
|
||||
|
||||
$finalize = {
|
||||
Out-Line ""
|
||||
Out-Line "=== Result: $($script:errors) errors, $($script:warnings) warnings ==="
|
||||
|
||||
$result = $script:output.ToString()
|
||||
Write-Host $result
|
||||
|
||||
if ($OutFile) {
|
||||
$utf8Bom = New-Object System.Text.UTF8Encoding $true
|
||||
[System.IO.File]::WriteAllText($OutFile, $result, $utf8Bom)
|
||||
Write-Host "Written to: $OutFile"
|
||||
}
|
||||
}
|
||||
|
||||
# --- Reference tables ---
|
||||
|
||||
$guidPattern = '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
|
||||
$identPattern = '^[A-Za-z\u0410-\u042F\u0401\u0430-\u044F\u0451_][A-Za-z0-9\u0410-\u042F\u0401\u0430-\u044F\u0451_]*$'
|
||||
|
||||
$classIds = @{
|
||||
"ExternalDataProcessor" = "c3831ec8-d8d5-4f93-8a22-f9bfae07327f"
|
||||
"ExternalReport" = "e41aff26-25cf-4bb6-b6c1-3f478a75f374"
|
||||
}
|
||||
|
||||
$allowedChildTypes = @("Attribute","TabularSection","Form","Template","Command")
|
||||
|
||||
# Expected order of child types in ChildObjects
|
||||
$childTypeOrder = @{
|
||||
"Attribute" = 0
|
||||
"TabularSection" = 1
|
||||
"Form" = 2
|
||||
"Template" = 3
|
||||
"Command" = 4
|
||||
}
|
||||
|
||||
$validPropertyValues = @{
|
||||
"FillChecking" = @("DontCheck","ShowError","ShowWarning")
|
||||
"Indexing" = @("DontIndex","Index","IndexWithAdditionalOrder")
|
||||
}
|
||||
|
||||
# --- 1. Parse XML ---
|
||||
|
||||
Out-Line ""
|
||||
|
||||
$xmlDoc = $null
|
||||
try {
|
||||
$xmlDoc = New-Object System.Xml.XmlDocument
|
||||
$xmlDoc.PreserveWhitespace = $false
|
||||
$xmlDoc.Load($resolvedPath)
|
||||
} catch {
|
||||
Out-Line "=== Validation: (parse failed) ==="
|
||||
Out-Line ""
|
||||
Report-Error "1. XML parse failed: $($_.Exception.Message)"
|
||||
& $finalize
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Register namespaces ---
|
||||
|
||||
$ns = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable)
|
||||
$ns.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
|
||||
$ns.AddNamespace("v8", "http://v8.1c.ru/8.1/data/core")
|
||||
$ns.AddNamespace("xr", "http://v8.1c.ru/8.3/xcf/readable")
|
||||
$ns.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance")
|
||||
$ns.AddNamespace("xs", "http://www.w3.org/2001/XMLSchema")
|
||||
$ns.AddNamespace("app", "http://v8.1c.ru/8.2/managed-application/core")
|
||||
|
||||
$root = $xmlDoc.DocumentElement
|
||||
|
||||
# --- Check 1: Root structure ---
|
||||
|
||||
$check1Ok = $true
|
||||
|
||||
if ($root.LocalName -ne "MetaDataObject") {
|
||||
Report-Error "1. Root element is '$($root.LocalName)', expected 'MetaDataObject'"
|
||||
& $finalize
|
||||
exit 1
|
||||
}
|
||||
|
||||
$expectedNs = "http://v8.1c.ru/8.3/MDClasses"
|
||||
if ($root.NamespaceURI -ne $expectedNs) {
|
||||
Report-Error "1. Root namespace is '$($root.NamespaceURI)', expected '$expectedNs'"
|
||||
$check1Ok = $false
|
||||
}
|
||||
|
||||
$version = $root.GetAttribute("version")
|
||||
if (-not $version) {
|
||||
Report-Warn "1. Missing version attribute on MetaDataObject"
|
||||
} elseif ($version -ne "2.17" -and $version -ne "2.20") {
|
||||
Report-Warn "1. Unusual version '$version' (expected 2.17 or 2.20)"
|
||||
}
|
||||
|
||||
# Detect type: ExternalDataProcessor or ExternalReport
|
||||
$typeNode = $null
|
||||
$mdType = ""
|
||||
$childElements = @()
|
||||
foreach ($child in $root.ChildNodes) {
|
||||
if ($child.NodeType -eq 'Element' -and $child.NamespaceURI -eq $expectedNs) {
|
||||
$childElements += $child
|
||||
}
|
||||
}
|
||||
|
||||
if ($childElements.Count -eq 0) {
|
||||
Report-Error "1. No metadata type element found inside MetaDataObject"
|
||||
& $finalize
|
||||
exit 1
|
||||
} elseif ($childElements.Count -gt 1) {
|
||||
Report-Error "1. Multiple type elements found: $($childElements | ForEach-Object { $_.LocalName })"
|
||||
$check1Ok = $false
|
||||
}
|
||||
|
||||
$typeNode = $childElements[0]
|
||||
$mdType = $typeNode.LocalName
|
||||
|
||||
if ($mdType -ne "ExternalDataProcessor" -and $mdType -ne "ExternalReport") {
|
||||
Report-Error "1. Unexpected type '$mdType' (expected ExternalDataProcessor or ExternalReport)"
|
||||
& $finalize
|
||||
exit 1
|
||||
}
|
||||
|
||||
$typeUuid = $typeNode.GetAttribute("uuid")
|
||||
if (-not $typeUuid) {
|
||||
Report-Error "1. Missing uuid on <$mdType>"
|
||||
$check1Ok = $false
|
||||
} elseif ($typeUuid -notmatch $guidPattern) {
|
||||
Report-Error "1. Invalid uuid '$typeUuid' on <$mdType>"
|
||||
$check1Ok = $false
|
||||
}
|
||||
|
||||
# Get object name
|
||||
$propsNode = $typeNode.SelectSingleNode("md:Properties", $ns)
|
||||
$nameNode = if ($propsNode) { $propsNode.SelectSingleNode("md:Name", $ns) } else { $null }
|
||||
$objName = if ($nameNode -and $nameNode.InnerText) { $nameNode.InnerText } else { "(unknown)" }
|
||||
|
||||
$shortType = if ($mdType -eq "ExternalDataProcessor") { "EPF" } else { "ERF" }
|
||||
$script:output.Insert(0, "=== Validation: $shortType.$objName ===$([Environment]::NewLine)") | Out-Null
|
||||
|
||||
if ($check1Ok) {
|
||||
Report-OK "1. Root structure: MetaDataObject/$mdType, version $version"
|
||||
}
|
||||
|
||||
if ($script:stopped) { & $finalize; exit 1 }
|
||||
|
||||
# --- Check 2: InternalInfo ---
|
||||
|
||||
$internalInfo = $typeNode.SelectSingleNode("md:InternalInfo", $ns)
|
||||
|
||||
if (-not $internalInfo) {
|
||||
Report-Error "2. InternalInfo block missing"
|
||||
} else {
|
||||
$check2Ok = $true
|
||||
|
||||
# ContainedObject / ClassId
|
||||
$containedObj = $internalInfo.SelectSingleNode("xr:ContainedObject", $ns)
|
||||
if (-not $containedObj) {
|
||||
Report-Error "2. InternalInfo: missing xr:ContainedObject"
|
||||
$check2Ok = $false
|
||||
} else {
|
||||
$classIdNode = $containedObj.SelectSingleNode("xr:ClassId", $ns)
|
||||
$objectIdNode = $containedObj.SelectSingleNode("xr:ObjectId", $ns)
|
||||
|
||||
$expectedClassId = $classIds[$mdType]
|
||||
if (-not $classIdNode -or -not $classIdNode.InnerText) {
|
||||
Report-Error "2. Missing ClassId in ContainedObject"
|
||||
$check2Ok = $false
|
||||
} elseif ($classIdNode.InnerText -ne $expectedClassId) {
|
||||
Report-Error "2. ClassId is '$($classIdNode.InnerText)', expected '$expectedClassId' for $mdType"
|
||||
$check2Ok = $false
|
||||
}
|
||||
|
||||
if ($objectIdNode -and $objectIdNode.InnerText -notmatch $guidPattern) {
|
||||
Report-Error "2. Invalid ObjectId UUID"
|
||||
$check2Ok = $false
|
||||
}
|
||||
}
|
||||
|
||||
# GeneratedType — expect exactly 1 with category "Object"
|
||||
$genTypes = $internalInfo.SelectNodes("xr:GeneratedType", $ns)
|
||||
if ($genTypes.Count -eq 0) {
|
||||
Report-Error "2. No GeneratedType entries found"
|
||||
$check2Ok = $false
|
||||
} else {
|
||||
foreach ($gt in $genTypes) {
|
||||
$gtName = $gt.GetAttribute("name")
|
||||
$gtCategory = $gt.GetAttribute("category")
|
||||
|
||||
if ($gtCategory -ne "Object") {
|
||||
Report-Warn "2. Unexpected GeneratedType category '$gtCategory' (expected 'Object')"
|
||||
}
|
||||
|
||||
# Name format: ExternalDataProcessorObject.Name or ExternalReportObject.Name
|
||||
$expectedPrefix = "${mdType}Object."
|
||||
if ($gtName -and $objName -ne "(unknown)" -and -not $gtName.StartsWith($expectedPrefix)) {
|
||||
Report-Warn "2. GeneratedType name '$gtName' does not start with '$expectedPrefix'"
|
||||
}
|
||||
|
||||
$typeId = $gt.SelectSingleNode("xr:TypeId", $ns)
|
||||
$valueId = $gt.SelectSingleNode("xr:ValueId", $ns)
|
||||
if ($typeId -and $typeId.InnerText -notmatch $guidPattern) {
|
||||
Report-Error "2. Invalid TypeId UUID in GeneratedType"
|
||||
$check2Ok = $false
|
||||
}
|
||||
if ($valueId -and $valueId.InnerText -notmatch $guidPattern) {
|
||||
Report-Error "2. Invalid ValueId UUID in GeneratedType"
|
||||
$check2Ok = $false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($check2Ok) {
|
||||
Report-OK "2. InternalInfo: ClassId correct, $($genTypes.Count) GeneratedType"
|
||||
}
|
||||
}
|
||||
|
||||
if ($script:stopped) { & $finalize; exit 1 }
|
||||
|
||||
# --- Check 3: Properties ---
|
||||
|
||||
if (-not $propsNode) {
|
||||
Report-Error "3. Properties block missing"
|
||||
} else {
|
||||
$check3Ok = $true
|
||||
|
||||
# Name
|
||||
if (-not $nameNode -or -not $nameNode.InnerText) {
|
||||
Report-Error "3. Properties: Name is missing or empty"
|
||||
$check3Ok = $false
|
||||
} else {
|
||||
$nameVal = $nameNode.InnerText
|
||||
if ($nameVal -notmatch $identPattern) {
|
||||
Report-Error "3. Properties: Name '$nameVal' is not a valid 1C identifier"
|
||||
$check3Ok = $false
|
||||
}
|
||||
if ($nameVal.Length -gt 80) {
|
||||
Report-Warn "3. Properties: Name '$nameVal' exceeds 80 characters ($($nameVal.Length))"
|
||||
}
|
||||
}
|
||||
|
||||
# Synonym
|
||||
$synNode = $propsNode.SelectSingleNode("md:Synonym", $ns)
|
||||
$synPresent = $false
|
||||
if ($synNode) {
|
||||
$synItem = $synNode.SelectSingleNode("v8:item", $ns)
|
||||
if ($synItem) {
|
||||
$synContent = $synItem.SelectSingleNode("v8:content", $ns)
|
||||
if ($synContent -and $synContent.InnerText) {
|
||||
$synPresent = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# DefaultForm cross-reference (collected now, checked after ChildObjects)
|
||||
$defaultFormNode = $propsNode.SelectSingleNode("md:DefaultForm", $ns)
|
||||
$defaultFormVal = if ($defaultFormNode -and $defaultFormNode.InnerText.Trim()) { $defaultFormNode.InnerText.Trim() } else { "" }
|
||||
|
||||
# AuxiliaryForm cross-reference
|
||||
$auxFormNode = $propsNode.SelectSingleNode("md:AuxiliaryForm", $ns)
|
||||
$auxFormVal = if ($auxFormNode -and $auxFormNode.InnerText.Trim()) { $auxFormNode.InnerText.Trim() } else { "" }
|
||||
|
||||
# ERF-specific: MainDataCompositionSchema
|
||||
$mainDCSVal = ""
|
||||
if ($mdType -eq "ExternalReport") {
|
||||
$mainDCSNode = $propsNode.SelectSingleNode("md:MainDataCompositionSchema", $ns)
|
||||
$mainDCSVal = if ($mainDCSNode -and $mainDCSNode.InnerText.Trim()) { $mainDCSNode.InnerText.Trim() } else { "" }
|
||||
}
|
||||
|
||||
if ($check3Ok) {
|
||||
$synInfo = if ($synPresent) { "Synonym present" } else { "no Synonym" }
|
||||
$extras = ""
|
||||
if ($defaultFormVal) { $extras += ", DefaultForm set" }
|
||||
if ($mainDCSVal) { $extras += ", MainDCS set" }
|
||||
Report-OK "3. Properties: Name=`"$objName`", $synInfo$extras"
|
||||
}
|
||||
}
|
||||
|
||||
if ($script:stopped) { & $finalize; exit 1 }
|
||||
|
||||
# --- Check 4: ChildObjects — allowed types and ordering ---
|
||||
|
||||
$childObjNode = $typeNode.SelectSingleNode("md:ChildObjects", $ns)
|
||||
$formNames = @()
|
||||
$templateNames = @()
|
||||
$allChildNames = @{}
|
||||
|
||||
if ($childObjNode) {
|
||||
$check4Ok = $true
|
||||
$childCounts = @{}
|
||||
$lastOrder = -1
|
||||
$orderOk = $true
|
||||
|
||||
foreach ($child in $childObjNode.ChildNodes) {
|
||||
if ($child.NodeType -ne 'Element') { continue }
|
||||
$childTag = $child.LocalName
|
||||
|
||||
if ($allowedChildTypes -notcontains $childTag) {
|
||||
Report-Error "4. ChildObjects: disallowed element '$childTag'"
|
||||
$check4Ok = $false
|
||||
continue
|
||||
}
|
||||
|
||||
if (-not $childCounts.ContainsKey($childTag)) {
|
||||
$childCounts[$childTag] = 0
|
||||
}
|
||||
$childCounts[$childTag]++
|
||||
|
||||
# Check ordering
|
||||
$thisOrder = $childTypeOrder[$childTag]
|
||||
if ($thisOrder -lt $lastOrder -and $orderOk) {
|
||||
Report-Warn "4. ChildObjects: '$childTag' appears after higher-order elements (expected: Attribute, TabularSection, Form, Template, Command)"
|
||||
$orderOk = $false
|
||||
}
|
||||
$lastOrder = $thisOrder
|
||||
|
||||
# Collect Form and Template names (simple text content)
|
||||
if ($childTag -eq "Form") {
|
||||
$formNames += $child.InnerText.Trim()
|
||||
} elseif ($childTag -eq "Template") {
|
||||
$templateNames += $child.InnerText.Trim()
|
||||
}
|
||||
}
|
||||
|
||||
if ($check4Ok) {
|
||||
$summary = ($childCounts.GetEnumerator() | Sort-Object { $childTypeOrder[$_.Name] } | ForEach-Object { "$($_.Name)($($_.Value))" }) -join ", "
|
||||
if ($summary) {
|
||||
Report-OK "4. ChildObjects: $summary"
|
||||
} else {
|
||||
Report-OK "4. ChildObjects: empty"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Report-OK "4. ChildObjects: absent"
|
||||
}
|
||||
|
||||
if ($script:stopped) { & $finalize; exit 1 }
|
||||
|
||||
# --- Check 5: DefaultForm / MainDCS cross-references ---
|
||||
|
||||
$check5Ok = $true
|
||||
|
||||
if ($defaultFormVal) {
|
||||
# Format: ExternalDataProcessor.Name.Form.FormName or ExternalReport.Name.Form.FormName
|
||||
$expectedPrefix = "$mdType.$objName.Form."
|
||||
if ($defaultFormVal.StartsWith($expectedPrefix)) {
|
||||
$refFormName = $defaultFormVal.Substring($expectedPrefix.Length)
|
||||
if ($formNames -notcontains $refFormName) {
|
||||
Report-Error "5. DefaultForm references '$refFormName', but no such Form in ChildObjects"
|
||||
$check5Ok = $false
|
||||
}
|
||||
} else {
|
||||
Report-Warn "5. DefaultForm value '$defaultFormVal' has unexpected prefix (expected '$expectedPrefix...')"
|
||||
}
|
||||
}
|
||||
|
||||
if ($auxFormVal) {
|
||||
$expectedPrefix = "$mdType.$objName.Form."
|
||||
if ($auxFormVal.StartsWith($expectedPrefix)) {
|
||||
$refFormName = $auxFormVal.Substring($expectedPrefix.Length)
|
||||
if ($formNames -notcontains $refFormName) {
|
||||
Report-Error "5. AuxiliaryForm references '$refFormName', but no such Form in ChildObjects"
|
||||
$check5Ok = $false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($mainDCSVal -and $mdType -eq "ExternalReport") {
|
||||
$expectedPrefix = "ExternalReport.$objName.Template."
|
||||
if ($mainDCSVal.StartsWith($expectedPrefix)) {
|
||||
$refTplName = $mainDCSVal.Substring($expectedPrefix.Length)
|
||||
if ($templateNames -notcontains $refTplName) {
|
||||
Report-Error "5. MainDataCompositionSchema references '$refTplName', but no such Template in ChildObjects"
|
||||
$check5Ok = $false
|
||||
}
|
||||
} else {
|
||||
Report-Warn "5. MainDataCompositionSchema value '$mainDCSVal' has unexpected prefix"
|
||||
}
|
||||
}
|
||||
|
||||
if ($check5Ok) {
|
||||
$refs = @()
|
||||
if ($defaultFormVal) { $refs += "DefaultForm" }
|
||||
if ($auxFormVal) { $refs += "AuxiliaryForm" }
|
||||
if ($mainDCSVal) { $refs += "MainDCS" }
|
||||
if ($refs.Count -gt 0) {
|
||||
Report-OK "5. Cross-references: $($refs -join ', ') valid"
|
||||
} else {
|
||||
Report-OK "5. Cross-references: none to check"
|
||||
}
|
||||
}
|
||||
|
||||
if ($script:stopped) { & $finalize; exit 1 }
|
||||
|
||||
# --- Check 6: Attributes — UUID, Name, Type ---
|
||||
|
||||
function Check-Attribute {
|
||||
param(
|
||||
[System.Xml.XmlNode]$node,
|
||||
[string]$context
|
||||
)
|
||||
|
||||
$uuid = $node.GetAttribute("uuid")
|
||||
if (-not $uuid) {
|
||||
Report-Error "6. $context Attribute missing uuid"
|
||||
return $false
|
||||
} elseif ($uuid -notmatch $guidPattern) {
|
||||
Report-Error "6. $context Attribute has invalid uuid '$uuid'"
|
||||
return $false
|
||||
}
|
||||
|
||||
$elProps = $node.SelectSingleNode("md:Properties", $ns)
|
||||
if (-not $elProps) {
|
||||
Report-Error "6. $context Attribute (uuid=$uuid) missing Properties"
|
||||
return $false
|
||||
}
|
||||
|
||||
$elName = $elProps.SelectSingleNode("md:Name", $ns)
|
||||
if (-not $elName -or -not $elName.InnerText) {
|
||||
Report-Error "6. $context Attribute (uuid=$uuid) missing or empty Name"
|
||||
return $false
|
||||
}
|
||||
|
||||
$nameVal = $elName.InnerText
|
||||
if ($nameVal -notmatch $identPattern) {
|
||||
Report-Error "6. $context Attribute '$nameVal' has invalid identifier"
|
||||
return $false
|
||||
}
|
||||
|
||||
$typeEl = $elProps.SelectSingleNode("md:Type", $ns)
|
||||
if (-not $typeEl) {
|
||||
Report-Error "6. $context Attribute '$nameVal' missing Type block"
|
||||
return $false
|
||||
}
|
||||
$v8Types = $typeEl.SelectNodes("v8:Type", $ns)
|
||||
$v8TypeSets = $typeEl.SelectNodes("v8:TypeSet", $ns)
|
||||
if ($v8Types.Count -eq 0 -and $v8TypeSets.Count -eq 0) {
|
||||
Report-Error "6. $context Attribute '$nameVal' Type block has no v8:Type or v8:TypeSet"
|
||||
return $false
|
||||
}
|
||||
|
||||
return $true
|
||||
}
|
||||
|
||||
if ($childObjNode) {
|
||||
$attrs = $childObjNode.SelectNodes("md:Attribute", $ns)
|
||||
$check6Ok = $true
|
||||
$attrCount = 0
|
||||
|
||||
foreach ($attr in $attrs) {
|
||||
if ($script:stopped) { break }
|
||||
$ok = Check-Attribute -node $attr -context ""
|
||||
if (-not $ok) { $check6Ok = $false }
|
||||
$attrCount++
|
||||
|
||||
# Collect name for uniqueness
|
||||
$ap = $attr.SelectSingleNode("md:Properties/md:Name", $ns)
|
||||
if ($ap -and $ap.InnerText) {
|
||||
$allChildNames["Attr:$($ap.InnerText)"] = $ap.InnerText
|
||||
}
|
||||
}
|
||||
|
||||
if ($attrCount -gt 0) {
|
||||
if ($check6Ok) {
|
||||
Report-OK "6. Attributes: $attrCount checked (UUID, Name, Type)"
|
||||
}
|
||||
} else {
|
||||
Report-OK "6. Attributes: none"
|
||||
}
|
||||
} else {
|
||||
Report-OK "6. Attributes: N/A"
|
||||
}
|
||||
|
||||
if ($script:stopped) { & $finalize; exit 1 }
|
||||
|
||||
# --- Check 7: TabularSections ---
|
||||
|
||||
if ($childObjNode) {
|
||||
$tsSections = $childObjNode.SelectNodes("md:TabularSection", $ns)
|
||||
if ($tsSections.Count -gt 0) {
|
||||
$check7Ok = $true
|
||||
$tsCount = 0
|
||||
$tsAttrTotal = 0
|
||||
|
||||
foreach ($ts in $tsSections) {
|
||||
if ($script:stopped) { break }
|
||||
$tsCount++
|
||||
|
||||
$tsUuid = $ts.GetAttribute("uuid")
|
||||
if (-not $tsUuid -or $tsUuid -notmatch $guidPattern) {
|
||||
Report-Error "7. TabularSection #${tsCount}: invalid or missing uuid"
|
||||
$check7Ok = $false
|
||||
}
|
||||
|
||||
$tsProps = $ts.SelectSingleNode("md:Properties", $ns)
|
||||
$tsNameNode = if ($tsProps) { $tsProps.SelectSingleNode("md:Name", $ns) } else { $null }
|
||||
$tsName = if ($tsNameNode -and $tsNameNode.InnerText) { $tsNameNode.InnerText } else { "(unnamed)" }
|
||||
|
||||
if (-not $tsNameNode -or -not $tsNameNode.InnerText) {
|
||||
Report-Error "7. TabularSection #${tsCount}: missing or empty Name"
|
||||
$check7Ok = $false
|
||||
} elseif ($tsName -notmatch $identPattern) {
|
||||
Report-Error "7. TabularSection '$tsName': invalid identifier"
|
||||
$check7Ok = $false
|
||||
}
|
||||
|
||||
$allChildNames["TS:$tsName"] = $tsName
|
||||
|
||||
# InternalInfo — expect 2 GeneratedType
|
||||
$tsIntInfo = $ts.SelectSingleNode("md:InternalInfo", $ns)
|
||||
if ($tsIntInfo) {
|
||||
$tsGens = $tsIntInfo.SelectNodes("xr:GeneratedType", $ns)
|
||||
if ($tsGens.Count -lt 2) {
|
||||
Report-Warn "7. TabularSection '$tsName': expected 2 GeneratedType, found $($tsGens.Count)"
|
||||
}
|
||||
}
|
||||
|
||||
# Inner attributes
|
||||
$tsChildObj = $ts.SelectSingleNode("md:ChildObjects", $ns)
|
||||
if ($tsChildObj) {
|
||||
$tsAttrs = $tsChildObj.SelectNodes("md:Attribute", $ns)
|
||||
$tsAttrNames = @{}
|
||||
foreach ($ta in $tsAttrs) {
|
||||
$taOk = Check-Attribute -node $ta -context "TabularSection '$tsName'."
|
||||
if (-not $taOk) { $check7Ok = $false }
|
||||
$tsAttrTotal++
|
||||
|
||||
$taProps = $ta.SelectSingleNode("md:Properties/md:Name", $ns)
|
||||
if ($taProps -and $taProps.InnerText) {
|
||||
if ($tsAttrNames.ContainsKey($taProps.InnerText)) {
|
||||
Report-Error "7. Duplicate attribute '$($taProps.InnerText)' in TabularSection '$tsName'"
|
||||
$check7Ok = $false
|
||||
} else {
|
||||
$tsAttrNames[$taProps.InnerText] = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($check7Ok) {
|
||||
Report-OK "7. TabularSections: $tsCount sections, $tsAttrTotal inner attributes"
|
||||
}
|
||||
} else {
|
||||
Report-OK "7. TabularSections: none"
|
||||
}
|
||||
} else {
|
||||
Report-OK "7. TabularSections: N/A"
|
||||
}
|
||||
|
||||
if ($script:stopped) { & $finalize; exit 1 }
|
||||
|
||||
# --- Check 8: Name uniqueness ---
|
||||
|
||||
$check8Ok = $true
|
||||
|
||||
# Collect all names: attributes + tabular sections + forms + templates + commands
|
||||
$allNames = @{}
|
||||
|
||||
if ($childObjNode) {
|
||||
$nameKinds = @(
|
||||
@{ XPath = "md:Attribute"; Kind = "Attribute" },
|
||||
@{ XPath = "md:TabularSection"; Kind = "TabularSection" },
|
||||
@{ XPath = "md:Command"; Kind = "Command" }
|
||||
)
|
||||
|
||||
foreach ($nk in $nameKinds) {
|
||||
$nodes = $childObjNode.SelectNodes($nk.XPath, $ns)
|
||||
foreach ($node in $nodes) {
|
||||
$np = $node.SelectSingleNode("md:Properties/md:Name", $ns)
|
||||
if ($np -and $np.InnerText) {
|
||||
$nameVal = $np.InnerText
|
||||
$key = "$($nk.Kind):$nameVal"
|
||||
if ($allNames.ContainsKey($nameVal)) {
|
||||
Report-Error "8. Duplicate name '$nameVal' ($($nk.Kind) conflicts with $($allNames[$nameVal]))"
|
||||
$check8Ok = $false
|
||||
} else {
|
||||
$allNames[$nameVal] = $nk.Kind
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Forms and Templates are simple text nodes
|
||||
foreach ($fn in $formNames) {
|
||||
if ($allNames.ContainsKey($fn)) {
|
||||
Report-Error "8. Duplicate name '$fn' (Form conflicts with $($allNames[$fn]))"
|
||||
$check8Ok = $false
|
||||
} else {
|
||||
$allNames[$fn] = "Form"
|
||||
}
|
||||
}
|
||||
foreach ($tn in $templateNames) {
|
||||
if ($allNames.ContainsKey($tn)) {
|
||||
Report-Error "8. Duplicate name '$tn' (Template conflicts with $($allNames[$tn]))"
|
||||
$check8Ok = $false
|
||||
} else {
|
||||
$allNames[$tn] = "Template"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($check8Ok) {
|
||||
Report-OK "8. Name uniqueness: $($allNames.Count) names, all unique"
|
||||
}
|
||||
|
||||
if ($script:stopped) { & $finalize; exit 1 }
|
||||
|
||||
# --- Check 9: File existence (forms and templates on disk) ---
|
||||
|
||||
$check9Ok = $true
|
||||
$filesChecked = 0
|
||||
|
||||
# Object directory: same level as root XML, named after the object
|
||||
$objDir = Join-Path $srcDir $objName
|
||||
|
||||
foreach ($fn in $formNames) {
|
||||
# FormName.xml — form descriptor
|
||||
$formMetaXml = Join-Path (Join-Path $objDir "Forms") "$fn.xml"
|
||||
if (-not (Test-Path $formMetaXml)) {
|
||||
Report-Error "9. Missing form descriptor: Forms/$fn.xml"
|
||||
$check9Ok = $false
|
||||
} else {
|
||||
$filesChecked++
|
||||
}
|
||||
|
||||
# FormName/Ext/Form.xml — form layout
|
||||
$formXml = Join-Path (Join-Path (Join-Path (Join-Path $objDir "Forms") $fn) "Ext") "Form.xml"
|
||||
if (-not (Test-Path $formXml)) {
|
||||
Report-Error "9. Missing form layout: Forms/$fn/Ext/Form.xml"
|
||||
$check9Ok = $false
|
||||
} else {
|
||||
$filesChecked++
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($tn in $templateNames) {
|
||||
# TemplateName.xml — template descriptor
|
||||
$tplMetaXml = Join-Path (Join-Path $objDir "Templates") "$tn.xml"
|
||||
if (-not (Test-Path $tplMetaXml)) {
|
||||
Report-Error "9. Missing template descriptor: Templates/$tn.xml"
|
||||
$check9Ok = $false
|
||||
} else {
|
||||
$filesChecked++
|
||||
}
|
||||
|
||||
# TemplateName/Ext/Template.* — template content (extension varies)
|
||||
$tplExtDir = Join-Path (Join-Path (Join-Path $objDir "Templates") $tn) "Ext"
|
||||
if (Test-Path $tplExtDir) {
|
||||
$tplFiles = @(Get-ChildItem $tplExtDir -Filter "Template.*" -File)
|
||||
if ($tplFiles.Count -eq 0) {
|
||||
Report-Error "9. Missing template content: Templates/$tn/Ext/Template.*"
|
||||
$check9Ok = $false
|
||||
} else {
|
||||
$filesChecked++
|
||||
}
|
||||
} else {
|
||||
Report-Error "9. Missing template Ext directory: Templates/$tn/Ext/"
|
||||
$check9Ok = $false
|
||||
}
|
||||
}
|
||||
|
||||
# ObjectModule.bsl
|
||||
$objModule = Join-Path (Join-Path $objDir "Ext") "ObjectModule.bsl"
|
||||
if (Test-Path $objModule) {
|
||||
$filesChecked++
|
||||
}
|
||||
|
||||
if ($check9Ok) {
|
||||
if ($filesChecked -gt 0) {
|
||||
Report-OK "9. File existence: $filesChecked files verified"
|
||||
} else {
|
||||
Report-OK "9. File existence: no forms/templates to check"
|
||||
}
|
||||
}
|
||||
|
||||
if ($script:stopped) { & $finalize; exit 1 }
|
||||
|
||||
# --- Check 10: Form descriptors structure ---
|
||||
|
||||
$check10Ok = $true
|
||||
$formsChecked = 0
|
||||
|
||||
foreach ($fn in $formNames) {
|
||||
$formMetaXml = Join-Path (Join-Path $objDir "Forms") "$fn.xml"
|
||||
if (-not (Test-Path $formMetaXml)) { continue }
|
||||
|
||||
try {
|
||||
$fDoc = New-Object System.Xml.XmlDocument
|
||||
$fDoc.PreserveWhitespace = $false
|
||||
$fDoc.Load($formMetaXml)
|
||||
$fRoot = $fDoc.DocumentElement
|
||||
|
||||
if ($fRoot.LocalName -ne "MetaDataObject") {
|
||||
Report-Error "10. Form '$fn': root element is '$($fRoot.LocalName)', expected 'MetaDataObject'"
|
||||
$check10Ok = $false
|
||||
continue
|
||||
}
|
||||
|
||||
$fTypeNode = $fRoot.SelectSingleNode("md:Form", $ns)
|
||||
if (-not $fTypeNode) {
|
||||
Report-Error "10. Form '$fn': missing <Form> element"
|
||||
$check10Ok = $false
|
||||
continue
|
||||
}
|
||||
|
||||
$fUuid = $fTypeNode.GetAttribute("uuid")
|
||||
if (-not $fUuid -or $fUuid -notmatch $guidPattern) {
|
||||
Report-Error "10. Form '$fn': invalid or missing uuid"
|
||||
$check10Ok = $false
|
||||
}
|
||||
|
||||
$fProps = $fTypeNode.SelectSingleNode("md:Properties", $ns)
|
||||
if ($fProps) {
|
||||
$fName = $fProps.SelectSingleNode("md:Name", $ns)
|
||||
if ($fName -and $fName.InnerText -ne $fn) {
|
||||
Report-Error "10. Form '$fn': Name in descriptor is '$($fName.InnerText)', expected '$fn'"
|
||||
$check10Ok = $false
|
||||
}
|
||||
|
||||
# FormType should be Managed
|
||||
$fType = $fProps.SelectSingleNode("md:FormType", $ns)
|
||||
if ($fType -and $fType.InnerText -ne "Managed") {
|
||||
Report-Warn "10. Form '$fn': FormType is '$($fType.InnerText)' (expected 'Managed')"
|
||||
}
|
||||
}
|
||||
|
||||
$formsChecked++
|
||||
} catch {
|
||||
Report-Error "10. Form '$fn': XML parse error: $($_.Exception.Message)"
|
||||
$check10Ok = $false
|
||||
}
|
||||
}
|
||||
|
||||
if ($check10Ok) {
|
||||
if ($formsChecked -gt 0) {
|
||||
Report-OK "10. Form descriptors: $formsChecked checked"
|
||||
} else {
|
||||
Report-OK "10. Form descriptors: none to check"
|
||||
}
|
||||
}
|
||||
|
||||
# --- Final output ---
|
||||
|
||||
& $finalize
|
||||
|
||||
if ($script:errors -gt 0) {
|
||||
exit 1
|
||||
}
|
||||
exit 0
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
name: erf-validate
|
||||
description: Валидация внешнего отчёта 1С (ERF). Используй после создания или модификации отчёта для проверки корректности
|
||||
argument-hint: <ObjectPath> [-MaxErrors 30]
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
- Glob
|
||||
---
|
||||
|
||||
# /erf-validate — валидация внешнего отчёта (ERF)
|
||||
|
||||
Проверяет структурную корректность XML-исходников внешнего отчёта: корневую структуру, InternalInfo, свойства (включая MainDataCompositionSchema), ChildObjects, реквизиты, табличные части, уникальность имён, наличие файлов форм и макетов.
|
||||
|
||||
Использует тот же скрипт, что и `/epf-validate` — автоопределение по типу элемента (ExternalReport).
|
||||
|
||||
## Использование
|
||||
|
||||
```
|
||||
/erf-validate <ObjectPath>
|
||||
```
|
||||
|
||||
## Параметры
|
||||
|
||||
| Параметр | Обязательный | По умолчанию | Описание |
|
||||
|------------|:------------:|--------------|-------------------------------------------------|
|
||||
| ObjectPath | да | — | Путь к корневому XML или каталогу отчёта |
|
||||
| MaxErrors | нет | 30 | Остановиться после N ошибок |
|
||||
| OutFile | нет | — | Записать результат в файл (UTF-8 BOM) |
|
||||
|
||||
`ObjectPath` авторезолв: если указана директория — ищет `<dirName>/<dirName>.xml`.
|
||||
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude\skills\epf-validate\scripts\epf-validate.ps1 -ObjectPath "<путь>"
|
||||
```
|
||||
|
||||
## Выполняемые проверки
|
||||
|
||||
| # | Проверка | Серьёзность |
|
||||
|----|-------------------------------------------------------|--------------|
|
||||
| 1 | Root structure: MetaDataObject/ExternalReport | ERROR |
|
||||
| 2 | InternalInfo: ClassId, ContainedObject, GeneratedType | ERROR / WARN |
|
||||
| 3 | Properties: Name, Synonym, MainDataCompositionSchema | ERROR / WARN |
|
||||
| 4 | ChildObjects: допустимые типы, порядок | ERROR / WARN |
|
||||
| 5 | Cross-references: DefaultForm, MainDCS → Template | ERROR / WARN |
|
||||
| 6 | Attributes: UUID, Name, Type | ERROR |
|
||||
| 7 | TabularSections: UUID, Name, GeneratedType, Attributes | ERROR / WARN |
|
||||
| 8 | Уникальность имён (Attribute, TS, Form, Template, Command) | ERROR |
|
||||
| 9 | Файлы: формы (.xml + Ext/Form.xml), макеты | ERROR |
|
||||
| 10 | Дескрипторы форм: корневая структура, uuid, Name, FormType | ERROR / WARN |
|
||||
|
||||
## Вывод
|
||||
|
||||
```
|
||||
=== Validation: ERF.МойОтчёт ===
|
||||
|
||||
[OK] 1. Root structure: MetaDataObject/ExternalReport, version 2.17
|
||||
[OK] 2. InternalInfo: ClassId correct, 1 GeneratedType
|
||||
[OK] 3. Properties: Name="МойОтчёт", Synonym present, MainDCS set
|
||||
[OK] 4. ChildObjects: Form(1), Template(1)
|
||||
[OK] 5. Cross-references: DefaultForm, MainDCS valid
|
||||
[OK] 6. Attributes: none
|
||||
[OK] 7. TabularSections: none
|
||||
[OK] 8. Name uniqueness: 2 names, all unique
|
||||
[OK] 9. File existence: 4 files verified
|
||||
[OK] 10. Form descriptors: 1 checked
|
||||
|
||||
=== Result: 0 errors, 0 warnings ===
|
||||
```
|
||||
|
||||
Код возврата: 0 = все проверки пройдены, 1 = есть ошибки.
|
||||
|
||||
## Верификация
|
||||
|
||||
```
|
||||
/erf-init <Name> --with-skd — создать отчёт с СКД
|
||||
/erf-validate src/<Name>.xml — проверить результат
|
||||
/erf-build <Name> — собрать ERF
|
||||
```
|
||||
|
||||
## Когда использовать
|
||||
|
||||
- **После `/erf-init`**: проверить scaffold
|
||||
- **После добавления формы/макета/СКД**: убедиться что ChildObjects и MainDCS корректны
|
||||
- **После ручного редактирования XML**: выявить структурные ошибки до сборки
|
||||
- **При отладке сборки**: найти причину ошибки Designer
|
||||
@@ -0,0 +1,136 @@
|
||||
---
|
||||
name: meta-remove
|
||||
description: Удалить объект метаданных из конфигурации 1С. Используй когда пользователь просит удалить, убрать объект из конфигурации
|
||||
argument-hint: <ConfigDir> -Object <Type.Name>
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
- Glob
|
||||
- AskUserQuestion
|
||||
---
|
||||
|
||||
# /meta-remove — удаление объекта метаданных
|
||||
|
||||
Безопасно удаляет объект из XML-выгрузки конфигурации. Перед удалением проверяет ссылки на объект в реквизитах, коде и других метаданных. Если ссылки найдены — удаление блокируется.
|
||||
|
||||
## Использование
|
||||
|
||||
```
|
||||
/meta-remove <ConfigDir> -Object <Type.Name>
|
||||
```
|
||||
|
||||
## Параметры
|
||||
|
||||
| Параметр | Обязательный | Описание |
|
||||
|------------|:------------:|-------------------------------------------------|
|
||||
| ConfigDir | да | Корневая директория выгрузки (где Configuration.xml) |
|
||||
| Object | да | Тип и имя объекта: `Catalog.Товары`, `Document.Заказ` и т.д. |
|
||||
| DryRun | нет | Только показать что будет удалено, без изменений |
|
||||
| KeepFiles | нет | Не удалять файлы, только дерегистрировать |
|
||||
| Force | нет | Удалить несмотря на найденные ссылки |
|
||||
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude\skills\meta-remove\scripts\meta-remove.ps1 -ConfigDir "<путь>" -Object "Catalog.Товары"
|
||||
```
|
||||
|
||||
## Что делает
|
||||
|
||||
1. **Находит файлы объекта**: `{TypePlural}/{Name}.xml` и `{TypePlural}/{Name}/`
|
||||
2. **Проверяет ссылки** (блокирует при наличии, если нет `-Force`):
|
||||
- XML-типы в реквизитах других объектов: `CatalogRef.Имя`, `DocumentRef.Имя` и т.д.
|
||||
- BSL-код: `Справочники.Имя`, `Catalogs.Имя`, вызовы общих модулей
|
||||
- Журналы документов, подписки на события, определяемые типы
|
||||
3. **Удаляет из Configuration.xml**: убирает из `<ChildObjects>`
|
||||
4. **Очищает подсистемы**: рекурсивно удаляет из `<Content>`
|
||||
5. **Удаляет файлы**: XML-файл и каталог объекта
|
||||
|
||||
## Поддерживаемые типы
|
||||
|
||||
Catalog, Document, Enum, Constant, InformationRegister, AccumulationRegister, AccountingRegister, CalculationRegister, ChartOfAccounts, ChartOfCharacteristicTypes, ChartOfCalculationTypes, BusinessProcess, Task, ExchangePlan, DocumentJournal, Report, DataProcessor, CommonModule, ScheduledJob, EventSubscription, HTTPService, WebService, DefinedType, Role, Subsystem, CommonForm, CommonTemplate, CommonPicture, CommonAttribute, SessionParameter, FunctionalOption, FunctionalOptionsParameter, Sequence, FilterCriterion, SettingsStorage, XDTOPackage, WSReference, StyleItem, Language
|
||||
|
||||
## Вывод (объект без ссылок)
|
||||
|
||||
```
|
||||
=== meta-remove: Catalog.Устаревший ===
|
||||
|
||||
[FOUND] Catalogs/Устаревший.xml
|
||||
[FOUND] Catalogs/Устаревший/ (8 files)
|
||||
|
||||
--- Reference check ---
|
||||
[OK] No references found
|
||||
|
||||
--- Configuration.xml ---
|
||||
[OK] Removed <Catalog>Устаревший</Catalog> from ChildObjects
|
||||
[OK] Configuration.xml saved
|
||||
|
||||
--- Subsystems ---
|
||||
[OK] Removed from subsystem 'Справочники'
|
||||
|
||||
--- Files ---
|
||||
[OK] Deleted directory: Catalogs/Устаревший/
|
||||
[OK] Deleted file: Catalogs/Устаревший.xml
|
||||
|
||||
=== Done: 4 actions performed (1 subsystem references removed) ===
|
||||
```
|
||||
|
||||
## Вывод (объект со ссылками — блокировка)
|
||||
|
||||
```
|
||||
=== meta-remove: Catalog.Валюты ===
|
||||
|
||||
[FOUND] Catalogs/Валюты.xml
|
||||
[FOUND] Catalogs/Валюты/ (4 files)
|
||||
|
||||
--- Reference check ---
|
||||
[WARN] Found 3 reference(s) to Catalog.Валюты:
|
||||
|
||||
Documents/СчетНаОплату.xml
|
||||
pattern: CatalogRef.Валюты
|
||||
InformationRegisters/КурсыВалют.xml
|
||||
pattern: CatalogRef.Валюты
|
||||
CommonModules/РаботаСВалютами/Ext/Module.bsl
|
||||
pattern: Справочники.Валюты
|
||||
|
||||
[ERROR] Cannot remove: object has 3 reference(s).
|
||||
Use -Force to remove anyway, or fix references first.
|
||||
```
|
||||
|
||||
Код возврата: 0 = успешно, 1 = ошибки или найдены ссылки.
|
||||
|
||||
## Проверяемые ссылки
|
||||
|
||||
| Категория | Паттерны поиска |
|
||||
|-----------|----------------|
|
||||
| XML-типы реквизитов | `CatalogRef.Name`, `DocumentRef.Name`, `EnumRef.Name` и др. |
|
||||
| BSL-код (рус.) | `Справочники.Name`, `Документы.Name`, `Перечисления.Name` и др. |
|
||||
| BSL-код (англ.) | `Catalogs.Name`, `Documents.Name`, `Enums.Name` и др. |
|
||||
| Общие модули | `Name.` (вызовы методов), `<Handler>Name.`, `<MethodName>Name.` |
|
||||
|
||||
Ссылки из Configuration.xml, ConfigDumpInfo.xml и подсистем НЕ считаются блокирующими — они очищаются автоматически.
|
||||
|
||||
## Примеры
|
||||
|
||||
```powershell
|
||||
# Проверка ссылок + dry run
|
||||
... -ConfigDir C:\WS\tasks\cfsrc\acc_8.3.24 -Object "Catalog.Устаревший" -DryRun
|
||||
|
||||
# Удалить объект без ссылок
|
||||
... -ConfigDir C:\WS\tasks\cfsrc\acc_8.3.24 -Object "Catalog.Устаревший"
|
||||
|
||||
# Принудительно удалить несмотря на ссылки
|
||||
... -ConfigDir C:\WS\tasks\cfsrc\acc_8.3.24 -Object "Catalog.Устаревший" -Force
|
||||
|
||||
# Только дерегистрировать (файлы оставить)
|
||||
... -ConfigDir C:\WS\tasks\cfsrc\acc_8.3.24 -Object "Report.Старый" -KeepFiles
|
||||
|
||||
# Удалить общий модуль
|
||||
... -ConfigDir src -Object "CommonModule.МойМодуль"
|
||||
```
|
||||
|
||||
## Когда использовать
|
||||
|
||||
- **Рефакторинг**: удаление неиспользуемых объектов
|
||||
- **Очистка**: удаление временных/тестовых объектов
|
||||
- **Перенос**: удаление объекта перед пересозданием с другой структурой
|
||||
@@ -0,0 +1,475 @@
|
||||
# meta-remove v1.1 — Remove metadata object from 1C configuration dump
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$ConfigDir,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[string]$Object,
|
||||
|
||||
[switch]$DryRun,
|
||||
|
||||
[switch]$KeepFiles,
|
||||
|
||||
[switch]$Force
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
# --- Type → plural directory mapping ---
|
||||
|
||||
$typePluralMap = @{
|
||||
"Catalog" = "Catalogs"
|
||||
"Document" = "Documents"
|
||||
"Enum" = "Enums"
|
||||
"Constant" = "Constants"
|
||||
"InformationRegister" = "InformationRegisters"
|
||||
"AccumulationRegister" = "AccumulationRegisters"
|
||||
"AccountingRegister" = "AccountingRegisters"
|
||||
"CalculationRegister" = "CalculationRegisters"
|
||||
"ChartOfAccounts" = "ChartsOfAccounts"
|
||||
"ChartOfCharacteristicTypes" = "ChartsOfCharacteristicTypes"
|
||||
"ChartOfCalculationTypes" = "ChartsOfCalculationTypes"
|
||||
"BusinessProcess" = "BusinessProcesses"
|
||||
"Task" = "Tasks"
|
||||
"ExchangePlan" = "ExchangePlans"
|
||||
"DocumentJournal" = "DocumentJournals"
|
||||
"Report" = "Reports"
|
||||
"DataProcessor" = "DataProcessors"
|
||||
"CommonModule" = "CommonModules"
|
||||
"ScheduledJob" = "ScheduledJobs"
|
||||
"EventSubscription" = "EventSubscriptions"
|
||||
"HTTPService" = "HTTPServices"
|
||||
"WebService" = "WebServices"
|
||||
"DefinedType" = "DefinedTypes"
|
||||
"Role" = "Roles"
|
||||
"Subsystem" = "Subsystems"
|
||||
"CommonForm" = "CommonForms"
|
||||
"CommonTemplate" = "CommonTemplates"
|
||||
"CommonPicture" = "CommonPictures"
|
||||
"CommonAttribute" = "CommonAttributes"
|
||||
"SessionParameter" = "SessionParameters"
|
||||
"FunctionalOption" = "FunctionalOptions"
|
||||
"FunctionalOptionsParameter" = "FunctionalOptionsParameters"
|
||||
"Sequence" = "Sequences"
|
||||
"FilterCriterion" = "FilterCriteria"
|
||||
"SettingsStorage" = "SettingsStorages"
|
||||
"XDTOPackage" = "XDTOPackages"
|
||||
"WSReference" = "WSReferences"
|
||||
"StyleItem" = "StyleItems"
|
||||
"Language" = "Languages"
|
||||
}
|
||||
|
||||
# --- Resolve paths ---
|
||||
|
||||
if (-not [System.IO.Path]::IsPathRooted($ConfigDir)) {
|
||||
$ConfigDir = Join-Path (Get-Location).Path $ConfigDir
|
||||
}
|
||||
|
||||
if (-not (Test-Path $ConfigDir -PathType Container)) {
|
||||
Write-Host "[ERROR] Config directory not found: $ConfigDir"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$configXml = Join-Path $ConfigDir "Configuration.xml"
|
||||
if (-not (Test-Path $configXml)) {
|
||||
Write-Host "[ERROR] Configuration.xml not found in: $ConfigDir"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Parse object spec ---
|
||||
|
||||
$parts = $Object -split "\.", 2
|
||||
if ($parts.Count -ne 2 -or -not $parts[0] -or -not $parts[1]) {
|
||||
Write-Host "[ERROR] Invalid object format '$Object'. Expected: Type.Name (e.g. Catalog.Товары)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$objType = $parts[0]
|
||||
$objName = $parts[1]
|
||||
|
||||
if (-not $typePluralMap.ContainsKey($objType)) {
|
||||
Write-Host "[ERROR] Unknown type '$objType'. Supported: $($typePluralMap.Keys -join ', ')"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$typePlural = $typePluralMap[$objType]
|
||||
|
||||
Write-Host "=== meta-remove: ${objType}.${objName} ==="
|
||||
Write-Host ""
|
||||
|
||||
if ($DryRun) {
|
||||
Write-Host "[DRY-RUN] No changes will be made"
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
$actions = 0
|
||||
$errors = 0
|
||||
|
||||
# --- 1. Find object files ---
|
||||
|
||||
$typeDir = Join-Path $ConfigDir $typePlural
|
||||
$objXml = Join-Path $typeDir "$objName.xml"
|
||||
$objDir = Join-Path $typeDir $objName
|
||||
|
||||
$hasXml = Test-Path $objXml
|
||||
$hasDir = Test-Path $objDir -PathType Container
|
||||
|
||||
if (-not $hasXml -and -not $hasDir) {
|
||||
Write-Host "[WARN] Object files not found: $typePlural/$objName.xml"
|
||||
Write-Host " Proceeding with deregistration only..."
|
||||
} else {
|
||||
if ($hasXml) { Write-Host "[FOUND] $typePlural/$objName.xml" }
|
||||
if ($hasDir) {
|
||||
$fileCount = @(Get-ChildItem $objDir -Recurse -File).Count
|
||||
Write-Host "[FOUND] $typePlural/$objName/ ($fileCount files)"
|
||||
}
|
||||
}
|
||||
|
||||
# --- 2. Reference check ---
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "--- Reference check ---"
|
||||
|
||||
# Build search patterns based on object type
|
||||
|
||||
# Type → reference type name (used in XML <v8:Type> elements)
|
||||
$typeRefNames = @{
|
||||
"Catalog" = @("CatalogRef","CatalogObject")
|
||||
"Document" = @("DocumentRef","DocumentObject")
|
||||
"Enum" = @("EnumRef")
|
||||
"ExchangePlan" = @("ExchangePlanRef","ExchangePlanObject")
|
||||
"ChartOfAccounts" = @("ChartOfAccountsRef","ChartOfAccountsObject")
|
||||
"ChartOfCharacteristicTypes" = @("ChartOfCharacteristicTypesRef","ChartOfCharacteristicTypesObject")
|
||||
"ChartOfCalculationTypes" = @("ChartOfCalculationTypesRef","ChartOfCalculationTypesObject")
|
||||
"BusinessProcess" = @("BusinessProcessRef","BusinessProcessObject")
|
||||
"Task" = @("TaskRef","TaskObject")
|
||||
}
|
||||
|
||||
# Type → Russian manager name (used in BSL code: Справочники.Товары)
|
||||
$typeRuManager = @{
|
||||
"Catalog" = "Справочники"
|
||||
"Document" = "Документы"
|
||||
"Enum" = "Перечисления"
|
||||
"Constant" = "Константы"
|
||||
"InformationRegister" = "РегистрыСведений"
|
||||
"AccumulationRegister" = "РегистрыНакопления"
|
||||
"AccountingRegister" = "РегистрыБухгалтерии"
|
||||
"CalculationRegister" = "РегистрыРасчета"
|
||||
"ChartOfAccounts" = "ПланыСчетов"
|
||||
"ChartOfCharacteristicTypes" = "ПланыВидовХарактеристик"
|
||||
"ChartOfCalculationTypes" = "ПланыВидовРасчета"
|
||||
"BusinessProcess" = "БизнесПроцессы"
|
||||
"Task" = "Задачи"
|
||||
"ExchangePlan" = "ПланыОбмена"
|
||||
"Report" = "Отчеты"
|
||||
"DataProcessor" = "Обработки"
|
||||
"DocumentJournal" = "ЖурналыДокументов"
|
||||
"CommonModule" = $null
|
||||
}
|
||||
|
||||
$searchPatterns = @()
|
||||
|
||||
# 1) XML type references: CatalogRef.Name, CatalogObject.Name
|
||||
if ($typeRefNames.ContainsKey($objType)) {
|
||||
foreach ($refName in $typeRefNames[$objType]) {
|
||||
$searchPatterns += "$refName.$objName"
|
||||
}
|
||||
}
|
||||
|
||||
# 2) BSL code references: Справочники.Name, Catalogs.Name
|
||||
$ruMgr = $typeRuManager[$objType]
|
||||
if ($ruMgr) {
|
||||
$searchPatterns += "$ruMgr.$objName"
|
||||
}
|
||||
# English manager = plural directory name
|
||||
$searchPatterns += "$typePlural.$objName"
|
||||
|
||||
# 3) CommonModule: method calls in BSL (ModuleName.)
|
||||
if ($objType -eq "CommonModule") {
|
||||
$searchPatterns += "$objName."
|
||||
}
|
||||
|
||||
# 4) ScheduledJob/EventSubscription handler references
|
||||
if ($objType -eq "CommonModule") {
|
||||
$searchPatterns += "<Handler>$objName."
|
||||
$searchPatterns += "<MethodName>$objName."
|
||||
}
|
||||
|
||||
# Exclude object's own files from search
|
||||
$excludeDirs = @()
|
||||
if ($hasDir) { $excludeDirs += $objDir }
|
||||
$excludeFile = ""
|
||||
if ($hasXml) { $excludeFile = $objXml }
|
||||
|
||||
# Search all XML and BSL files
|
||||
$references = @()
|
||||
$searchExtensions = @("*.xml", "*.bsl")
|
||||
|
||||
foreach ($ext in $searchExtensions) {
|
||||
$files = @(Get-ChildItem $ConfigDir -Filter $ext -Recurse -File -ErrorAction SilentlyContinue)
|
||||
foreach ($file in $files) {
|
||||
# Skip own files
|
||||
if ($excludeFile -and $file.FullName -eq $excludeFile) { continue }
|
||||
if ($excludeDirs.Count -gt 0) {
|
||||
$skip = $false
|
||||
foreach ($ed in $excludeDirs) {
|
||||
if ($file.FullName.StartsWith($ed)) { $skip = $true; break }
|
||||
}
|
||||
if ($skip) { continue }
|
||||
}
|
||||
# Skip auto-cleaned files (Configuration.xml, ConfigDumpInfo.xml, Subsystems)
|
||||
$relPath = $file.FullName.Substring($ConfigDir.Length + 1)
|
||||
if ($relPath -eq "Configuration.xml" -or $relPath -eq "ConfigDumpInfo.xml" -or $relPath.StartsWith("Subsystems")) { continue }
|
||||
|
||||
$content = [System.IO.File]::ReadAllText($file.FullName, [System.Text.Encoding]::UTF8)
|
||||
foreach ($pat in $searchPatterns) {
|
||||
if ($content.Contains($pat)) {
|
||||
$references += @{ File = $relPath; Pattern = $pat }
|
||||
break # one match per file is enough
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Also check for Type.Name references (subsystem content, doc journal, etc.) — but NOT in own files
|
||||
$typeNameRef = "${objType}.${objName}"
|
||||
$files = @(Get-ChildItem $ConfigDir -Filter "*.xml" -Recurse -File -ErrorAction SilentlyContinue)
|
||||
foreach ($file in $files) {
|
||||
if ($excludeFile -and $file.FullName -eq $excludeFile) { continue }
|
||||
if ($excludeDirs.Count -gt 0) {
|
||||
$skip = $false
|
||||
foreach ($ed in $excludeDirs) {
|
||||
if ($file.FullName.StartsWith($ed)) { $skip = $true; break }
|
||||
}
|
||||
if ($skip) { continue }
|
||||
}
|
||||
# Skip Configuration.xml and Subsystems — they will be cleaned automatically
|
||||
$relPath = $file.FullName.Substring($ConfigDir.Length + 1)
|
||||
if ($relPath -eq "Configuration.xml") { continue }
|
||||
if ($relPath -eq "ConfigDumpInfo.xml") { continue }
|
||||
if ($relPath.StartsWith("Subsystems")) { continue }
|
||||
|
||||
$content = [System.IO.File]::ReadAllText($file.FullName, [System.Text.Encoding]::UTF8)
|
||||
if ($content.Contains($typeNameRef)) {
|
||||
# Check it's not already in references
|
||||
$alreadyFound = $false
|
||||
foreach ($r in $references) {
|
||||
if ($r.File -eq $relPath) { $alreadyFound = $true; break }
|
||||
}
|
||||
if (-not $alreadyFound) {
|
||||
$references += @{ File = $relPath; Pattern = $typeNameRef }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($references.Count -gt 0) {
|
||||
Write-Host "[WARN] Found $($references.Count) reference(s) to ${objType}.${objName}:"
|
||||
Write-Host ""
|
||||
$shown = 0
|
||||
foreach ($ref in $references) {
|
||||
Write-Host " $($ref.File)"
|
||||
Write-Host " pattern: $($ref.Pattern)"
|
||||
$shown++
|
||||
if ($shown -ge 20) {
|
||||
$remaining = $references.Count - $shown
|
||||
if ($remaining -gt 0) {
|
||||
Write-Host " ... and $remaining more"
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
Write-Host ""
|
||||
|
||||
if (-not $Force) {
|
||||
Write-Host "[ERROR] Cannot remove: object has $($references.Count) reference(s)."
|
||||
Write-Host " Use -Force to remove anyway, or fix references first."
|
||||
exit 1
|
||||
} else {
|
||||
Write-Host "[WARN] -Force specified, proceeding despite references"
|
||||
}
|
||||
} else {
|
||||
Write-Host "[OK] No references found"
|
||||
}
|
||||
|
||||
# --- 3. Remove from Configuration.xml ChildObjects ---
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "--- Configuration.xml ---"
|
||||
|
||||
$xmlDoc = New-Object System.Xml.XmlDocument
|
||||
$xmlDoc.PreserveWhitespace = $true
|
||||
$xmlDoc.Load($configXml)
|
||||
|
||||
$ns = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable)
|
||||
$ns.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
|
||||
$ns.AddNamespace("v8", "http://v8.1c.ru/8.1/data/core")
|
||||
|
||||
$cfgNode = $xmlDoc.DocumentElement.SelectSingleNode("md:Configuration", $ns)
|
||||
if (-not $cfgNode) {
|
||||
Write-Host "[ERROR] Configuration element not found in Configuration.xml"
|
||||
$errors++
|
||||
} else {
|
||||
$childObjects = $cfgNode.SelectSingleNode("md:ChildObjects", $ns)
|
||||
if ($childObjects) {
|
||||
$found = $false
|
||||
foreach ($child in @($childObjects.ChildNodes)) {
|
||||
if ($child.NodeType -ne 'Element') { continue }
|
||||
if ($child.LocalName -eq $objType -and $child.InnerText.Trim() -eq $objName) {
|
||||
$found = $true
|
||||
if (-not $DryRun) {
|
||||
# Remove preceding whitespace if present
|
||||
$prev = $child.PreviousSibling
|
||||
if ($prev -and $prev.NodeType -eq 'Whitespace') {
|
||||
$childObjects.RemoveChild($prev) | Out-Null
|
||||
}
|
||||
$childObjects.RemoveChild($child) | Out-Null
|
||||
}
|
||||
Write-Host "[OK] Removed <$objType>$objName</$objType> from ChildObjects"
|
||||
$actions++
|
||||
break
|
||||
}
|
||||
}
|
||||
if (-not $found) {
|
||||
Write-Host "[WARN] <$objType>$objName</$objType> not found in ChildObjects"
|
||||
}
|
||||
}
|
||||
|
||||
# Save Configuration.xml
|
||||
if ($actions -gt 0 -and -not $DryRun) {
|
||||
$enc = New-Object System.Text.UTF8Encoding $true
|
||||
$sw = New-Object System.IO.StreamWriter($configXml, $false, $enc)
|
||||
$xmlDoc.Save($sw)
|
||||
$sw.Close()
|
||||
Write-Host "[OK] Configuration.xml saved"
|
||||
}
|
||||
}
|
||||
|
||||
# --- 4. Remove from subsystem Content ---
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "--- Subsystems ---"
|
||||
|
||||
$subsystemsDir = Join-Path $ConfigDir "Subsystems"
|
||||
$subsystemsFound = 0
|
||||
$subsystemsCleaned = 0
|
||||
|
||||
function Remove-FromSubsystems {
|
||||
param([string]$dir)
|
||||
|
||||
$xmlFiles = @(Get-ChildItem $dir -Filter "*.xml" -File -ErrorAction SilentlyContinue)
|
||||
foreach ($xmlFile in $xmlFiles) {
|
||||
$ssDoc = New-Object System.Xml.XmlDocument
|
||||
$ssDoc.PreserveWhitespace = $true
|
||||
try { $ssDoc.Load($xmlFile.FullName) } catch { continue }
|
||||
|
||||
$ssNs = New-Object System.Xml.XmlNamespaceManager($ssDoc.NameTable)
|
||||
$ssNs.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
|
||||
$ssNs.AddNamespace("v8", "http://v8.1c.ru/8.1/data/core")
|
||||
|
||||
$ssNode = $ssDoc.DocumentElement.SelectSingleNode("md:Subsystem", $ssNs)
|
||||
if (-not $ssNode) { continue }
|
||||
|
||||
$propsNode = $ssNode.SelectSingleNode("md:Properties", $ssNs)
|
||||
if (-not $propsNode) { continue }
|
||||
|
||||
$contentNode = $propsNode.SelectSingleNode("md:Content", $ssNs)
|
||||
if (-not $contentNode) { continue }
|
||||
|
||||
$ssNameNode = $propsNode.SelectSingleNode("md:Name", $ssNs)
|
||||
$ssName = if ($ssNameNode) { $ssNameNode.InnerText } else { $xmlFile.BaseName }
|
||||
|
||||
# Content items are <v8:Value>Type.Name</v8:Value>
|
||||
$targetRef = "${objType}.${objName}"
|
||||
$modified = $false
|
||||
|
||||
foreach ($item in @($contentNode.ChildNodes)) {
|
||||
if ($item.NodeType -ne 'Element') { continue }
|
||||
$val = $item.InnerText.Trim()
|
||||
# Content format: "Subsystem.X" or "Catalog.X" etc.
|
||||
if ($val -eq $targetRef) {
|
||||
$script:subsystemsFound++
|
||||
if (-not $DryRun) {
|
||||
$prev = $item.PreviousSibling
|
||||
if ($prev -and $prev.NodeType -eq 'Whitespace') {
|
||||
$contentNode.RemoveChild($prev) | Out-Null
|
||||
}
|
||||
$contentNode.RemoveChild($item) | Out-Null
|
||||
$modified = $true
|
||||
}
|
||||
Write-Host "[OK] Removed from subsystem '$ssName'"
|
||||
$script:subsystemsCleaned++
|
||||
}
|
||||
}
|
||||
|
||||
if ($modified -and -not $DryRun) {
|
||||
$enc = New-Object System.Text.UTF8Encoding $true
|
||||
$sw = New-Object System.IO.StreamWriter($xmlFile.FullName, $false, $enc)
|
||||
$ssDoc.Save($sw)
|
||||
$sw.Close()
|
||||
}
|
||||
|
||||
# Recurse into child subsystems
|
||||
$childDir = Join-Path $dir ($xmlFile.BaseName)
|
||||
$childSubsystems = Join-Path $childDir "Subsystems"
|
||||
if (Test-Path $childSubsystems -PathType Container) {
|
||||
Remove-FromSubsystems -dir $childSubsystems
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Test-Path $subsystemsDir -PathType Container) {
|
||||
Remove-FromSubsystems -dir $subsystemsDir
|
||||
if ($subsystemsCleaned -eq 0) {
|
||||
Write-Host "[OK] Not referenced in any subsystem"
|
||||
}
|
||||
} else {
|
||||
Write-Host "[OK] No Subsystems directory"
|
||||
}
|
||||
|
||||
# --- 5. Delete object files ---
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "--- Files ---"
|
||||
|
||||
if (-not $KeepFiles) {
|
||||
if ($hasDir -and -not $DryRun) {
|
||||
Remove-Item $objDir -Recurse -Force
|
||||
Write-Host "[OK] Deleted directory: $typePlural/$objName/"
|
||||
$actions++
|
||||
} elseif ($hasDir) {
|
||||
Write-Host "[DRY] Would delete directory: $typePlural/$objName/"
|
||||
$actions++
|
||||
}
|
||||
|
||||
if ($hasXml -and -not $DryRun) {
|
||||
Remove-Item $objXml -Force
|
||||
Write-Host "[OK] Deleted file: $typePlural/$objName.xml"
|
||||
$actions++
|
||||
} elseif ($hasXml) {
|
||||
Write-Host "[DRY] Would delete file: $typePlural/$objName.xml"
|
||||
$actions++
|
||||
}
|
||||
|
||||
if (-not $hasXml -and -not $hasDir) {
|
||||
Write-Host "[OK] No files to delete"
|
||||
}
|
||||
} else {
|
||||
Write-Host "[SKIP] File deletion skipped (-KeepFiles)"
|
||||
}
|
||||
|
||||
# --- Summary ---
|
||||
|
||||
Write-Host ""
|
||||
$totalActions = $actions + $subsystemsCleaned
|
||||
if ($DryRun) {
|
||||
Write-Host "=== Dry run complete: $totalActions actions would be performed ==="
|
||||
} else {
|
||||
Write-Host "=== Done: $totalActions actions performed ($subsystemsCleaned subsystem references removed) ==="
|
||||
}
|
||||
|
||||
if ($errors -gt 0) {
|
||||
exit 1
|
||||
}
|
||||
exit 0
|
||||
@@ -21,14 +21,14 @@
|
||||
|
||||
| Группа | Навыки | Описание | Гайд |
|
||||
|--------|--------|----------|------|
|
||||
| Внешние обработки (EPF) | 6 навыков `/epf-*` | Создание, сборка, разборка обработок из XML-исходников | [Подробнее](docs/epf-guide.md) |
|
||||
| Внешние отчёты (ERF) | 3 навыка `/erf-*` | Создание, сборка, разборка внешних отчётов | [Подробнее](docs/epf-guide.md#внешние-отчёты-erf) |
|
||||
| Внешние обработки (EPF) | 7 навыков `/epf-*` | Создание, сборка, разборка, валидация обработок из XML-исходников | [Подробнее](docs/epf-guide.md) |
|
||||
| Внешние отчёты (ERF) | 4 навыка `/erf-*` | Создание, сборка, разборка, валидация внешних отчётов | [Подробнее](docs/epf-guide.md#внешние-отчёты-erf) |
|
||||
| Универсальные операции | `/template-add`, `/template-remove`, `/help-add`, `/form-remove` | Добавление/удаление макетов, форм, справки для любых объектов | [Подробнее](docs/epf-guide.md#универсальные-навыки) |
|
||||
| Табличный документ (MXL) | 4 навыка `/mxl-*` | Анализ, создание, компиляция макетов печатных форм | [Подробнее](docs/mxl-guide.md) |
|
||||
| Управляемые формы (Form) | 6 навыков `/form-*` | Создание, анализ, генерация, модификация, валидация управляемых форм | [Подробнее](docs/form-guide.md) |
|
||||
| Роли (Role) | 3 навыка `/role-*` | Анализ прав роли, создание из JSON DSL, валидация | [Подробнее](docs/role-guide.md) |
|
||||
| Схема компоновки (СКД) | 4 навыка `/skd-*` | Анализ, генерация из JSON DSL, точечное редактирование, валидация схем компоновки данных | [Подробнее](docs/skd-guide.md) |
|
||||
| Метаданные конфигурации | 4 навыка `/meta-*` | Создание, анализ, редактирование, валидация объектов метаданных (23 типа) | [Подробнее](docs/meta-guide.md) |
|
||||
| Метаданные конфигурации | 5 навыков `/meta-*` | Создание, анализ, редактирование, удаление, валидация объектов метаданных (23 типа) | [Подробнее](docs/meta-guide.md) |
|
||||
| Корневая конфигурация | 4 навыка `/cf-*` | Создание, анализ, редактирование, валидация корневых файлов конфигурации | [Подробнее](docs/cf-guide.md) |
|
||||
| Расширения (CFE) | 5 навыков `/cfe-*` | Создание, заимствование, перехват методов, валидация, анализ расширений | [Подробнее](docs/cfe-guide.md) |
|
||||
| Подсистемы (Subsystem) | 4 навыка `/subsystem-*` | Анализ, создание, редактирование, валидация подсистем конфигурации | — |
|
||||
@@ -70,9 +70,11 @@
|
||||
├── epf-dump/ # Разборка EPF
|
||||
├── epf-bsp-init/ # Регистрация БСП
|
||||
├── epf-bsp-add-command/ # Команда БСП
|
||||
├── epf-validate/ # Валидация обработки
|
||||
├── erf-init/ # Создание внешнего отчёта
|
||||
├── erf-build/ # Сборка ERF
|
||||
├── erf-dump/ # Разборка ERF
|
||||
├── erf-validate/ # Валидация отчёта
|
||||
├── template-add/ # Добавление макета (универсальный)
|
||||
├── template-remove/ # Удаление макета (универсальный)
|
||||
├── form-add/ # Добавление формы (универсальный)
|
||||
@@ -97,6 +99,7 @@
|
||||
├── meta-info/ # Структура объекта метаданных
|
||||
├── meta-compile/ # Создание объекта метаданных
|
||||
├── meta-edit/ # Редактирование объекта метаданных
|
||||
├── meta-remove/ # Удаление объекта метаданных
|
||||
├── meta-validate/ # Валидация объекта метаданных
|
||||
├── cf-info/ # Анализ структуры конфигурации
|
||||
├── cf-init/ # Создание пустой конфигурации
|
||||
|
||||
+10
-3
@@ -51,7 +51,7 @@
|
||||
"user": "Admin",
|
||||
"password": "",
|
||||
"aliases": ["dev", "разработка"],
|
||||
"branches": ["dev", "develop"],
|
||||
"branches": ["dev", "develop", "feature/*"],
|
||||
"configSrc": "C:\\WS\\myapp\\cfsrc"
|
||||
},
|
||||
{
|
||||
@@ -82,12 +82,19 @@
|
||||
| `user` | string | нет | Пользователь 1С |
|
||||
| `password` | string | нет | Пароль |
|
||||
| `aliases` | string[] | нет | Альтернативные имена |
|
||||
| `branches` | string[] | нет | Git-ветки |
|
||||
| `branches` | string[] | нет | Git-ветки или glob-паттерны (`release/*`, `feature/*`) |
|
||||
| `configSrc` | string | нет | Каталог XML-выгрузки |
|
||||
|
||||
### Разрешение базы
|
||||
|
||||
Все навыки `db-*` используют единый алгоритм: id → alias → branch → name → default → спросить пользователя.
|
||||
Все навыки `db-*` (а также `epf-build`, `epf-dump`, `erf-build`, `erf-dump`) используют единый алгоритм:
|
||||
|
||||
1. Если пользователь указал **параметры подключения** (путь, сервер) — используй напрямую
|
||||
2. Если указал **базу по имени** — ищи: `id` → `aliases` (с учётом морфологии) → `name` (нечёткое)
|
||||
3. Если **не указал** — сопоставь текущую ветку Git с `branches` (точно или по glob-паттерну)
|
||||
4. Fallback на `default`
|
||||
5. Если не найдено — спроси пользователя
|
||||
6. После выполнения: если использованная база не зарегистрирована — предложи добавить через `/db-list add`
|
||||
|
||||
## Сценарии использования
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
| `/epf-dump` | `<EpfFile>` | Разобрать EPF в XML (через 1cv8.exe) |
|
||||
| `/epf-bsp-init` | `<ProcessorName> <Вид>` | Добавить регистрацию БСП (СведенияОВнешнейОбработке) |
|
||||
| `/epf-bsp-add-command` | `<ProcessorName> <Идентификатор>` | Добавить команду в дополнительную обработку БСП |
|
||||
| `/epf-validate` | `<ObjectPath> [-MaxErrors 30]` | Валидация структурной корректности обработки (10 проверок) |
|
||||
|
||||
## Внешние отчёты (ERF)
|
||||
|
||||
@@ -20,6 +21,7 @@
|
||||
| `/erf-init` | `<ReportName> [Synonym] [--WithSKD]` | Создать новый отчёт (корневой XML + модуль объекта + опционально СКД) |
|
||||
| `/erf-build` | `<ReportName>` | Собрать ERF из XML (через 1cv8.exe) |
|
||||
| `/erf-dump` | `<ErfFile>` | Разобрать ERF в XML (через 1cv8.exe) |
|
||||
| `/erf-validate` | `<ObjectPath> [-MaxErrors 30]` | Валидация структурной корректности отчёта (10 проверок) |
|
||||
|
||||
Флаг `--WithSKD` создаёт макет `ОсновнаяСхемаКомпоновкиДанных` и привязывает его к `MainDataCompositionSchema`.
|
||||
|
||||
|
||||
+13
-2
@@ -9,6 +9,7 @@
|
||||
| `/meta-info` | `<ObjectPath> [-Mode] [-Name]` | Анализ структуры объекта: реквизиты, ТЧ, формы, движения, типы (8 режимов) |
|
||||
| `/meta-compile` | `<JsonPath> <OutputPath>` | Создание объекта метаданных из JSON DSL: реквизиты, ТЧ, свойства, формы |
|
||||
| `/meta-edit` | `<ObjectPath> -Operation <op> -Value "<val>"` | Точечное редактирование: 30+ атомарных операций (add/remove/modify/set) |
|
||||
| `/meta-remove` | `<ConfigDir> -Object <Type.Name> [-DryRun] [-Force]` | Безопасное удаление объекта с проверкой ссылок (блокирует при наличии) |
|
||||
| `/meta-validate` | `<ObjectPath> [-MaxErrors 20]` | Валидация структурной корректности: ~40 проверок |
|
||||
|
||||
## Рабочий цикл
|
||||
@@ -16,13 +17,15 @@
|
||||
```
|
||||
Описание объекта (текст) → JSON DSL → /meta-compile → XML-исходники → /meta-validate
|
||||
↕ /meta-edit → /meta-info
|
||||
↕ /meta-remove (безопасное удаление)
|
||||
```
|
||||
|
||||
1. Claude формирует JSON-определение объекта (тип, реквизиты, ТЧ, свойства)
|
||||
2. `/meta-compile` генерирует XML-исходники с корректными UUID, namespace, структурой ChildObjects
|
||||
3. `/meta-edit` вносит точечные изменения: добавление/удаление реквизитов, ТЧ, владельцев, движений и т.д.
|
||||
4. `/meta-validate` проверяет корректность XML
|
||||
5. `/meta-info` выводит компактную сводку для визуальной проверки
|
||||
4. `/meta-remove` безопасно удаляет объект (проверяет ссылки в реквизитах, коде, формах; чистит Configuration.xml и подсистемы)
|
||||
5. `/meta-validate` проверяет корректность XML
|
||||
6. `/meta-info` выводит компактную сводку для визуальной проверки
|
||||
|
||||
## Поддерживаемые типы объектов (23 типа)
|
||||
|
||||
@@ -147,6 +150,14 @@ Claude вызовет `/meta-edit` дважды: `add-attribute` для рекв
|
||||
|
||||
Claude вызовет `/meta-edit` с операцией `set-registerRecords`.
|
||||
|
||||
### Удаление неиспользуемого объекта
|
||||
|
||||
```
|
||||
> Удали справочник Catalogs/Устаревший из конфигурации src/
|
||||
```
|
||||
|
||||
Claude вызовет `/meta-remove` с `-DryRun`, покажет что будет удалено и проверит ссылки. Если объект нигде не используется — удалит файлы, уберёт из Configuration.xml и подсистем. Если есть ссылки — покажет список и заблокирует удаление.
|
||||
|
||||
### Проверка объекта после изменений
|
||||
|
||||
```
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
# Подсистемы и командный интерфейс
|
||||
|
||||
Навыки групп `/subsystem-*` и `/interface-*` позволяют анализировать, создавать, редактировать и проверять подсистемы 1С и их командный интерфейс — XML-файлы Subsystem и CommandInterface.xml из выгрузки конфигурации.
|
||||
|
||||
## Навыки
|
||||
|
||||
| Навык | Параметры | Описание |
|
||||
|-------|-----------|----------|
|
||||
| `/subsystem-info` | `<SubsystemPath> [-Mode] [-Name]` | Анализ структуры подсистемы: состав, дочерние, CI, дерево иерархии (4 режима) |
|
||||
| `/subsystem-compile` | `<JsonPath> <OutputDir> [-Parent]` | Генерация подсистемы из JSON DSL: XML + регистрация в Configuration.xml |
|
||||
| `/subsystem-edit` | `<SubsystemPath> -Operation <op> -Value "<value>"` | Точечное редактирование: 5 операций (add/remove content/child, set-property) |
|
||||
| `/subsystem-validate` | `<SubsystemPath> [-MaxErrors 30]` | Валидация структурной корректности: 13 проверок |
|
||||
| `/interface-edit` | `<CIPath> -Operation <op> -Value "<value>"` | Настройка CommandInterface.xml: 6 операций (hide/show/place/order) |
|
||||
| `/interface-validate` | `<CIPath> [-MaxErrors 30]` | Валидация CommandInterface.xml: 13 проверок |
|
||||
|
||||
## Рабочий цикл
|
||||
|
||||
```
|
||||
Описание раздела (текст) → JSON DSL → /subsystem-compile → XML → /subsystem-validate
|
||||
↕ /subsystem-edit → /subsystem-info
|
||||
/interface-edit → CommandInterface.xml → /interface-validate
|
||||
```
|
||||
|
||||
1. Claude формирует JSON-определение подсистемы (имя, состав, дочерние, картинка)
|
||||
2. `/subsystem-compile` генерирует XML-файл подсистемы и регистрирует в Configuration.xml
|
||||
3. `/subsystem-edit` вносит точечные изменения: добавление объектов, дочерних подсистем, изменение свойств
|
||||
4. `/interface-edit` настраивает командный интерфейс: скрытие/показ команд, размещение в группах, порядок
|
||||
5. `/subsystem-validate` и `/interface-validate` проверяют корректность XML
|
||||
6. `/subsystem-info` выводит компактную сводку для визуальной проверки
|
||||
|
||||
## JSON DSL — формат подсистемы
|
||||
|
||||
Подсистемы описываются в JSON:
|
||||
|
||||
### Минимальный пример
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Тест"
|
||||
}
|
||||
```
|
||||
|
||||
Умолчания: `includeInCommandInterface = true`, `useOneCommand = false`, synonym генерируется из name.
|
||||
|
||||
### Полный пример
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Продажи",
|
||||
"synonym": "Продажи",
|
||||
"comment": "",
|
||||
"includeInCommandInterface": true,
|
||||
"useOneCommand": false,
|
||||
"explanation": "Управление продажами и взаимодействием с клиентами",
|
||||
"picture": "CommonPicture.Продажи",
|
||||
"content": ["Catalog.Товары", "Document.Заказ", "Report.Продажи"],
|
||||
"children": ["Настройки", "Отчёты"]
|
||||
}
|
||||
```
|
||||
|
||||
### Свойства
|
||||
|
||||
| Свойство | Тип | Умолчание | Описание |
|
||||
|----------|-----|-----------|----------|
|
||||
| `name` | string | *(обязательно)* | Идентификатор подсистемы |
|
||||
| `synonym` | string | из name | Отображаемое имя |
|
||||
| `comment` | string | `""` | Комментарий |
|
||||
| `includeInCommandInterface` | bool | `true` | Включать в командный интерфейс |
|
||||
| `useOneCommand` | bool | `false` | Режим одной команды (требует ровно 1 элемент в content) |
|
||||
| `explanation` | string | `""` | Описание раздела (подсказка) |
|
||||
| `picture` | string | — | Ссылка на общую картинку (`CommonPicture.Имя`) |
|
||||
| `content` | string[] | `[]` | Состав: `"Тип.Имя"` (Catalog.X, Document.Y, Report.Z, ...) |
|
||||
| `children` | string[] | `[]` | Имена дочерних подсистем |
|
||||
|
||||
## Командный интерфейс — операции
|
||||
|
||||
Навык `/interface-edit` управляет файлом `CommandInterface.xml` подсистемы.
|
||||
|
||||
### Форматы ссылок на команды
|
||||
|
||||
| Формат | Пример |
|
||||
|--------|--------|
|
||||
| StandardCommand | `Catalog.Товары.StandardCommand.OpenList` |
|
||||
| Command | `Report.Продажи.Command.Отчёт` |
|
||||
| CommonCommand | `CommonCommand.МояКоманда` |
|
||||
|
||||
### Операции
|
||||
|
||||
| Операция | Значение | Описание |
|
||||
|----------|----------|----------|
|
||||
| `hide` | `"Cmd.Name"` или массив | Скрыть команду (CommandsVisibility, false) |
|
||||
| `show` | `"Cmd.Name"` или массив | Показать команду (CommandsVisibility, true) |
|
||||
| `place` | `{"command":"...","group":"CommandGroup.X"}` | Разместить команду в группе |
|
||||
| `order` | `{"group":"...","commands":[...]}` | Задать порядок команд в группе |
|
||||
| `subsystem-order` | `["Subsystem.X.Subsystem.A",...]` | Порядок дочерних подсистем |
|
||||
| `group-order` | `["NavigationPanelOrdinary",...]` | Порядок групп |
|
||||
|
||||
### Примеры
|
||||
|
||||
```powershell
|
||||
# Скрыть команду
|
||||
... -CIPath Subsystems/Продажи/Ext/CommandInterface.xml -Operation hide -Value "Catalog.Товары.StandardCommand.OpenList"
|
||||
|
||||
# Показать команду
|
||||
... -Operation show -Value "Report.Продажи.Command.Отчёт"
|
||||
|
||||
# Разместить в группе
|
||||
... -Operation place -Value '{"command":"Report.X.Command.Y","group":"CommandGroup.Отчеты"}'
|
||||
|
||||
# Задать порядок подсистем
|
||||
... -Operation subsystem-order -Value '["Subsystem.X.Subsystem.A","Subsystem.X.Subsystem.B"]'
|
||||
```
|
||||
|
||||
## Сценарии использования
|
||||
|
||||
### Анализ структуры подсистем
|
||||
|
||||
```
|
||||
> Покажи дерево подсистем конфигурации
|
||||
```
|
||||
|
||||
Claude вызовет `/subsystem-info` (tree → overview → ci) и опишет:
|
||||
- иерархию подсистем с маркерами [CI], [OneCmd], [Скрыт]
|
||||
- состав каждой подсистемы (объекты по типам)
|
||||
- настройки командного интерфейса (видимость, размещение, порядок)
|
||||
|
||||
### Создание подсистемы по описанию
|
||||
|
||||
```
|
||||
> Создай подсистему Продажи: справочник Товары, документ Заказ, отчёт Продажи.
|
||||
> Дочерние подсистемы: Настройки, Отчёты. Картинка — CommonPicture.Продажи.
|
||||
```
|
||||
|
||||
Claude сформирует JSON:
|
||||
```json
|
||||
{
|
||||
"name": "Продажи",
|
||||
"synonym": "Продажи",
|
||||
"content": ["Catalog.Товары", "Document.Заказ", "Report.Продажи"],
|
||||
"children": ["Настройки", "Отчёты"],
|
||||
"picture": "CommonPicture.Продажи"
|
||||
}
|
||||
```
|
||||
|
||||
И вызовет `/subsystem-compile` → `/subsystem-validate` → `/subsystem-info`.
|
||||
|
||||
### Добавление объектов в подсистему
|
||||
|
||||
```
|
||||
> Добавь Document.Счёт и Report.Задолженность в подсистему Продажи
|
||||
```
|
||||
|
||||
Claude вызовет `/subsystem-edit` с операцией `add-content`:
|
||||
```powershell
|
||||
... -SubsystemPath Subsystems/Продажи.xml -Operation add-content -Value '["Document.Счёт","Report.Задолженность"]'
|
||||
```
|
||||
|
||||
### Настройка командного интерфейса
|
||||
|
||||
```
|
||||
> Скрой команду открытия списка товаров и размести отчёт Продажи в группе Отчёты
|
||||
```
|
||||
|
||||
Claude вызовет `/interface-edit`:
|
||||
```powershell
|
||||
# Скрыть команду
|
||||
... -CIPath Subsystems/Продажи/Ext/CommandInterface.xml -Operation hide -Value "Catalog.Товары.StandardCommand.OpenList"
|
||||
|
||||
# Разместить в группе
|
||||
... -Operation place -Value '{"command":"Report.Продажи.Command.Отчёт","group":"CommandGroup.Отчёты"}'
|
||||
```
|
||||
|
||||
### Проверка подсистемы
|
||||
|
||||
```
|
||||
> Проверь подсистему Subsystems/Продажи.xml
|
||||
```
|
||||
|
||||
Claude вызовет `/subsystem-validate` и `/interface-validate`, покажет результат: ошибки (невалидный XML, отсутствующие файлы, дубликаты) и предупреждения.
|
||||
|
||||
## Структура файлов подсистемы
|
||||
|
||||
```
|
||||
Subsystems/
|
||||
├── ИмяПодсистемы.xml # Определение подсистемы (UUID, свойства, Content)
|
||||
└── ИмяПодсистемы/
|
||||
├── Ext/
|
||||
│ └── CommandInterface.xml # Командный интерфейс (видимость, размещение, порядок)
|
||||
└── Subsystems/ # Вложенные подсистемы
|
||||
├── Дочерняя.xml
|
||||
└── Дочерняя/
|
||||
└── Ext/
|
||||
└── CommandInterface.xml
|
||||
```
|
||||
|
||||
Регистрация в `Configuration.xml`:
|
||||
```xml
|
||||
<ChildObjects>
|
||||
<Subsystem>ИмяПодсистемы</Subsystem>
|
||||
</ChildObjects>
|
||||
```
|
||||
|
||||
## Спецификация
|
||||
|
||||
- [1c-subsystem-spec.md](1c-subsystem-spec.md) — XML-формат подсистем, CommandInterface.xml, namespace, элементы
|
||||
Reference in New Issue
Block a user