diff --git a/.claude/skills/meta-compile/SKILL.md b/.claude/skills/meta-compile/SKILL.md new file mode 100644 index 00000000..5d48e2d1 --- /dev/null +++ b/.claude/skills/meta-compile/SKILL.md @@ -0,0 +1,126 @@ +--- +name: meta-compile +description: Компиляция объекта метаданных 1С (Справочник, Документ, Перечисление, Константа, Регистр) из компактного JSON-определения +argument-hint: +allowed-tools: + - Bash + - Read + - Write + - Glob +--- + +# /meta-compile — генерация объектов метаданных из JSON DSL + +Принимает JSON-определение объекта метаданных → генерирует XML + модули в структуре выгрузки конфигурации + регистрирует в Configuration.xml. + +## Параметры и команда + +| Параметр | Описание | +|----------|----------| +| `JsonPath` | Путь к JSON-определению объекта | +| `OutputDir` | Корневая директория выгрузки конфигурации (где `Catalogs/`, `Documents/` и т.д.) | + +```powershell +powershell.exe -NoProfile -File .claude\skills\meta-compile\scripts\meta-compile.ps1 -JsonPath "" -OutputDir "" +``` + +`OutputDir` — директория, содержащая подпапки `Catalogs/`, `Documents/`, `Enums/`, `Constants/`, `InformationRegisters/`, `AccumulationRegisters/`, а также `Configuration.xml`. + +## Поддерживаемые типы + +Catalog (Справочник), Document (Документ), Enum (Перечисление), Constant (Константа), InformationRegister (РегистрСведений), AccumulationRegister (РегистрНакопления). + +## JSON DSL — краткий справочник + +Полная спецификация: `docs/meta-dsl-spec.md`. + +### Корневая структура + +```json +{ + "type": "Catalog", + "name": "Номенклатура", + "synonym": "авто из name", + ...type-specific..., + "attributes": [...], + "tabularSections": {...} +} +``` + +### Реквизиты — shorthand + +``` +"ИмяРеквизита" — String без квалификаторов +"ИмяРеквизита: Тип" — с типом +"ИмяРеквизита: Тип | req, index" — с флагами +``` + +Типы: `String(100)`, `Number(15,2)`, `Boolean`, `Date`, `DateTime`, `CatalogRef.Xxx`, `DocumentRef.Xxx`, `EnumRef.Xxx`, `DefinedType.Xxx`. Русские синонимы: `Строка(100)`, `Число(15,2)`, `Булево`, `Дата`, `СправочникСсылка.Xxx`. + +Флаги: `req` (обязательное), `index`, `indexAdditional`, `nonneg`, `master`, `mainFilter`, `denyIncomplete`, `useInTotals`. + +### Табличные части (Catalog, Document) + +```json +"tabularSections": { + "Товары": ["Номенклатура: CatalogRef.Xxx | req", "Количество: Number(10,3)"] +} +``` + +### Перечисления + +```json +"values": ["Приход", "Расход", { "name": "НДС20", "synonym": "НДС 20%" }] +``` + +### Измерения и ресурсы (регистры) + +```json +"dimensions": ["Организация: CatalogRef.Xxx | master, mainFilter"], +"resources": ["Количество: Number(15,3)"] +``` + +## Примеры + +### Минимальный справочник + +```json +{ "type": "Catalog", "name": "Валюты" } +``` + +### Перечисление + +```json +{ "type": "Enum", "name": "Статусы", "values": ["Новый", "Закрыт"] } +``` + +### Константа + +```json +{ "type": "Constant", "name": "ОсновнаяВалюта", "valueType": "CatalogRef.Валюты" } +``` + +### Регистр сведений + +```json +{ + "type": "InformationRegister", + "name": "КурсыВалют", + "periodicity": "Day", + "dimensions": ["Валюта: CatalogRef.Валюты | master, mainFilter, denyIncomplete"], + "resources": ["Курс: Number(15,4)", "Кратность: Number(10,0)"] +} +``` + +## Что генерируется + +- `{OutputDir}/{TypePlural}/{Name}.xml` — метаданные объекта +- `{OutputDir}/{TypePlural}/{Name}/Ext/ObjectModule.bsl` — пустой модуль (Catalog, Document) +- `{OutputDir}/{TypePlural}/{Name}/Ext/RecordSetModule.bsl` — пустой модуль (регистры) +- `Configuration.xml` — автоматическая регистрация в `` + +## Верификация + +``` +/meta-info //.xml — проверка структуры +``` diff --git a/.claude/skills/meta-compile/scripts/meta-compile.ps1 b/.claude/skills/meta-compile/scripts/meta-compile.ps1 new file mode 100644 index 00000000..7146850d --- /dev/null +++ b/.claude/skills/meta-compile/scripts/meta-compile.ps1 @@ -0,0 +1,1399 @@ +# meta-compile v1.0 — Compile 1C metadata object from JSON +param( + [Parameter(Mandatory)] + [string]$JsonPath, + + [Parameter(Mandatory)] + [string]$OutputDir +) + +$ErrorActionPreference = "Stop" +[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 + +# --- 1. Load and validate JSON --- + +if (-not (Test-Path $JsonPath)) { + Write-Error "File not found: $JsonPath" + exit 1 +} + +$json = Get-Content -Raw -Encoding UTF8 $JsonPath +$def = $json | ConvertFrom-Json + +# Object type synonyms (Russian → English) +$script:objectTypeSynonyms = @{ + "Справочник" = "Catalog" + "Документ" = "Document" + "Перечисление" = "Enum" + "Константа" = "Constant" + "РегистрСведений" = "InformationRegister" + "РегистрНакопления"= "AccumulationRegister" +} + +if (-not $def.type) { + Write-Error "JSON must have 'type' field" + exit 1 +} + +# Resolve type synonym +$objType = "$($def.type)" +if ($script:objectTypeSynonyms.ContainsKey($objType)) { + $objType = $script:objectTypeSynonyms[$objType] +} + +$validTypes = @("Catalog","Document","Enum","Constant","InformationRegister","AccumulationRegister") +if ($objType -notin $validTypes) { + Write-Error "Unsupported type: $objType. Valid: $($validTypes -join ', ')" + exit 1 +} + +if (-not $def.name) { + Write-Error "JSON must have 'name' field" + exit 1 +} + +$objName = "$($def.name)" + +# --- 2. XML helpers --- + +$script:xml = New-Object System.Text.StringBuilder 32768 + +function X { + param([string]$text) + $script:xml.AppendLine($text) | Out-Null +} + +function Esc-Xml { + param([string]$s) + return $s.Replace('&','&').Replace('<','<').Replace('>','>').Replace('"','"') +} + +function Emit-MLText { + param([string]$indent, [string]$tag, [string]$text) + if (-not $text) { + X "$indent<$tag/>" + return + } + X "$indent<$tag>" + X "$indent`t" + X "$indent`t`tru" + X "$indent`t`t$(Esc-Xml $text)" + X "$indent`t" + X "$indent" +} + +function New-Guid-String { + return [System.Guid]::NewGuid().ToString() +} + +# --- 3. CamelCase splitter --- + +function Split-CamelCase { + param([string]$name) + if (-not $name) { return $name } + # Insert space before uppercase that follows lowercase (Cyrillic + Latin) + $result = [regex]::Replace($name, '([а-яё])([А-ЯЁ])', '$1 $2') + $result = [regex]::Replace($result, '([a-z])([A-Z])', '$1 $2') + # Lowercase all but first character of the result + if ($result.Length -gt 1) { + $result = $result.Substring(0,1) + $result.Substring(1).ToLower() + } + return $result +} + +# Auto-synonym +$synonym = if ($def.synonym) { "$($def.synonym)" } else { Split-CamelCase $objName } +$comment = if ($def.comment) { "$($def.comment)" } else { "" } + +# --- 4. Type system --- + +$script:typeSynonyms = New-Object System.Collections.Hashtable +$script:typeSynonyms["число"] = "Number" +$script:typeSynonyms["строка"] = "String" +$script:typeSynonyms["булево"] = "Boolean" +$script:typeSynonyms["дата"] = "Date" +$script:typeSynonyms["датавремя"]= "DateTime" +$script:typeSynonyms["number"] = "Number" +$script:typeSynonyms["string"] = "String" +$script:typeSynonyms["boolean"] = "Boolean" +$script:typeSynonyms["date"] = "Date" +$script:typeSynonyms["datetime"] = "DateTime" +$script:typeSynonyms["bool"] = "Boolean" +# Reference synonyms (Russian, lowercase) +$script:typeSynonyms["справочникссылка"] = "CatalogRef" +$script:typeSynonyms["документссылка"] = "DocumentRef" +$script:typeSynonyms["перечислениессылка"] = "EnumRef" +$script:typeSynonyms["плансчетовссылка"] = "ChartOfAccountsRef" +$script:typeSynonyms["планвидовхарактеристикссылка"] = "ChartOfCharacteristicTypesRef" +$script:typeSynonyms["определяемыйтип"] = "DefinedType" +$script:typeSynonyms["definedtype"] = "DefinedType" + +function Resolve-TypeStr { + param([string]$typeStr) + if (-not $typeStr) { return $typeStr } + + # Check for parameterized types: Number(15,2), Строка(100), etc. + if ($typeStr -match '^([^(]+)\((.+)\)$') { + $baseName = $Matches[1].Trim() + $params = $Matches[2] + $resolved = $script:typeSynonyms[$baseName.ToLower()] + if ($resolved) { return "$resolved($params)" } + return $typeStr + } + + # Check for reference types: СправочникСсылка.Организации → CatalogRef.Организации + if ($typeStr.Contains('.')) { + $dotIdx = $typeStr.IndexOf('.') + $prefix = $typeStr.Substring(0, $dotIdx) + $suffix = $typeStr.Substring($dotIdx) # includes the dot + $resolved = $script:typeSynonyms[$prefix.ToLower()] + if ($resolved) { return "$resolved$suffix" } + return $typeStr + } + + # Simple name lookup + $resolved = $script:typeSynonyms[$typeStr.ToLower()] + if ($resolved) { return $resolved } + + return $typeStr +} + +function Emit-TypeContent { + param([string]$indent, [string]$typeStr) + if (-not $typeStr) { return } + + $typeStr = Resolve-TypeStr $typeStr + + # Boolean + if ($typeStr -eq "Boolean") { + X "$indentxs:boolean" + return + } + + # String or String(N) + if ($typeStr -match '^String(\((\d+)\))?$') { + $len = if ($Matches[2]) { $Matches[2] } else { "0" } + X "$indentxs:string" + X "$indent" + X "$indent`t$len" + X "$indent`tVariable" + X "$indent" + return + } + + # Number(D,F) or Number(D,F,nonneg) + if ($typeStr -match '^Number\((\d+),(\d+)(,nonneg)?\)$') { + $digits = $Matches[1] + $fraction = $Matches[2] + $sign = if ($Matches[3]) { "Nonnegative" } else { "Any" } + X "$indentxs:decimal" + X "$indent" + X "$indent`t$digits" + X "$indent`t$fraction" + X "$indent`t$sign" + X "$indent" + return + } + + # Date / DateTime + if ($typeStr -eq "Date") { + X "$indentxs:dateTime" + X "$indent" + X "$indent`tDate" + X "$indent" + return + } + if ($typeStr -eq "DateTime") { + X "$indentxs:dateTime" + X "$indent" + X "$indent`tDateTime" + X "$indent" + return + } + + # DefinedType + if ($typeStr -match '^DefinedType\.(.+)$') { + $dtName = $Matches[1] + X "$indentcfg:DefinedType.$dtName" + return + } + + # Reference types: CatalogRef.XXX, DocumentRef.XXX, etc. + if ($typeStr -match '^(CatalogRef|DocumentRef|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef)\.(.+)$') { + X "$indentcfg:$typeStr" + return + } + + # Fallback — emit as-is + X "$indent$typeStr" +} + +function Emit-ValueType { + param([string]$indent, [string]$typeStr) + X "$indent" + Emit-TypeContent "$indent`t" $typeStr + X "$indent" +} + +function Emit-FillValue { + param([string]$indent, [string]$typeStr) + if (-not $typeStr) { + X "$indent" + return + } + + $typeStr = Resolve-TypeStr $typeStr + + if ($typeStr -eq "Boolean") { + X "$indentfalse" + return + } + if ($typeStr -match '^String') { + X "$indent" + return + } + if ($typeStr -match '^Number') { + X "$indent0" + return + } + if ($typeStr -match '^(Date|DateTime)$') { + X "$indent" + return + } + # References and others + X "$indent" +} + +# --- 5. Attribute shorthand parser --- + +function Parse-AttributeShorthand { + param($val) + + if ($val -is [string]) { + $str = "$val" + $parsed = @{ + name = "" + type = "" + synonym = "" + comment = "" + flags = @() + } + + # Split by | for flags + $parts = $str -split '\|', 2 + $mainPart = $parts[0].Trim() + if ($parts.Count -gt 1) { + $flagStr = $parts[1].Trim() + $parsed.flags = @($flagStr -split ',' | ForEach-Object { $_.Trim().ToLower() } | Where-Object { $_ }) + } + + # Split by : for name and type + $colonParts = $mainPart -split ':', 2 + $parsed.name = $colonParts[0].Trim() + if ($colonParts.Count -gt 1) { + $parsed.type = $colonParts[1].Trim() + } + + $parsed.synonym = Split-CamelCase $parsed.name + return $parsed + } + + # Object form + $name = "$($val.name)" + return @{ + name = $name + type = if ($val.type) { "$($val.type)" } else { "" } + synonym = if ($val.synonym) { "$($val.synonym)" } else { Split-CamelCase $name } + comment = if ($val.comment) { "$($val.comment)" } else { "" } + flags = @(if ($val.flags) { $val.flags } else { @() }) + fillChecking = if ($val.fillChecking) { "$($val.fillChecking)" } else { "" } + indexing = if ($val.indexing) { "$($val.indexing)" } else { "" } + } +} + +function Parse-EnumValueShorthand { + param($val) + + if ($val -is [string]) { + $name = "$val" + return @{ + name = $name + synonym = Split-CamelCase $name + comment = "" + } + } + + $name = "$($val.name)" + return @{ + name = $name + synonym = if ($val.synonym) { "$($val.synonym)" } else { Split-CamelCase $name } + comment = if ($val.comment) { "$($val.comment)" } else { "" } + } +} + +# --- 6. GeneratedType categories --- + +$script:generatedTypes = @{ + "Catalog" = @( + @{ prefix = "CatalogObject"; category = "Object" } + @{ prefix = "CatalogRef"; category = "Ref" } + @{ prefix = "CatalogSelection"; category = "Selection" } + @{ prefix = "CatalogList"; category = "List" } + @{ prefix = "CatalogManager"; category = "Manager" } + ) + "Document" = @( + @{ prefix = "DocumentObject"; category = "Object" } + @{ prefix = "DocumentRef"; category = "Ref" } + @{ prefix = "DocumentSelection"; category = "Selection" } + @{ prefix = "DocumentList"; category = "List" } + @{ prefix = "DocumentManager"; category = "Manager" } + ) + "Enum" = @( + @{ prefix = "EnumRef"; category = "Ref" } + @{ prefix = "EnumManager"; category = "Manager" } + @{ prefix = "EnumList"; category = "List" } + ) + "Constant" = @( + @{ prefix = "ConstantManager"; category = "Manager" } + @{ prefix = "ConstantValueManager"; category = "ValueManager" } + @{ prefix = "ConstantValueKey"; category = "ValueKey" } + ) + "InformationRegister" = @( + @{ prefix = "InformationRegisterRecord"; category = "Record" } + @{ prefix = "InformationRegisterManager"; category = "Manager" } + @{ prefix = "InformationRegisterSelection"; category = "Selection" } + @{ prefix = "InformationRegisterList"; category = "List" } + @{ prefix = "InformationRegisterRecordSet"; category = "RecordSet" } + @{ prefix = "InformationRegisterRecordKey"; category = "RecordKey" } + @{ prefix = "InformationRegisterRecordManager"; category = "RecordManager" } + ) + "AccumulationRegister" = @( + @{ prefix = "AccumulationRegisterRecord"; category = "Record" } + @{ prefix = "AccumulationRegisterManager"; category = "Manager" } + @{ prefix = "AccumulationRegisterSelection"; category = "Selection" } + @{ prefix = "AccumulationRegisterList"; category = "List" } + @{ prefix = "AccumulationRegisterRecordSet"; category = "RecordSet" } + @{ prefix = "AccumulationRegisterRecordKey"; category = "RecordKey" } + ) +} + +function Emit-InternalInfo { + param([string]$indent, [string]$objectType, [string]$objectName) + $types = $script:generatedTypes[$objectType] + if (-not $types) { return } + + X "$indent" + foreach ($gt in $types) { + $fullName = "$($gt.prefix).$objectName" + X "$indent`t" + X "$indent`t`t$(New-Guid-String)" + X "$indent`t`t$(New-Guid-String)" + X "$indent`t" + } + X "$indent" +} + +# --- 7. StandardAttributes --- + +$script:standardAttributesByType = @{ + "Catalog" = @("PredefinedDataName","Predefined","Ref","DeletionMark","IsFolder","Owner","Parent","Description","Code") + "Document" = @("Posted","Ref","DeletionMark","Date","Number") + "Enum" = @("Order","Ref") + "InformationRegister" = @("Active","LineNumber","Recorder","Period") + "AccumulationRegister" = @("Active","LineNumber","Recorder","Period") +} + +function Emit-StandardAttribute { + param([string]$indent, [string]$attrName) + X "$indent" + X "$indent`t" + X "$indent`tDontCheck" + X "$indent`tfalse" + X "$indent`tfalse" + X "$indent`tAuto" + X "$indent`t" + X "$indent`t" + X "$indent`tfalse" + X "$indent`t" + X "$indent`t" + X "$indent`tAuto" + X "$indent`tAuto" + X "$indent`t" + X "$indent`tfalse" + X "$indent`tUse" + X "$indent`tfalse" + X "$indent`t" + X "$indent`t" + X "$indent`t" + X "$indent`tUse" + X "$indent`t" + X "$indent`t" + X "$indent`t" + X "$indent`t" + X "$indent" +} + +function Emit-StandardAttributes { + param([string]$indent, [string]$objectType) + $attrs = $script:standardAttributesByType[$objectType] + if (-not $attrs) { return } + X "$indent" + foreach ($a in $attrs) { + Emit-StandardAttribute "$indent`t" $a + } + X "$indent" +} + +# TabularSection standard attributes (just LineNumber) +function Emit-TabularStandardAttributes { + param([string]$indent) + X "$indent" + Emit-StandardAttribute "$indent`t" "LineNumber" + X "$indent" +} + +# --- 8. Attribute emitter --- + +function Emit-Attribute { + param([string]$indent, $parsed, [string]$context) + # $context: "catalog", "document", "tabular", "register" + $uuid = New-Guid-String + X "$indent" + X "$indent`t" + X "$indent`t`t$(Esc-Xml $parsed.name)" + Emit-MLText "$indent`t`t" "Synonym" $parsed.synonym + X "$indent`t`t" + + # Type + $typeStr = $parsed.type + if ($typeStr) { + Emit-ValueType "$indent`t`t" $typeStr + } else { + # Default: unqualified string + X "$indent`t`t" + X "$indent`t`t`txs:string" + X "$indent`t`t" + } + + X "$indent`t`tfalse" + X "$indent`t`t" + X "$indent`t`t" + X "$indent`t`t" + X "$indent`t`tfalse" + X "$indent`t`t" + X "$indent`t`tfalse" + X "$indent`t`tfalse" + X "$indent`t`t" + X "$indent`t`t" + + # FillFromFillingValue + X "$indent`t`tfalse" + + # FillValue + Emit-FillValue "$indent`t`t" $typeStr + + # FillChecking + $fillChecking = "DontCheck" + if ($parsed.flags -contains "req") { $fillChecking = "ShowError" } + if ($parsed.fillChecking) { $fillChecking = $parsed.fillChecking } + X "$indent`t`t$fillChecking" + + X "$indent`t`tItems" + X "$indent`t`t" + X "$indent`t`t" + X "$indent`t`tAuto" + X "$indent`t`tAuto" + X "$indent`t`t" + X "$indent`t`t" + X "$indent`t`tAuto" + + # Use — only for catalog/document top-level attributes + if ($context -eq "catalog" -or $context -eq "document") { + X "$indent`t`tForItem" + } + + # Indexing + $indexing = "DontIndex" + if ($parsed.flags -contains "index") { $indexing = "Index" } + if ($parsed.flags -contains "indexadditional") { $indexing = "IndexWithAdditionalOrder" } + if ($parsed.indexing) { $indexing = $parsed.indexing } + X "$indent`t`t$indexing" + + X "$indent`t`tUse" + X "$indent`t`tUse" + + X "$indent`t" + X "$indent" +} + +# --- 9. TabularSection emitter --- + +function Emit-TabularSection { + param([string]$indent, [string]$tsName, $columns, [string]$objectType, [string]$objectName) + $uuid = New-Guid-String + X "$indent" + + # InternalInfo for TabularSection + $typePrefix = switch ($objectType) { + "Catalog" { "CatalogTabularSection" } + "Document" { "DocumentTabularSection" } + } + $rowPrefix = switch ($objectType) { + "Catalog" { "CatalogTabularSectionRow" } + "Document" { "DocumentTabularSectionRow" } + } + + X "$indent`t" + X "$indent`t`t" + X "$indent`t`t`t$(New-Guid-String)" + X "$indent`t`t`t$(New-Guid-String)" + X "$indent`t`t" + X "$indent`t`t" + X "$indent`t`t`t$(New-Guid-String)" + X "$indent`t`t`t$(New-Guid-String)" + X "$indent`t`t" + X "$indent`t" + + $tsSynonym = Split-CamelCase $tsName + + X "$indent`t" + X "$indent`t`t$(Esc-Xml $tsName)" + Emit-MLText "$indent`t`t" "Synonym" $tsSynonym + X "$indent`t`t" + X "$indent`t`t" + X "$indent`t`tDontCheck" + Emit-TabularStandardAttributes "$indent`t`t" + X "$indent`t`tForItem" + X "$indent`t" + + X "$indent`t" + foreach ($col in $columns) { + $parsed = Parse-AttributeShorthand $col + Emit-Attribute "$indent`t`t" $parsed "tabular" + } + X "$indent`t" + + X "$indent" +} + +# --- 10. EnumValue emitter --- + +function Emit-EnumValue { + param([string]$indent, $parsed) + $uuid = New-Guid-String + X "$indent" + X "$indent`t" + X "$indent`t`t$(Esc-Xml $parsed.name)" + Emit-MLText "$indent`t`t" "Synonym" $parsed.synonym + X "$indent`t`t" + X "$indent`t" + X "$indent" +} + +# --- 11. Dimension emitter --- + +function Emit-Dimension { + param([string]$indent, $parsed, [string]$registerType) + # $registerType: "InformationRegister" or "AccumulationRegister" + $uuid = New-Guid-String + X "$indent" + X "$indent`t" + X "$indent`t`t$(Esc-Xml $parsed.name)" + Emit-MLText "$indent`t`t" "Synonym" $parsed.synonym + X "$indent`t`t" + + $typeStr = $parsed.type + if ($typeStr) { + Emit-ValueType "$indent`t`t" $typeStr + } else { + X "$indent`t`t" + X "$indent`t`t`txs:string" + X "$indent`t`t" + } + + X "$indent`t`tfalse" + X "$indent`t`t" + X "$indent`t`t" + X "$indent`t`t" + X "$indent`t`tfalse" + X "$indent`t`t" + X "$indent`t`tfalse" + X "$indent`t`tfalse" + X "$indent`t`t" + X "$indent`t`t" + + # InformationRegister dimensions have FillFromFillingValue + if ($registerType -eq "InformationRegister") { + $fillFrom = if ($parsed.flags -contains "master") { "true" } else { "false" } + X "$indent`t`t$fillFrom" + X "$indent`t`t" + } + + $fillChecking = "DontCheck" + if ($parsed.flags -contains "req") { $fillChecking = "ShowError" } + X "$indent`t`t$fillChecking" + + X "$indent`t`tItems" + X "$indent`t`t" + X "$indent`t`t" + X "$indent`t`tAuto" + X "$indent`t`tAuto" + X "$indent`t`t" + X "$indent`t`t" + X "$indent`t`tAuto" + + # InformationRegister dimensions: Master, MainFilter, DenyIncompleteValues + if ($registerType -eq "InformationRegister") { + $master = if ($parsed.flags -contains "master") { "true" } else { "false" } + $mainFilter = if ($parsed.flags -contains "mainfilter") { "true" } else { "false" } + $denyIncomplete = if ($parsed.flags -contains "denyincomplete") { "true" } else { "false" } + X "$indent`t`t$master" + X "$indent`t`t$mainFilter" + X "$indent`t`t$denyIncomplete" + } + + # AccumulationRegister dimensions: DenyIncompleteValues + if ($registerType -eq "AccumulationRegister") { + $denyIncomplete = if ($parsed.flags -contains "denyincomplete") { "true" } else { "false" } + X "$indent`t`t$denyIncomplete" + } + + $indexing = "DontIndex" + if ($parsed.flags -contains "index") { $indexing = "Index" } + X "$indent`t`t$indexing" + + X "$indent`t`tUse" + + # AccumulationRegister dimensions: UseInTotals + if ($registerType -eq "AccumulationRegister") { + $useInTotals = if ($parsed.flags -contains "nouseintotals") { "false" } else { "true" } + X "$indent`t`t$useInTotals" + } + + # InformationRegister dimensions: DataHistory + if ($registerType -eq "InformationRegister") { + X "$indent`t`tUse" + } + + X "$indent`t" + X "$indent" +} + +# --- 12. Resource emitter --- + +function Emit-Resource { + param([string]$indent, $parsed, [string]$registerType) + $uuid = New-Guid-String + X "$indent" + X "$indent`t" + X "$indent`t`t$(Esc-Xml $parsed.name)" + Emit-MLText "$indent`t`t" "Synonym" $parsed.synonym + X "$indent`t`t" + + $typeStr = $parsed.type + if ($typeStr) { + Emit-ValueType "$indent`t`t" $typeStr + } else { + X "$indent`t`t" + X "$indent`t`t`txs:decimal" + X "$indent`t`t`t" + X "$indent`t`t`t`t15" + X "$indent`t`t`t`t2" + X "$indent`t`t`t`tAny" + X "$indent`t`t`t" + X "$indent`t`t" + } + + X "$indent`t`tfalse" + X "$indent`t`t" + X "$indent`t`t" + X "$indent`t`t" + X "$indent`t`tfalse" + X "$indent`t`t" + X "$indent`t`tfalse" + X "$indent`t`tfalse" + X "$indent`t`t" + X "$indent`t`t" + + # InformationRegister resources have FillFromFillingValue, FillValue + if ($registerType -eq "InformationRegister") { + X "$indent`t`tfalse" + X "$indent`t`t" + } + + $fillChecking = "DontCheck" + if ($parsed.flags -contains "req") { $fillChecking = "ShowError" } + X "$indent`t`t$fillChecking" + + X "$indent`t`tItems" + X "$indent`t`t" + X "$indent`t`t" + X "$indent`t`tAuto" + X "$indent`t`tAuto" + X "$indent`t`t" + X "$indent`t`t" + X "$indent`t`tAuto" + + # InformationRegister resources: Indexing, FullTextSearch, DataHistory + if ($registerType -eq "InformationRegister") { + X "$indent`t`tDontIndex" + X "$indent`t`tUse" + X "$indent`t`tUse" + } + + # AccumulationRegister resources: FullTextSearch (no Indexing, no DataHistory) + if ($registerType -eq "AccumulationRegister") { + X "$indent`t`tUse" + } + + X "$indent`t" + X "$indent" +} + +# --- 13. Property emitters per type --- + +function Emit-CatalogProperties { + param([string]$indent) + $i = $indent + + X "$i$(Esc-Xml $objName)" + Emit-MLText $i "Synonym" $synonym + X "$i" + + $hierarchical = if ($def.hierarchical -eq $true) { "true" } else { "false" } + $hierarchyType = if ($def.hierarchyType) { "$($def.hierarchyType)" } else { "HierarchyFoldersAndItems" } + X "$i$hierarchical" + X "$i$hierarchyType" + X "$ifalse" + X "$i2" + X "$itrue" + X "$itrue" + X "$i" + X "$iToItems" + + $codeLength = if ($null -ne $def.codeLength) { "$($def.codeLength)" } else { "9" } + $descriptionLength = if ($null -ne $def.descriptionLength) { "$($def.descriptionLength)" } else { "25" } + $codeType = if ($def.codeType) { "$($def.codeType)" } else { "String" } + $codeAllowedLength = if ($def.codeAllowedLength) { "$($def.codeAllowedLength)" } else { "Variable" } + $autonumbering = if ($def.autonumbering -eq $false) { "false" } else { "true" } + $checkUnique = if ($def.checkUnique -eq $true) { "true" } else { "false" } + + X "$i$codeLength" + X "$i$descriptionLength" + X "$i$codeType" + X "$i$codeAllowedLength" + X "$iWholeCatalog" + X "$i$checkUnique" + X "$i$autonumbering" + + $defaultPresentation = if ($def.defaultPresentation) { "$($def.defaultPresentation)" } else { "AsDescription" } + X "$i$defaultPresentation" + + Emit-StandardAttributes $i "Catalog" + X "$i" + X "$iAuto" + X "$iInDialog" + X "$itrue" + X "$iBothWays" + X "$i" + X "$i`tCatalog.$objName.StandardAttribute.Description" + X "$i`tCatalog.$objName.StandardAttribute.Code" + X "$i" + X "$iBegin" + X "$iDontUse" + X "$iDirectly" + X "$i" + X "$i" + X "$i" + X "$i" + X "$i" + X "$i" + X "$i" + X "$i" + X "$i" + X "$i" + X "$ifalse" + X "$i" + X "$i" + + $dataLockControlMode = if ($def.dataLockControlMode) { "$($def.dataLockControlMode)" } else { "Automatic" } + X "$i$dataLockControlMode" + + $fullTextSearch = if ($def.fullTextSearch) { "$($def.fullTextSearch)" } else { "Use" } + X "$i$fullTextSearch" + + X "$i" + X "$i" + X "$i" + X "$i" + X "$i" + X "$iDontUse" + X "$iAuto" + X "$iDontUse" + X "$ifalse" + X "$ifalse" +} + +function Emit-DocumentProperties { + param([string]$indent) + $i = $indent + + X "$i$(Esc-Xml $objName)" + Emit-MLText $i "Synonym" $synonym + X "$i" + X "$itrue" + X "$i" + + $numberType = if ($def.numberType) { "$($def.numberType)" } else { "String" } + $numberLength = if ($null -ne $def.numberLength) { "$($def.numberLength)" } else { "11" } + $numberAllowedLength = if ($def.numberAllowedLength) { "$($def.numberAllowedLength)" } else { "Variable" } + $numberPeriodicity = if ($def.numberPeriodicity) { "$($def.numberPeriodicity)" } else { "Year" } + $checkUnique = if ($def.checkUnique -eq $false) { "false" } else { "true" } + $autonumbering = if ($def.autonumbering -eq $false) { "false" } else { "true" } + + X "$i$numberType" + X "$i$numberLength" + X "$i$numberAllowedLength" + X "$i$numberPeriodicity" + X "$i$checkUnique" + X "$i$autonumbering" + + Emit-StandardAttributes $i "Document" + X "$i" + + X "$i" + X "$i" + X "$i`tDocument.$objName.StandardAttribute.Number" + X "$i" + X "$iDontUse" + X "$iBegin" + X "$iDontUse" + X "$iDirectly" + X "$i" + X "$i" + X "$i" + X "$i" + X "$i" + X "$i" + + $posting = if ($def.posting) { "$($def.posting)" } else { "Allow" } + $realTimePosting = if ($def.realTimePosting) { "$($def.realTimePosting)" } else { "Deny" } + $registerRecordsDeletion = if ($def.registerRecordsDeletion) { "$($def.registerRecordsDeletion)" } else { "AutoDelete" } + $registerRecordsWritingOnPost = if ($def.registerRecordsWritingOnPost) { "$($def.registerRecordsWritingOnPost)" } else { "WriteModified" } + $sequenceFilling = if ($def.sequenceFilling) { "$($def.sequenceFilling)" } else { "AutoFill" } + $postInPrivilegedMode = if ($def.postInPrivilegedMode -eq $false) { "false" } else { "true" } + $unpostInPrivilegedMode = if ($def.unpostInPrivilegedMode -eq $false) { "false" } else { "true" } + + X "$i$posting" + X "$i$realTimePosting" + X "$i$registerRecordsDeletion" + X "$i$registerRecordsWritingOnPost" + X "$i$sequenceFilling" + + # RegisterRecords + $regRecords = @() + if ($def.registerRecords) { + foreach ($rr in $def.registerRecords) { + $rrStr = "$rr" + # Resolve Russian synonyms in register records + if ($rrStr.Contains('.')) { + $dotIdx = $rrStr.IndexOf('.') + $rrPrefix = $rrStr.Substring(0, $dotIdx) + $rrSuffix = $rrStr.Substring($dotIdx + 1) + if ($script:objectTypeSynonyms.ContainsKey($rrPrefix)) { + $rrPrefix = $script:objectTypeSynonyms[$rrPrefix] + } + $regRecords += "$rrPrefix.$rrSuffix" + } else { + $regRecords += $rrStr + } + } + } + + if ($regRecords.Count -gt 0) { + X "$i" + foreach ($rr in $regRecords) { + X "$i`t$rr" + } + X "$i" + } else { + X "$i" + } + + X "$i$postInPrivilegedMode" + X "$i$unpostInPrivilegedMode" + X "$ifalse" + X "$i" + + $dataLockControlMode = if ($def.dataLockControlMode) { "$($def.dataLockControlMode)" } else { "Automatic" } + X "$i$dataLockControlMode" + + $fullTextSearch = if ($def.fullTextSearch) { "$($def.fullTextSearch)" } else { "Use" } + X "$i$fullTextSearch" + + X "$i" + X "$i" + X "$i" + X "$i" + X "$i" + X "$iAuto" + X "$iDontUse" + X "$ifalse" + X "$ifalse" +} + +function Emit-EnumProperties { + param([string]$indent) + $i = $indent + + X "$i$(Esc-Xml $objName)" + Emit-MLText $i "Synonym" $synonym + X "$i" + X "$ifalse" + + Emit-StandardAttributes $i "Enum" + X "$i" + + X "$itrue" + X "$iBothWays" + X "$i" + X "$i" + X "$i" + X "$i" + X "$i" + X "$i" + X "$i" + X "$iAuto" +} + +function Emit-ConstantProperties { + param([string]$indent) + $i = $indent + + X "$i$(Esc-Xml $objName)" + Emit-MLText $i "Synonym" $synonym + X "$i" + + # Type + $valueType = if ($def.valueType) { "$($def.valueType)" } else { "String" } + Emit-ValueType $i $valueType + + X "$itrue" + X "$i" + X "$i" + X "$i" + X "$ifalse" + X "$i" + X "$i" + X "$i" + X "$ifalse" + X "$i" + X "$ifalse" + X "$ifalse" + X "$i" + X "$i" + X "$iDontCheck" + X "$iItems" + X "$i" + X "$i" + X "$iAuto" + X "$i" + X "$i" + X "$iAuto" + + $dataLockControlMode = if ($def.dataLockControlMode) { "$($def.dataLockControlMode)" } else { "Automatic" } + X "$i$dataLockControlMode" + X "$iDontUse" + X "$ifalse" + X "$ifalse" +} + +function Emit-InformationRegisterProperties { + param([string]$indent) + $i = $indent + + X "$i$(Esc-Xml $objName)" + Emit-MLText $i "Synonym" $synonym + X "$i" + X "$itrue" + X "$iInDialog" + X "$i" + X "$i" + X "$i" + X "$i" + + Emit-StandardAttributes $i "InformationRegister" + + $periodicity = if ($def.periodicity) { "$($def.periodicity)" } else { "Nonperiodical" } + $writeMode = if ($def.writeMode) { "$($def.writeMode)" } else { "Independent" } + + # MainFilterOnPeriod: auto based on periodicity unless explicitly set + $mainFilterOnPeriod = "false" + if ($null -ne $def.mainFilterOnPeriod) { + $mainFilterOnPeriod = if ($def.mainFilterOnPeriod -eq $true) { "true" } else { "false" } + } elseif ($periodicity -ne "Nonperiodical") { + $mainFilterOnPeriod = "true" + } + + X "$i$periodicity" + X "$i$writeMode" + X "$i$mainFilterOnPeriod" + X "$ifalse" + + $dataLockControlMode = if ($def.dataLockControlMode) { "$($def.dataLockControlMode)" } else { "Automatic" } + X "$i$dataLockControlMode" + + $fullTextSearch = if ($def.fullTextSearch) { "$($def.fullTextSearch)" } else { "Use" } + X "$i$fullTextSearch" + + X "$ifalse" + X "$ifalse" + X "$i" + X "$i" + X "$i" + X "$i" + X "$i" + X "$iDontUse" + X "$ifalse" + X "$ifalse" +} + +function Emit-AccumulationRegisterProperties { + param([string]$indent) + $i = $indent + + X "$i$(Esc-Xml $objName)" + Emit-MLText $i "Synonym" $synonym + X "$i" + X "$itrue" + X "$i" + X "$i" + + $registerType = if ($def.registerType) { "$($def.registerType)" } else { "Balance" } + X "$i$registerType" + + X "$ifalse" + + Emit-StandardAttributes $i "AccumulationRegister" + + $dataLockControlMode = if ($def.dataLockControlMode) { "$($def.dataLockControlMode)" } else { "Automatic" } + X "$i$dataLockControlMode" + + $fullTextSearch = if ($def.fullTextSearch) { "$($def.fullTextSearch)" } else { "Use" } + X "$i$fullTextSearch" + + $enableTotalsSplitting = if ($def.enableTotalsSplitting -eq $false) { "false" } else { "true" } + X "$i$enableTotalsSplitting" + + X "$i" + X "$i" + X "$i" +} + +# --- 14. Namespaces --- + +$script:xmlnsDecl = 'xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' + +# --- 15. Main assembler --- + +$uuid = New-Guid-String + +# XML declaration +X '' +X "" +X "`t<$objType uuid=`"$uuid`">" + +# InternalInfo +Emit-InternalInfo "`t`t" $objType $objName + +# Properties +X "`t`t" + +switch ($objType) { + "Catalog" { Emit-CatalogProperties "`t`t`t" } + "Document" { Emit-DocumentProperties "`t`t`t" } + "Enum" { Emit-EnumProperties "`t`t`t" } + "Constant" { Emit-ConstantProperties "`t`t`t" } + "InformationRegister" { Emit-InformationRegisterProperties "`t`t`t" } + "AccumulationRegister" { Emit-AccumulationRegisterProperties "`t`t`t" } +} + +X "`t`t" + +# ChildObjects +$hasChildren = $false + +# Catalog/Document: attributes + tabularSections +if ($objType -in @("Catalog","Document")) { + $attrs = @() + if ($def.attributes) { + foreach ($a in $def.attributes) { + $attrs += Parse-AttributeShorthand $a + } + } + $tsSections = @{} + if ($def.tabularSections) { + $def.tabularSections.PSObject.Properties | ForEach-Object { + $tsSections[$_.Name] = @($_.Value) + } + } + + if ($attrs.Count -gt 0 -or $tsSections.Count -gt 0) { + $hasChildren = $true + X "`t`t" + $context = $objType.ToLower() + foreach ($a in $attrs) { + Emit-Attribute "`t`t`t" $a $context + } + foreach ($tsName in $tsSections.Keys) { + $columns = $tsSections[$tsName] + Emit-TabularSection "`t`t`t" $tsName $columns $objType $objName + } + X "`t`t" + } else { + X "`t`t" + } +} + +# Enum: enum values +if ($objType -eq "Enum") { + $values = @() + if ($def.values) { + foreach ($v in $def.values) { + $values += Parse-EnumValueShorthand $v + } + } + if ($values.Count -gt 0) { + $hasChildren = $true + X "`t`t" + foreach ($v in $values) { + Emit-EnumValue "`t`t`t" $v + } + X "`t`t" + } else { + X "`t`t" + } +} + +# Constant: no ChildObjects element at all + +# Registers: dimensions + resources + attributes +if ($objType -in @("InformationRegister","AccumulationRegister")) { + $dims = @() + $resources = @() + $regAttrs = @() + if ($def.dimensions) { + foreach ($d in $def.dimensions) { + $dims += Parse-AttributeShorthand $d + } + } + if ($def.resources) { + foreach ($r in $def.resources) { + $resources += Parse-AttributeShorthand $r + } + } + if ($def.attributes) { + foreach ($a in $def.attributes) { + $regAttrs += Parse-AttributeShorthand $a + } + } + + if ($dims.Count -gt 0 -or $resources.Count -gt 0 -or $regAttrs.Count -gt 0) { + $hasChildren = $true + X "`t`t" + foreach ($r in $resources) { + Emit-Resource "`t`t`t" $r $objType + } + foreach ($d in $dims) { + Emit-Dimension "`t`t`t" $d $objType + } + foreach ($a in $regAttrs) { + Emit-Attribute "`t`t`t" $a "register" + } + X "`t`t" + } else { + X "`t`t" + } +} + +X "`t" +X "" + +$metadataXml = $script:xml.ToString() + +# --- 16. Write files --- + +# Type → plural directory mapping +$script:typePluralMap = @{ + "Catalog" = "Catalogs" + "Document" = "Documents" + "Enum" = "Enums" + "Constant" = "Constants" + "InformationRegister" = "InformationRegisters" + "AccumulationRegister" = "AccumulationRegisters" +} + +$typePlural = $script:typePluralMap[$objType] +$typeDir = Join-Path $OutputDir $typePlural + +# Main XML file: {OutputDir}/{TypePlural}/{Name}.xml +$mainXmlPath = Join-Path $typeDir "$objName.xml" + +# Object subdirectory: {OutputDir}/{TypePlural}/{Name}/Ext/ +$objSubDir = Join-Path $typeDir $objName +$extDir = Join-Path $objSubDir "Ext" + +if (-not (Test-Path $typeDir)) { + New-Item -ItemType Directory -Path $typeDir -Force | Out-Null +} +if (-not (Test-Path $extDir)) { + New-Item -ItemType Directory -Path $extDir -Force | Out-Null +} + +$enc = New-Object System.Text.UTF8Encoding($true) +[System.IO.File]::WriteAllText($mainXmlPath, $metadataXml, $enc) + +# Module files +$moduleCreated = "" +switch ($objType) { + "Catalog" { + $modulePath = Join-Path $extDir "ObjectModule.bsl" + if (-not (Test-Path $modulePath)) { + [System.IO.File]::WriteAllText($modulePath, "", $enc) + $moduleCreated = $modulePath + } + } + "Document" { + $modulePath = Join-Path $extDir "ObjectModule.bsl" + if (-not (Test-Path $modulePath)) { + [System.IO.File]::WriteAllText($modulePath, "", $enc) + $moduleCreated = $modulePath + } + } + "InformationRegister" { + $modulePath = Join-Path $extDir "RecordSetModule.bsl" + if (-not (Test-Path $modulePath)) { + [System.IO.File]::WriteAllText($modulePath, "", $enc) + $moduleCreated = $modulePath + } + } + "AccumulationRegister" { + $modulePath = Join-Path $extDir "RecordSetModule.bsl" + if (-not (Test-Path $modulePath)) { + [System.IO.File]::WriteAllText($modulePath, "", $enc) + $moduleCreated = $modulePath + } + } +} + +# --- 17. Register in Configuration.xml --- + +$configXmlPath = Join-Path $OutputDir "Configuration.xml" +$regResult = $null + +# XML tag name for Configuration.xml ChildObjects +$childTag = $objType + +if (Test-Path $configXmlPath) { + $configDoc = New-Object System.Xml.XmlDocument + $configDoc.PreserveWhitespace = $true + $configDoc.Load($configXmlPath) + + $nsMgr = New-Object System.Xml.XmlNamespaceManager($configDoc.NameTable) + $nsMgr.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses") + + $childObjects = $configDoc.SelectSingleNode("//md:Configuration/md:ChildObjects", $nsMgr) + if ($childObjects) { + $existing = $childObjects.SelectNodes("md:$childTag", $nsMgr) + $alreadyExists = $false + foreach ($e in $existing) { + if ($e.InnerText -eq $objName) { + $alreadyExists = $true + break + } + } + + if ($alreadyExists) { + $regResult = "already" + } else { + $newElem = $configDoc.CreateElement($childTag, "http://v8.1c.ru/8.3/MDClasses") + $newElem.InnerText = $objName + + if ($existing.Count -gt 0) { + # Insert after last existing element of same type + $lastElem = $existing[$existing.Count - 1] + $newWs = $configDoc.CreateWhitespace("`n`t`t`t") + $childObjects.InsertAfter($newWs, $lastElem) | Out-Null + $childObjects.InsertAfter($newElem, $newWs) | Out-Null + } else { + # No existing elements of this type — insert before closing whitespace + $lastChild = $childObjects.LastChild + if ($lastChild.NodeType -eq [System.Xml.XmlNodeType]::Whitespace) { + $newWs = $configDoc.CreateWhitespace("`n`t`t`t") + $childObjects.InsertBefore($newWs, $lastChild) | Out-Null + $childObjects.InsertBefore($newElem, $lastChild) | Out-Null + } else { + $childObjects.AppendChild($configDoc.CreateWhitespace("`n`t`t`t")) | Out-Null + $childObjects.AppendChild($newElem) | Out-Null + $childObjects.AppendChild($configDoc.CreateWhitespace("`n`t`t")) | Out-Null + } + } + + # Save + $cfgSettings = New-Object System.Xml.XmlWriterSettings + $cfgSettings.Encoding = New-Object System.Text.UTF8Encoding($true) + $cfgSettings.Indent = $false + $stream = New-Object System.IO.FileStream($configXmlPath, [System.IO.FileMode]::Create) + $writer = [System.Xml.XmlWriter]::Create($stream, $cfgSettings) + $configDoc.Save($writer) + $writer.Close() + $stream.Close() + + $regResult = "added" + } + } else { + $regResult = "no-childobj" + } +} else { + $regResult = "no-config" +} + +# --- 18. Summary --- + +$attrCount = 0 +$tsCount = 0 +$dimCount = 0 +$resCount = 0 +$valCount = 0 + +if ($def.attributes) { $attrCount = @($def.attributes).Count } +if ($def.tabularSections) { $tsCount = @($def.tabularSections.PSObject.Properties).Count } +if ($def.dimensions) { $dimCount = @($def.dimensions).Count } +if ($def.resources) { $resCount = @($def.resources).Count } +if ($def.values) { $valCount = @($def.values).Count } + +Write-Host "[OK] $objType '$objName' compiled" +Write-Host " UUID: $uuid" +Write-Host " File: $mainXmlPath" + +$details = @() +if ($attrCount -gt 0) { $details += "Attributes: $attrCount" } +if ($tsCount -gt 0) { $details += "TabularSections: $tsCount" } +if ($dimCount -gt 0) { $details += "Dimensions: $dimCount" } +if ($resCount -gt 0) { $details += "Resources: $resCount" } +if ($valCount -gt 0) { $details += "Values: $valCount" } + +if ($details.Count -gt 0) { + Write-Host " $($details -join ', ')" +} + +if ($moduleCreated) { + Write-Host " Module: $moduleCreated" +} + +switch ($regResult) { + "added" { Write-Host " Configuration.xml: <$childTag>$objName added to ChildObjects" } + "already" { Write-Host " Configuration.xml: <$childTag>$objName already registered" } + "no-childobj" { Write-Warning "Configuration.xml found but not found" } + "no-config" { Write-Host " Configuration.xml: not found at $configXmlPath (register manually)" } +} diff --git a/docs/meta-dsl-spec.md b/docs/meta-dsl-spec.md new file mode 100644 index 00000000..bae3bb21 --- /dev/null +++ b/docs/meta-dsl-spec.md @@ -0,0 +1,412 @@ +# Meta DSL — спецификация JSON-формата для объектов метаданных 1С + +Версия: 1.0 + +## Обзор + +JSON DSL для описания объектов метаданных конфигурации 1С. Компактный формат компилируется в полноценный XML, совместимый с выгрузкой конфигурации 1С:Предприятие 8.3. + +Поддерживаемые типы (Фаза 1): **Catalog**, **Document**, **Enum**, **Constant**, **InformationRegister**, **AccumulationRegister**. + +--- + +## 1. Корневая структура + +```json +{ + "type": "Catalog", + "name": "Номенклатура", + "synonym": "авто из name", + "comment": "", + ...type-specific properties..., + "attributes": [...], + "tabularSections": {...} +} +``` + +| Поле | Тип | Обязательное | Описание | +|------|-----|-------------|----------| +| `type` | string | да | Тип объекта (см. §8) | +| `name` | string | да | Имя объекта (идентификатор 1С) | +| `synonym` | string | нет | Синоним; если не указан — авто из CamelCase (§2) | +| `comment` | string | нет | Комментарий | + +Дополнительные поля зависят от типа (§7). + +--- + +## 2. Автогенерация синонима (CamelCase → слова) + +Если `synonym` не указан, имя автоматически разбивается на слова: + +| Вход | Результат | +|------|-----------| +| `АвансовыйОтчет` | `Авансовый отчет` | +| `ОсновнаяВалюта` | `Основная валюта` | +| `НДС20` | `НДС20` | +| `IncomingDocument` | `Incoming document` | + +Правила: +- Граница на переходе `[а-яё][А-ЯЁ]` и `[a-z][A-Z]` +- Первое слово сохраняет заглавную, остальные — строчные +- Явный `synonym` перекрывает автогенерацию + +--- + +## 3. Система типов + +Совместима с skd-compile. + +### 3.1 Примитивные типы + +| DSL | XML | +|-----|-----| +| `String` или `String(100)` | `xs:string` + StringQualifiers | +| `Number(15,2)` | `xs:decimal` + NumberQualifiers | +| `Number(10,0,nonneg)` | `xs:decimal` + AllowedSign=Nonnegative | +| `Boolean` | `xs:boolean` | +| `Date` | `xs:dateTime` + DateFractions=Date | +| `DateTime` | `xs:dateTime` + DateFractions=DateTime | + +### 3.2 Ссылочные типы + +| DSL | XML | +|-----|-----| +| `CatalogRef.Xxx` | `cfg:CatalogRef.Xxx` | +| `DocumentRef.Xxx` | `cfg:DocumentRef.Xxx` | +| `EnumRef.Xxx` | `cfg:EnumRef.Xxx` | +| `ChartOfAccountsRef.Xxx` | `cfg:ChartOfAccountsRef.Xxx` | +| `DefinedType.Xxx` | `cfg:DefinedType.Xxx` (через `v8:TypeSet`) | + +### 3.3 Русские синонимы типов + +| Русский | Канонический | +|---------|-------------| +| `Строка(100)` | `String(100)` | +| `Число(15,2)` | `Number(15,2)` | +| `Булево` | `Boolean` | +| `Дата` | `Date` | +| `ДатаВремя` | `DateTime` | +| `СправочникСсылка.Xxx` | `CatalogRef.Xxx` | +| `ДокументСсылка.Xxx` | `DocumentRef.Xxx` | +| `ПеречислениеСсылка.Xxx` | `EnumRef.Xxx` | +| `ПланСчетовСсылка.Xxx` | `ChartOfAccountsRef.Xxx` | +| `ОпределяемыйТип.Xxx` | `DefinedType.Xxx` | + +Регистронезависимые. + +--- + +## 4. Сокращённая запись реквизитов + +### 4.1 Строковая форма + +``` +"ИмяРеквизита" → String (без квалификаторов) +"ИмяРеквизита: Тип" → с типом +"ИмяРеквизита: Тип | req, index" → с флагами +``` + +### 4.2 Объектная форма + +```json +{ + "name": "Имя", + "type": "String(100)", + "synonym": "Мой синоним", + "comment": "Комментарий", + "fillChecking": "ShowError", + "indexing": "Index" +} +``` + +### 4.3 Флаги + +| Флаг | Действие | Применимость | +|------|---------|-------------| +| `req` | FillChecking = ShowError | attributes, dimensions, resources | +| `index` | Indexing = Index | attributes, dimensions | +| `indexAdditional` | Indexing = IndexWithAdditionalOrder | attributes | +| `nonneg` | MinValue = 0 (+ nonneg для Number) | attributes, resources | +| `master` | Master = true | dimensions (РС) | +| `mainFilter` | MainFilter = true | dimensions (РС) | +| `denyIncomplete` | DenyIncompleteValues = true | dimensions | +| `useInTotals` | UseInTotals = true | dimensions (РН) | + +Флаги разделяются запятой после `|`. + +--- + +## 5. Табличные части + +Только для Catalog и Document. + +```json +"tabularSections": { + "Товары": [ + "Номенклатура: CatalogRef.Номенклатура | req", + "Количество: Number(10,3)", + "Цена: Number(15,2)", + "Сумма: Number(15,2)" + ], + "Услуги": [ + "Описание: String(200)" + ] +} +``` + +Ключ — имя табличной части, значение — массив реквизитов (в строковой или объектной форме). + +--- + +## 6. Значения перечислений + +Только для Enum. + +```json +"values": [ + "Приход", + "Расход", + { "name": "НДС20", "synonym": "НДС 20%" } +] +``` + +Строка — имя (синоним авто из CamelCase). Объект — полная форма. + +--- + +## 7. Свойства по типам + +### 7.1 Catalog + +| Поле JSON | Умолчание | XML элемент | +|-----------|----------|-------------| +| `hierarchical` | `false` | Hierarchical | +| `hierarchyType` | `HierarchyFoldersAndItems` | HierarchyType | +| `codeLength` | `9` | CodeLength | +| `codeType` | `String` | CodeType | +| `codeAllowedLength` | `Variable` | CodeAllowedLength | +| `descriptionLength` | `25` | DescriptionLength | +| `autonumbering` | `true` | Autonumbering | +| `checkUnique` | `false` | CheckUnique | +| `defaultPresentation` | `AsDescription` | DefaultPresentation | +| `dataLockControlMode` | `Automatic` | DataLockControlMode | +| `fullTextSearch` | `Use` | FullTextSearch | +| `owners` | `[]` | Owners | +| `attributes` | `[]` | → Attribute в ChildObjects | +| `tabularSections` | `{}` | → TabularSection в ChildObjects | + +### 7.2 Document + +| Поле JSON | Умолчание | XML элемент | +|-----------|----------|-------------| +| `numberType` | `String` | NumberType | +| `numberLength` | `11` | NumberLength | +| `numberAllowedLength` | `Variable` | NumberAllowedLength | +| `numberPeriodicity` | `Year` | NumberPeriodicity | +| `checkUnique` | `true` | CheckUnique | +| `autonumbering` | `true` | Autonumbering | +| `posting` | `Allow` | Posting | +| `realTimePosting` | `Deny` | RealTimePosting | +| `registerRecordsDeletion` | `AutoDelete` | RegisterRecordsDeletion | +| `registerRecordsWritingOnPost` | `WriteModified` | RegisterRecordsWritingOnPost | +| `postInPrivilegedMode` | `true` | PostInPrivilegedMode | +| `unpostInPrivilegedMode` | `true` | UnpostInPrivilegedMode | +| `dataLockControlMode` | `Automatic` | DataLockControlMode | +| `fullTextSearch` | `Use` | FullTextSearch | +| `registerRecords` | `[]` | RegisterRecords | +| `attributes` | `[]` | → Attribute в ChildObjects | +| `tabularSections` | `{}` | → TabularSection в ChildObjects | + +### 7.3 Enum + +| Поле JSON | Умолчание | XML элемент | +|-----------|----------|-------------| +| `values` | `[]` | → EnumValue в ChildObjects | + +Других настраиваемых свойств нет — все дефолтные. + +### 7.4 Constant + +| Поле JSON | Умолчание | XML элемент | +|-----------|----------|-------------| +| `valueType` | `String` | Type | +| `dataLockControlMode` | `Automatic` | DataLockControlMode | + +### 7.5 InformationRegister + +| Поле JSON | Умолчание | XML элемент | +|-----------|----------|-------------| +| `writeMode` | `Independent` | WriteMode | +| `periodicity` | `Nonperiodical` | InformationRegisterPeriodicity | +| `mainFilterOnPeriod` | авто* | MainFilterOnPeriod | +| `dataLockControlMode` | `Automatic` | DataLockControlMode | +| `fullTextSearch` | `Use` | FullTextSearch | +| `dimensions` | `[]` | → Dimension в ChildObjects | +| `resources` | `[]` | → Resource в ChildObjects | +| `attributes` | `[]` | → Attribute в ChildObjects | + +\* `mainFilterOnPeriod` = `true` если `periodicity` != `Nonperiodical`, иначе `false`. + +### 7.6 AccumulationRegister + +| Поле JSON | Умолчание | XML элемент | +|-----------|----------|-------------| +| `registerType` | `Balance` | RegisterType | +| `enableTotalsSplitting` | `true` | EnableTotalsSplitting | +| `dataLockControlMode` | `Automatic` | DataLockControlMode | +| `fullTextSearch` | `Use` | FullTextSearch | +| `dimensions` | `[]` | → Dimension в ChildObjects | +| `resources` | `[]` | → Resource в ChildObjects | +| `attributes` | `[]` | → Attribute в ChildObjects | + +--- + +## 8. Русские синонимы типов объектов + +| Русский | Канонический | +|---------|-------------| +| `Справочник` | `Catalog` | +| `Документ` | `Document` | +| `Перечисление` | `Enum` | +| `Константа` | `Constant` | +| `РегистрСведений` | `InformationRegister` | +| `РегистрНакопления` | `AccumulationRegister` | + +--- + +## 9. RegisterRecords для документов + +```json +"registerRecords": [ + "AccumulationRegister.Продажи", + "InformationRegister.Цены" +] +``` + +Или с русскими синонимами: `"РегистрНакопления.Продажи"`. + +--- + +## 10. Измерения и ресурсы регистров + +Синтаксис аналогичен реквизитам (§4), но с дополнительными флагами: + +### Измерения (dimensions) + +```json +"dimensions": [ + "Организация: CatalogRef.Организации | master, mainFilter, denyIncomplete", + "Номенклатура: CatalogRef.Номенклатура" +] +``` + +### Ресурсы (resources) + +```json +"resources": [ + "Количество: Number(15,3)", + "Сумма: Number(15,2)" +] +``` + +Флаг `useInTotals` — только для измерений AccumulationRegister (по умолчанию `true`). + +--- + +## 11. Примеры + +### Минимальные + +```json +{ "type": "Catalog", "name": "Валюты" } +``` + +```json +{ "type": "Enum", "name": "Статусы", "values": ["Новый", "Закрыт"] } +``` + +```json +{ "type": "Constant", "name": "ОсновнаяВалюта", "valueType": "CatalogRef.Валюты" } +``` + +### Справочник с реквизитами и табличной частью + +```json +{ + "type": "Catalog", + "name": "Номенклатура", + "codeLength": 11, + "descriptionLength": 100, + "hierarchical": true, + "attributes": [ + "Артикул: String(25)", + "ЕдиницаИзмерения: CatalogRef.ЕдиницыИзмерения | req", + "ВидНоменклатуры: EnumRef.ВидыНоменклатуры", + "Цена: Number(15,2)" + ], + "tabularSections": { + "Штрихкоды": [ + "Штрихкод: String(200) | req, index" + ] + } +} +``` + +### Документ с движениями + +```json +{ + "type": "Document", + "name": "РеализацияТоваров", + "posting": "Allow", + "registerRecords": ["AccumulationRegister.Продажи"], + "attributes": [ + "Организация: CatalogRef.Организации | req", + "Контрагент: CatalogRef.Контрагенты | req", + "Склад: CatalogRef.Склады" + ], + "tabularSections": { + "Товары": [ + "Номенклатура: CatalogRef.Номенклатура | req", + "Количество: Number(15,3)", + "Цена: Number(15,2)", + "Сумма: Number(15,2)" + ] + } +} +``` + +### Регистр сведений с периодичностью + +```json +{ + "type": "InformationRegister", + "name": "КурсыВалют", + "periodicity": "Day", + "dimensions": [ + "Валюта: CatalogRef.Валюты | master, mainFilter, denyIncomplete" + ], + "resources": [ + "Курс: Number(15,4)", + "Кратность: Number(10,0)" + ] +} +``` + +### Регистр накопления + +```json +{ + "type": "AccumulationRegister", + "name": "ОстаткиТоваров", + "registerType": "Balance", + "dimensions": [ + "Номенклатура: CatalogRef.Номенклатура", + "Склад: CatalogRef.Склады" + ], + "resources": [ + "Количество: Number(15,3)" + ] +} +```