mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-06-15 10:24:57 +03:00
Merge branch 'dev'
This commit is contained in:
@@ -16,6 +16,14 @@ allowed-tools:
|
||||
|
||||
Расширение должно быть создано (`/cfe-init`) и содержать валидный `Configuration.xml`.
|
||||
|
||||
### Авто-определение ConfigPath
|
||||
|
||||
Если пользователь не указал `-ConfigPath` — попробуй определить автоматически:
|
||||
1. Прочитай `.v8-project.json` из корня проекта
|
||||
2. Разреши целевую базу (по имени, ветке или `default` — алгоритм из `/db-list`)
|
||||
3. Если у базы есть поле `configSrc` — используй как `-ConfigPath`
|
||||
4. Если `configSrc` нет — спроси у пользователя
|
||||
|
||||
## Параметры
|
||||
|
||||
| Параметр | Описание |
|
||||
@@ -30,9 +38,20 @@ allowed-tools:
|
||||
- `CommonModule.РаботаСФайлами` — общий модуль
|
||||
- `Document.РеализацияТоваров` — документ
|
||||
- `Enum.ВидыОплат` — перечисление
|
||||
- `Catalog.Контрагенты.Form.ФормаЭлемента` — форма объекта (заимствование формы)
|
||||
- `Catalog.X ;; CommonModule.Y ;; Enum.Z` — несколько объектов
|
||||
Поддерживаются все 44 типа объектов конфигурации.
|
||||
|
||||
### Заимствование форм
|
||||
|
||||
Формат `Тип.Имя.Form.ИмяФормы` заимствует форму конкретного объекта. Если родительский объект ещё не заимствован — он будет заимствован автоматически.
|
||||
|
||||
Создаётся:
|
||||
1. **Метаданные формы** — `Forms/ИмяФормы.xml` с `ObjectBelonging=Adopted`, `FormType=Managed`
|
||||
2. **Form.xml** — `Forms/ИмяФормы/Ext/Form.xml` с копией исходной формы + `<BaseForm>` (начальное состояние)
|
||||
3. **Module.bsl** — пустой файл `Forms/ИмяФормы/Ext/Form/Module.bsl`
|
||||
4. **Регистрация** — `<Form>` в ChildObjects родительского объекта
|
||||
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
@@ -45,6 +64,9 @@ powershell.exe -NoProfile -File .claude/skills/cfe-borrow/scripts/cfe-borrow.ps1
|
||||
# Заимствовать один объект
|
||||
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты"
|
||||
|
||||
# Заимствовать форму (автоматически заимствует родительский объект)
|
||||
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты.Form.ФормаЭлемента"
|
||||
|
||||
# Несколько объектов за раз
|
||||
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты ;; CommonModule.ОбщийМодуль ;; Enum.ВидыОплат"
|
||||
```
|
||||
|
||||
@@ -249,10 +249,12 @@ $script:generatedTypes = @{
|
||||
@{ prefix = "DocumentJournalManager"; category = "Manager" }
|
||||
)
|
||||
"Report" = @(
|
||||
@{ prefix = "ReportObject"; category = "Object" }
|
||||
@{ prefix = "ReportObject"; category = "Object" }
|
||||
@{ prefix = "ReportManager"; category = "Manager" }
|
||||
)
|
||||
"DataProcessor" = @(
|
||||
@{ prefix = "DataProcessorObject"; category = "Object" }
|
||||
@{ prefix = "DataProcessorObject"; category = "Object" }
|
||||
@{ prefix = "DataProcessorManager"; category = "Manager" }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -384,6 +386,300 @@ function Read-SourceObject {
|
||||
}
|
||||
}
|
||||
|
||||
# --- 10b. Helper: read source form UUID ---
|
||||
function Read-SourceFormUuid {
|
||||
param([string]$typeName, [string]$objName, [string]$formName)
|
||||
|
||||
$dirName = $childTypeDirMap[$typeName]
|
||||
$srcFile = Join-Path (Join-Path (Join-Path (Join-Path $cfgDir $dirName) $objName) "Forms") "${formName}.xml"
|
||||
if (-not (Test-Path $srcFile)) {
|
||||
Write-Error "Source form not found: $srcFile"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$srcDoc = New-Object System.Xml.XmlDocument
|
||||
$srcDoc.PreserveWhitespace = $false
|
||||
$srcDoc.Load($srcFile)
|
||||
|
||||
$srcEl = $null
|
||||
foreach ($c in $srcDoc.DocumentElement.ChildNodes) {
|
||||
if ($c.NodeType -eq 'Element') { $srcEl = $c; break }
|
||||
}
|
||||
if (-not $srcEl) {
|
||||
Write-Error "No metadata element found in source form: $srcFile"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$srcUuid = $srcEl.GetAttribute("uuid")
|
||||
if (-not $srcUuid) {
|
||||
Write-Error "No uuid attribute on source form element: $srcFile"
|
||||
exit 1
|
||||
}
|
||||
|
||||
return $srcUuid
|
||||
}
|
||||
|
||||
# --- 10c. Helper: borrow a form ---
|
||||
function Borrow-Form {
|
||||
param([string]$typeName, [string]$objName, [string]$formName)
|
||||
|
||||
$dirName = $childTypeDirMap[$typeName]
|
||||
$enc = New-Object System.Text.UTF8Encoding($true)
|
||||
|
||||
# 1. Read source form UUID
|
||||
$formUuid = Read-SourceFormUuid $typeName $objName $formName
|
||||
Info " Source form UUID: $formUuid"
|
||||
|
||||
# 2. Read source Form.xml content
|
||||
$srcFormXmlPath = Join-Path (Join-Path (Join-Path (Join-Path (Join-Path $cfgDir $dirName) $objName) "Forms") $formName) "Ext/Form.xml"
|
||||
if (-not (Test-Path $srcFormXmlPath)) {
|
||||
Write-Error "Source Form.xml not found: $srcFormXmlPath"
|
||||
exit 1
|
||||
}
|
||||
$srcFormContent = [System.IO.File]::ReadAllText($srcFormXmlPath, $enc)
|
||||
|
||||
# 3. Generate form metadata XML (ФормаЭлемента.xml)
|
||||
$newFormUuid = [guid]::NewGuid().ToString()
|
||||
$formMetaSb = New-Object System.Text.StringBuilder
|
||||
$formMetaSb.AppendLine("<?xml version=`"1.0`" encoding=`"UTF-8`"?>") | Out-Null
|
||||
$formMetaSb.AppendLine("<MetaDataObject $($script:xmlnsDecl) version=`"2.17`">") | Out-Null
|
||||
$formMetaSb.AppendLine("`t<Form uuid=`"${newFormUuid}`">") | Out-Null
|
||||
$formMetaSb.AppendLine("`t`t<InternalInfo/>") | Out-Null
|
||||
$formMetaSb.AppendLine("`t`t<Properties>") | Out-Null
|
||||
$formMetaSb.AppendLine("`t`t`t<ObjectBelonging>Adopted</ObjectBelonging>") | Out-Null
|
||||
$formMetaSb.AppendLine("`t`t`t<Name>${formName}</Name>") | Out-Null
|
||||
$formMetaSb.AppendLine("`t`t`t<Comment/>") | Out-Null
|
||||
$formMetaSb.AppendLine("`t`t`t<ExtendedConfigurationObject>${formUuid}</ExtendedConfigurationObject>") | Out-Null
|
||||
$formMetaSb.AppendLine("`t`t`t<FormType>Managed</FormType>") | Out-Null
|
||||
$formMetaSb.AppendLine("`t`t</Properties>") | Out-Null
|
||||
$formMetaSb.AppendLine("`t</Form>") | Out-Null
|
||||
$formMetaSb.Append("</MetaDataObject>") | Out-Null
|
||||
|
||||
# 4. Create directories
|
||||
$formMetaDir = Join-Path (Join-Path (Join-Path $extDir $dirName) $objName) "Forms"
|
||||
if (-not (Test-Path $formMetaDir)) {
|
||||
New-Item -ItemType Directory -Path $formMetaDir -Force | Out-Null
|
||||
}
|
||||
|
||||
# Write form metadata
|
||||
$formMetaFile = Join-Path $formMetaDir "${formName}.xml"
|
||||
[System.IO.File]::WriteAllText($formMetaFile, $formMetaSb.ToString(), $enc)
|
||||
Info " Created: $formMetaFile"
|
||||
|
||||
# 5. Generate Form.xml with BaseForm (visual elements only)
|
||||
# Parse source Form.xml as XmlDocument
|
||||
$srcFormDoc = New-Object System.Xml.XmlDocument
|
||||
$srcFormDoc.PreserveWhitespace = $true
|
||||
$srcFormDoc.Load($srcFormXmlPath)
|
||||
$srcFormEl = $srcFormDoc.DocumentElement
|
||||
|
||||
$formVersion = $srcFormEl.GetAttribute("version")
|
||||
if (-not $formVersion) { $formVersion = "2.17" }
|
||||
|
||||
# Find direct children: AutoCommandBar, ChildItems (visual elements only)
|
||||
$srcAutoCmd = $null
|
||||
$srcChildItems = $null
|
||||
foreach ($fc in $srcFormEl.ChildNodes) {
|
||||
if ($fc.NodeType -ne 'Element') { continue }
|
||||
if ($fc.LocalName -eq 'AutoCommandBar' -and -not $srcAutoCmd) { $srcAutoCmd = $fc }
|
||||
elseif ($fc.LocalName -eq 'ChildItems' -and -not $srcChildItems) { $srcChildItems = $fc }
|
||||
}
|
||||
|
||||
# Get OuterXml and strip redundant namespace redeclarations (they're on root <Form>)
|
||||
$nsStripPattern = '\s+xmlns(?::\w+)?="[^"]*"'
|
||||
|
||||
$autoCmdXml = ""
|
||||
if ($srcAutoCmd) {
|
||||
$autoCmdXml = $srcAutoCmd.OuterXml
|
||||
$autoCmdXml = [regex]::Replace($autoCmdXml, $nsStripPattern, '')
|
||||
# Replace all CommandName values with 0 (base form buttons lose command refs)
|
||||
$autoCmdXml = [regex]::Replace($autoCmdXml, '<CommandName>[^<]*</CommandName>', '<CommandName>0</CommandName>')
|
||||
# Replace Autofill true → false
|
||||
$autoCmdXml = $autoCmdXml -replace '<Autofill>true</Autofill>', '<Autofill>false</Autofill>'
|
||||
}
|
||||
|
||||
$childItemsXml = ""
|
||||
if ($srcChildItems) {
|
||||
$childItemsXml = $srcChildItems.OuterXml
|
||||
$childItemsXml = [regex]::Replace($childItemsXml, $nsStripPattern, '')
|
||||
# Replace all CommandName values with 0 in ChildItems too
|
||||
$childItemsXml = [regex]::Replace($childItemsXml, '<CommandName>[^<]*</CommandName>', '<CommandName>0</CommandName>')
|
||||
} else {
|
||||
$childItemsXml = "<ChildItems/>"
|
||||
}
|
||||
|
||||
# Extract the <Form ...> opening tag from source text (preserves namespace declarations)
|
||||
$xmlDecl = '<?xml version="1.0" encoding="UTF-8"?>'
|
||||
$formTag = "<Form version=`"${formVersion}`">"
|
||||
if ($srcFormContent -match '(?s)^(<\?xml[^?]*\?>)') { $xmlDecl = $Matches[1] }
|
||||
if ($srcFormContent -match '(<Form[^>]*>)') { $formTag = $Matches[1] }
|
||||
|
||||
# Build output Form.xml
|
||||
$formXmlSb = New-Object System.Text.StringBuilder
|
||||
$formXmlSb.Append($xmlDecl) | Out-Null
|
||||
$formXmlSb.Append("`r`n") | Out-Null
|
||||
$formXmlSb.Append($formTag) | Out-Null
|
||||
$formXmlSb.Append("`r`n") | Out-Null
|
||||
|
||||
# Part 1: visual elements (add leading tab to first line of each block)
|
||||
if ($autoCmdXml) {
|
||||
$formXmlSb.Append("`t$autoCmdXml") | Out-Null
|
||||
$formXmlSb.Append("`r`n") | Out-Null
|
||||
}
|
||||
$formXmlSb.Append("`t$childItemsXml") | Out-Null
|
||||
$formXmlSb.Append("`r`n") | Out-Null
|
||||
$formXmlSb.Append("`t<Attributes/>") | Out-Null
|
||||
$formXmlSb.Append("`r`n") | Out-Null
|
||||
|
||||
# BaseForm: same visual elements, indented one more level
|
||||
$formXmlSb.Append("`t<BaseForm version=`"${formVersion}`">") | Out-Null
|
||||
$formXmlSb.Append("`r`n") | Out-Null
|
||||
|
||||
if ($autoCmdXml) {
|
||||
# Reindent for BaseForm: first line gets 2 tabs, other lines get +1 tab
|
||||
$acLines = $autoCmdXml -split "`r?`n"
|
||||
for ($li = 0; $li -lt $acLines.Count; $li++) {
|
||||
if ($li -eq 0) { $formXmlSb.Append("`t`t$($acLines[$li])") | Out-Null }
|
||||
else { $formXmlSb.Append("`t$($acLines[$li])") | Out-Null }
|
||||
$formXmlSb.Append("`r`n") | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
$ciLines = $childItemsXml -split "`r?`n"
|
||||
for ($li = 0; $li -lt $ciLines.Count; $li++) {
|
||||
if ($li -eq 0) { $formXmlSb.Append("`t`t$($ciLines[$li])") | Out-Null }
|
||||
else { $formXmlSb.Append("`t$($ciLines[$li])") | Out-Null }
|
||||
$formXmlSb.Append("`r`n") | Out-Null
|
||||
}
|
||||
|
||||
$formXmlSb.Append("`t`t<Attributes/>") | Out-Null
|
||||
$formXmlSb.Append("`r`n") | Out-Null
|
||||
$formXmlSb.Append("`t</BaseForm>") | Out-Null
|
||||
$formXmlSb.Append("`r`n") | Out-Null
|
||||
$formXmlSb.Append("</Form>") | Out-Null
|
||||
|
||||
# Write Form.xml
|
||||
$formXmlDir = Join-Path (Join-Path $formMetaDir $formName) "Ext"
|
||||
if (-not (Test-Path $formXmlDir)) {
|
||||
New-Item -ItemType Directory -Path $formXmlDir -Force | Out-Null
|
||||
}
|
||||
$formXmlFile = Join-Path $formXmlDir "Form.xml"
|
||||
[System.IO.File]::WriteAllText($formXmlFile, $formXmlSb.ToString(), $enc)
|
||||
Info " Created: $formXmlFile"
|
||||
|
||||
# 6. Create empty Module.bsl
|
||||
$moduleDir = Join-Path $formXmlDir "Form"
|
||||
if (-not (Test-Path $moduleDir)) {
|
||||
New-Item -ItemType Directory -Path $moduleDir -Force | Out-Null
|
||||
}
|
||||
$moduleBslFile = Join-Path $moduleDir "Module.bsl"
|
||||
[System.IO.File]::WriteAllText($moduleBslFile, "", $enc)
|
||||
Info " Created: $moduleBslFile"
|
||||
|
||||
# 7. Register form in parent object ChildObjects
|
||||
Register-FormInObject $typeName $objName $formName
|
||||
|
||||
return @($formMetaFile, $formXmlFile, $moduleBslFile)
|
||||
}
|
||||
|
||||
# --- 10d. Helper: register form in parent object's ChildObjects ---
|
||||
function Register-FormInObject {
|
||||
param([string]$typeName, [string]$objName, [string]$formName)
|
||||
|
||||
$dirName = $childTypeDirMap[$typeName]
|
||||
$objFile = Join-Path (Join-Path $extDir $dirName) "${objName}.xml"
|
||||
|
||||
if (-not (Test-Path $objFile)) {
|
||||
Warn "Parent object file not found: $objFile — form not registered in ChildObjects"
|
||||
return
|
||||
}
|
||||
|
||||
$objDoc = New-Object System.Xml.XmlDocument
|
||||
$objDoc.PreserveWhitespace = $true
|
||||
$objDoc.Load($objFile)
|
||||
|
||||
$objNs = New-Object System.Xml.XmlNamespaceManager($objDoc.NameTable)
|
||||
$objNs.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
|
||||
|
||||
# Find the type element
|
||||
$objEl = $null
|
||||
foreach ($c in $objDoc.DocumentElement.ChildNodes) {
|
||||
if ($c.NodeType -eq 'Element') { $objEl = $c; break }
|
||||
}
|
||||
if (-not $objEl) {
|
||||
Warn "No type element in $objFile — form not registered"
|
||||
return
|
||||
}
|
||||
|
||||
# Find or create ChildObjects
|
||||
$childObjs = $objEl.SelectSingleNode("md:ChildObjects", $objNs)
|
||||
if (-not $childObjs) {
|
||||
# Create ChildObjects element
|
||||
$childObjs = $objDoc.CreateElement("ChildObjects", "http://v8.1c.ru/8.3/MDClasses")
|
||||
$objEl.AppendChild($objDoc.CreateWhitespace("`r`n`t`t")) | Out-Null
|
||||
$objEl.AppendChild($childObjs) | Out-Null
|
||||
$objEl.AppendChild($objDoc.CreateWhitespace("`r`n`t")) | Out-Null
|
||||
}
|
||||
|
||||
# Check dedup
|
||||
foreach ($c in $childObjs.ChildNodes) {
|
||||
if ($c.NodeType -eq 'Element' -and $c.LocalName -eq "Form" -and $c.InnerText -eq $formName) {
|
||||
Warn "Form '$formName' already in ChildObjects of ${typeName}.${objName}"
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
# Expand self-closing if needed
|
||||
if (-not $childObjs.HasChildNodes -or $childObjs.IsEmpty) {
|
||||
$closeWs = $objDoc.CreateWhitespace("`r`n`t`t")
|
||||
$childObjs.AppendChild($closeWs) | Out-Null
|
||||
}
|
||||
|
||||
# Add <Form>formName</Form>
|
||||
$formEl = $objDoc.CreateElement("Form", "http://v8.1c.ru/8.3/MDClasses")
|
||||
$formEl.InnerText = $formName
|
||||
|
||||
$trailing = $childObjs.LastChild
|
||||
$ws = $objDoc.CreateWhitespace("`r`n`t`t`t")
|
||||
if ($trailing -and ($trailing.NodeType -eq 'Whitespace' -or $trailing.NodeType -eq 'SignificantWhitespace')) {
|
||||
$childObjs.InsertBefore($ws, $trailing) | Out-Null
|
||||
$childObjs.InsertBefore($formEl, $trailing) | Out-Null
|
||||
} else {
|
||||
$childObjs.AppendChild($ws) | Out-Null
|
||||
$childObjs.AppendChild($formEl) | Out-Null
|
||||
}
|
||||
|
||||
# Save object XML
|
||||
$settings2 = New-Object System.Xml.XmlWriterSettings
|
||||
$settings2.Encoding = New-Object System.Text.UTF8Encoding($true)
|
||||
$settings2.Indent = $false
|
||||
$settings2.NewLineHandling = [System.Xml.NewLineHandling]::None
|
||||
|
||||
$memStream2 = New-Object System.IO.MemoryStream
|
||||
$writer2 = [System.Xml.XmlWriter]::Create($memStream2, $settings2)
|
||||
$objDoc.Save($writer2)
|
||||
$writer2.Flush(); $writer2.Close()
|
||||
|
||||
$bytes2 = $memStream2.ToArray()
|
||||
$memStream2.Close()
|
||||
$text2 = [System.Text.Encoding]::UTF8.GetString($bytes2)
|
||||
if ($text2.Length -gt 0 -and $text2[0] -eq [char]0xFEFF) { $text2 = $text2.Substring(1) }
|
||||
$text2 = $text2.Replace('encoding="utf-8"', 'encoding="UTF-8"')
|
||||
|
||||
$utf8Bom2 = New-Object System.Text.UTF8Encoding($true)
|
||||
[System.IO.File]::WriteAllText($objFile, $text2, $utf8Bom2)
|
||||
Info " Registered form in: $objFile"
|
||||
}
|
||||
|
||||
# --- 10e. Helper: check if object is already borrowed in extension ---
|
||||
function Test-ObjectBorrowed {
|
||||
param([string]$typeName, [string]$objName)
|
||||
|
||||
$dirName = $childTypeDirMap[$typeName]
|
||||
$objFile = Join-Path (Join-Path $extDir $dirName) "${objName}.xml"
|
||||
return (Test-Path $objFile)
|
||||
}
|
||||
|
||||
# --- 11. Helper: generate InternalInfo XML ---
|
||||
function Build-InternalInfoXml {
|
||||
param([string]$typeName, [string]$objName, [string]$indent)
|
||||
@@ -534,11 +830,11 @@ $borrowedCount = 0
|
||||
foreach ($item in $items) {
|
||||
$dotIdx = $item.IndexOf(".")
|
||||
if ($dotIdx -lt 1) {
|
||||
Write-Error "Invalid format '${item}', expected 'Type.Name'"
|
||||
Write-Error "Invalid format '${item}', expected 'Type.Name' or 'Type.Name.Form.FormName'"
|
||||
exit 1
|
||||
}
|
||||
$typeName = $item.Substring(0, $dotIdx)
|
||||
$objName = $item.Substring($dotIdx + 1)
|
||||
$remainder = $item.Substring($dotIdx + 1)
|
||||
|
||||
# Resolve Russian synonym to English type name
|
||||
if ($synonymMap.ContainsKey($typeName)) { $typeName = $synonymMap[$typeName] }
|
||||
@@ -548,34 +844,71 @@ foreach ($item in $items) {
|
||||
exit 1
|
||||
}
|
||||
|
||||
$dirName = $childTypeDirMap[$typeName]
|
||||
|
||||
Info "Borrowing ${typeName}.${objName}..."
|
||||
|
||||
# Read source object
|
||||
$src = Read-SourceObject $typeName $objName
|
||||
Info " Source UUID: $($src.Uuid)"
|
||||
|
||||
# Build borrowed object XML
|
||||
$borrowedXml = Build-BorrowedObjectXml $typeName $objName $src.Uuid $src.Properties
|
||||
|
||||
# Create directory in extension if needed
|
||||
$targetDir = Join-Path $extDir $dirName
|
||||
if (-not (Test-Path $targetDir)) {
|
||||
New-Item -ItemType Directory -Path $targetDir -Force | Out-Null
|
||||
# Check for .Form. pattern: Type.ObjName.Form.FormName
|
||||
$formName = $null
|
||||
$formIdx = $remainder.IndexOf(".Form.")
|
||||
if ($formIdx -gt 0) {
|
||||
$objName = $remainder.Substring(0, $formIdx)
|
||||
$formName = $remainder.Substring($formIdx + 6) # skip ".Form."
|
||||
} else {
|
||||
$objName = $remainder
|
||||
}
|
||||
|
||||
# Write borrowed object XML with UTF-8 BOM
|
||||
$targetFile = Join-Path $targetDir "${objName}.xml"
|
||||
$enc = New-Object System.Text.UTF8Encoding($true)
|
||||
[System.IO.File]::WriteAllText($targetFile, $borrowedXml, $enc)
|
||||
Info " Created: $targetFile"
|
||||
$dirName = $childTypeDirMap[$typeName]
|
||||
|
||||
# Add to ChildObjects
|
||||
Add-ToChildObjects $typeName $objName
|
||||
if ($formName) {
|
||||
# --- Form borrowing ---
|
||||
Info "Borrowing form ${typeName}.${objName}.Form.${formName}..."
|
||||
|
||||
$borrowedFiles += $targetFile
|
||||
$borrowedCount++
|
||||
# Auto-borrow parent object if not yet borrowed
|
||||
if (-not (Test-ObjectBorrowed $typeName $objName)) {
|
||||
Info " Parent object ${typeName}.${objName} not yet borrowed — borrowing first..."
|
||||
|
||||
$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"
|
||||
|
||||
Add-ToChildObjects $typeName $objName
|
||||
$borrowedFiles += $targetFile
|
||||
}
|
||||
|
||||
# Borrow the form
|
||||
$formFiles = Borrow-Form $typeName $objName $formName
|
||||
$borrowedFiles += $formFiles
|
||||
$borrowedCount++
|
||||
} 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"
|
||||
|
||||
Add-ToChildObjects $typeName $objName
|
||||
|
||||
$borrowedFiles += $targetFile
|
||||
$borrowedCount++
|
||||
}
|
||||
}
|
||||
|
||||
# --- 15. Save modified Configuration.xml ---
|
||||
|
||||
@@ -38,9 +38,18 @@ powershell.exe -NoProfile -File .claude/skills/cfe-diff/scripts/cfe-diff.ps1 -Ex
|
||||
&ИзменениеИКонтроль("РеквизитыРедактируемыеВГрупповойОбработке") — line 4 in ...
|
||||
&Перед("ЗагрузитьКурсыВалют") — line 13 in ...
|
||||
ChildObjects: 1 own attrs, 1 own TS, 3 own forms
|
||||
Form.ФормаЭлемента (borrowed):
|
||||
Event:OnCreateAtServer [After] -> Расш1_ПриСозданииПосле
|
||||
Command:Подбор [Before] -> Расш1_ПодборПеред
|
||||
Form.Расш1_МояФорма (own)
|
||||
[OWN] Catalog.Расш5_Справочник1
|
||||
```
|
||||
|
||||
Для каждой формы заимствованного объекта показывается:
|
||||
- `(borrowed)` / `(own)` — заимствованная или собственная форма
|
||||
- callType-события формы и элементов
|
||||
- callType на командах
|
||||
|
||||
## Mode B — проверка переноса
|
||||
|
||||
Для каждого `&ИзменениеИКонтроль` извлекает блоки `#Вставка`/`#КонецВставки` из расширения и ищет их в соответствующем модуле конфигурации.
|
||||
|
||||
@@ -210,6 +210,63 @@ function Get-InsertionBlocks {
|
||||
return $blocks
|
||||
}
|
||||
|
||||
# --- Helper: analyze form for callType events and commands ---
|
||||
function Get-FormInterceptors {
|
||||
param([string]$formXmlPath)
|
||||
|
||||
if (-not (Test-Path $formXmlPath)) { return $null }
|
||||
|
||||
$formDoc = New-Object System.Xml.XmlDocument
|
||||
$formDoc.PreserveWhitespace = $false
|
||||
try { $formDoc.Load($formXmlPath) } catch { return $null }
|
||||
|
||||
$fNs = New-Object System.Xml.XmlNamespaceManager($formDoc.NameTable)
|
||||
$fNs.AddNamespace("f", "http://v8.1c.ru/8.3/xcf/logform")
|
||||
|
||||
$fRoot = $formDoc.DocumentElement
|
||||
$baseForm = $fRoot.SelectSingleNode("f:BaseForm", $fNs)
|
||||
$isBorrowed = ($baseForm -ne $null)
|
||||
|
||||
$interceptors = @()
|
||||
|
||||
# Form-level events with callType
|
||||
$eventsNode = $fRoot.SelectSingleNode("f:Events", $fNs)
|
||||
if ($eventsNode) {
|
||||
foreach ($evt in $eventsNode.SelectNodes("f:Event", $fNs)) {
|
||||
$ct = $evt.GetAttribute("callType")
|
||||
if ($ct) {
|
||||
$interceptors += "Event:$($evt.GetAttribute('name')) [$ct] -> $($evt.InnerText)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Element-level events with callType (scan all elements recursively)
|
||||
$childItems = $fRoot.SelectSingleNode("f:ChildItems", $fNs)
|
||||
if ($childItems) {
|
||||
foreach ($evtNode in $childItems.SelectNodes(".//*[f:Events/f:Event[@callType]]", $fNs)) {
|
||||
$elName = $evtNode.GetAttribute("name")
|
||||
foreach ($evt in $evtNode.SelectNodes("f:Events/f:Event[@callType]", $fNs)) {
|
||||
$ct = $evt.GetAttribute("callType")
|
||||
$interceptors += "Element:${elName}.$($evt.GetAttribute('name')) [$ct] -> $($evt.InnerText)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Commands with callType on Action
|
||||
foreach ($cmd in $fRoot.SelectNodes("f:Commands/f:Command", $fNs)) {
|
||||
$cmdName = $cmd.GetAttribute("name")
|
||||
foreach ($action in $cmd.SelectNodes("f:Action[@callType]", $fNs)) {
|
||||
$ct = $action.GetAttribute("callType")
|
||||
$interceptors += "Command:$cmdName [$ct] -> $($action.InnerText)"
|
||||
}
|
||||
}
|
||||
|
||||
return @{
|
||||
IsBorrowed = $isBorrowed
|
||||
Interceptors = $interceptors
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# MODE A: Extension overview
|
||||
# ============================================================
|
||||
@@ -255,6 +312,7 @@ if ($Mode -eq "A") {
|
||||
$ownForms = 0
|
||||
$ownTS = 0
|
||||
$borrowedItems = 0
|
||||
$formNames = @()
|
||||
foreach ($c in $childObj.ChildNodes) {
|
||||
if ($c.NodeType -ne 'Element') { continue }
|
||||
$cProps = $c.SelectSingleNode("md:Properties", $info.ObjNs)
|
||||
@@ -268,7 +326,7 @@ if ($Mode -eq "A") {
|
||||
switch ($c.LocalName) {
|
||||
"Attribute" { $ownAttrs++ }
|
||||
"TabularSection" { $ownTS++ }
|
||||
"Form" { $ownForms++ }
|
||||
"Form" { $formNames += $c.InnerText; $ownForms++ }
|
||||
}
|
||||
}
|
||||
$parts = @()
|
||||
@@ -279,6 +337,27 @@ if ($Mode -eq "A") {
|
||||
if ($parts.Count -gt 0) {
|
||||
Write-Host " ChildObjects: $($parts -join ', ')"
|
||||
}
|
||||
|
||||
# Analyze forms
|
||||
$borrowedFormCount = 0
|
||||
$ownFormCount = 0
|
||||
foreach ($fn in $formNames) {
|
||||
$formXmlPath = Join-Path (Join-Path (Join-Path (Join-Path (Join-Path $ExtensionPath $info.DirName) $info.Name) "Forms") $fn) "Ext/Form.xml"
|
||||
$fi = Get-FormInterceptors $formXmlPath
|
||||
if (-not $fi) {
|
||||
Write-Host " Form.$fn (?)"
|
||||
continue
|
||||
}
|
||||
$formTag = if ($fi.IsBorrowed) { "borrowed"; $borrowedFormCount++ } else { "own"; $ownFormCount++ }
|
||||
if ($fi.Interceptors.Count -gt 0) {
|
||||
Write-Host " Form.$fn ($formTag):"
|
||||
foreach ($ic in $fi.Interceptors) {
|
||||
Write-Host " $ic"
|
||||
}
|
||||
} else {
|
||||
Write-Host " Form.$fn ($formTag)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: cfe-init
|
||||
description: Создать расширение конфигурации 1С (CFE) — scaffold XML-исходников. Используй когда нужно создать новое расширение для исправления, доработки или дополнения конфигурации
|
||||
argument-hint: <Name> [-Purpose Patch|Customization|AddOn] [-CompatibilityMode Version8_3_24]
|
||||
argument-hint: <Name> [-ConfigPath <path>] [-Purpose Patch|Customization|AddOn] [-CompatibilityMode Version8_3_24]
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
@@ -14,13 +14,17 @@ allowed-tools:
|
||||
|
||||
## Подготовка
|
||||
|
||||
Перед созданием расширения рекомендуется получить версию и режим совместимости базовой конфигурации:
|
||||
Если есть выгрузка базовой конфигурации, передай `-ConfigPath` — скрипт автоматически определит `CompatibilityMode` и UUID языка из базовой конфигурации.
|
||||
|
||||
```
|
||||
/cf-info <ConfigPath> -Mode brief
|
||||
```
|
||||
### Авто-определение ConfigPath
|
||||
|
||||
Это даст `CompatibilityMode` (передать в `-CompatibilityMode`) и версию конфигурации (для `-Version`, например `<ВерсияКонфигурации>.1`).
|
||||
Если пользователь не указал `-ConfigPath` — попробуй определить автоматически:
|
||||
1. Прочитай `.v8-project.json` из корня проекта
|
||||
2. Разреши целевую базу (по имени, ветке или `default` — алгоритм из `/db-list`)
|
||||
3. Если у базы есть поле `configSrc` — используй как `-ConfigPath`
|
||||
4. Если `configSrc` нет — спроси у пользователя
|
||||
|
||||
Если `.v8-project.json` не найден и `-ConfigPath` не задан — расширение создастся с предупреждением (UUID языка = нули, CompatibilityMode по умолчанию).
|
||||
|
||||
## Параметры
|
||||
|
||||
@@ -34,6 +38,7 @@ allowed-tools:
|
||||
| `Version` | Версия расширения | — |
|
||||
| `Vendor` | Поставщик | — |
|
||||
| `CompatibilityMode` | Режим совместимости | `Version8_3_24` |
|
||||
| `ConfigPath` | Путь к выгрузке базовой конфигурации (авто-определяет CompatibilityMode и Language UUID) | — |
|
||||
| `NoRole` | Без основной роли | false |
|
||||
|
||||
## Команда
|
||||
@@ -56,7 +61,10 @@ powershell.exe -NoProfile -File .claude/skills/cfe-init/scripts/cfe-init.ps1 -Na
|
||||
## Примеры
|
||||
|
||||
```powershell
|
||||
# Расширение-исправление для ERP
|
||||
# Расширение для ERP с авто-определением совместимости из базовой конфигурации
|
||||
... -Name Расш1 -ConfigPath C:\WS\tasks\cfsrc\erp_8.3.24 -OutputDir src
|
||||
|
||||
# Расширение-исправление с явным режимом совместимости
|
||||
... -Name Расш1 -Purpose Patch -CompatibilityMode Version8_3_17 -OutputDir src
|
||||
|
||||
# Расширение-доработка с версией
|
||||
|
||||
@@ -11,6 +11,7 @@ param(
|
||||
[string]$Version,
|
||||
[string]$Vendor,
|
||||
[string]$CompatibilityMode = "Version8_3_24",
|
||||
[string]$ConfigPath,
|
||||
[switch]$NoRole
|
||||
)
|
||||
|
||||
@@ -34,6 +35,57 @@ if (Test-Path $cfgFile) {
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Resolve ConfigPath ---
|
||||
$baseLangUuid = "00000000-0000-0000-0000-000000000000"
|
||||
if ($ConfigPath) {
|
||||
if (-not [System.IO.Path]::IsPathRooted($ConfigPath)) {
|
||||
$ConfigPath = Join-Path (Get-Location).Path $ConfigPath
|
||||
}
|
||||
if (Test-Path $ConfigPath -PathType Container) {
|
||||
$candidate = Join-Path $ConfigPath "Configuration.xml"
|
||||
if (Test-Path $candidate) { $ConfigPath = $candidate }
|
||||
else { Write-Error "No Configuration.xml in config directory: $ConfigPath"; exit 1 }
|
||||
}
|
||||
if (-not (Test-Path $ConfigPath)) { Write-Error "Config file not found: $ConfigPath"; exit 1 }
|
||||
$cfgDir = Split-Path (Resolve-Path $ConfigPath).Path -Parent
|
||||
|
||||
# 3a. Read Language UUID from base config
|
||||
$baseLangFile = Join-Path (Join-Path $cfgDir "Languages") "Русский.xml"
|
||||
if (Test-Path $baseLangFile) {
|
||||
$baseLangDoc = New-Object System.Xml.XmlDocument
|
||||
$baseLangDoc.PreserveWhitespace = $false
|
||||
$baseLangDoc.Load($baseLangFile)
|
||||
$langEl = $null
|
||||
foreach ($c in $baseLangDoc.DocumentElement.ChildNodes) {
|
||||
if ($c.NodeType -eq 'Element' -and $c.LocalName -eq 'Language') { $langEl = $c; break }
|
||||
}
|
||||
if ($langEl) {
|
||||
$baseLangUuid = $langEl.GetAttribute("uuid")
|
||||
Write-Host "[INFO] Base config Language UUID: $baseLangUuid"
|
||||
} else {
|
||||
Write-Host "[WARN] No <Language> element in $baseLangFile"
|
||||
}
|
||||
} else {
|
||||
Write-Host "[WARN] Base config language not found: $baseLangFile"
|
||||
}
|
||||
|
||||
# 3b. Read CompatibilityMode from base config
|
||||
$baseCfgDoc = New-Object System.Xml.XmlDocument
|
||||
$baseCfgDoc.PreserveWhitespace = $false
|
||||
$baseCfgDoc.Load((Resolve-Path $ConfigPath).Path)
|
||||
$baseCfgNs = New-Object System.Xml.XmlNamespaceManager($baseCfgDoc.NameTable)
|
||||
$baseCfgNs.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
|
||||
$compatNode = $baseCfgDoc.SelectSingleNode("//md:Configuration/md:Properties/md:CompatibilityMode", $baseCfgNs)
|
||||
if ($compatNode -and $compatNode.InnerText) {
|
||||
$CompatibilityMode = $compatNode.InnerText.Trim()
|
||||
Write-Host "[INFO] Base config CompatibilityMode: $CompatibilityMode"
|
||||
} else {
|
||||
Write-Host "[WARN] CompatibilityMode not found in base config, using default: $CompatibilityMode"
|
||||
}
|
||||
} else {
|
||||
Write-Host "[WARN] Language ExtendedConfigurationObject set to zeros. Use -ConfigPath to auto-resolve from base config, or fix manually before loading."
|
||||
}
|
||||
|
||||
# --- Generate UUIDs ---
|
||||
$uuidCfg = [guid]::NewGuid().ToString()
|
||||
$uuidLang = [guid]::NewGuid().ToString()
|
||||
@@ -149,7 +201,7 @@ $langXml = @"
|
||||
<ObjectBelonging>Adopted</ObjectBelonging>
|
||||
<Name>Русский</Name>
|
||||
<Comment/>
|
||||
<ExtendedConfigurationObject>00000000-0000-0000-0000-000000000000</ExtendedConfigurationObject>
|
||||
<ExtendedConfigurationObject>$baseLangUuid</ExtendedConfigurationObject>
|
||||
<LanguageCode>ru</LanguageCode>
|
||||
</Properties>
|
||||
</Language>
|
||||
@@ -201,6 +253,7 @@ Write-Host "[OK] Создано расширение: $Name"
|
||||
Write-Host " Каталог: $OutputDir"
|
||||
Write-Host " Назначение: $Purpose"
|
||||
Write-Host " Префикс: $NamePrefix"
|
||||
Write-Host " Совместимость: $CompatibilityMode"
|
||||
Write-Host " Configuration.xml: $cfgFile"
|
||||
Write-Host " Languages: $langFile"
|
||||
if (-not $NoRole) {
|
||||
|
||||
@@ -157,6 +157,21 @@ $bslCode += "$endKeyword"
|
||||
|
||||
$bslText = ($bslCode -join "`r`n") + "`r`n"
|
||||
|
||||
# --- Check form borrowing for .Form. paths ---
|
||||
if ($parts.Count -ge 4 -and $parts[2] -eq "Form") {
|
||||
$formName = $parts[3]
|
||||
$dirName = $typeDirMap[$objType]
|
||||
$formMetaFile = Join-Path (Join-Path (Join-Path (Join-Path $ExtensionPath $dirName) $objName) "Forms") "${formName}.xml"
|
||||
$formXmlFile = Join-Path (Join-Path (Join-Path (Join-Path (Join-Path $ExtensionPath $dirName) $objName) "Forms") $formName) "Ext/Form.xml"
|
||||
|
||||
if (-not (Test-Path $formMetaFile) -or -not (Test-Path $formXmlFile)) {
|
||||
Write-Host "[WARN] Form '$formName' metadata or Form.xml not found in extension."
|
||||
Write-Host " Run /cfe-borrow first:"
|
||||
Write-Host " /cfe-borrow -ExtensionPath $ExtensionPath -ConfigPath <ConfigPath> -Object `"$objType.$objName.Form.$formName`""
|
||||
Write-Host ""
|
||||
}
|
||||
}
|
||||
|
||||
# --- Check if file exists and append ---
|
||||
$bslDir = Split-Path $bslFile -Parent
|
||||
if (-not (Test-Path $bslDir)) {
|
||||
|
||||
@@ -271,7 +271,11 @@ if ($Purpose -eq "List" -or $Purpose -eq "Choice") {
|
||||
"@
|
||||
}
|
||||
|
||||
[System.IO.File]::WriteAllText($formXmlPath, $formXml, $encBom)
|
||||
if (Test-Path $formXmlPath) {
|
||||
Write-Host "[SKIP] Form.xml already exists: $formXmlPath — not overwriting"
|
||||
} else {
|
||||
[System.IO.File]::WriteAllText($formXmlPath, $formXml, $encBom)
|
||||
}
|
||||
|
||||
# --- 3c. Module.bsl ---
|
||||
|
||||
@@ -304,7 +308,11 @@ $moduleBsl = @"
|
||||
#КонецОбласти
|
||||
"@
|
||||
|
||||
[System.IO.File]::WriteAllText($modulePath, $moduleBsl, $encBom)
|
||||
if (Test-Path $modulePath) {
|
||||
Write-Host "[SKIP] Module.bsl already exists: $modulePath — not overwriting"
|
||||
} else {
|
||||
[System.IO.File]::WriteAllText($modulePath, $moduleBsl, $encBom)
|
||||
}
|
||||
|
||||
# --- Фаза 4: Регистрация в родительском объекте ---
|
||||
|
||||
|
||||
@@ -395,6 +395,12 @@ powershell.exe -NoProfile -File .claude/skills/form-compile/scripts/form-compile
|
||||
- **ID**: последовательная нумерация, AutoCommandBar = id="-1"
|
||||
- **Unknown keys**: выводится предупреждение о нераспознанных ключах
|
||||
|
||||
## Workflow
|
||||
|
||||
1. **Компиляция**: `/form-compile` генерирует `Form.xml` и автоматически регистрирует `<Form>` в `ChildObjects` родительского объекта (если OutputPath следует конвенции `.../TypePlural/ObjectName/Forms/FormName/Ext/Form.xml`).
|
||||
2. **Метаданные формы** (`ФормаСписка.xml`) и `Module.bsl` создаёт `/form-add`. Если `/form-add` ещё не вызывался — вызови после `/form-compile`. Он не перезаписывает существующий Form.xml.
|
||||
3. **Проверка**: `/form-validate`, `/form-info`.
|
||||
|
||||
## Верификация
|
||||
|
||||
```
|
||||
|
||||
@@ -1137,6 +1137,71 @@ if (-not (Test-Path $outDir)) {
|
||||
$enc = New-Object System.Text.UTF8Encoding($true)
|
||||
[System.IO.File]::WriteAllText($outPath, $xml.ToString(), $enc)
|
||||
|
||||
# --- 13b. Auto-register form in parent object XML ---
|
||||
|
||||
# Infer parent from OutputPath: .../TypePlural/ObjectName/Forms/FormName/Ext/Form.xml
|
||||
$formXmlDir = [System.IO.Path]::GetDirectoryName($outPath)
|
||||
$formNameDir = [System.IO.Path]::GetDirectoryName($formXmlDir)
|
||||
$formsDir = [System.IO.Path]::GetDirectoryName($formNameDir)
|
||||
$objectDir = [System.IO.Path]::GetDirectoryName($formsDir)
|
||||
$typePluralDir = [System.IO.Path]::GetDirectoryName($objectDir)
|
||||
|
||||
$formName = [System.IO.Path]::GetFileName($formNameDir)
|
||||
$objectName = [System.IO.Path]::GetFileName($objectDir)
|
||||
$formsLeaf = [System.IO.Path]::GetFileName($formsDir)
|
||||
|
||||
if ($formsLeaf -eq 'Forms') {
|
||||
$objectXmlPath = Join-Path $typePluralDir "$objectName.xml"
|
||||
if (Test-Path $objectXmlPath) {
|
||||
$objDoc = New-Object System.Xml.XmlDocument
|
||||
$objDoc.PreserveWhitespace = $true
|
||||
$objDoc.Load($objectXmlPath)
|
||||
|
||||
$nsMgr = New-Object System.Xml.XmlNamespaceManager($objDoc.NameTable)
|
||||
$nsMgr.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
|
||||
|
||||
$childObjects = $objDoc.SelectSingleNode("//md:ChildObjects", $nsMgr)
|
||||
if ($childObjects) {
|
||||
$existing = $childObjects.SelectSingleNode("md:Form[text()='$formName']", $nsMgr)
|
||||
if (-not $existing) {
|
||||
$formElem = $objDoc.CreateElement("Form", "http://v8.1c.ru/8.3/MDClasses")
|
||||
$formElem.InnerText = $formName
|
||||
|
||||
$insertBefore = $childObjects.SelectSingleNode("md:Template", $nsMgr)
|
||||
if (-not $insertBefore) { $insertBefore = $childObjects.SelectSingleNode("md:TabularSection", $nsMgr) }
|
||||
|
||||
if ($insertBefore) {
|
||||
$childObjects.InsertBefore($formElem, $insertBefore) | Out-Null
|
||||
$ws = $objDoc.CreateWhitespace("`n`t`t`t")
|
||||
$childObjects.InsertBefore($ws, $insertBefore) | Out-Null
|
||||
} else {
|
||||
$lastChild = $childObjects.LastChild
|
||||
if ($lastChild -and $lastChild.NodeType -eq [System.Xml.XmlNodeType]::Whitespace) {
|
||||
$childObjects.InsertBefore($objDoc.CreateWhitespace("`n`t`t`t"), $lastChild) | Out-Null
|
||||
$childObjects.InsertBefore($formElem, $lastChild) | Out-Null
|
||||
} else {
|
||||
$childObjects.AppendChild($objDoc.CreateWhitespace("`n`t`t`t")) | Out-Null
|
||||
$childObjects.AppendChild($formElem) | Out-Null
|
||||
$childObjects.AppendChild($objDoc.CreateWhitespace("`n`t`t")) | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
$regEnc = New-Object System.Text.UTF8Encoding($true)
|
||||
$regSettings = New-Object System.Xml.XmlWriterSettings
|
||||
$regSettings.Encoding = $regEnc
|
||||
$regSettings.Indent = $false
|
||||
$regStream = New-Object System.IO.FileStream($objectXmlPath, [System.IO.FileMode]::Create)
|
||||
$regWriter = [System.Xml.XmlWriter]::Create($regStream, $regSettings)
|
||||
$objDoc.Save($regWriter)
|
||||
$regWriter.Close()
|
||||
$regStream.Close()
|
||||
|
||||
Write-Host " Registered: <Form>$formName</Form> in $objectName.xml"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# --- 14. Summary ---
|
||||
|
||||
$elCount = $script:nextId - 1
|
||||
|
||||
@@ -50,6 +50,32 @@ powershell.exe -NoProfile -File .claude/skills/form-edit/scripts/form-edit.ps1 -
|
||||
}
|
||||
```
|
||||
|
||||
### Расширения (extension-формы)
|
||||
|
||||
Для заимствованных форм (с `<BaseForm>`) автоматически активируется extension-режим: ID начинаются с 1000000+. Доступны дополнительные секции:
|
||||
|
||||
```json
|
||||
{
|
||||
"formEvents": [
|
||||
{ "name": "OnCreateAtServer", "handler": "Расш1_ПриСозданииПосле", "callType": "After" },
|
||||
{ "name": "OnOpen", "handler": "Расш1_ПриОткрытии", "callType": "Before" }
|
||||
],
|
||||
"elementEvents": [
|
||||
{ "element": "Банк", "name": "OnChange", "handler": "Расш1_БанкПриИзменении", "callType": "Before" }
|
||||
],
|
||||
"commands": [
|
||||
{ "name": "Подбор", "action": "Расш1_ПодборПосле", "callType": "After" },
|
||||
{ "name": "Запрос", "actions": [
|
||||
{ "callType": "Before", "handler": "Расш1_ЗапросПеред" },
|
||||
{ "callType": "After", "handler": "Расш1_ЗапросПосле" }
|
||||
]}
|
||||
],
|
||||
"elements": [
|
||||
{ "input": "Поле", "path": "Объект.Поле", "on": [{ "event": "OnChange", "callType": "After" }] }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Позиционирование элементов
|
||||
|
||||
| Ключ | По умолчанию | Описание |
|
||||
@@ -96,19 +122,35 @@ powershell.exe -NoProfile -File .claude/skills/form-edit/scripts/form-edit.ps1 -
|
||||
|
||||
`string`, `string(100)`, `decimal(15,2)`, `boolean`, `date`, `dateTime`, `CatalogRef.XXX`, `DocumentObject.XXX`, `ValueTable`, `DynamicList`, `Type1 | Type2` (составной).
|
||||
|
||||
### Секции расширений
|
||||
|
||||
| Секция | Назначение |
|
||||
|--------|-----------|
|
||||
| `formEvents` | События уровня формы с `callType` (Before/After/Override) |
|
||||
| `elementEvents` | События на существующих элементах заимствованной формы |
|
||||
| `callType` на `commands` | callType на Action команды |
|
||||
| `callType` на `on` | callType на событиях новых элементов (объектный формат) |
|
||||
|
||||
Все extension-секции опциональны — без них навык работает как с обычными формами.
|
||||
|
||||
## Вывод
|
||||
|
||||
```
|
||||
=== form-edit: Форма ===
|
||||
|
||||
[EXTENSION] BaseForm detected — IDs start at 1000000+
|
||||
|
||||
Added form events:
|
||||
+ OnCreateAtServer[After] -> Расш1_ПриСозданииПосле
|
||||
|
||||
Added elements (into ГруппаШапка, after Контрагент):
|
||||
+ [Input] Склад -> Объект.Склад {OnChange}
|
||||
|
||||
Added attributes:
|
||||
+ СуммаИтого: decimal(15,2) (id=12)
|
||||
+ СуммаИтого: decimal(15,2) (id=1000000)
|
||||
|
||||
---
|
||||
Total: 1 element(s) (+2 companions), 1 attribute(s)
|
||||
Total: 1 form event(s), 1 element(s) (+2 companions), 1 attribute(s)
|
||||
Run /form-validate to verify.
|
||||
```
|
||||
|
||||
|
||||
@@ -110,6 +110,16 @@ $script:nextElemId++
|
||||
$script:nextAttrId++
|
||||
$script:nextCmdId++
|
||||
|
||||
# --- 4b. Auto-detect extension mode (BaseForm present) ---
|
||||
$script:isExtension = $false
|
||||
$baseForm = $root.SelectSingleNode("f:BaseForm", $nsMgr)
|
||||
if ($baseForm) {
|
||||
$script:isExtension = $true
|
||||
if ($script:nextAttrId -lt 1000000) { $script:nextAttrId = 1000000 }
|
||||
if ($script:nextCmdId -lt 1000000) { $script:nextCmdId = 1000000 }
|
||||
if ($script:nextElemId -lt 1000000) { $script:nextElemId = 1000000 }
|
||||
}
|
||||
|
||||
function New-ElemId { $id = $script:nextElemId; $script:nextElemId++; return $id }
|
||||
function New-AttrId { $id = $script:nextAttrId; $script:nextAttrId++; return $id }
|
||||
function New-CmdId { $id = $script:nextCmdId; $script:nextCmdId++; return $id }
|
||||
@@ -260,18 +270,29 @@ function Emit-Events {
|
||||
if ($typeKey -and $script:knownEvents.ContainsKey($typeKey)) {
|
||||
$allowed = $script:knownEvents[$typeKey]
|
||||
foreach ($evt in $el.on) {
|
||||
if ($allowed.Count -gt 0 -and $allowed -notcontains "$evt") {
|
||||
Write-Host "[WARN] Unknown event '$evt' for $typeKey '$elementName'. Known: $($allowed -join ', ')"
|
||||
$evtStr = if ($evt -is [string]) { "$evt" } else { "$($evt.event)" }
|
||||
if ($allowed.Count -gt 0 -and $allowed -notcontains $evtStr) {
|
||||
Write-Host "[WARN] Unknown event '$evtStr' for $typeKey '$elementName'. Known: $($allowed -join ', ')"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
X "$indent<Events>"
|
||||
foreach ($evt in $el.on) {
|
||||
$evtName = "$evt"
|
||||
$handler = if ($el.handlers -and $el.handlers.$evtName) { "$($el.handlers.$evtName)" }
|
||||
else { Get-HandlerName -elementName $elementName -eventName $evtName }
|
||||
X "$indent`t<Event name=`"$evtName`">$handler</Event>"
|
||||
# Support both string ("OnChange") and object ({ "event": "OnChange", "callType": "After" })
|
||||
if ($evt -is [string] -or -not $evt.event) {
|
||||
$evtName = "$evt"
|
||||
$handler = if ($el.handlers -and $el.handlers.$evtName) { "$($el.handlers.$evtName)" }
|
||||
else { Get-HandlerName -elementName $elementName -eventName $evtName }
|
||||
X "$indent`t<Event name=`"$evtName`">$handler</Event>"
|
||||
} else {
|
||||
$evtName = "$($evt.event)"
|
||||
$handler = if ($evt.handler) { "$($evt.handler)" }
|
||||
elseif ($el.handlers -and $el.handlers.$evtName) { "$($el.handlers.$evtName)" }
|
||||
else { Get-HandlerName -elementName $elementName -eventName $evtName }
|
||||
$callTypeAttr = if ($evt.callType) { " callType=`"$($evt.callType)`"" } else { "" }
|
||||
X "$indent`t<Event name=`"$evtName`"$callTypeAttr>$handler</Event>"
|
||||
}
|
||||
}
|
||||
X "$indent</Events>"
|
||||
}
|
||||
@@ -964,7 +985,20 @@ if ($def.commands -and $def.commands.Count -gt 0) {
|
||||
$inner = "$cmdChildIndent`t"
|
||||
|
||||
if ($cmd.title) { Emit-MLText -tag "Title" -text "$($cmd.title)" -indent $inner }
|
||||
if ($cmd.action) { X "$inner<Action>$($cmd.action)</Action>" }
|
||||
|
||||
# Support single action with optional callType, or multiple actions
|
||||
if ($cmd.actions) {
|
||||
# Multiple actions: [{ "callType": "Before", "handler": "..." }, ...]
|
||||
foreach ($act in $cmd.actions) {
|
||||
$actHandler = "$($act.handler)"
|
||||
$callTypeAttr = if ($act.callType) { " callType=`"$($act.callType)`"" } else { "" }
|
||||
X "$inner<Action$callTypeAttr>$actHandler</Action>"
|
||||
}
|
||||
} elseif ($cmd.action) {
|
||||
$callTypeAttr = if ($cmd.callType) { " callType=`"$($cmd.callType)`"" } else { "" }
|
||||
X "$inner<Action$callTypeAttr>$($cmd.action)</Action>"
|
||||
}
|
||||
|
||||
if ($cmd.shortcut) { X "$inner<Shortcut>$($cmd.shortcut)</Shortcut>" }
|
||||
if ($cmd.picture) {
|
||||
X "$inner<Picture>"
|
||||
@@ -975,7 +1009,7 @@ if ($def.commands -and $def.commands.Count -gt 0) {
|
||||
if ($cmd.representation) { X "$inner<Representation>$($cmd.representation)</Representation>" }
|
||||
|
||||
X "$cmdChildIndent</Command>"
|
||||
$actionStr = if ($cmd.action) { " -> $($cmd.action)" } else { "" }
|
||||
$actionStr = if ($cmd.action) { " -> $($cmd.action)" } elseif ($cmd.actions) { " -> $($cmd.actions.Count) action(s)" } else { "" }
|
||||
$addedCmds += " + ${cmdName}${actionStr} (id=$cmdId)"
|
||||
}
|
||||
X "</_F>"
|
||||
@@ -988,6 +1022,127 @@ if ($def.commands -and $def.commands.Count -gt 0) {
|
||||
}
|
||||
}
|
||||
|
||||
# === 12b. Add form-level events ===
|
||||
|
||||
$addedFormEvents = @()
|
||||
|
||||
if ($def.formEvents -and $def.formEvents.Count -gt 0) {
|
||||
$eventsSection = $root.SelectSingleNode("f:Events", $nsMgr)
|
||||
if (-not $eventsSection) {
|
||||
# Create Events section — insert after AutoCommandBar or at the beginning
|
||||
$eventsSection = $xmlDoc.CreateElement("Events", $formNs)
|
||||
$insertAfter = $root.SelectSingleNode("f:AutoCommandBar", $nsMgr)
|
||||
if ($insertAfter) {
|
||||
$refNode = $insertAfter
|
||||
$ws = $xmlDoc.CreateWhitespace("`r`n`t")
|
||||
# Insert before the AutoCommandBar (Events come before AutoCommandBar in 1C)
|
||||
$root.InsertBefore($ws, $refNode) | Out-Null
|
||||
$root.InsertBefore($eventsSection, $refNode) | Out-Null
|
||||
} else {
|
||||
$firstChild = $root.FirstChild
|
||||
if ($firstChild) {
|
||||
$ws = $xmlDoc.CreateWhitespace("`r`n`t")
|
||||
$root.InsertBefore($eventsSection, $firstChild) | Out-Null
|
||||
$root.InsertBefore($ws, $eventsSection) | Out-Null
|
||||
} else {
|
||||
$root.AppendChild($xmlDoc.CreateWhitespace("`r`n`t")) | Out-Null
|
||||
$root.AppendChild($eventsSection) | Out-Null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$evtChildIndent = Get-ChildIndent $eventsSection
|
||||
if (-not $evtChildIndent -or $evtChildIndent -eq "") { $evtChildIndent = "`t`t" }
|
||||
|
||||
# Generate event fragments
|
||||
$script:xml = New-Object System.Text.StringBuilder 512
|
||||
X "<_F $allNsDecl>"
|
||||
foreach ($fe in $def.formEvents) {
|
||||
$feName = "$($fe.name)"
|
||||
$feHandler = "$($fe.handler)"
|
||||
$callTypeAttr = if ($fe.callType) { " callType=`"$($fe.callType)`"" } else { "" }
|
||||
X "$evtChildIndent<Event name=`"$feName`"$callTypeAttr>$feHandler</Event>"
|
||||
$ctStr = if ($fe.callType) { "[$($fe.callType)]" } else { "" }
|
||||
$addedFormEvents += " + $feName${ctStr} -> $feHandler"
|
||||
}
|
||||
X "</_F>"
|
||||
|
||||
$fragDoc = Parse-Fragment $script:xml.ToString()
|
||||
$importedEvents = Import-ElementNodes $fragDoc
|
||||
|
||||
foreach ($node in $importedEvents) {
|
||||
Insert-IntoContainer -container $eventsSection -newNode $node -afterName $null -childIndent $evtChildIndent
|
||||
}
|
||||
}
|
||||
|
||||
# === 12c. Add element-level events ===
|
||||
|
||||
$addedElemEvents = @()
|
||||
|
||||
if ($def.elementEvents -and $def.elementEvents.Count -gt 0) {
|
||||
if (-not $rootCI) {
|
||||
$rootCI = $root.SelectSingleNode("f:ChildItems", $nsMgr)
|
||||
}
|
||||
|
||||
foreach ($ee in $def.elementEvents) {
|
||||
$targetName = "$($ee.element)"
|
||||
$targetEl = Find-Element $rootCI $targetName
|
||||
if (-not $targetEl) {
|
||||
Write-Host "[WARN] Element '$targetName' not found — skipping elementEvent"
|
||||
continue
|
||||
}
|
||||
|
||||
# Find or create Events element within the target
|
||||
$targetEvents = $targetEl.SelectSingleNode("f:Events", $nsMgr)
|
||||
if (-not $targetEvents) {
|
||||
$targetEvents = $xmlDoc.CreateElement("Events", $formNs)
|
||||
# Insert Events before closing tag (after last property, before ChildItems if any)
|
||||
$ciNode = $targetEl.SelectSingleNode("f:ChildItems", $nsMgr)
|
||||
if ($ciNode) {
|
||||
$ws = $xmlDoc.CreateWhitespace("`r`n" + (Get-ChildIndent $targetEl))
|
||||
$targetEl.InsertBefore($ws, $ciNode) | Out-Null
|
||||
$targetEl.InsertBefore($targetEvents, $ciNode) | Out-Null
|
||||
} else {
|
||||
$trailing = $targetEl.LastChild
|
||||
if ($trailing -and ($trailing.NodeType -eq 'Whitespace' -or $trailing.NodeType -eq 'SignificantWhitespace')) {
|
||||
$ws = $xmlDoc.CreateWhitespace("`r`n" + (Get-ChildIndent $targetEl))
|
||||
$targetEl.InsertBefore($ws, $trailing) | Out-Null
|
||||
$targetEl.InsertBefore($targetEvents, $trailing) | Out-Null
|
||||
} else {
|
||||
$targetEl.AppendChild($xmlDoc.CreateWhitespace("`r`n" + (Get-ChildIndent $targetEl))) | Out-Null
|
||||
$targetEl.AppendChild($targetEvents) | Out-Null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$eeChildIndent = Get-ChildIndent $targetEvents
|
||||
if (-not $eeChildIndent -or $eeChildIndent -eq "") {
|
||||
$parentIndent = Get-ChildIndent $targetEl
|
||||
$eeChildIndent = "$parentIndent`t"
|
||||
}
|
||||
|
||||
# Create Event element
|
||||
$eeName = "$($ee.name)"
|
||||
$eeHandler = "$($ee.handler)"
|
||||
$callTypeAttr = if ($ee.callType) { " callType=`"$($ee.callType)`"" } else { "" }
|
||||
|
||||
$script:xml = New-Object System.Text.StringBuilder 256
|
||||
X "<_F $allNsDecl>"
|
||||
X "$eeChildIndent<Event name=`"$eeName`"$callTypeAttr>$eeHandler</Event>"
|
||||
X "</_F>"
|
||||
|
||||
$fragDoc = Parse-Fragment $script:xml.ToString()
|
||||
$importedEE = Import-ElementNodes $fragDoc
|
||||
|
||||
foreach ($node in $importedEE) {
|
||||
Insert-IntoContainer -container $targetEvents -newNode $node -afterName $null -childIndent $eeChildIndent
|
||||
}
|
||||
|
||||
$ctStr = if ($ee.callType) { "[$($ee.callType)]" } else { "" }
|
||||
$addedElemEvents += " + $targetName.$eeName${ctStr} -> $eeHandler"
|
||||
}
|
||||
}
|
||||
|
||||
# === 13. Save ===
|
||||
|
||||
$content = $xmlDoc.OuterXml
|
||||
@@ -999,6 +1154,23 @@ $enc = New-Object System.Text.UTF8Encoding($true)
|
||||
|
||||
# === 14. Summary ===
|
||||
|
||||
if ($script:isExtension) {
|
||||
Write-Host "[EXTENSION] BaseForm detected — IDs start at 1000000+"
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
if ($addedFormEvents.Count -gt 0) {
|
||||
Write-Host "Added form events:"
|
||||
foreach ($line in $addedFormEvents) { Write-Host $line }
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
if ($addedElemEvents.Count -gt 0) {
|
||||
Write-Host "Added element events:"
|
||||
foreach ($line in $addedElemEvents) { Write-Host $line }
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
if ($addedElems.Count -gt 0) {
|
||||
$posStr = ""
|
||||
if ($def.into) { $posStr += "into $($def.into)" }
|
||||
@@ -1023,6 +1195,8 @@ if ($addedCmds.Count -gt 0) {
|
||||
|
||||
Write-Host "---"
|
||||
$totalParts = @()
|
||||
if ($addedFormEvents.Count -gt 0) { $totalParts += "$($addedFormEvents.Count) form event(s)" }
|
||||
if ($addedElemEvents.Count -gt 0) { $totalParts += "$($addedElemEvents.Count) element event(s)" }
|
||||
if ($addedElems.Count -gt 0) {
|
||||
$compStr = if ($companionCount -gt 0) { " (+$companionCount companions)" } else { "" }
|
||||
$totalParts += "$($addedElems.Count) element(s)$compStr"
|
||||
|
||||
@@ -45,6 +45,11 @@ powershell.exe -NoProfile -File .claude/skills/form-info/scripts/form-info.ps1 -
|
||||
=== Form: ФормаДокумента — "Реализация товаров и услуг" (Documents.РеализацияТоваровУслуг) ===
|
||||
```
|
||||
|
||||
Для заимствованных форм расширения (с `<BaseForm>`):
|
||||
```
|
||||
=== Form: ФормаЭлемента [EXTENSION] (Catalogs.Валюты) ===
|
||||
```
|
||||
|
||||
Имя формы, заголовок (Title) и контекст объекта определяются из пути к файлу и XML.
|
||||
|
||||
### Properties — свойства формы
|
||||
@@ -63,6 +68,13 @@ Events:
|
||||
OnOpen -> ПриОткрытии
|
||||
```
|
||||
|
||||
Для расширений с callType:
|
||||
```
|
||||
Events:
|
||||
OnCreateAtServer[After] -> Расш1_ПриСозданииПосле
|
||||
OnOpen[Before] -> Расш1_ПриОткрытии
|
||||
```
|
||||
|
||||
### Elements — дерево UI-элементов
|
||||
|
||||
Компактное дерево с типами, привязками к данным, флагами и событиями:
|
||||
@@ -114,7 +126,7 @@ Elements:
|
||||
|
||||
**Привязка к команде**: `-> ИмяКоманды [cmd]` — команда формы, `-> Close [std]` — стандартная команда
|
||||
|
||||
**События**: `{OnChange, StartChoice}` — имена обработчиков
|
||||
**События**: `{OnChange, StartChoice}` — имена обработчиков; `{OnChange[Before]}` — с callType для расширений
|
||||
|
||||
**Заголовок**: `[title:Текст]` — только если отличается от имени элемента
|
||||
|
||||
@@ -151,8 +163,21 @@ Commands:
|
||||
Заполнить -> ЗаполнитьОбработка
|
||||
```
|
||||
|
||||
Для расширений с callType на Action:
|
||||
```
|
||||
Commands:
|
||||
Подбор -> Расш1_ПодборПеред[Before], Расш1_ПодборПосле[After]
|
||||
```
|
||||
|
||||
Формат: `Имя -> Обработчик [Сочетание]`
|
||||
|
||||
### BaseForm (расширения)
|
||||
|
||||
Для заимствованных форм в конце выводится:
|
||||
```
|
||||
BaseForm: present (version 2.17)
|
||||
```
|
||||
|
||||
## Что пропускается
|
||||
|
||||
Скрипт убирает 80%+ XML-объёма:
|
||||
|
||||
@@ -35,6 +35,10 @@ $ns.AddNamespace("dcsset", "http://v8.1c.ru/8.1/data-composition-system/settings
|
||||
|
||||
$root = $xmlDoc.DocumentElement
|
||||
|
||||
# --- Detect extension (BaseForm) ---
|
||||
$baseFormNode = $root.SelectSingleNode("d:BaseForm", $ns)
|
||||
$isExtension = ($baseFormNode -ne $null)
|
||||
|
||||
# --- Helper: extract multilang text ---
|
||||
|
||||
function Get-MLText($node) {
|
||||
@@ -140,7 +144,10 @@ function Get-EventsStr($node) {
|
||||
if (-not $eventsNode) { return "" }
|
||||
$evts = @()
|
||||
foreach ($e in $eventsNode.SelectNodes("d:Event", $ns)) {
|
||||
$evts += $e.GetAttribute("name")
|
||||
$eName = $e.GetAttribute("name")
|
||||
$ct = $e.GetAttribute("callType")
|
||||
if ($ct) { $evts += "$eName[$ct]" }
|
||||
else { $evts += $eName }
|
||||
}
|
||||
if ($evts.Count -eq 0) { return "" }
|
||||
return " {$($evts -join ', ')}"
|
||||
@@ -340,7 +347,8 @@ if ($titleNode) {
|
||||
$formTitle = Get-MLText $titleNode
|
||||
if (-not $formTitle) { $formTitle = $titleNode.InnerText }
|
||||
}
|
||||
$header = "=== Form: $formName"
|
||||
$extMarker = if ($isExtension) { " [EXTENSION]" } else { "" }
|
||||
$header = "=== Form: $formName$extMarker"
|
||||
if ($formTitle) { $header += " — `"$formTitle`"" }
|
||||
if ($objectContext) { $header += " ($objectContext)" }
|
||||
$header += " ==="
|
||||
@@ -390,7 +398,9 @@ if ($formEvents -and $formEvents.HasChildNodes) {
|
||||
foreach ($e in $formEvents.SelectNodes("d:Event", $ns)) {
|
||||
$eName = $e.GetAttribute("name")
|
||||
$eHandler = $e.InnerText
|
||||
$lines += " $eName -> $eHandler"
|
||||
$ct = $e.GetAttribute("callType")
|
||||
$ctStr = if ($ct) { "[$ct]" } else { "" }
|
||||
$lines += " $eName${ctStr} -> $eHandler"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -491,12 +501,27 @@ if ($cmdsNode) {
|
||||
$cmdLines = @()
|
||||
foreach ($cmd in $cmdsNode.SelectNodes("d:Command", $ns)) {
|
||||
$cName = $cmd.GetAttribute("name")
|
||||
$action = $cmd.SelectSingleNode("d:Action", $ns)
|
||||
$shortcut = $cmd.SelectSingleNode("d:Shortcut", $ns)
|
||||
|
||||
$actionStr = if ($action) { " -> $($action.InnerText)" } else { "" }
|
||||
$scStr = if ($shortcut) { " [$($shortcut.InnerText)]" } else { "" }
|
||||
|
||||
# Collect all Action elements (may have multiple with callType)
|
||||
$actions = $cmd.SelectNodes("d:Action", $ns)
|
||||
if ($actions.Count -gt 1) {
|
||||
$actParts = @()
|
||||
foreach ($a in $actions) {
|
||||
$ct = $a.GetAttribute("callType")
|
||||
$ctStr = if ($ct) { "[$ct]" } else { "" }
|
||||
$actParts += "$($a.InnerText)$ctStr"
|
||||
}
|
||||
$actionStr = " -> $($actParts -join ', ')"
|
||||
} elseif ($actions.Count -eq 1) {
|
||||
$ct = $actions[0].GetAttribute("callType")
|
||||
$ctStr = if ($ct) { "[$ct]" } else { "" }
|
||||
$actionStr = " -> $($actions[0].InnerText)$ctStr"
|
||||
} else {
|
||||
$actionStr = ""
|
||||
}
|
||||
|
||||
$cmdLines += " $cName$actionStr$scStr"
|
||||
}
|
||||
if ($cmdLines.Count -gt 0) {
|
||||
@@ -506,6 +531,15 @@ if ($cmdsNode) {
|
||||
}
|
||||
}
|
||||
|
||||
# --- BaseForm footer ---
|
||||
|
||||
if ($isExtension) {
|
||||
$bfVersion = $baseFormNode.GetAttribute("version")
|
||||
$bfStr = if ($bfVersion) { "present (version $bfVersion)" } else { "present" }
|
||||
$lines += ""
|
||||
$lines += "BaseForm: $bfStr"
|
||||
}
|
||||
|
||||
# --- Truncation protection ---
|
||||
|
||||
$totalLines = $lines.Count
|
||||
|
||||
@@ -46,6 +46,10 @@ powershell.exe -NoProfile -File .claude/skills/form-validate/scripts/form-valida
|
||||
| 9 | События имеют непустые имена обработчиков | ERROR |
|
||||
| 10 | Команды имеют Action (обработчик) | ERROR |
|
||||
| 11 | Не более одного MainAttribute | ERROR |
|
||||
| 12 | BaseForm: наличие и version (при расширении) | OK / WARN |
|
||||
| 13 | callType значения: Before, After, Override | ERROR |
|
||||
| 14 | ID расширения >= 1000000 для добавленных attrs/commands | WARN |
|
||||
| 15 | callType без BaseForm — некорректная структура | WARN |
|
||||
|
||||
## Вывод
|
||||
|
||||
@@ -71,8 +75,18 @@ All checks passed.
|
||||
|
||||
Код возврата: 0 = все проверки пройдены, 1 = есть ошибки.
|
||||
|
||||
### Расширения
|
||||
|
||||
При обнаружении `<BaseForm>` автоматически активируются дополнительные проверки:
|
||||
- Валидность значений `callType` (Before/After/Override)
|
||||
- ID расширения >= 1000000 для добавленных атрибутов и команд
|
||||
- Наличие version на `<BaseForm>`
|
||||
|
||||
Формы без `<BaseForm>` проверяются только стандартными проверками.
|
||||
|
||||
## Когда использовать
|
||||
|
||||
- **После `/form-compile`**: проверить корректность сгенерированной формы
|
||||
- **После `/form-edit`**: проверить добавленные элементы, особенно в extension-формах
|
||||
- **После ручного редактирования Form.xml**: убедиться что ID уникальны, companions на месте, ссылки валидны
|
||||
- **При отладке**: выявить ошибки в структуре формы до сборки EPF
|
||||
|
||||
@@ -76,6 +76,9 @@ if ($parentDir) {
|
||||
Write-Host "=== Validation: $formName ==="
|
||||
Write-Host ""
|
||||
|
||||
# Early BaseForm detection (used in Check 5 to skip base element DataPath validation)
|
||||
$hasBaseForm = ($root.SelectSingleNode("f:BaseForm", $nsMgr) -ne $null)
|
||||
|
||||
# --- Check 1: Root element and version ---
|
||||
|
||||
if ($root.LocalName -ne "Form") {
|
||||
@@ -297,6 +300,7 @@ if (-not $stopped) {
|
||||
if (-not $stopped) {
|
||||
$pathErrors = 0
|
||||
$pathChecked = 0
|
||||
$pathBaseSkipped = 0
|
||||
|
||||
foreach ($el in $allElements) {
|
||||
if ($stopped) { break }
|
||||
@@ -309,6 +313,11 @@ if (-not $stopped) {
|
||||
continue
|
||||
}
|
||||
|
||||
# In borrowed forms, skip DataPath check for base elements (id < 1000000)
|
||||
if ($hasBaseForm -and $el.Id) {
|
||||
try { if ([int]$el.Id -lt 1000000) { $pathBaseSkipped++; continue } } catch {}
|
||||
}
|
||||
|
||||
$dpNode = $node.SelectSingleNode("f:DataPath", $nsMgr)
|
||||
if (-not $dpNode) { continue }
|
||||
|
||||
@@ -328,9 +337,15 @@ if (-not $stopped) {
|
||||
}
|
||||
}
|
||||
|
||||
if ($pathErrors -eq 0 -and $pathChecked -gt 0) {
|
||||
Report-OK "DataPath references: $pathChecked paths checked"
|
||||
} elseif ($pathChecked -eq 0) {
|
||||
$pathMsg = ""
|
||||
if ($pathChecked -gt 0) { $pathMsg = "$pathChecked paths checked" }
|
||||
if ($pathBaseSkipped -gt 0) {
|
||||
$skipNote = "$pathBaseSkipped base skipped"
|
||||
$pathMsg = if ($pathMsg) { "$pathMsg, $skipNote" } else { $skipNote }
|
||||
}
|
||||
if ($pathErrors -eq 0 -and $pathMsg) {
|
||||
Report-OK "DataPath references: $pathMsg"
|
||||
} elseif ($pathErrors -eq 0) {
|
||||
Report-OK "DataPath references: none"
|
||||
}
|
||||
}
|
||||
@@ -479,6 +494,152 @@ if (-not $stopped) {
|
||||
}
|
||||
}
|
||||
|
||||
# --- Check 11: Extension-specific validations ---
|
||||
|
||||
$baseFormNode = $root.SelectSingleNode("f:BaseForm", $nsMgr)
|
||||
$isExtension = ($baseFormNode -ne $null)
|
||||
|
||||
if (-not $stopped -and $isExtension) {
|
||||
# 11a. BaseForm version
|
||||
$bfVersion = $baseFormNode.GetAttribute("version")
|
||||
if ($bfVersion) {
|
||||
Report-OK "BaseForm: version=$bfVersion"
|
||||
} else {
|
||||
Report-Warn "BaseForm: version attribute missing"
|
||||
}
|
||||
|
||||
# 11b. callType values validation (Before, After, Override)
|
||||
$validCallTypes = @("Before", "After", "Override")
|
||||
$ctErrors = 0
|
||||
$ctChecked = 0
|
||||
|
||||
# Check form-level events
|
||||
$formEventsNode = $root.SelectSingleNode("f:Events", $nsMgr)
|
||||
if ($formEventsNode) {
|
||||
foreach ($evt in $formEventsNode.SelectNodes("f:Event", $nsMgr)) {
|
||||
$ct = $evt.GetAttribute("callType")
|
||||
if ($ct) {
|
||||
$ctChecked++
|
||||
if ($validCallTypes -notcontains $ct) {
|
||||
Report-Error "Form event '$($evt.GetAttribute('name'))': invalid callType='$ct' (expected: Before, After, Override)"
|
||||
$ctErrors++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Check element-level events
|
||||
foreach ($el in $allElements) {
|
||||
if ($stopped) { break }
|
||||
$eventsNode = $el.Node.SelectSingleNode("f:Events", $nsMgr)
|
||||
if (-not $eventsNode) { continue }
|
||||
foreach ($evt in $eventsNode.SelectNodes("f:Event", $nsMgr)) {
|
||||
$ct = $evt.GetAttribute("callType")
|
||||
if ($ct) {
|
||||
$ctChecked++
|
||||
if ($validCallTypes -notcontains $ct) {
|
||||
Report-Error "[$($el.Tag)] '$($el.Name)' event '$($evt.GetAttribute('name'))': invalid callType='$ct'"
|
||||
$ctErrors++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Check command actions
|
||||
foreach ($cmd in $cmdNodes) {
|
||||
if ($stopped) { break }
|
||||
$cmdName = $cmd.GetAttribute("name")
|
||||
foreach ($action in $cmd.SelectNodes("f:Action", $nsMgr)) {
|
||||
$ct = $action.GetAttribute("callType")
|
||||
if ($ct) {
|
||||
$ctChecked++
|
||||
if ($validCallTypes -notcontains $ct) {
|
||||
Report-Error "Command '$cmdName' Action: invalid callType='$ct'"
|
||||
$ctErrors++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $stopped -and $ctErrors -eq 0 -and $ctChecked -gt 0) {
|
||||
Report-OK "callType values: $ctChecked checked"
|
||||
}
|
||||
|
||||
# 11c. Extension ID ranges — warn if extension-added attrs/commands have id < 1000000
|
||||
# Collect BaseForm attribute names to distinguish added ones
|
||||
$baseAttrNames = @{}
|
||||
$baseCmdNames = @{}
|
||||
$bfNs = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable)
|
||||
$bfNs.AddNamespace("f", "http://v8.1c.ru/8.3/xcf/logform")
|
||||
foreach ($bAttr in $baseFormNode.SelectNodes("f:Attributes/f:Attribute", $bfNs)) {
|
||||
$baName = $bAttr.GetAttribute("name")
|
||||
if ($baName) { $baseAttrNames[$baName] = $true }
|
||||
}
|
||||
foreach ($bCmd in $baseFormNode.SelectNodes("f:Commands/f:Command", $bfNs)) {
|
||||
$bcName = $bCmd.GetAttribute("name")
|
||||
if ($bcName) { $baseCmdNames[$bcName] = $true }
|
||||
}
|
||||
|
||||
$idWarnCount = 0
|
||||
foreach ($attr in $attrNodes) {
|
||||
$aName = $attr.GetAttribute("name")
|
||||
$aId = $attr.GetAttribute("id")
|
||||
if ($aName -and -not $baseAttrNames.ContainsKey($aName) -and $aId) {
|
||||
try {
|
||||
$intId = [int]$aId
|
||||
if ($intId -lt 1000000) {
|
||||
Report-Warn "Attribute '$aName' (id=$aId): extension-added attribute has id < 1000000"
|
||||
$idWarnCount++
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($cmd in $cmdNodes) {
|
||||
$cName = $cmd.GetAttribute("name")
|
||||
$cId = $cmd.GetAttribute("id")
|
||||
if ($cName -and -not $baseCmdNames.ContainsKey($cName) -and $cId) {
|
||||
try {
|
||||
$intId = [int]$cId
|
||||
if ($intId -lt 1000000) {
|
||||
Report-Warn "Command '$cName' (id=$cId): extension-added command has id < 1000000"
|
||||
$idWarnCount++
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $stopped -and $idWarnCount -eq 0) {
|
||||
$extAttrCount = ($attrNodes | Where-Object { -not $baseAttrNames.ContainsKey($_.GetAttribute("name")) }).Count
|
||||
$extCmdCount = ($cmdNodes | Where-Object { -not $baseCmdNames.ContainsKey($_.GetAttribute("name")) }).Count
|
||||
if (($extAttrCount + $extCmdCount) -gt 0) {
|
||||
Report-OK "Extension ID ranges: $extAttrCount attr(s), $extCmdCount cmd(s) — all >= 1000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Check callType without BaseForm (structural warning)
|
||||
if (-not $stopped -and -not $isExtension) {
|
||||
$callTypeWithoutBase = $false
|
||||
$feNode = $root.SelectSingleNode("f:Events", $nsMgr)
|
||||
if ($feNode) {
|
||||
foreach ($evt in $feNode.SelectNodes("f:Event", $nsMgr)) {
|
||||
if ($evt.GetAttribute("callType")) { $callTypeWithoutBase = $true; break }
|
||||
}
|
||||
}
|
||||
if (-not $callTypeWithoutBase) {
|
||||
foreach ($cmd in $cmdNodes) {
|
||||
foreach ($action in $cmd.SelectNodes("f:Action", $nsMgr)) {
|
||||
if ($action.GetAttribute("callType")) { $callTypeWithoutBase = $true; break }
|
||||
}
|
||||
if ($callTypeWithoutBase) { break }
|
||||
}
|
||||
}
|
||||
if ($callTypeWithoutBase) {
|
||||
Report-Warn "callType attributes found but no BaseForm — possible incorrect structure"
|
||||
}
|
||||
}
|
||||
|
||||
# --- Summary ---
|
||||
|
||||
Write-Host ""
|
||||
|
||||
@@ -478,10 +478,12 @@ $script:generatedTypes = @{
|
||||
@{ prefix = "DocumentJournalManager"; category = "Manager" }
|
||||
)
|
||||
"Report" = @(
|
||||
@{ prefix = "ReportObject"; category = "Object" }
|
||||
@{ prefix = "ReportObject"; category = "Object" }
|
||||
@{ prefix = "ReportManager"; category = "Manager" }
|
||||
)
|
||||
"DataProcessor" = @(
|
||||
@{ prefix = "DataProcessorObject"; category = "Object" }
|
||||
@{ prefix = "DataProcessorObject"; category = "Object" }
|
||||
@{ prefix = "DataProcessorManager"; category = "Manager" }
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -257,8 +257,8 @@ Ext/ # Расширение конфигураци
|
||||
| Task | Object, Ref, Selection, List, Manager |
|
||||
| ExchangePlan | Object, Ref, Selection, List, Manager |
|
||||
| DocumentJournal | Selection, List, Manager |
|
||||
| Report | Object |
|
||||
| DataProcessor | Object |
|
||||
| Report | Object, Manager |
|
||||
| DataProcessor | Object, Manager |
|
||||
|
||||
Формат имени: `{ТипОбъектаEng}.{ИмяОбъекта}` (напр. `CatalogObject.Номенклатура`, `DocumentRef.АвансовыйОтчет`).
|
||||
|
||||
|
||||
+216
-3
@@ -324,9 +324,20 @@ Enums/ # Перечисления
|
||||
</EnumValue>
|
||||
```
|
||||
|
||||
### 5.4. Заимствованные формы
|
||||
### 5.4. Формы в расширениях
|
||||
|
||||
Метаданные формы (файл `.xml` в каталоге `Forms/`):
|
||||
В расширении существуют **два принципиально разных сценария** работы с формами:
|
||||
|
||||
| Сценарий | Описание | `<BaseForm>` | ID элементов | `callType` |
|
||||
|----------|----------|:------------:|:------------:|:----------:|
|
||||
| **Собственная форма** на заимствованном объекте | Новая форма, не существующая в базовой конфигурации | Нет | Обычные (1+) | Нет |
|
||||
| **Заимствованная форма** | Расширение существующей формы базовой конфигурации | Есть | Базовые + 1000000+ | Есть |
|
||||
|
||||
> **Как отличить:** Если файл метаданных формы (`.xml`) содержит `<ObjectBelonging>Adopted</ObjectBelonging>` — это заимствованная форма. Собственные формы не имеют `ObjectBelonging`.
|
||||
|
||||
#### 5.4.1. Метаданные заимствованной формы
|
||||
|
||||
Файл `.xml` в каталоге `Forms/`:
|
||||
|
||||
```xml
|
||||
<Form uuid="8fcebcc1-...">
|
||||
@@ -341,7 +352,209 @@ Enums/ # Перечисления
|
||||
</Form>
|
||||
```
|
||||
|
||||
Содержимое формы (расширение) хранится в `Forms/ФормаСписка/Ext/Form.xml`, модуль формы — в `Forms/ФормаСписка/Ext/Form/Module.bsl`.
|
||||
Содержимое формы хранится в `Forms/ФормаСписка/Ext/Form.xml`, модуль формы — в `Forms/ФормаСписка/Ext/Form/Module.bsl`.
|
||||
|
||||
#### 5.4.2. Структура Form.xml заимствованной формы
|
||||
|
||||
Form.xml заимствованной формы — **двухчастный файл**: Part 1 (результирующая форма) и BaseForm (исходная форма). Обе части содержат **только визуальные элементы** — атрибуты, события, параметры и команды базовой конфигурации **НЕ включаются**.
|
||||
|
||||
```xml
|
||||
<Form xmlns="http://v8.1c.ru/8.3/xcf/logform" ... version="2.17">
|
||||
|
||||
<!-- ═══ ЧАСТЬ 1: Результирующая форма (база + изменения расширения) ═══ -->
|
||||
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">
|
||||
<ChildItems>
|
||||
<!-- Базовые кнопки: CommandName заменён на 0 -->
|
||||
<Button name="ФормаОбработкаЗагрузитьИзФайла" id="51">
|
||||
<CommandName>0</CommandName>
|
||||
...
|
||||
</Button>
|
||||
<!-- Кнопки, добавленные расширением: CommandName указывает на команду -->
|
||||
<Button name="ФормаНоваяКоманда" id="159">
|
||||
<CommandName>Form.Command.НоваяКоманда</CommandName>
|
||||
...
|
||||
</Button>
|
||||
</ChildItems>
|
||||
</AutoCommandBar>
|
||||
<ChildItems>
|
||||
<!-- Все визуальные элементы: базовые + добавленные расширением -->
|
||||
</ChildItems>
|
||||
<Attributes/> <!-- пустой, ИЛИ только реквизиты расширения (id ≥ 1000000) -->
|
||||
<!-- Events — только обработчики расширения с callType (если есть) -->
|
||||
<Events>
|
||||
<Event name="OnCreateAtServer" callType="After">Расш1_ПриСозданииПосле</Event>
|
||||
</Events>
|
||||
<!-- Commands — только команды расширения (id ≥ 1000000, если есть) -->
|
||||
<Commands>
|
||||
<Command name="НоваяКоманда" id="1000000">
|
||||
<Action callType="Override">Расш1_НоваяКомандаВместо</Action>
|
||||
</Command>
|
||||
</Commands>
|
||||
|
||||
<!-- ═══ ЧАСТЬ 2: Исходная форма из базовой конфигурации ═══ -->
|
||||
<BaseForm version="2.17">
|
||||
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">
|
||||
<ChildItems>
|
||||
<!-- Только базовые кнопки, все CommandName = 0 -->
|
||||
<Button name="ФормаОбработкаЗагрузитьИзФайла" id="51">
|
||||
<CommandName>0</CommandName>
|
||||
...
|
||||
</Button>
|
||||
</ChildItems>
|
||||
</AutoCommandBar>
|
||||
<ChildItems>
|
||||
<!-- Только визуальные элементы базовой конфигурации -->
|
||||
</ChildItems>
|
||||
<Attributes/> <!-- всегда пустой -->
|
||||
<!-- НЕТ Events, Commands, Parameters -->
|
||||
</BaseForm>
|
||||
|
||||
</Form>
|
||||
```
|
||||
|
||||
**Ключевые правила:**
|
||||
|
||||
1. **Часть 1** (до `<BaseForm>`) — **результирующая форма**. Содержит визуальные элементы (AutoCommandBar + ChildItems) из базовой конфигурации плюс элементы расширения. Атрибуты базовой конфигурации (DynamicList, QueryText и др.) **не включаются** — только реквизиты расширения (id ≥ 1000000) или пустой `<Attributes/>`. Events и Commands — только добавленные расширением (с `callType`).
|
||||
|
||||
2. **Часть 2** (`<BaseForm>`) — **визуальный снимок исходной формы**. Содержит только AutoCommandBar + ChildItems + пустой `<Attributes/>`. НЕ содержит Events, Commands, Parameters. Все `<CommandName>` в кнопках заменены на `0`. Платформа использует BaseForm для контроля совместимости при обновлении конфигурации.
|
||||
|
||||
3. **Правило `<CommandName>0</CommandName>`**: во всех кнопках базовой формы (как в Part 1, так и в BaseForm) значение `<CommandName>` заменяется на `0`. Ссылки на команды конфигурации не сохраняются. Только кнопки, добавленные расширением, сохраняют ссылку на команду (напр. `Form.Command.XXX`).
|
||||
|
||||
4. Элемент `<BaseForm>` всегда идёт **последним** в `<Form>` и имеет атрибут `version`.
|
||||
|
||||
#### 5.4.3. Нумерация ID элементов
|
||||
|
||||
| Диапазон | Принадлежность |
|
||||
|----------|---------------|
|
||||
| `-1` | Авто-командная панель (`AutoCommandBar`) — фиксированный ID |
|
||||
| `1` – `999999` | Элементы базовой формы (сохраняют оригинальные ID) |
|
||||
| `1000000`+ | Реквизиты (`Attributes`) и команды (`Commands`), добавленные расширением |
|
||||
|
||||
> **Важно:** Визуальные элементы форм (элементы в `ChildItems`), добавленные расширением в тело базовой формы, могут использовать ID из обычного диапазона (продолжая нумерацию базовой формы). Диапазон 1000000+ гарантирован для `Attributes` и `Commands`.
|
||||
|
||||
#### 5.4.4. Атрибут callType — перехват событий и команд
|
||||
|
||||
В заимствованных формах события и действия команд используют атрибут `callType` для определения момента перехвата:
|
||||
|
||||
| Значение | Описание |
|
||||
|----------|----------|
|
||||
| `Before` | Обработчик расширения вызывается **до** оригинального обработчика |
|
||||
| `After` | Обработчик расширения вызывается **после** оригинального обработчика |
|
||||
| `Override` | Обработчик расширения **заменяет** оригинальный обработчик |
|
||||
|
||||
##### События формы (form-level)
|
||||
|
||||
```xml
|
||||
<Events>
|
||||
<Event name="OnCreateAtServer" callType="After">Расш1_ПриСозданииНаСервереПосле</Event>
|
||||
<Event name="OnOpen" callType="Before">Расш1_ПриОткрытииПеред</Event>
|
||||
<Event name="BeforeWriteAtServer" callType="After">Расш1_ПередЗаписьюНаСервереПосле</Event>
|
||||
<Event name="NotificationProcessing" callType="After">Расш1_ОбработкаОповещенияПосле</Event>
|
||||
</Events>
|
||||
```
|
||||
|
||||
##### События элементов формы (element-level)
|
||||
|
||||
```xml
|
||||
<InputField name="Банк" id="37">
|
||||
<Events>
|
||||
<Event name="OnChange" callType="Before">Расш1_БанкПриИзменении</Event>
|
||||
<Event name="Clearing" callType="Before">Расш1_БанкОчистка</Event>
|
||||
</Events>
|
||||
...
|
||||
</InputField>
|
||||
|
||||
<Table name="СписокСпецификаций" id="102">
|
||||
<Events>
|
||||
<Event name="Selection" callType="Before">Расш1_СписокВыборПеред</Event>
|
||||
</Events>
|
||||
...
|
||||
</Table>
|
||||
```
|
||||
|
||||
##### Действия команд (Command Action)
|
||||
|
||||
Команда может иметь **несколько элементов `<Action>`** с разными `callType`:
|
||||
|
||||
```xml
|
||||
<!-- Перехват существующей команды базовой формы: до + после -->
|
||||
<Command name="ПодборИзКлассификатора" id="1000002">
|
||||
<Action callType="Before">Расш1_ПодборИзКлассификатораПеред</Action>
|
||||
<Action callType="After">Расш1_ПодборИзКлассификатораПосле</Action>
|
||||
</Command>
|
||||
|
||||
<!-- Полная замена обработчика команды -->
|
||||
<Command name="НоваяКоманда" id="1000000">
|
||||
<Action callType="Override">Расш1_НоваяКомандаВместо</Action>
|
||||
</Command>
|
||||
|
||||
<!-- Один перехват (только после) -->
|
||||
<Command name="ЗапросКорректировки" id="1000005">
|
||||
<Action callType="After">Расш1_ЗапросКорректировкиПосле</Action>
|
||||
</Command>
|
||||
```
|
||||
|
||||
> **Отличие от обычной формы:** В обычной форме (конфигурации или собственной форме расширения) у `<Event>` и `<Action>` **нет** атрибута `callType` — обработчик вызывается напрямую.
|
||||
|
||||
#### 5.4.5. Собственная форма на заимствованном объекте
|
||||
|
||||
Расширение может добавить к заимствованному объекту **собственную форму**, не существующую в базовой конфигурации. Такая форма:
|
||||
|
||||
- **Не имеет** `ObjectBelonging` и `ExtendedConfigurationObject` в метаданных формы
|
||||
- **Не содержит** `<BaseForm>` в Form.xml
|
||||
- **Не использует** атрибут `callType`
|
||||
- Использует обычную нумерацию ID (1+)
|
||||
- Формат полностью совпадает с форматом форм конфигурации (см. [1c-form-spec.md](1c-form-spec.md))
|
||||
|
||||
```xml
|
||||
<!-- Метаданные: Forms/МояФорма.xml — без ObjectBelonging -->
|
||||
<Form uuid="...">
|
||||
<Properties>
|
||||
<Name>Расш1_МояФорма</Name>
|
||||
<Synonym>...</Synonym>
|
||||
<Comment/>
|
||||
<FormType>Managed</FormType>
|
||||
<UsePurposes>...</UsePurposes>
|
||||
</Properties>
|
||||
</Form>
|
||||
```
|
||||
|
||||
```xml
|
||||
<!-- Содержимое: Forms/МояФорма/Ext/Form.xml — обычная форма без BaseForm -->
|
||||
<Form ... version="2.17">
|
||||
<Events>
|
||||
<Event name="OnCreateAtServer">ПриСозданииНаСервере</Event>
|
||||
</Events>
|
||||
<ChildItems>...</ChildItems>
|
||||
<Attributes>...</Attributes>
|
||||
</Form>
|
||||
```
|
||||
|
||||
#### 5.4.6. Модуль заимствованной формы
|
||||
|
||||
Модуль формы (`Forms/Имя/Ext/Form/Module.bsl`) в заимствованной форме использует те же декораторы перехвата, что и другие модули расширений (см. раздел 7.2):
|
||||
|
||||
```bsl
|
||||
&НаСервере
|
||||
&Вместо("ЗаполнитьПодменюПараметры")
|
||||
Процедура Расш1_ЗаполнитьПодменюПараметры()
|
||||
ПродолжитьВызов();
|
||||
КонецПроцедуры
|
||||
|
||||
&НаКлиенте
|
||||
&ИзменениеИКонтроль("ПараметрыНаЯзыке")
|
||||
Функция Расш1_ПараметрыНаЯзыке(КодЯзыка)
|
||||
// ... тело с #Вставка / #Удаление маркерами ...
|
||||
КонецФункции
|
||||
|
||||
// Обработчик собственной команды расширения (без декоратора)
|
||||
&НаКлиенте
|
||||
Процедура Расш1_НоваяКомандаВместо(Команда)
|
||||
// ...
|
||||
КонецПроцедуры
|
||||
```
|
||||
|
||||
> **Обработчики событий с `callType`** (определённые в Form.xml секции Events/Action) реализуются в модуле как обычные процедуры **без** аннотаций-декораторов — привязка к событию уже задана в XML через `callType`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user