From 2235b117005d5b2ae615bb127199b8dcc90e8f00 Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Fri, 22 May 2026 17:09:48 +0300 Subject: [PATCH] =?UTF-8?q?chore(skd-compile):=20=D0=BF=D0=BE=D1=80=D1=82?= =?UTF-8?q?=20PS=20=E2=86=92=20PY=20+=20spec=20=D0=B4=D0=BB=D1=8F=20=D0=BF?= =?UTF-8?q?=D0=BE=D1=81=D0=BB=D0=B5=D0=B4=D0=BD=D0=B8=D1=85=20=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D1=88=D0=B8=D1=80=D0=B5=D0=BD=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit В PS-версии накопилось три блока изменений за сессию, которые не были отражены в Python-порте — синхронизирую: - Emit-TableAxisBlock (filter/order/selection/outputParameters на column/row/point/series) - Emit-UserFields (UserFieldExpression / UserFieldCase в settings) DSL spec обновлён: добавлены разделы userFields, расширены примеры table column/row и chart points/series. В SKILL.md изменения не вносятся — фичи редкие, описаны только в spec. Co-Authored-By: Claude Opus 4.7 --- .../skills/skd-compile/scripts/skd-compile.py | 92 ++++++++++++++----- docs/skd-dsl-spec.md | 78 +++++++++++++++- 2 files changed, 147 insertions(+), 23 deletions(-) diff --git a/.claude/skills/skd-compile/scripts/skd-compile.py b/.claude/skills/skd-compile/scripts/skd-compile.py index aa5ee862..0eb42577 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.39 — Compile 1C DCS from JSON +# skd-compile v1.40 — Compile 1C DCS from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import json @@ -1956,6 +1956,68 @@ def parse_structure_shorthand(s): return [] +def emit_user_fields(lines, items, indent): + if not items or len(items) == 0: + return + lines.append(f'{indent}') + for uf in items: + u_type = 'UserFieldCase' if uf.get('cases') is not None else 'UserFieldExpression' + lines.append(f'{indent}\t') + if uf.get('dataPath'): + lines.append(f'{indent}\t\t{esc_xml(str(uf["dataPath"]))}') + if uf.get('title'): + emit_mltext(lines, f'{indent}\t\t', 'dcsset:lwsTitle', uf['title'], no_xsi_type=True) + if u_type == 'UserFieldExpression': + d = uf.get('detail') or {} + if d.get('expression'): + lines.append(f'{indent}\t\t{esc_xml(str(d["expression"]))}') + if d.get('presentation'): + lines.append(f'{indent}\t\t{esc_xml(str(d["presentation"]))}') + t = uf.get('total') or {} + if t.get('expression'): + lines.append(f'{indent}\t\t{esc_xml(str(t["expression"]))}') + if t.get('presentation'): + lines.append(f'{indent}\t\t{esc_xml(str(t["presentation"]))}') + else: + cases = uf.get('cases') or [] + if len(cases) == 0: + lines.append(f'{indent}\t\t') + else: + lines.append(f'{indent}\t\t') + for c in cases: + lines.append(f'{indent}\t\t\t') + if c.get('filter'): + emit_filter(lines, c['filter'], f'{indent}\t\t\t\t') + if c.get('value') is not None: + cv = c['value'] + if isinstance(cv, bool): + lines.append(f'{indent}\t\t\t\t{str(cv).lower()}') + elif isinstance(cv, (int, float)): + lines.append(f'{indent}\t\t\t\t{cv}') + else: + lines.append(f'{indent}\t\t\t\t{esc_xml(str(cv))}') + if c.get('presentation'): + emit_mltext(lines, f'{indent}\t\t\t\t', 'dcsset:lwsPresentationValue', c['presentation'], no_xsi_type=True) + lines.append(f'{indent}\t\t\t') + lines.append(f'{indent}\t\t') + lines.append(f'{indent}\t') + lines.append(f'{indent}') + + +def emit_table_axis_block(lines, block, indent): + """Shared emitter for table column/row and chart point/series.""" + gb = block.get('groupBy') or block.get('groupFields') + emit_group_items(lines, gb, indent) + if block.get('filter'): + emit_filter(lines, block['filter'], indent) + if block.get('order'): + emit_order(lines, block['order'], indent) + if block.get('selection'): + emit_selection(lines, block['selection'], indent) + if block.get('outputParameters'): + emit_output_parameters(lines, block['outputParameters'], indent) + + def emit_structure_item(lines, item, indent): item_type = str(item.get('type', 'group')) @@ -2001,11 +2063,7 @@ def emit_structure_item(lines, item, indent): if item.get('columns'): for col in item['columns']: lines.append(f'{indent}\t') - emit_group_items(lines, col.get('groupBy') or col.get('groupFields'), f'{indent}\t\t') - col_order = col.get('order') or ['Auto'] - emit_order(lines, col_order, f'{indent}\t\t') - col_sel = col.get('selection') or ['Auto'] - emit_selection(lines, col_sel, f'{indent}\t\t') + emit_table_axis_block(lines, col, f'{indent}\t\t') lines.append(f'{indent}\t') # Rows @@ -2014,11 +2072,7 @@ def emit_structure_item(lines, item, indent): lines.append(f'{indent}\t') if row.get('name'): lines.append(f'{indent}\t\t{esc_xml(str(row["name"]))}') - emit_group_items(lines, row.get('groupBy') or row.get('groupFields'), f'{indent}\t\t') - row_order = row.get('order') or ['Auto'] - emit_order(lines, row_order, f'{indent}\t\t') - row_sel = row.get('selection') or ['Auto'] - emit_selection(lines, row_sel, f'{indent}\t\t') + emit_table_axis_block(lines, row, f'{indent}\t\t') lines.append(f'{indent}\t') lines.append(f'{indent}') @@ -2032,21 +2086,13 @@ def emit_structure_item(lines, item, indent): # Points if item.get('points'): lines.append(f'{indent}\t') - emit_group_items(lines, item['points'].get('groupBy') or item['points'].get('groupFields'), f'{indent}\t\t') - pt_order = item['points'].get('order') or ['Auto'] - emit_order(lines, pt_order, f'{indent}\t\t') - pt_sel = item['points'].get('selection') or ['Auto'] - emit_selection(lines, pt_sel, f'{indent}\t\t') + emit_table_axis_block(lines, item['points'], f'{indent}\t\t') lines.append(f'{indent}\t') # Series if item.get('series'): lines.append(f'{indent}\t') - emit_group_items(lines, item['series'].get('groupBy') or item['series'].get('groupFields'), f'{indent}\t\t') - sr_order = item['series'].get('order') or ['Auto'] - emit_order(lines, sr_order, f'{indent}\t\t') - sr_sel = item['series'].get('selection') or ['Auto'] - emit_selection(lines, sr_sel, f'{indent}\t\t') + emit_table_axis_block(lines, item['series'], f'{indent}\t\t') lines.append(f'{indent}\t') # Selection (chart values) @@ -2108,6 +2154,10 @@ def emit_settings_variants(lines, defn): return str(s[prop]) return None + # userFields — пользовательские вычисляемые поля (Expression / Case) + if s.get('userFields'): + emit_user_fields(lines, s['userFields'], '\t\t\t') + # Selection if s.get('selection'): emit_selection(lines, s['selection'], '\t\t\t', skip_auto=True, block_view_mode=_block_vm('selection')) diff --git a/docs/skd-dsl-spec.md b/docs/skd-dsl-spec.md index 2c58851b..6332fab1 100644 --- a/docs/skd-dsl-spec.md +++ b/docs/skd-dsl-spec.md @@ -494,6 +494,7 @@ XML-маппинг — по `` на каждый элемент: "name": "Основной", "presentation": "Основной вариант", "settings": { + "userFields": [...], "selection": [...], "filter": [...], "order": [...], @@ -780,22 +781,95 @@ XML-маппинг — по `` на каждый элемент: { "groupBy": ["Номенклатура"], "selection": ["Auto"], "order": ["Auto"] } ], "columns": [ - { "groupBy": ["Период"], "selection": ["Auto"], "order": ["Auto"] } + { + "groupBy": ["Период"], + "filter": ["Сумма > 0"], + "selection": ["Auto"], + "order": ["Auto"], + "outputParameters": { "РасположениеИтогов": "None" } + } ] } ``` +Каждая `column`/`row` принимает те же поля что и `group`: `groupBy`/`groupFields`, `filter`, `order`, `selection`, `outputParameters`. + #### Диаграмма (chart) ```json { "type": "chart", - "points": { "groupBy": ["Организация"], "order": ["Auto"] }, + "points": { "groupBy": ["Организация"], "order": ["Auto"], "filter": [...] }, "series": { "groupBy": ["Месяц"], "order": ["Auto"] }, "selection": ["Сумма"] } ``` +`points` и `series` принимают те же поля что table column/row. + +### userFields (пользовательские вычисляемые поля) + +Дополнительные поля, которые пользователь может задать в режиме «Изменить вариант» через UI. Хранятся в settings варианта. Два подтипа определяются по содержимому объекта: + +**Expression-форма** — поле вычисляется выражением (опционально с разделением для детальных строк и для итогов): + +```json +"userFields": [ + { + "dataPath": "ПользовательскиеПоля.Поле1", + "title": { "ru": "Отработано дней", "en": "Days worked" }, + "detail": { + "expression": "Выбор Когда Группа = ... Тогда ОтработаноДней Иначе 0 Конец", + "presentation": "Выбор Когда Группа = ... Тогда [Отработано дней] Иначе 0 Конец" + }, + "total": { + "expression": "Сумма(Выбор Когда Группа = ... Тогда ОтработаноДней Иначе 0 Конец)", + "presentation": "Сумма(Выбор Когда Группа = ... Тогда [Отработано дней] Иначе 0 Конец)" + } + } +] +``` + +| Поле | Описание | +|------|----------| +| `dataPath` | Путь поля в формате `ПользовательскиеПоля.ПолеN` | +| `title` | Заголовок (строка или multilang dict) | +| `detail.expression` | Выражение для детальных записей | +| `detail.presentation` | Тот же expression с подстановкой `[Имя поля]` (для UI) | +| `total.expression` | Выражение для итоговой строки | +| `total.presentation` | Same для UI | + +**Case-форма** — поле принимает разные значения в зависимости от условий: + +```json +"userFields": [ + { + "dataPath": "ПользовательскиеПоля.Поле1", + "title": { "ru": "Вид продаж" }, + "cases": [ + { + "filter": ["ХозОперация <> Перечисление.ХозяйственныеОперации.РеализацияВРозницу"], + "value": 2, + "presentation": { "ru": "Только оптовые продажи", "en": "Wholesale only" } + }, + { + "filter": ["ХозОперация = Перечисление.ХозяйственныеОперации.РеализацияВРозницу"], + "value": 3, + "presentation": { "ru": "Только розничные продажи", "en": "Retail only" } + } + ] + } +] +``` + +| Поле | Описание | +|------|----------| +| `cases[].filter` | Условие (как в settings filter) | +| `cases[].value` | Значение поля если условие выполнено (типы автоопределяются: bool/decimal/string) | +| `cases[].presentation` | Текст значения для UI (multilang) | + +Тип элемента определяется автоматически: наличие `cases` → `UserFieldCase`, иначе → `UserFieldExpression`. + ### viewMode (режим доступности) `viewMode` управляет доступностью элемента в **пользовательских настройках** отчёта («Изменить вариант…» / «Настройки»). Возможные значения: