mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-06-10 16:14:54 +03:00
Add skd-compile and skd-validate skills for DCS schema generation
- skd-compile: JSON DSL → Template.xml (DataCompositionSchema) Shorthand parsers for fields, totals, parameters, calculated fields. Full type system, settings variants with selection/filter/order/structure. - skd-validate: structural validation of Template.xml (~30 checks) DataSources, DataSets, fields, links, params, templates, variants. - docs/skd-dsl-spec.md: full DSL specification Tested on compiled examples and 5+ real DCS from acc_8.3.24 (0 errors). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
---
|
||||
name: skd-compile
|
||||
description: Компиляция схемы компоновки данных 1С (СКД) — Template.xml из компактного JSON-определения
|
||||
argument-hint: <JsonPath> <OutputPath>
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
- Write
|
||||
- Glob
|
||||
---
|
||||
|
||||
# /skd-compile — генерация СКД из JSON DSL
|
||||
|
||||
Принимает JSON-определение схемы компоновки данных → генерирует Template.xml (DataCompositionSchema).
|
||||
|
||||
## Параметры и команда
|
||||
|
||||
| Параметр | Описание |
|
||||
|----------|----------|
|
||||
| `JsonPath` | Путь к JSON-определению СКД |
|
||||
| `OutputPath` | Путь к выходному Template.xml |
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude\skills\skd-compile\scripts\skd-compile.ps1 -JsonPath "<json>" -OutputPath "<Template.xml>"
|
||||
```
|
||||
|
||||
## JSON DSL — краткий справочник
|
||||
|
||||
Полная спецификация: `docs/skd-dsl-spec.md`.
|
||||
|
||||
### Корневая структура
|
||||
|
||||
```json
|
||||
{
|
||||
"dataSets": [...],
|
||||
"calculatedFields": [...],
|
||||
"totalFields": [...],
|
||||
"parameters": [...],
|
||||
"dataSetLinks": [...],
|
||||
"settingsVariants": [...]
|
||||
}
|
||||
```
|
||||
|
||||
Умолчания: `dataSources` → авто `ИсточникДанных1/Local`; `settingsVariants` → авто "Основной" с деталями.
|
||||
|
||||
### Наборы данных
|
||||
|
||||
Тип по ключу: `query` → DataSetQuery, `objectName` → DataSetObject, `items` → DataSetUnion.
|
||||
|
||||
```json
|
||||
{ "name": "Продажи", "query": "ВЫБРАТЬ ...", "fields": [...] }
|
||||
```
|
||||
|
||||
### Поля — shorthand
|
||||
|
||||
```
|
||||
"Наименование" — просто имя
|
||||
"Количество: decimal(15,2)" — имя + тип
|
||||
"Организация: CatalogRef.Организации @dimension" — + роль
|
||||
"Служебное: string #noFilter #noOrder" — + ограничения
|
||||
```
|
||||
|
||||
Типы: `string`, `string(N)`, `decimal(D,F)`, `boolean`, `date`, `dateTime`, `CatalogRef.X`, `DocumentRef.X`, `EnumRef.X`, `StandardPeriod`.
|
||||
|
||||
Роли: `@dimension`, `@account`, `@balance`, `@period`.
|
||||
|
||||
Ограничения: `#noField`, `#noFilter`, `#noGroup`, `#noOrder`.
|
||||
|
||||
### Итоги (shorthand)
|
||||
|
||||
```json
|
||||
"totalFields": ["Количество: Сумма", "Стоимость: Сумма(Кол * Цена)"]
|
||||
```
|
||||
|
||||
### Параметры (shorthand)
|
||||
|
||||
```json
|
||||
"parameters": ["Период: StandardPeriod = LastMonth", "Организация: CatalogRef.Организации"]
|
||||
```
|
||||
|
||||
### Варианты настроек
|
||||
|
||||
```json
|
||||
"settingsVariants": [{
|
||||
"name": "Основной",
|
||||
"presentation": "Основной",
|
||||
"settings": {
|
||||
"selection": ["Наименование", "Количество", "Auto"],
|
||||
"filter": [{ "field": "Организация", "op": "=", "use": false, "userSettingID": "auto" }],
|
||||
"order": ["Количество desc", "Auto"],
|
||||
"outputParameters": { "Заголовок": "Мой отчёт" },
|
||||
"structure": [{ "type": "group", "groupBy": ["Организация"], "selection": ["Auto"], "order": ["Auto"],
|
||||
"children": [{ "type": "group", "selection": ["Auto"], "order": ["Auto"] }]
|
||||
}]
|
||||
}
|
||||
}]
|
||||
```
|
||||
|
||||
## Примеры
|
||||
|
||||
### Минимальный
|
||||
|
||||
```json
|
||||
{
|
||||
"dataSets": [{
|
||||
"query": "ВЫБРАТЬ Номенклатура.Наименование КАК Наименование ИЗ Справочник.Номенклатура КАК Номенклатура",
|
||||
"fields": ["Наименование"]
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
### С ресурсами и параметрами
|
||||
|
||||
```json
|
||||
{
|
||||
"dataSets": [{
|
||||
"query": "ВЫБРАТЬ Продажи.Номенклатура, Продажи.Количество, Продажи.Сумма ИЗ РегистрНакопления.Продажи КАК Продажи",
|
||||
"fields": ["Номенклатура: CatalogRef.Номенклатура @dimension", "Количество: decimal(15,3)", "Сумма: decimal(15,2)"]
|
||||
}],
|
||||
"totalFields": ["Количество: Сумма", "Сумма: Сумма"],
|
||||
"parameters": ["Период: StandardPeriod = LastMonth"]
|
||||
}
|
||||
```
|
||||
|
||||
## Верификация
|
||||
|
||||
```
|
||||
/skd-validate <OutputPath> — валидация структуры XML
|
||||
/skd-info <OutputPath> — визуальная сводка
|
||||
/skd-info <OutputPath> -Mode variant -Name 1 — проверка варианта настроек
|
||||
```
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,74 @@
|
||||
---
|
||||
name: skd-validate
|
||||
description: Валидация структурной корректности схемы компоновки данных 1С (СКД) — Template.xml
|
||||
argument-hint: <TemplatePath> [-MaxErrors 20]
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
- Glob
|
||||
---
|
||||
|
||||
# /skd-validate — валидация СКД (DataCompositionSchema)
|
||||
|
||||
Проверяет структурную корректность Template.xml схемы компоновки данных. Выявляет ошибки формата, битые ссылки, дубликаты имён.
|
||||
|
||||
## Параметры и команда
|
||||
|
||||
| Параметр | Описание |
|
||||
|----------|----------|
|
||||
| `TemplatePath` | Путь к Template.xml или каталогу макета (авто-резолв в `Ext/Template.xml`) |
|
||||
| `MaxErrors` | Макс. ошибок до остановки (по умолчанию 20) |
|
||||
| `OutFile` | Записать результат в файл |
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude\skills\skd-validate\scripts\skd-validate.ps1 -TemplatePath "<путь>"
|
||||
```
|
||||
|
||||
## Проверки (~30)
|
||||
|
||||
| Группа | Что проверяется |
|
||||
|--------|-----------------|
|
||||
| **Root** | XML parse, корневой элемент `DataCompositionSchema`, default namespace, ns-префиксы |
|
||||
| **DataSource** | Наличие, name не пуст, type валиден (Local/External), уникальность имён |
|
||||
| **DataSet** | Наличие, xsi:type валиден, name не пуст, уникальность, ссылка на dataSource, query не пуст |
|
||||
| **Fields** | dataPath не пуст, field не пуст, уникальность dataPath в наборе |
|
||||
| **Links** | source/dest ссылаются на существующие наборы, expressions не пусты |
|
||||
| **CalcFields** | dataPath не пуст, expression не пуст, уникальность, коллизии с полями наборов |
|
||||
| **TotalFields** | dataPath не пуст, expression не пуст |
|
||||
| **Parameters** | name не пуст, уникальность |
|
||||
| **Templates** | name не пуст, уникальность |
|
||||
| **GroupTemplates** | template ссылается на существующий template, templateType валиден |
|
||||
| **Variants** | Наличие, name не пуст, settings element присутствует |
|
||||
| **Settings** | selection/filter/order ссылаются на известные поля, comparisonType валиден, structure items типизированы |
|
||||
|
||||
## Коды выхода
|
||||
|
||||
| Код | Значение |
|
||||
|-----|----------|
|
||||
| 0 | Ошибок нет (могут быть предупреждения) |
|
||||
| 1 | Есть ошибки |
|
||||
|
||||
## Пример вывода
|
||||
|
||||
```
|
||||
=== Validation: Template.xml ===
|
||||
|
||||
[OK] XML parsed successfully
|
||||
[OK] Root element: DataCompositionSchema
|
||||
[OK] Default namespace correct
|
||||
[OK] 1 dataSource(s) found, names unique
|
||||
[OK] 1 dataSet(s) found, names unique
|
||||
[OK] DataSet "НаборДанных1": 2 fields, dataPath unique
|
||||
[OK] 1 totalField(s): dataPath and expression present
|
||||
[OK] 1 settingsVariant(s) found
|
||||
|
||||
=== Result: 0 errors, 0 warnings ===
|
||||
```
|
||||
|
||||
## Верификация
|
||||
|
||||
```
|
||||
/skd-compile <JsonPath> <OutputPath> — генерация XML
|
||||
/skd-validate <OutputPath> — проверка результата
|
||||
/skd-info <OutputPath> — визуальная сводка
|
||||
```
|
||||
@@ -0,0 +1,717 @@
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$TemplatePath,
|
||||
|
||||
[int]$MaxErrors = 20,
|
||||
|
||||
[string]$OutFile
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
# --- Resolve path ---
|
||||
|
||||
if (-not $TemplatePath.EndsWith(".xml")) {
|
||||
$candidate = Join-Path (Join-Path $TemplatePath "Ext") "Template.xml"
|
||||
if (Test-Path $candidate) {
|
||||
$TemplatePath = $candidate
|
||||
}
|
||||
}
|
||||
|
||||
if (-not (Test-Path $TemplatePath)) {
|
||||
Write-Error "File not found: $TemplatePath"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$resolvedPath = (Resolve-Path $TemplatePath).Path
|
||||
$fileName = [System.IO.Path]::GetFileName($resolvedPath)
|
||||
|
||||
# --- Output infrastructure ---
|
||||
|
||||
$script:errors = 0
|
||||
$script:warnings = 0
|
||||
$script:stopped = $false
|
||||
$script:output = New-Object System.Text.StringBuilder 4096
|
||||
|
||||
function Out-Line {
|
||||
param([string]$msg)
|
||||
$script:output.AppendLine($msg) | Out-Null
|
||||
}
|
||||
|
||||
function Report-OK {
|
||||
param([string]$msg)
|
||||
Out-Line "[OK] $msg"
|
||||
}
|
||||
|
||||
function Report-Error {
|
||||
param([string]$msg)
|
||||
$script:errors++
|
||||
Out-Line "[ERROR] $msg"
|
||||
if ($script:errors -ge $MaxErrors) {
|
||||
$script:stopped = $true
|
||||
}
|
||||
}
|
||||
|
||||
function Report-Warn {
|
||||
param([string]$msg)
|
||||
$script:warnings++
|
||||
Out-Line "[WARN] $msg"
|
||||
}
|
||||
|
||||
$finalize = {
|
||||
Out-Line ""
|
||||
Out-Line "=== Result: $($script:errors) errors, $($script:warnings) warnings ==="
|
||||
|
||||
$result = $script:output.ToString()
|
||||
Write-Host $result
|
||||
|
||||
if ($OutFile) {
|
||||
$utf8Bom = New-Object System.Text.UTF8Encoding $true
|
||||
[System.IO.File]::WriteAllText($OutFile, $result, $utf8Bom)
|
||||
Write-Host "Written to: $OutFile"
|
||||
}
|
||||
}
|
||||
|
||||
Out-Line "=== Validation: $fileName ==="
|
||||
Out-Line ""
|
||||
|
||||
# --- 1. Parse XML ---
|
||||
|
||||
$xmlDoc = $null
|
||||
try {
|
||||
$xmlDoc = New-Object System.Xml.XmlDocument
|
||||
$xmlDoc.PreserveWhitespace = $false
|
||||
$xmlDoc.Load($resolvedPath)
|
||||
Report-OK "XML parsed successfully"
|
||||
} catch {
|
||||
Report-Error "XML parse failed: $($_.Exception.Message)"
|
||||
# Cannot continue
|
||||
$result = $script:output.ToString()
|
||||
Write-Host $result
|
||||
if ($OutFile) {
|
||||
$utf8Bom = New-Object System.Text.UTF8Encoding $true
|
||||
[System.IO.File]::WriteAllText($OutFile, $result, $utf8Bom)
|
||||
}
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- 2. Register namespaces ---
|
||||
|
||||
$ns = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable)
|
||||
$ns.AddNamespace("s", "http://v8.1c.ru/8.1/data-composition-system/schema")
|
||||
$ns.AddNamespace("dcscom", "http://v8.1c.ru/8.1/data-composition-system/common")
|
||||
$ns.AddNamespace("dcscor", "http://v8.1c.ru/8.1/data-composition-system/core")
|
||||
$ns.AddNamespace("dcsset", "http://v8.1c.ru/8.1/data-composition-system/settings")
|
||||
$ns.AddNamespace("v8", "http://v8.1c.ru/8.1/data/core")
|
||||
$ns.AddNamespace("v8ui", "http://v8.1c.ru/8.1/data/ui")
|
||||
$ns.AddNamespace("xs", "http://www.w3.org/2001/XMLSchema")
|
||||
$ns.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance")
|
||||
$ns.AddNamespace("dcsat", "http://v8.1c.ru/8.1/data-composition-system/area-template")
|
||||
|
||||
$root = $xmlDoc.DocumentElement
|
||||
|
||||
# --- 3. Root element checks ---
|
||||
|
||||
if ($root.LocalName -ne "DataCompositionSchema") {
|
||||
Report-Error "Root element is '$($root.LocalName)', expected 'DataCompositionSchema'"
|
||||
} else {
|
||||
Report-OK "Root element: DataCompositionSchema"
|
||||
}
|
||||
|
||||
$expectedNs = "http://v8.1c.ru/8.1/data-composition-system/schema"
|
||||
if ($root.NamespaceURI -ne $expectedNs) {
|
||||
Report-Error "Default namespace is '$($root.NamespaceURI)', expected '$expectedNs'"
|
||||
} else {
|
||||
Report-OK "Default namespace correct"
|
||||
}
|
||||
|
||||
if ($script:stopped) { & $finalize; exit 1 }
|
||||
|
||||
# --- 4. Collect inventories ---
|
||||
|
||||
# DataSources
|
||||
$dataSourceNodes = $root.SelectNodes("s:dataSource", $ns)
|
||||
$dataSourceNames = @{}
|
||||
foreach ($dsn in $dataSourceNodes) {
|
||||
$name = $dsn.SelectSingleNode("s:name", $ns)
|
||||
if ($name) { $dataSourceNames[$name.InnerText] = $true }
|
||||
}
|
||||
|
||||
# DataSets (recursive for unions)
|
||||
$dataSetNodes = $root.SelectNodes("s:dataSet", $ns)
|
||||
$dataSetNames = @{}
|
||||
$allFieldPaths = @{} # Global: dataPath → dataSet name
|
||||
|
||||
function Collect-DataSetFields {
|
||||
param($dsNode, [string]$dsName)
|
||||
|
||||
$fields = $dsNode.SelectNodes("s:field", $ns)
|
||||
$localPaths = @{}
|
||||
foreach ($f in $fields) {
|
||||
$dp = $f.SelectSingleNode("s:dataPath", $ns)
|
||||
if ($dp) {
|
||||
$path = $dp.InnerText
|
||||
$localPaths[$path] = $true
|
||||
$allFieldPaths[$path] = $dsName
|
||||
}
|
||||
}
|
||||
|
||||
# Union items
|
||||
$items = $dsNode.SelectNodes("s:item", $ns)
|
||||
foreach ($item in $items) {
|
||||
$itemName = $item.SelectSingleNode("s:name", $ns)
|
||||
if ($itemName) {
|
||||
Collect-DataSetFields -dsNode $item -dsName $itemName.InnerText
|
||||
}
|
||||
}
|
||||
|
||||
return $localPaths
|
||||
}
|
||||
|
||||
$dataSetFieldMap = @{} # dsName → hashtable of dataPath
|
||||
foreach ($ds in $dataSetNodes) {
|
||||
$nameNode = $ds.SelectSingleNode("s:name", $ns)
|
||||
if ($nameNode) {
|
||||
$dsName = $nameNode.InnerText
|
||||
$dataSetNames[$dsName] = $true
|
||||
$dataSetFieldMap[$dsName] = Collect-DataSetFields -dsNode $ds -dsName $dsName
|
||||
}
|
||||
}
|
||||
|
||||
# CalculatedFields
|
||||
$calcFieldNodes = $root.SelectNodes("s:calculatedField", $ns)
|
||||
$calcFieldPaths = @{}
|
||||
foreach ($cf in $calcFieldNodes) {
|
||||
$dp = $cf.SelectSingleNode("s:dataPath", $ns)
|
||||
if ($dp) { $calcFieldPaths[$dp.InnerText] = $true }
|
||||
}
|
||||
|
||||
# TotalFields
|
||||
$totalFieldNodes = $root.SelectNodes("s:totalField", $ns)
|
||||
|
||||
# Parameters
|
||||
$paramNodes = $root.SelectNodes("s:parameter", $ns)
|
||||
$paramNames = @{}
|
||||
foreach ($p in $paramNodes) {
|
||||
$nameNode = $p.SelectSingleNode("s:name", $ns)
|
||||
if ($nameNode) { $paramNames[$nameNode.InnerText] = $true }
|
||||
}
|
||||
|
||||
# Templates
|
||||
$templateNodes = $root.SelectNodes("s:template", $ns)
|
||||
$templateNames = @{}
|
||||
foreach ($t in $templateNodes) {
|
||||
$nameNode = $t.SelectSingleNode("s:name", $ns)
|
||||
if ($nameNode) { $templateNames[$nameNode.InnerText] = $true }
|
||||
}
|
||||
|
||||
# GroupTemplates
|
||||
$groupTemplateNodes = $root.SelectNodes("s:groupTemplate", $ns)
|
||||
|
||||
# SettingsVariants
|
||||
$variantNodes = $root.SelectNodes("s:settingsVariant", $ns)
|
||||
|
||||
# Known fields = dataset fields + calculated fields
|
||||
$knownFields = @{}
|
||||
foreach ($key in $allFieldPaths.Keys) { $knownFields[$key] = $true }
|
||||
foreach ($key in $calcFieldPaths.Keys) { $knownFields[$key] = $true }
|
||||
|
||||
# --- 5. DataSource checks ---
|
||||
|
||||
if ($dataSourceNodes.Count -eq 0) {
|
||||
Report-Warn "No dataSource elements found (settings-only DCS?)"
|
||||
} else {
|
||||
$dsNamesSeen = @{}
|
||||
$dsOk = $true
|
||||
foreach ($dsn in $dataSourceNodes) {
|
||||
$name = $dsn.SelectSingleNode("s:name", $ns)
|
||||
$type = $dsn.SelectSingleNode("s:dataSourceType", $ns)
|
||||
if (-not $name -or -not $name.InnerText) {
|
||||
Report-Error "DataSource has empty name"
|
||||
$dsOk = $false
|
||||
} elseif ($dsNamesSeen.ContainsKey($name.InnerText)) {
|
||||
Report-Error "Duplicate dataSource name: $($name.InnerText)"
|
||||
$dsOk = $false
|
||||
} else {
|
||||
$dsNamesSeen[$name.InnerText] = $true
|
||||
}
|
||||
if ($type) {
|
||||
$tv = $type.InnerText
|
||||
if ($tv -ne "Local" -and $tv -ne "External") {
|
||||
Report-Warn "DataSource '$($name.InnerText)' has unusual type: $tv"
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($dsOk) {
|
||||
Report-OK "$($dataSourceNodes.Count) dataSource(s) found, names unique"
|
||||
}
|
||||
}
|
||||
|
||||
if ($script:stopped) { & $finalize; exit 1 }
|
||||
|
||||
# --- 6. DataSet checks ---
|
||||
|
||||
$validDsTypes = @("DataSetQuery", "DataSetObject", "DataSetUnion")
|
||||
|
||||
if ($dataSetNodes.Count -eq 0) {
|
||||
Report-Warn "No dataSet elements found (settings-only DCS?)"
|
||||
} else {
|
||||
$dsNamesSeen = @{}
|
||||
$dsOk = $true
|
||||
foreach ($ds in $dataSetNodes) {
|
||||
$xsiType = $ds.GetAttribute("type", "http://www.w3.org/2001/XMLSchema-instance")
|
||||
$nameNode = $ds.SelectSingleNode("s:name", $ns)
|
||||
$dsName = if ($nameNode) { $nameNode.InnerText } else { "(unnamed)" }
|
||||
|
||||
if (-not $nameNode -or -not $nameNode.InnerText) {
|
||||
Report-Error "DataSet has empty name"
|
||||
$dsOk = $false
|
||||
} elseif ($dsNamesSeen.ContainsKey($dsName)) {
|
||||
Report-Error "Duplicate dataSet name: $dsName"
|
||||
$dsOk = $false
|
||||
} else {
|
||||
$dsNamesSeen[$dsName] = $true
|
||||
}
|
||||
|
||||
if (-not $xsiType) {
|
||||
Report-Error "DataSet '$dsName' missing xsi:type"
|
||||
$dsOk = $false
|
||||
} elseif ($validDsTypes -notcontains $xsiType) {
|
||||
Report-Warn "DataSet '$dsName' has unusual xsi:type: $xsiType"
|
||||
}
|
||||
|
||||
# Check dataSource reference
|
||||
if ($xsiType -ne "DataSetUnion") {
|
||||
$srcNode = $ds.SelectSingleNode("s:dataSource", $ns)
|
||||
if ($srcNode -and $srcNode.InnerText) {
|
||||
if (-not $dataSourceNames.ContainsKey($srcNode.InnerText)) {
|
||||
Report-Error "DataSet '$dsName' references unknown dataSource: $($srcNode.InnerText)"
|
||||
$dsOk = $false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Check query not empty for Query type
|
||||
if ($xsiType -eq "DataSetQuery") {
|
||||
$queryNode = $ds.SelectSingleNode("s:query", $ns)
|
||||
if (-not $queryNode -or -not $queryNode.InnerText.Trim()) {
|
||||
Report-Warn "DataSet '$dsName' (Query) has empty query"
|
||||
}
|
||||
}
|
||||
|
||||
# Check objectName for Object type
|
||||
if ($xsiType -eq "DataSetObject") {
|
||||
$objNode = $ds.SelectSingleNode("s:objectName", $ns)
|
||||
if (-not $objNode -or -not $objNode.InnerText.Trim()) {
|
||||
Report-Error "DataSet '$dsName' (Object) has empty objectName"
|
||||
$dsOk = $false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($dsOk) {
|
||||
Report-OK "$($dataSetNodes.Count) dataSet(s) found, names unique"
|
||||
}
|
||||
}
|
||||
|
||||
if ($script:stopped) { & $finalize; exit 1 }
|
||||
|
||||
# --- 7. Field checks ---
|
||||
|
||||
function Check-DataSetFields {
|
||||
param($dsNode, [string]$dsName)
|
||||
|
||||
$fields = $dsNode.SelectNodes("s:field", $ns)
|
||||
if ($fields.Count -eq 0) { return }
|
||||
|
||||
$pathsSeen = @{}
|
||||
$fieldOk = $true
|
||||
|
||||
foreach ($f in $fields) {
|
||||
$dp = $f.SelectSingleNode("s:dataPath", $ns)
|
||||
$fn = $f.SelectSingleNode("s:field", $ns)
|
||||
|
||||
if (-not $dp -or -not $dp.InnerText) {
|
||||
Report-Error "DataSet '$dsName': field has empty dataPath"
|
||||
$fieldOk = $false
|
||||
continue
|
||||
}
|
||||
|
||||
$path = $dp.InnerText
|
||||
if ($pathsSeen.ContainsKey($path)) {
|
||||
Report-Warn "DataSet '$dsName': duplicate dataPath '$path'"
|
||||
} else {
|
||||
$pathsSeen[$path] = $true
|
||||
}
|
||||
|
||||
if (-not $fn -or -not $fn.InnerText) {
|
||||
Report-Warn "DataSet '$dsName': field '$path' has empty <field> element"
|
||||
}
|
||||
}
|
||||
|
||||
if ($fieldOk) {
|
||||
Report-OK "DataSet `"$dsName`": $($fields.Count) fields, dataPath unique"
|
||||
}
|
||||
|
||||
# Check union items recursively
|
||||
$items = $dsNode.SelectNodes("s:item", $ns)
|
||||
foreach ($item in $items) {
|
||||
$itemName = $item.SelectSingleNode("s:name", $ns)
|
||||
$iName = if ($itemName) { $itemName.InnerText } else { "(unnamed item)" }
|
||||
Check-DataSetFields -dsNode $item -dsName $iName
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($ds in $dataSetNodes) {
|
||||
$nameNode = $ds.SelectSingleNode("s:name", $ns)
|
||||
$dsName = if ($nameNode) { $nameNode.InnerText } else { "(unnamed)" }
|
||||
Check-DataSetFields -dsNode $ds -dsName $dsName
|
||||
}
|
||||
|
||||
if ($script:stopped) { & $finalize; exit 1 }
|
||||
|
||||
# --- 8. DataSetLink checks ---
|
||||
|
||||
$linkNodes = $root.SelectNodes("s:dataSetLink", $ns)
|
||||
if ($linkNodes.Count -gt 0) {
|
||||
$linkOk = $true
|
||||
foreach ($link in $linkNodes) {
|
||||
$src = $link.SelectSingleNode("s:sourceDataSet", $ns)
|
||||
$dst = $link.SelectSingleNode("s:destinationDataSet", $ns)
|
||||
$srcExpr = $link.SelectSingleNode("s:sourceExpression", $ns)
|
||||
$dstExpr = $link.SelectSingleNode("s:destinationExpression", $ns)
|
||||
|
||||
if ($src -and $src.InnerText -and -not $dataSetNames.ContainsKey($src.InnerText)) {
|
||||
Report-Error "DataSetLink: sourceDataSet '$($src.InnerText)' not found"
|
||||
$linkOk = $false
|
||||
}
|
||||
if ($dst -and $dst.InnerText -and -not $dataSetNames.ContainsKey($dst.InnerText)) {
|
||||
Report-Error "DataSetLink: destinationDataSet '$($dst.InnerText)' not found"
|
||||
$linkOk = $false
|
||||
}
|
||||
if (-not $srcExpr -or -not $srcExpr.InnerText.Trim()) {
|
||||
Report-Error "DataSetLink: empty sourceExpression"
|
||||
$linkOk = $false
|
||||
}
|
||||
if (-not $dstExpr -or -not $dstExpr.InnerText.Trim()) {
|
||||
Report-Error "DataSetLink: empty destinationExpression"
|
||||
$linkOk = $false
|
||||
}
|
||||
}
|
||||
if ($linkOk) {
|
||||
Report-OK "$($linkNodes.Count) dataSetLink(s): references valid"
|
||||
}
|
||||
}
|
||||
|
||||
if ($script:stopped) { & $finalize; exit 1 }
|
||||
|
||||
# --- 9. CalculatedField checks ---
|
||||
|
||||
if ($calcFieldNodes.Count -gt 0) {
|
||||
$cfOk = $true
|
||||
$cfSeen = @{}
|
||||
foreach ($cf in $calcFieldNodes) {
|
||||
$dp = $cf.SelectSingleNode("s:dataPath", $ns)
|
||||
$expr = $cf.SelectSingleNode("s:expression", $ns)
|
||||
|
||||
if (-not $dp -or -not $dp.InnerText) {
|
||||
Report-Error "CalculatedField has empty dataPath"
|
||||
$cfOk = $false
|
||||
continue
|
||||
}
|
||||
|
||||
$path = $dp.InnerText
|
||||
if ($cfSeen.ContainsKey($path)) {
|
||||
Report-Error "Duplicate calculatedField dataPath: $path"
|
||||
$cfOk = $false
|
||||
} else {
|
||||
$cfSeen[$path] = $true
|
||||
}
|
||||
|
||||
if (-not $expr -or -not $expr.InnerText.Trim()) {
|
||||
Report-Error "CalculatedField '$path' has empty expression"
|
||||
$cfOk = $false
|
||||
}
|
||||
|
||||
# Warn if collides with a dataset field
|
||||
if ($allFieldPaths.ContainsKey($path)) {
|
||||
Report-Warn "CalculatedField '$path' shadows dataSet field in '$($allFieldPaths[$path])'"
|
||||
}
|
||||
}
|
||||
|
||||
if ($cfOk) {
|
||||
Report-OK "$($calcFieldNodes.Count) calculatedField(s): dataPath and expression valid"
|
||||
}
|
||||
}
|
||||
|
||||
if ($script:stopped) { & $finalize; exit 1 }
|
||||
|
||||
# --- 10. TotalField checks ---
|
||||
|
||||
if ($totalFieldNodes.Count -gt 0) {
|
||||
$tfOk = $true
|
||||
foreach ($tf in $totalFieldNodes) {
|
||||
$dp = $tf.SelectSingleNode("s:dataPath", $ns)
|
||||
$expr = $tf.SelectSingleNode("s:expression", $ns)
|
||||
|
||||
if (-not $dp -or -not $dp.InnerText) {
|
||||
Report-Error "TotalField has empty dataPath"
|
||||
$tfOk = $false
|
||||
continue
|
||||
}
|
||||
|
||||
if (-not $expr -or -not $expr.InnerText.Trim()) {
|
||||
Report-Error "TotalField '$($dp.InnerText)' has empty expression"
|
||||
$tfOk = $false
|
||||
}
|
||||
}
|
||||
|
||||
if ($tfOk) {
|
||||
Report-OK "$($totalFieldNodes.Count) totalField(s): dataPath and expression present"
|
||||
}
|
||||
}
|
||||
|
||||
if ($script:stopped) { & $finalize; exit 1 }
|
||||
|
||||
# --- 11. Parameter checks ---
|
||||
|
||||
if ($paramNodes.Count -gt 0) {
|
||||
$paramOk = $true
|
||||
$paramSeen = @{}
|
||||
foreach ($p in $paramNodes) {
|
||||
$nameNode = $p.SelectSingleNode("s:name", $ns)
|
||||
if (-not $nameNode -or -not $nameNode.InnerText) {
|
||||
Report-Error "Parameter has empty name"
|
||||
$paramOk = $false
|
||||
continue
|
||||
}
|
||||
$pName = $nameNode.InnerText
|
||||
if ($paramSeen.ContainsKey($pName)) {
|
||||
Report-Error "Duplicate parameter name: $pName"
|
||||
$paramOk = $false
|
||||
} else {
|
||||
$paramSeen[$pName] = $true
|
||||
}
|
||||
}
|
||||
if ($paramOk) {
|
||||
Report-OK "$($paramNodes.Count) parameter(s): names unique"
|
||||
}
|
||||
}
|
||||
|
||||
if ($script:stopped) { & $finalize; exit 1 }
|
||||
|
||||
# --- 12. Template checks ---
|
||||
|
||||
if ($templateNodes.Count -gt 0) {
|
||||
$tplOk = $true
|
||||
$tplSeen = @{}
|
||||
foreach ($t in $templateNodes) {
|
||||
$nameNode = $t.SelectSingleNode("s:name", $ns)
|
||||
if (-not $nameNode -or -not $nameNode.InnerText) {
|
||||
Report-Error "Template has empty name"
|
||||
$tplOk = $false
|
||||
continue
|
||||
}
|
||||
$tName = $nameNode.InnerText
|
||||
if ($tplSeen.ContainsKey($tName)) {
|
||||
Report-Error "Duplicate template name: $tName"
|
||||
$tplOk = $false
|
||||
} else {
|
||||
$tplSeen[$tName] = $true
|
||||
}
|
||||
}
|
||||
if ($tplOk) {
|
||||
Report-OK "$($templateNodes.Count) template(s): names unique"
|
||||
}
|
||||
}
|
||||
|
||||
# --- 13. GroupTemplate checks ---
|
||||
|
||||
if ($groupTemplateNodes.Count -gt 0) {
|
||||
$gtOk = $true
|
||||
$validTplTypes = @("Header", "Footer", "Overall", "OverallHeader", "OverallFooter")
|
||||
foreach ($gt in $groupTemplateNodes) {
|
||||
$tplRef = $gt.SelectSingleNode("s:template", $ns)
|
||||
$tplType = $gt.SelectSingleNode("s:templateType", $ns)
|
||||
|
||||
if ($tplRef -and $tplRef.InnerText -and -not $templateNames.ContainsKey($tplRef.InnerText)) {
|
||||
Report-Error "GroupTemplate references unknown template: $($tplRef.InnerText)"
|
||||
$gtOk = $false
|
||||
}
|
||||
if ($tplType -and $validTplTypes -notcontains $tplType.InnerText) {
|
||||
Report-Warn "GroupTemplate has unusual templateType: $($tplType.InnerText)"
|
||||
}
|
||||
}
|
||||
if ($gtOk) {
|
||||
Report-OK "$($groupTemplateNodes.Count) groupTemplate(s): references valid"
|
||||
}
|
||||
}
|
||||
|
||||
if ($script:stopped) { & $finalize; exit 1 }
|
||||
|
||||
# --- 14. Settings helper functions ---
|
||||
|
||||
$validComparisonTypes = @(
|
||||
"Equal","NotEqual","Greater","GreaterOrEqual","Less","LessOrEqual",
|
||||
"InList","NotInList","InHierarchy","InListByHierarchy",
|
||||
"Contains","NotContains","BeginsWith","NotBeginsWith",
|
||||
"Filled","NotFilled"
|
||||
)
|
||||
|
||||
$validStructureTypes = @(
|
||||
"dcsset:StructureItemGroup",
|
||||
"dcsset:StructureItemTable",
|
||||
"dcsset:StructureItemChart",
|
||||
"dcsset:StructureItemNestedObject"
|
||||
)
|
||||
|
||||
function Check-FilterItems {
|
||||
param($parentNode, [string]$variantName)
|
||||
|
||||
$filterItems = $parentNode.SelectNodes("dcsset:filter/dcsset:item", $ns)
|
||||
foreach ($fi in $filterItems) {
|
||||
if ($script:stopped) { return }
|
||||
$xsiType = $fi.GetAttribute("type", "http://www.w3.org/2001/XMLSchema-instance")
|
||||
if ($xsiType -eq "dcsset:FilterItemComparison") {
|
||||
$compType = $fi.SelectSingleNode("dcsset:comparisonType", $ns)
|
||||
if ($compType -and $validComparisonTypes -notcontains $compType.InnerText) {
|
||||
Report-Error "Variant '$variantName' filter: invalid comparisonType '$($compType.InnerText)'"
|
||||
}
|
||||
} elseif ($xsiType -eq "dcsset:FilterItemGroup") {
|
||||
$groupType = $fi.SelectSingleNode("dcsset:groupType", $ns)
|
||||
if ($groupType) {
|
||||
$validGroupTypes = @("AndGroup","OrGroup","NotGroup")
|
||||
if ($validGroupTypes -notcontains $groupType.InnerText) {
|
||||
Report-Warn "Variant '$variantName' filter group: unusual groupType '$($groupType.InnerText)'"
|
||||
}
|
||||
}
|
||||
# Recurse into nested items
|
||||
$nestedItems = $fi.SelectNodes("dcsset:item", $ns)
|
||||
foreach ($ni in $nestedItems) {
|
||||
$niType = $ni.GetAttribute("type", "http://www.w3.org/2001/XMLSchema-instance")
|
||||
if ($niType -eq "dcsset:FilterItemComparison") {
|
||||
$compType = $ni.SelectSingleNode("dcsset:comparisonType", $ns)
|
||||
if ($compType -and $validComparisonTypes -notcontains $compType.InnerText) {
|
||||
Report-Error "Variant '$variantName' filter: invalid comparisonType '$($compType.InnerText)'"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Check-StructureItem {
|
||||
param($itemNode, [string]$variantName)
|
||||
|
||||
if ($script:stopped) { return }
|
||||
|
||||
$xsiType = $itemNode.GetAttribute("type", "http://www.w3.org/2001/XMLSchema-instance")
|
||||
if (-not $xsiType) {
|
||||
Report-Error "Variant '$variantName': structure item missing xsi:type"
|
||||
return
|
||||
}
|
||||
if ($validStructureTypes -notcontains $xsiType) {
|
||||
Report-Warn "Variant '$variantName': unusual structure item type '$xsiType'"
|
||||
}
|
||||
|
||||
# Recurse into nested items (groups can contain groups)
|
||||
$nestedItems = $itemNode.SelectNodes("dcsset:item", $ns)
|
||||
foreach ($ni in $nestedItems) {
|
||||
Check-StructureItem -itemNode $ni -variantName $variantName
|
||||
}
|
||||
|
||||
# Check column/row in tables
|
||||
if ($xsiType -eq "dcsset:StructureItemTable") {
|
||||
$columns = $itemNode.SelectNodes("dcsset:column", $ns)
|
||||
$rows = $itemNode.SelectNodes("dcsset:row", $ns)
|
||||
if ($columns.Count -eq 0) {
|
||||
Report-Warn "Variant '$variantName': table has no columns"
|
||||
}
|
||||
if ($rows.Count -eq 0) {
|
||||
Report-Warn "Variant '$variantName': table has no rows"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Check-Settings {
|
||||
param($settingsNode, [string]$variantName)
|
||||
|
||||
if ($script:stopped) { return }
|
||||
|
||||
# Selection
|
||||
$selItems = $settingsNode.SelectNodes("dcsset:selection/dcsset:item", $ns)
|
||||
foreach ($si in $selItems) {
|
||||
$xsiType = $si.GetAttribute("type", "http://www.w3.org/2001/XMLSchema-instance")
|
||||
if ($xsiType -eq "dcsset:SelectedItemField") {
|
||||
$field = $si.SelectSingleNode("dcsset:field", $ns)
|
||||
if ($field -and $field.InnerText -and $field.InnerText -ne "SystemFields.Number") {
|
||||
$basePath = ($field.InnerText -split '\.')[0]
|
||||
if (-not $knownFields.ContainsKey($field.InnerText) -and -not $knownFields.ContainsKey($basePath)) {
|
||||
# Soft check — autoFillFields may add fields not listed explicitly
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Filter
|
||||
Check-FilterItems -parentNode $settingsNode -variantName $variantName
|
||||
|
||||
# Order
|
||||
$orderItems = $settingsNode.SelectNodes("dcsset:order/dcsset:item", $ns)
|
||||
foreach ($oi in $orderItems) {
|
||||
$xsiType = $oi.GetAttribute("type", "http://www.w3.org/2001/XMLSchema-instance")
|
||||
if ($xsiType -eq "dcsset:OrderItemField") {
|
||||
$orderType = $oi.SelectSingleNode("dcsset:orderType", $ns)
|
||||
if ($orderType -and $orderType.InnerText -ne "Asc" -and $orderType.InnerText -ne "Desc") {
|
||||
Report-Warn "Variant '$variantName' order: invalid orderType '$($orderType.InnerText)'"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Structure items
|
||||
$structItems = $settingsNode.SelectNodes("dcsset:item", $ns)
|
||||
foreach ($si in $structItems) {
|
||||
Check-StructureItem -itemNode $si -variantName $variantName
|
||||
}
|
||||
}
|
||||
|
||||
# --- 15. SettingsVariant checks ---
|
||||
|
||||
if ($variantNodes.Count -eq 0) {
|
||||
Report-Warn "No settingsVariant elements found"
|
||||
} else {
|
||||
$vOk = $true
|
||||
$vIdx = 0
|
||||
foreach ($v in $variantNodes) {
|
||||
$vIdx++
|
||||
$vName = $v.SelectSingleNode("dcsset:name", $ns)
|
||||
if (-not $vName -or -not $vName.InnerText) {
|
||||
Report-Error "SettingsVariant #$vIdx has empty name"
|
||||
$vOk = $false
|
||||
}
|
||||
|
||||
$settings = $v.SelectSingleNode("dcsset:settings", $ns)
|
||||
if (-not $settings) {
|
||||
Report-Error "SettingsVariant '$($vName.InnerText)' has no settings element"
|
||||
$vOk = $false
|
||||
continue
|
||||
}
|
||||
|
||||
# Check settings internals
|
||||
Check-Settings -settingsNode $settings -variantName "$($vName.InnerText)"
|
||||
}
|
||||
|
||||
if ($vOk) {
|
||||
Report-OK "$($variantNodes.Count) settingsVariant(s) found"
|
||||
}
|
||||
}
|
||||
|
||||
# --- Final output ---
|
||||
|
||||
& $finalize
|
||||
|
||||
if ($script:errors -gt 0) {
|
||||
exit 1
|
||||
}
|
||||
exit 0
|
||||
@@ -0,0 +1,639 @@
|
||||
# JSON DSL для схемы компоновки данных (СКД)
|
||||
|
||||
Компактный JSON-формат для описания `DataCompositionSchema` (Template.xml).
|
||||
Компилируется навыком `/skd-compile` в XML, валидируется `/skd-validate`.
|
||||
|
||||
---
|
||||
|
||||
## 1. Корневая структура
|
||||
|
||||
```json
|
||||
{
|
||||
"dataSources": [...],
|
||||
"dataSets": [...],
|
||||
"dataSetLinks": [...],
|
||||
"calculatedFields": [...],
|
||||
"totalFields": [...],
|
||||
"parameters": [...],
|
||||
"templates": [...],
|
||||
"groupTemplates": [...],
|
||||
"settingsVariants": [...]
|
||||
}
|
||||
```
|
||||
|
||||
**Умолчания:**
|
||||
- `dataSources` опущен → авто-создаётся `{ "name": "ИсточникДанных1", "type": "Local" }`
|
||||
- `source` в наборе опущен → первый dataSource
|
||||
- `name` набора опущен → "НаборДанных1", "НаборДанных2"...
|
||||
- `settingsVariants` опущен → один вариант "Основной" с детальной группировкой и `selection: ["Auto"]`
|
||||
|
||||
---
|
||||
|
||||
## 2. Источники данных (dataSources)
|
||||
|
||||
```json
|
||||
"dataSources": [
|
||||
{ "name": "ИсточникДанных1", "type": "Local" }
|
||||
]
|
||||
```
|
||||
|
||||
| Поле | Обязат. | Умолчание | XML-маппинг |
|
||||
|------|---------|-----------|-------------|
|
||||
| `name` | да | — | `<name>` |
|
||||
| `type` | нет | `"Local"` | `<dataSourceType>` |
|
||||
|
||||
Значения `type`: `"Local"`, `"External"`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Наборы данных (dataSets)
|
||||
|
||||
Тип определяется по ключу-дискриминатору:
|
||||
|
||||
| Ключ | Тип | xsi:type |
|
||||
|------|-----|----------|
|
||||
| `query` | Запрос | `DataSetQuery` |
|
||||
| `objectName` | Объект | `DataSetObject` |
|
||||
| `items` | Объединение | `DataSetUnion` |
|
||||
|
||||
### DataSetQuery (самый частый)
|
||||
|
||||
```json
|
||||
{ "name": "Продажи", "query": "ВЫБРАТЬ ...", "fields": [...], "autoFillFields": false }
|
||||
```
|
||||
|
||||
### DataSetObject
|
||||
|
||||
```json
|
||||
{ "name": "ТаблицаПроверки", "objectName": "ТаблицаПроверки", "fields": [...] }
|
||||
```
|
||||
|
||||
### DataSetUnion
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Объединение",
|
||||
"items": [
|
||||
{ "name": "Набор1", "query": "...", "fields": [...] },
|
||||
{ "name": "Набор2", "query": "...", "fields": [...] }
|
||||
],
|
||||
"fields": [...]
|
||||
}
|
||||
```
|
||||
|
||||
| Поле | Обязат. | Описание |
|
||||
|------|---------|----------|
|
||||
| `name` | нет | Авто: "НаборДанных1"... |
|
||||
| `source` | нет | Имя dataSource (авто: первый) |
|
||||
| `query` | да* | Текст запроса (DataSetQuery) |
|
||||
| `objectName` | да* | Имя объекта (DataSetObject) |
|
||||
| `items` | да* | Вложенные наборы (DataSetUnion) |
|
||||
| `fields` | нет | Массив полей |
|
||||
| `autoFillFields` | нет | `false` — отключить автозаполнение (по умолчанию не выводится = true) |
|
||||
|
||||
---
|
||||
|
||||
## 4. Поля — shorthand и объектная форма
|
||||
|
||||
### Shorthand-строка
|
||||
|
||||
```
|
||||
"<dataPath>[: <type>] [@role...] [#restrict...]"
|
||||
```
|
||||
|
||||
Примеры:
|
||||
|
||||
```json
|
||||
"fields": [
|
||||
"Наименование",
|
||||
"Количество: decimal(15,2)",
|
||||
"Организация: CatalogRef.Организации @dimension",
|
||||
"Служебное: string #noFilter #noOrder",
|
||||
"Счёт: CatalogRef.Хозрасчетный @account",
|
||||
"Сумма: decimal(15,2) @balance"
|
||||
]
|
||||
```
|
||||
|
||||
### Объектная форма
|
||||
|
||||
```json
|
||||
{
|
||||
"dataPath": "Сумма",
|
||||
"field": "Сумма",
|
||||
"title": "Сумма продаж",
|
||||
"type": "decimal(15,2)",
|
||||
"role": { "dimension": true },
|
||||
"restrict": ["noFilter", "noGroup"],
|
||||
"attrRestrict": ["noFilter"],
|
||||
"appearance": { "Формат": "ЧДЦ=2" },
|
||||
"presentationExpression": "Формат(Сумма, \"ЧДЦ=2\")"
|
||||
}
|
||||
```
|
||||
|
||||
### Парсинг shorthand
|
||||
|
||||
1. Разделить по пробелам; найти `@`-роли и `#`-ограничения
|
||||
2. Остаток до первого `:` — `dataPath` (и `field` по умолчанию)
|
||||
3. После `:` до `@`/`#` — тип
|
||||
|
||||
### Типы
|
||||
|
||||
| DSL | XML v8:Type | Квалификатор |
|
||||
|-----|-------------|--------------|
|
||||
| `string` | `xs:string` | Length=0, AllowedLength=Variable |
|
||||
| `string(N)` | `xs:string` | Length=N, AllowedLength=Variable |
|
||||
| `decimal(D,F)` | `xs:decimal` | Digits=D, FractionDigits=F, AllowedSign=Any |
|
||||
| `decimal(D,F,nonneg)` | `xs:decimal` | Digits=D, FractionDigits=F, AllowedSign=Nonnegative |
|
||||
| `boolean` | `xs:boolean` | — |
|
||||
| `date` | `xs:dateTime` | DateFractions=Date |
|
||||
| `dateTime` | `xs:dateTime` | DateFractions=DateTime |
|
||||
| `CatalogRef.XXX` | `cfg:CatalogRef.XXX` | — |
|
||||
| `DocumentRef.XXX` | `cfg:DocumentRef.XXX` | — |
|
||||
| `EnumRef.XXX` | `cfg:EnumRef.XXX` | — |
|
||||
| `ChartOfAccountsRef.XXX` | `cfg:ChartOfAccountsRef.XXX` | — |
|
||||
| `StandardPeriod` | `v8:StandardPeriod` | — |
|
||||
|
||||
### Роли
|
||||
|
||||
| DSL shorthand | Объектная форма | XML |
|
||||
|---------------|----------------|-----|
|
||||
| `@dimension` | `"role": "dimension"` или `{"dimension": true}` | `<dcscom:dimension>true</dcscom:dimension>` |
|
||||
| `@account` | `"role": "account"` или `{"account": true}` | `<dcscom:account>true</dcscom:account>` |
|
||||
| `@balance` | `"role": "balance"` или `{"balance": true}` | `<dcscom:balance>true</dcscom:balance>` |
|
||||
| `@period` | `"role": "period"` или `{"period": true}` | `<dcscom:period>true</dcscom:period>` |
|
||||
|
||||
Объектная форма с доп. полями:
|
||||
```json
|
||||
"role": {
|
||||
"account": true,
|
||||
"accountTypeExpression": "Счёт.ВидСчёта",
|
||||
"balanceGroup": "/Остатки"
|
||||
}
|
||||
```
|
||||
|
||||
### Ограничения
|
||||
|
||||
| DSL shorthand | Объектная форма | XML useRestriction |
|
||||
|---------------|----------------|-----|
|
||||
| `#noField` | `"noField"` | `<field>true</field>` |
|
||||
| `#noFilter` / `#noCondition` | `"noFilter"` | `<condition>true</condition>` |
|
||||
| `#noGroup` | `"noGroup"` | `<group>true</group>` |
|
||||
| `#noOrder` | `"noOrder"` | `<order>true</order>` |
|
||||
|
||||
### Оформление (appearance)
|
||||
|
||||
```json
|
||||
"appearance": {
|
||||
"Формат": "ЧДЦ=2",
|
||||
"ГоризонтальноеПоложение": "Center"
|
||||
}
|
||||
```
|
||||
|
||||
Маппинг на XML:
|
||||
```xml
|
||||
<appearance>
|
||||
<dcscor:item xsi:type="dcsset:SettingsParameterValue">
|
||||
<dcscor:parameter>Формат</dcscor:parameter>
|
||||
<dcscor:value xsi:type="xs:string">ЧДЦ=2</dcscor:value>
|
||||
</dcscor:item>
|
||||
</appearance>
|
||||
```
|
||||
|
||||
Значения `ГоризонтальноеПоложение` → `xsi:type="v8ui:HorizontalAlign"`.
|
||||
|
||||
---
|
||||
|
||||
## 5. Итоговые поля (totalFields)
|
||||
|
||||
### Shorthand
|
||||
|
||||
```
|
||||
"<dataPath>: <Функция>"
|
||||
"<dataPath>: <Функция>(<выражение>)"
|
||||
```
|
||||
|
||||
Примеры:
|
||||
|
||||
```json
|
||||
"totalFields": [
|
||||
"Количество: Сумма",
|
||||
"Цена: Максимум",
|
||||
"Стоимость: Сумма(Кол * Цена)"
|
||||
]
|
||||
```
|
||||
|
||||
**Парсинг:** `"A: Func"` → `dataPath=A`, `expression=Func(A)`. `"A: Func(expr)"` → `dataPath=A`, `expression=Func(expr)`.
|
||||
|
||||
Функции (русские): `Сумма`, `Количество`, `Максимум`, `Минимум`, `Среднее`.
|
||||
|
||||
### Объектная форма
|
||||
|
||||
```json
|
||||
{ "dataPath": "X", "expression": "Максимум(X)", "group": "Группа1" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Параметры (parameters)
|
||||
|
||||
### Shorthand
|
||||
|
||||
```
|
||||
"<name>: <type> [= <default>]"
|
||||
```
|
||||
|
||||
Примеры:
|
||||
|
||||
```json
|
||||
"parameters": [
|
||||
"Период: StandardPeriod = LastMonth",
|
||||
"Организация: CatalogRef.Организации",
|
||||
"ДатаОтчета: date"
|
||||
]
|
||||
```
|
||||
|
||||
**Парсинг:** `"A: T = V"` → `name=A`, `type=T`, `value=V`. Значение `LastMonth` и другие варианты периодов → `v8:StandardPeriod` с `v8:variant`.
|
||||
|
||||
### Объектная форма
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "ДатаНач",
|
||||
"title": "Дата начала",
|
||||
"type": "date",
|
||||
"value": "0001-01-01T00:00:00",
|
||||
"expression": "&Период.ДатаНачала",
|
||||
"availableAsField": false,
|
||||
"useRestriction": true,
|
||||
"use": "Always"
|
||||
}
|
||||
```
|
||||
|
||||
| Поле | Описание |
|
||||
|------|----------|
|
||||
| `name` | Имя параметра |
|
||||
| `title` | Заголовок (умолч. = name) |
|
||||
| `type` | Тип (см. таблицу типов) |
|
||||
| `value` | Значение по умолчанию |
|
||||
| `expression` | Выражение для вычисления |
|
||||
| `availableAsField` | `false` — скрыть из полей |
|
||||
| `useRestriction` | `true` — скрыть от пользователя |
|
||||
| `use` | `"Always"`, `"Auto"` |
|
||||
|
||||
### Значения параметров по типу
|
||||
|
||||
| Тип | value | XML |
|
||||
|-----|-------|-----|
|
||||
| `StandardPeriod` | `"LastMonth"`, `"ThisYear"` и др. | `<v8:variant xsi:type="v8:StandardPeriodVariant">LastMonth</v8:variant>` |
|
||||
| `date` | `"0001-01-01T00:00:00"` | `xsi:type="xs:dateTime"` |
|
||||
| `string` | `"текст"` | `xsi:type="xs:string"` |
|
||||
| `boolean` | `true`/`false` | `xsi:type="xs:boolean"` |
|
||||
|
||||
Стандартные варианты периодов: `Custom`, `Today`, `ThisWeek`, `ThisMonth`, `ThisQuarter`, `ThisYear`, `LastMonth`, `LastQuarter`, `LastYear`.
|
||||
|
||||
---
|
||||
|
||||
## 7. Вычисляемые поля (calculatedFields)
|
||||
|
||||
### Shorthand
|
||||
|
||||
```
|
||||
"<dataPath> = <expression>"
|
||||
```
|
||||
|
||||
```json
|
||||
"calculatedFields": [
|
||||
"УИД = Строка(Ссылка.УникальныйИдентификатор())",
|
||||
"Итого = Количество * Цена"
|
||||
]
|
||||
```
|
||||
|
||||
### Объектная форма
|
||||
|
||||
```json
|
||||
{
|
||||
"dataPath": "Итого",
|
||||
"expression": "Количество * Цена",
|
||||
"title": "Итого",
|
||||
"type": "decimal(15,2)",
|
||||
"restrict": ["noGroup"],
|
||||
"appearance": { "Формат": "ЧДЦ=2" }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Связи наборов (dataSetLinks)
|
||||
|
||||
Только объектная форма:
|
||||
|
||||
```json
|
||||
"dataSetLinks": [
|
||||
{
|
||||
"source": "Периоды",
|
||||
"dest": "Данные",
|
||||
"sourceExpr": "Месяц",
|
||||
"destExpr": "Месяц",
|
||||
"parameter": "НачалоМесяца"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
| Поле | XML |
|
||||
|------|-----|
|
||||
| `source` | `<sourceDataSet>` |
|
||||
| `dest` | `<destinationDataSet>` |
|
||||
| `sourceExpr` | `<sourceExpression>` |
|
||||
| `destExpr` | `<destinationExpression>` |
|
||||
| `parameter` | `<parameter>` (опц.) |
|
||||
|
||||
---
|
||||
|
||||
## 9. Варианты настроек (settingsVariants)
|
||||
|
||||
```json
|
||||
"settingsVariants": [{
|
||||
"name": "Основной",
|
||||
"presentation": "Основной вариант",
|
||||
"settings": {
|
||||
"selection": [...],
|
||||
"filter": [...],
|
||||
"order": [...],
|
||||
"outputParameters": {...},
|
||||
"dataParameters": [...],
|
||||
"structure": [...]
|
||||
}
|
||||
}]
|
||||
```
|
||||
|
||||
### selection
|
||||
|
||||
```json
|
||||
"selection": [
|
||||
"Наименование",
|
||||
{ "field": "Количество", "title": "Кол-во" },
|
||||
"Auto"
|
||||
]
|
||||
```
|
||||
|
||||
- Строка → `SelectedItemField`
|
||||
- `"Auto"` → `SelectedItemAuto`
|
||||
- Объект с `field`/`title` → `SelectedItemField` с `lwsTitle`
|
||||
|
||||
### filter
|
||||
|
||||
```json
|
||||
"filter": [
|
||||
{ "field": "Организация", "op": "=", "use": false, "userSettingID": "auto" },
|
||||
{ "field": "Дата", "op": ">=", "value": "0001-01-01T00:00:00", "valueType": "xs:dateTime" },
|
||||
{ "group": "Or", "items": [
|
||||
{ "field": "Статус", "op": "=", "value": true, "valueType": "xs:boolean" },
|
||||
{ "field": "Пометка", "op": "filled" }
|
||||
]}
|
||||
]
|
||||
```
|
||||
|
||||
| Поле | Описание |
|
||||
|------|----------|
|
||||
| `field` | Имя поля |
|
||||
| `op` | Оператор (см. таблицу) |
|
||||
| `value` | Правая часть (опц.) |
|
||||
| `valueType` | xsi:type для значения (опц.) |
|
||||
| `use` | Включён (`true` по умолчанию) |
|
||||
| `presentation` | Текст подсказки |
|
||||
| `viewMode` | `"Normal"`, `"QuickAccess"`, `"Inaccessible"` |
|
||||
| `userSettingID` | `"auto"` → генерировать GUID |
|
||||
|
||||
Операторы:
|
||||
|
||||
| DSL | XML comparisonType |
|
||||
|-----|--------------------|
|
||||
| `=` | `Equal` |
|
||||
| `<>` | `NotEqual` |
|
||||
| `>` | `Greater` |
|
||||
| `>=` | `GreaterOrEqual` |
|
||||
| `<` | `Less` |
|
||||
| `<=` | `LessOrEqual` |
|
||||
| `in` | `InList` |
|
||||
| `notIn` | `NotInList` |
|
||||
| `inHierarchy` | `InHierarchy` |
|
||||
| `contains` | `Contains` |
|
||||
| `notContains` | `NotContains` |
|
||||
| `beginsWith` | `BeginsWith` |
|
||||
| `filled` | `Filled` |
|
||||
| `notFilled` | `NotFilled` |
|
||||
|
||||
Группа условий: `{ "group": "And"|"Or"|"Not", "items": [...] }` → `FilterItemGroup` с `groupType`.
|
||||
|
||||
### order
|
||||
|
||||
```json
|
||||
"order": ["Количество desc", "Наименование", "Auto"]
|
||||
```
|
||||
|
||||
- `"Field"` → `OrderItemField`, `orderType=Asc`
|
||||
- `"Field desc"` → `OrderItemField`, `orderType=Desc`
|
||||
- `"Field asc"` → `OrderItemField`, `orderType=Asc`
|
||||
- `"Auto"` → `OrderItemAuto`
|
||||
|
||||
### outputParameters
|
||||
|
||||
```json
|
||||
"outputParameters": {
|
||||
"Заголовок": "Мой отчёт",
|
||||
"ВыводитьЗаголовок": "Output",
|
||||
"МакетОформления": "ОформлениеОтчетовЧерноБелый"
|
||||
}
|
||||
```
|
||||
|
||||
Ключ → `dcscor:parameter`, значение → `dcscor:value`.
|
||||
|
||||
Типы значений определяются автоматически:
|
||||
- `"Заголовок"` → `v8:LocalStringType`
|
||||
- `"ВыводитьЗаголовок"`, `"ВыводитьПараметрыДанных"`, `"ВыводитьОтбор"` → `dcsset:DataCompositionTextOutputType`
|
||||
- `"РасположениеПолейГруппировки"` → `dcsset:DataCompositionGroupFieldsPlacement`
|
||||
- `"РасположениеРеквизитов"` → `dcsset:DataCompositionAttributesPlacement`
|
||||
- `"ГоризонтальноеРасположениеОбщихИтогов"`, `"ВертикальноеРасположениеОбщихИтогов"` → `dcscor:DataCompositionTotalPlacement`
|
||||
- Прочие → `xs:string`
|
||||
|
||||
### dataParameters
|
||||
|
||||
```json
|
||||
"dataParameters": [
|
||||
{ "parameter": "Период", "value": { "variant": "LastMonth" }, "userSettingID": "auto" },
|
||||
{ "parameter": "Организация", "use": false, "viewMode": "Normal", "userSettingID": "auto" }
|
||||
]
|
||||
```
|
||||
|
||||
### structure
|
||||
|
||||
```json
|
||||
"structure": [
|
||||
{
|
||||
"type": "group",
|
||||
"groupBy": ["Организация"],
|
||||
"selection": ["Auto"],
|
||||
"order": ["Auto"],
|
||||
"children": [
|
||||
{ "type": "group", "selection": ["Auto"], "order": ["Auto"] }
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
#### Группировка (group)
|
||||
|
||||
| Поле | Описание |
|
||||
|------|----------|
|
||||
| `type` | `"group"` |
|
||||
| `name` | Имя группировки (опц.) |
|
||||
| `groupBy` | Массив полей. Пусто/опущено = детальные записи |
|
||||
| `groupType` | `"Items"` (умолч.), `"Hierarchy"`, `"HierarchyOnly"` |
|
||||
| `selection` | Выборка (как в settings) |
|
||||
| `filter` | Отборы (как в settings) |
|
||||
| `order` | Сортировка (как в settings) |
|
||||
| `outputParameters` | Параметры вывода (как в settings) |
|
||||
| `children` | Вложенные элементы структуры |
|
||||
|
||||
Пустой `groupBy` (или `[]`) = детальные записи (без `groupItems` в XML).
|
||||
|
||||
#### Таблица (table)
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "table",
|
||||
"name": "Таблица",
|
||||
"rows": [
|
||||
{ "groupBy": ["Номенклатура"], "selection": ["Auto"], "order": ["Auto"] }
|
||||
],
|
||||
"columns": [
|
||||
{ "groupBy": ["Период"], "selection": ["Auto"], "order": ["Auto"] }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Диаграмма (chart)
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "chart",
|
||||
"points": { "groupBy": ["Организация"], "order": ["Auto"] },
|
||||
"series": { "groupBy": ["Месяц"], "order": ["Auto"] },
|
||||
"selection": ["Сумма"]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Макеты и привязки (templates, groupTemplates)
|
||||
|
||||
Редко используются. Поддерживаются в объектной форме, близкой к XML.
|
||||
|
||||
### templates
|
||||
|
||||
```json
|
||||
"templates": [
|
||||
{
|
||||
"name": "Макет1",
|
||||
"template": "<raw XML dcsat:AreaTemplate>",
|
||||
"parameters": [
|
||||
{ "name": "ТипЦены", "expression": "Представление(ТипЦен)" }
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### groupTemplates
|
||||
|
||||
```json
|
||||
"groupTemplates": [
|
||||
{ "groupField": "ТипЦен", "templateType": "Header", "template": "Макет1" }
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. Полный пример — минимальный
|
||||
|
||||
```json
|
||||
{
|
||||
"dataSets": [
|
||||
{
|
||||
"name": "НаборДанных1",
|
||||
"query": "ВЫБРАТЬ\n\tНоменклатура.Наименование КАК Наименование,\n\tКОЛИЧЕСТВО(1) КАК Количество\nИЗ\n\tСправочник.Номенклатура КАК Номенклатура\nСГРУППИРОВАТЬ ПО\n\tНоменклатура.Наименование",
|
||||
"fields": [
|
||||
{ "dataPath": "Наименование", "title": "Наименование" },
|
||||
"Количество"
|
||||
]
|
||||
}
|
||||
],
|
||||
"totalFields": ["Количество: Сумма"],
|
||||
"settingsVariants": [{
|
||||
"name": "Основной",
|
||||
"presentation": "Основной",
|
||||
"settings": {
|
||||
"selection": ["Наименование", "Количество"],
|
||||
"structure": [
|
||||
{ "type": "group", "order": ["Auto"], "selection": ["Auto"] }
|
||||
]
|
||||
}
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
## 12. Полный пример — средний
|
||||
|
||||
```json
|
||||
{
|
||||
"dataSets": [
|
||||
{
|
||||
"name": "Продажи",
|
||||
"query": "ВЫБРАТЬ\n\tПродажи.Организация,\n\tПродажи.Номенклатура,\n\tПродажи.Количество,\n\tПродажи.Сумма\nИЗ\n\tРегистрНакопления.Продажи КАК Продажи\n{ГДЕ\n\tПродажи.Период >= &ДатаНачала\n\tИ Продажи.Период < &ДатаОкончания}",
|
||||
"fields": [
|
||||
"Организация: CatalogRef.Организации @dimension",
|
||||
"Номенклатура: CatalogRef.Номенклатура @dimension",
|
||||
"Количество: decimal(15,3)",
|
||||
"Сумма: decimal(15,2)"
|
||||
]
|
||||
}
|
||||
],
|
||||
"totalFields": [
|
||||
"Количество: Сумма",
|
||||
"Сумма: Сумма"
|
||||
],
|
||||
"parameters": [
|
||||
"Период: StandardPeriod = LastMonth",
|
||||
{ "name": "ДатаНачала", "type": "date", "expression": "&Период.ДатаНачала", "availableAsField": false },
|
||||
{ "name": "ДатаОкончания", "type": "date", "expression": "&Период.ДатаОкончания", "availableAsField": false }
|
||||
],
|
||||
"settingsVariants": [{
|
||||
"name": "Основной",
|
||||
"presentation": "Продажи по организациям",
|
||||
"settings": {
|
||||
"selection": ["Номенклатура", "Количество", "Сумма", "Auto"],
|
||||
"filter": [
|
||||
{ "field": "Организация", "op": "=", "use": false, "userSettingID": "auto" }
|
||||
],
|
||||
"order": ["Сумма desc", "Auto"],
|
||||
"outputParameters": {
|
||||
"Заголовок": "Анализ продаж",
|
||||
"ВыводитьЗаголовок": "Output"
|
||||
},
|
||||
"dataParameters": [
|
||||
{ "parameter": "Период", "value": { "variant": "LastMonth" }, "userSettingID": "auto" }
|
||||
],
|
||||
"structure": [
|
||||
{
|
||||
"type": "group",
|
||||
"groupBy": ["Организация"],
|
||||
"selection": ["Auto"],
|
||||
"order": ["Auto"],
|
||||
"children": [
|
||||
{ "type": "group", "selection": ["Auto"], "order": ["Auto"] }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}]
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user