diff --git a/.claude/skills/skd-compile/SKILL.md b/.claude/skills/skd-compile/SKILL.md
index f7321c2d..6d958f4a 100644
--- a/.claude/skills/skd-compile/SKILL.md
+++ b/.claude/skills/skd-compile/SKILL.md
@@ -85,6 +85,23 @@ powershell.exe -NoProfile -File .claude/skills/skd-compile/scripts/skd-compile.p
В объектной форме: `"useRestriction": { "field": true, "condition": true, "group": true, "order": true }` или `"restrict": ["noField", "noFilter"]`.
+### Вычисляемые поля (calculatedFields)
+
+Shorthand: `"Имя [Заголовок]: тип = Выражение #noField #noFilter #noGroup #noOrder"` — все части кроме имени опциональны.
+
+```json
+"calculatedFields": [
+ "Маржа = Цена - Закупка",
+ "Наценка [Наценка, %]: decimal(10,2) = Маржа / Закупка * 100",
+ "Служебное: string = \"\" #noField #noFilter #noGroup #noOrder"
+]
+```
+
+Объектная форма — когда нужна `appearance`:
+```json
+{ "name": "Маржа", "title": "Маржа", "expression": "Цена - Закупка", "type": "decimal(15,2)", "useRestriction": "#noField #noFilter" }
+```
+
### Итоги (shorthand)
```json
diff --git a/.claude/skills/skd-compile/scripts/skd-compile.ps1 b/.claude/skills/skd-compile/scripts/skd-compile.ps1
index 52909ae8..f1201d0b 100644
--- a/.claude/skills/skd-compile/scripts/skd-compile.ps1
+++ b/.claude/skills/skd-compile/scripts/skd-compile.ps1
@@ -1,4 +1,4 @@
-# skd-compile v1.12 — Compile 1C DCS from JSON
+# skd-compile v1.13 — Compile 1C DCS from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[string]$DefinitionFile,
@@ -367,15 +367,51 @@ function Parse-ParamShorthand {
function Parse-CalcShorthand {
param([string]$s)
- # "DataPath = Expression"
- $idx = $s.IndexOf('=')
- if ($idx -gt 0) {
- return @{
- dataPath = $s.Substring(0, $idx).Trim()
- expression = $s.Substring($idx + 1).Trim()
- }
+ # Pattern: "Name [Title]: type = Expression #noField #noFilter ...".
+ # - `[Title]` is extracted only from the LHS of '=' so that `[...]` inside
+ # an expression (e.g. index access) isn't interpreted as a title.
+ # - `#restrict` flags use a known-names pattern and are extracted globally —
+ # the docs put them after `=`, and the closed flag set avoids matching
+ # `#word` that happens to appear inside a string literal.
+ $restrictPattern = '#(noField|noFilter|noCondition|noGroup|noOrder)\b'
+
+ $restrict = @()
+ foreach ($m in [regex]::Matches($s, $restrictPattern)) {
+ $restrict += $m.Groups[1].Value
+ }
+ $s = [regex]::Replace($s, "\s*$restrictPattern", '')
+
+ $eqIdx = $s.IndexOf('=')
+ if ($eqIdx -gt 0) {
+ $lhs = $s.Substring(0, $eqIdx)
+ $rhs = $s.Substring($eqIdx + 1).Trim()
+ } else {
+ $lhs = $s
+ $rhs = ""
+ }
+
+ $title = ""
+ if ($lhs -match '\[([^\]]+)\]') {
+ $title = $Matches[1]
+ $lhs = $lhs -replace '\s*\[[^\]]+\]', ''
+ }
+ $lhs = $lhs.Trim()
+
+ $type = ""
+ $dataPath = $lhs
+ if ($lhs.Contains(':')) {
+ $parts = $lhs -split ':', 2
+ $dataPath = $parts[0].Trim()
+ $type = Resolve-TypeStr ($parts[1].Trim())
+ }
+
+ return @{
+ dataPath = $dataPath
+ expression = $rhs
+ type = $type
+ title = $title
+ restrict = $restrict
}
- return @{ dataPath = $s.Trim(); expression = "" }
}
# --- 8b. DataParameter shorthand parser ---
@@ -771,64 +807,90 @@ function Emit-DataSetLinks {
# === CalculatedFields ===
function Emit-CalcFields {
if (-not $def.calculatedFields) { return }
+ $restrictMap = @{
+ "noField" = "field"; "noFilter" = "condition"; "noCondition" = "condition"
+ "noGroup" = "group"; "noOrder" = "order"
+ }
foreach ($cf in $def.calculatedFields) {
+ # Collect dataPath/expression/title/type/restrict/appearance from either
+ # shorthand string or object form. Object form accepts dataPath/field/name
+ # as synonyms; useRestriction/restrict accepts object, array, or flag string.
+ $title = ""
+ $typeStr = ""
+ $restrictTokens = @()
+ $restrictObj = $null
+ $appearance = $null
+
if ($cf -is [string]) {
$parsed = Parse-CalcShorthand $cf
+ $dataPath = "$($parsed.dataPath)"
+ $expression = "$($parsed.expression)"
+ $title = "$($parsed.title)"
+ $typeStr = "$($parsed.type)"
+ if ($parsed.restrict) { $restrictTokens = @($parsed.restrict) }
} else {
- $dp = if ($cf.dataPath) { "$($cf.dataPath)" } else { "$($cf.field)" }
- $parsed = @{
- dataPath = $dp
- expression = "$($cf.expression)"
- }
- }
+ $dataPath = if ($cf.dataPath) { "$($cf.dataPath)" }
+ elseif ($cf.field) { "$($cf.field)" }
+ else { "$($cf.name)" }
+ $expression = "$($cf.expression)"
+ if ($cf.title) { $title = "$($cf.title)" }
+ if ($cf.type) { $typeStr = Resolve-TypeStr "$($cf.type)" }
- X "`t"
- X "`t`t$(Esc-Xml $parsed.dataPath)"
- X "`t`t$(Esc-Xml $parsed.expression)"
-
- if ($cf -isnot [string]) {
- if ($cf.title) {
- Emit-MLText -tag "title" -text "$($cf.title)" -indent "`t`t"
- }
- if ($cf.type) {
- $cfType = Resolve-TypeStr "$($cf.type)"
- X "`t`t"
- Emit-ValueType -typeStr $cfType -indent "`t`t`t"
- X "`t`t"
- }
$restrictVal = if ($cf.restrict) { $cf.restrict } elseif ($cf.useRestriction) { $cf.useRestriction } else { $null }
if ($restrictVal) {
- X "`t`t"
if ($restrictVal -is [System.Management.Automation.PSCustomObject] -or $restrictVal -is [hashtable]) {
- # Object form: { "field": true, "condition": true, ... }
- foreach ($prop in $restrictVal.PSObject.Properties) {
- if ($prop.Value -eq $true) {
- X "`t`t`t<$($prop.Name)>true$($prop.Name)>"
- }
+ $restrictObj = $restrictVal
+ } elseif ($restrictVal -is [string]) {
+ # Flag-string form: "#noField #noFilter #noGroup #noOrder" (or without `#`)
+ foreach ($tok in ($restrictVal -split '\s+')) {
+ $t = $tok.Trim().TrimStart('#')
+ if ($t) { $restrictTokens += $t }
}
} else {
# Array form: ["noField", "noFilter", ...]
- $restrictMap = @{
- "noField" = "field"; "noFilter" = "condition"; "noCondition" = "condition"
- "noGroup" = "group"; "noOrder" = "order"
- }
- foreach ($r in $restrictVal) {
- $xmlName = $restrictMap["$r"]
- if ($xmlName) { X "`t`t`t<$xmlName>true$xmlName>" }
+ foreach ($r in $restrictVal) { $restrictTokens += "$r" }
+ }
+ }
+ if ($cf.appearance) { $appearance = $cf.appearance }
+ }
+
+ X "`t"
+ X "`t`t$(Esc-Xml $dataPath)"
+ X "`t`t$(Esc-Xml $expression)"
+
+ if ($title) {
+ Emit-MLText -tag "title" -text $title -indent "`t`t"
+ }
+ if ($typeStr) {
+ X "`t`t"
+ Emit-ValueType -typeStr $typeStr -indent "`t`t`t"
+ X "`t`t"
+ }
+ if ($restrictObj -or $restrictTokens.Count -gt 0) {
+ X "`t`t"
+ if ($restrictObj) {
+ foreach ($prop in $restrictObj.PSObject.Properties) {
+ if ($prop.Value -eq $true) {
+ X "`t`t`t<$($prop.Name)>true$($prop.Name)>"
}
}
- X "`t`t"
- }
- if ($cf.appearance) {
- X "`t`t"
- foreach ($prop in $cf.appearance.PSObject.Properties) {
- X "`t`t`t"
- X "`t`t`t`t$(Esc-Xml $prop.Name)"
- X "`t`t`t`t$(Esc-Xml "$($prop.Value)")"
- X "`t`t`t"
+ } else {
+ foreach ($r in $restrictTokens) {
+ $xmlName = $restrictMap["$r"]
+ if ($xmlName) { X "`t`t`t<$xmlName>true$xmlName>" }
}
- X "`t`t"
}
+ X "`t`t"
+ }
+ if ($appearance) {
+ X "`t`t"
+ foreach ($prop in $appearance.PSObject.Properties) {
+ X "`t`t`t"
+ X "`t`t`t`t$(Esc-Xml $prop.Name)"
+ X "`t`t`t`t$(Esc-Xml "$($prop.Value)")"
+ X "`t`t`t"
+ }
+ X "`t`t"
}
X "`t"
diff --git a/.claude/skills/skd-compile/scripts/skd-compile.py b/.claude/skills/skd-compile/scripts/skd-compile.py
index 6c56512b..3d9add72 100644
--- a/.claude/skills/skd-compile/scripts/skd-compile.py
+++ b/.claude/skills/skd-compile/scripts/skd-compile.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
-# skd-compile v1.12 — Compile 1C DCS from JSON
+# skd-compile v1.13 — Compile 1C DCS from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import json
@@ -277,13 +277,46 @@ def parse_param_shorthand(s):
# --- Calculated field shorthand parser ---
def parse_calc_shorthand(s):
- idx = s.find('=')
- if idx > 0:
- return {
- 'dataPath': s[:idx].strip(),
- 'expression': s[idx + 1:].strip(),
- }
- return {'dataPath': s.strip(), 'expression': ''}
+ # Pattern: "Name [Title]: type = Expression #noField #noFilter ...".
+ # - `[Title]` is extracted only from the LHS of '=' so that `[...]` inside
+ # an expression (e.g. index access) isn't interpreted as a title.
+ # - `#restrict` flags use a known-names pattern and are extracted globally —
+ # the docs put them after `=`, and the closed flag set avoids matching
+ # `#word` that happens to appear inside a string literal.
+ restrict_pattern = r'#(noField|noFilter|noCondition|noGroup|noOrder)\b'
+
+ restrict = re.findall(restrict_pattern, s)
+ s = re.sub(r'\s*' + restrict_pattern, '', s)
+
+ eq_idx = s.find('=')
+ if eq_idx > 0:
+ lhs = s[:eq_idx]
+ rhs = s[eq_idx + 1:].strip()
+ else:
+ lhs = s
+ rhs = ''
+
+ title = ''
+ m = re.search(r'\[([^\]]+)\]', lhs)
+ if m:
+ title = m.group(1)
+ lhs = re.sub(r'\s*\[[^\]]+\]', '', lhs)
+ lhs = lhs.strip()
+
+ type_str = ''
+ data_path = lhs
+ if ':' in lhs:
+ colon_idx = lhs.index(':')
+ data_path = lhs[:colon_idx].strip()
+ type_str = resolve_type_str(lhs[colon_idx + 1:].strip())
+
+ return {
+ 'dataPath': data_path,
+ 'expression': rhs,
+ 'type': type_str,
+ 'title': title,
+ 'restrict': restrict,
+ }
# --- DataParameter shorthand parser ---
@@ -623,56 +656,81 @@ def emit_data_set_links(lines, defn):
def emit_calc_fields(lines, defn):
if not defn.get('calculatedFields'):
return
+ restrict_map = {
+ 'noField': 'field', 'noFilter': 'condition', 'noCondition': 'condition',
+ 'noGroup': 'group', 'noOrder': 'order',
+ }
for cf in defn['calculatedFields']:
+ # Collect dataPath/expression/title/type/restrict/appearance from either
+ # shorthand string or object form. Object form accepts dataPath/field/name
+ # as synonyms; useRestriction/restrict accepts object, array, or flag string.
+ title = ''
+ type_str = ''
+ restrict_tokens = []
+ restrict_obj = None
+ appearance = None
+
if isinstance(cf, str):
parsed = parse_calc_shorthand(cf)
- is_obj = False
+ data_path = parsed['dataPath']
+ expression = parsed['expression']
+ title = parsed.get('title', '') or ''
+ type_str = parsed.get('type', '') or ''
+ restrict_tokens = list(parsed.get('restrict') or [])
else:
- parsed = {
- 'dataPath': str(cf.get('dataPath') or cf.get('field', '')),
- 'expression': str(cf.get('expression', '')),
- }
- is_obj = True
-
- lines.append('\t')
- lines.append(f'\t\t{esc_xml(parsed["dataPath"])}')
- lines.append(f'\t\t{esc_xml(parsed["expression"])}')
-
- if is_obj:
+ data_path = str(cf.get('dataPath') or cf.get('field') or cf.get('name') or '')
+ expression = str(cf.get('expression', ''))
if cf.get('title'):
- emit_mltext(lines, '\t\t', 'title', str(cf['title']))
+ title = str(cf['title'])
if cf.get('type'):
- cf_type = resolve_type_str(str(cf['type']))
- lines.append('\t\t')
- emit_value_type(lines, cf_type, '\t\t\t')
- lines.append('\t\t')
- restrict_val = cf.get('restrict') or cf.get('useRestriction')
+ type_str = resolve_type_str(str(cf['type']))
+
+ restrict_val = cf.get('restrict') if cf.get('restrict') is not None else cf.get('useRestriction')
if restrict_val:
- lines.append('\t\t')
if isinstance(restrict_val, dict):
- # Object form: { "field": true, "condition": true, ... }
- for xml_name, flag in restrict_val.items():
- if flag:
- lines.append(f'\t\t\t<{esc_xml(str(xml_name))}>true{esc_xml(str(xml_name))}>')
+ restrict_obj = restrict_val
+ elif isinstance(restrict_val, str):
+ # Flag-string form: "#noField #noFilter #noGroup #noOrder" (or without `#`)
+ for tok in restrict_val.split():
+ t = tok.strip().lstrip('#')
+ if t:
+ restrict_tokens.append(t)
else:
# Array form: ["noField", "noFilter", ...]
- restrict_map = {
- 'noField': 'field', 'noFilter': 'condition', 'noCondition': 'condition',
- 'noGroup': 'group', 'noOrder': 'order',
- }
for r in restrict_val:
- xml_name = restrict_map.get(str(r))
- if xml_name:
- lines.append(f'\t\t\t<{xml_name}>true{xml_name}>')
- lines.append('\t\t')
- if cf.get('appearance'):
- lines.append('\t\t')
- for k, v in cf['appearance'].items():
- lines.append('\t\t\t')
- lines.append(f'\t\t\t\t{esc_xml(k)}')
- lines.append(f'\t\t\t\t{esc_xml(str(v))}')
- lines.append('\t\t\t')
- lines.append('\t\t')
+ restrict_tokens.append(str(r))
+ appearance = cf.get('appearance')
+
+ lines.append('\t')
+ lines.append(f'\t\t{esc_xml(data_path)}')
+ lines.append(f'\t\t{esc_xml(expression)}')
+
+ if title:
+ emit_mltext(lines, '\t\t', 'title', title)
+ if type_str:
+ lines.append('\t\t')
+ emit_value_type(lines, type_str, '\t\t\t')
+ lines.append('\t\t')
+ if restrict_obj or restrict_tokens:
+ lines.append('\t\t')
+ if restrict_obj:
+ for xml_name, flag in restrict_obj.items():
+ if flag:
+ lines.append(f'\t\t\t<{esc_xml(str(xml_name))}>true{esc_xml(str(xml_name))}>')
+ else:
+ for r in restrict_tokens:
+ xml_name = restrict_map.get(str(r))
+ if xml_name:
+ lines.append(f'\t\t\t<{xml_name}>true{xml_name}>')
+ lines.append('\t\t')
+ if appearance:
+ lines.append('\t\t')
+ for k, v in appearance.items():
+ lines.append('\t\t\t')
+ lines.append(f'\t\t\t\t{esc_xml(k)}')
+ lines.append(f'\t\t\t\t{esc_xml(str(v))}')
+ lines.append('\t\t\t')
+ lines.append('\t\t')
lines.append('\t')
diff --git a/.claude/skills/skd-edit/scripts/skd-edit.ps1 b/.claude/skills/skd-edit/scripts/skd-edit.ps1
index 7a8cc25d..7b59bef2 100644
--- a/.claude/skills/skd-edit/scripts/skd-edit.ps1
+++ b/.claude/skills/skd-edit/scripts/skd-edit.ps1
@@ -1,4 +1,4 @@
-# skd-edit v1.10 — Atomic 1C DCS editor
+# skd-edit v1.11 — Atomic 1C DCS editor
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
@@ -251,36 +251,46 @@ function Parse-TotalShorthand {
function Parse-CalcShorthand {
param([string]$s)
- $title = ""
- # Extract [Title] first
- if ($s -match '\[([^\]]+)\]') {
- $title = $Matches[1]
- $s = $s -replace '\s*\[[^\]]+\]', ''
- }
+ # Pattern: "Name [Title]: type = Expression #noField #noFilter ...".
+ # - `[Title]` is extracted only from the LHS of '=' so that `[...]` inside
+ # an expression (e.g. index access) isn't interpreted as a title.
+ # - `#restrict` flags use a known-names pattern and are extracted globally —
+ # the docs put them after `=`, and the closed flag set avoids matching
+ # `#word` that happens to appear inside a string literal.
+ $restrictPattern = '#(noField|noFilter|noCondition|noGroup|noOrder)\b'
- # Extract #restrictions
$restrict = @()
- $restrictMatches = [regex]::Matches($s, '#(\w+)')
- foreach ($m in $restrictMatches) {
+ foreach ($m in [regex]::Matches($s, $restrictPattern)) {
$restrict += $m.Groups[1].Value
}
- $s = [regex]::Replace($s, '\s*#\w+', '')
+ $s = [regex]::Replace($s, "\s*$restrictPattern", '')
- # Support "Name: Type = Expression" and "Name = Expression"
$eqIdx = $s.IndexOf('=')
if ($eqIdx -gt 0) {
- $left = $s.Substring(0, $eqIdx).Trim()
- $expression = $s.Substring($eqIdx + 1).Trim()
-
- if ($left.Contains(':')) {
- $colonIdx = $left.IndexOf(':')
- $dataPath = $left.Substring(0, $colonIdx).Trim()
- $type = Resolve-TypeStr ($left.Substring($colonIdx + 1).Trim())
- return @{ dataPath = $dataPath; expression = $expression; type = $type; title = $title; restrict = $restrict }
- }
- return @{ dataPath = $left; expression = $expression; type = ""; title = $title; restrict = $restrict }
+ $lhs = $s.Substring(0, $eqIdx)
+ $rhs = $s.Substring($eqIdx + 1).Trim()
+ } else {
+ $lhs = $s
+ $rhs = $null
}
- return @{ dataPath = $s.Trim(); expression = ""; type = ""; title = $title; restrict = $restrict }
+
+ $title = ""
+ if ($lhs -match '\[([^\]]+)\]') {
+ $title = $Matches[1]
+ $lhs = $lhs -replace '\s*\[[^\]]+\]', ''
+ }
+ $lhs = $lhs.Trim()
+
+ if ($null -ne $rhs) {
+ if ($lhs.Contains(':')) {
+ $colonIdx = $lhs.IndexOf(':')
+ $dataPath = $lhs.Substring(0, $colonIdx).Trim()
+ $type = Resolve-TypeStr ($lhs.Substring($colonIdx + 1).Trim())
+ return @{ dataPath = $dataPath; expression = $rhs; type = $type; title = $title; restrict = $restrict }
+ }
+ return @{ dataPath = $lhs; expression = $rhs; type = ""; title = $title; restrict = $restrict }
+ }
+ return @{ dataPath = $lhs; expression = ""; type = ""; title = $title; restrict = $restrict }
}
function Parse-ParamShorthand {
diff --git a/.claude/skills/skd-edit/scripts/skd-edit.py b/.claude/skills/skd-edit/scripts/skd-edit.py
index b63ed1d6..9f480516 100644
--- a/.claude/skills/skd-edit/scripts/skd-edit.py
+++ b/.claude/skills/skd-edit/scripts/skd-edit.py
@@ -1,4 +1,4 @@
-# skd-edit v1.10 — Atomic 1C DCS editor (Python port)
+# skd-edit v1.11 — Atomic 1C DCS editor (Python port)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import os
@@ -260,26 +260,42 @@ def parse_total_shorthand(s):
def parse_calc_shorthand(s):
- title = ""
- m = re.search(r'\[([^\]]+)\]', s)
- if m:
- title = m.group(1)
- s = re.sub(r'\s*\[[^\]]+\]', '', s)
+ # Pattern: "Name [Title]: type = Expression #noField #noFilter ...".
+ # - `[Title]` is extracted only from the LHS of '=' so that `[...]` inside
+ # an expression (e.g. index access) isn't interpreted as a title.
+ # - `#restrict` flags use a known-names pattern and are extracted globally —
+ # the docs put them after `=`, and the closed flag set avoids matching
+ # `#word` that happens to appear inside a string literal.
+ restrict_pattern = r'#(noField|noFilter|noCondition|noGroup|noOrder)\b'
- restrict_matches = re.findall(r'#(\w+)', s)
- s = re.sub(r'\s*#\w+', '', s)
+ restrict_matches = re.findall(restrict_pattern, s)
+ s = re.sub(r'\s*' + restrict_pattern, '', s)
eq_idx = s.find("=")
if eq_idx > 0:
- left = s[:eq_idx].strip()
- expression = s[eq_idx + 1:].strip()
- if ":" in left:
- colon_idx = left.index(":")
- data_path = left[:colon_idx].strip()
- type_str = resolve_type_str(left[colon_idx + 1:].strip())
- return {"dataPath": data_path, "expression": expression, "type": type_str, "title": title, "restrict": restrict_matches}
- return {"dataPath": left, "expression": expression, "type": "", "title": title, "restrict": restrict_matches}
- return {"dataPath": s.strip(), "expression": "", "type": "", "title": title, "restrict": restrict_matches}
+ lhs = s[:eq_idx]
+ rhs = s[eq_idx + 1:].strip()
+ has_rhs = True
+ else:
+ lhs = s
+ rhs = ""
+ has_rhs = False
+
+ title = ""
+ m = re.search(r'\[([^\]]+)\]', lhs)
+ if m:
+ title = m.group(1)
+ lhs = re.sub(r'\s*\[[^\]]+\]', '', lhs)
+ lhs = lhs.strip()
+
+ if has_rhs:
+ if ":" in lhs:
+ colon_idx = lhs.index(":")
+ data_path = lhs[:colon_idx].strip()
+ type_str = resolve_type_str(lhs[colon_idx + 1:].strip())
+ return {"dataPath": data_path, "expression": rhs, "type": type_str, "title": title, "restrict": restrict_matches}
+ return {"dataPath": lhs, "expression": rhs, "type": "", "title": title, "restrict": restrict_matches}
+ return {"dataPath": lhs, "expression": "", "type": "", "title": title, "restrict": restrict_matches}
def parse_param_shorthand(s):
diff --git a/tests/skills/cases/skd-compile/calc-object-name-restrict-string.json b/tests/skills/cases/skd-compile/calc-object-name-restrict-string.json
new file mode 100644
index 00000000..254d602a
--- /dev/null
+++ b/tests/skills/cases/skd-compile/calc-object-name-restrict-string.json
@@ -0,0 +1,23 @@
+{
+ "name": "calculatedFields объектная форма с name и строковым useRestriction",
+ "params": { "outputPath": "Template.xml" },
+ "input": {
+ "dataSets": [{
+ "name": "Основной",
+ "query": "ВЫБРАТЬ Т.Номенклатура, Т.Сумма ИЗ Регистр КАК Т",
+ "fields": ["Номенклатура", "Сумма: decimal(15,2)"]
+ }],
+ "calculatedFields": [
+ {
+ "name": "ИмяРесурса",
+ "title": "Имя ресурса",
+ "expression": "\"\"",
+ "useRestriction": "#noField #noFilter #noGroup #noOrder"
+ }
+ ]
+ },
+ "validatePath": "Template.xml",
+ "expect": {
+ "files": ["Template.xml"]
+ }
+}
diff --git a/tests/skills/cases/skd-compile/calc-shorthand-extended.json b/tests/skills/cases/skd-compile/calc-shorthand-extended.json
new file mode 100644
index 00000000..8570dba9
--- /dev/null
+++ b/tests/skills/cases/skd-compile/calc-shorthand-extended.json
@@ -0,0 +1,19 @@
+{
+ "name": "calculatedFields shorthand с [Title], :type, =expr, #restrict",
+ "params": { "outputPath": "Template.xml" },
+ "input": {
+ "dataSets": [{
+ "name": "Основной",
+ "query": "ВЫБРАТЬ Т.Цена, Т.Закупка ИЗ Регистр КАК Т",
+ "fields": ["Цена: decimal(15,2)", "Закупка: decimal(15,2)"]
+ }],
+ "calculatedFields": [
+ "ИмяРесурса [Имя ресурса]: string = \"\" #noField #noFilter #noGroup #noOrder",
+ "Маржа [Маржа]: decimal(15,2) = Цена - Закупка"
+ ]
+ },
+ "validatePath": "Template.xml",
+ "expect": {
+ "files": ["Template.xml"]
+ }
+}
diff --git a/tests/skills/cases/skd-compile/snapshots/calc-object-name-restrict-string/Template.xml b/tests/skills/cases/skd-compile/snapshots/calc-object-name-restrict-string/Template.xml
new file mode 100644
index 00000000..a3e62e1c
--- /dev/null
+++ b/tests/skills/cases/skd-compile/snapshots/calc-object-name-restrict-string/Template.xml
@@ -0,0 +1,72 @@
+
+
+
+ ИсточникДанных1
+ Local
+
+
+ Основной
+
+ Номенклатура
+ Номенклатура
+
+
+ Сумма
+ Сумма
+
+ xs:decimal
+
+ 15
+ 2
+ Any
+
+
+
+ ИсточникДанных1
+ ВЫБРАТЬ Т.Номенклатура, Т.Сумма ИЗ Регистр КАК Т
+
+
+ ИмяРесурса
+ ""
+
+
+ ru
+ Имя ресурса
+
+
+
+ true
+ true
+ true
+ true
+
+
+
+ Основной
+
+
+ ru
+ Основной
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/skills/cases/skd-compile/snapshots/calc-shorthand-extended/Template.xml b/tests/skills/cases/skd-compile/snapshots/calc-shorthand-extended/Template.xml
new file mode 100644
index 00000000..212f0a72
--- /dev/null
+++ b/tests/skills/cases/skd-compile/snapshots/calc-shorthand-extended/Template.xml
@@ -0,0 +1,105 @@
+
+
+
+ ИсточникДанных1
+ Local
+
+
+ Основной
+
+ Цена
+ Цена
+
+ xs:decimal
+
+ 15
+ 2
+ Any
+
+
+
+
+ Закупка
+ Закупка
+
+ xs:decimal
+
+ 15
+ 2
+ Any
+
+
+
+ ИсточникДанных1
+ ВЫБРАТЬ Т.Цена, Т.Закупка ИЗ Регистр КАК Т
+
+
+ ИмяРесурса
+ ""
+
+
+ ru
+ Имя ресурса
+
+
+
+ xs:string
+
+ 0
+ Variable
+
+
+
+ true
+ true
+ true
+ true
+
+
+
+ Маржа
+ Цена - Закупка
+
+
+ ru
+ Маржа
+
+
+
+ xs:decimal
+
+ 15
+ 2
+ Any
+
+
+
+
+ Основной
+
+
+ ru
+ Основной
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+