diff --git a/.claude/skills/skd-compile/SKILL.md b/.claude/skills/skd-compile/SKILL.md
index 38a21d6d..161bc981 100644
--- a/.claude/skills/skd-compile/SKILL.md
+++ b/.claude/skills/skd-compile/SKILL.md
@@ -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"
+ }
+ }]
}
```
diff --git a/.claude/skills/skd-compile/scripts/skd-compile.ps1 b/.claude/skills/skd-compile/scripts/skd-compile.ps1
index 72fd6e0e..3e208fd5 100644
--- a/.claude/skills/skd-compile/scripts/skd-compile.ps1
+++ b/.claude/skills/skd-compile/scripts/skd-compile.ps1
@@ -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 "$indentxs:boolean"
@@ -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"
- Emit-ValueType -typeStr "$($cf.type)" -indent "`t`t`t"
+ Emit-ValueType -typeStr $cfType -indent "`t`t`t"
X "`t`t"
}
if ($cf.restrict) {
@@ -568,6 +757,52 @@ function Emit-TotalFields {
}
# === Parameters ===
+
+function Emit-SingleParam {
+ param($p, $parsed)
+
+ X "`t"
+ X "`t`t$(Esc-Xml $parsed.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"
+ Emit-ValueType -typeStr $parsed.type -indent "`t`t`t"
+ X "`t`t"
+ }
+
+ # Value
+ Emit-ParamValue -type $parsed.type -val $parsed.value -indent "`t`t"
+
+ # UseRestriction
+ if ($p -isnot [string] -and $p.useRestriction -eq $true) {
+ X "`t`ttrue"
+ }
+
+ # Expression
+ if ($parsed.expression) {
+ X "`t`t$(Esc-Xml $parsed.expression)"
+ }
+
+ # AvailableAsField
+ if ($parsed.availableAsField -eq $false) {
+ X "`t`tfalse"
+ }
+
+ # Use
+ if ($p -isnot [string] -and $p.use) {
+ X "`t`t"
+ }
+
+ X "`t"
+}
+
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"
- X "`t`t$(Esc-Xml $parsed.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"
- Emit-ValueType -typeStr $parsed.type -indent "`t`t`t"
- X "`t`t"
- }
-
- # Value
- Emit-ParamValue -type $parsed.type -val $parsed.value -indent "`t`t"
-
- # UseRestriction
- if ($p -isnot [string] -and $p.useRestriction -eq $true) {
- X "`t`ttrue"
- }
-
- # Expression
- if ($p -isnot [string] -and $p.expression) {
- X "`t`t$(Esc-Xml "$($p.expression)")"
- }
-
- # AvailableAsField
- if ($p -isnot [string] -and $p.availableAsField -eq $false) {
- X "`t`tfalse"
- }
-
- # Use
- if ($p -isnot [string] -and $p.use) {
- X "`t`t"
- }
-
- X "`t"
}
}
@@ -806,7 +1021,31 @@ function Emit-Filter {
X "$indent"
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"
}
@@ -873,6 +1112,26 @@ function Emit-DataParameters {
X "$indent"
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"
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"
+ X "$indent`t`t`t$($dp.value.variant)"
+ X "$indent`t`t"
+ } elseif ($dp.value -is [hashtable] -and $dp.value.variant) {
+ # StandardPeriod (hashtable from shorthand parser)
X "$indent`t`t"
X "$indent`t`t`t$($dp.value.variant)"
X "$indent`t`t"
@@ -945,6 +1209,37 @@ function Emit-GroupItems {
X "$indent"
}
+# 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"
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"
}
}
@@ -1001,8 +1307,10 @@ function Emit-StructureItem {
X "$indent`t`t$(Esc-Xml "$($row.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"
}
}
@@ -1020,8 +1328,10 @@ function Emit-StructureItem {
if ($item.points) {
X "$indent`t"
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"
}
@@ -1029,8 +1339,10 @@ function Emit-StructureItem {
if ($item.series) {
X "$indent`t"
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"
}
@@ -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"
}
}
diff --git a/docs/skd-dsl-spec.md b/docs/skd-dsl-spec.md
index 52c3eb6c..19eb76ba 100644
--- a/docs/skd-dsl-spec.md
+++ b/docs/skd-dsl-spec.md
@@ -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
```
-": [= ]"
+": [= ] [@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 строки).