mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-06-10 16:14:54 +03:00
Add DSL v2 shorthand improvements to skd-compile
Type synonyms (число/строка/булево/дата/СправочникСсылка, case-insensitive),
@autoDates for auto-generating ДатаНачала/ДатаОкончания from StandardPeriod,
string shorthand for structure ("Организация > details"), filter shorthand
("Организация = _ @off @user"), dataParameters shorthand ("Период = LastMonth
@user"), and default selection/order ["Auto"] on all structure levels.
Compression ratio improved from 3.9x to 5.8x on the medium example.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -62,6 +62,8 @@ powershell.exe -NoProfile -File .claude\skills\skd-compile\scripts\skd-compile.p
|
||||
|
||||
Типы: `string`, `string(N)`, `decimal(D,F)`, `boolean`, `date`, `dateTime`, `CatalogRef.X`, `DocumentRef.X`, `EnumRef.X`, `StandardPeriod`.
|
||||
|
||||
**Синонимы типов** (русские и альтернативные): `число` = decimal, `строка` = string, `булево` = boolean, `дата` = date, `датаВремя` = dateTime, `СтандартныйПериод` = StandardPeriod, `СправочникСсылка.X` = CatalogRef.X, `ДокументСсылка.X` = DocumentRef.X, `int`/`number` = decimal, `bool` = boolean. Регистронезависимые.
|
||||
|
||||
Роли: `@dimension`, `@account`, `@balance`, `@period`.
|
||||
|
||||
Ограничения: `#noField`, `#noFilter`, `#noGroup`, `#noOrder`.
|
||||
@@ -72,26 +74,62 @@ powershell.exe -NoProfile -File .claude\skills\skd-compile\scripts\skd-compile.p
|
||||
"totalFields": ["Количество: Сумма", "Стоимость: Сумма(Кол * Цена)"]
|
||||
```
|
||||
|
||||
### Параметры (shorthand)
|
||||
### Параметры (shorthand + @autoDates)
|
||||
|
||||
```json
|
||||
"parameters": ["Период: StandardPeriod = LastMonth", "Организация: CatalogRef.Организации"]
|
||||
"parameters": [
|
||||
"Период: StandardPeriod = LastMonth @autoDates"
|
||||
]
|
||||
```
|
||||
|
||||
`@autoDates` — автоматически генерирует параметры `ДатаНачала` и `ДатаОкончания` с выражениями `&Период.ДатаНачала` / `&Период.ДатаОкончания` и `availableAsField=false`. Заменяет 5 строк на 1.
|
||||
|
||||
### Фильтры — shorthand
|
||||
|
||||
```json
|
||||
"filter": [
|
||||
"Организация = _ @off @user",
|
||||
"Дата >= 2024-01-01T00:00:00",
|
||||
"Статус filled"
|
||||
]
|
||||
```
|
||||
|
||||
Формат: `"Поле оператор значение @флаги"`. Значение `_` = пустое (placeholder). Флаги: `@off` (use=false), `@user` (userSettingID=auto), `@quickAccess`.
|
||||
|
||||
### Параметры данных — shorthand
|
||||
|
||||
```json
|
||||
"dataParameters": [
|
||||
"Период = LastMonth @user",
|
||||
"Организация @off @user"
|
||||
]
|
||||
```
|
||||
|
||||
Формат: `"Имя [= значение] @флаги"`. Для StandardPeriod варианты (LastMonth, ThisYear и т.д.) распознаются автоматически.
|
||||
|
||||
### Структура — string shorthand
|
||||
|
||||
```json
|
||||
"structure": "Организация > details"
|
||||
"structure": "Организация > Номенклатура > details"
|
||||
```
|
||||
|
||||
`>` разделяет уровни группировки. `details` (или `детали`) = детальные записи. `selection` и `order` по умолчанию `["Auto"]` на каждом уровне.
|
||||
|
||||
Для сложных случаев (таблицы, диаграммы, фильтры на уровне группировки) используется объектная форма.
|
||||
|
||||
### Варианты настроек
|
||||
|
||||
```json
|
||||
"settingsVariants": [{
|
||||
"name": "Основной",
|
||||
"presentation": "Основной",
|
||||
"settings": {
|
||||
"selection": ["Наименование", "Количество", "Auto"],
|
||||
"filter": [{ "field": "Организация", "op": "=", "use": false, "userSettingID": "auto" }],
|
||||
"selection": ["Номенклатура", "Количество", "Auto"],
|
||||
"filter": ["Организация = _ @off @user"],
|
||||
"order": ["Количество desc", "Auto"],
|
||||
"outputParameters": { "Заголовок": "Мой отчёт" },
|
||||
"structure": [{ "type": "group", "groupBy": ["Организация"], "selection": ["Auto"], "order": ["Auto"],
|
||||
"children": [{ "type": "group", "selection": ["Auto"], "order": ["Auto"] }]
|
||||
}]
|
||||
"dataParameters": ["Период = LastMonth @user"],
|
||||
"structure": "Организация > details"
|
||||
}
|
||||
}]
|
||||
```
|
||||
@@ -109,16 +147,25 @@ powershell.exe -NoProfile -File .claude\skills\skd-compile\scripts\skd-compile.p
|
||||
}
|
||||
```
|
||||
|
||||
### С ресурсами и параметрами
|
||||
### С ресурсами, параметрами и @autoDates
|
||||
|
||||
```json
|
||||
{
|
||||
"dataSets": [{
|
||||
"query": "ВЫБРАТЬ Продажи.Номенклатура, Продажи.Количество, Продажи.Сумма ИЗ РегистрНакопления.Продажи КАК Продажи",
|
||||
"fields": ["Номенклатура: CatalogRef.Номенклатура @dimension", "Количество: decimal(15,3)", "Сумма: decimal(15,2)"]
|
||||
"fields": ["Номенклатура: СправочникСсылка.Номенклатура @dimension", "Количество: число(15,3)", "Сумма: число(15,2)"]
|
||||
}],
|
||||
"totalFields": ["Количество: Сумма", "Сумма: Сумма"],
|
||||
"parameters": ["Период: StandardPeriod = LastMonth"]
|
||||
"parameters": ["Период: СтандартныйПериод = LastMonth @autoDates"],
|
||||
"settingsVariants": [{
|
||||
"name": "Основной",
|
||||
"settings": {
|
||||
"selection": ["Номенклатура", "Количество", "Сумма", "Auto"],
|
||||
"filter": ["Организация = _ @off @user"],
|
||||
"dataParameters": ["Период = LastMonth @user"],
|
||||
"structure": "Организация > details"
|
||||
}
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -80,11 +80,71 @@ foreach ($ds in $def.dataSets) {
|
||||
|
||||
# --- 4. Type system ---
|
||||
|
||||
# Type synonyms — normalize Russian/common names to canonical DSL types
|
||||
# Use case-sensitive hashtable to avoid PS 5.1 DuplicateKeyInHashLiteral
|
||||
$script:typeSynonyms = New-Object System.Collections.Hashtable
|
||||
# Russian names (case doesn't matter — we'll also do case-insensitive lookup)
|
||||
$script:typeSynonyms["число"] = "decimal"
|
||||
$script:typeSynonyms["строка"] = "string"
|
||||
$script:typeSynonyms["булево"] = "boolean"
|
||||
$script:typeSynonyms["дата"] = "date"
|
||||
$script:typeSynonyms["датавремя"] = "dateTime"
|
||||
$script:typeSynonyms["стандартныйпериод"] = "StandardPeriod"
|
||||
# English canonical (lowercase for lookup)
|
||||
$script:typeSynonyms["bool"] = "boolean"
|
||||
$script:typeSynonyms["str"] = "string"
|
||||
$script:typeSynonyms["int"] = "decimal"
|
||||
$script:typeSynonyms["integer"] = "decimal"
|
||||
$script:typeSynonyms["number"] = "decimal"
|
||||
$script:typeSynonyms["num"] = "decimal"
|
||||
# Reference synonyms (Russian, lowercase)
|
||||
$script:typeSynonyms["справочникссылка"] = "CatalogRef"
|
||||
$script:typeSynonyms["документссылка"] = "DocumentRef"
|
||||
$script:typeSynonyms["перечислениессылка"] = "EnumRef"
|
||||
$script:typeSynonyms["плансчетовссылка"] = "ChartOfAccountsRef"
|
||||
$script:typeSynonyms["планвидовхарактеристикссылка"] = "ChartOfCharacteristicTypesRef"
|
||||
|
||||
function Resolve-TypeStr {
|
||||
param([string]$typeStr)
|
||||
if (-not $typeStr) { return $typeStr }
|
||||
|
||||
# Check for parameterized types: число(15,2), строка(100), etc.
|
||||
if ($typeStr -match '^([^(]+)\((.+)\)$') {
|
||||
$baseName = $Matches[1].Trim()
|
||||
$params = $Matches[2]
|
||||
|
||||
# Resolve base name (case-insensitive via .ToLower())
|
||||
$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 (case-insensitive)
|
||||
$resolved = $script:typeSynonyms[$typeStr.ToLower()]
|
||||
if ($resolved) { return $resolved }
|
||||
|
||||
return $typeStr
|
||||
}
|
||||
|
||||
function Emit-ValueType {
|
||||
param([string]$typeStr, [string]$indent)
|
||||
|
||||
if (-not $typeStr) { return }
|
||||
|
||||
# Resolve synonyms first
|
||||
$typeStr = Resolve-TypeStr $typeStr
|
||||
|
||||
# boolean
|
||||
if ($typeStr -eq "boolean") {
|
||||
X "$indent<v8:Type>xs:boolean</v8:Type>"
|
||||
@@ -178,7 +238,7 @@ function Parse-FieldShorthand {
|
||||
if ($s.Contains(':')) {
|
||||
$parts = $s -split ':', 2
|
||||
$result.dataPath = $parts[0].Trim()
|
||||
$result.type = $parts[1].Trim()
|
||||
$result.type = Resolve-TypeStr ($parts[1].Trim())
|
||||
} else {
|
||||
$result.dataPath = $s
|
||||
}
|
||||
@@ -211,12 +271,18 @@ function Parse-TotalShorthand {
|
||||
function Parse-ParamShorthand {
|
||||
param([string]$s)
|
||||
|
||||
$result = @{ name = ""; type = ""; value = $null }
|
||||
$result = @{ name = ""; type = ""; value = $null; autoDates = $false }
|
||||
|
||||
# Extract @autoDates flag
|
||||
if ($s -match '@autoDates') {
|
||||
$result.autoDates = $true
|
||||
$s = $s -replace '\s*@autoDates', ''
|
||||
}
|
||||
|
||||
# Split "Name: Type = Value"
|
||||
if ($s -match '^([^:]+):\s*(\S+)(\s*=\s*(.+))?$') {
|
||||
$result.name = $Matches[1].Trim()
|
||||
$result.type = $Matches[2].Trim()
|
||||
$result.type = Resolve-TypeStr ($Matches[2].Trim())
|
||||
if ($Matches[4]) {
|
||||
$result.value = $Matches[4].Trim()
|
||||
}
|
||||
@@ -243,6 +309,128 @@ function Parse-CalcShorthand {
|
||||
return @{ dataPath = $s.Trim(); expression = "" }
|
||||
}
|
||||
|
||||
# --- 8b. DataParameter shorthand parser ---
|
||||
# Formats: "Период = LastMonth @user", "Организация @off @user", "Период @user"
|
||||
function Parse-DataParamShorthand {
|
||||
param([string]$s)
|
||||
|
||||
$result = @{ parameter = ""; value = $null; use = $true; userSettingID = $null; viewMode = $null }
|
||||
|
||||
# Extract @flags
|
||||
if ($s -match '@user') {
|
||||
$result.userSettingID = "auto"
|
||||
$s = $s -replace '\s*@user', ''
|
||||
}
|
||||
if ($s -match '@off') {
|
||||
$result.use = $false
|
||||
$s = $s -replace '\s*@off', ''
|
||||
}
|
||||
if ($s -match '@quickAccess') {
|
||||
$result.viewMode = "QuickAccess"
|
||||
$s = $s -replace '\s*@quickAccess', ''
|
||||
}
|
||||
if ($s -match '@normal') {
|
||||
$result.viewMode = "Normal"
|
||||
$s = $s -replace '\s*@normal', ''
|
||||
}
|
||||
|
||||
$s = $s.Trim()
|
||||
|
||||
# Split "Name = Value"
|
||||
if ($s -match '^([^=]+)=\s*(.+)$') {
|
||||
$result.parameter = $Matches[1].Trim()
|
||||
$valStr = $Matches[2].Trim()
|
||||
|
||||
# Detect StandardPeriod variants
|
||||
$periodVariants = @("Custom","Today","ThisWeek","ThisTenDays","ThisMonth","ThisQuarter","ThisHalfYear","ThisYear","FromBeginningOfThisWeek","FromBeginningOfThisTenDays","FromBeginningOfThisMonth","FromBeginningOfThisQuarter","FromBeginningOfThisHalfYear","FromBeginningOfThisYear","LastWeek","LastTenDays","LastMonth","LastQuarter","LastHalfYear","LastYear","NextDay","NextWeek","NextTenDays","NextMonth","NextQuarter","NextHalfYear","NextYear","TillEndOfThisWeek","TillEndOfThisTenDays","TillEndOfThisMonth","TillEndOfThisQuarter","TillEndOfThisHalfYear","TillEndOfThisYear")
|
||||
if ($periodVariants -contains $valStr) {
|
||||
$result.value = @{ variant = $valStr }
|
||||
} elseif ($valStr -match '^\d{4}-\d{2}-\d{2}T') {
|
||||
$result.value = $valStr
|
||||
} elseif ($valStr -eq "true" -or $valStr -eq "false") {
|
||||
$result.value = [bool]($valStr -eq "true")
|
||||
} else {
|
||||
$result.value = $valStr
|
||||
}
|
||||
} else {
|
||||
$result.parameter = $s
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
|
||||
# --- 8c. Filter item shorthand parser ---
|
||||
# Formats: "Организация = _ @off @user", "Дата >= 2024-01-01T00:00:00", "Статус filled"
|
||||
function Parse-FilterShorthand {
|
||||
param([string]$s)
|
||||
|
||||
$result = @{ field = ""; op = "Equal"; value = $null; use = $true; userSettingID = $null; viewMode = $null; presentation = $null }
|
||||
|
||||
# Extract @flags
|
||||
if ($s -match '@user') {
|
||||
$result.userSettingID = "auto"
|
||||
$s = $s -replace '\s*@user', ''
|
||||
}
|
||||
if ($s -match '@off') {
|
||||
$result.use = $false
|
||||
$s = $s -replace '\s*@off', ''
|
||||
}
|
||||
if ($s -match '@quickAccess') {
|
||||
$result.viewMode = "QuickAccess"
|
||||
$s = $s -replace '\s*@quickAccess', ''
|
||||
}
|
||||
|
||||
$s = $s.Trim()
|
||||
|
||||
# Try to match: Field op Value, or Field op (no value for filled/notFilled)
|
||||
# Operators sorted longest first to match >= before >
|
||||
$opPatterns = @('<>', '>=', '<=', '=', '>', '<',
|
||||
'notIn\b', 'in\b', 'inHierarchy\b', 'inListByHierarchy\b',
|
||||
'notContains\b', 'contains\b', 'notBeginsWith\b', 'beginsWith\b',
|
||||
'notFilled\b', 'filled\b')
|
||||
$opJoined = $opPatterns -join '|'
|
||||
|
||||
if ($s -match "^(.+?)\s+($opJoined)\s*(.*)?$") {
|
||||
$result.field = $Matches[1].Trim()
|
||||
$opRaw = $Matches[2].Trim()
|
||||
$valPart = if ($Matches[3]) { $Matches[3].Trim() } else { "" }
|
||||
|
||||
# Map op
|
||||
$opMap = @{
|
||||
"=" = "Equal"; "<>" = "NotEqual"; ">" = "Greater"; ">=" = "GreaterOrEqual"
|
||||
"<" = "Less"; "<=" = "LessOrEqual"; "in" = "InList"; "notIn" = "NotInList"
|
||||
"inHierarchy" = "InHierarchy"; "inListByHierarchy" = "InListByHierarchy"
|
||||
"contains" = "Contains"; "notContains" = "NotContains"
|
||||
"beginsWith" = "BeginsWith"; "notBeginsWith" = "NotBeginsWith"
|
||||
"filled" = "Filled"; "notFilled" = "NotFilled"
|
||||
}
|
||||
$mapped = $opMap[$opRaw]
|
||||
if ($mapped) { $result.op = $opRaw } else { $result.op = $opRaw }
|
||||
|
||||
# Parse value (skip "_" which means empty/placeholder)
|
||||
if ($valPart -and $valPart -ne "_") {
|
||||
if ($valPart -eq "true" -or $valPart -eq "false") {
|
||||
$result.value = [bool]($valPart -eq "true")
|
||||
$result["valueType"] = "xs:boolean"
|
||||
} elseif ($valPart -match '^\d{4}-\d{2}-\d{2}T') {
|
||||
$result.value = $valPart
|
||||
$result["valueType"] = "xs:dateTime"
|
||||
} elseif ($valPart -match '^\d+(\.\d+)?$') {
|
||||
$result.value = $valPart
|
||||
$result["valueType"] = "xs:decimal"
|
||||
} else {
|
||||
$result.value = $valPart
|
||||
$result["valueType"] = "xs:string"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
# No operator found — just a field name
|
||||
$result.field = $s
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
|
||||
# --- 9. Comparison type mapper ---
|
||||
|
||||
$script:comparisonTypes = @{
|
||||
@@ -293,7 +481,7 @@ function Emit-Field {
|
||||
dataPath = "$($fieldDef.dataPath)"
|
||||
field = if ($fieldDef.field) { "$($fieldDef.field)" } else { "$($fieldDef.dataPath)" }
|
||||
title = if ($fieldDef.title) { "$($fieldDef.title)" } else { "" }
|
||||
type = if ($fieldDef.type) { "$($fieldDef.type)" } else { "" }
|
||||
type = if ($fieldDef.type) { Resolve-TypeStr "$($fieldDef.type)" } else { "" }
|
||||
roles = @()
|
||||
restrict = @()
|
||||
appearance = @{}
|
||||
@@ -511,8 +699,9 @@ function Emit-CalcFields {
|
||||
Emit-MLText -tag "title" -text "$($cf.title)" -indent "`t`t"
|
||||
}
|
||||
if ($cf.type) {
|
||||
$cfType = Resolve-TypeStr "$($cf.type)"
|
||||
X "`t`t<valueType>"
|
||||
Emit-ValueType -typeStr "$($cf.type)" -indent "`t`t`t"
|
||||
Emit-ValueType -typeStr $cfType -indent "`t`t`t"
|
||||
X "`t`t</valueType>"
|
||||
}
|
||||
if ($cf.restrict) {
|
||||
@@ -568,6 +757,52 @@ function Emit-TotalFields {
|
||||
}
|
||||
|
||||
# === Parameters ===
|
||||
|
||||
function Emit-SingleParam {
|
||||
param($p, $parsed)
|
||||
|
||||
X "`t<parameter>"
|
||||
X "`t`t<name>$(Esc-Xml $parsed.name)</name>"
|
||||
|
||||
# Title
|
||||
$title = if ($p -isnot [string] -and $p.title) { "$($p.title)" } else { "" }
|
||||
if ($title) {
|
||||
Emit-MLText -tag "title" -text $title -indent "`t`t"
|
||||
}
|
||||
|
||||
# ValueType
|
||||
if ($parsed.type) {
|
||||
X "`t`t<valueType>"
|
||||
Emit-ValueType -typeStr $parsed.type -indent "`t`t`t"
|
||||
X "`t`t</valueType>"
|
||||
}
|
||||
|
||||
# Value
|
||||
Emit-ParamValue -type $parsed.type -val $parsed.value -indent "`t`t"
|
||||
|
||||
# UseRestriction
|
||||
if ($p -isnot [string] -and $p.useRestriction -eq $true) {
|
||||
X "`t`t<useRestriction>true</useRestriction>"
|
||||
}
|
||||
|
||||
# Expression
|
||||
if ($parsed.expression) {
|
||||
X "`t`t<expression>$(Esc-Xml $parsed.expression)</expression>"
|
||||
}
|
||||
|
||||
# AvailableAsField
|
||||
if ($parsed.availableAsField -eq $false) {
|
||||
X "`t`t<availableAsField>false</availableAsField>"
|
||||
}
|
||||
|
||||
# Use
|
||||
if ($p -isnot [string] -and $p.use) {
|
||||
X "`t`t<use>$($p.use)</use>"
|
||||
}
|
||||
|
||||
X "`t</parameter>"
|
||||
}
|
||||
|
||||
function Emit-Parameters {
|
||||
if (-not $def.parameters) { return }
|
||||
foreach ($p in $def.parameters) {
|
||||
@@ -576,51 +811,31 @@ function Emit-Parameters {
|
||||
} else {
|
||||
$parsed = @{
|
||||
name = "$($p.name)"
|
||||
type = if ($p.type) { "$($p.type)" } else { "" }
|
||||
type = if ($p.type) { Resolve-TypeStr "$($p.type)" } else { "" }
|
||||
value = $p.value
|
||||
autoDates = $false
|
||||
}
|
||||
if ($p.expression) { $parsed.expression = "$($p.expression)" }
|
||||
if ($p.availableAsField -eq $false) { $parsed.availableAsField = $false }
|
||||
if ($p.autoDates -eq $true) { $parsed.autoDates = $true }
|
||||
}
|
||||
|
||||
X "`t<parameter>"
|
||||
X "`t`t<name>$(Esc-Xml $parsed.name)</name>"
|
||||
Emit-SingleParam -p $p -parsed $parsed
|
||||
|
||||
# Title
|
||||
$title = if ($p -isnot [string] -and $p.title) { "$($p.title)" } else { "" }
|
||||
if ($title) {
|
||||
Emit-MLText -tag "title" -text $title -indent "`t`t"
|
||||
# @autoDates: auto-generate ДатаНачала and ДатаОкончания
|
||||
if ($parsed.autoDates) {
|
||||
$paramName = $parsed.name
|
||||
$beginParsed = @{
|
||||
name = "ДатаНачала"; type = "date"; value = $null
|
||||
expression = "&$paramName.ДатаНачала"; availableAsField = $false
|
||||
}
|
||||
Emit-SingleParam -p $null -parsed $beginParsed
|
||||
$endParsed = @{
|
||||
name = "ДатаОкончания"; type = "date"; value = $null
|
||||
expression = "&$paramName.ДатаОкончания"; availableAsField = $false
|
||||
}
|
||||
Emit-SingleParam -p $null -parsed $endParsed
|
||||
}
|
||||
|
||||
# ValueType
|
||||
if ($parsed.type) {
|
||||
X "`t`t<valueType>"
|
||||
Emit-ValueType -typeStr $parsed.type -indent "`t`t`t"
|
||||
X "`t`t</valueType>"
|
||||
}
|
||||
|
||||
# Value
|
||||
Emit-ParamValue -type $parsed.type -val $parsed.value -indent "`t`t"
|
||||
|
||||
# UseRestriction
|
||||
if ($p -isnot [string] -and $p.useRestriction -eq $true) {
|
||||
X "`t`t<useRestriction>true</useRestriction>"
|
||||
}
|
||||
|
||||
# Expression
|
||||
if ($p -isnot [string] -and $p.expression) {
|
||||
X "`t`t<expression>$(Esc-Xml "$($p.expression)")</expression>"
|
||||
}
|
||||
|
||||
# AvailableAsField
|
||||
if ($p -isnot [string] -and $p.availableAsField -eq $false) {
|
||||
X "`t`t<availableAsField>false</availableAsField>"
|
||||
}
|
||||
|
||||
# Use
|
||||
if ($p -isnot [string] -and $p.use) {
|
||||
X "`t`t<use>$($p.use)</use>"
|
||||
}
|
||||
|
||||
X "`t</parameter>"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -806,7 +1021,31 @@ function Emit-Filter {
|
||||
|
||||
X "$indent<dcsset:filter>"
|
||||
foreach ($item in $items) {
|
||||
Emit-FilterItem -item $item -indent "$indent`t"
|
||||
if ($item -is [string]) {
|
||||
# Parse shorthand: "Организация = _ @off @user"
|
||||
$parsed = Parse-FilterShorthand $item
|
||||
$filterObj = New-Object PSObject
|
||||
$filterObj | Add-Member -NotePropertyName "field" -NotePropertyValue $parsed.field
|
||||
$filterObj | Add-Member -NotePropertyName "op" -NotePropertyValue $parsed.op
|
||||
if ($parsed.use -eq $false) {
|
||||
$filterObj | Add-Member -NotePropertyName "use" -NotePropertyValue $false
|
||||
}
|
||||
if ($null -ne $parsed.value) {
|
||||
$filterObj | Add-Member -NotePropertyName "value" -NotePropertyValue $parsed.value
|
||||
}
|
||||
if ($parsed["valueType"]) {
|
||||
$filterObj | Add-Member -NotePropertyName "valueType" -NotePropertyValue $parsed["valueType"]
|
||||
}
|
||||
if ($parsed.userSettingID) {
|
||||
$filterObj | Add-Member -NotePropertyName "userSettingID" -NotePropertyValue $parsed.userSettingID
|
||||
}
|
||||
if ($parsed.viewMode) {
|
||||
$filterObj | Add-Member -NotePropertyName "viewMode" -NotePropertyValue $parsed.viewMode
|
||||
}
|
||||
Emit-FilterItem -item $filterObj -indent "$indent`t"
|
||||
} else {
|
||||
Emit-FilterItem -item $item -indent "$indent`t"
|
||||
}
|
||||
}
|
||||
X "$indent</dcsset:filter>"
|
||||
}
|
||||
@@ -873,6 +1112,26 @@ function Emit-DataParameters {
|
||||
|
||||
X "$indent<dcsset:dataParameters>"
|
||||
foreach ($dp in $items) {
|
||||
# Support string shorthand
|
||||
if ($dp -is [string]) {
|
||||
$parsed = Parse-DataParamShorthand $dp
|
||||
$dpObj = New-Object PSObject
|
||||
$dpObj | Add-Member -NotePropertyName "parameter" -NotePropertyValue $parsed.parameter
|
||||
if ($null -ne $parsed.value) {
|
||||
$dpObj | Add-Member -NotePropertyName "value" -NotePropertyValue $parsed.value
|
||||
}
|
||||
if ($parsed.use -eq $false) {
|
||||
$dpObj | Add-Member -NotePropertyName "use" -NotePropertyValue $false
|
||||
}
|
||||
if ($parsed.userSettingID) {
|
||||
$dpObj | Add-Member -NotePropertyName "userSettingID" -NotePropertyValue $parsed.userSettingID
|
||||
}
|
||||
if ($parsed.viewMode) {
|
||||
$dpObj | Add-Member -NotePropertyName "viewMode" -NotePropertyValue $parsed.viewMode
|
||||
}
|
||||
$dp = $dpObj
|
||||
}
|
||||
|
||||
X "$indent`t<dcscor:item xsi:type=`"dcsset:SettingsParameterValue`">"
|
||||
|
||||
if ($dp.use -eq $false) {
|
||||
@@ -883,8 +1142,13 @@ function Emit-DataParameters {
|
||||
|
||||
# Value
|
||||
if ($null -ne $dp.value) {
|
||||
if ($dp.value.variant) {
|
||||
# StandardPeriod
|
||||
if ($dp.value -is [PSCustomObject] -and $dp.value.variant) {
|
||||
# StandardPeriod (object form from JSON)
|
||||
X "$indent`t`t<dcscor:value xsi:type=`"v8:StandardPeriod`">"
|
||||
X "$indent`t`t`t<v8:variant xsi:type=`"v8:StandardPeriodVariant`">$($dp.value.variant)</v8:variant>"
|
||||
X "$indent`t`t</dcscor:value>"
|
||||
} elseif ($dp.value -is [hashtable] -and $dp.value.variant) {
|
||||
# StandardPeriod (hashtable from shorthand parser)
|
||||
X "$indent`t`t<dcscor:value xsi:type=`"v8:StandardPeriod`">"
|
||||
X "$indent`t`t`t<v8:variant xsi:type=`"v8:StandardPeriodVariant`">$($dp.value.variant)</v8:variant>"
|
||||
X "$indent`t`t</dcscor:value>"
|
||||
@@ -945,6 +1209,37 @@ function Emit-GroupItems {
|
||||
X "$indent</dcsset:groupItems>"
|
||||
}
|
||||
|
||||
# Parse structure string shorthand: "Организация > Номенклатура > details"
|
||||
function Parse-StructureShorthand {
|
||||
param([string]$s)
|
||||
|
||||
$segments = $s -split '\s*>\s*'
|
||||
$result = @()
|
||||
|
||||
# Build nested groups from right to left
|
||||
$innermost = $null
|
||||
for ($i = $segments.Count - 1; $i -ge 0; $i--) {
|
||||
$seg = $segments[$i].Trim()
|
||||
$group = New-Object PSObject
|
||||
$group | Add-Member -NotePropertyName "type" -NotePropertyValue "group"
|
||||
|
||||
if ($seg -match '^(?i)(details|детали)$') {
|
||||
# Empty groupBy = detailed records
|
||||
$group | Add-Member -NotePropertyName "groupBy" -NotePropertyValue @()
|
||||
} else {
|
||||
$group | Add-Member -NotePropertyName "groupBy" -NotePropertyValue @($seg)
|
||||
}
|
||||
|
||||
if ($null -ne $innermost) {
|
||||
$group | Add-Member -NotePropertyName "children" -NotePropertyValue @($innermost)
|
||||
}
|
||||
$innermost = $group
|
||||
}
|
||||
|
||||
if ($innermost) { $result += $innermost }
|
||||
return ,$result
|
||||
}
|
||||
|
||||
function Emit-StructureItem {
|
||||
param($item, [string]$indent)
|
||||
|
||||
@@ -958,8 +1253,17 @@ function Emit-StructureItem {
|
||||
}
|
||||
|
||||
Emit-GroupItems -groupBy $item.groupBy -indent "$indent`t"
|
||||
Emit-Order -items $item.order -indent "$indent`t"
|
||||
Emit-Selection -items $item.selection -indent "$indent`t"
|
||||
|
||||
# Default order to ["Auto"] if not specified
|
||||
$orderItems = $item.order
|
||||
if (-not $orderItems) { $orderItems = @("Auto") }
|
||||
Emit-Order -items $orderItems -indent "$indent`t"
|
||||
|
||||
# Default selection to ["Auto"] if not specified
|
||||
$selItems = $item.selection
|
||||
if (-not $selItems) { $selItems = @("Auto") }
|
||||
Emit-Selection -items $selItems -indent "$indent`t"
|
||||
|
||||
Emit-Filter -items $item.filter -indent "$indent`t"
|
||||
|
||||
if ($item.outputParameters) {
|
||||
@@ -987,8 +1291,10 @@ function Emit-StructureItem {
|
||||
foreach ($col in $item.columns) {
|
||||
X "$indent`t<dcsset:column>"
|
||||
Emit-GroupItems -groupBy $col.groupBy -indent "$indent`t`t"
|
||||
Emit-Order -items $col.order -indent "$indent`t`t"
|
||||
Emit-Selection -items $col.selection -indent "$indent`t`t"
|
||||
$colOrder = $col.order; if (-not $colOrder) { $colOrder = @("Auto") }
|
||||
Emit-Order -items $colOrder -indent "$indent`t`t"
|
||||
$colSel = $col.selection; if (-not $colSel) { $colSel = @("Auto") }
|
||||
Emit-Selection -items $colSel -indent "$indent`t`t"
|
||||
X "$indent`t</dcsset:column>"
|
||||
}
|
||||
}
|
||||
@@ -1001,8 +1307,10 @@ function Emit-StructureItem {
|
||||
X "$indent`t`t<dcsset:name>$(Esc-Xml "$($row.name)")</dcsset:name>"
|
||||
}
|
||||
Emit-GroupItems -groupBy $row.groupBy -indent "$indent`t`t"
|
||||
Emit-Order -items $row.order -indent "$indent`t`t"
|
||||
Emit-Selection -items $row.selection -indent "$indent`t`t"
|
||||
$rowOrder = $row.order; if (-not $rowOrder) { $rowOrder = @("Auto") }
|
||||
Emit-Order -items $rowOrder -indent "$indent`t`t"
|
||||
$rowSel = $row.selection; if (-not $rowSel) { $rowSel = @("Auto") }
|
||||
Emit-Selection -items $rowSel -indent "$indent`t`t"
|
||||
X "$indent`t</dcsset:row>"
|
||||
}
|
||||
}
|
||||
@@ -1020,8 +1328,10 @@ function Emit-StructureItem {
|
||||
if ($item.points) {
|
||||
X "$indent`t<dcsset:point>"
|
||||
Emit-GroupItems -groupBy $item.points.groupBy -indent "$indent`t`t"
|
||||
Emit-Order -items $item.points.order -indent "$indent`t`t"
|
||||
Emit-Selection -items $item.points.selection -indent "$indent`t`t"
|
||||
$ptOrder = $item.points.order; if (-not $ptOrder) { $ptOrder = @("Auto") }
|
||||
Emit-Order -items $ptOrder -indent "$indent`t`t"
|
||||
$ptSel = $item.points.selection; if (-not $ptSel) { $ptSel = @("Auto") }
|
||||
Emit-Selection -items $ptSel -indent "$indent`t`t"
|
||||
X "$indent`t</dcsset:point>"
|
||||
}
|
||||
|
||||
@@ -1029,8 +1339,10 @@ function Emit-StructureItem {
|
||||
if ($item.series) {
|
||||
X "$indent`t<dcsset:series>"
|
||||
Emit-GroupItems -groupBy $item.series.groupBy -indent "$indent`t`t"
|
||||
Emit-Order -items $item.series.order -indent "$indent`t`t"
|
||||
Emit-Selection -items $item.series.selection -indent "$indent`t`t"
|
||||
$srOrder = $item.series.order; if (-not $srOrder) { $srOrder = @("Auto") }
|
||||
Emit-Order -items $srOrder -indent "$indent`t`t"
|
||||
$srSel = $item.series.selection; if (-not $srSel) { $srSel = @("Auto") }
|
||||
Emit-Selection -items $srSel -indent "$indent`t`t"
|
||||
X "$indent`t</dcsset:series>"
|
||||
}
|
||||
|
||||
@@ -1120,9 +1432,13 @@ function Emit-SettingsVariants {
|
||||
Emit-DataParameters -items $s.dataParameters -indent "`t`t`t"
|
||||
}
|
||||
|
||||
# Structure
|
||||
# Structure (supports string shorthand: "Организация > details")
|
||||
if ($s.structure) {
|
||||
foreach ($item in $s.structure) {
|
||||
$structItems = $s.structure
|
||||
if ($structItems -is [string]) {
|
||||
$structItems = Parse-StructureShorthand $structItems
|
||||
}
|
||||
foreach ($item in $structItems) {
|
||||
Emit-StructureItem -item $item -indent "`t`t`t"
|
||||
}
|
||||
}
|
||||
|
||||
+110
-40
@@ -153,6 +153,27 @@
|
||||
| `ChartOfAccountsRef.XXX` | `cfg:ChartOfAccountsRef.XXX` | — |
|
||||
| `StandardPeriod` | `v8:StandardPeriod` | — |
|
||||
|
||||
### Синонимы типов
|
||||
|
||||
Все имена типов регистронезависимые. Поддерживаются русские и альтернативные имена:
|
||||
|
||||
| Синоним | Канонический тип |
|
||||
|---------|-----------------|
|
||||
| `число`, `Число` | `decimal` |
|
||||
| `строка`, `Строка` | `string` |
|
||||
| `булево`, `Булево`, `bool` | `boolean` |
|
||||
| `дата`, `Дата` | `date` |
|
||||
| `датаВремя`, `ДатаВремя` | `dateTime` |
|
||||
| `СтандартныйПериод` | `StandardPeriod` |
|
||||
| `int`, `integer`, `number`, `num` | `decimal` |
|
||||
| `СправочникСсылка.XXX` | `CatalogRef.XXX` |
|
||||
| `ДокументСсылка.XXX` | `DocumentRef.XXX` |
|
||||
| `ПеречислениеСсылка.XXX` | `EnumRef.XXX` |
|
||||
| `ПланСчетовСсылка.XXX` | `ChartOfAccountsRef.XXX` |
|
||||
| `ПланВидовХарактеристикСсылка.XXX` | `ChartOfCharacteristicTypesRef.XXX` |
|
||||
|
||||
Параметризованные: `число(15,2)` → `decimal(15,2)`, `строка(100)` → `string(100)`.
|
||||
|
||||
### Роли
|
||||
|
||||
| DSL shorthand | Объектная форма | XML |
|
||||
@@ -239,14 +260,14 @@
|
||||
### Shorthand
|
||||
|
||||
```
|
||||
"<name>: <type> [= <default>]"
|
||||
"<name>: <type> [= <default>] [@autoDates]"
|
||||
```
|
||||
|
||||
Примеры:
|
||||
|
||||
```json
|
||||
"parameters": [
|
||||
"Период: StandardPeriod = LastMonth",
|
||||
"Период: StandardPeriod = LastMonth @autoDates",
|
||||
"Организация: CatalogRef.Организации",
|
||||
"ДатаОтчета: date"
|
||||
]
|
||||
@@ -254,6 +275,26 @@
|
||||
|
||||
**Парсинг:** `"A: T = V"` → `name=A`, `type=T`, `value=V`. Значение `LastMonth` и другие варианты периодов → `v8:StandardPeriod` с `v8:variant`.
|
||||
|
||||
### @autoDates
|
||||
|
||||
Флаг `@autoDates` в shorthand параметра автоматически генерирует два дополнительных параметра:
|
||||
- `ДатаНачала` (date, expression=`&<Имя>.ДатаНачала`, availableAsField=false)
|
||||
- `ДатаОкончания` (date, expression=`&<Имя>.ДатаОкончания`, availableAsField=false)
|
||||
|
||||
Заменяет типовой бойлерплейт из 5 строк на 1:
|
||||
|
||||
```json
|
||||
// Было:
|
||||
"parameters": [
|
||||
"Период: StandardPeriod = LastMonth",
|
||||
{ "name": "ДатаНачала", "type": "date", "expression": "&Период.ДатаНачала", "availableAsField": false },
|
||||
{ "name": "ДатаОкончания", "type": "date", "expression": "&Период.ДатаОкончания", "availableAsField": false }
|
||||
]
|
||||
|
||||
// Стало:
|
||||
"parameters": ["Период: StandardPeriod = LastMonth @autoDates"]
|
||||
```
|
||||
|
||||
### Объектная форма
|
||||
|
||||
```json
|
||||
@@ -382,6 +423,27 @@
|
||||
|
||||
### filter
|
||||
|
||||
#### Shorthand-строка
|
||||
|
||||
```json
|
||||
"filter": [
|
||||
"Организация = _ @off @user",
|
||||
"Дата >= 2024-01-01T00:00:00",
|
||||
"Статус filled",
|
||||
"Количество > 0"
|
||||
]
|
||||
```
|
||||
|
||||
Формат: `"<Поле> <оператор> [<значение>] [@off] [@user] [@quickAccess]"`.
|
||||
|
||||
- Значение `_` — пустое (placeholder, не выводится в XML)
|
||||
- `@off` → `use=false`
|
||||
- `@user` → `userSettingID=auto` (генерировать GUID)
|
||||
- `@quickAccess` → `viewMode=QuickAccess`
|
||||
- Типы значений автоопределяются: `true`/`false` → boolean, `2024-01-01T00:00:00` → dateTime, числа → decimal, прочее → string
|
||||
|
||||
#### Объектная форма
|
||||
|
||||
```json
|
||||
"filter": [
|
||||
{ "field": "Организация", "op": "=", "use": false, "userSettingID": "auto" },
|
||||
@@ -458,6 +520,22 @@
|
||||
|
||||
### dataParameters
|
||||
|
||||
#### Shorthand-строка
|
||||
|
||||
```json
|
||||
"dataParameters": [
|
||||
"Период = LastMonth @user",
|
||||
"Организация @off @user"
|
||||
]
|
||||
```
|
||||
|
||||
Формат: `"<Имя> [= <значение>] [@off] [@user] [@quickAccess] [@normal]"`.
|
||||
|
||||
- Значения-варианты периодов (`LastMonth`, `ThisYear` и др.) автоматически оборачиваются в `v8:StandardPeriod`
|
||||
- `@off` → `use=false`, `@user` → `userSettingID=auto`
|
||||
|
||||
#### Объектная форма
|
||||
|
||||
```json
|
||||
"dataParameters": [
|
||||
{ "parameter": "Период", "value": { "variant": "LastMonth" }, "userSettingID": "auto" },
|
||||
@@ -467,20 +545,32 @@
|
||||
|
||||
### structure
|
||||
|
||||
#### String shorthand (рекомендуется для типичных случаев)
|
||||
|
||||
```json
|
||||
"structure": "Организация > details"
|
||||
"structure": "Организация > Номенклатура > details"
|
||||
"structure": "Период > Организация > Номенклатура > details"
|
||||
```
|
||||
|
||||
`>` разделяет уровни вложенности. Каждый сегмент — группировка по указанному полю. `details` (или `детали`) — детальные записи (пустой `groupBy`). Для каждого уровня `selection` и `order` автоматически `["Auto"]`.
|
||||
|
||||
#### Массив объектов
|
||||
|
||||
```json
|
||||
"structure": [
|
||||
{
|
||||
"type": "group",
|
||||
"groupBy": ["Организация"],
|
||||
"selection": ["Auto"],
|
||||
"order": ["Auto"],
|
||||
"children": [
|
||||
{ "type": "group", "selection": ["Auto"], "order": ["Auto"] }
|
||||
{ "type": "group" }
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**Умолчания:** `selection` и `order` по умолчанию `["Auto"]` на каждом уровне (в группировках, строках/колонках таблиц, точках/сериях диаграмм). Указывать явно нужно только если требуется другой набор полей.
|
||||
|
||||
#### Группировка (group)
|
||||
|
||||
| Поле | Описание |
|
||||
@@ -489,9 +579,9 @@
|
||||
| `name` | Имя группировки (опц.) |
|
||||
| `groupBy` | Массив полей. Пусто/опущено = детальные записи |
|
||||
| `groupType` | `"Items"` (умолч.), `"Hierarchy"`, `"HierarchyOnly"` |
|
||||
| `selection` | Выборка (как в settings) |
|
||||
| `selection` | Выборка (умолч. `["Auto"]`) |
|
||||
| `filter` | Отборы (как в settings) |
|
||||
| `order` | Сортировка (как в settings) |
|
||||
| `order` | Сортировка (умолч. `["Auto"]`) |
|
||||
| `outputParameters` | Параметры вывода (как в settings) |
|
||||
| `children` | Вложенные элементы структуры |
|
||||
|
||||
@@ -570,18 +660,15 @@
|
||||
"totalFields": ["Количество: Сумма"],
|
||||
"settingsVariants": [{
|
||||
"name": "Основной",
|
||||
"presentation": "Основной",
|
||||
"settings": {
|
||||
"selection": ["Наименование", "Количество"],
|
||||
"structure": [
|
||||
{ "type": "group", "order": ["Auto"], "selection": ["Auto"] }
|
||||
]
|
||||
"structure": [{ "type": "group" }]
|
||||
}
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
## 12. Полный пример — средний
|
||||
## 12. Полный пример — средний (с shorthand v2)
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -590,50 +677,33 @@
|
||||
"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)"
|
||||
"Организация: СправочникСсылка.Организации @dimension",
|
||||
"Номенклатура: СправочникСсылка.Номенклатура @dimension",
|
||||
"Количество: число(15,3)",
|
||||
"Сумма: число(15,2)"
|
||||
]
|
||||
}
|
||||
],
|
||||
"totalFields": [
|
||||
"Количество: Сумма",
|
||||
"Сумма: Сумма"
|
||||
],
|
||||
"totalFields": ["Количество: Сумма", "Сумма: Сумма"],
|
||||
"parameters": [
|
||||
"Период: StandardPeriod = LastMonth",
|
||||
{ "name": "ДатаНачала", "type": "date", "expression": "&Период.ДатаНачала", "availableAsField": false },
|
||||
{ "name": "ДатаОкончания", "type": "date", "expression": "&Период.ДатаОкончания", "availableAsField": false }
|
||||
"Период: СтандартныйПериод = LastMonth @autoDates"
|
||||
],
|
||||
"settingsVariants": [{
|
||||
"name": "Основной",
|
||||
"presentation": "Продажи по организациям",
|
||||
"settings": {
|
||||
"selection": ["Номенклатура", "Количество", "Сумма", "Auto"],
|
||||
"filter": [
|
||||
{ "field": "Организация", "op": "=", "use": false, "userSettingID": "auto" }
|
||||
],
|
||||
"filter": ["Организация = _ @off @user"],
|
||||
"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"] }
|
||||
]
|
||||
}
|
||||
]
|
||||
"dataParameters": ["Период = LastMonth @user"],
|
||||
"structure": "Организация > details"
|
||||
}
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
**Сравнение с v1:** средний пример сократился с 58 до 33 строк (−43%). Основная экономия: `@autoDates` (−4 строки), structure shorthand (−9 строк), filter/dataParam shorthand (−4 строки).
|
||||
|
||||
Reference in New Issue
Block a user