Compare commits

..

256 Commits

Author SHA1 Message Date
Nick Shirokov cd3e50c408 docs(skd-guide): добавить /skd-decompile и сценарий «по образцу»
Дополняем гайд группы skd-*:
- В таблицу навыков добавлена строка /skd-decompile с пометкой об
  отключённом автоподборе моделью.
- В блок «Рабочий цикл» нарисована обратная стрелка Template.xml →
  /skd-decompile → JSON DSL.
- Новый под-раздел «Когда /skd-decompile, а когда /skd-edit» с явным
  предупреждением о неполноте преобразования и тихих потерях.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 14:37:11 +03:00
Nick Shirokov da6ac2bab8 Merge branch 'skd-decompile' into dev 2026-05-25 13:08:37 +03:00
Nick Shirokov 7a7d03dcff docs(skd-decompile): причёсываем SKILL.md перед merge
- description короче и с явным «не для точечных правок»
- унифицируем терминологию (sentinel-узлы)
- расширяем «Когда не использовать» — почему /skd-edit лучше для адресных правок
- в критичные конструкции добавляем вложенные схемы

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 13:07:53 +03:00
Nick Shirokov 20a243143a fix(skd-decompile): убрать падения на ERP-отчётах с dataSetLink и StandardPeriod без companions
Два бага PS-версии, незаметно роняли 9/30 отчётов sample30 в decompile-fail:

1. Get-Text без xpath → SelectSingleNode("", $ns) с .NET XPathException
   "Результатом выражения должен быть NodeSet". Шесть call-sites в
   dataSetLinks (sourceDataSet/destinationDataSet/sourceExpression/...)
   передавали уже-выбранный узел без второго аргумента; [string]$xpath
   дефолтился в "". Фикс: Get-Text возвращает $node.InnerText, если
   xpath пустой.

2. $paramByName[$startMatch] при $startMatch=$null → "индекс массива
   вычислен как NULL". Возникает на StandardPeriod-параметре, для
   которого в отчёте нет companion expressions. Фикс: guard через if.

Python-порт #2 уже был защищён .get(); по #1 в py был обходной костыль
inline-через-inner_text — заменён на единый get_text(node) после
обновления сигнатуры до get_text(node, xpath=None).

verify-roundtrip sample30: 9 bit-perfect + 20 with-diff + 1 ring3 = 30
(до фикса 9 silently падали как decompile-fail, сумма не сходилась).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 12:29:06 +03:00
Nick Shirokov fea2f37ba6 feat(skd-decompile): Python-порт зеркалом PS v0.88
Зеркало skd-decompile.ps1 v0.88 (~3022 строки) → skd-decompile.py v0.88
(~3140 строк). По образцу пары skd-compile.ps1 ↔ skd-compile.py.

- runner.mjs --filter skd-decompile --runtime python: 16/16 зелёные
- runner.mjs --filter skd-decompile (PS, регрессия): 16/16
- runner.mjs --filter skd-compile --runtime python (регрессия): 23/23
- verify-roundtrip на titan2-subset (13 отчётов): PS ≡ Py байт-в-байт
- verify-roundtrip на sample30 (20 общих отчётов): тот же распред
  8 BP + 12 diff, у Py чуть меньше diff-строк на edge-кейсах empty
  multilang content

Нетривиальные места порта:
- ET в Python не понимает prefix-aware XPath → тонкая обёртка XNode +
  ручной _xpath_steps/_all/_single для PS-style путей
- ET.Element (C-impl) не позволяет навешивать атрибуты → per-element
  nsmap хранится во внешнем _NSMAP_BY_ID[id(el)], заполняется через
  iterparse + start-ns
- JSON-сериализатор (convert_to_compact_json, try_inline_json,
  lineLimit=400, inline-when-fits) портирован 1-в-1

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 12:15:19 +03:00
Nick Shirokov d8457bb307 docs(skd-dsl-spec): актуализация под session-фичи
- value параметра может быть массивом (для valueListAllowed)
- расшифровка namespace-ов цветов: style: (палитра темы), web: (web-имена),
  win: (Windows-системные)
2026-05-24 21:28:30 +03:00
Nick Shirokov daa7716f24 fix(skd-decompile): не сохранять valueType для известных outputParameters keys
Compile имеет map outputParamTypes — для известных ключей (Заголовок,
ВыводитьПараметрыДанных, РасположениеИтогов и др.) тип эмитится
автоматически по имени параметра. Decompile же всегда оборачивал в
wrapper {value, valueType} для не-xs:* типов — лишний шум.

Зеркалирован тот же map в decompile (15 keys), при чтении проверяем
совпадение fullType с map → если auto-detect compile вернёт тот же
тип, валидируем как простое значение.

JSON outputParameters стал заметно компактнее. Эффект на diff
нейтральный (compile эмитит то же).
2026-05-24 21:25:34 +03:00
Nick Shirokov bbed308c85 fix(skd-decompile): не сохранять valueType если auto-detect compile даст тот же тип
Для filter right values с типом dcscor:DesignTimeValue, чьё значение
соответствует ref-pattern (Перечисление.X.Y / Справочник.X.Y / ПланСчетов.X.Y
и др.), compile auto-detect-ит DesignTimeValue из значения. Раньше
decompile всё равно сохранял valueType явно — лишний шум в DSL.

Применено к single-right и multi-right. Эффект на diff нулевой (compile
эмитит то же самое), но decompiled JSON становится компактнее и читабельнее.
2026-05-24 21:17:03 +03:00
Nick Shirokov 11ddc2b5a2 fix(skd-compile): single-line эмиссия <DataCompositionSchema xmlns=...>
Раньше эмитили шапку схемы в 8 строк (каждый xmlns на отдельной строке).
Оригинал платформы пишет всё в одну строку. Был с самого начала
существования skd-compile — не регрессия, но косметика на каждый отчёт
давала 9 строк diff (1 LOST + 8 ADDED).

Sample30 total: 458 → 238 строк diff.
2026-05-24 21:09:34 +03:00
Nick Shirokov e59c3281fd feat(skd): различать 3 namespace цветов (style/web/win)
Раньше decompile Normalize-Color сворачивал любой dN: префикс в style:
независимо от URI. Compile Emit-ColorValue знал только style: → один xmlns
(http://v8.1c.ru/8.1/data/ui/style). Цвета web:GhostWhite и win:Highlight
терялись/искажались.

URI mapping:
- http://v8.1c.ru/8.1/data/ui/style          → style:
- http://v8.1c.ru/8.1/data/ui/colors/web     → web:
- http://v8.1c.ru/8.1/data/ui/colors/windows → win:

decompile: резолвим prefix через GetNamespaceOfPrefix.
compile (PS+Py): Emit-ColorValue для template cells — long form с
локальным xmlns. Emit-AppearanceValue (внутри settings) — short form,
т.к. <dcsset:settings> уже объявляет xmlns:style/web/win/sys на корне.

См. upload/erf/ЦветаВМакете — образец с 4 формами цвета.
2026-05-24 20:57:12 +03:00
Nick Shirokov 609698b00d feat(skd): multi-value параметры (valueListAllowed список значений по умолчанию)
Параметр с valueListAllowed=true может иметь несколько <value> элементов
подряд — список значений по умолчанию (например список счетов плана).
Раньше decompile читал только первый (SelectSingleNode), compile эмитил
один <value> → терялись все остальные значения.

DSL: value в параметре может быть массивом строк (или native bool/int/double).

decompile: SelectNodes("r:value") → если >1 элемент, value = array.
compile (PS+Py): если value — array, эмитим каждый отдельным <value>
через Emit-ParamValue.

Альт-indent корпус: 18009 → 17946 строк diff.
2026-05-24 20:38:10 +03:00
Nick Shirokov ceaaa8bc55 fix(skd-decompile): не сворачивать @autoDates companions с availableAsField=false
Companions НачалоПериода/КонецПериода имеют canonical имена/expression в
некоторых корпусах, но дополнительно несут availableAsField=false
(поле-ограничение, не участвует в выборе). Наш @autoDates auto-generator
не передаёт этот атрибут — для совместимости с другими корпусами без него.

Решение в decompile: не сворачивать в @autoDates если хотя бы один
companion имеет availableAsField=false → companions остаются явными
параметрами, compile эмитит их со всеми атрибутами через стандартный
path.

Sample30: 607 → 458 строк diff (-149).
2026-05-24 20:27:21 +03:00
Nick Shirokov 61cc8f3b9a fix(skd-decompile): сохранять useRestriction=true на не-hidden/не-autoDates параметрах
Раньше параметр с useRestriction=true (без hidden/autoDates/expression/
других object-form-триггеров) сохранялся как shorthand string —
useRestriction в shorthand не выражается, и compile эмитил default
useRestriction=false, теряя ограничение.

Render-Parameter: добавлен тригер object form для случая
useRestriction=true. Объект уже корректно эмитил это поле.
2026-05-24 20:07:07 +03:00
Nick Shirokov 4630af463f feat(skd-decompile): fail-fast (Ring 3) для отчётов без dataSet
Служебные отчёты-обёртки содержат только settingsVariant с outputParameters
(МакетОформления и подобное) и используются для динамического заполнения
из кода. Compile требует ≥1 dataSet, и весь DSL заточен под data-driven
отчёты — раньше decompile проходил, но compile падал с exit 1.

Теперь decompile fail-fast (exit 3, ring3 skip) — это правильнее
классифицирует такие отчёты в verify-roundtrip как "не поддерживается"
вместо "compile сломался".

См. C:/WS/projects/titan/src/cfe/Титан2/Reports/
ккСправкаРасчетРезервыПоСомнительнымДолгам.
2026-05-24 19:12:11 +03:00
Nick Shirokov dd02dcf3c4 feat(skd): TypeSet (композитный тип-набор) в valueType параметра
Параметры типа «исключаемые документы» имеют valueType с
<v8:TypeSet xmlns:dN="...">dN:DocumentRef</v8:TypeSet> — указывает на
все ссылки указанного класса конфигурации, а не на конкретный объект.

Раньше теряли целиком: decompile читал только <v8:Type>, compile
эмитил голое имя как <v8:Type>DocumentRef</v8:Type> (что не валидно).

DSL — голое имя ref-класса без точки (CatalogRef, DocumentRef, EnumRef,
ChartOfAccountsRef, ChartOfCharacteristicTypesRef, ChartOfCalculationTypesRef,
BusinessProcessRef, TaskRef, ExchangePlanRef, InformationRegisterRef,
AnyRef) → TypeSet. С точкой (DocumentRef.X) — конкретный Ref как было.

decompile: Get-ValueTypeShorthand читает v8:TypeSet и сохраняет
local-name (после prefix:).
compile (PS+Py): Emit-SingleValueType распознаёт голое имя из набора и
эмитит <v8:TypeSet xmlns:d5p1=...>d5p1:NAME</v8:TypeSet>.

Sample30 total: 618 → 607 строк diff.
2026-05-24 18:51:46 +03:00
Nick Shirokov e2e3e02a1b fix(skd): сохранять явные startDate/endDate в top-level StandardPeriod parameter
Параметр типа StandardPeriod с variant=Custom может иметь явные даты,
отличающиеся от default 0001-01-01T00:00:00 (например endDate=23:59:59).
См. АнализПричинБлокировкиВычетаНДС @941.

Раньше decompile сохранял variant только если ≠Custom (для Custom —
ничего), и compile эмитил захардкоженные 0001-01-01T00:00:00 для обеих
дат. Теряли явное время.

decompile: для StandardPeriod с явными датами (любая ≠00:00:00) →
object form {variant: Custom, startDate, endDate}.
compile (PS+Py): Emit-ParamValue принимает dict val с variant/startDate/
endDate; для Custom без явных дат сохраняется текущий fallback на
0001-01-01T00:00:00.

Sample30 total: 620 → 618 строк diff.
2026-05-24 18:42:58 +03:00
Nick Shirokov 9b331aa41d feat(skd): user-settings + axis-viewMode + use=false на StructureItemTable/Chart
Раньше для StructureItemTable читали только viewMode/userSettingID/
userSettingPresentation/itemsViewMode, а для StructureItemChart — вовсе
ничего из user-settings. Также не поддерживали axis-level режим
доступности секций (columnsViewMode/rowsViewMode/pointsViewMode/
seriesViewMode) и use=false на самих table/chart.

Расширено:
- table: + use=false, + columnsViewMode, + rowsViewMode
- chart: + use=false, + viewMode, + userSettingID, + userSettingPresentation,
         + itemsViewMode, + pointsViewMode, + seriesViewMode

Все эти атрибуты эмитятся платформой как direct children самой item-ноды
после rows/columns (table) или points/series (chart). DSL — простые поля
прямо на table/chart-объекте (как у table уже было для viewMode/etc).

Sample30 total: 729 → 620 строк diff (-109).
2026-05-24 18:23:36 +03:00
Nick Shirokov 91ef1d07eb feat(skd): v8ui:Line + nested side-styles в appearance
conditionalAppearance может содержать СтильГраницы со сложным value:
<dcscor:value xsi:type="v8ui:Line" width="0" gap="false">
  <v8ui:style xsi:type="v8ui:SpreadsheetDocumentCellLineType">None</v8ui:style>
</dcscor:value>
+ nested <dcscor:item> для side-стилей (СтильГраницы.Сверху/.Снизу/.Слева/.Справа),
каждый со своим v8ui:Line value и опц. <dcscor:use>false</dcscor:use>.

Раньше теряли всю структуру и эмитили <value xsi:type="xs:string">None</value>.

DSL form B (выбранный пользователем) — Line как top-level плоский объект:
"СтильГраницы": {
  "@type": "Line", "width": 0, "gap": false, "style": "None",
  "items": {
    "СтильГраницы.Сверху": {
      "value": { "@type": "Line", "width": 1, "gap": false, "style": "Solid" },
      "use": false
    }
  }
}

Nested items — универсальный wrapper {value, use?, items?} (как у outputParameters).
Эмитятся как siblings <dcscor:item> внутри родительского <dcscor:item> (после
закрытия родительского <dcscor:value>).

decompile: Read-AppearanceValueNode распознаёт Line и возвращает inline объект;
Get-SettingsAppearance читает nested dcscor:item children и собирает их в items.
compile (PS+Py): emit_appearance_value расширен — Line ветка + рекурсивный
вызов для items siblings.

Sample30 total: 767 → 729 строк diff (-38).
2026-05-24 18:10:25 +03:00
Nick Shirokov 8cb7309ee5 fix(skd-decompile): сохранять <selection>Auto</selection> на StructureItemChart
Раньше пропускали top-level selection в chart если она содержит только
SelectedItemAuto (считая дефолтом). Но оригинал платформы всегда эмитит
блок явно — для bit-perfect нужно сохранять presence (по аналогии с
table axis и structure-group, где selection presence уже сохраняется).

Sample30 total: 782 → 767 строк diff.
2026-05-24 17:53:11 +03:00
Nick Shirokov 0425b79a87 fix(skd-compile): не эмитить пустую <dcsat:appearance/> для cells без атрибутов
Cells со style "none" (без цветов/шрифтов/границ) и без width/merge
получали пустой блок <dcsat:appearance></dcsat:appearance>. Оригинал
платформы для таких cells appearance не пишет вовсе.

Добавлен ранний return в Emit-CellAppearance если все атрибуты пустые.

PS + Py синхронизированы. Sample30 total: 794 → 782 строки.
2026-05-24 17:49:30 +03:00
Nick Shirokov c8cba6f7ce feat(skd): локальный xmlns + use=false на nested sub-items outputParameters
В chart-параметрах типа ТипДиаграммы.СоединениеЗначенийПоСериям platform
эмитит nested SettingsParameterValue с:
- <dcscor:use>false</dcscor:use> (sub-параметр отключён)
- <dcscor:value xmlns:dN="http://v8.1c.ru/8.2/data/chart" xsi:type="dN:X">
  (локальный xmlns на value — префикс не объявлен в корне схемы)

Раньше теряли оба:
- decompile не читал sub.use и не резолвил xmlns кастомного xsi:type
- compile эмитил value без xmlns и без use=false

decompile: читаем dcscor:use на sub-item, резолвим prefix→URI через
GetNamespaceOfPrefix; если URI не из стандартных корневых xmlns —
сохраняем valueType как объект {uri, name}.
compile (PS+Py): новый helper Emit-OutputParametersSubItem; если
valueType — объект, эмитим xmlns:dN=<uri> + xsi:type=dN:<name>;
+ <dcscor:use>false</dcscor:use> если sub.use === false.

Sample30 total: 809 → 794 строк diff.
2026-05-24 17:41:44 +03:00
Nick Shirokov f34303f9ed docs(skd-dsl-spec): актуализация под фичи текущей сессии
Откатываем расширение SKILL.md (детальные формы расшифровки —
редкая необходимость) и переносим документацию в spec:

- selection folder placement (Horizontally/Vertically/...)
- groupBy field object-form: periodAdditionType +
  periodAdditionBegin/End (auto-detect xs:dateTime vs dcscor:Field)
- chart multi-series/multi-points (points/series как массив)
- template parameters drilldown форма A/B/C
- cell object-form { value, drilldown } override
- fieldTemplates секция
- choiceParameters values native bool/int/double/string
- filter valueType работает и для массива value
- dataParameters valueType=xs:string как nil-placeholder для use=false
2026-05-24 17:33:33 +03:00
Nick Shirokov c230142bf1 feat(skd): form C drilldown + per-cell drilldown override + fieldTemplates
Расширение DSL для area-templates под отчёты с расшифровкой через
data-параметр (типа АнализВыполненияМаршрутныхЛистов в ERP).

Раньше шорткат `drilldown: "X"` всегда генерировал
`Расшифровка_X` + `ИмяРесурса`/`DrillDown`. Появилось три формы:

- A: { name, expression }                              — без drilldown
- B: { name, expression, drilldown: "X" }              — текущий shortcut (без изменений)
- C: { name, drilldown: { field, expression, action } } — самостоятельный
                                                          DetailsAreaTemplateParameter

Per-cell override: ячейка может быть object `{ value, drilldown }` —
тогда appearance ссылается на указанный параметр расшифровки (без
префикса `Расшифровка_`). Используется когда несколько ячеек должны
указывать на один details-параметр (МаршрутныйЛист).

Новая корневая секция:
  "fieldTemplates": [{ "field": "X", "template": "Макет1" }]
— XML <fieldTemplate><field/><template/></fieldTemplate>.

compile (PS+Py): новый helper Emit-AreaTemplateParameter, ветка form C
в Emit-AreaTemplateDSL/Emit-Templates raw-mode, Emit-FieldTemplates.
Cell rendering: проверка cell.drilldown override перед drilldownMap.

decompile (PS): Build-Template переписан — собирает все
DetailsAreaTemplateParameter в map; распознаёт shortcut form B по
canonical shape (Расшифровка_<Y>/ИмяРесурса/"<Y>"/DrillDown) И наличию
cell appearance ref на этот target; не-shortcut → form C entry.
Cell wrapping: если drilldownTarget ≠ shortcut Расшифровка_Y, ячейка
оборачивается в { value, drilldown }. Plus Build-FieldTemplates.

Размер diff на sample30: 1045 → 809 строк (-22.5% за сессию).
2026-05-24 17:25:46 +03:00
Nick Shirokov d9010cd580 feat(skd): multi-series и multi-point в StructureItemChart
Диаграмма может содержать несколько <dcsset:series> (и аналогично
<dcsset:point>) — каждая со своим groupItems, filter, order, selection,
viewMode, userSettingID и userSettingPresentation. Используется для
multi-series графиков с группировкой по фильтру (например,
АнализЛояльностиКлиентовXYZ — series по СтадияОтношений: Постоянный,
Разовый, Потенциальный, Потерянный клиент).

Раньше декомпилировали только первый блок (SelectSingleNode), теряя
остальные → −113 строк diff на одном этом отчёте (134 → 21).

decompile: SelectNodes → если >1, сохраняем как массив; single → object
(backward-compat).
compile: проверяем тип points/series — array → цикл, иначе single
блок (backward-compat для существующего DSL).

PS и Py compile синхронизированы.
2026-05-24 16:18:16 +03:00
Nick Shirokov 19da4df61f feat(skd): periodAdditionBegin/End с типом dcscor:Field
GroupItemField может иметь periodAdditionBegin/End:
- xsi:type="xs:dateTime" с конкретной датой
- xsi:type="dcscor:Field" с путём к параметру/полю (например
  "ПараметрыДанных.ДатаНачала"). См. АнализДоходовРасходов @8933.

Раньше эмитили захардкоженный default xs:dateTime 0001-01-01T00:00:00,
теряя любые non-default значения.

decompile: читаем оба элемента, сохраняем как string поля
periodAdditionBegin/End в object form group field (если non-default).
compile: auto-detect типа по pattern (ISO date → xs:dateTime,
иначе → dcscor:Field).

PS и Py compile синхронизированы.
2026-05-24 16:10:15 +03:00
Nick Shirokov fe9d8500dc feat(skd): placement в SelectedItemFolder (selection)
SelectedItemFolder (группа полей с заголовком в selection) имеет
<dcsset:placement> — может быть Auto/Horizontally/Vertically/Special.
Мы захардкоженно эмитили Auto, теряя non-default значения.

decompile: читаем placement, сохраняем если ≠ Auto.
compile: эмитим из item.placement (default Auto для bit-perfect-default).

PS и Py compile синхронизированы.
2026-05-24 15:59:25 +03:00
Nick Shirokov 2ad35f484c fix(skd): empty xs:string placeholder в SettingsParameterValue use=false
Параметр типа DateTime в dataParameters внутри settings с use=false и
без значения сохраняется оригиналом как <dcscor:value xsi:type="xs:string"/>
(пустой placeholder), а не xsi:nil. См. АнализПлановыхНачислений @1506
для ОкончаниеПериода.

decompile: detect-ит empty xs:string + use=false → object form
{parameter, use:false, value:'', valueType:'xs:string'}.
compile: Emit-EmptyValue нормализует префикс xs: (xs:string → ^string),
теперь корректно эмитит пустой xs:string вместо fallback xsi:nil.

PS и Py синхронизированы.
2026-05-24 15:56:28 +03:00
Nick Shirokov abca61da66 fix(skd-decompile): сохранять xsi:type в multi-right filter values
При чтении FilterItemComparison с несколькими <right> элементами теряли
xsi:type — все значения уходили как строки, и compile эмитил DesignTimeValue
по auto-detect для строк вида "Перечисление.*". Но оригинал может хранить
эти значения как xs:string (см. АнализНачисленийИУдержаний @22718).

Теперь читаем xsi:type каждого <right>; если все одинаковые — сохраняем
в valueType. Compile уже умеет использовать переданный valueType вместо
auto-detect.
2026-05-24 15:43:41 +03:00
Nick Shirokov db6a1f2212 feat(skd): bool/numeric values в inputParameters choiceParameters
Раньше все values в <dcscor:value xsi:type="dcscor:ChoiceParameters">
эмитились как DesignTimeValue. Оригинал использует xs:boolean для bool-флагов
(пример: Отбор.ОбособленноеПодразделение=false в ChoiceParameters элементе
inputParameters поля ДанныеОрганизации).

decompile: читает тип xsi из <dcscor:value> и конвертит в JSON-native
(true/false для boolean, int/double для decimal, иначе строка).
compile: при эмите inputParameters → choiceParameters → values детектирует
тип значения и эмитит соответствующий xsi:type.

PS и Py синхронизированы.
2026-05-24 15:37:07 +03:00
Nick Shirokov 85d42ec34c docs(skd-dsl-spec): догон по фичам третьей сессии bit-perfect round-trip
Добавлено:
- outputParameters wrapper {value, valueType, use, items, viewMode, USID, USP}
- v8ui:Font в appearance — @type:Font + атрибуты
- dataParameters: valueType, nilValue
- StandardPeriod/StandardBeginningDate shape inference (без @type marker)
- selection/filter/order/CA UserSettingID на settings
- Пустые блоки SF/F/O/CA с только block-level meta
- inputParameters value valueType {uri, name} для кастомных xsi:type
- availableValues — типы значений сохраняются нативно
- itemsViewMode на column/row/table
- nilValue marker для параметров
- StructureItemGroup short form внутри table axis (платформ-паттерн)
2026-05-23 22:37:55 +03:00
Nick Shirokov a7344a1397 feat(skd): StandardBeginningDate в dataParameters через shape inference
Раньше StandardBeginningDate (используется когда top-level param имеет
тип xs:dateTime + UI выбор "Начало дня"/"Custom") декомпилировалась
через InnerText: variant+date конкатенировались в "Custom2022-10-25..."
и compile эмитил как xs:string.

Cross-reference на корпусе (671 отчёт):
  608 v8:StandardPeriod → v8:StandardPeriod
  180 xs:dateTime       → v8:StandardBeginningDate
  120 xs:dateTime       → xs:dateTime (raw)

Decompile теперь сохраняет SBD как объект {variant, date} (без @type marker).
Compile различает SP/SBD по форме value:
  {variant, date}                 → SBD
  {variant, startDate, endDate}   → SP с датами
  {variant} only                  → инференс по имени (BeginningOf* → SBD)

В корпусе ни одного ambiguous (variant=Custom без полей) не существует:
все 33 SBD/Custom имеют date, все 93 SP/Custom — startDate/endDate.

sample30: −22 строки (932 → 910).
2026-05-23 22:29:34 +03:00
Nick Shirokov f642f673d9 fix(skd-compile): не пропускать Auto-items в top-level selection/order
Platform может содержать SelectedItemAuto/OrderItemAuto в top-level
<dcsset:selection>/<dcsset:order> (рядом с явными полями) — это валидно.
Раньше compile использовал -skipAuto на top-level, теряя эти items.

Снапшоты регенерированы.

sample30: −10 строк (942 → 932).
2026-05-23 22:06:03 +03:00
Nick Shirokov fdc8c518aa fix(skd-compile): эмитить пустые selection/filter/order/CA блоки если есть block-level meta
Раньше Emit-Selection/Filter/Order/ConditionalAppearance early-return при
пустых items, даже если decompile сохранил blockViewMode/blockUserSettingID.
Caller (settingsVariant) тоже не вызывал эти функции на пустых items.

Теперь блоки эмитятся когда есть items ИЛИ block-level meta. Реальный кейс
из ERP: <dcsset:conditionalAppearance><dcsset:viewMode>Normal</dcsset:viewMode>
</dcsset:conditionalAppearance> — пустой CA блок только с viewMode.

sample30: −84 строки (1026 → 942).
2026-05-23 22:00:51 +03:00
Nick Shirokov 53536b72f5 fix(skd-compile): short form <dcsset:item> для StructureItemGroup внутри row/column
Анализ корпуса ERP (671 отчёт): items внутри dcsset:row/column/points/series
ВСЕГДА используют короткую форму <dcsset:item> без xsi:type (531 случай,
0 explicit). В остальных контекстах — explicit <dcsset:item xsi:type="dcsset:StructureItemGroup">.

Emit-StructureItem получил switch -shortGroup, который Emit-TableAxisBlock
передаёт для nested children. Флаг наследуется recursive в детей через
Emit-StructureItem рекурсию.

Снапшоты регенерированы.

sample30: −166 строк (1192 → 1026).
2026-05-23 21:46:27 +03:00
Nick Shirokov 21ae9a6d80 Revert "fix(skd-validate): принимать <dcsset:item> без xsi:type как StructureItemGroup"
This reverts commit 3ef4f44028.
2026-05-23 21:30:26 +03:00
Nick Shirokov ad99f3db0b fix(skd-compile): startDate/endDate в StandardPeriod ТОЛЬКО для variant=Custom
Анализ корпуса ERP/БСП (671 отчёт): из 635 StandardPeriod values только
93 (все Custom) имели <v8:startDate>/<v8:endDate>. Остальные 542 варианта
(ThisMonth, LastYear, Today, ThisQuarter, FromBeginningOfThisYear и т.д.)
эмитятся БЕЗ дат — это canonical platform-pattern.

Раньше compile добавлял boilerplate 0001-01-01 даты ко всем вариантам
независимо от типа. Снапшоты регенерированы.

sample30: −138 строк (1330 → 1192).
2026-05-23 21:24:42 +03:00
Nick Shirokov 3ef4f44028 fix(skd-validate): принимать <dcsset:item> без xsi:type как StructureItemGroup
Platform эмитит StructureItemGroup как короткую форму <dcsset:item> без
xsi:type (это дефолтный тип). Раньше валидатор выдавал error на любой
реальный XML с такой формой. Теперь — отсутствие xsi:type трактуется
как dcsset:StructureItemGroup (с warning только для нестандартных типов).
2026-05-23 21:16:43 +03:00
Nick Shirokov bb7696bf28 fix(skd-decompile): StandardPeriod в object-form dataParameter эмитим как {variant}
Когда dataParameter имеет viewMode/userSettingPresentation и значение —
StandardPeriod без явных дат (просто вариант "Custom"/"ThisMonth"),
decompile сохранял value как плоскую строку "Custom" + valueType=v8:StandardPeriod.
Compile, видя valueType с префиксом, эмитил <value xsi:type="v8:StandardPeriod">Custom</value>
— плоский xs:string-like тег вместо StandardPeriod-блока с <v8:variant>.

Теперь когда object-form нужен по другим причинам (viewMode/USP),
StandardPeriod-вариант сохраняется как {variant: ...}, и compile
правильно генерирует <v8:variant xsi:type="v8:StandardPeriodVariant">.

sample30: −66 строк (1396 → 1330).
2026-05-23 21:01:59 +03:00
Nick Shirokov 0466ae8fd8 feat(skd-compile): @autoDates companions использует type=dateTime (канон БСП)
Раньше @autoDates генерировал НачалоПериода/КонецПериода с type=date →
DateFractions=Date. Реальный БСП паттерн использует DateTime (платформа сама
приводит конец периода к 23:59:59 для дат без времени, но DateTime более
явное и матчит шаблоны БСП).

Снапшоты регенерированы.
2026-05-23 20:54:48 +03:00
Nick Shirokov 796403abe3 fix(skd-decompile): fold @autoDates только для канонических имён НачалоПериода/КонецПериода
Раньше любая пара companion-параметров с expression "&P.ДатаНачала"/
"&P.ДатаОкончания" сворачивалась в @autoDates, независимо от их имён.
Compile же всегда генерирует строго "НачалоПериода"/"КонецПериода" +
type=date + DateFractions=Date. Для отчётов с шаблоном "Период<X>" →
"НачалоПериода<X>"/"КонецПериода<X>" + DateFractions=DateTime
(типовой паттерн БСП — ПериодКонтрольСроков, ПериодОбязательств и т.п.)
это давало некорректный round-trip с потерей суффикса и формата дат.

Теперь fold срабатывает ТОЛЬКО для канонической пары — остальные
companion'ы остаются явными параметрами с полным сохранением имени,
type=dateTime, DateFractions=DateTime и expression.

sample30: −152 строки (1548 → 1396).
2026-05-23 20:38:33 +03:00
Nick Shirokov 639568c039 feat(skd): кастомные xsi:type с локальным xmlns в inputParameters values
Параметр ВыборГруппИЭлементов имеет значение типа FoldersAndItemsUse:
<dcscor:value xmlns:d6p1="http://v8.1c.ru/8.1/data/enterprise"
 xsi:type="d6p1:FoldersAndItemsUse">Items</dcscor:value>

Decompile теряла xsi:type → compile эмитил xs:string. Теперь сохраняем
{uri, name} в valueType wrapper'е, compile воспроизводит локальный
xmlns:dN="..." prefix и правильный xsi:type. GetNamespaceOfPrefix
извлекает URI из контекста элемента, что работает для всех 7 известных
xmlns URI (enterprise, current-config, ui/style, chart, types и т.п.).

sample30: −8 строк (1556 → 1548).
2026-05-23 20:24:46 +03:00
Nick Shirokov 9ef554a576 feat(skd): block-level userSettingID на selection/filter/order/CA + meta на StructureItemTable
Раньше теряли userSettingID, который platform пишет:
1. Прямо в блоки selection/filter/order/conditionalAppearance под
   <dcsset:settings> (рядом с block-level viewMode). Расширена цепочка
   Get-BlockUSID + новый -blockUserSettingID параметр в Emit-*.
2. На самой <dcsset:item xsi:type="dcsset:StructureItemTable"> (рядом
   с viewMode/userSettingPresentation/itemsViewMode). Build-Structure
   читает их, Emit-StructureItem (type=table) эмитит.

sample30: −46 строк (1602 → 1556).
2026-05-23 20:12:14 +03:00
Nick Shirokov 1b36aa97c8 feat(skd-decompile): useRestriction=true в object form для non-hidden/non-autoDates параметров
Параметры с явно заданным <useRestriction>true</useRestriction>, которые
не покрываются auto-emit от @hidden (где compile сам ставит true) и не
участвуют в @autoDates fold (где compile также ставит true companion'ам),
теряли это свойство. Типичный кейс: параметры-выражения вида
ПериодНачало = &Период.ДатаНачала с useRestriction=true.

Render-Parameter теперь явно эмитит useRestriction:true в object form
с защитой от двойного перекрытия hidden/autoDates.

sample30: −60 строк (1710 → 1650).
2026-05-23 19:44:58 +03:00
Nick Shirokov 573602ae65 fix(skd-decompile): сохранение кастомных xsi:type в outputParameters items
Параметры типа ВариантИспользованияГруппировки с xsi:type=
dcsset:DataCompositionGroupUseVariant теряли тип, потому что wrapper
создавался только при наличии viewMode/userSettingID/use=false. Теперь
кастомный xsi:type (не xs:* / LocalStringType / Font) сам по себе
триггерит wrapping — сохраняем valueType для bit-perfect эмиссии.

sample30: −28 строк (1738 → 1710).
2026-05-23 19:37:23 +03:00
Nick Shirokov 632c58eef1 fix(skd): сохранение xs:boolean и xs:decimal в filter values + availableValues
Get-FilterValueWithType теряла тип значения — "true"/"false" приходило как
string, и compile эмитил xs:string вместо xs:boolean. Decompile теперь
конвертирует по xsi:type → реальный [bool]/[int]/[double].

Shorthand эмиссия фильтра также фиксирована: bool теперь рендерится как
"true"/"false" (lowercase), не "True"/"False" (PS-style).

Аналогично availableValues в параметрах: bool/decimal значения теперь
сохраняются с правильным типом, а не downgrade в xs:string.

sample30: −30 строк (1768 → 1738).
2026-05-23 19:33:47 +03:00
Nick Shirokov 64c2037fe1 feat(skd): block-level viewMode/userSettingID на <dcsset:order> внутри structure group
Раньше теряли viewMode/userSettingID, которые platform пишет прямо в блок order
(не в item). Build-Structure теперь читает их как orderViewMode/orderUserSettingID,
Emit-Order принимает -blockUserSettingID параметр.

sample30: −10 строк (1778 → 1768).
2026-05-23 19:25:20 +03:00
Nick Shirokov fb9d29408c feat(skd): viewMode/userSettingPresentation на dataParameters items
Build-DataParameters не читал viewMode и userSettingPresentation на отдельных
параметрах данных — теряли 74 viewMode references только в одном крупном отчёте.
Теперь object form {parameter, value, valueType?, viewMode?, userSettingID?, ...}
с auto-конверсией bool/decimal по xsi:type.

Compile добавлен early-branch на полный xsi:type (xs:boolean, dcscor:DesignTimeValue
и т.п.) — раньше string "true" эмитился как xs:string вместо xs:boolean.

Расследование: viewmode-trace.py показал ровно 66 LOST viewMode=Normal
+ 8 Inaccessible на пути '/r:DataCompositionSchema/r:settingsVariant/
dcsset:settings/dcsset:dataParameters/dcscor:item[SettingsParameterValue]'.

sample30: −204 строки (1982 → 1778).
2026-05-23 19:19:39 +03:00
Nick Shirokov 730decf9ce feat(skd): itemsViewMode на table axis (column/row/point/series)
Build-TableAxisBlock не читал itemsViewMode на самой оси (только на
StructureItemGroup). Теперь сохраняется и эмитится — bit-perfect для
шаблонов отчётов с явно заданным itemsViewMode=Normal/Inaccessible
на колонках/строках таблицы.

sample30: −30 строк (2012 → 1982).
2026-05-23 19:04:22 +03:00
Nick Shirokov 48e2b6bd44 feat(skd): nilValue marker для параметров с xsi:nil="true"
Параметры со скалярным типом (decimal/string/dateTime) и <value xsi:nil="true"/>
теперь сохраняют значение nil через object form {nilValue:true}. Раньше compile
эмитил типизированный default (xs:decimal>0, xs:string/>, xs:dateTime>0001-01-01)
вместо nil — мismatch на bit-perfect round-trip.

sample30: −22 строки (2034 → 2012).
2026-05-23 17:51:35 +03:00
Nick Shirokov f75c71064c feat(skd): userSettingPresentation на conditionalAppearance item
Build-ConditionalAppearance не читал userSettingPresentation
(read только viewMode/userSettingID), теперь сохраняет multilang
презентацию в JSON, а Emit-ConditionalAppearance эмитит её обратно.

sample30: −80 строк (2114 → 2034).
2026-05-23 17:27:21 +03:00
Nick Shirokov 5ca8ce2b64 fix(skd-compile): widths-unwrap и indent в template cell appearance
Две bug-фиксы для шаблонов:

1. PS-quirk: \$widths = if (\$t.widths) { @(\$t.widths) } else { @() }
   разворачивал одно-элементный массив в строку, после чего \$widths[0]
   возвращал первый Char (например '1' для "15.625"), а [double][Char]'1'=49.
   Заменил if-expression на обычный if-statement.

2. Indent в <dcsat:appearance>: компайлер ставил 4 таба вместо 5
   (и 5 вместо 6 у items внутри). Поправлено в обоих рантаймах.

sample30: −552 строки (2666 → 2114).
2026-05-23 17:18:50 +03:00
Nick Shirokov 5793f91ebb feat(skd): StandardPeriod с явными datами в dataParameters
Когда v8:StandardPeriod имеет non-default startDate/endDate, decompile
выдаёт object form {parameter, value:{variant, startDate, endDate}, ...}
вместо shorthand "P = Custom". Compile использует переданные даты
вместо boilerplate 0001-01-01.

sample30: −24 строки (2690 → 2666).
2026-05-23 16:54:26 +03:00
Nick Shirokov b83bbc333f feat(skd-compile): multilang static text в ячейках шаблона
Get-CellValue теперь пропускает dict без ключа value (multilang dict
{ru, en, ...}), а главный цикл Emit-Templates эмитит для таких ячеек
<dcsat:item xsi:type="dcsat:Field"><dcsat:value xsi:type="v8:LocalStringType">
с lwsTitle-структурой. Раньше multilang-ячейки терялись (Get-CellValue
возвращал null → cell не эмитился).

sample30: −180 строк (2870 → 2690).
2026-05-23 16:48:08 +03:00
Nick Shirokov a417b76e2c feat(skd-decompile): StandardPeriod Custom + use=false на dataParameters items
Build-DataParameters раньше пропускал variant=Custom в шорткоде, теряя как
сам StandardPeriod-маркер, так и use=false (читался из dcsset:use вместо
правильного dcscor:use). Теперь Custom попадает в shorthand как "=Custom",
compile воспроизводит StandardPeriod tag + boilerplate dates корректно.

sample30: −166 строк (3036 → 2870).
2026-05-23 16:41:38 +03:00
Nick Shirokov 659451815d feat(skd): nested sub-параметры и valueType в outputParameters wrapper
Wrapper расширен полями valueType (полный xsi:type значения, для bit-perfect
неизвестных compile параметров) и items (вложенные dcscor:item, паттерн
ТипДиаграммы.ВидПодписей внутри ТипДиаграммы).

sample30: −194 строки (3230 → 3036).
2026-05-23 16:36:49 +03:00
Nick Shirokov f271a6f6ba feat(skd): viewMode/userSettingID/userSettingPresentation на outputParameters items
Wrapper {value, ...} расширен: помимо use=false поддерживает viewMode,
userSettingID, userSettingPresentation на каждом item внутри
<dcsset:outputParameters>. Также value=Font dict теперь работает в wrapper.

sample30: −92 строки (3322 → 3230).
2026-05-23 16:19:48 +03:00
Nick Shirokov 342b3f0687 feat(skd): v8ui:Font в appearance + use=false в conditionalAppearance
Font хранится как объект {@type:Font, ref, faceName, height, bold, italic,
underline, strikeout, kind, scale} — все исходные атрибуты сохраняются для
bit-perfect round-trip.

Заодно Get-SettingsAppearance теперь читает dcscor:use на conditionalAppearance
items (раньше игнорировал — терялся use=false на appearance value).

sample30: −315 строк (3637 → 3322).
2026-05-23 16:04:12 +03:00
Nick Shirokov 4b3819762c docs(skd-dsl-spec): dataSetLinks полная схема + multi-orderExpression + пустые userField expressions 2026-05-23 15:55:30 +03:00
Nick Shirokov 5e864cb05f feat(skd): пустые detail/totalExpression в userFields
В UserFieldExpression XML присутствуют все 4 элемента (detailExpression,
detailExpressionPresentation, totalExpression, totalExpressionPresentation),
даже когда они пустые (<dcsset:totalExpression/>). Раньше пустые опускались.

decompile теперь читает по присутствию узла, compile эмитит self-closing
форму для пустых строк.

sample30: −106 строк (3743 → 3637).
2026-05-23 13:03:24 +03:00
Nick Shirokov a66246095c feat(skd): multi-orderExpression на dataSet field
На одном поле может быть несколько <orderExpression> (multi-sort fallback).
decompile сохраняет массив (single → object back-compat), compile принимает оба.

sample30: −30 строк, +2 отчёта в bit-perfect (27 with-diff).
2026-05-23 12:54:53 +03:00
Nick Shirokov 9b4bb3d9b8 feat(skd): dataSetLinks с расширенными атрибутами
skd-decompile теперь извлекает блоки <dataSetLink> на уровне схемы,
skd-compile поддерживает поля parameterListAllowed/startExpression/
linkConditionExpression (раньше был только parameter).

На sample30 это даёт −1100 строк diff (4873 → 3773), один отчёт
(АнализНачисленийНДССАвансовПолученных) переходит в bit-perfect.
2026-05-23 12:47:56 +03:00
Nick Shirokov b1eb8bebe3 docs(skd-dsl-spec): догон по последним коммитам
- order item: use=false в object form
- outputParameters: wrapper {value, use: false} для отключённого параметра
- table: top-level selection/conditionalAppearance/outputParameters
  (отдельно от column/row)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 22:13:47 +03:00
Nick Shirokov 87bc274346 feat(skd-decompile): top-level блоки на StructureItemTable
Build-Structure для table теперь читает selection / outputParameters /
conditionalAppearance прямо на самой <dcsset:item xsi:type="StructureItemTable">,
не только внутри row/column.

Эффект на sample30: −906 строк diff (большой эффект — многие отчёты
с таблицами имеют top-level selection и outputParameters для названия
таблицы и формата вывода).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 22:07:51 +03:00
Nick Shirokov 6a8efc9538 feat(skd-compile): top-level selection/condApp/outputParameters на StructureItemTable
StructureItemTable может иметь свои selection / conditionalAppearance /
outputParameters прямо на уровне таблицы (отдельно от row/column).
Раньше Emit-StructureItem для table эмитил только columns и rows; теперь
после rows эмитятся top-level блоки.

Аналогично сделано для chart (там было раньше).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 22:07:51 +03:00
Nick Shirokov 0846740db7 feat(skd-compile): пустой <dcsset:filter/> на conditionalAppearance item
Платформа эмитит <dcsset:filter/> (self-closing, без условий) на
каждом condApp item, где фильтр не задан — это нормальная форма
"правило применяется ко всем строкам без дополнительных условий".

Compile теперь эмитит пустой тег если filter отсутствует/пуст.
Decompile-side уже корректно игнорировал пустой filter (Build-CondApp
читает items только если они есть).

Эффект на sample30: −252 строки diff.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 21:51:49 +03:00
Nick Shirokov 480d828c35 feat(skd-decompile): use=false на outputParameters item
Build-OutputParameters теперь читает <dcscor:use>false</dcscor:use> на
item и сохраняет значение в форме {value, use: false}.

Эффект на sample30: −198 строк diff.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 21:37:27 +03:00
Nick Shirokov 8009a8150f feat(skd-compile): use=false wrapper в outputParameters
outputParameters item тоже может иметь <dcscor:use>false</dcscor:use>
(например — отключённый «Заголовок» в варианте). Emit-OutputParameters
теперь распознаёт wrapper {value, use: false} и эмитит <dcscor:use>
в начале item, как уже делал Emit-AppearanceValue.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 21:37:26 +03:00
Nick Shirokov 29a9fbe950 feat(skd): use=false на OrderItemField
OrderItemField в settings может иметь <dcsset:use>false</dcsset:use>
(отключённая сортировка-пункт пользовательских настроек). Build-Order
теперь читает use=false и переводит item в object form
{field, direction, use, viewMode}. Compile эмитит <dcsset:use> в
начале item, перед <dcsset:field>.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 21:28:50 +03:00
Nick Shirokov 3832952400 feat(skd-decompile): use=false на appearance value items
Get-AppearanceDict теперь читает <dcscor:use>false</dcscor:use> на
appearance items и возвращает значение в форме {value, use: false}.
Compile-side уже принимал этот wrapper.

Эффект на sample30: −21 строка diff.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 21:21:48 +03:00
Nick Shirokov 10fef03681 docs(skd-dsl-spec): догон по последним расширениям DSL
- conditionalAppearance: use=false, useInDontUse массив, multilang
  presentation, userSettingPresentation, расширены auto-detect типов
  appearance (Размещение, ГориZontальноеПоложение, ЦветТекста без
  префикса, числовые строки)
- outputParameters: новые типы для placement (РасположениеИтогов,
  РасположениеГруппировки и др.), ТипМакета
- structure group: use=false, userSettingID, userSettingPresentation
- table column/row + chart axis: conditionalAppearance, children
- settings: additionalProperties (служебные key/value свойства)
- parameter: inputParameters (ФорматРедактирования и т.п.)
- filter shorthand: упомянут auto-detect dcscor:DesignTimeValue

В SKILL.md изменения не вносятся — фичи редкие, для bit-perfect
round-trip с реальных схем.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 21:14:49 +03:00
Nick Shirokov 957af1c421 feat(skd-decompile): user-settings на StructureItemGroup
Build-Structure для group теперь читает userSettingID и
userSettingPresentation (multilang dict) наряду с viewMode/itemsViewMode.

Try-StructureShorthand расширена — структура не сворачивается в
shorthand при наличии любого из новых полей (use, conditionalAppearance,
outputParameters, userSettingID, userSettingPresentation).

Эффект на sample30: −462 строки diff.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 21:09:38 +03:00
Nick Shirokov 616ac2a23e feat(skd-compile): userSettingID/userSettingPresentation на StructureItemGroup
StructureItemGroup может быть зарегистрирована как пункт пользовательских
настроек (например, "По сотрудникам" — позволяет включить/выключить
группировку через UI). Поля userSettingID и userSettingPresentation
эмитятся после viewMode, перед itemsViewMode (платформенный порядок).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 21:09:37 +03:00
Nick Shirokov a9deeee2d0 feat(skd-compile): auto-detect DesignTimeValue в filter right
При парсинге shorthand "Поле = Перечисление.X.Y" Parse-FilterShorthand
уже распознавал тип dcscor:DesignTimeValue. Но в auto-detect веток
Emit-FilterItem (single-right и multi-right) этой проверки не было,
поэтому ссылочные значения из object form (где valueType не сохранён)
эмитились как xs:string.

Добавлено в обе ветки: проверка regex ^(Перечисление|Справочник|...
|Catalog|Enum|...)\. → dcscor:DesignTimeValue.

Эффект на sample30: −326 строк diff.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 21:01:38 +03:00
Nick Shirokov 4af51235db feat(skd-decompile): conditionalAppearance внутри table/chart axis
Build-TableAxisBlock теперь читает <dcsset:conditionalAppearance>
блока column/row/point/series. Это типовая категория для table
с условным оформлением колонок (например, разный текст для разных
групп начислений в строке таблицы).

Эффект на sample30: −1026 строк diff.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 20:52:08 +03:00
Nick Shirokov da0b326c40 feat(skd-compile): conditionalAppearance внутри table/chart axis
Колонки/строки таблицы и оси диаграммы (column/row/point/series)
могут содержать собственный <dcsset:conditionalAppearance> — правила
оформления специфичные для этой оси. Emit-TableAxisBlock теперь его
эмитит между outputParameters и nested children.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 20:52:07 +03:00
Nick Shirokov 65a2b5870d feat(skd-compile): типизация appearance/outputParameters значений
Реальные платформенные значения имеют конкретные xsi:type, которые
compile терял в roundtrip:

Output параметры (расширена таблица OUTPUT_PARAM_TYPES):
- РасположениеОбщихИтогов, РасположениеИтогов → DataCompositionTotalPlacement
- РасположениеГруппировки → DataCompositionFieldGroupPlacement
- РасположениеРесурсов → DataCompositionResourcesPlacement
- ТипМакета → DataCompositionGroupTemplateType

Appearance keys (новая key-type карта в Emit-AppearanceValue):
- Размещение → DataCompositionTextPlacementType
- ГоризонтальноеПоложение/ВертикальноеПоложение → v8ui:HorizontalAlign/VerticalAlign
- ОриентацияТекста, РасположениеИтогов, ТипМакета

Auto-detect расширения:
- Числовые строки (МинимальнаяШирина=40 и др.) → xs:decimal
- ЦветТекста/ЦветФона/ЦветГраницы без префикса style:/web:/win: → v8ui:Color
  (для значений "auto", "#FFC8C8" и т.п.)

Эффект на sample30: −1122 строки diff.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 20:45:18 +03:00
Nick Shirokov cab0b4d26b feat(skd-decompile): чтение attributeUseRestriction на DataSet field
DataSet field может иметь <attributeUseRestriction> наравне с
<useRestriction> — те же 4 подэлемента (field/condition/group/order),
но ограничения применяются к атрибутам ссылочного поля (например,
"запретить выбирать атрибуты Контрагента в фильтре").

Compile-side уже принимал attrRestrict в JSON; decompile теперь его
заполняет. Item переходит в object form при наличии attrRestrict.

Эффект на sample30: −257 строк diff.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 20:32:38 +03:00
Nick Shirokov 092cd8ebb4 feat(skd-decompile): чтение additionalProperties в settings
<dcsset:additionalProperties> → dict в settings.additionalProperties.
Все значения xs:string, простые key→value пары.

Эффект на sample30: −121 строка diff.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 20:22:22 +03:00
Nick Shirokov f19032594c feat(skd-compile): additionalProperties в settings
<dcsset:additionalProperties> — список <v8:Property name="X">
<v8:Value xsi:type="xs:string">Y</v8:Value></v8:Property>. Используется
платформой для служебных свойств варианта (ВариантНаименование,
КлючВарианта, Адрес — URL tempstorage).

DSL: settings.additionalProperties = { "имя": "значение", ... }
Эмит — после itemsViewMode, перед закрытием settings.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 20:22:22 +03:00
Nick Shirokov a07a105024 feat(skd-decompile): inputParameters на параметрах + multilang presentation
Три связанных закрытия:
- Build-Parameter вызывает Read-InputParameters (раньше только Field)
- Read-InputParameters читает LocalStringType value как multilang dict
  (ФорматРедактирования и т.п.)
- Build-FilterItem (FilterItemComparison) читает <dcsset:presentation>
  с поддержкой multilang (Get-MLText + fallback InnerText); item
  переходит в object form при наличии presentation.

Эффект на sample30: −533 строки diff.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 20:06:47 +03:00
Nick Shirokov f5432eb48d feat(skd-compile): inputParameters на параметрах + multilang в value
Параметр (parameters[]) может иметь свой inputParameters блок —
например <ФорматРедактирования> со значением xs:LocalStringType.
Раньше Emit-InputParameters использовался только для DataSet field;
теперь подключён и к Emit-Parameter (вывод после <use>).

emit_input_parameters value: добавлена поддержка multilang dict
({ru, en, ...} → LocalStringType). Раньше падал в xs:string.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 20:06:46 +03:00
Nick Shirokov b8a6783ccf feat(skd-decompile): чтение multilang presentation в condApp и filter group
Build-ConditionalAppearance и FilterItemGroup читали presentation через
Get-Text (теряли multilang). Теперь читают через Get-MLText с fallback
на InnerText — multilang dict {ru, en, ...} сохраняется в JSON.

Эффект на sample30: −946 строк diff.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 19:40:59 +03:00
Nick Shirokov 2b8cdc40ca feat(skd-compile): multilang presentation на conditionalAppearance item
При значении-словаре {ru, en, ...} эмитим <dcsset:presentation> как
LocalStringType с <v8:item>/<v8:lang>/<v8:content>; при строке —
по-прежнему xs:string. Раньше всегда жёстко xs:string, что давало
LOST для multilang.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 19:40:59 +03:00
Nick Shirokov 013d3c3a01 feat(skd-decompile): чтение useInXxx и use=false на conditionalAppearance
Build-ConditionalAppearance теперь читает:
- <dcsset:use>false</...> → use: false
- любые <dcsset:useInXxx>DontUse</...> → элемент в массиве useInDontUse
  (имена тегов: useInGroup → "group", useInFieldsHeader → "fieldsHeader",
   и т.п.)

Эффект на sample30: −187 строк diff. Существенная часть LOST <use> и
LOST <content>/<lang> (внутри useInXxx-окружения) закрыта.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 19:34:32 +03:00
Nick Shirokov eee5aaafd3 feat(skd-compile): useInXxx и use=false на conditionalAppearance item
Расширение DSL для бит-перфект roundtrip на условном оформлении:
- use: false — отключённое правило (эмитится в начале item)
- useInDontUse: array — список областей где правило НЕ применяется
  (\"group\", \"hierarchicalGroup\", \"overall\", \"fieldsHeader\",
   \"header\", \"parameters\", \"filter\", \"resourceFieldsHeader\",
   \"overallHeader\", \"overallResourceFieldsHeader\")
  Compile эмитит <dcsset:useInGroup>DontUse</...> и т.п. в платформенном
  порядке.

Семантика: \"useIn\" в платформе — это белый список применения правила;
DSL хранит инверсный список (что отключено) — короче для редких
ограничений.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 19:34:32 +03:00
Nick Shirokov 32e06cbc56 fix(skd-compile): всегда эмитить useRestriction для параметра
Платформа эмитит <useRestriction>true|false</useRestriction> у каждого
параметра безусловно. Раньше compile эмитил только если =true, что
приводило к LOST <useRestriction>false</useRestriction> в roundtrip.

Эффект на sample30: −84 строки diff.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 19:27:36 +03:00
Nick Shirokov 77fc0cee2f feat(skd-decompile): nested children в table axis + structure-group default
Три связанных изменения:
- Build-TableAxisBlock читает вложенные <dcsset:item> как children
  (StructureItemGroup внутри row/column/point/series)
- Build-Structure принимает <dcsset:item> без явного xsi:type как
  StructureItemGroup (реальные XML используют такую default-форму
  для вложенных групп — раньше попадало в sentinel)
- Чтение use=false на StructureItemGroup

Эффект на sample30: −3253 строки diff (массовая категория —
table row almost always содержит nested grouping).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 18:55:02 +03:00
Nick Shirokov ac72ca8a51 feat(skd-compile): nested children + use=false на StructureItemGroup
В реальных отчётах внутри table row / column / chart axis (point/series)
часто живут вложенные группы — StructureItemGroup в children, со своими
groupItems / filter / order / selection / outputParameters / nested
children глубже. До этого Emit-TableAxisBlock эмитил только axis-level
поля, без children.

Также: на самой StructureItemGroup может быть use=false (отключённая
ветка структуры в settings) — добавлено в DSL и в эмит.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 18:55:02 +03:00
Nick Shirokov 6e3632e5ff revert(skd-decompile): вернуть @normal shorthand-флаг
Раньше при наличии явного <viewMode>Normal</viewMode> decompile
переводил filter item в полноценный object form. Это раздувало JSON
без причины — @normal в shorthand функционально эквивалентен
"viewMode": "Normal" в object form, и compile уже его парсит.

Теперь: object form триггерится только реальными причинами
(userSettingPresentation, value-массив, dcscor:Field валуетайп);
явный Normal сохраняется как @normal в shorthand. Object form
по-прежнему может содержать "viewMode": "Normal" — это равнозначно.

Compile-side изменений не требуется. Spec обновлён.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 18:41:57 +03:00
Nick Shirokov e843cd8997 docs(skd-dsl-spec): догон по последним расширениям DSL
- selection items: use=false (на field и Auto), пример обновлён
- filter:
  - примеры с valueType: dcscor:Field (field-to-field comparison),
    value: [a,b,c] (multi-right InList), value: [] (ValueListType placeholder)
  - явное описание форм value (скаляр / массив / пустой массив)
  - FilterItemGroup принимает user-settings (viewMode/userSettingID/...)
- table column/row + chart points/series: name на всех осях (раньше
  только row), плюс user-settings поля
- секция «Стратегия сохранения viewMode» — описана модель explicit-only
  (decompile сохраняет точное присутствие, compile эмитит только заданное)
- @normal убран из перечня shorthand-флагов (Normal — default, не
  эмитится shorthand'ом; явный Normal переводит в object form)

В SKILL.md изменения не вносятся — фичи редкие, нужны для bit-perfect
round-trip с реальных схем.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 18:33:06 +03:00
Nick Shirokov 4c26e97abf feat(skd-decompile): сохранение явного valueType в filter right (dcscor:Field)
Get-FilterValueWithType возвращает xsi:type вместе со значением.
Build-FilterItem теперь сохраняет valueType в object form, если тип
не xs:* (auto-detect compile обрабатывает xs:* сам). Это закрывает
field-to-field comparison: <right xsi:type=\"dcscor:Field\">FieldB</right>
теперь корректно эмитится обратно через valueType=\"dcscor:Field\".

Item переходит в object form при наличии valueType (shorthand не выразим).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 18:28:48 +03:00
Nick Shirokov cbad0fe743 fix(skd-compile): авто-определение xs:decimal по тексту числа
Для filter right value compile уже различал bool / native-number /
dateTime, но не различал числовые строки. Реальные отчёты часто хранят
сравнения как числа: <right xsi:type=\"xs:decimal\">5</right>.

Decompile при чтении видит "5" как строку (через InnerText), и без
этого фикса compile эмитил xs:string. Теперь добавлена проверка
по regex ^-?\d+(\.\d+)?$ → xs:decimal.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 18:28:48 +03:00
Nick Shirokov 03cc59d243 feat(skd-decompile): чтение multi-right и ValueListType в filter
Build-FilterItem теперь читает все <dcsset:right> элементы (раньше
только первый — терялись значения для InList с несколькими values).
Первый <right> типа v8:ValueListType трактуется как пустой list-placeholder
(`value: []` в JSON).

Item переходит в object form если value — массив (shorthand не выразим
для multi-value/empty-list).

Shorthand fallback для null/empty value теперь снова `_` (placeholder).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 18:19:20 +03:00
Nick Shirokov 540af9655d feat(skd-compile): filter right поддерживает массив и пустой ValueListType
DSL: value на filter item может быть массив:
- value: []           — пустой ValueListType placeholder (для InList с
                        пользовательскими настройками — пользователь
                        заполнит значения через UI)
- value: [3, 4, 5]    — InList с несколькими конкретными значениями
                        (compile эмитит несколько <right> подряд)
- value: 3            — single value (как раньше)

Compile автоопределяет тип каждого значения (bool/decimal/dateTime/string).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 18:19:20 +03:00
Nick Shirokov a7d5c46176 feat(skd): use=false на selection items
SelectedItemField и SelectedItemAuto могут иметь <dcsset:use>false</dcsset:use>
— отключённое поле выборки. Раньше игнорировалось при roundtrip.

DSL расширения:
- selection item object form: { field, use: false, title?, viewMode? }
- новый объект для отключённого Auto: { auto: true, use: false }

Decompile переходит в object form если есть use=false (помимо title и
viewMode); compile эмитит <use>false</use> в начале item (XML-порядок).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 18:05:27 +03:00
Nick Shirokov 38b5445f15 fix(skd): откат implicit viewMode=Normal — сохраняем точное присутствие
Реальные отчёты непоследовательны: одни filter/item имеют
<viewMode>Normal</viewMode> с userSettingID, другие — нет (зависит от
момента редактирования через UI). Стратегия "compile добавляет implicit
Normal когда есть userSettingID" даёт ложные ADDED строки в bit-perfect.

Меняю на корректную модель:
- decompile сохраняет viewMode даже = 'Normal' если node физически
  присутствует в XML (object form переходит автоматически)
- compile эмитит viewMode только если явно задан в JSON

Применено к: filter (item + group), dataParameters, conditionalAppearance,
selection items, order items.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 18:01:09 +03:00
Nick Shirokov 9aac032ac8 feat(skd-decompile): user-settings на table/chart axis и FilterItemGroup
Build-TableAxisBlock теперь читает name на любой оси (раньше только
для row), плюс viewMode (если non-Normal), userSettingID и
userSettingPresentation на самом блоке column/row/point/series.

Build-FilterItem для FilterItemGroup теперь читает presentation,
viewMode (non-Normal), userSettingID, userSettingPresentation —
раньше группа сохраняла только items.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 17:45:16 +03:00
Nick Shirokov f9774d799c feat(skd-compile): implicit viewMode=Normal + user-settings на FilterItemGroup и axis
Платформа эмитит <viewMode>Normal</viewMode> автоматически когда у
элемента есть <userSettingID> (это сигнал пользовательской настройки).
Теперь compile делает то же:
- filter item, dataParameters item, conditionalAppearance item, table
  axis (column/row/point/series) — все эмитят Normal если userSettingID
  задан и явный viewMode не указан

Кроме того: FilterItemGroup теперь поддерживает свой viewMode /
userSettingID / presentation / userSettingPresentation (наравне с
обычными filter items).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 17:45:15 +03:00
Nick Shirokov 49f17ef5fd docs(skd-dsl-spec): availableValues на полях + conditionalAppearance в group
Догнал spec за последние коммиты — описаны availableValues на DataSet
fields (по аналогии с parameters) и conditionalAppearance как
доступное поле структурного элемента group.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 17:31:33 +03:00
Nick Shirokov 515c82c398 feat(skd-decompile): conditionalAppearance + outputParameters внутри structure group
Build-Structure для StructureItemGroup теперь читает локальные
conditionalAppearance и outputParameters — раньше они терялись для
вложенных групп (только для top-level settings работало).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 17:29:18 +03:00
Nick Shirokov 206fed0125 feat(skd-compile): conditionalAppearance + outputParameters внутри structure group
Реальные отчёты задают conditionalAppearance прямо на вложенной
StructureItemGroup (например — особое оформление шапки группировки).
Compile теперь эмитит её сразу после filter, перед outputParameters,
если задана в JSON.

outputParameters на StructureItemGroup уже эмитился — без изменений.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 17:29:18 +03:00
Nick Shirokov e5e6392b8c feat(skd-decompile): чтение availableValues на полях dataSet
Build-Field теперь читает <availableValue> на DataSetFieldField,
типизирует value по xsi:type (boolean/decimal/string/dateTime),
сохраняет presentation как multilang dict если возможно.

Поле переходит в object form если есть availableValues.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 17:25:18 +03:00
Nick Shirokov b58f9aa6a2 feat(skd-compile): availableValues на DataSet fields
Раньше availableValues эмитились только для parameters. Реальные
отчёты также задают availableValues на полях dataSet (например
ТипЗаписи=1..5 со ссылочными значениями), что давало отсутствие
важной семантики при roundtrip.

DSL: `field.availableValues: [{value, presentation, valueType?}]` —
типы значений автоопределяются (bool/decimal/dateTime/string),
presentation поддерживает multilang.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 17:25:08 +03:00
Nick Shirokov 2235b11700 chore(skd-compile): порт PS → PY + spec для последних расширений
В 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 <noreply@anthropic.com>
2026-05-22 17:09:48 +03:00
Nick Shirokov 04b742fe78 feat(skd-decompile): чтение userFields (UserFieldExpression/Case)
Build userFields array в settings из <dcsset:userFields>. Поддержаны
оба подтипа (Expression с detail+total / Case с cases). Multilang title
и presentation корректно читаются как объекты.

Эффект на sample30: -5500 строк diff (целая ветка пользовательских
полей со всеми expression/presentation/case-структурами).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 17:05:29 +03:00
Nick Shirokov 1d75456f4e feat(skd-compile): userFields в settings (UserFieldExpression/UserFieldCase)
Пользовательские вычисляемые поля — отдельный блок <dcsset:userFields>
в начале settings. Два подтипа:

- UserFieldExpression: dataPath + title + detail{expression,presentation}
  + total{expression,presentation}
- UserFieldCase: dataPath + title + cases[{filter, value, presentation}]

DSL: тип определяется наличием 'cases' (case-форма) vs detail/total
(expression-форма) — без явного 'type'.

В SKILL.md не упоминаем (редкая фича — обычно настраивается пользователем
через UI «Изменить вариант»). Описано в spec.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 17:05:18 +03:00
Nick Shirokov 3e0f6bba02 feat(skd-decompile): table/chart axis filter+outputParameters + nestedSchema Ring 3
Build-TableAxisBlock теперь читает filter и outputParameters блока,
order/selection сохраняются с точным присутствием (даже [Auto]) для
bit-perfect round-trip.

Дополнительно: nestedSchema (вложенные DCS-подсхемы) добавлены в Ring 3
fail-fast — фича редкая (15/490 в ERP), требует значительного
расширения DSL (рекурсивная DCS внутри JSON). Поддержку можно
вернуть позднее как nestedSchemas массив в корне JSON.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 16:55:09 +03:00
Nick Shirokov 19c2557778 feat(skd-compile): filter/outputParameters/order/selection на table/chart axis
Колонки и строки таблицы (StructureItemTable.column/row) и оси диаграммы
(StructureItemChart.point/series) могут иметь свои filter, order,
selection, outputParameters — реальные отчёты активно это используют
для отбора и оформления внутри каждой оси.

Compile теперь:
- эмитит filter и outputParameters на column/row/point/series
- order/selection эмитятся только если заданы в JSON (раньше дефолтили
  [Auto], что иногда расходилось с оригиналом)

Логика вынесена в общий helper Emit-TableAxisBlock.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 16:54:59 +03:00
Nick Shirokov 3453e64bea fix(skd-decompile): чтение DataSetUnion inner <item> элементов
Build-DataSet для типа DataSetUnion теперь читает <item xsi:type="...">
(платформенный формат), сохранена обратная совместимость с <dataSet>
для XML, сгенерированных предыдущими версиями skd-compile.

Эффект на sample30: -12000 строк diff (LOST <v8:item>/<lang>/<content>
в полях inner-Union datasets).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 16:01:08 +03:00
Nick Shirokov eac0ae5a02 fix(skd-compile): DataSetUnion inner items оборачиваются как <item>
Платформенный 1С пишет вложенные dataSets внутри DataSetUnion как
<item xsi:type="DataSetQuery">, а наш compile эмитил <dataSet xsi:type=...>.
Это вело к двум проблемам:
- сгенерированный XML отличался от платформенного (косметика для bit-perfect)
- skd-decompile симметрично искал <dataSet> и пропускал inner items
  при чтении реальных схем — теряя все вложенные fields/titles

Эталон: upload/erf/ПроверкаЭкранирования/.../Templates/СКД_Объединение
показывает что Designer всегда пишет <item xsi:type="..."> внутри Union.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 16:00:59 +03:00
Nick Shirokov a46d5a166b feat(skd-decompile): сохранение viewMode/itemsViewMode для round-trip
Decompile теперь читает viewMode/itemsViewMode из XML и сохраняет в JSON
точно как было — даже Normal-значения (платформа эмитит эти теги
контекстно, и для bit-perfect нам важно наличие, а не сам режим).

Чтение:
- item-level: selection item, order item (новая object form)
- block-level: selection/filter/order/conditionalAppearance →
  XViewMode на settings
- structure group: viewMode + itemsViewMode на самом item
- settings: itemsViewMode

Дополнительно:
- Убран shorthand @normal из filter/condApp/dataParam (Normal — default,
  шум в JSON)
- Структурный shorthand "A > B > details" не сворачивается если есть
  viewMode/itemsViewMode на элементе
- Selection/order на structure-item сохраняются даже = [Auto] —
  compile теперь не дефолтит, поэтому наличие важно для bit-perfect

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 15:38:42 +03:00
Nick Shirokov bf4005bf76 feat(skd-compile): viewMode/itemsViewMode на блоках и structure items
DSL расширения (item-level — паттерн object form расширен):
- selection: {field, viewMode}
- order: {field, direction, viewMode} (новая object form)
- structure group: {type:group, viewMode, itemsViewMode}

DSL расширения (block-level на settings):
- selectionViewMode, filterViewMode, orderViewMode
- conditionalAppearanceViewMode
- itemsViewMode (на самих settings)

Compile эмитит viewMode/itemsViewMode только если явно задано в JSON —
это позволяет decompile сохранить точное наличие/отсутствие из XML и
получить bit-perfect round-trip (платформа эмитит эти теги
контекстно — на ABCXYZ-стиле для каждого блока, а в простых отчётах
без пользовательских настроек — не эмитит).

Дополнительно:
- Пустой LocalStringType теперь эмитится как self-closing (как платформа)
- Убран default order/selection=["Auto"] на StructureItemGroup
  (раньше compile дефолтил, теперь эмитит только если задано)

В SKILL.md не упоминаем — фича редкая. Полное описание в spec.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 15:38:29 +03:00
Nick Shirokov 501abd9fac fix(skd-compile): multilang в outputParameters value
Emit-OutputParameters принудительно использовал str(value), теряя
multilang dict {ru, en} → эмитил как "@{ru=...; en=...}". Теперь
auto-promote ptype=mltext если значение — PSCustomObject/dict.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 14:25:32 +03:00
Nick Shirokov c3a8a9c874 fix(skd): multilang в calcField appearance и selection lwsTitle + hidden combo
Найдено через новый debug-tool debug/skd-decompile/verify-roundtrip.ps1
(XML→decompile→compile→diff на сэмпле 30 ERP).

1. Emit-CalcFields appearance: третий дубликат-emitter с тем же
   multilang-багом как был в Emit-AppearanceValue для field/cond. Унифи-
   цирован через Emit-AppearanceValue. (compile.ps1+py)

2. Emit-SelectionItem: lwsTitle для folder + field-with-title
   эмитили "$($item.folder)" — для hashtable получали "@{ru=X; en=Y}".
   Унифицирован через Emit-MLText с новой опцией -NoXsiType
   (lwsTitle в оригинале без xsi:type, в отличие от <title> в fields).
   (compile.ps1+py)

3. Build-Parameter hidden detection: combo availableAsField=false +
   useRestriction=true. Только availableAsField=false (без
   useRestriction) → object form `availableAsField: false`.

На сэмпле 30 ERP roundtrip-diff: ADDED <content> 667 → 129
(multilang-потери в selection закрыты). Остаточные LOST <item> ~10k —
другие потери (attributeUseRestriction и проч.) — отдельная задача.

Versions: compile v1.35→v1.36, decompile v0.20→v0.21.
2026-05-21 21:50:46 +03:00
Nick Shirokov 8b71054478 feat(skd-decompile): query file префикс + inline объекты/массивы по lineLimit
1. Внешний .sql теперь именуется <outputBasename>-<datasetName>.sql
   (раньше просто <datasetName>.sql). Защищает от коллизий при
   batch-decompile нескольких отчётов в одну папку: имена dataset'ов
   часто совпадают ("НаборДанных1" — почти везде).
   В JSON: "query": "@<outputBasename>-<datasetName>.sql".

2. ConvertTo-CompactJson: Try-InlineJson — пытается сериализовать
   container на одну строку. Если результат + текущий indent ≤120
   chars → inline; иначе multi-line. Применяется и к объектам и к
   массивам (включая массивы из примитивов — раньше они всегда были
   inline, что давало гигантские строки на длинных fields).

   Примеры inline (объекты ≤120 chars):
   - { "value": "B", "style": "header" }
   - { "name": "Имя", "expression": "Имя" }
   Длинные объекты и массивы — multi-line как раньше.

v0.19 → v0.20.
2026-05-21 21:04:21 +03:00
Nick Shirokov 55b80fdc08 feat(skd-decompile): компактный JSON-сериализатор + расширенный shorthand totalFields
Заменил ConvertTo-Json (PS5.1) на собственный ConvertTo-CompactJson:
- 2-пробельный indent (вместо 4 + выравнивание keys по длине)
- Массивы примитивов (string/number/bool/null) — inline `[a, b, c]`
- Массивы с объектами/nested arrays — multi-line как раньше
- Кириллица в UTF-8 (без \uXXXX-escapes)
- Корректный escape строк (\\", \\, \n, \r, \t, \uXXXX для control chars)

Build-TotalField: shorthand "name: expr" для любого однострочного
expression. Раньше object form применялась когда expression не Func(arg).
Теперь — только когда есть group или expression многострочный.
Compile принимает любой shorthand вида "dataPath: expression" (Parse-
TotalShorthand делает split по первому ":").

Save-UserStyles тоже использует новый сериализатор.

Все 16 декомпиляционных snapshot'ов обновлены (косметика — JSON
структурно тот же, тесты round-trip проходят).

На реальном отчёте (целевой корпус): 405 → 264 строк (-35%).
v0.18 → v0.19.
2026-05-21 20:50:34 +03:00
Nick Shirokov e0ee927156 feat(skd-decompile): externalize multi-line queries в отдельные .sql файлы
Если query ≥3 строк и указан -OutputPath, decompile выносит SQL в
<datasetName>.sql рядом с decompiled.json. В JSON эмитится "@<name>.sql"
вместо inline-строки.

- Имя файла: dataset name (sanitized — non-word chars → _), коллизии
  разрешаются суффиксом _2/_3/...
- compile уже поддерживает синтаксис @file.sql (Resolve-QueryValue)
  — round-trip симметричен.
- Тесты не изменились: все тестовые queries по 1 строке (порог не
  срабатывает).
- На реальных отчётах (ERP/ACC main DCS — 10-50 строк query типично)
  даёт значительно более компактный JSON + читаемый .sql с подсветкой
  синтаксиса в IDE.

Новый тест dataset-query-multiline (round-trip с внешним .sql).
v0.17 → v0.18.
2026-05-21 20:37:15 +03:00
Nick Shirokov a1131965cc feat(skd): DataSetFieldFolder + GroupItemAuto + empty-field selection
Закрывает три gap'a, выявленных при полном прогоне ERP+ACC:

1. DataSetFieldFolder — поле-папка для UI-группировки полей в композиторе
   настроек. Только dataPath + title, без valueType/role.
   - DSL: object form поля с `folder: true`.
   - Compile: при folder=true → <field xsi:type="DataSetFieldFolder">.
   - Decompile: распознать xsi:type, эмитить object form.

2. GroupItemAuto — пустой <item xsi:type="GroupItemAuto"/> в groupItems
   (auto-grouping, аналогично "Auto" в selection).
   - DSL: строка "Auto" в groupFields.
   - Compile/decompile: round-trip.

3. Empty <field/> в conditionalAppearance/selection (wildcard — apply
   to all). Раньше — SelectionItem: sentinel. Теперь эмитим как "Auto"
   (семантический эквивалент через SelectedItemAuto).

Новый тест dataset-folder-and-auto-group (round-trip).
Versions: compile v1.34→v1.35, decompile v0.16→v0.17.

На предыдущем прогоне ERP+ACC: 227 sentinel'ов (218 DataSetFieldFolder
+ 7 GroupItemAuto + 2 SelectionItem). После — 0.
2026-05-21 20:28:45 +03:00
Nick Shirokov be9ebedf14 fix(skd-compile): multilang appearance value (Формат={ru,en} и др.)
Emit-AppearanceValue / emit_appearance_value: hashtable/PSCustomObject/dict
значение → LocalStringType независимо от ключа. Раньше для значения
{ru: "ДЛФ=D", en: "DLF=D"} compile эмитил xs:string "@{ru=ДЛФ=D; en=DLF=D}"
(строковое представление PS hashtable) — потеря структуры и неверный XML.

Wrapper {use: false, value: ...} распознаётся точечно (требуются оба ключа,
чтобы не путать с multilang dict без 'use').

Унификация field-level appearance: parse сохраняет значение как есть
(а не str(v)), emit использует Emit-AppearanceValue вместо дублированной
mini-логики. Side-effect: "true"/"false" в field appearance теперь эмитятся
как xs:boolean (раньше — xs:string). Корректнее для 1С; обновлён один
snapshot теста compile.

Новый тест appearance-multilang-value (поле + conditionalAppearance с
multilang Формат — round-trip bit-perfect).
Versions: compile v1.33→v1.34.

Закрывает п.2 из handoff («известный баг с multilang appearance values»).
2026-05-21 20:01:31 +03:00
Nick Shirokov 7f3a8861ad feat(skd): inline cell style override + закрытие категории C
Cell в rows теперь может быть либо string ("text"/"{param}"/"|"/">"/null),
либо объектом {value, style: "presetName"}. Object form применяется когда
стиль ячейки отличается от template default.

compile (ps1+py): helpers _get_cell_value / _get_cell_style_or_default.
Emit-AreaTemplateDSL / _emit_area_template_dsl используют per-cell style
для appearance вместо единого template style.

decompile: refactor Build-Template. Первый pass — собрать style name per
cell в cellStyleMap. Второй pass — выбрать template default как most
frequent style. Третий pass — обернуть в {value, style} ячейки, чьи
стили отличаются от default. TemplateStyleMismatch sentinel удалён —
теперь все случаи покрываются через inline override.

Дедуп при обоих pass'ах (Match-PresetByShape) работает через
effectivePresets (built-in + user + ранее аллоцированные customN), так
что одинаковые shape'ы получают одно имя.

Новый тест template-inline-cell-style (round-trip bit-perfect).
Versions: compile v1.32→v1.33, decompile v0.15→v0.16.

Метрики на момент коммита:
- ERP-сэмпл 30: 30/30 clean, 0 sentinel'ов
- Корпус из 40 отчётов целевого класса: 40/40 clean, 0 sentinel'ов

Закрывает категории A, B, C полностью на обоих корпусах.
2026-05-21 19:53:41 +03:00
Nick Shirokov 3119700c71 feat(skd-decompile): авто-генерация skd-styles.json для custom appearance
Категория C — закрыта для однородных шаблонов с custom appearance.

Refactor fingerprint → preset shape (11 полей: font/fontSize/bold/italic/
hAlign/vAlign/wrap/bgColor/textColor/borderColor/borders). vAlign теперь
учитывается в matching (раньше игнорировался).

Алгоритм:
1. При -OutputPath загружается existing skd-styles.json рядом (если есть);
   user presets накладываются на built-in по той же логике что и compile.
2. Каждая ячейка → Extract-CellPreset → Match-PresetByShape против
   effectivePresets (built-in + user).
3. Если не match — Allocate-CustomStyle: новый customN, регистрируется
   в effectivePresets и accumulator.
4. По окончании Save-UserStyles пишет skd-styles.json рядом с outputPath
   (preserved existing + новые customN).
5. Compile подхватит файл по своим search-путям (cwd/dirname/scan-up).

В SKILL.md не добавляем (custom стили — для round-trip, не для написания
модель с нуля; built-in `data/header/subheader/total/none` остаются
основным интерфейсом для модели).

- runner.mjs: новый preRun step `writeFile` для подготовки fixture-файлов
  в workDir (нужен для теста с предзаписанным skd-styles.json).
- Новый тест template-custom-style: preRun пишет myHeader preset,
  скомпилирует темплейт, decompile reverse'ит → переиспользует имя
  myHeader (не создаёт customN).
- v0.14 → v0.15.

Метрики:
- ERP-сэмпл 30: 24 → 0 sentinel'ов, clean 26 → 30/30
- Целевой корпус 40 отчётов: 39 → 25 sentinel'ов (часть закрыта), clean
  19 → 20/40. Остаточные — шаблоны с разными стилями в разных ячейках
  одного шаблона (нужно per-cell style override — отдельная задача).
2026-05-21 19:32:17 +03:00
Nick Shirokov 4bd8f27dec feat(skd): preset style=none + детект пустого fingerprint в decompile
Закрывает простую часть категории C: шаблоны где у ячеек appearance
содержит только per-cell атрибуты (МинимальнаяШирина и др.) без font/
borders/colors. Раньше такие шаблоны попадали в TemplateStyleMismatch.

- skd-compile (ps1+py): новый preset 'none' со всеми стилевыми полями
  null/false. Emit-CellAppearance / _emit_cell_appearance пропускают
  Font-элемент когда style.font=null.
- skd-decompile: пустой fingerprint (после отсева per-cell ключей) не
  считается за стиль ячейки; если все non-merge ячейки шаблона имели
  пустой fp — эмитим style="none" вместо sentinel.
- Новый тест template-no-style (round-trip bit-perfect).
- Versions: compile v1.31→v1.32, decompile v0.13→v0.14.

Метрики:
- ERP-сэмпл 30: 32 → 24 sentinel'ов, clean 24→26/30
- Корпус из 40 отчётов целевого класса: 45 → 39 sentinel'ов, 19/40 clean

Остаточные sentinel'ы — реальный custom appearance (нестандартный шрифт/
выравнивание/цвет вне built-in пресетов). Требует расширения DSL под
hashtable-style — отдельная задача.
2026-05-21 18:38:34 +03:00
Nick Shirokov a73517ee07 feat(skd): nested folder + nestedObject + groupItem object form (round-trip)
Закрывает категорию B полностью на ERP-корпусе:
- selection.folder теперь рекурсивный: внутри items могут быть string,
  {field, title}, или ещё одна {folder, items: [...]}. Compile/decompile
  обходят дерево рекурсивно (Emit-SelectionItem / Build-SelectionItem).
- structure: новая ветка type=nestedObject с {objectID, settings:
  {selection, filter, order, conditionalAppearance, outputParameters}}.
- groupFields теперь объектная форма {field, groupType?, periodAdditionType?}
  когда не дефолт (Items / None). Compile уже принимал; decompile перестаёт
  ставить warning GroupItemDetails. Try-StructureShorthand игнорирует
  object-form поля при сворачивании в строку.
- Refactor: Build-Structure для StructureItemGroup теперь использует
  общий Get-GroupFields вместо дублированного inline-кода.

В SKILL.md не добавляем (формы редкие/сложные, модель не пишет с нуля).

Новый тест structure-nested-and-folder покрывает все три случая bit-perfect.
Versions: compile v1.30→v1.31, decompile v0.12→v0.13.

На сэмпле 30 ERP-отчётов: 754 → 32 sentinel'ов (-96%), clean 4 → 24/30.
Остаточные 32 — все TemplateStyleMismatch (категория C, диагностика).
2026-05-21 18:19:49 +03:00
Nick Shirokov 3a68e1cb44 chore: ignore debug-templates.txt (локальный листинг ERP-путей) 2026-05-21 18:08:16 +03:00
Nick Shirokov cbc9f0cf61 feat(skd): inputParameters — ChoiceParameters/Links + typed values (round-trip)
DSL: object-form ключ inputParameters — массив элементов, каждый типизирован
по форме value:
- choiceParameters: [{name, values: [...]}] — параметры выбора (DesignTimeValue)
- choiceParameterLinks: [{name, value, mode}] — связи параметров выбора
- value (+ optional use=false) — простое типизированное значение (bool/string/number)

Compile: Emit-InputParameters / emit_input_parameters → <r:inputParameters>...
Decompile: Read-InputParameters читает любой xsi:type, без SilentDrop warnings.
Build-Parameter — убран вызов несуществующего Check-InputParameters.

В SKILL.md не добавляем (форма сложная — модель не пишет с нуля, но при
декомпиляции из реального отчёта получает корректно и compile примет назад).

Новый тест field-input-parameters (3 типа элементов, bit-perfect round-trip).
Versions: compile v1.29→v1.30, decompile v0.11→v0.12.

На сэмпле 30 ERP-отчётов: SilentDrop:ChoiceParameters/Links 51 → 0,
clean reports 8 → 21, total sentinel'ы 109 → 58.
2026-05-21 18:07:59 +03:00
Nick Shirokov 4413a06c49 feat(skd): orderExpression — сортировка поля по выражению (round-trip)
- skd-compile (ps1+py): object-form ключ orderExpression{expression,orderType,autoOrder}
  → <r:orderExpression><dcscom:expression/><dcscom:orderType/><dcscom:autoOrder/>
- skd-decompile: читает <r:orderExpression> → object form поля, без SilentDrop warning
- SKILL.md skd-compile: одна строка в "Дополнительные ключи объектной формы"
- docs/skd-dsl-spec.md: пример в объектной форме поля
- Новый тест field-order-expression (round-trip bit-perfect)
- Versions: compile v1.28→v1.29, decompile v0.10→v0.11

На сэмпле 30 ERP-отчётов: SilentDrop:orderExpression 11 → 0.
2026-05-21 17:59:19 +03:00
Nick Shirokov 537adfd3f8 feat(skd-decompile): shorthand-render роли + extras без whitelist
- Get-RoleInfo: любой <dcscom:KEY> со строковым значением → extras; whitelist убран
- Render-Role: shorthand-строка "@flag K=V" когда все extras-значения простые
  (regex ^[\w\.\-]+$); иначе object form
- Build-Field: shorthand-роль встраивается в field-shorthand-строку
- v0.9 → v0.10

Новый тест-кейс field-roles-rich (балансовые поля с balanceGroupName/balanceType,
@dimension @required) — bit-perfect round-trip с compile.

На сэмпле 30 ERP-отчётов: 754 → 120 sentinel'ов (-84%), 8/30 clean.
ComplexRole 27 → 0.
2026-05-21 17:43:05 +03:00
Nick Shirokov 009656991f feat(skd-compile): расширенный синтаксис role — shorthand + KV без whitelist
- Parse-RoleSpec (ps1+py): принимает string ("dim"/"flag1 flag2 K=V") / array / object
- Parse-FieldShorthand: извлекает K=V из shorthand поля (regex \w+=\S+)
- emit: токены → <dcscom:KEY>true</dcscom:KEY>; extras → <dcscom:KEY>VALUE</dcscom:KEY>
  (без whitelist; раньше принимались только accountTypeExpression и balanceGroup)
- @period sugar поддерживает override через periodNumber/periodType KV
- Fix имени: balanceGroup в JSON принимается как deprecated alias для balanceGroupName
  (в реальном XML 1С элемент называется balanceGroupName; старый код compile эмитил
   несуществующий <dcscom:balanceGroup> — ни одного попадания в ERP-корпусе)
- SKILL.md, docs/skd-dsl-spec.md: единое описание четырёх форм роли
- v1.27 → v1.28
2026-05-21 17:42:52 +03:00
Nick Shirokov 8cf29c601e feat(skd-decompile): расширенные role (object form) + silent-drop visibility
- Get-RoleInfo (вместо Get-RoleTokens): любой dcscom:KEY=true → @KEY;
  accountTypeExpression/balanceGroup → extras → object form role
  (compile уже поддерживает object form через {key: true})
- Build-Field: object form role при наличии extras
- Silent-drop → warnings (без ломания round-trip):
  * Check-InputParameters — ChoiceParameters/ChoiceParameterLinks (non-empty)
  * orderExpression на field
  * scope в conditionalAppearance item

На сэмпле 30 ERP-отчётов: 754 → 147 sentinel'ов (-80%), 8/30 clean.
2026-05-21 17:13:17 +03:00
Nick Shirokov d8d80af88e feat(skd-decompile): категория A — SelectionItem implicit, StructureItemTable, StructureItemChart
- Build-Selection: implicit SelectedItemField при пустом xsi:type (платформа эмитит в conditionalAppearance)
- Build-Structure: ветки для StructureItemTable (columns/rows) и StructureItemChart (points/series/selection/outputParameters)
- Try-StructureShorthand: отказ от свёртки при type≠group
- Вспомогательные Get-GroupFields, Build-TableAxisBlock

На сэмпле 30 ERP-отчётов sentinel'ов категории A: 0 (было 640).
2026-05-21 17:03:31 +03:00
Nick Shirokov 48b08d77e5 test(skd-decompile): 6 snapshot-based test cases по слоям
Кейсы создают исходник через preRun (skd-compile), декомпилируют его и
сравнивают workDir со снапшотом (Template.xml + decompiled.json):

- minimal-query — базовый DataSetQuery
- fields-types-and-restrictions — типы, роли, restrictions, multilang
  title, appearance, composite type, presentationExpression
- calc-total-params — calculatedFields, totalFields, parameters с
  autoDates/valueList/hidden/availableValues
- templates-with-style-merge-drilldown — built-in стили header/data,
  merge >/|, drilldown свёртка
- variant-full — selection с folder, filter Or, conditionalAppearance,
  outputParameters, dataParameters="auto", structure shorthand,
  groupTemplates
- dataset-types — DataSetQuery + DataSetObject + DataSetUnion

Все 6 passes на runtime=powershell. Готовая база для регрессии при
питон-порте (можно прогнать тот же набор через --runtime python).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 16:20:59 +03:00
Nick Shirokov 31d1ae2650 feat(skd-compile): sentinel-check для интеграции со skd-decompile
При наличии __unsupported__ маркеров в JSON (от skd-decompile, Кольцо 2)
compile завершается с exit 4 и понятным сообщением: id/kind/loc каждого
sentinel и подсказка про .warnings.md рядом.

Рекурсивный walk JSON покрывает hashtable/PSCustomObject/array. Sentinel
в любом месте дерева — фейл.

Bump skd-compile v1.26 → v1.27.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 16:10:14 +03:00
Nick Shirokov 54d4dd6904 feat(skd-decompile): слои 16-17 — sentinel, warnings.md, fail-fast Ring 3
Sentinel/warnings уже работали по ходу разработки слоёв. Добавил
явные Ring 3 проверки до основного pipeline:
- Picture cells в шаблонах (<dcsat:item xsi:type=Picture>) → exit 3
- Параметры типа ХранилищеЗначения (v8:ValueStorage) → exit 3
- templateCondition (вариативные шаблоны) → exit 3
- Не-DCS корневой элемент → exit 2 (теперь через [Console]::Error,
  без обвязки Write-Error и с понятным сообщением по-русски).

Сообщения fail-fast включают рекомендацию использовать /skd-edit
для точечной работы.

Регрессий нет — все 7 синтетических тестов остаются 0 diff.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 16:09:11 +03:00
Nick Shirokov 840e3ed768 feat(skd-decompile): слой 15 — DataSetObject + DataSetUnion
- DataSetObject: уже была базовая поддержка objectName, теперь fields
  тоже извлекаются (отвязали от switch специально для query).
- DataSetUnion: рекурсивный walk вложенных <dataSet> элементов через
  Build-DataSet → items[] с полными nested-dataset объектами.
- Вынес логику в Build-DataSet функцию.

Round-trip clean (GUID-normalized) на всех 7 синтетических тестах,
включая ds-types-test с Query + Object + Union в одной схеме.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 16:07:11 +03:00
Nick Shirokov 4497b1d4e2 feat(skd-decompile): слои 10-14 — groupTemplates, settingsVariants
groupTemplates:
- <groupHeaderTemplate> → templateType=GroupHeader.
- <groupTemplate> → templateType из inner <templateType>.
- groupField/groupName/template — прямой перенос.

settingsVariants:
- selection: SelectedItemAuto/Field/Folder → "Auto"/имя/{folder,items}.
- filter: shorthand "Field op value @flags" + Or/And/Not-группы;
  reverse-map для всех операторов сравнения/списка/строкового/nullity.
- order: shorthand "Field" или "Field desc".
- conditionalAppearance: selection/filter/appearance/presentation/viewMode/userSettingID.
- outputParameters с LocalStringType поддержкой.
- dataParameters: auto-детект "auto" формы; иначе явный список.
- structure: рекурсивный walk StructureItemGroup; попытка свернуть
  линейную цепочку в string shorthand "A > B > details".
- Skip pure-default variant — compile его сгенерирует сам.

Auxiliary:
- Comma-operator `,$arr` на возвратах функций — избегаем разворот single-item
  массивов PS pipeline в скаляр.
- autoDates companions исключаются из visibleTop в Build-DataParameters,
  чтобы можно было свернуть в "auto".
- "_" placeholder восстанавливается для nil/empty filter right.
- Cleanup форматирования warnings.md (PS interpolation issue с `$).

Round-trip clean (GUID-normalized): все 6 синтетических тестов 0 diff —
fields/calc-params/templates/merge/variant/gt-test.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 15:09:05 +03:00
Nick Shirokov 61c4bd418d feat(skd-decompile): слои 6-9 — templates (cells, merge, style, drilldown)
- Парсинг <template>/<template xsi:type=AreaTemplate>/<dcsat:TableRow>/
  <dcsat:tableCell> в rows[][].
- Распознавание содержимого ячейки: dcsat:Text → строка, dcsat:Field с
  dcscor:Parameter → "{Имя}", dcsat:Field с LocalStringType → строка/multilang,
  пустая → null.
- Merge через appearance-флаги ОбъединятьПоВертикали/ОбъединятьПоГоризонтали
  на пустых ячейках → "|"/">".
- Детект built-in стилей (header/data/subheader/total) через нормализованный
  fingerprint appearance — без сравнения per-cell ширин/высот/merge-флагов.
  При несовпадении или неоднородности — sentinel TemplateStyleMismatch.
- Извлечение widths из appearance первого row и minHeight из первой ячейки.
- Drilldown-свёртка: для cells с appearance Расшифровка=Расшифровка_X
  и template-параметром DetailsAreaTemplateParameter Расшифровка_X →
  свертываем в `{name, expression, drilldown: X}`.
- Сохранение порядка template parameters через [ordered]@{}.
- Fix namespace URI для areatemplate (`area-template` с дефисом).

Bit-perfect round-trip 55924→55924 и 28590→28590 на синтетике с header/data
стилями, merge, drilldown, шаблонными параметрами.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 14:58:48 +03:00
Nick Shirokov be69bc231c feat(skd-decompile): слои 4-5 — calculatedFields, totalFields, parameters
- calculatedFields: shorthand с [title], type, expression и #restrict-флагами;
  object form при appearance или multilang title.
- totalFields: детект Func(name) и Func(expr) → shorthand "name: Func"/"name: Func(expr)";
  object form при привязке к группе.
- parameters:
  - shorthand с [title], type, value, @-флагами;
  - распознавание StandardPeriod variants → значение в shorthand;
  - @valueList, @hidden флаги;
  - availableValues с presentation;
  - object form для availableValues/multilang/composite type/expression.
- autoDates-сворачивание: для каждого StandardPeriod-параметра ищем пару
  dependent с expression `&P.ДатаНачала`/`&P.ДатаОкончания` (распознаём по
  expression, не по имени) и сворачиваем в @autoDates на родителе.
- decimal-тип всегда эмитится с явными (D,F) — JSON читаемее.
- useRestriction суппрессим в параметрах (auto-generated для @hidden).

Bit-perfect round-trip 7468→7468 байт на синтетике
(3 calc + 2 total + 5 параметров включая @autoDates).
Реальный ERP «АнализИзмененийЛичныхДанныхСотрудников» (1035 строк) —
0 warnings при декомпиляции.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 14:41:15 +03:00
Nick Shirokov 765e1d8885 feat(skd-decompile): слои 2-3 — dataSources, dataSets, поля
- Распознавание типов (string/decimal/boolean/date/dateTime/time/CatalogRef.X
  и пр.) с qualifiers (decimal-точность/знак, string Length/AllowedLength,
  date fractions) → shorthand или composite-массив.
- Роли (@dimension/@account/@balance/@period) с детектом сложных roleAttributes
  как sentinel.
- Restrictions (#noField/#noFilter/#noGroup/#noOrder) из useRestriction.
- Multilingual title с авто-сворачиванием {ru:"..."} в строку.
- appearance с поддержкой LocalStringType значений (например, Формат).
- presentationExpression.
- Свёртка дефолтного dataSource (ИсточникДанных1/Local) в умолчание.
- Автодетект object vs shorthand формы поля.

Bit-perfect round-trip на синтетике из 11 разнотипных полей.
Реальный ERP-отчёт АнализВерсийОбъектов декомпилируется с 0 warnings.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 13:54:17 +03:00
Nick Shirokov 643211f2fb docs(skd-decompile): честная формулировка scope в SKILL.md
Убрал ложное обещание «структурной эквивалентности» (DSL покрывает
подмножество СКД). Слил «Гарантии» и «Не поддерживается» в раздел
«Что получаешь» с тремя категориями (покрытое / sentinel / fail-fast).
Добавил Workflow — декомпил это начало процесса, а не финал.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 13:50:46 +03:00
Nick Shirokov 5ec21f24b4 feat(skd-decompile): scaffold — Ring 3 fail-fast, sentinel/warnings, query extraction
Layer 1 of the skd-decompile plan: SKILL.md with disable-model-invocation,
ps1 skeleton with XML→JSON pipeline, namespace probe for non-DCS root,
sentinel/warnings accumulator, and DataSetQuery extraction (query only).
Test case minimal-query demonstrates round-trip via skd-compile preRun.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 13:40:46 +03:00
Nick Shirokov 048edafc15 docs(skd-edit): document group-selection idiom for add-field
Добавляет одну строку-подсказку в add-field: для попадания в Selection
конкретной группировки (а не variant) — связка -NoSelection + add-selection
с @group=. Это уже работало, но не было явно зафиксировано в SKILL.md.

Расширять сам add-field параметром -Group/@group= не стали — текущий
двухкомандный идиом более атомарен и не создаёт edge cases вроде
взаимодействия @group= и -NoSelection.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 10:30:29 +03:00
Nick Shirokov 334241bea4 fix(skd-info): handle absolute -OutFile paths correctly
Раньше PS1-порт делал `Join-Path (Get-Location) $OutFile` без проверки,
что приводило к невалидным склейкам типа `C:\cwd\C:\abs\path.txt`, и
запись падала с «The given path's format is not supported».

Теперь: если путь абсолютный — нормализуется через `Path::GetFullPath`,
если относительный — резолвится против CWD. Python-порт уже был корректен,
только version bump.

Дополнительно: `args_extra` в runner.mjs теперь поддерживает подстановку
`{workDir}` — нужно для тестов с абсолютными путями внутри workspace.

Тесты: `skd-info/outfile-absolute-cyrillic` (PS + Python).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 10:19:20 +03:00
Nick Shirokov ce1ba0bab1 feat(skd-edit): normalize line endings + diagnostics on patch-query not-found
patch-query теперь нормализует CRLF/CR → LF в old/new/query перед поиском,
поэтому многострочные шаблоны с любым стилем переводов строк находятся
корректно (XmlDocument декодирует text-узлы как LF).

При not-found вместо сухого сообщения выводится воронка диагностики:
  1) cross-dataset probe — «Found in dataset 'Y' instead — wrong -DataSet?»
  2) tolerant probe (collapse whitespace + NBSP) — «would match with
     whitespace normalized» + точка расхождения
  3) prefix divergence — «matched N of M chars, expected 'X' (U+...) but
     got 'Y' (U+...)» + короткий контекст

Тесты: 4 новых кейса (positive CRLF-tolerant + 3 диагностических negative).
Регрессия 45/45 PS + 45/45 Python.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 19:53:24 +03:00
Nick Shirokov 6e14f2502e feat(skd-edit): empty parameter values, decimal/time/fix/composite
Brings skd-edit to parity with the skd-compile fixes from 449f814 / 0537410
/ ff2d851. Same helpers (Test-EmptyValue / Build-EmptyValueXml in ps1,
is_empty_value / build_empty_value_xml in py) shared by add-parameter,
modify-parameter (value=...), availableValues, add-dataParameter and
modify-dataParameter.

Behavior:
- Sentinel empty (null / "" / "_" / "null") serializes per declared type,
  matching what 1C Designer writes — ref/no-type → xsi:nil, string →
  xsi:type="xs:string" empty, date/time/decimal/boolean → typed zero,
  StandardPeriod → Custom + zero dates, dataParameters → dcscor:value
  xsi:nil="true". @valueList omits <value> entirely.
- Build-ValueTypeXml accepts bare decimal (10,2), decimal(N) (N,0),
  string(N,fix) (AllowedLength=Fixed), time (DateFractions=Time), and
  composite array of types.
- Parse-ParamShorthand / Parse-DataParamShorthand regex .+ → .* so a
  trailing `=` is treated as the empty-value sentinel. New @valueList flag.

New test cases: empty-param-values-add / -modify / empty-dataparam-values.
Three outdated skd-edit snapshots regenerated to reflect upstream skd-compile
empty-value emission (rename-parameter, reorder-parameters,
conditional-appearance-v2).

Regression: 41/41 ps1 + 41/41 py runner; 41/41 verify-snapshots ps1 + py
(live load into 1С 8.3.24). skd-compile 23/23 and skd-validate 15/15
unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 13:38:52 +03:00
Nick Shirokov ff2d8513c4 feat(skd-compile): time type, string(N,fix), and composite type parameters
Calibrated against live Designer output in upload/erf/ПроверкаЭкранирования.

- New type 'time' (synonym 'время'): xs:dateTime with DateFractions=Time
  for time-of-day values. Designer uses the same xs:dateTime XSD type as
  date/dateTime — only DateFractions differs. Empty value: typed-zero
  0001-01-01T00:00:00 (same as dateTime).

- Extended string regex to accept (N,fix) → AllowedLength=Fixed (was
  Variable-only). Non-empty fixed-string values are emitted as-given
  without space-padding to Length — the platform handles padding on save.

- Composite types in parameters (array of types in object form, e.g.
  ["string(10,fix)", "CatalogRef.X"]) now work end-to-end: valueType
  emits each type with its qualifiers, and empty composite values
  serialize as <value xsi:nil="true"/> matching Designer.

Test case empty-param-values extended with 5 new params covering all
three additions. Snapshot validated by skd-validate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 12:52:26 +03:00
Nick Shirokov efdf56691c fix(skd-validate): eliminate false positives on real ERP/БП reports
Calibrated against 1106 vendor reports (ERP 8.3.24 + БП 8.3.27).
Three categories of false positive removed:

- CalculatedField with empty <expression/> demoted error→warning.
  Three legitimate vendor patterns surfaced:
    * sibling totalField with same dataPath provides the formula
      (used in cancellation-rate and share-percentage reports)
    * groupTemplate references the field as group name
    * field exists only as a declarative anchor for settingsVariants
  Warning preserved so genuinely-missing formulas still surface.

- Duplicate template name demoted error→warning. Vendor configs ship
  reports (БазаНормируемыхРасходов/Выручка) with three <template> blocks
  named Макет1 — the platform identifies templates by position, not by
  <name>. Warning still flags the collision without failing validation.

- comparisonType whitelist extended with NotInHierarchy and
  NotInListByHierarchy. Existing list was missing the negated
  hierarchy operators used in 20 of the 1106 reports.

Result: 0 false positives across the corpus, all genuine errors still
caught (verified separately against intentionally-broken fixtures).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 12:27:21 +03:00
Nick Shirokov 12745b14c3 fix(skd-validate): handle composite valueType + system-type namespace
Calibrated against ~868 real ERP/БП reports — three false positives caught:

1. Composite types: <v8:Type>xs:string</v8:Type> followed by
   <v8:Type>d4p1:CatalogRef.X</v8:Type> with a single trailing
   <v8:StringQualifiers> is a legitimate pattern. Rewritten check to
   collect all <v8:Type> and qualifier blocks per <valueType>, then
   verify each qualifier has a matching scalar type anywhere in the
   block — not necessarily right before it.

2. System types: AccumulationRecordType (and similar enum-like system
   types) use the http://v8.1c.ru/8.1/data/enterprise namespace
   (without /current-config) and a plain TypeName local name with no
   dot. Whitelisted as a second valid namespace for ref-like types.

3. v8: scalar types extended: v8:Null, v8:Type, v8:ValueStorage —
   present in real configs as type-less placeholders.

Also reverted SKILL.md change from previous commit (validator details
don't belong in user-facing docs).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 12:12:48 +03:00
Nick Shirokov a5a1636918 feat(skd-validate): catch broken XDTO in valueType and value
skd-validate was purely structural (names/refs/duplicates) and missed an
entire class of bugs that XDTO rejects at db-load-xml — exactly the
kinds of mistakes the LLM (or hand-edits) commonly introduce.

New section 16: valueType structural — each <v8:Type> must have a known
prefix (xs:/v8: or any prefix bound to enterprise/current-config),
qualifier blocks must match their preceding type, and qualifier
internals (Digits/FractionDigits/AllowedSign, Length/AllowedLength,
DateFractions) must use legal tokens.

New section 17: value content — <value xsi:type="dcscor:DesignTimeValue">
rejects literal placeholders ('_') and empty strings, since these are
the exact symptom of the titan team's BUG-2.

5 new fixtures cover: bare-decimal, missing-qualifiers,
qualifier/type mismatch, ref-literal '_', bad AllowedSign token.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 11:57:20 +03:00
Nick Shirokov 05374100c1 fix(skd-compile): accept bare decimal and decimal(N) with sensible defaults
Emit-SingleValueType / emit_single_value_type previously required full
decimal(D,F) — anything else fell through to a fallback that produced
invalid <v8:Type>decimal</v8:Type> (no xs: prefix, no qualifiers).

New regex `^decimal(\((\d+)(,(\d+))?(,nonneg)?\))?$` accepts:
- decimal                → 10,2,Any (money default — most common 1C intent)
- decimal(N)             → N,0,Any (integer)
- decimal(N,nonneg)      → N,0,Nonnegative
- decimal(N,M)           → as before
- decimal(N,M,nonneg)    → as before

Synonyms (число, число(N), etc.) inherit the same forms via Resolve-TypeStr.

Shared Emit-ValueType is called from fields, parameters, and output
parameters — one fix covers all three paths. 3 existing snapshots
regenerated with proper xs:decimal + qualifiers, plus new
decimal-qualifier-defaults test case covering all 5 forms × synonyms.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 11:45:23 +03:00
Nick Shirokov 449f814d16 fix(skd-compile): Designer-compatible empty parameter values
Centralized empty-value handling: shorthand `=`, `= _`, `= null` and
object-form `value: null` / `""` now serialize per type, matching what 1C
Designer writes:
- ref / no-type → <value xsi:nil="true"/>
- string → <value xsi:type="xs:string"/>
- date/decimal/boolean → typed zero (0001-01-01 / 0 / false)
- StandardPeriod → Custom variant with zero dates
- @valueList → omit <value> entirely

Closes BUG-1 (StandardPeriod @autoDates) and BUG-2 (CatalogRef.X = _
producing invalid <value>_</value>) reported by titan team. New helpers
Test-EmptyValue / Emit-EmptyValue (ps1) and is_empty_value /
emit_empty_value (py) shared by Emit-ParamValue, availableValues loop,
and explicit dataParameters emit. Shorthand regex .+ → .* so trailing
`=` parses as empty.

Reference: upload/erf/ПроверкаЭкранирования (live Designer dump).
New test case empty-param-values covers all 10 type×sentinel combos;
3 existing snapshots regenerated to include the now-correct <value>
tags.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 11:27:24 +03:00
Nick Shirokov 3eaa7ffa3b fix(skd-edit): drop unneeded " → &quot; in query/expression
Зеркалим решение из skd-compile: убираем .Replace('"','&quot;') из Esc-Xml
и удаляем post-process, который принудительно ставил &quot; внутри
<query>/<expression>. Реальный Конфигуратор так не пишет — экранирование
было анти-1С-стилем и портило round-trip diff.

Снимок add-calculated-field-restrict обновлён под новый формат.
Кейс preserve-entities-modify-parameter-title удалён: его смысл
инвертировался (теперь проверял бы нормализацию, а не сохранение),
а часть про многострочный xmlns уже покрыта preserve-xmlns-multiline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 16:51:04 +03:00
Nick Shirokov 98ebb478ee fix(skd-compile): drop unneeded " → &quot; — matches Designer style
Конфигуратор внутри текстового контента <query>/<expression> оставляет " сырыми
(проверено на ERP DCS: 1504 raw " против 0 &quot;). Убираем .Replace('"','&quot;')
из esc_xml — теперь round-trip diff против типовых остаётся чистым.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 16:50:55 +03:00
Nick Shirokov fb67b1b80d fix(skd-edit): realistic multilang fixture (ERP-style appearance block)
multilang-base/Template.xml содержал <editFormat xsi:type="v8:LocalStringType">
на <field xsi:type="DataSetFieldField">, что нелегально по XDTO-схеме DCS —
1С Designer падал с "Исключение XDTO" при загрузке через
LoadExternalDataProcessorOrReportFromFiles. Snapshot-тесты этого не ловили
(только byte-equality), а platform-verify (tests/skills/verify-snapshots.mjs)
ронялся на трёх кейсах с этой фикстурой.

Заменил <editFormat> на реалистичный <appearance> блок с вложенным
<dcscor:item xsi:type="dcsset:SettingsParameterValue"> и многоязычным
<dcscor:value> (ru + en) — структура взята из типовой ERP-выгрузки. Это
даёт более правильный test для preserve-unknown-children: <appearance>
содержит вложенный multi-lang xsi:type-узел, который точно прошёл бы
через DOM round-trip с искажениями, если бы _unknownChildren не работал.

preserve-unknown-children-modify-field: shorthand изменён с
"@ignoreNullsInGroups" на "@dimension" (no-op по составу role, но
триггерит rebuild). Прежний @ignoreNullsInGroups без @dimension давал
комбинацию, которую Designer отвергает (ignoreNullsInGroups валиден
только в контексте resource-роли).

39/39 snapshot suite (PS+PY) + 39/39 platform verify через erf-build →
Designer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 15:19:13 +03:00
Nick Shirokov 79db5de6ee fix(skd-edit): preserve multi-lang title + unknown children in modify-*
В типовых конфигурациях (ERP, БП, ЗУП и т.д.) у полей и параметров обычно
есть мульти-язык title (ru + en, иногда + локализация). До этого modify-field /
modify-parameter / modify-dataParameter, перестраивая элемент через
Build-MLTextXml, оставляли только последнее найденное <v8:content> в ru —
en/uk/kk siblings молча терялись, и при следующей выгрузке Designer
ломал миграцию.

Read-FieldProperties сохраняет полный OuterXml <title> в _rawTitle и
коллекционирует OuterXml неизвестных дочерних элементов
(<editFormat>, <appearance>, кастомные расширения) в _unknownChildren.
Build-FieldFragment эмитит:
* _rawTitle как есть, если user не задал новый title;
* Patch-MLTextRu(_rawTitle, newRu) если user задал ru-override — патчит
  только <v8:content> в <v8:lang>ru</v8:lang>, остальные языки сохраняет;
* _unknownChildren в конце поля (после valueType).

modify-parameter аналогично: при title-override проверяет multi-lang
(>1 <v8:item>) и патчит ru через Patch-MLTextRu, иначе ребилдит ru-only.

set-field-role сохраняет нестандартные подэлементы <role> (например
<dcscom:addition>, <dcscom:groupFields>), не входящие в фиксированный
known-children set и не указанные через kv в shorthand.

xmlns-стрип на захваченных OuterXml — лишние декларации (которые сериализаторы
добавляют для standalone-фрагментов) убираются.

PY: lxml etree.tostring по умолчанию включает .tail (whitespace после
закрывающего тега), что приводило к non-idempotent ростy whitespace при
повторных прогонах. Везде добавлен with_tail=False.

Новые тесты с idempotent: true:
* preserve-multilang-modify-field (ru-override на multi-lang title);
* preserve-multilang-modify-parameter (то же для параметра);
* preserve-unknown-children-modify-field (role flag, проверяем что
  <editFormat> и en title не теряются).

Общая fixture: multilang-base/Template.xml с полем и параметром,
у каждого ru + en title; поле также имеет <editFormat>.

39/39 PS + 39/39 PY. skd-edit v1.20 -> v1.21.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 15:18:07 +03:00
Nick Shirokov 23d2cb42de fix(skd-edit): preserve <valueType>, detect line endings, drop CRLF leak
Targeted follow-ups к round-trip фиксу:

* modify-field больше не теряет <valueType> при перестройке поля —
  Read-FieldProperties сохраняет полный OuterXml элемента (StringQualifiers,
  NumberQualifiers, DateQualifiers и т.п.), Build-FieldFragment отдаёт его
  обратно. Лишние xmlns-декларации, добавляемые сериализатором при
  выгрузке поддерева, стрипаются регексом.
* Line-ending convention теперь определяется при load (CRLF vs LF) и
  единообразно применяется в финале save. Раньше CreateWhitespace и
  Build-*Fragment везде использовали CRLF, что приводило к смешанным
  переносам в LF-исходниках (и наоборот) и к non-idempotent выходу
  modify-parameter title (run 1 → \n\t\t<title>\r\n... → run 2 →
  \r\n\t\t<title>\r\n...).
* PS Insert-BeforeElement переведён на LF; все -join "`r`n" → "`n";
  py "\r\n".join → "\n". Конечная нормализация переносов делается в
  save в соответствии со script:LineEnding.
* preserve-entities-modify-parameter-title.json теперь idempotent: true
  (после фикса CRLF leak'а двойной прогон byte-identical).

На реальной схеме diff после modify-field составил 30 строк: целевая
вставка title плюс полезная одноразовая коррекция ранее повреждённых
&quot; в text-content <dcsat:expression>. modify-field идемпотентен.

skd-edit v1.19 -> v1.20.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 15:18:07 +03:00
Nick Shirokov 511bfe7fdf fix(skd-edit): NO-OP skip + format-preserve post-process (round-trip)
XmlDocument round-trip искажал Template.xml даже при отсутствии правок:
декодировал &quot; в <query>/<expression>, схлопывал многострочный xmlns
корня, добавлял пробел перед /> и записывал файл при [WARN] not found.

Дирти-флаг ($script:Dirty / dirty) ставится только на успешной мутации;
финальный save пропускается с [INFO] No changes -- file untouched, если
ни одна операция в batch ничего не изменила. Post-process после OuterXml
восстанавливает raw-форматирование корневого xmlns из исходного файла,
re-escape `"` в текстах <query>/<expression> с anchored regex (не задевая
xsi:type="..."), и нормализует <foo .../> к <foo.../>.

Замеры на реальной схеме после modify-field: diff упал с 423 строк до 37
(94% шума устранено), повторный прогон byte-identical.

В runner.mjs добавлен caseData.idempotent: re-run + byte-equality на всех
файлах workDir. Три новых кейса (NO-OP, entity-preserve, xmlns-multiline)
+ общий fixture roundtrip-base. Все 33 ранее существовавших snapshot
перегенерированы под корректное форматирование (восстанавливают то, что
старый skd-edit ломал).

skd-edit v1.18 -> v1.19. PS и PY порты синхронизированы.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 15:17:42 +03:00
Nick Shirokov f91b569564 docs(web-test): спецификация регресс-движка + чистка regress.md
Новый канонический документ docs/web-test-regression-spec.md —
техническое описание движка регрессионных тестов: CLI, формат
тест-модулей, ctx-контракт, утверждения, три уровня хуков
(инфра/тест/контекст), конфиг, контексты Playwright и режимы
изоляции, форматы отчётов (JSON/Allure/JUnit), обнаружение тестов,
ошибки/таймауты/повторы, анализ результатов, глоссарий.

Документ предназначен для CI-интеграторов, ручного редактирования
сгенерированных тестов и сопровождения самого движка. Без дорожной
карты и внутренних self-тестов — только публичный контракт.

regress.md в скилле почищен: добавлены контракт ctx и список
утверждений (раньше модели приходилось читать исходники), срезаны
дубликаты с SKILL.md (live recon, паттерны catalog/document),
переформулированы анти-паттерны под специфику регресс-движка.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 23:01:38 +03:00
Nick Shirokov e93185c18b fix(web-test): сериализовать onecError и платформенный стек 1С в JSON / Allure
В тест-обёртке ACTION_FN при 1С-исключении на throw-ed Error
вешалась полная структура (step, args, errors, formState, stack,
screenshot), но при сборке отчёта движок брал из неё только
{message, step, screenshot} — остальные поля терялись. Платформенный
стек 1С, ради которого делается fetchErrorStack, в JSON-отчёт не
попадал; в Allure statusDetails.trace писался только log()-вывод
теста.

Что поменялось:
- errInfo собирается один раз после teardown (раньше был дубликат на
  732 и 745), используется и для ctx.testResult (afterEach), и для
  lastError, и для итоговой записи в results[].
- В errInfo добавлено поле onecError: e.onecError — структура с
  stack.entries[{location, code}], formState, args, errors доезжает
  до JSON-отчёта без обрезания.
- writeAllure склеивает statusDetails.trace из tr.output + (если есть)
  onecError.stack.raw под разделителем "--- 1C stack ---". В Allure UI
  платформенный стек теперь виден прямо в карточке упавшего теста.

Обратная совместимость: для падений без 1С-исключения (assertion,
навигация и т.п.) e.onecError === undefined → JSON.stringify его
выкидывает, форма записи { message, screenshot } сохраняется в
точности.

Проверено вручную на стенде tests/web-test/ — падающий тест с
ВызватьИсключение, JSON и Allure оба содержат полный stack.entries и
формированный trace.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 23:01:25 +03:00
Nick Shirokov 7fa279c354 feat(skd-edit): clear-conditionalAppearance + multiline patch-query (доки)
- Новая операция clear-conditionalAppearance в стиле clear-selection/
  order/filter. Закрывает потребность "заменить набор правил оформления"
  через clear + re-add.
- patch-query: многострочные подстроки уже работали (string.Replace
  корректно обрабатывает \n). Зафиксировано в SKILL.md.
- add-total: shorthand-шаблон с тремя случаями (Func, Func(expr),
  identity-выражение) — после fix Bug 6 поведение нужно явно объяснить.
- Косметика: убрана утечка XML-внутренностей в комментарии примера
  set-field-role @period.
- Пример patch-query @once заменён на более типовой случай уникальной
  подстроки (КАК ВТ_СтароеИмя вместо ЛЕВОЕ СОЕДИНЕНИЕ).

Регресс: 33/33 PS, 33/33 PY, 33/33 платформенный verify.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 17:39:24 +03:00
Nick Shirokov 28a2a34c84 fix(skd-edit): add-total identity expression для не-аггрегатных функций
Раньше "DataPath: X" всегда заворачивалось в X(DataPath). Если X не
аггрегатная функция (например, имя другого ресурса или сам DataPath),
получалось некорректное выражение типа Проверка(Проверка).

Зеркалю логику из skd-compile: whitelist аггрегатных функций
(Сумма, Количество, Минимум, Максимум, Среднее + EN-варианты).
Для остального — identity (использовать funcPart как есть).

Сообщение [OK] теперь показывает фактически записанный expression.

Регресс: 32/32 PS, 32/32 PY, 32/32 платформенный verify.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 16:16:27 +03:00
Nick Shirokov f0f1e88aaa feat(skd-edit): patch-query @once — assert ровно одно вхождение
Защищает от случайных замен в комментариях/совпадениях имён:

  "ЛЕВОЕ СОЕДИНЕНИЕ => ВНУТРЕННЕЕ СОЕДИНЕНИЕ @once"
  # fail, если в запросе 0 или 2+ вхождений

Без флага default — replace-all (как раньше, обратная совместимость).

При успехе сообщение содержит фактическое число вхождений
"(N occurrence(s))", помогает заметить неожиданную множественность
без явного @once.

Регресс: 31/31 PS, 31/31 PY, 31/31 платформенный verify.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 16:00:55 +03:00
Nick Shirokov e7cbf306a0 feat(skd-edit): availableValue — список с replace-семантикой и в add-parameter
- Единый list-синтаксис: availableValue=v1[: p1], v2[: p2], ...
  Элементы через запятую, представление после двоеточия.
- Запятые/двоеточия внутри значений и представлений — в одинарных кавычках:
  availableValue=Окр1: 'руб., коп.', Окр1000: руб.
- add-parameter теперь принимает availableValue= и создаёт начальный список
  в одном вызове (раньше требовался последующий modify-parameter).
- modify-parameter availableValue=... ЗАМЕНЯЕТ весь список (раньше
  append). Согласуется с остальными modify-* для одиночных свойств.
- SKILL.md: добавлен shorthand-шаблон для modify-parameter,
  расширен для add-parameter [availableValue=список].

Существующие тесты мигрированы со старого ;;-batch на новый list-синтаксис.
Снапшоты сохранились (тесты стартовали с пустого списка — semantics
совпадает для greenfield).

Регресс: 29/29 PS, 29/29 PY, 29/29 платформенный verify.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 15:37:56 +03:00
Nick Shirokov 610720334b feat(skd-info): kv-параметры роли в детализации поля
В -Mode fields -Name <field> к сводке Role добавляются не-bool
параметры роли (balanceGroupName, balanceType, parentDimension,
accountTypeExpression и т.д.) в формате name=value.

Bool-флаги (@balance, @dimension, ...) отображаются как раньше.
False-значения по-прежнему скрыты.

Регресс: 6/6 PS, 6/6 PY (существующие snapshots не задеты).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 14:54:03 +03:00
Nick Shirokov 5090deb5bc feat(skd-edit): set-field-role — управление ролями поля
Новая операция: полная замена <role>-блока поля dataSet.

- Shorthand: "<dataPath> [@флаги] [kv=значение]"
- Флаги (зеркало skd-compile): @balance, @dimension, @account, @period,
  @required, @autoOrder, @ignoreNullValues
- KV: balanceGroupName, balanceType, parentDimension, accountTypeExpression,
  orderType, expression, periodNumber, periodType
- Пустой spec (только dataPath) — снимает роль целиком
- Поддерживает пакетный режим

Закрывает потребность временного toggle off/on роли при отладке
(было: ручной Edit XML), а также корректировку balance/dimension
после add-total.

Регресс: 27/27 PS, 27/27 PY, 27/27 платформенный verify.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 14:53:55 +03:00
Nick Shirokov 8b0bcf0194 feat(skd-edit): флаги @hidden и @always для параметров
- @hidden — скрывает параметр от пользовательских настроек
  (useRestriction=true + availableAsField=false). Для констант-параметров.
- @always — параметр всегда подставляется в запрос (use=Always).
  Используется самостоятельно для видимых обязательных параметров.
- Композируются: @hidden @always одной строкой даёт типовой паттерн
  "скрытая константа всегда применяется".
- Поддержка в add-parameter и modify-parameter, идемпотентны.

Регресс: 25/25 PS, 25/25 PY, 25/25 платформенный verify.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 14:14:19 +03:00
Nick Shirokov 529a5cacae feat(skd-edit): modify-structure + фиксы set-structure/parameter/patch-query
- modify-structure: новая операция, меняет groupItems группы по @name=,
  сохраняя Selection/order/filter/conditionalAppearance (Bug 1)
- set-structure: shorthand поддерживает запятую для нескольких полей
  в одном уровне группировки (Bug 2)
- set-structure: @name= с обрамляющими кавычками (двойными/одинарными)
  снимает их при записи в <dcsset:name> (Bug 3)
- add-parameter: ссылочные типы (CatalogRef, ChartOfAccountsRef, …)
  пишут <value xsi:type="dcscor:DesignTimeValue">, не xs:string (Bug 4a)
- modify-parameter: namespace-aware lookup существующих свойств
  — обновляет inplace, не плодит дубли (Bug 4b)
- modify-parameter value=…: пересборка <value> с корректным xsi:type
  из <valueType> (попутно лечит ранее битый XML)
- patch-query: батч ;;-сегментов триммится по краям (Bug 5)
- skd-compile: симметричный фикс ссылочных типов в emit_value

Регресс: 23/23 PS, 23/23 PY (skd-edit), 21/21 PS+PY (skd-compile),
23/23 платформенный verify-snapshots.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 13:40:49 +03:00
Nick Shirokov 8b0f55f1cc feat(form-validate): silent-skip числовых и UUID-DataPath в Check 5
В реальных выгрузках ERP/БП встречаются непрозрачные платформенные
DataPath, которые невозможно проверить из одного Form.xml:
- bare numeric ("10", "1000003") — внутренние индексы платформы
- "N/M:<uuid>" — ссылка на метаданные по UUID

Раньше Check 5 ругался на них "attribute not found". Теперь такие
пути пропускаются без счёта в paths checked и без ошибки.

Реалистичные пользовательские опечатки (кириллица в имени атрибута)
продолжают ловиться обычной проверкой attrMap.

Добавлен тест-кейс datapath-opaque-refs, версия v1.5 → v1.6.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 11:40:27 +03:00
Nick Shirokov 54cbc69a59 feat(form-validate): резолв Items.<Table>.CurrentData.* и ~<Attr>.* в DataPath
В Check 5 раньше брался первый сегмент DataPath и искался в attrMap,
из-за чего ложно ругались реальные формы ERP/БП с путями вида
Items.<TableName>.CurrentData.<Field> (подвалы, инфо-панели) и
~<DynamicListAttr>.<Field> (текущая строка списка).

Теперь:
- ведущий ~ стрипается перед разбором сегментов;
- для Items.<Table>.CurrentData.* находим элемент-таблицу по name,
  берём её <DataPath> (атрибут DynamicList/TableSection) и проверяем
  его в attrMap. Если таблицы нет — Error; если третий сегмент не
  CurrentData — Warn.

Добавлен тест-кейс datapath-currentdata, версия скриптов v1.4 → v1.5.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 11:38:56 +03:00
Nick Shirokov ac3047cf55 docs(readme): ссылка на гайд регрессионного тестирования
Добавлена строка в таблицу навыков и в перечень docs/ для нового
docs/web-test-regression-guide.md (был забыт при первичном коммите
регресс-гайда).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 20:41:33 +03:00
Nick Shirokov 5da154adea Merge feature/web-test-runner into dev
web-test regression runner: M5-pre синтетика + M6 автономный стенд +
M7 testInfo/contexts/testlevel-хуки + M8 per-context lifecycle +
Allure-форматтер с auto-suite/severity + _allure/categories.json +
пользовательский гайд регресса (docs/web-test-regression-guide.md) +
skill-инструкция regress.md.
2026-05-13 20:28:39 +03:00
Nick Shirokov f4748d76af docs(web-test): пользовательский гайд регресса + skill-инструкция regress.md
- docs/web-test-regression-guide.md — пользовательские сценарии работы
  с моделью для покрытия прикладного решения регрессом (русский, по
  аналогии с web-test-recording-guide.md): структура tests/<app-name>/,
  диалоги с моделью, пример организации покрытия, отчёты Allure +
  categories.json.
- .claude/skills/web-test/regress.md — инструкция модели по написанию
  регрессионного набора: разведка (метаданные + живой проход через exec),
  layout по фичам, готовые шаблоны (CRUD/document/DCS/multi-user/repro),
  severity, anti-patterns, failure triage, _allure/ конвенция.
- SKILL.md — указатель на regress.md в конце файла (рядом с recording).
- docs/web-test-runner-spec.md → upload/ (был внутренним планом
  разработки, не пользовательской документацией).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 20:25:44 +03:00
Nick Shirokov b992cd11c5 feat(web-test): _allure/ конвенция + categories.json для триажа падений
run.mjs:
- syncAllureExtras(testDir, reportDir) копирует все файлы из
  <testDir>/_allure/ в reportDir перед генерацией отчёта. Underscore
  в имени параллелен _hooks.mjs (инфра, не тест) — discovery его
  пропускает.
- Вызов после writeAllure при --format=allure.

tests/web-test/_allure/categories.json — 7 правил классификации падений
по нашему 1С-домену:
  1. License pool exhausted (1C) — известный multi-context flake.
  2. 1C application error (modal) — exception modal через fetchErrorStack.
  3. Section panel icon-only — деградация состояния стенда.
  4. Navigation lookup miss — navigateSection/openCommand/navigateLink/switchTab.
  5. Element not found — clickElement/fillField/selectValue/closeForm/fillTableRow/deleteTableRow.
  6. Test timeout — Timeout (Nms) от раннера.
  7. Assertion failure — наши createAssertions + 1С-specific (formHasField/tableHasRow/noErrors).

spec §9: раздел «Доп. файлы Allure через <testDir>/_allure/» с таблицей
поддерживаемых типов (categories.json / environment.properties /
executor.json) и минимальным примером.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 18:53:09 +03:00
Nick Shirokov fc76407877 feat(web-test): auto-suite + severity-резолвер для Allure
run.mjs:
- buildSeverityIndex(config) — валидация config.severity (inverted map
  «уровень → [теги]») при загрузке: ключи только из blocker|critical|
  normal|minor|trivial, теги не дублируются между bucket'ами,
  defaultSeverity тоже валидируется. fail-fast через die.
- resolveSeverity(t, severityIndex):
  1. mod.severity если задан и валидный — выигрывает.
  2. max-rank среди тегов (стандартные имена severity или маппинг).
  3. config.defaultSeverity или 'normal'.
  Rank: blocker(5) > critical(4) > normal(3) > minor(2) > trivial(1).
  Max-wins инвариантен к порядку тегов.
- writeAllure: добавлены labels suite (= dirname(t.file) или 'root') +
  severity. Тег `tag` остался как раньше.
- testResult пробрасывает t.severity (для passed/failed веток).
- SEVERITY_RANK/LEVELS объявлены в модульной шапке (top-level await на
  cmdTest начинается до конца тела модуля, TDZ-аккуратность).

webtest.config.mjs: severity policy для нашего сьюта (smoke +
multi-context → critical, recording → minor, defaultSeverity = normal).

spec.md §7: раздел про severity-policy в конфиге с валидацией.
spec.md §9: «Авто-эмиссия label-ов» — tag/suite/severity + правила резолва.

Регресс 19/19 ✓ (9m 7.6s). Распределение по уровням после исправления
'record' → 'recording' в маппинге: 13 critical / 5 normal / 1 minor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 18:37:58 +03:00
Nick Shirokov a55195ab66 docs(web-test): §16.1 — вложенные каталоги (что работает, что нет)
Зафиксирована конвенция:
- Discovery рекурсивный, путь попадает в отчёт.
- Per-folder hooks/config/context-default НЕ поддерживаются (by design).
- Группировку в отчётах делать через tags, не через путь.
- Сортировка по полному пути (`warehouse/01-x` после `sales/02-y`) —
  для глобального порядка нужны 3-значные префиксы или теги-фазы.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 17:33:10 +03:00
Nick Shirokov 1eff62de42 docs(web-test): полный sync спеки + contexts[] в testResult
spec.md v0.2 (последний sync 2026-05-13):

§1 CLI: добавлены --report-dir и `--` separator в таблицу флагов.
§1 «Режим выполнения»: убрана несуществующая «группировка по контексту»,
  описана реальная алфавитная модель + lazy ensureContext.
§2 пример multi-context: latin ID контекстов вместо кириллицы (clerk/manager)
  + showcase closeContext в финальном шаге.
§3 список API расширен: контексты (createContext/closeContext/setActive/
  listContexts/hasContext/getActiveContext), overlay-helpers (hideTitleSlide/
  hideImage/setHighlight/isHighlightMode), error-helpers (dismissPendingErrors/
  fetchErrorStack).
§6 пример _hooks.mjs: убран mock-навигация в beforeAll, добавлены примеры
  afterOpenContext/beforeCloseContext, afterEach показывает testResult.
§8 переписан раздел «Реализация в browser.mjs» (мульти-контекст уже live)
  + новая таблица режимов изоляции tab/window.
§9 JSON example: поле "context" → "contexts": [...] (массив).
§10: убрано упоминание несуществующего verbose-режима.
§13 «Параметризация»: убран статус «будущее», описана реальная семантика
  T6 (template name, param 2-м аргументом, testInfo.param).
§14 buildContext: переписан под done-состояние + scoped-вариант.
§16 каталог тест-кейсов: 13 → 19 файлов (multi-context, recording,
  errors-stack, tree-form, misc, hooks).
§17 дорожная карта: 10 → 18 пунктов, M4–M8 включены.

run.mjs:
- testResult получил поле contexts: [...names] во всех ветках
  (passed/failed/skipped/context-setup-failed). Раннер передаёт
  declaredContexts из единой точки до if(skip), чтобы skip-результаты
  тоже несли структурную информацию.

Регресс 19/19 ✓ (9m 8.7s) после --rebuild-stand.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 17:11:51 +03:00
Nick Shirokov eb87be5c04 feat(web-test): M8 — per-context lifecycle (closeContext + afterOpenContext/beforeCloseContext)
browser.mjs:
- + closeContext(name): logout slot + close page (tab) или context (window),
  удаление из реестра. Throw если name неактивен (рулило: nicht den aktiven
  closen, recorder always attached к active → invariant простой).
- _logoutSlot(slot, waitMs) — извлечён из disconnect, переиспользуется в
  closeContext.

run.mjs:
- ensureContext() после createContext вызывает hooks.afterOpenContext(ctx, name, spec).
- wrapCloseContextHook() оборачивает ctx.closeContext (и каждую scoped-обёртку)
  чтобы перед browser.closeContext fir'ить hooks.beforeCloseContext.
- Финальный teardown в finally: для всех живых контекстов кроме первого
  (survivor) — beforeCloseContext + closeContext; для survivor только хук,
  его закрывает disconnect().

_hooks.mjs v0.5:
- afterOpenContext инжектит persistent DOM-badge с displayName в правый
  верхний угол page — в записанном видео всегда видно, какой контекст.
- beforeCloseContext counter-only.
- _state расширен полями afterOpenContext / beforeCloseContext.

15-multi-context-handover.test.mjs:
- +2 шага: closeContext('b') после handover, попытка closeContext(active)
  ловится throw'ом с проверкой message.

00-hooks.test.mjs:
- +1 ассерт: afterOpenContext >= 1 (default уже создан), beforeCloseContext === 0
  в теле первого теста.

spec §6:
- Раздел «Контекстный уровень» (afterOpenContext / beforeCloseContext + правила closeContext).
- ASCII-диаграмма порядка хуков обновлена с per-context lifecycle.

Регресс 19/19 ✓ (9m 16.8s).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 16:07:45 +03:00
Nick Shirokov 43ed9ba142 feat(web-test): M7.5 — title slide в beforeEach для --record
_hooks.mjs v0.4: beforeEach под условием ctx.isRecording() показывает
title slide с testInfo.name + displayName первичного контекста как
subtitle, ждёт 1.5с через ctx.wait() и убирает.

В обычном регрессе (без --record) — ветка скипается, overhead ноль.
Под --record: 01-navigation 12.1s → 13.9s (+1.8с на слайд).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 14:45:14 +03:00
Nick Shirokov 588382cec1 feat(web-test): M7.4 — testlevel-хуки + 00-hooks индикатор
_hooks.mjs v0.3: добавлены beforeAll/afterAll/beforeEach/afterEach
(counter-only) и shared `_state` (счётчики + events log).

tests/web-test/00-hooks.test.mjs (новый, 4 шага, 0s) — индикатор
порядка вызовов: проверяет beforeAll===1, beforeEach для текущего
теста, доступность ctx.testInfo, afterEach < beforeEach.

Multi-context хуки оставлены one-shot. Разведка beforeAll:
navigateSection не нужен, 1С после входа уже на дефолтной секции.

Регресс 19/19 ✓ (9m 12.7s).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 14:35:20 +03:00
Nick Shirokov e0197683e1 feat(web-test): M7.1+M7.2 — ctx.testInfo + проброс custom-полей контекстов
- ctx.testInfo (name/file/filePath/tags/timeout/attempt/maxAttempts/param/contexts/primaryContext)
  выставляется перед каждой попыткой, доступен в beforeEach/test/afterEach
- ctx.testResult (status/duration/attempts/error/steps) доступен в afterEach
- run.mjs:411 spread полного contextSpec (был whitelist {url, isolation});
  CLI --url override сохраняет custom-поля через merge
- webtest.config.mjs: displayName для a/b
- spec §3 — подраздел «Метаданные теста», §6 — availability testInfo/testResult,
  §7 — рекомендация латинский ID + кириллический displayName
- Full regression 18/18 ✓ (9m 9.8s)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 12:44:07 +03:00
Nick Shirokov 96dad75b2f feat(web-test): M6-MVP follow-up — 13-misc setup + URL webtest-runner
13-misc.test.mjs: setup-шаг упрощён до `assert.ok(existsSync(epfPath))`.
EPF-сборку (epf-init → form-add → form-compile → epf-build) забрал
_hooks.mjs.prepare() — здесь только проверка артефакта с понятной
ошибкой при отсутствии: «запустите раннер с `-- --rebuild-epf`».

webtest.config.mjs: URL обоих контекстов переключён на
`/webtest-runner/ru_RU` — отдельная публикация автономного стенда,
не конфликтует с интерактивной разведкой через `/webtest` на 8081.
2026-05-12 20:25:54 +03:00
Nick Shirokov 5c734202b6 feat(web-test): M6-MVP — автономный стенд через _hooks.mjs
Новый tests/web-test/_hooks.mjs v0.2 с prepare()/cleanup().
prepare() поднимает изолированный стенд:
- Hash-locks `tests/skills/.cache/webtest-stand/{config,epf}.lock`
  на sha256 от build-steps и EPF_SPEC — автоматический skip
  пересборки при отсутствии изменений.
- Слои конфиг XML / БД / EPF пересобираются независимо. Триггер
  ручной — флаги `--rebuild-config`/`--reload-data`/`--rebuild-epf`/
  `--rebuild-stand` (через `-- ...` после CLI раннера).
- Smart Apache: web-stop+web-publish выполняются только когда
  пересоздаём БД (нужно освободить блокировку). Иначе probe-first:
  жив (200) → no-op; мёртв → publish + probeReady. На warm-старте
  prepare сводится к чтению локов и одному probe (~200ms).
- web-publish на собственном AppName `webtest-runner` :9191 — не
  пересекается с интерактивной публикацией `webtest`.
- Кросс-платформенно: env WEBTEST_HOOKS_RUNTIME=python переключает
  на зеркальные py-порты скиллов (для не-Windows стендов).

cleanup() пока stub — оставляем стенд поднятым между прогонами,
для full-shutdown ручной /web-stop или `-- --rebuild-stand`.

E2E-проверено: cold-start `--rebuild-stand` поднимает стенд за
~38s; warm-старт prepare = 0.0s; полный регресс 18/18 зелёный
за 9m 7.1s (включая оба multi-context-теста, которые исторически
флапали).
2026-05-12 20:25:47 +03:00
Nick Shirokov a92bce05fb feat(web-test): runner v1.11 — -- separator + spec §6.1
В CLI раннера всё после `--` собирается в массив hookArgs и
передаётся в инфра-хуки prepare/cleanup без интерпретации со
стороны раннера. Сигнатура расширена до { hookArgs, log, config }:
log — структурированный вывод раннера, config — разобранный
webtest.config.mjs. Шаблон «всё после `--` принадлежит вложенному
инструменту» — стандартная shell-конвенция (npm, cargo, pytest).

Спека §6 обновлена под новую сигнатуру, §6.1 закрепляет контракт
`--` ↔ hookArgs с примером. Help-строка раннера упоминает
разделитель.
2026-05-12 20:25:33 +03:00
Nick Shirokov b8ebbf6a6f feat(build-webtest-db): v0.2 — dual-mode CLI + module exports
Извлечены exports: getProjectInfo, resolveScript, execSkill,
replacePlaceholders, runSteps, platformLoadSteps, loadBuildSteps.
CLI-режим сохранён через import.meta.url-guard. Подготовка к
переиспользованию из tests/web-test/_hooks.mjs без дублирования
exec-логики и pipeline-шагов.
2026-05-12 20:25:25 +03:00
Nick Shirokov 43ba6ce16c feat(web-test): M5-pre #4b — 09-filter/unfilter-specific (multi-badge)
Раньше шаг был deferred с комментарием «требует список с видимой
filter-панелью». На самом деле существующая абстракция работает:
два advanced filterList на разных колонках Контрагентов создают
два badge'а в state.filters[], а unfilterList({field}) снимает
конкретный — оставляя остальные.

Новый шаг 09-filter/unfilter-specific (~14s):
- filterList('ООО', {field:'Наименование'}) + filterList('123', {field:'ИНН'})
  → state.filters = [{field:'Наименование',value:'ООО'}, {field:'ИНН',value:'123'}]
- unfilterList({field:'ИНН'}) → остался только Наименование badge
- unfilterList() → пусто

Старый комментарий «defer to filter-panel synthetic» удалён —
оказался устаревшим (видимо unfilterList({field}) уже умел работать
с advanced-filter badge'ами на синтетических списках).

timeout 09-filter поднят с 60000 → 120000ms (8 шагов теперь, +14s
для unfilter-specific).

Регресс: 16/18 зелёных. Два multi-context-теста (14/15) упали на
лицензионном пределе 1С — known environmental issue, не связано с
этим коммитом.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 18:01:00 +03:00
Nick Shirokov 51e37f9874 feat(web-test): M5-pre #4a — Менеджер (choiceHistoryOnInput=Auto) + selectValue/auto-history
Реквизит шапки ПриходнаяНакладная.Менеджер типа CatalogRef.Контрагенты
с дефолтным choiceHistoryOnInput=Auto. Существующий Контрагент в той же
шапке имеет DontUse, что даёт парный контраст для тестирования влияния
флага на selectValue.

Новый шаг 04-selectvalue/auto-history:
- selectValue('Менеджер', 'ООО Юг') → method='dropdown' (typeahead активен,
  префиксный поиск по Description находит «ООО Юг» в catalogue).
- Парный 04-selectvalue/direct-form (existing): selectValue('Контрагент',
  'Север') → method='form' (typeahead подавлен DontUse → форма выбора).

Тест покрывает существующее ветвление selectValue по флагу
choiceHistoryOnInput без engine-доработок. Истории на сервере писать
заранее не нужно: typeahead использует prefix-match по Description,
а не статистику истории.

Полный регресс **18/18 зелёный** (8m 47.3s).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 16:56:00 +03:00
Nick Shirokov 62e864e474 feat(web-test): M5-pre #3 — textEdit:false поле + 03-fillfields/direct-edit-form
Расширение синтетики: реквизит Поставщик типа CatalogRef.Контрагенты
добавлен в шапку ПриходнаяНакладная. Элемент формы Поставщик скомпилирован
с textEdit:false (новый DSL ключ form-compile v1.21 из коммита 32bf9c1):
ручной ввод запрещён, селект-кнопки нет, выбор только через форму выбора
по pick-кнопке.

Новый шаг 03-fillfields/direct-edit-form (~7s) — fillFields на Поставщик
('ООО Юг') возвращает method:'form', минуя обычные paste/typeahead/dropdown
ветки. fillFields внутренне детектит textEdit:false и сразу идёт через
форму выбора (selectValue path).

Полный регресс **18/18 зелёный** (8m 40.6s).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 16:11:46 +03:00
Nick Shirokov ddebd7b6df feat(web-test): M5-pre #2 — составной тип Источник + 03-fillfields/composite
Расширение синтетики: реквизит Источник составного типа
(CatalogRef.Контрагенты + CatalogRef.Номенклатура + CatalogRef.Организации)
добавлен в шапку ПриходнаяНакладная и в ТЧ Товары. meta-compile принимает
составной тип через строковый синтаксис `A + B + C` (см. SKILL.md:56) —
эмитит три `<v8:Type>` элемента с правильным `d5p1:` префиксом.

Элемент ТЧ-колонки переименован в ИсточникТЧ (path/title оставлены
оригинальные) — иначе form-compile генерирует одинаковые companion-имена
(`ИсточникКонтекстноеМеню`) для шапки и ТЧ, и платформа отказывает в
открытии формы документа: "К сожалению, возникла непредвиденная ошибка"
(server-side, без полезного stack). TODO в form-compile-bugs.md: учитывать
путь поля при генерации companion-имён, чтобы избежать конфликта.

Новый шаг 03-fillfields/composite (~25s) — покрывает selectValue с
параметром `{type}` на составном поле:
- Шапка: selectValue('Источник', 'ООО Север', {type:'Контрагенты'})
  → method:'form', type:'Контрагенты', выбор через каталог-форму.
- ТЧ: fillTableRow({Источник: {value:'Альфа', type:'Организации'}},
  {row:0}) → method:'form', type:'Организации' (quickChoice=true →
  без формы выбора, прямой dropdown).

fillFields на composite без type выбрасывает понятную ошибку
с инструкцией «specify the type: selectValue(...,{type:'ИмяТипа'})» —
поведение API стабильно.

timeout 03-fillfields поднят с 60000 → 120000ms (6 шагов суммарно
~63s, новый composite step добавляет ~25s).

Полный регресс **18/18 зелёный** (8m 28.7s).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 15:51:41 +03:00
Nick Shirokov 3d16e35e80 feat(web-test): M5-pre #1 — ValueTree + ДеревоНоменклатуры + tree-form smoke
Расширение синтетики: новая обработка ДеревоНоменклатуры с реквизитом
формы Дерево типа ДеревоЗначений и колонками Номенклатура (ссылка,
read-only) + Цена (Number, editable). ПриСозданииНаСервере рекурсивно
обходит Справочник.Номенклатура и заполняет дерево, отражая иерархию
групп/элементов из справочника.

Обработка зарегистрирована в подсистеме Администрирование и в роли
Администратор (Use+View).

Новый smoke 16-tree-form.test.mjs (5 шагов, 17.1s) — покрывает
05-table/edit-form (fillTableRow method:'direct' на FormDataTree-колонке)
и 08-hierarchy/tree-edit (expand узла + правка Цены через index-row):
- setup: navigateLink('Обработка.ДеревоНоменклатуры'), таблица Дерево
- read-roots: 2 корневые группы (_kind:'group'), columns=Номенклатура,Цена
- expand: clickElement('Товары',{expand:true}) → 16 строк (1 + 15)
- tree-edit: fillTableRow({Цена:1500},{row:1}) → method:'direct',
  Цена становится '1 500,00' (с non-breaking space 1С)
- cleanup: closeForm

Гэп: fillTableRow с row-by-name ('Товар 01') ловит SyntaxError в JS
eval. Использую row-by-index (TODO в web-test-bugs).

Полный регресс **18/18 зелёный** (8m 9.8s) на порту 9191.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 14:03:28 +03:00
Nick Shirokov 56822c4533 test(web-test): switch webtest publication to port 9191
Чтобы не конфликтовать с интерактивной разработкой на основном
Apache (8081, занят сторонним проектом), регрессионный регресс
теперь использует отдельный httpd-процесс на порту 9191. Тот же
httpd запускает /web-publish webtest -Port 9191 -V8Path 8.3.24.

Один процесс Apache → собственный пул лицензий 1С. На 8081 другие
проекты — наши тесты их не блокируют и наоборот.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 14:03:15 +03:00
Nick Shirokov 32bf9c1a3f feat(form-compile): textEdit key for InputField (TextEdit=false)
v1.20 → v1.21 (ps1 + py).

Добавлен ключ DSL `textEdit` для элемента input. Эмитит
`<TextEdit>false</TextEdit>` после AutoMarkIncomplete (значение
true — дефолт платформы, не эмитируется). Закрывает блокер для
03-fillfields/direct-edit-form в синтетике web-test: поле с
запрещённым ручным вводом → выбор только через pick-кнопку/F4.

Snapshot-тест: tests/skills/cases/form-compile/text-edit-flag.json
(2 поля, проверяет наличие TextEdit только на втором). 30/30
form-compile зелёные обоих runtime'ов.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 12:56:35 +03:00
Nick Shirokov c94f86a9cd test(web-test): M4.D2 — openFile EPF + security confirm
Новый 13-misc.test.mjs (3 шага, 11s) — покрытие openFile() для
внешних обработок с автоматической обработкой security confirmation.

- setup: автономный билд EPF (идемпотентный) через epf-init →
  form-add → form-compile (с текстовой декорацией) → epf-build.
  child_process.spawnSync для вызова PowerShell скриптов.
- openFile: проверки state.form, activeTab='Тест открытия',
  state.texts[] содержит декорацию с ожидаемым value,
  opened.attempt>=1, security confirm modal не пробивается.
- cleanup: closeForm + soft-проверка activeTab (между тестами в
  desktop могут оставаться формы от других тестов — не настаиваем
  на formCount=0).

Артефакты в test-tmp/13-openfile/ (.gitignore). Полный регресс
17/17 зелёный (8m 8s).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 18:27:22 +03:00
Nick Shirokov 8b5fed98e0 test(web-test): M4.E — hierarchy + tree-grid (Номенклатура)
Новый 08-hierarchy.test.mjs (7 шагов, 24s) — покрывает группы и
tree-grid режима «Дерево» на форме списка Номенклатуры через UI
переключение viewMode. Без расширения синтетики.

- setup: явное переключение в «Иерархический список» через Ещё →
  Режим просмотра (viewMode сохраняется между сессиями и НЕ
  сбрасывается «Установить стандартные настройки»).
- read-groups (P1): readTable возвращает 2 группы (_kind=group).
- group-expand (P1): clickElement({expand:true}) развёртывает группу,
  внутри 15 элементов.
- switch-tree: «Ещё → Режим просмотра → Дерево» → viewMode='tree'.
- read-tree (P2): readTable.rows[]._tree (collapsed|expanded) — проверка
  только наличия поля (состояние сохраняется между сессиями).
- tree-expand (P1): defensive свёртка через {expand:false} если узел
  expanded, затем {expand:true} → kind='gridTreeNode' toggled=true,
  видны 15 элементов под Товарами.
- cleanup: восстановить иерархический список.

Замечание: clickElement({expand:true}) — только развернуть (no-op для
expanded), {expand:false} — только свернуть, {toggle:true} —
безусловно переключить.

05-table/direct-edit-form, edit-dblclick остаются deferred — нужен
документ с иерархической ТЧ. Полный регресс 16/16 зелёный (7m 53s).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 17:43:31 +03:00
Nick Shirokov 9e677cfc61 test(web-test): M4.F — recording smoke (video + captions + TTS + overlays)
Новый 15-recording.test.mjs (5 шагов, 20.7s) — покрытие полного
публичного API recording.md.

- record + captions: startRecording → 2× showCaption → stopRecording.
  Проверки isRecording, duration/size, mp4 на диске, .captions.json,
  getCaptions с правильными text и time.
- narration: addNarration через Edge TTS (ru-RU-DmitryNeural), narrated
  mp4 больше исходного (добавлен аудио-трек).
- title-slide: showTitleSlide/hideTitleSlide — overlay fullscreen
  (w==innerWidth, h==innerHeight).
- image-overlay: showImage/hideImage с тестовой картинкой из screenshot.
- highlight: setHighlight toggles isHighlightMode, manual highlight на
  кнопке «Создать» создаёт overlay позиционированный на элементе.

Артефакты в test-tmp/recording-smoke/ (.gitignore), идемпотентный.
Полный регресс 15/15 зелёный (7m 27s).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 17:20:13 +03:00
Nick Shirokov 211a4726d6 test(web-test): M4.C+D — drill-down + submenu-read
11-report/drill-down: dblclick по ячейке Номенклатуры сформированного
DCS-отчёта открывает форму элемента (DCS auto-drill). После Сформировать
ищется первая строка с заполненной номенклатурой, проверяется что после
clickElement({row,column},{dblclick:true}) form изменился и есть кнопка
«Записать».

02-crud/more-menu усилен под P2 submenu-read: добавлены явные проверки
clicked.kind='submenu', наличия типовых пунктов «Создать», «Изменить»,
«Расширенный поиск» (length>=5).

Покрыто 2 P2-кейса coverage matrix (11-report/drill-down,
02-crud/submenu-read). Полный регресс 14/14 зелёный (7m 1.6s).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:58:28 +03:00
Nick Shirokov 91b39b758b test(web-test): M4.B+G — subordinate-nav + platform dialogs в 12-formstate
Расширены тесты getFormState: проверка ветвей navigation[] и
platformDialogs[] возвращаемой структуры.

- subordinate-nav: форма элемента Контрагент → state.navigation содержит
  «Основное» (active) и «Контактные лица» (подчинённый каталог).
- platform-dialogs: открытый через hamburger «О программе…» виден в
  state.platformDialogs[{type:'about'}].
- platform-dialog-close: closeForm закрывает платформенный диалог,
  массив становится пустым.

Покрыто 3 P2-кейса coverage matrix (12-formstate/subordinate-nav,
platform-dialogs, platform-dialog-close). Полный регресс 14/14 зелёный.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:30:51 +03:00
Nick Shirokov 4af69f1600 test(web-test): M4.A — validation messages + exception modal + error stack
10-validation.test.mjs (3 шага): Сообщить() → state.errors.messages,
ВызватьИсключение → onecError.errors.modal с автоматическим закрытием
fetchErrorStack.

14-errors-stack.test.mjs (3 шага): Path 1 OpenReport автоматически фетчит
стек для серверных исключений (entries[] содержит кадр ОбщиеФункции);
оставленная error modal через raw page.click закрывается closeForm;
платформенный диалог «О программе» виден в state.platformDialogs и
закрывается closeForm.

Покрыто 4 P2-кейса coverage matrix: 10-validation/messages,
10-validation/exception-modal, 14-errors/path1, 14-errors/dismiss-platform
+ бонус dismiss-modal. Открытие обработки ТестовыеОшибки через
navigateLink('Обработка.ТестовыеОшибки') — стандартные команды у
DataProcessor отключены.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 15:21:11 +03:00
Nick Shirokov 8c7c442705 feat(meta-compile): DSL choiceHistoryOnInput для аттрибутов
meta-compile v1.12 (ps1 + py): Parse-AttributeShorthand принимает поле
choiceHistoryOnInput в object-форме аттрибута, Emit-Attribute эмитит его
вместо хардкода Auto. Покрывает атрибуты Catalog/Document/TabularSection
(Emit-Attribute, единственная точка эмиссии в работе). Другие контексты
(register dimensions, resources, etc.) пока эмитят Auto — расширим
при необходимости.

build-webtest-config: реквизит Документ.ПриходнаяНакладная.Контрагент
получил choiceHistoryOnInput='DontUse'. Это убирает 1С-историю выбора
для поля и фиксит pre-existing flake 04-selectvalue/direct-form:
после 03 значение «ООО Север» оставалось в истории и selectValue
выбирал его через dropdown вместо ожидаемой формы выбора.

Live: полный регресс 12/12 впервые зелёный (5m 28s).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 13:47:50 +03:00
Nick Shirokov c541d51f33 fix(web-test): resetState не закрывал form 0 + error screenshot снимался после reset
run.mjs:

1. resetState проверял `if (!state.form) break`. form === 0 (фоновая
   форма 1С, которую detectForm может вернуть) рассматривался как
   "форм нет" → cleanup прерывался, форма оставалась → следующий тест
   получал грязное состояние. Замена на `state.form == null` корректно
   различает null (desktop) и 0 (реальная фоновая форма).

2. Error screenshot в catch-блоке cmdTest снимался ПОСЛЕ resetState,
   который уже закрывал все формы → скрин показывал пустой рабочий
   стол вместо места падения. Перенёс снимок в начало catch (до
   teardown/afterEach/resetState).

Эффекты:
- 15-multi-context-handover теперь стабильно проходит в полном прогоне
  (раньше падал когда предыдущий тест оставлял form=0).
- 04-selectvalue/direct-form остался pre-existing flake (история
  выбора 1С после 03 — отдельная задача в синтетике).
- Скриншоты падения теперь показывают реальный UI на момент исключения.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 11:52:22 +03:00
Nick Shirokov a650325baf fix(web-test): убрать стуб showCaption/hideCaption в cmdTest
run.mjs v1.10: cmdTest больше не передаёт noRecord:true в buildContext.
Тестам доступен полный API browser.mjs (showCaption, hideCaption,
startRecording, stopRecording, addNarration).

Изначальный стуб с noRecord:true прятал showCaption/hideCaption тестов
вместе с recording-функциями. Это блокировало визуальные оверлеи в
мульти-контекстных тестах: a.showCaption() тихо превращался в no-op,
баннер никогда не отображался даже под --record.

Smart wait внутри showCaption и так гейтится на наличие recorder
(`if (recorder && ...)`), поэтому без --record тесты остаются быстрыми
(никаких 2-секундных пауз на каждый вызов).

startRecording/stopRecording/addNarration теперь тоже доступны тестам.
При попытке вызвать startRecording в момент активной runner-записи
browser.startRecording бросает "Already recording" — loud failure
лучше silent no-op.

Регресс: 15-multi-context-handover один проходит за 19.9s. Полный
прогон 10/12 (04 и 15 флапают независимо в последовательности).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 11:37:42 +03:00
Nick Shirokov 6c19846051 feat(web-test): T4.6 — гибридные режимы изоляции контекстов (tab default, window opt-in)
browser.mjs v1.12 + run.mjs v1.9: createContext принимает isolation параметр.
По умолчанию 'tab' — все контексты живут в одном launchPersistentContext, каждый
слот получает свою Page (вкладку). Преимущества: 1С extension грузится
надёжно (через --load-extension в persistent profile), один процесс Chromium,
дешёвая память. Cookies делятся между вкладками, но скоупятся по URL-path —
для модели «разные пользователи через разные vrd-публикации» это естественно
и достаточно.

isolation: 'window' (opt-in) — старый путь chromium.launch() + newContext():
полная изоляция cookies, отдельный BrowserContext (и окно) на каждый слот,
но extension может не подняться. Использовать когда нужна изоляция auth
внутри одного URL.

Смешивать режимы в одном прогоне нельзя — createContext бросает явную
ошибку (первый createContext устанавливает activeMode, остальные обязаны
совпадать).

Конфиг tests/web-test/webtest.config.mjs: добавлен комментарий с описанием
обоих режимов. По умолчанию tab — синтетика и наши smoke-тесты идут им.

Live: 11/12 в полном прогоне (default tab) + 3/3 sanity-check в window mode
(01-navigation + 14 + 15). Видеозапись из T4.5 работает в обоих режимах.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 18:34:44 +03:00
Nick Shirokov eef4f4bcea feat(web-test): T4.5 — мульти-контекстная запись видео
browser.mjs v1.11: recorder стал глобальным (не per-slot) — один ffmpeg,
один mp4 на тест с любым числом переключений контекста.

Frame state (lastFrameBuf/lastFrameTime/handler) переехал в поля recorder.
Добавлен recorder._attachPage(targetPage) — стопает старый CDP screencast,
заводит новый на нужной странице, route'ит фреймы в тот же ffmpeg pipe.

setActiveContext: при активной записи делает _flushFrames (замораживает
хвост уходящего окна), затем _attachPage(page) после _activateSlot. Видео
получается непрерывным с плавным сюжетом — пока активен a, видно a; пока
активен b, видно b.

_saveActiveSlot/_activateSlot больше не трогают recorder/lastCaptions/
lastRecordingDuration — recorder следует за активной страницей через
_attachPage, не через slot mirror.

disconnect: убрал leftover из T4.1, который пытался итерировать slot.recorder.

Live: 15-multi-context-handover с --record → 17.84s mp4, 446 кадров @ 25fps,
извлечённые кадры показывают переключение между окнами a (1920x1042) и
b (982x546). Полный регресс 11/12 (04-selectvalue — pre-existing flake).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 17:58:31 +03:00
Nick Shirokov 2c553fee98 feat(web-test): T4 — мульти-контекст BrowserContext
browser.mjs v1.10: createContext/setActiveContext/listContexts/getActiveContext/
hasContext. Несколько изолированных BrowserContext в одном Chromium-процессе через
chromium.launch() + newContext(). Module-level page/sessionPrefix/seanceId/recorder
зеркалят активный слот (атомарный своп через _saveActiveSlot/_activateSlot).
connect() оставлен для exec/run/start без изменений (launchPersistentContext).

run.mjs v1.8: ensureContext(name) + ленивое создание. Single-routing через
export const context = 'name'. Multi через export const contexts = ['a','b'] +
buildScopedContext(name) строит ctx.a/ctx.b — каждое действие префиксится
setActiveContext. Reset state после теста по всем активным контекстам.

Конфиг tests/web-test/webtest.config.mjs: два контекста a/b на одну webtest
публикацию (изолированные cookies через newContext).

Smoke-тесты:
- 14-multi-context-routing.test.mjs — single routing в b (2.6s)
- 15-multi-context-handover.test.mjs — ctx.a создаёт Контрагента, ctx.b в
  независимой сессии видит запись через filterList, ctx.a cleanup (14.5s, 4/4)

Live: 11/12 в полном прогоне. 04-selectvalue/direct-form флапает —
pre-existing, воспроизводится на baseline 95e4674 (03→04 sequence).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 17:24:24 +03:00
Nick Shirokov 95e4674825 test(01-navigation): M3 P1 — section/command/switchTab errors + navigateLink
section-error / command-error / switchTab error: проверка throw для
несуществующих имён.

navigateLink: link-type (Catalog.Контрагенты) + e1cib URL (с soft-skip
для платформ без поддержки e1cib через Shift+F11).

Live на webtest: 10/10 passed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 16:29:54 +03:00
Nick Shirokov 9751840cc8 test(09-filter): M3 P1 — exact, hidden-field, date, reference, unfilter-all
exact: filterList exact:true строго 1 совпадение.
hidden-field: filterList по неотображённому реквизиту через FieldSelector
DLB (КодКПП в синтетике нет — soft-skip).
date: filterList по колонке Дата поступления (синтетика выводит её в форму
списка Номенклатуры).
reference: filterList по ссылочной колонке Контрагент (форма списка ПН).
unfilter-all: unfilterList() полностью восстанавливает список.

unfilter-specific отложен — требует списка с видимой filter-панелью,
synthetic списки фильтруют без создания badge.
cancel-search/clear-input семантически дубликаты unfilter-all через
публичный API.
show-all-form требует quickChoice=true каталога с количеством > порога
(в синтетике нет).

Live на webtest: все 7 шагов passed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 16:28:09 +03:00
Nick Shirokov f257bb428c test(12-formstate): M3 P1 — modal + tabs
modal: F4 на ref-поле открывает модальную форму выбора Контрагентов,
state.modal=true, formCount=2.

tabs: форма элемента Номенклатуры с двумя табами (Основное/Дополнительно)
возвращает state.tabs[].

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 15:43:53 +03:00
Nick Shirokov 71e3691cf1 test(web-test): M3 P1 batch 1 — confirm-save-no/pending, more-menu, clear/ref-form, table checkbox/clear
02-crud: confirm-save-no (rollback при save:false), confirm-pending
(closeForm() без решения возвращает confirmation), more-menu (clickElement
'Ещё' возвращает submenu).

03-fillfields: clear (Shift+F4 через пустое значение), reference-non-quickchoice
(fillFields на quickChoice=false поле — method=dropdown через DLB; чистый
form-path требует hasPick && !hasSelect, такого поля в синтетике нет).

04-selectvalue: clear (selectValue '' → Shift+F4). show-all-form отложен —
требует quickChoice=true каталога с количеством > порога dropdown
(в синтетике нет).

05-table: checkbox (fillTableRow с Boolean), clear (Shift+F4 на ref-ячейке +
восстановление для последующего delete).

Live на webtest: все шаги проходят.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 15:40:27 +03:00
Nick Shirokov 1af318325d test(05-table): добавить явный tab-loop step с двумя числовыми полями
fillTableRow({Количество, Цена}, {row:1}) — purpose-built проверка inEdit
multi-cell tab-loop. method='direct' для обоих полей, значения
подставляются корректно (live на webtest).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 15:23:03 +03:00
Nick Shirokov 986480748e Merge branch 'dev' into feature/web-test-runner 2026-05-10 15:10:38 +03:00
Nick Shirokov 7561faf736 test(web-test): покрыть Tumbler через clickElement в radio-шаге
Tumbler-представление RadioButtonField не парсится fillFields, но варианты
видны в state.buttons[] и кликаются через clickElement. Уточнили шаг radio:
- RadioButtons (КатегорияЦены) → fillFields с method=radio
- Tumbler (СпособУчёта) → проверка наличия в buttons[] + clickElement('ФИФО')

Семантика Tumbler через fillFields остаётся как баг web-test/browser.mjs
(см. upload/web-test-bugs.md пункт 5), но рабочий путь интеракции есть.

10/10 smoke зелёные после рестарта Apache.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 18:19:42 +03:00
Nick Shirokov 2849087fd9 test(web-test): покрытие quickChoice + radio (RadioButtons)
03-fillfields:
- reference-dropdown: переведён с Контрагент на Организация
  (после смены quickChoice Контрагенты идут через форму выбора)
- новый шаг radio: КатегорияЦены через method=radio (RadioButtons)

04-selectvalue:
- dropdown: переведён на Организация (quickChoice=true)
- новый шаг direct-form: Контрагент (quickChoice=false), method=form

Закрывает selectValue#3 dropdown (P0), selectValue#6 direct-form (P1),
fillFields#3 radio (P1) из coverage matrix.

Tumbler-представление радио (СпособУчёта) пока не покрыто — getFormState
не возвращает Tumbler в fields[]. Зафиксировано в upload/web-test-bugs.md
пункт 5.

10/10 smoke зелёные на webtest базе.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 16:17:19 +03:00
Nick Shirokov 105171cdc2 test(webtest-config): Организации/quickChoice + radio (RadioButtons+Tumbler)
Расширение синтетики под новые возможности meta-compile/form-compile,
закрывает три ветки coverage matrix:
- Catalog.Организации (quickChoice: true) → selectValue#3 dropdown (P0)
- Catalog.Контрагенты (дефолт quickChoice: false) → selectValue#6 direct-form (P1)
- form-compile radio с видами RadioButtons (КатегорияЦены) и Tumbler
  (СпособУчёта) → fillFields#3 radio (P1)

В шапку ПриходнаяНакладная добавлен реквизит Организация (dropdown ветка),
Контрагент остаётся на форме выбора. Фикстура ЗаполнитьОрганизации создаёт
2 организации (Альфа, Бета); первая подставляется в документы.

Платформенная верификация: build-webtest-db (45 шагов, 30.3s) зелёная,
db-create + db-load-xml + db-update проходят. Функциональный прогон
runner.mjs integration/build-webtest — 42 шага зелёные.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 14:58:27 +03:00
Nick Shirokov c9cd0d62ab Merge branch 'dev' into feature/web-test-runner 2026-05-04 13:15:49 +03:00
Nick Shirokov c1a0a54971 feat(web-test): --record и export const params
Раннер v1.7.

T5 --record: startRecording перед каждым тестом, stopRecording
после (и в passed, и в failed ветке). Файл
{reportDir}/{testIdx}-{slug}.mp4. testResult.video содержит путь.
В Allure — attachment типа video/mp4. config.record читается
тоже. Использует существующую инфраструктуру browser.mjs.

T6 export const params: материализация в N тестов на этапе
discovery. Имя через {key}-шаблон в mod.name (например
'demo {type}'); если шаблона нет — суффикс [index]. Тест-функция
получает param как второй аргумент: default(ctx, param).
В отчёте каждый набор — отдельная test entry с собственным uuid
в Allure / testcase в JUnit.

Live-проверка:
- params: 2 теста с именами demo A / demo B из шаблона.
- record: mp4 91KB на 6-секундном тесте, путь в JSON и
  Allure attachment video/mp4.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 16:19:52 +03:00
Nick Shirokov 927c0827f3 feat(web-test): --format=allure и --format=junit
Раннер v1.6. Реализованы оба формата отчётов из spec §9.

allure: {reportDir}/{uuid}-result.json на каждый тест. uuid через
randomUUID, labels из tags, steps рекурсивно с attachments из
step.screenshot, statusDetails для упавших шагов и тестов.
Пропускает skipped (нет start/stop).

junit: один XML в --report=path.xml. Валидация: --format=junit
требует --report=. xmlEscape для name/message/trace. <failure>
для упавших, <skipped/> для пропущенных, <system-out> со ссылкой
на screenshot.

Валидация формата (json|allure|junit) на старте cmdTest.
testResult теперь хранит start/stop в мс — нужно для Allure
и полезно в JSON-отчёте.

Live-проверка: 01-navigation в Allure (5 шагов с attachments,
все ссылки на существующие PNG); JUnit с passed и forced-fail
(спецсимволы корректно экранированы).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 16:03:31 +03:00
Nick Shirokov 56cd18a6b4 feat(web-test): --screenshot=on-failure|every-step|off + --report-dir
Раннер v1.5. Парсит --screenshot и --report-dir, мерж с config.screenshot.
- every-step: после успешного step() пишет {reportDir}/{testIdx}-{stepIdx}-{slug}.png,
  путь в step.screenshot.
- off: ни пошаговых, ни error-shot.
- on-failure (default): error-shot уехал из .claude/skills/web-test/
  в {reportDir}/error-{testIdx}-{slug}.png.

reportDir фоллбэчит: --report-dir → dirname(--report) → testDir.

Известная нестыковка: error-shot из buildContext/executeScript остаётся в
.claude/skills/web-test/error-shot.png — затронем при T2 (Allure).

Live-проверка: 01-navigation с every-step (5 PNG), off (пусто),
default on-failure на стуб-failing тесте (error-shot в reportDir).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 15:54:38 +03:00
Nick Shirokov 3ac1d425cd test(11-report): DCS-отчёт ОстаткиТоваров + smoke с быстрым фильтром
Синтетика: добавлен template-add ОсновнаяСхемаКомпоновкиДанных к отчёту
(без него skd-compile писал Template.xml в незарегистрированный путь),
переписан DSL skd-compile — fields внутри dataSets, типы полей, totalFields,
явный settingsVariants со structure и быстрым отбором по Номенклатуре
(@off @user @quickAccess).

Тест 11-report покрывает: регистрацию команды в подсистеме, открытие формы
отчёта с дефолтной кнопкой Сформировать, видимость и структуру быстрого
DCS-фильтра, формирование отчёта, применение фильтра через selectValue
(auto-enable чекбокса + значение), пересчёт с фильтром, снятие фильтра
через fillFields toggle off с восстановлением исходных данных.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 15:22:22 +03:00
Nick Shirokov 3c596f4550 test(12-formstate): smoke базовых полей getFormState
Покрывает форму списка (form, formCount, openForms, tables, buttons)
и форму элемента (fields с label и value, проверка по конкретному
полю Наименование).
2026-05-02 20:15:20 +03:00
Nick Shirokov 36d29a51a9 test(09-filter): smoke filterList simple-search и advanced-column
Покрывает:
- filterList('Север') — поиск по всем колонкам списка Контрагенты
- filterList('Север', { field: 'Наименование' }) — фильтр по
  конкретной колонке через расширенный поиск
- unfilterList — восстановление исходного набора

Третий запланированный кейс (text-field filter) семантически совпадает
с advanced-column когда колонка строкового типа — оставлен на регресс P1.
2026-05-02 20:11:58 +03:00
Nick Shirokov 11e961c816 test(07-tabs): smoke переключение страниц формы Основное/Дополнительно
Покрывает clickElement по имени страницы как механизм переключения
вкладок формы. Используем форму элемента Номенклатура: page1
показывает шапку (Артикул, ВидНоменклатуры, ...), page2 — Дополнительно
(ЕдиницаИзмерения, Комментарий). Verify: набор state.fields различен
после переключения и совпадает после возврата.
2026-05-02 20:07:46 +03:00
Nick Shirokov 05ca810461 test(06-document): сверка с Комментарий=docId, защита от грязной базы
Раньше verify-list брал первый попавшийся проведённый документ Север —
если в базе уже лежал проведённый Север из прошлого прогона, тест
проходил даже если текущий не сохранился. Теперь среди кандидатов
открываем каждый и сверяем Комментарий с уникальным docId текущего
прогона; ассерт срабатывает только при совпадении.
2026-05-02 20:04:38 +03:00
Nick Shirokov a0407b74dc test(06-document): проверка закрытия по смене номера формы вместо костыля
Раньше использовалось отсутствие поля Контрагент после Провести и закрыть
как косвенный признак закрытия — это работало, но было привязано к
конкретному реквизиту накладной. Заменил на сравнение state.form до и
после: номер активной формы меняется (11 → 5), это прямой и общий
признак, что мы переключились с формы документа на другую.
2026-05-02 19:58:56 +03:00
Nick Shirokov 3aad254399 test(06-document): smoke workflow проведения накладной
Создание, заполнение шапки и табличной части, Провести и закрыть,
проверка появления документа в списке с Проведён=Да.

Проверка закрытия формы документа: в синтетике web-test форма списка и
форма документа делят один слот (formCount=1 в обоих состояниях),
поэтому используем признак отсутствия поля Контрагент в текущем
state.fields после Провести и закрыть — если поле есть, мы остались
на форме документа.
2026-05-02 19:54:34 +03:00
Nick Shirokov 07753921be test(05-table): smoke add/edit/delete для табличной части накладной
Покрывает работу с табличной частью Товары документа Приходная накладная:
- fillTableRow с add:true добавляет строки последовательно
- fillTableRow с row:N редактирует существующую строку (Tab-навигация)
- deleteTableRow удаляет строку по индексу

Закрытие формы без сохранения (save:false) — соответствует новой
семантике после фикса form-compile (SavedData).
2026-05-02 19:49:19 +03:00
Nick Shirokov ba0c71fa45 test(smoke): починить 01-navigation и 04-selectvalue после фикса form-compile
01-navigation: первое открытое окно 1С имеет form=0 (number), и
assert.ok(state.form, ...) валился на falsy при первом запуске сессии.
Сменил на state.form != null.

04-selectvalue: явный save:false при закрытии модифицированной формы
накладной — после фикса SavedData=true главного реквизита платформа
требует решения по confirmation dialog.
2026-05-02 19:45:15 +03:00
Nick Shirokov 33c9fdade0 test(03-fillfields): boolean → CheckBoxField, явный save:false при закрытии
После фикса form-compile (kind=check для Boolean + SavedData=true для
главного реквизита) Активен передаётся как настоящий boolean (toggle),
getFormState возвращает value:true/false. Закрытие модифицированных форм
теперь требует явного save:false — иначе платформа показывает
confirmation dialog «Записать?».
2026-05-02 19:40:26 +03:00
Nick Shirokov 1c1fe7b2d9 test(02-crud): убрать устаревший комментарий про T11/SavedData
После фикса form-compile (a59be4b SavedData=true для главного реквизита)
canonical confirm-save-yes flow работает без ручного патча Form.xml —
предупреждение в шаге неактуально.
2026-05-02 19:26:56 +03:00
Nick Shirokov 0bd2587e74 test(build-webtest-config): Активен как check вместо input для Boolean
После фикса form-compile (Дефект 2: kind=check → CheckBoxField) булевый
реквизит Активен в форме элемента и форме списка Номенклатуры теперь
описывается как check — рендерится настоящим чекбоксом.
2026-05-02 19:24:50 +03:00
Nick Shirokov 6f17b1c2f6 Merge branch 'dev' into feature/web-test-runner 2026-05-02 19:08:28 +03:00
Nick Shirokov 36ad686316 feat(web-test): smoke-тест 04-selectvalue (dropdown быстрый выбор)
Один P0 кейс из coverage matrix:
- dropdown: selectValue('Контрагент', 'ООО Север') → method='dropdown'
  на форме новой ПриходнойНакладной (CatalogRef + малый список)

API возвращает form state с .selected = {field, search, method}.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 15:57:37 +03:00
Nick Shirokov 66e37fb8cc feat(web-test): smoke-тест 03-fillfields (text, dropdown, date, reference)
2 шага, 5 типов полей зелёные на синтетике webtest:
- text (paste): Артикул на форме Номенклатура
- dropdown (Да/Нет): Активен — Boolean рендерится как Да/Нет селектор
- dropdown (EnumRef): ВидНоменклатуры
- date (paste): ДатаПоступления
- reference (dropdown CatalogRef): Контрагент в новой ПриходнаяНакладная

NB: 1C рендерит Boolean-атрибут не как чекбокс, а как dropdown «Да/Нет»
(actions: ["select"]) — fillFields правильно определяет это.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 15:55:14 +03:00
Nick Shirokov 99c77e1dde fix(web-test): 02-crud использует canonical closeForm({save:true})
Гипотеза о баге fillField paste была ошибочной — реальная причина в form-compile
который не эмитит <SavedData>true</SavedData> для MainAttribute главной формы.
Платформа без SavedData не трекает modified-state, confirmation dialog не
появляется.

Платформенная верификация на патченной Form.xml: closeForm({save:true})
после fillField корректно ловит confirmation, жмёт «Да», изменения
сохраняются. См. T11 в upload/web-test-runner-tasks.md.

ВНИМАНИЕ: тест зависит от ручного патча Form.xml. После прогона
build-webtest-db.mjs тест упадёт до фикса form-compile (T11).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 15:44:39 +03:00
Nick Shirokov 8d6612027f feat(web-test): smoke-тест 02-crud (open-item, close-clean, read, save)
4 шага зелёные на синтетике webtest:
- read: список Контрагентов отдаёт колонки/строки/total
- open-item: dblclick открывает форму элемента
- close-clean: Escape без изменений закрывает форму без диалога
- save-via-button: fillField + «Записать и закрыть» → значение сохраняется

confirm-save-yes (P0 из coverage matrix) отложен — fillField через paste не
выставляет 1C "modified" флаг, confirmation dialog не появляется. Зафиксировано
в upload/web-test-runner-tasks.md как T11.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 15:34:37 +03:00
Nick Shirokov c3b67a18cb feat(tests): build-webtest-db скрипт для постоянной webtest базы
Заменяет одноразовый platform-webtest-config.test.mjs на скрипт сборки в
постоянные пути из .v8-project.json (tests/skills/.cache/webtest-config
+ C:\edt\IB\webtest). Переиспользует steps из build-webtest-config.test.mjs.

Generic platform-config.test.mjs уже покрывает regression «платформа принимает
сборку» — отдельный синтетический тест дублировал.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 15:23:42 +03:00
Nick Shirokov 4d1b66638c Merge branch 'dev' into feature/web-test-runner 2026-05-02 14:54:50 +03:00
Nick Shirokov 363a9f34f2 Merge dev: cf-info раскладка, cf-edit set-panels с русскими синонимами 2026-05-01 17:06:51 +03:00
Nick Shirokov 4f8ce7b747 chore(web-test): убрать инлайн ClientApplicationInterface.xml
Файл теперь генерируется самим cf-init с ERP-дефолтом (см. предыдущий
коммит на dev), отдельный writeFile в build-webtest-config больше не нужен.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 16:48:13 +03:00
Nick Shirokov fc48d68ed1 Merge dev: cf-init создаёт Ext/ClientApplicationInterface.xml 2026-05-01 16:46:30 +03:00
Nick Shirokov 3e34ec0bdd fix(web-test): заголовки и стиль вкладок на форме Номенклатуры
Page элементы в DSL получали name (через ключ 'page'), но не получали
title, поэтому вкладки рендерились пустыми квадратиками. Также Pages
без явного pagesRepresentation отображались в режиме None (без табов).

- Добавил title к каждой Page (Основное, Дополнительно)
- pagesRepresentation: 'TabsOnTop' на Pages

После: getFormState().tabs возвращает [{name:'Основное'},{name:'Дополнительно'}]
вместо пустого массива.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:33:19 +03:00
Nick Shirokov fff2e83960 fix(web-test): починить runtime синтетики для тонкого клиента
Два бага, найденные при попытке запустить синтетическую ИБ через
web-publish + web-test:

1. ОбщиеФункции без ServerCall=true — ManagedApplicationModule (клиент)
   не мог звать процедуры серверного модуля напрямую. ПриНачалеРаботыСистемы
   падал с ошибкой компиляции в runtime, страница не догружалась. Добавил
   serverCall: true в DSL meta-compile.

2. Без Ext/ClientApplicationInterface.xml панель разделов рендерилась
   icon-only (без подписей), web-test navigateSection не находил секции.
   Добавил writeFile-шаг с раскладкой панелей как в acc/erp:
   - top: панель разделов (8e10648b...) + панель информации (cbab57f2...)
   - left: панель функций текущего раздела (b553047f...)

Проверено end-to-end: после пересборки runner-ом + web-publish + start
работают navigateSection, openCommand, readTable. Фикстуры (4 контрагента,
25 номенклатуры в группах, 3 документа) автоматически заполняются при
первом старте через ManagedApplicationModule → ОбщиеФункции.ЗаполнитьФикстурыЕслиНужно.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:12:43 +03:00
Nick Shirokov 1ff209849f feat(web-test): первоначальное заполнение фикстур (M1 Step 5)
Покрытие matrix #9 — данные для smoke-тестов:
- Константа ДанныеЗаполнены (Boolean) — флаг идемпотентности
- ОбщиеФункции.ЗаполнитьФикстурыЕслиНужно() — транзакционно создаёт:
  * 4 контрагента (ООО Север/Юг/Восток, АО Запад)
  * 25 номенклатуры в группах Товары (15) и Услуги (10)
  * 3 приходных накладных по 3 строки
- Ext/ManagedApplicationModule.bsl с ПриНачалеРаботыСистемы — вызывает
  заполнение при первом старте тонкого клиента

Платформенная верификация компилирует BSL (43 шага, 23.7s). Реальное
выполнение заполнения произойдёт при первом подключении web-test
runner-а к синтетической базе.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 14:36:13 +03:00
Nick Shirokov 1a8415283e chore(web-test): убрать избыточный cf-edit (объекты регистрируются автоматически)
meta-compile/subsystem-compile/role-compile сами добавляют записи в
Configuration.xml. cf-edit в каждом прогоне рапортовал Added: 0 — был
no-op + дублировал список объектов, который надо было синхронизировать
руками при каждом изменении.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 14:32:48 +03:00
Nick Shirokov db1e78a534 feat(web-test): подчинённый каталог КонтактныеЛица (M1 Step 4)
Покрытие matrix #8 — getFormState.navigation (12-formstate/subordinate-nav):
- Catalog.КонтактныеЛица с Owner=Catalog.Контрагенты
- Реквизиты: Должность, Телефон
- ФормаЭлемента (с владельцем) + ФормаСписка
- Регистрация в Configuration + Subsystem.Склад + Role

Платформенная верификация: 41 шаг, 23.8s.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 14:30:15 +03:00
Nick Shirokov a828f1847f feat(web-test): обработка ТестовыеОшибки + bsl-модули (M1 Step 3)
Покрытие matrix #6 — errors balloon/messages/modal (10-validation,
fetchErrorStack Path 2):
- ОбщиеФункции.ПоказатьСообщение() → Сообщить("Тестовое сообщение")
- ОбщиеФункции.ВызватьТестовоеИсключение() → ВызватьИсключение
- DataProcessor.ТестовыеОшибки + ФормаОбработки с двумя кнопками,
  каждая делает клиент→сервер вызов соответствующей процедуры
- Регистрация в Configuration + Subsystem.Администрирование

Runner расширен step-типом writeFile — записывает произвольный текст
(обычно Module.bsl) в workDir. Нет навыка для модификации bsl-кода
модулей, и плодить отдельный навык под одну задачу избыточно.

Платформенная верификация: 36 шагов, 21.2s.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 14:27:38 +03:00
Nick Shirokov 3e8159b591 feat(web-test): формы списков с правильными колонками (M1 Step 2)
Расширение синтетики (пункты 3, 4, 5 из M1):
- Контрагенты.КодКПП: новый String реквизит, НЕ выводимый в форму списка
  (для теста filterList #5 — FieldSelector DLB по скрытой колонке)
- Catalog.Контрагенты.ФормаСписка: Code, Description, ИНН, Телефон, Адрес
- Catalog.Номенклатура.ФормаСписка: Code, Description, Артикул,
  ВидНоменклатуры, ДатаПоступления, Цена, Активен (date-колонка для
  filterList #6)
- Document.ПриходнаяНакладная.ФормаСписка: Date, Number, Контрагент, Posted
  (reference-колонка для filterList #7)

Платформенная верификация: 31 шаг, 21.4s.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 14:23:38 +03:00
Nick Shirokov 57bb964c1e feat(web-test): ссылочные типы и Boolean в синтетике (M1 Step 1)
Расширение build-webtest-config под coverage matrix (пункты 1, 2, 7
из upload/web-test-runner-tasks.md M1):
- Перечисление КатегорииЦен (для будущего radio-button теста)
- Номенклатура.ВидНоменклатуры → EnumRef.ВидыНоменклатуры
- Номенклатура.КатегорияЦены → EnumRef.КатегорииЦен
- ПриходнаяНакладная.Контрагент: String → CatalogRef.Контрагенты
- ПриходнаяНакладная.Товары.Номенклатура: String → CatalogRef.Номенклатура
- ПриходнаяНакладная.Товары.Согласовано: новый Boolean (для checkbox
  в grid, fillTableRow ветка #6)
- Формы Номенклатура и Документ обновлены под новые поля
- Subsystem.Склад: добавлены Enum.* в content
- Configuration.xml регистрирует Enum.КатегорииЦен

Платформенная верификация (platform-webtest-config.test.mjs) зелёная,
25 шагов 16.7s.

Гэп: form-compile не умеет рендерить RadioButtonField — представление
КатегорияЦены остаётся обычным input. Будет отдельной задачей перед
тестами P1 fillFields/radio.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 14:21:22 +03:00
Nick Shirokov 41c4b6b1f7 fix(skills/tests): cleanupWorkspace терпимо переживает EBUSY от 1cv8
После платформенных тестов (db-create/db-load-xml/db-update) Windows
держит файловые хэндлы 1cv8 ещё несколько сотен миллисекунд. rmSync без
ретраев падал EBUSY на Roles/.../Rights.xml, и uncaught-ошибка в finally
рушила весь node-процесс — теряли результат теста.

Теперь rmSync с maxRetries: 10, retryDelay: 200 (≈2с буфер) и try/catch
вокруг — в худшем случае warning + лишняя tmp-папка вместо краша.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 14:21:08 +03:00
Nick Shirokov ffb0ee740d fix(web-test): восстановить синтетическую конфу + платформенная верификация
build-webtest-config упал после ужесточения form-compile (запрет runtime-типа
FormDataStructure для главного реквизита). Перевёл типы на конкретные
CatalogObject.X / DocumentObject.X без cfg:-префикса. Добавил
platform-webtest-config.test.mjs — переиспользует шаги сборки и в хвосте
делает db-create + db-load-xml + db-update. Зелёный, 24 шага.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 12:12:45 +03:00
Nick Shirokov f5e487096f Merge branch 'dev' into feature/web-test-runner 2026-05-01 11:58:28 +03:00
Nick Shirokov 6d5c1a0b19 Merge branch 'dev' into feature/web-test-runner 2026-04-05 18:18:25 +03:00
Nick Shirokov b322c02fdb fix(web-test): discoverTests для одиночного файла + первый smoke-тест
- Fix: discoverTests падал с ENOTDIR при передаче .test.mjs файла
- Добавлен 01-navigation.test.mjs — навигация по разделам, открытие
  списков через navigateLink, переключение между подсистемами

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 15:22:40 +03:00
Nick Shirokov 61ef7ac891 fix(web-test): фикс синтетической конфигурации для загрузки в платформу
- Подсистемы: singular формы в Content (Catalog вместо Catalogs)
- КурсыВалют: Independent вместо RecorderSubordinate
- Убран AccumulationRegister (требует регистратор, не нужен для UI)
- Отчёт: запрос из ТЧ документа вместо регистра

Формы загружаются без Form.xml (автогенерация платформой) —
баг form-compile (XDTO exception) требует отдельного исследования.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 15:09:43 +03:00
Nick Shirokov ba19b4111d feat(web-test): синтетическая конфигурация для регресс-тестов
22 шага: cf-init → meta-compile (10 объектов) → form-compile (3 формы,
вкл. 2 вкладки для Номенклатуры) → skd-compile → subsystem-compile
(Склад + Администрирование) → role-compile (полные права) → cf-validate.

Расширения: иерархический справочник, разнотипные реквизиты (Number,
Boolean, Date, String unlimited), FillChecking, вторая подсистема.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 14:57:52 +03:00
Nick Shirokov ded11437c5 docs(web-test): обновить статус дорожной карты — #1-5 done
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 14:55:00 +03:00
Nick Shirokov 5eda7f8eb3 feat(web-test): test runner — buildContext, cmdTest, assertions, step
- Извлечён buildContext() из executeScript (переиспользуется)
- Новая команда `test [url] <dir> [--tags/--bail/--retry/--timeout/--report]`
- Обнаружение *.test.mjs, импорт ES-модулей, фильтрация по тегам/grep/only
- Хуки: prepare/cleanup (без браузера) + beforeAll/afterAll/beforeEach/afterEach
- Встроенный сброс состояния (dismissPendingErrors + closeForm) после каждого теста
- step(name, fn) обёртка с вложенностью и таймингами
- Assertions: ok/equal/deepEqual/includes/match/throws + 1C-специфичные
- Консольный вывод с деревом шагов, JSON-отчёт
- Поддержка webtest.config.mjs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 14:53:33 +03:00
Nick Shirokov f39a0d9c5e docs(web-test): BrowserContext вместо sequential reconnect для мульти-контекста
Один процесс браузера, несколько изолированных BrowserContext'ов.
Мгновенное переключение между пользователями, состояние каждой
сессии сохраняется. Не требует полного рефакторинга createContext().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 14:47:26 +03:00
Nick Shirokov 2347859bdd docs(web-test): спецификация test runner для регрессионного тестирования
Единый механизм для внутреннего регресса browser.mjs API и
пользовательского регресса 1С-приложений. Паттерны Playwright Test.

Содержание: CLI, формат тестов, контексты, хуки, assertions, step(),
отчёты (JSON/Allure/JUnit), синтетическая конфигурация, дорожная карта.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 14:37:47 +03:00
236 changed files with 27714 additions and 3113 deletions
@@ -1,4 +1,4 @@
# form-compile v1.20 — Compile 1C managed form from JSON or object metadata
# form-compile v1.21 — Compile 1C managed form from JSON or object metadata
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[string]$JsonPath,
@@ -1912,6 +1912,7 @@ function Emit-Element {
# input-specific
"multiLine"=1;"passwordMode"=1;"choiceButton"=1;"clearButton"=1
"spinButton"=1;"dropListButton"=1;"markIncomplete"=1;"skipOnInput"=1;"inputHint"=1
"textEdit"=1
# label/hyperlink
"hyperlink"=1
# group-specific
@@ -2137,6 +2138,7 @@ function Emit-Input {
if ($el.spinButton -eq $true) { X "$inner<SpinButton>true</SpinButton>" }
if ($el.dropListButton -eq $true) { X "$inner<DropListButton>true</DropListButton>" }
if ($el.markIncomplete -eq $true) { X "$inner<AutoMarkIncomplete>true</AutoMarkIncomplete>" }
if ($el.textEdit -eq $false) { X "$inner<TextEdit>false</TextEdit>" }
if ($el.skipOnInput -eq $true) { X "$inner<SkipOnInput>true</SkipOnInput>" }
$hasAmw = $el.PSObject.Properties.Name -contains 'autoMaxWidth'
if ($hasAmw) {
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# form-compile v1.20 — Compile 1C managed form from JSON or object metadata
# form-compile v1.21 — Compile 1C managed form from JSON or object metadata
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import copy
@@ -1350,6 +1350,7 @@ KNOWN_KEYS = {
"maxWidth", "maxHeight",
"multiLine", "passwordMode", "choiceButton", "clearButton",
"spinButton", "dropListButton", "markIncomplete", "skipOnInput", "inputHint",
"textEdit",
"hyperlink",
"showTitle", "united", "collapsed",
"children", "columns",
@@ -1940,6 +1941,8 @@ def emit_input(lines, el, name, eid, indent):
lines.append(f'{inner}<DropListButton>true</DropListButton>')
if el.get('markIncomplete') is True:
lines.append(f'{inner}<AutoMarkIncomplete>true</AutoMarkIncomplete>')
if el.get('textEdit') is False:
lines.append(f'{inner}<TextEdit>false</TextEdit>')
if el.get('skipOnInput') is True:
lines.append(f'{inner}<SkipOnInput>true</SkipOnInput>')
if 'autoMaxWidth' in el:
@@ -1,4 +1,4 @@
# form-validate v1.4 — Validate 1C managed form
# form-validate v1.6 — Validate 1C managed form
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
@@ -366,13 +366,51 @@ if (-not $stopped) {
$dataPath = $dpNode.InnerText.Trim()
if (-not $dataPath) { continue }
# Opaque platform-internal DataPath shapes — not validatable from Form.xml alone:
# - bare numeric (e.g. "10", "1000003") — internal index
# - "N/M:<uuid>" — metadata reference by UUID
if ($dataPath -match '^\d+$' -or $dataPath -match '^\d+/\d+:[0-9a-fA-F-]+$') {
continue
}
$pathChecked++
# Extract root segment of path, strip array indices like [0]
$cleanPath = $dataPath -replace '\[\d+\]', ''
# Strip leading '~' (current row of DynamicList: ~Список.Поле)
if ($cleanPath.StartsWith('~')) { $cleanPath = $cleanPath.Substring(1) }
$segments = $cleanPath -split '\.'
$rootAttr = $segments[0]
# Resolve Items.<TableName>.CurrentData.<Field>... — table element, not attribute
if ($rootAttr -eq 'Items') {
if ($segments.Count -lt 3 -or $segments[2] -ne 'CurrentData') {
Report-Warn "[$tag] '$elName': DataPath='$dataPath' — unknown Items.* shape, expected Items.<Table>.CurrentData.*"
continue
}
$tableName = $segments[1]
$tableEl = $null
foreach ($candidate in $allElements) {
if ($candidate.Tag -eq 'Table' -and $candidate.Name -eq $tableName) {
$tableEl = $candidate
break
}
}
if (-not $tableEl) {
Report-Error "[$tag] '$elName': DataPath='$dataPath' — table element '$tableName' not found"
$pathErrors++
continue
}
$tableDpNode = $tableEl.Node.SelectSingleNode("f:DataPath", $nsMgr)
if (-not $tableDpNode -or -not $tableDpNode.InnerText.Trim()) {
# Table without DataPath — can't resolve further, accept silently
continue
}
$tableDp = $tableDpNode.InnerText.Trim() -replace '\[\d+\]', ''
if ($tableDp.StartsWith('~')) { $tableDp = $tableDp.Substring(1) }
$rootAttr = ($tableDp -split '\.')[0]
}
if (-not $attrMap.ContainsKey($rootAttr)) {
Report-Error "[$tag] '$elName': DataPath='$dataPath' — attribute '$rootAttr' not found"
$pathErrors++
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# form-validate v1.4 — Validate 1C managed form
# form-validate v1.6 — Validate 1C managed form
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -376,12 +376,44 @@ def main():
if not data_path:
continue
# Opaque platform-internal DataPath shapes — not validatable from Form.xml alone:
# - bare numeric (e.g. "10", "1000003") — internal index
# - "N/M:<uuid>" — metadata reference by UUID
if re.match(r'^\d+$', data_path) or re.match(r'^\d+/\d+:[0-9a-fA-F-]+$', data_path):
continue
path_checked += 1
clean_path = re.sub(r'\[\d+\]', '', data_path)
# Strip leading '~' (current row of DynamicList: ~\u0421\u043f\u0438\u0441\u043e\u043a.\u041f\u043e\u043b\u0435)
if clean_path.startswith('~'):
clean_path = clean_path[1:]
segments = clean_path.split(".")
root_attr = segments[0]
# Resolve Items.<TableName>.CurrentData.<Field>... \u2014 table element, not attribute
if root_attr == 'Items':
if len(segments) < 3 or segments[2] != 'CurrentData':
report_warn(f"[{tag}] '{el_name}': DataPath='{data_path}' \u2014 unknown Items.* shape, expected Items.<Table>.CurrentData.*")
continue
table_name = segments[1]
table_el = None
for candidate in all_elements:
if candidate["Tag"] == 'Table' and candidate["Name"] == table_name:
table_el = candidate
break
if table_el is None:
report_error(f"[{tag}] '{el_name}': DataPath='{data_path}' \u2014 table element '{table_name}' not found")
path_errors += 1
continue
table_dp_node = table_el["Node"].find(f"{{{F_NS}}}DataPath")
if table_dp_node is None or not (table_dp_node.text or "").strip():
continue
table_dp = re.sub(r'\[\d+\]', '', (table_dp_node.text or "").strip())
if table_dp.startswith('~'):
table_dp = table_dp[1:]
root_attr = table_dp.split(".")[0]
if root_attr not in attr_map:
report_error(f"[{tag}] '{el_name}': DataPath='{data_path}' \u2014 attribute '{root_attr}' not found")
path_errors += 1
@@ -1,4 +1,4 @@
# meta-compile v1.11 — Compile 1C metadata object from JSON
# meta-compile v1.12 — Compile 1C metadata object from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
@@ -502,6 +502,7 @@ function Parse-AttributeShorthand {
fillChecking = if ($val.fillChecking) { "$($val.fillChecking)" } else { "" }
indexing = if ($val.indexing) { "$($val.indexing)" } else { "" }
multiLine = if ($val.multiLine -eq $true) { $true } else { $false }
choiceHistoryOnInput = if ($val.choiceHistoryOnInput) { "$($val.choiceHistoryOnInput)" } else { "" }
}
}
@@ -822,7 +823,8 @@ function Emit-Attribute {
X "$indent`t`t<CreateOnInput>Auto</CreateOnInput>"
X "$indent`t`t<ChoiceForm/>"
X "$indent`t`t<LinkByType/>"
X "$indent`t`t<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>"
$chi = if ($parsed.choiceHistoryOnInput) { $parsed.choiceHistoryOnInput } else { "Auto" }
X "$indent`t`t<ChoiceHistoryOnInput>$chi</ChoiceHistoryOnInput>"
# Use — only for catalog top-level attributes
if ($context -eq "catalog") {
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# meta-compile v1.11 — Compile 1C metadata object from JSON
# meta-compile v1.12 — Compile 1C metadata object from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -465,6 +465,7 @@ def parse_attribute_shorthand(val):
'fillChecking': str(val['fillChecking']) if val.get('fillChecking') else '',
'indexing': str(val['indexing']) if val.get('indexing') else '',
'multiLine': True if val.get('multiLine') is True else False,
'choiceHistoryOnInput': str(val['choiceHistoryOnInput']) if val.get('choiceHistoryOnInput') else '',
}
def parse_enum_value_shorthand(val):
@@ -774,7 +775,8 @@ def emit_attribute(indent, parsed, context):
X(f'{indent}\t\t<CreateOnInput>Auto</CreateOnInput>')
X(f'{indent}\t\t<ChoiceForm/>')
X(f'{indent}\t\t<LinkByType/>')
X(f'{indent}\t\t<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>')
chi = parsed.get('choiceHistoryOnInput') or 'Auto'
X(f'{indent}\t\t<ChoiceHistoryOnInput>{chi}</ChoiceHistoryOnInput>')
if context == 'catalog':
X(f'{indent}\t\t<Use>ForItem</Use>')
if context not in ('processor', 'processor-tabular'):
+14 -2
View File
@@ -88,11 +88,20 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/skd-compile.ps1" -V
Многоязычный заголовок: `"title": { "ru": "...", "en": "..." }`. Применимо везде, где принимается title/presentation (поля, calculatedFields, parameters, settingsVariants, availableValues и пр.). Строка эквивалентна `{ "ru": "..." }`.
Типы: `string`, `string(N)`, `decimal(D,F)`, `boolean`, `date`, `dateTime`, `CatalogRef.X`, `DocumentRef.X`, `EnumRef.X`, `StandardPeriod`. Ссылочные типы эмитируются с inline namespace `d5p1:` (`http://v8.1c.ru/8.1/data/enterprise/current-config`). Сборка EPF со ссылочными типами требует базу с соответствующей конфигурацией.
Типы: `string`, `string(N)`, `decimal`, `decimal(D)`, `decimal(D,F)`, `boolean`, `date`, `dateTime`, `CatalogRef.X`, `DocumentRef.X`, `EnumRef.X`, `StandardPeriod`. Ссылочные типы эмитируются с inline namespace `d5p1:` (`http://v8.1c.ru/8.1/data/enterprise/current-config`). Сборка EPF со ссылочными типами требует базу с соответствующей конфигурацией.
`decimal` без скобок = `10,2` (деньги по умолчанию), `decimal(N)` = `N,0` (целое); `,nonneg` в конце скобок → AllowedSign=Nonnegative.
Составной тип (несколько типов значений) — массив в объектной форме: `"type": ["CatalogRef.A", "CatalogRef.B"]`. Квалификаторы (`(N)`, `(D,F)`) применяются к каждому элементу.
Роли: `@dimension`, `@account`, `@balance`, `@period`.
Роли (shorthand или объект):
- `@`-флаги: `@dimension`, `@account`, `@balance`, `@period`, `@required`, `@autoOrder`, `@ignoreNullValues`
- KV: `balanceGroupName`, `balanceType` (`OpeningBalance`/`ClosingBalance`), `parentDimension`, `accountTypeExpression`, `expression`, `orderType` (`Asc`/`Desc`), `periodNumber`, `periodType`
```
"Сумма: decimal(15,2) @balance balanceGroupName=Сумма balanceType=OpeningBalance"
```
Ограничения: `#noField`, `#noFilter`, `#noGroup`, `#noOrder`.
@@ -101,6 +110,7 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/skd-compile.ps1" -V
Дополнительные ключи объектной формы:
- `"presentationExpression": "<выражение>"` — что показывать вместо значения поля. Исходное значение остаётся «под капотом» для перехода/расшифровки.
- `"appearance": { "<параметр>": "<значение>" }` — оформление колонки по умолчанию (применяется во всех вариантах настроек). Ключи — параметры платформы (`ГоризонтальноеПоложение`, `МинимальнаяШирина`, `Формат`, `Текст` и т.п.).
- `"orderExpression": { "expression": "<выражение>", "orderType": "Asc"/"Desc", "autoOrder": true/false }` — сортировка поля по выражению (например `ЕстьNULL(Поле.Порядок, 10000)`).
```json
{ "field": "Сумма", "title": "Сумма продажи", "type": "decimal(15,2)",
@@ -147,6 +157,8 @@ Shorthand: `"Имя [Заголовок]: тип = значение @флаги"
Объектная форма: `title`, `hidden: true`, `valueListAllowed: true`, `availableAsField: false`, `denyIncompleteValues: true`, `use: "Always"`.
Если значения по умолчанию нет — пропусти `=` в shorthand или укажи `"value": null` в объектной форме.
Список допустимых значений (availableValues):
```json
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+52
View File
@@ -0,0 +1,52 @@
---
name: skd-decompile
description: Декомпиляция схемы компоновки данных 1С (СКД) в JSON-черновик в формате skd-compile. Используй для scaffold нового отчёта по образцу или структурного рефакторинга. Не для точечных правок
argument-hint: <TemplatePath> [-OutputPath <out.json>]
disable-model-invocation: true
allowed-tools:
- Bash
- Read
- Write
- Glob
---
# /skd-decompile — JSON-черновик из Template.xml СКД
Читает Template.xml и эмитит JSON в формате `skd-compile`. **Результат — черновик**, а не обратимое представление: см. раздел «Что получаешь».
## Когда использовать
- **Scaffold нового отчёта по образцу** — взять существующий СКД, получить JSON, поправить и скомпилировать в новый.
- **Структурный рефакторинг** — переписать вариант, перерисовать шаблон, перебрать набор полей.
## Когда **не** использовать
- **Точечные правки готового отчёта** (добавить поле, фильтр, итог, переименовать) → `/skd-edit`. Цикл «декомпиляция → правка JSON → компиляция» переписывает шаблон целиком, может терять непокрытые конструкции и даёт большой diff в исходниках. `/skd-edit` правит адресно, без полной реконструкции.
## Параметры
| Параметр | Описание |
|----------|----------|
| `TemplatePath` | Путь к Template.xml (обязательный) |
| `OutputPath` | Путь к выходному JSON. Если не задан — JSON в stdout |
```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/skd-decompile.ps1" -TemplatePath "<Template.xml>" -OutputPath "<out.json>"
```
## Что получаешь
JSON-черновик в формате `/skd-compile`**не полное обратимое представление СКД**. На вход компилятору такой JSON напрямую может не пойти: в нём встречаются sentinel-узлы (маркер `__unsupported__`).
- **Готовые узлы** — большая часть СКД (поля, параметры, шаблоны, варианты со structure/filter/order/conditionalAppearance и т.п.) ложится в JSON как обычные узлы DSL.
- **Sentinel-узлы** — места, где встретилась конструкция, которую декомпилятор не умеет выразить в DSL. JSON остаётся валидным, но компилятор откажется его собирать, пока sentinel не **заменён ручной реализацией** (явный raw `template`, прописанный appearance и т.п.) **или не удалён**, если в новом отчёте конструкция не нужна. Это намеренный барьер — чтобы непокрытое не уехало в финальный отчёт незамеченным.
- **`<basename>.warnings.md`** рядом с `OutputPath` — список всех sentinel-узлов с координатами в исходнике, по нему удобно обходить места под ручную доработку.
- **Критичные конструкции** (Picture cells, ХранилищеЗначения, вложенные схемы, не-СКД root) — скрипт падает с ненулевым кодом и сообщением в stderr; такой Template как образец не годится.
## Workflow
1. `/skd-decompile <Template.xml> -OutputPath draft.json` — получить черновик.
2. Открыть `draft.warnings.md`, посмотреть, что не покрылось.
3. Поправить JSON под задачу. Sentinel-узлы — заменить на ручную реализацию (через явный raw `template`, через ручное описание appearance и т.п.) либо удалить, если конструкция в новом отчёте не нужна.
4. `/skd-compile -DefinitionFile draft.json -OutputPath new-Template.xml` — собрать обратно.
5. `/skd-validate` + `/skd-info` — проверить.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+70 -10
View File
@@ -52,11 +52,16 @@ Shorthand: `"Имя [Заголовок]: тип @роль #ограничени
Поле добавляется в набор и в selection варианта (если нет `-NoSelection`). Дубликат dataPath — предупреждение, пропуск.
Чтобы поле попало в selection не варианта, а конкретной группировки структуры — используй `-NoSelection` и затем `add-selection "Имя @group=ИмяГруппы"`.
### add-total — добавить итог
Shorthand: `"<dataPath>: <выражение>"`. Если выражение — известная аггрегатная функция без скобок (`Сумма`, `Количество`, `Минимум`, `Максимум`, `Среднее`), оно автоматически оборачивается в `Func(dataPath)`. Если функция со скобками или произвольное выражение — используется как есть.
```
"Цена: Среднее"
"Стоимость: Сумма(Кол * Цена)"
"Цена: Среднее" # → Среднее(Цена)
"Стоимость: Сумма(Кол * Цена)" # → как есть
"Проверка: Проверка" # identity: выражение = Проверка
```
### add-calculated-field — добавить вычисляемое поле
@@ -80,24 +85,46 @@ Shorthand: `"Имя [Заголовок]: тип = Выражение #noFilter
"Организация: CatalogRef.Организации"
```
Shorthand: `"Имя [Заголовок]: тип = значение @флаги"`. `[Заголовок]` опциональный — добавляет `<title>`.
Shorthand: `"Имя [Заголовок]: тип = значение [availableValue=список] [@флаги]"`. `[Заголовок]` опциональный — добавляет `<title>`.
`@autoDates` генерирует пару скрытых параметров `ДатаНачала`/`ДатаОкончания` для StandardPeriod-параметра — для БСП-отчётов, чтобы получить пару полей «Начало/Конец» в панели быстрых настроек.
Флаги:
- `@autoDates` — генерирует пару скрытых параметров `ДатаНачала`/`ДатаОкончания` для StandardPeriod-параметра.
- `@hidden` — скрывает параметр от пользовательских настроек (для параметров-констант, используемых в запросе).
- `@always` — параметр всегда подставляется в запрос. Часто вместе с `@hidden`, но используется и отдельно (для видимых обязательных параметров типа отчётного периода).
```
"ПС: CatalogRef.Контрагенты = Справочник.Контрагенты.ПустаяСсылка @hidden"
"Период: StandardPeriod = LastMonth @always"
"ПСчет: ChartOfAccountsRef.Хозрасчетный = ПланСчетов.Хозрасчетный.X @hidden @always"
"Округление: EnumRef.Округления = Окр1 availableValue=Перечисление.Округления.Окр1: руб., Перечисление.Округления.Окр1000: тыс."
```
`availableValue=` задаёт начальный список допустимых значений. Формат списка: `v1[: p1], v2[: p2], ...` — элементы через `,`, представление после `:`. Если в значении или представлении встречается `,` или `:` — оборачивай в одинарные кавычки `'...'`:
```
"Округление: ... = Окр1 availableValue=Окр1_00: 'руб., коп.', Окр1: руб."
```
### modify-parameter — изменить существующий параметр
Находит параметр по имени, добавляет/обновляет свойства.
Shorthand: `"ИмяПараметра [Заголовок] [ключ=значение]... [@флаги]"`. Находит параметр по имени, обновляет указанные свойства.
```
"ПорядокОкругления use=Always"
"ПорядокОкругления [Округление сумм] denyIncompleteValues=true"
"ПериодОтчета [Отчетный период]" # только title
"ПорядокОкругления availableValue=Перечисление.Округления.Окр1 presentation=руб."
"ПорядокОкругления availableValue=Перечисление.Округления.Окр1: руб., Перечисление.Округления.Окр1000: тыс."
"СчетПС value=ПланСчетов.Хозрасчетный.КассаПредприятия"
"Контрагент @hidden @always"
```
`[Заголовок]` опциональный — устанавливает или заменяет `<title>`. Можно вызывать без других kv-пар, чтобы только обновить title.
`availableValue=` добавляет один элемент списка допустимых значений (можно несколько через `;;`). Тип значения определяется автоматически (DesignTimeValue для ссылок).
`availableValue=` **заменяет весь список** допустимых значений (старые удаляются). Формат и кавычки — те же, что в `add-parameter`.
`value=` заменяет значение параметра (тип значения подбирается автоматически по объявленному типу параметра).
Флаги `@hidden` / `@always` — те же, что и в `add-parameter`. Идемпотентны.
### rename-parameter — переименовать параметр
@@ -231,13 +258,17 @@ Value — имена ресурсов (как в полях/вычисляемы
### patch-query — точечная замена в тексте запроса
Shorthand: `"старое => новое"`. Заменяет все вхождения подстроки. Поддерживает пакетный режим и `-DataSet`.
Shorthand: `"старое => новое [@once]"`. По умолчанию заменяет все вхождения подстроки. Поддерживает пакетный режим и `-DataSet`.
```
"СубконтоДт1) В => СубконтоКт1) В"
"ЛЕВОЕ СОЕДИНЕНИЕ => ВНУТРЕННЕЕ СОЕДИНЕНИЕ"
"КАК ВТ_СтароеИмя => КАК ВТ_НовоеИмя @once"
```
`@once` — упасть с ошибкой, если в запросе не **ровно одно** вхождение. Защищает от случайных замен в комментариях и однотипных идентификаторах.
Многострочные подстроки поддерживаются.
### set-outputParameter — установить параметр вывода
```
@@ -249,16 +280,27 @@ Shorthand: `"старое => новое"`. Заменяет все вхожде
### set-structure — установить структуру варианта
Shorthand: `"Поле1 > Поле2 > details"`. `details`/`детали` — детальные записи. Заменяет всю структуру. Не поддерживает пакетный режим.
Shorthand: `"Поле1 > Поле2 > details"`. `>` — вложенный уровень группировки, `,` — несколько полей в одном уровне, `details` — детальные записи. **Заменяет всю структуру полностью** (включая Selection/order/filter/conditionalAppearance каждой группы). Для точечной модификации полей группировки с сохранением настроек — используй `modify-structure`. Не поддерживает пакетный режим.
```
"Организация > Номенклатура > details"
"Валюта, НаименованиеБанка, ИНН"
"details"
"СчетМеждународногоУчета @name=ДанныеОтчета"
```
`@name=Имя` — присваивает имя группировке (`<dcsset:name>`). Используется для привязки шаблонов через `groupName`.
### modify-structure — изменить поля группировки существующей группы
Тот же shorthand что и `set-structure`. Находит группу по `@name=`, заменяет только `<groupItems>` (поля группировки). Selection/order/filter/conditionalAppearance/outputParameters группы сохраняются. Без `@name=` — ошибка.
```
"Валюта @name=ДанныеОтчета"
"Валюта, НаименованиеБанка @name=ДанныеОтчета"
"details @name=ДанныеОтчета"
```
### modify-field — изменить существующее поле
Тот же shorthand что и `add-field`. Находит по dataPath, объединяет свойства (непустые переопределяют), сохраняет позицию.
@@ -267,6 +309,23 @@ Shorthand: `"Поле1 > Поле2 > details"`. `details`/`детали` — д
"Цена [Цена USD]: decimal(10,4) @dimension"
```
### set-field-role — установить роль поля
Shorthand: `"<dataPath> [@флаги] [kv=значение]"`. **Полностью заменяет** роль поля. Если в значении только dataPath без флагов/kv — удаляет роль.
```
"Сумма" # снять роль полностью
"СуммаОстаток @balance" # простая балансовая роль
"СуммаНач @balance balanceGroupName=Сумма balanceType=OpeningBalance" # с уточнением
"Контрагент @dimension parentDimension=Группа"
"Период @period" # роль периода
```
Флаги: `@balance`, `@dimension`, `@account`, `@period`, `@required`, `@autoOrder`, `@ignoreNullValues`.
KV: `balanceGroupName`, `balanceType` (OpeningBalance/ClosingBalance), `parentDimension`, `accountTypeExpression`, `orderType` (Asc/Desc), `expression`, `periodNumber`, `periodType`.
Поддерживает пакетный режим (`;;`).
### modify-filter — изменить существующий фильтр
Тот же shorthand что и `add-filter`. Находит по полю, обновляет оператор/значение/флаги. См. правило для `<use>` ниже.
@@ -294,6 +353,7 @@ Shorthand: `"Поле1 > Поле2 > details"`. `details`/`детали` — д
| `clear-selection` | `*` | Очищает все элементы selection |
| `clear-order` | `*` | Очищает все элементы order |
| `clear-filter` | `*` | Очищает все элементы filter |
| `clear-conditionalAppearance` | `*` | Очищает все правила условного оформления |
## Верификация
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+14 -3
View File
@@ -1,4 +1,4 @@
# skd-info v1.3 — Analyze 1C DCS structure
# skd-info v1.5 — Analyze 1C DCS structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory=$true)]
@@ -824,8 +824,14 @@ function Show-Fields {
$roleParts = @()
if ($role) {
foreach ($child in $role.ChildNodes) {
if ($child.NodeType -eq "Element" -and $child.InnerText -eq "true") {
if ($child.NodeType -ne "Element") { continue }
$txt = $child.InnerText.Trim()
if ($txt -eq "true") {
$roleParts += $child.LocalName
} elseif ($txt -eq "false") {
# skip default-false flags
} else {
$roleParts += "$($child.LocalName)=$txt"
}
}
}
@@ -1869,7 +1875,12 @@ $totalLines = $result.Count
# OutFile
if ($OutFile) {
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllLines((Join-Path (Get-Location) $OutFile), $result, $utf8Bom)
if ([System.IO.Path]::IsPathRooted($OutFile)) {
$outPath = [System.IO.Path]::GetFullPath($OutFile)
} else {
$outPath = [System.IO.Path]::GetFullPath((Join-Path (Get-Location).Path $OutFile))
}
[System.IO.File]::WriteAllLines($outPath, $result, $utf8Bom)
Write-Host "Written $totalLines lines to $OutFile"
exit 0
}
+9 -2
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# skd-info v1.3 — Analyze 1C DCS structure
# skd-info v1.5 — Analyze 1C DCS structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -777,8 +777,15 @@ def main():
role_parts = []
if role is not None:
for child in role:
if isinstance(child.tag, str) and (child.text or "").strip() == "true":
if not isinstance(child.tag, str):
continue
txt = (child.text or "").strip()
if txt == "true":
role_parts.append(localname(child))
elif txt == "false":
pass
else:
role_parts.append(f"{localname(child)}={txt}")
info["role"] = ", ".join(role_parts)
# UseRestriction
@@ -1,4 +1,4 @@
# skd-validate v1.1 — Validate 1C DCS structure
# skd-validate v1.2 — Validate 1C DCS structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
@@ -438,6 +438,17 @@ if ($script:stopped) { & $finalize; exit 1 }
if ($calcFieldNodes.Count -gt 0) {
$cfOk = $true
$cfSeen = @{}
# Collect totalField dataPaths — an empty calculatedField is legitimate if a
# totalField with the same dataPath provides the expression (real-world
# pattern in vendor ERP/БП reports for fields visible only in totals).
$tfPaths = @{}
foreach ($tf in $totalFieldNodes) {
$tfDp = $tf.SelectSingleNode("s:dataPath", $ns)
if ($tfDp -and $tfDp.InnerText) {
$tfPaths[$tfDp.InnerText] = $true
}
}
foreach ($cf in $calcFieldNodes) {
$dp = $cf.SelectSingleNode("s:dataPath", $ns)
$expr = $cf.SelectSingleNode("s:expression", $ns)
@@ -457,8 +468,15 @@ if ($calcFieldNodes.Count -gt 0) {
}
if (-not $expr -or -not $expr.InnerText.Trim()) {
Report-Error "CalculatedField '$path' has empty expression"
$cfOk = $false
# Empty expression is legitimate in several vendor patterns:
# - totalField with same dataPath provides the calculation
# - groupTemplate uses the field as group name (declarative only)
# - field is referenced only by settingsVariants for grouping
# Surface as warning, not error, to avoid false positives on real
# ERP/БП reports while still flagging the unusual shape.
if (-not $tfPaths.ContainsKey($path)) {
Report-Warn "CalculatedField '$path' has empty expression (declarative-only?)"
}
}
# Warn if collides with a dataset field
@@ -542,14 +560,16 @@ if ($templateNodes.Count -gt 0) {
}
$tName = $nameNode.InnerText
if ($tplSeen.ContainsKey($tName)) {
Report-Error "Duplicate template name: $tName"
$tplOk = $false
# Vendor configs (ERP/БП) ship templates with repeating names — the
# platform identifies them by position/context, not by <name>. Demote
# to warning so the check still surfaces the collision without failing.
Report-Warn "Duplicate template name: $tName (allowed by platform but ambiguous)"
} else {
$tplSeen[$tName] = $true
}
}
if ($tplOk) {
Report-OK "$($templateNodes.Count) template(s): names unique"
Report-OK "$($templateNodes.Count) template(s) found"
}
}
@@ -581,7 +601,8 @@ if ($script:stopped) { & $finalize; exit 1 }
$validComparisonTypes = @(
"Equal","NotEqual","Greater","GreaterOrEqual","Less","LessOrEqual",
"InList","NotInList","InHierarchy","InListByHierarchy",
"InList","NotInList","InHierarchy","NotInHierarchy",
"InListByHierarchy","NotInListByHierarchy",
"Contains","NotContains","BeginsWith","NotBeginsWith",
"Filled","NotFilled"
)
@@ -734,6 +755,176 @@ if ($variantNodes.Count -eq 0) {
}
}
# --- 16. valueType structural checks ---
# Catches broken XDTO that XML/structural checks miss (decimal without xs:,
# missing qualifiers, mismatched qualifier blocks, unknown sign/length tokens).
$validTypeQualifier = @{
'xs:decimal' = 'v8:NumberQualifiers'
'xs:string' = 'v8:StringQualifiers'
'xs:dateTime' = 'v8:DateQualifiers'
'xs:boolean' = ''
'v8:StandardPeriod' = ''
'v8:UUID' = ''
'v8:Null' = ''
'v8:Type' = ''
'v8:ValueStorage' = ''
}
$validSign = @('Any', 'Nonnegative', 'Negative')
$validLength = @('Variable', 'Fixed')
$validFractions = @('Date', 'DateTime', 'Time')
# DCS supports composite types: multiple <v8:Type> blocks may share a single
# trailing qualifier block (e.g. xs:string + CatalogRef.X + StringQualifiers).
# So we collect all types and qualifiers per valueType, then check consistency.
$qualifierProducers = @{
'v8:NumberQualifiers' = 'xs:decimal'
'v8:StringQualifiers' = 'xs:string'
'v8:DateQualifiers' = 'xs:dateTime'
}
$valueTypeNodes = $root.SelectNodes("//s:valueType", $ns)
$vtChecked = 0
$vtOk = $true
foreach ($vt in $valueTypeNodes) {
$vtChecked++
$types = @() # list of short type strings; '' marks a ref type
$qualifiers = @() # list of @{ name = 'v8:XQualifiers'; node = $child }
foreach ($child in $vt.ChildNodes) {
if ($child.NodeType -ne 'Element') { continue }
if ($child.NamespaceURI -ne 'http://v8.1c.ru/8.1/data/core') { continue }
$localName = $child.LocalName
if ($localName -eq 'Type') {
$t = "$($child.InnerText)".Trim()
if (-not $t) {
Report-Error "valueType: <v8:Type> is empty"
$vtOk = $false
continue
}
if ($t -match '^([A-Za-z][A-Za-z0-9]*):(.+)$') {
$prefix = $Matches[1]
$localT = $Matches[2]
if ($prefix -eq 'xs' -or $prefix -eq 'v8') {
if (-not $validTypeQualifier.ContainsKey($t)) {
Report-Error "valueType: unknown type '$t' (allowed: xs:decimal/xs:string/xs:dateTime/xs:boolean/v8:StandardPeriod or <prefix>:*Ref.X)"
$vtOk = $false
} else {
$types += $t
}
} else {
$prefixNs = $child.GetNamespaceOfPrefix($prefix)
if ($prefixNs -eq 'http://v8.1c.ru/8.1/data/enterprise/current-config') {
if (-not ($localT -match '^[A-Za-z]+(Ref)?\.')) {
Report-Error "valueType: ref type '$t' must look like '<prefix>:<Kind>.<Name>' (e.g. d5p1:CatalogRef.X)"
$vtOk = $false
} else {
$types += '' # ref — no qualifier needed
}
} elseif ($prefixNs -eq 'http://v8.1c.ru/8.1/data/enterprise') {
# System types: AccumulationRecordType etc. — no qualifiers
if (-not ($localT -match '^[A-Za-z][A-Za-z0-9]*$')) {
Report-Error "valueType: system type '$t' has unexpected local-name shape"
$vtOk = $false
} else {
$types += ''
}
} else {
Report-Error "valueType: type '$t' uses prefix '$prefix' bound to unexpected namespace '$prefixNs'"
$vtOk = $false
}
}
} else {
Report-Error "valueType: type '$t' has no namespace prefix (expected xs:/v8:/d5p1: — e.g. xs:decimal not decimal)"
$vtOk = $false
}
} elseif ($localName -match 'Qualifiers$') {
$qName = "v8:$localName"
$qualifiers += @{ name = $qName; node = $child }
# Validate qualifier internals
if ($qName -eq 'v8:NumberQualifiers') {
$digits = $child.SelectSingleNode("v8:Digits", $ns)
$frac = $child.SelectSingleNode("v8:FractionDigits", $ns)
$sign = $child.SelectSingleNode("v8:AllowedSign", $ns)
if (-not $digits -or -not ($digits.InnerText -match '^\d+$')) {
Report-Error "v8:NumberQualifiers: <v8:Digits> missing or not a non-negative integer"
$vtOk = $false
}
if (-not $frac -or -not ($frac.InnerText -match '^\d+$')) {
Report-Error "v8:NumberQualifiers: <v8:FractionDigits> missing or not a non-negative integer"
$vtOk = $false
}
if ($sign -and $sign.InnerText -and $sign.InnerText -notin $validSign) {
Report-Error "v8:NumberQualifiers: <v8:AllowedSign>$($sign.InnerText)</v8:AllowedSign> — must be one of: $($validSign -join ', ')"
$vtOk = $false
}
} elseif ($qName -eq 'v8:StringQualifiers') {
$len = $child.SelectSingleNode("v8:Length", $ns)
$al = $child.SelectSingleNode("v8:AllowedLength", $ns)
if (-not $len -or -not ($len.InnerText -match '^\d+$')) {
Report-Error "v8:StringQualifiers: <v8:Length> missing or not a non-negative integer"
$vtOk = $false
}
if ($al -and $al.InnerText -and $al.InnerText -notin $validLength) {
Report-Error "v8:StringQualifiers: <v8:AllowedLength>$($al.InnerText)</v8:AllowedLength> — must be one of: $($validLength -join ', ')"
$vtOk = $false
}
} elseif ($qName -eq 'v8:DateQualifiers') {
$df = $child.SelectSingleNode("v8:DateFractions", $ns)
if ($df -and $df.InnerText -and $df.InnerText -notin $validFractions) {
Report-Error "v8:DateQualifiers: <v8:DateFractions>$($df.InnerText)</v8:DateFractions> — must be one of: $($validFractions -join ', ')"
$vtOk = $false
}
}
}
}
# Cross-check: every qualifier must have a matching scalar type in this valueType
foreach ($q in $qualifiers) {
$producer = $qualifierProducers[$q.name]
if (-not $producer) { continue }
if ($types -notcontains $producer) {
Report-Error "valueType: <$($q.name)> has no matching <v8:Type>$producer</v8:Type> in this valueType"
$vtOk = $false
}
}
}
if ($vtChecked -gt 0 -and $vtOk) {
Report-OK "$vtChecked valueType block(s): structure and qualifiers OK"
}
if ($script:stopped) { & $finalize; exit 1 }
# --- 17. value content checks ---
# Catches literal placeholders ("_") and empty strings in DesignTimeValue refs
# that XDTO would reject at db-load-xml.
$valueNodes = @()
$valueNodes += @($root.SelectNodes("//s:value[@xsi:type]", $ns))
$valueNodes += @($root.SelectNodes("//dcscor:value[@xsi:type]", $ns))
$vChecked = 0
$vOk = $true
foreach ($vn in $valueNodes) {
if (-not $vn) { continue }
$vChecked++
$xsiType = $vn.GetAttribute("type", "http://www.w3.org/2001/XMLSchema-instance")
$text = $vn.InnerText
if ($xsiType -eq 'dcscor:DesignTimeValue') {
if (-not $text -or $text.Trim() -eq '' -or $text.Trim() -eq '_') {
Report-Error "<value xsi:type=`"dcscor:DesignTimeValue`">$text</value> — DesignTimeValue must be a reference path (e.g. Перечисление.X.Y), not '$text'"
$vOk = $false
} elseif (-not ($text -match '^[A-Za-zА-Яа-яЁё]+\.[A-Za-zА-Яа-яЁё0-9_]+')) {
Report-Warn "<value xsi:type=`"dcscor:DesignTimeValue`">$text</value> — doesn't look like a typical ref path"
}
}
}
if ($vChecked -gt 0 -and $vOk) {
Report-OK "$vChecked <value> element(s) with xsi:type: content OK"
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Final output ---
& $finalize
@@ -1,4 +1,4 @@
# skd-validate v1.1 — Validate 1C DCS structure (Python port)
# skd-validate v1.2 — Validate 1C DCS structure (Python port)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import os
@@ -434,6 +434,15 @@ if stopped:
if len(calc_field_nodes) > 0:
cf_ok = True
cf_seen = {}
# Collect totalField dataPaths — an empty calculatedField is legitimate if a
# totalField with the same dataPath provides the expression (real-world
# pattern in vendor ERP/БП reports for fields visible only in totals).
tf_paths = set()
for tf in total_field_nodes:
tf_dp = find(tf, "s:dataPath")
if tf_dp is not None and inner_text(tf_dp):
tf_paths.add(inner_text(tf_dp))
for cf in calc_field_nodes:
dp = find(cf, "s:dataPath")
expr = find(cf, "s:expression")
@@ -451,8 +460,14 @@ if len(calc_field_nodes) > 0:
cf_seen[path] = True
if expr is None or not text_of(expr):
report_error(f"CalculatedField '{path}' has empty expression")
cf_ok = False
# Empty expression is legitimate in several vendor patterns:
# - totalField with same dataPath provides the calculation
# - groupTemplate uses the field as group name (declarative only)
# - field is referenced only by settingsVariants for grouping
# Surface as warning, not error, to avoid false positives on real
# ERP/БП reports while still flagging the unusual shape.
if path not in tf_paths:
report_warn(f"CalculatedField '{path}' has empty expression (declarative-only?)")
# Warn if collides with a dataset field
if path in all_field_paths:
@@ -526,12 +541,14 @@ if len(template_nodes) > 0:
continue
t_name = inner_text(name_node)
if t_name in tpl_seen:
report_error(f"Duplicate template name: {t_name}")
tpl_ok = False
# Vendor configs (ERP/БП) ship templates with repeating names — the
# platform identifies them by position/context, not by <name>. Demote
# to warning so the check still surfaces the collision without failing.
report_warn(f"Duplicate template name: {t_name} (allowed by platform but ambiguous)")
else:
tpl_seen[t_name] = True
if tpl_ok:
report_ok(f"{len(template_nodes)} template(s): names unique")
report_ok(f"{len(template_nodes)} template(s) found")
# ── 13. GroupTemplate checks ─────────────────────────────────
@@ -558,7 +575,8 @@ if stopped:
valid_comparison_types = (
"Equal", "NotEqual", "Greater", "GreaterOrEqual", "Less", "LessOrEqual",
"InList", "NotInList", "InHierarchy", "InListByHierarchy",
"InList", "NotInList", "InHierarchy", "NotInHierarchy",
"InListByHierarchy", "NotInListByHierarchy",
"Contains", "NotContains", "BeginsWith", "NotBeginsWith",
"Filled", "NotFilled",
)
@@ -685,6 +703,166 @@ else:
if v_ok:
report_ok(f"{len(variant_nodes)} settingsVariant(s) found")
# ── 16. valueType structural checks ───────────────────────────
# Catches broken XDTO that XML/structural checks miss (decimal without xs:,
# missing qualifiers, mismatched qualifier blocks, unknown sign/length tokens).
import re as _re_vt
_VALID_TYPE_QUALIFIER = {
'xs:decimal': 'v8:NumberQualifiers',
'xs:string': 'v8:StringQualifiers',
'xs:dateTime': 'v8:DateQualifiers',
'xs:boolean': '',
'v8:StandardPeriod': '',
'v8:UUID': '',
'v8:Null': '',
'v8:Type': '',
'v8:ValueStorage': '',
}
_VALID_SIGN = ('Any', 'Nonnegative', 'Negative')
_VALID_LENGTH = ('Variable', 'Fixed')
_VALID_FRACTIONS = ('Date', 'DateTime', 'Time')
_V8_NS_URI = 'http://v8.1c.ru/8.1/data/core'
_CONFIG_NS_URI = 'http://v8.1c.ru/8.1/data/enterprise/current-config'
# DCS supports composite types: multiple <v8:Type> blocks may share a single
# trailing qualifier block (e.g. xs:string + CatalogRef.X + StringQualifiers).
# So we collect all types and qualifiers per valueType, then check consistency.
_QUALIFIER_PRODUCERS = {
'v8:NumberQualifiers': 'xs:decimal',
'v8:StringQualifiers': 'xs:string',
'v8:DateQualifiers': 'xs:dateTime',
}
vt_nodes = find_all(root, "//s:valueType")
vt_checked = 0
vt_ok = True
for vt in vt_nodes:
vt_checked += 1
types = [] # short type strings; '' marks a ref type
qualifiers = [] # list of (qName, node)
for child in vt:
if not isinstance(child.tag, str):
continue
qn = etree.QName(child.tag)
if qn.namespace != _V8_NS_URI:
continue
local = qn.localname
if local == 'Type':
t = (child.text or '').strip()
if not t:
report_error("valueType: <v8:Type> is empty")
vt_ok = False
continue
m = _re_vt.match(r'^([A-Za-z][A-Za-z0-9]*):(.+)$', t)
if not m:
report_error(f"valueType: type '{t}' has no namespace prefix (expected xs:/v8:/d5p1: — e.g. xs:decimal not decimal)")
vt_ok = False
continue
prefix, local_t = m.group(1), m.group(2)
if prefix in ('xs', 'v8'):
if t not in _VALID_TYPE_QUALIFIER:
report_error(f"valueType: unknown type '{t}' (allowed: xs:decimal/xs:string/xs:dateTime/xs:boolean/v8:StandardPeriod or <prefix>:*Ref.X)")
vt_ok = False
else:
types.append(t)
else:
prefix_ns = child.nsmap.get(prefix)
if prefix_ns == _CONFIG_NS_URI:
if not _re_vt.match(r'^[A-Za-z]+(Ref)?\.', local_t):
report_error(f"valueType: ref type '{t}' must look like '<prefix>:<Kind>.<Name>' (e.g. d5p1:CatalogRef.X)")
vt_ok = False
else:
types.append('') # ref — no qualifier needed
elif prefix_ns == 'http://v8.1c.ru/8.1/data/enterprise':
# System types: AccumulationRecordType etc. — no qualifiers
if not _re_vt.match(r'^[A-Za-z][A-Za-z0-9]*$', local_t):
report_error(f"valueType: system type '{t}' has unexpected local-name shape")
vt_ok = False
else:
types.append('')
else:
report_error(f"valueType: type '{t}' uses prefix '{prefix}' bound to unexpected namespace '{prefix_ns}'")
vt_ok = False
elif local.endswith('Qualifiers'):
q_name = f"v8:{local}"
qualifiers.append((q_name, child))
if q_name == 'v8:NumberQualifiers':
digits = find(child, "v8:Digits")
frac = find(child, "v8:FractionDigits")
sign = find(child, "v8:AllowedSign")
if digits is None or not _re_vt.match(r'^\d+$', text_of(digits)):
report_error("v8:NumberQualifiers: <v8:Digits> missing or not a non-negative integer")
vt_ok = False
if frac is None or not _re_vt.match(r'^\d+$', text_of(frac)):
report_error("v8:NumberQualifiers: <v8:FractionDigits> missing or not a non-negative integer")
vt_ok = False
if sign is not None and text_of(sign) and text_of(sign) not in _VALID_SIGN:
report_error(f"v8:NumberQualifiers: <v8:AllowedSign>{text_of(sign)}</v8:AllowedSign> — must be one of: {', '.join(_VALID_SIGN)}")
vt_ok = False
elif q_name == 'v8:StringQualifiers':
length = find(child, "v8:Length")
al = find(child, "v8:AllowedLength")
if length is None or not _re_vt.match(r'^\d+$', text_of(length)):
report_error("v8:StringQualifiers: <v8:Length> missing or not a non-negative integer")
vt_ok = False
if al is not None and text_of(al) and text_of(al) not in _VALID_LENGTH:
report_error(f"v8:StringQualifiers: <v8:AllowedLength>{text_of(al)}</v8:AllowedLength> — must be one of: {', '.join(_VALID_LENGTH)}")
vt_ok = False
elif q_name == 'v8:DateQualifiers':
df = find(child, "v8:DateFractions")
if df is not None and text_of(df) and text_of(df) not in _VALID_FRACTIONS:
report_error(f"v8:DateQualifiers: <v8:DateFractions>{text_of(df)}</v8:DateFractions> — must be one of: {', '.join(_VALID_FRACTIONS)}")
vt_ok = False
# Cross-check: every qualifier must have a matching scalar type in this valueType
for q_name, _ in qualifiers:
producer = _QUALIFIER_PRODUCERS.get(q_name)
if not producer:
continue
if producer not in types:
report_error(f"valueType: <{q_name}> has no matching <v8:Type>{producer}</v8:Type> in this valueType")
vt_ok = False
if vt_checked > 0 and vt_ok:
report_ok(f"{vt_checked} valueType block(s): structure and qualifiers OK")
if stopped:
finalize()
sys.exit(1)
# ── 17. value content checks ──────────────────────────────────
# Catches literal placeholders ('_') and empty strings in DesignTimeValue refs
# that XDTO would reject at db-load-xml.
value_nodes = find_all(root, "//s:value[@xsi:type]") + find_all(root, "//dcscor:value[@xsi:type]")
v_checked = 0
v_ok = True
for vn in value_nodes:
if vn is None:
continue
v_checked += 1
xsi_type = vn.get(XSI_TYPE) or ''
text = vn.text or ''
if xsi_type == 'dcscor:DesignTimeValue':
stripped = text.strip()
if not stripped or stripped == '_':
report_error(f"<value xsi:type=\"dcscor:DesignTimeValue\">{text}</value> — DesignTimeValue must be a reference path (e.g. Перечисление.X.Y), not '{text}'")
v_ok = False
elif not _re_vt.match(r'^[A-Za-zА-Яа-яЁё]+\.[A-Za-zА-Яа-яЁё0-9_]+', stripped):
report_warn(f"<value xsi:type=\"dcscor:DesignTimeValue\">{text}</value> — doesn't look like a typical ref path")
if v_checked > 0 and v_ok:
report_ok(f"{v_checked} <value> element(s) with xsi:type: content OK")
if stopped:
finalize()
sys.exit(1)
# ── Final output ──────────────────────────────────────────────
finalize()
+4
View File
@@ -529,3 +529,7 @@ On error (auto-screenshot taken):
- **Cyrillic in bash** — use `cat <<'SCRIPT' | node $RUN exec -` to avoid escaping issues
- **Non-breaking spaces** — 1C uses `\u00a0` instead of regular spaces. All matching is normalized internally
- **Section panel display**`navigateSection()` works with any panel position (side, top) but requires "Picture and text" or "Text" display mode. Icon-only mode is not supported — API cannot read section names from icons alone
## Regression suites
When the user asks to cover a 1C solution with automated regression — multi-file test suites with assertions, hooks, tags, retries, Allure/JUnit reports, multi-user process tests — switch to the `test` mode. See [regress.md](regress.md) for authoring discipline, recon flow (metadata + live walkthrough via `exec`), per-application folder layout, ready-to-paste templates, and failure triage. Default to ad-hoc `run`/`exec` for single-script automation — `test` is the specialised mode for project-wide coverage.
+420
View File
@@ -0,0 +1,420 @@
# Regression suite authoring
Use this when the user asks to cover a 1C solution with automated regression tests, build out a test suite, or run an existing suite and analyse failures. For ad-hoc single-script automation, stay with the `run`/`exec` modes from SKILL.md instead.
The runner is the same `run.mjs`. The mode is `test`:
```bash
node $RUN test [url] <dir|file> [flags]
```
Tests live next to the project they cover (not inside the skill). Convention: `tests/` at the project root, with `_hooks.mjs` and `webtest.config.mjs` at the suite root. Tests are ES modules with `*.test.mjs` suffix.
## When to choose `test` over `exec`
| Goal | Mode |
|------|------|
| Explore a form, prototype a single step, debug one selector | `exec` (interactive session) |
| Reproduce a bug as a failing test before fixing it | `test` |
| Cover a feature so future changes are checked automatically | `test` |
| Run the project's regression on a new build | `test` |
| Generate a screencast walkthrough | `exec` with `startRecording` |
Don't write a `.test.mjs` for a one-shot user request. Don't drive a regression suite through chained `exec` calls.
## Before writing tests — recon
Two layers, in order.
**1. Static recon — metadata.** Never invent identifiers. For every metadata object the user mentions, run the matching info skill first: `/meta-info` (attributes/tabular sections), `/form-info` (form layout), `/skd-info` (DCS), `/mxl-info` (templates), `/role-info` (rights), `/subsystem-info` (composition / command interface). If the user names objects you can't find — stop and ask.
**2. Live recon — interactive walkthrough.** For any non-trivial scenario, walk the path live in `exec` mode before transcribing it. Metadata tells you what exists; the live walkthrough tells you what actually happens. Capture from `getFormState()`: exact button names (`'Провести и закрыть'`, not `'Сохранить'`), table section names for multi-grid forms, required fields, places where a real async wait is needed. Then transcribe the working sequence into `*.test.mjs`, wrapping logical chunks in `step('...', async () => { ... })`.
The mechanics of `exec` / `getFormState` / `fillFields` / `clickElement` are in [SKILL.md](SKILL.md) — read it before recon if you haven't already.
When live recon is overkill: trivial reads (`navigateSection` + `readTable` + assert non-empty), or scenarios you've already proven once in this session. When it's essential: confirmation dialogs, posting/cancellation flows, reports with custom filters, multi-grid forms, user-customised forms.
## Suite layout
**Each application gets its own subfolder under `tests/`.** A single repo may host several independent suites side by side — they must not share `_hooks.mjs` or `webtest.config.mjs`, because each suite restores a different DB, publishes to a different URL, and ships its own test data.
```
tests/
<app-name>/ # application regression — one per solution
_hooks.mjs
webtest.config.mjs
_allure/ # optional static Allure config
01-login/
02-counterparties/
...
<another-app>/ # second solution, fully isolated
```
Inside the application subfolder, organize by **feature**, not by metadata kind. Numeric prefixes on both folder and file enforce run order — discovery walks recursively and sorts files by full relative path; entries starting with `_` or `.` are skipped (so `_hooks.mjs`, `_allure/` won't be picked up as tests).
```
tests/<app-name>/
01-login/
01-open-base.test.mjs
02-section-navigation.test.mjs
02-counterparties/
01-create.test.mjs
02-edit-phone.test.mjs
03-goods-receipt/
01-fill.test.mjs
02-post.test.mjs
05-approval-process/
01-end-to-end.test.mjs # multi-user
```
Per-folder `_hooks.mjs` / `webtest.config.mjs` inside the application subfolder are NOT supported — only the application-root copies are loaded.
## Test file anatomy
```js
export const name = 'Создание контрагента'; // required
export const tags = ['catalog', 'create']; // optional, used for filtering + Allure
export const timeout = 60000; // optional, default 30000
// export const skip = 'pending fix #123'; // optional: true | string
// export const only = true; // debug-only — never commit
// export const context = 'manager'; // optional, single non-default context
// export const contexts = ['clerk', 'manager']; // optional, multi-user test
// export const severity = 'critical'; // optional, overrides config severity
export async function setup(ctx) {
// per-test prep — runs before default. Skip if not needed.
}
export async function teardown(ctx) {
// per-test cleanup — runs after default, always (even on failure).
}
export default async function(ctx) {
const { navigateSection, openCommand, clickElement, fillFields,
readTable, closeForm, getFormState,
assert, step, log } = ctx;
await step('Открыть список контрагентов', async () => {
await navigateSection('Продажи');
await openCommand('Контрагенты');
});
await step('Создать нового контрагента', async () => {
await clickElement('Создать');
await fillFields({ 'Наименование': 'Тест ' + Date.now() });
await clickElement('Записать и закрыть');
});
await step('Убедиться, что элемент появился в списке', async () => {
const t = await readTable();
assert.tableHasRow(t, r => r['Наименование']?.startsWith('Тест '));
});
}
```
**Step names — in Russian, descriptive.** Step labels surface in the console output, in JSON/JUnit, and as Allure step nodes. Russian-speaking QA reads them. Use a full action phrase (`'Создать нового контрагента'`), not a tag (`'create'`) and not a transliteration. Same applies to `export const name` and `displayName` in `webtest.config.mjs`.
## `ctx` contract
The runner injects every `browser.mjs` export into `ctx` (all 1C action functions auto-detect platform errors — see SKILL.md), plus the test utilities below.
### Test utilities
```js
step(name, fn) // async wrapper. Records start/stop. Nested calls supported.
// On throw: marks the step failed, re-throws.
// On screenshot='every-step': captures after fn().
log(...args) // adds a line to ctx.testInfo's output (goes into JSON / Allure
// attachment). Use instead of console.log inside tests.
assert.* // see "Assertions" below
```
### `ctx.testInfo` (always set, read-only)
```js
{
name, // 'Навигация по разделам' (with params substituted)
file, // '01-navigation.test.mjs' (basename)
filePath, // relative path inside testDir
tags, // ['nav', 'smoke']
timeout, // ms
attempt, // 1..maxAttempts (1-based)
maxAttempts, // 1 + retry
param, // { ... } | undefined (only when export const params is set)
contexts: { // mirrors config.contexts; includes custom fields like displayName
clerk: { url, isolation, displayName, ... },
manager: { ... },
},
primaryContext, // 'clerk' — name of the context active at test entry
// (= t.context for single, t.contexts[0] for multi)
}
```
### `ctx.testResult` (only in `afterEach`)
```js
{
status, // 'passed' | 'failed'
duration, // ms
attempts, // attempts actually executed
error, // { message, step?, screenshot? } | null
steps, // array of step results (each: { name, start, stop, status, error?, steps[] })
}
```
### Context shape
- **Single-context (default or `export const context = 'manager'`):** all API on `ctx` top-level — `ctx.clickElement(...)`, `ctx.getFormState()`, etc.
- **Multi-context (`export const contexts = ['clerk', 'manager']`):** each name is its own scoped namespace — `ctx.clerk.clickElement(...)`, `ctx.manager.fillFields(...)`. `step`, `assert`, `log`, `testInfo` stay top-level. Scoped methods auto-switch the active page before each call.
## Assertions
All on `ctx.assert`. Throw `AssertionError` with `.message`, `.actual`, `.expected`. No dependencies.
```js
// generic
assert.ok(value, msg?) // truthy
assert.equal(actual, expected, msg?) // ===
assert.notEqual(actual, expected, msg?) // !==
assert.deepEqual(actual, expected, msg?) // JSON-compare
assert.includes(haystack, needle, msg?) // string.includes / array.includes
assert.match(string, regex, msg?) // regex.test(string)
await assert.throws(asyncFn, msg?) // passes if fn throws (use await)
// 1C-specific — operate on getFormState() / readTable() output
assert.formHasField(state, 'Контрагент', msg?) // state.fields[name] exists
assert.formTitle(state, expected, msg?) // state.title includes expected
assert.tableHasRow(table, predicate, msg?) // predicate: object (partial match) or fn(row) => bool
// object form: { 'Наименование': 'Тест' }
// fn form: r => r['Сумма'] > 100
assert.tableRowCount(table, expected, msg?) // table.rows.length === expected
assert.noErrors(state, msg?) // !state.errors
```
Beyond these, just use plain JS (`throw new Error(...)`) — there's no custom matcher extension API. The 1C-specific helpers are the ones worth preferring over hand-rolled equivalents because their error messages name the actual fields/rows present, which speeds up triage.
## webtest.config.mjs
```js
export default {
// Single-context shorthand:
url: 'http://localhost:9191/myapp/ru_RU',
// OR multi-context:
// contexts: {
// clerk: { url: 'http://localhost:9191/myapp-clerk/ru_RU', displayName: 'Кладовщик' },
// manager: { url: 'http://localhost:9191/myapp-manager/ru_RU', displayName: 'Менеджер' },
// },
// defaultContext: 'clerk',
timeout: 30000,
retries: 0,
screenshot: 'on-failure', // 'every-step' | 'off'
record: false,
// Severity → tags mapping for Allure. Each tag at most one bucket.
severity: {
critical: ['smoke', 'crud'],
minor: ['recording'],
},
defaultSeverity: 'normal',
};
```
CLI flags override config. Use latin context IDs + Russian `displayName` for ergonomics — `ctx.testInfo.contexts.clerk.displayName` is friendlier than mixed-case Cyrillic keys.
## _hooks.mjs
Two layers. Infra hooks run without a browser; testlevel hooks receive `ctx`.
```js
import { execSync } from 'child_process';
// Infra — runs once around the whole suite.
export async function prepare({ hookArgs, log, config }) {
// hookArgs: everything after `--` on the CLI, as a string[]. Parse yourself.
const force = hookArgs.includes('--rebuild-stand');
const dataArg = hookArgs.find(a => a.startsWith('--data='))?.slice('--data='.length);
log('preparing stand, force=', force, 'data=', dataArg);
// Idempotent hash-locks on inputs (config sources, EPF spec, DB dump) keep
// warm starts to a liveness probe.
}
export async function cleanup({ log, config }) { /* optional */ }
// Testlevel — runs with browser ctx.
export async function beforeAll(ctx) { /* once after first context opens */ }
export async function afterAll(ctx) { /* once before final teardown */ }
export async function beforeEach(ctx) { /* ctx.testInfo is set */ }
export async function afterEach(ctx) { /* ctx.testInfo + ctx.testResult set */ }
// Per-context — runs whenever a context is created/closed.
export async function afterOpenContext(ctx, name, spec) { /* spec = config.contexts[name] */ }
export async function beforeCloseContext(ctx, name, spec) { }
```
Built-in state reset (`dismissPendingErrors` + close all forms) runs after `afterEach` automatically. Don't reimplement it in `afterEach`.
Pass hook args after `--`:
```bash
node $RUN test tests/<app-name>/ --bail -- --rebuild-stand --data=demo
└─runner─┘ └────── hookArgs ─────────┘
```
**Where to put data setup:**
- DB restore, publication, EPF build → `prepare()`. Make it idempotent (hash-locks).
- Test-specific seed data → per-test `setup`.
- Shared session-wide warmup → `beforeAll`.
## Ready-to-paste patterns
A minimal CRUD shape is in *Test file anatomy* above — use it as the rhythm for catalog/document tests, swapping in the right section/command/fields. The patterns below cover what's specific to the regression engine, not the browser API (those live in SKILL.md).
### DCS report
```js
await openCommand('Остатки товаров');
// Reset user settings — 1C persists them between sessions.
await clickElement('Ещё');
await clickElement('Установить стандартные настройки');
await selectValue('Номенклатура', 'Товар 02'); // auto-enables the filter checkbox
await clickElement('Сформировать');
await wait(3);
const r = await readSpreadsheet();
assert.deepEqual(r.headers, ['Номенклатура', 'Количество', 'Сумма']);
assert.ok(r.data.length >= 1);
assert.ok(r.totals?.['Сумма']);
```
### Multi-user process
```js
export const contexts = ['clerk', 'manager'];
export default async function({ clerk, manager, step, assert }) {
await step('Кладовщик создаёт накладную', async () => {
await clerk.navigateSection('Склад');
await clerk.openCommand('Приходные накладные');
await clerk.clickElement('Создать');
await clerk.fillFields({ 'Контрагент': 'ООО Север' });
await clerk.clickElement('Записать');
});
await step('Менеджер утверждает накладную', async () => {
await manager.navigateSection('Согласование');
await manager.openCommand('На утверждении');
await manager.clickElement('ООО Север', { dblclick: true });
await manager.clickElement('Утвердить');
});
await step('Кладовщик видит новый статус', async () => {
const s = await clerk.getFormState();
assert.equal(s.fields['Статус']?.value, 'Утверждён');
});
await step('Освободить сессию кладовщика', async () => {
await manager.closeContext('clerk'); // free a 1C license for the next test
});
}
```
Close contexts you no longer need (`manager.closeContext('clerk')`) before the next multi-user test starts — frees a 1C web-client license and stops the previous role from holding state.
### Failing-test repro
```js
export const name = 'Bug #123: накладная без контрагента не должна проводиться';
export const tags = ['bug', 'validation'];
export default async function({ openCommand, clickElement, getFormState, assert, step }) {
await openCommand('Приходные накладные');
await clickElement('Создать');
await clickElement('Провести');
const s = await getFormState();
assert.ok(s.errorModal || s.fields['Контрагент']?.required,
'Должна быть ошибка валидации или поле помечено обязательным');
}
```
Write it red first, hand it to the user, fix the underlying issue, re-run green.
### Parameterised test
```js
export const name = 'Заполнение поля {type}';
export const params = [
{ type: 'String', field: 'Наименование', value: 'Тест' },
{ type: 'Number', field: 'Цена', value: '100.50' },
{ type: 'Date', field: 'ДатаПоступления', value: '01.01.2024' },
];
export default async function({ fillFields, getFormState, assert }, { type, field, value }) {
await fillFields({ [field]: value });
const state = await getFormState();
assert.equal(state.fields[field]?.value, String(value));
}
```
Each `params` entry becomes its own test in the report. `{key}` placeholders in `name` get substituted; without placeholders, a `[index]` suffix is added. `ctx.testInfo.param` carries the current row.
## Running
```bash
node $RUN test tests/<app-name>/ # full app suite
node $RUN test tests/<app-name>/03-goods-receipt/ # one feature folder
node $RUN test tests/<app-name>/02-counterparties/01-create.test.mjs # one file
node $RUN test tests/<app-name>/ --tags=smoke # by tag (intersection)
node $RUN test tests/<app-name>/ --grep='накладн' # by name regex
node $RUN test tests/<app-name>/ --bail --retry=1 # stop on first fail, allow 1 retry
node $RUN test tests/<app-name>/ --report=allure-results --format=allure --report-dir=allure-results
node $RUN test tests/<app-name>/ -- --rebuild-stand # after `--` → hookArgs
```
Default report is JSON when `--report=…` is given. Allure needs `--format=allure` + a directory. JUnit similarly with `--format=junit`.
### Allure static config — `_allure/`
The runner copies `<testDir>/_allure/` into the report directory before generating Allure output. Drop in `categories.json` (regex-based failure classification — useful for 1C-specific buckets: license pool exhaustion, platform exceptions, runner timeouts, assertion failures), `environment.properties` (optional, often emitted dynamically by `prepare()`), `executor.json` (CI metadata, skip locally). The underscore prefix keeps the directory out of test discovery.
## Severity guidance
When the user doesn't dictate, default to:
| Test kind | Severity |
|-----------|----------|
| Login + section navigation, basic CRUD on covered entities | `critical` (also tag `smoke`) |
| Documents posting, report generation, end-to-end processes | `critical` |
| Field-level edge cases, formatting, optional flows | `normal` |
| Cosmetic / recording / non-functional | `minor` |
| Reserved for show-stopper protections | `blocker` (use sparingly) |
Don't promote everything to `critical` — it loses signal in the Allure dashboard.
## Anti-patterns
- **Sleeps as a substitute for assertions.** `wait(5)` after `openCommand` is fine; `wait(30)` because something flakes is a bug — wait on `getFormState` instead.
- **Retry as a substitute for understanding.** "Not found" twice means the data isn't there or the label is wrong. Don't loop.
- **Position-based row identification** (`rows[0]`) when the DB has shared seed data. Filter by a unique marker (`Date.now()` suffix) instead.
- **Hand-writing reset code in `afterEach`.** The runner already closes forms and dismisses errors after the hook.
- **Cross-test state assumptions.** Each test must start from the desktop and seed its own data. Order-of-execution coupling is a regression-suite trap.
- **`tags: ['smoke']` on a 90-second test.** Smoke means fast.
- **Skipping recon** because "I know what this catalog looks like." The project's customisation almost certainly differs from stock.
(General browser-API anti-patterns — raw DOM, `clickElement('Закрыть')` instead of `closeForm()` — live in SKILL.md.)
## After a run — failure triage
1. Scan the JSON or Allure summary for `failed`.
2. For each failure, read `error.message` + `error.step` + screenshot.
3. If `error.onecError.stack` is present — it's a 1C exception, look at the platform trace.
4. Classify:
- **Test bug** — selector wrong, expectation wrong, race with no anchor → fix the test.
- **Application bug** — actual misbehaviour reproduced → report to the user with the failing step name and the platform stack.
- **Stand flake** — Apache timeout, login form not loading, license shortage → fix the hook idempotency or session-cleanup logic, not the test.
5. After fixes, re-run only the affected files before the full suite.
Report back to the user with the classification, not raw failure dumps.
## Reference
- Browser API: [SKILL.md](SKILL.md)
- Video and narration: [recording.md](recording.md)
+305 -66
View File
@@ -1,4 +1,4 @@
// web-test browser v1.9 — Playwright browser management for 1C web client
// web-test browser v1.12 — Playwright browser management for 1C web client
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
/**
* Playwright browser management for 1C web client.
@@ -37,6 +37,16 @@ let lastCaptions = []; // captions from the last completed recording (for addNar
let lastRecordingDuration = null; // wall-clock duration of the last recording (seconds)
let highlightMode = false;
// Multi-context registry: name → { context, page, sessionPrefix, seanceId, recorder, lastCaptions, lastRecordingDuration, highlightMode }
// Populated by createContext(); module-level vars above mirror the active slot.
// connect() does NOT use this Map — it preserves legacy single-session behavior for exec/run/start.
const contexts = new Map();
let activeContextName = null;
// Isolation mode for the current cmdTest session — set by the first createContext call.
// 'tab': all contexts share one persistent context (one window, multiple tabs, extension loads reliably).
// 'window': each context gets its own BrowserContext (separate window per context, full cookie isolation, extension may not load).
let activeMode = null;
const LOAD_TIMEOUT = 60000;
const INIT_TIMEOUT = 60000;
const ACTION_WAIT = 2000; // fallback minimum wait
@@ -158,31 +168,51 @@ export async function connect(url, { extensionPath } = {}) {
return await getPageState();
}
/**
* Best-effort POST /e1cib/logout on a slot to release the 1C session license.
* Silent if page is closed or session info missing, just returns.
* @param {object} slot { page, sessionPrefix, seanceId } from contexts Map
* @param {number} [waitMs=500] pause after logout fetch (gives 1C time to process)
*/
async function _logoutSlot(slot, waitMs = 500) {
if (!slot?.page || slot.page.isClosed() || !slot.seanceId || !slot.sessionPrefix) return;
try {
const logoutUrl = `${slot.sessionPrefix}/e1cib/logout?seanceId=${slot.seanceId}`;
await slot.page.evaluate(async (url) => {
await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{"root":{}}' });
}, logoutUrl);
await slot.page.waitForTimeout(waitMs);
} catch {}
}
/**
* Gracefully terminate the 1C session and close the browser.
* Sends POST /e1cib/logout to release the license before closing.
*/
export async function disconnect() {
// Auto-stop recording if active (prevents orphaned ffmpeg)
// Multi-context path: stop recording + logout each slot before closing browser
if (contexts.size > 0) {
_saveActiveSlot();
// Recorder is global — one stop covers all contexts
if (recorder) {
try { await stopRecording(); } catch {}
}
for (const [, slot] of contexts.entries()) {
await _logoutSlot(slot);
}
contexts.clear();
activeContextName = null;
activeMode = null;
}
// Single-session path (connect): auto-stop recording if active
if (recorder) {
try { await stopRecording(); } catch {}
}
if (browser) {
// Graceful logout — release the 1C license
if (page && !page.isClosed() && seanceId && sessionPrefix) {
try {
const logoutUrl = `${sessionPrefix}/e1cib/logout?seanceId=${seanceId}`;
await page.evaluate(async (url) => {
await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: '{"root":{}}'
});
}, logoutUrl);
await page.waitForTimeout(1000);
} catch {}
}
// Graceful logout — release the 1C license (single-session connect path)
await _logoutSlot({ page, sessionPrefix, seanceId }, 1000);
await browser.close().catch(() => {});
browser = null;
page = null;
@@ -228,6 +258,203 @@ export function getSession() {
return { sessionPrefix, seanceId };
}
// ============================================================
// Multi-context support (used by run.mjs cmdTest only)
// ============================================================
/**
* Save current module-level state into the active slot before switching.
* No-op if no active slot.
*/
function _saveActiveSlot() {
if (!activeContextName) return;
const slot = contexts.get(activeContextName);
if (!slot) return;
slot.page = page;
slot.sessionPrefix = sessionPrefix;
slot.seanceId = seanceId;
slot.highlightMode = highlightMode;
// Note: `recorder`, `lastCaptions`, `lastRecordingDuration` are intentionally NOT
// mirrored per-slot. A multi-context recording produces one continuous output file —
// the recorder follows the active page via recorder._attachPage(), not per-slot state.
}
/** Load a slot's state into module-level vars and mark it active. */
function _activateSlot(name) {
const slot = contexts.get(name);
if (!slot) throw new Error(`Context "${name}" not found. Create it via createContext() first.`);
page = slot.page;
sessionPrefix = slot.sessionPrefix;
seanceId = slot.seanceId;
highlightMode = slot.highlightMode || false;
activeContextName = name;
}
/** Attach 1C session listeners to a page, writing into the given slot. */
function _attachSessionListeners(pg, slot, name) {
pg.on('dialog', dialog => dialog.accept().catch(() => {}));
pg.on('request', req => {
if (slot.seanceId) return;
const m = req.url().match(/^(https?:\/\/[^/]+\/[^/]+\/[^/]+)\/e1cib\/.+[?&]seanceId=([^&]+)/);
if (m) {
slot.sessionPrefix = m[1];
slot.seanceId = m[2];
if (activeContextName === name) {
sessionPrefix = m[1];
seanceId = m[2];
}
}
});
}
/**
* Create (or navigate) a named browser context.
* First call launches Chromium via chromium.launch() (NOT launchPersistentContext) so that
* subsequent calls can create additional isolated BrowserContexts in the same process.
* Trade-off: 1C browser extension is loaded via --load-extension (process-level) rather than
* persistent profile.
*
* Use this from run.mjs cmdTest only exec/run/start use connect() and stay on the
* legacy persistent-context path.
*/
export async function createContext(name, url, { extensionPath, isolation = 'tab' } = {}) {
if (contexts.has(name)) {
await setActiveContext(name);
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: LOAD_TIMEOUT });
try { await page.waitForSelector('#themesCell_theme_0', { timeout: INIT_TIMEOUT }); }
catch { await page.waitForTimeout(5000); }
await closeModals();
return await getPageState();
}
if (!['tab', 'window'].includes(isolation)) {
throw new Error(`createContext: invalid isolation "${isolation}", expected 'tab' or 'window'`);
}
if (activeMode && activeMode !== isolation) {
throw new Error(`createContext: cannot mix isolation modes — first context used "${activeMode}", "${name}" requested "${isolation}". Use the same mode for all contexts in one run.`);
}
// First context: launch browser. Subsequent: reuse existing.
let isFirstContext = !browser;
if (isFirstContext) {
const extPath = findExtension(extensionPath);
const launchArgs = ['--start-maximized'];
if (extPath) {
launchArgs.push('--disable-extensions-except=' + extPath, '--load-extension=' + extPath);
}
if (isolation === 'tab') {
// Persistent context: extension loads reliably, one window with tabs per context
persistentUserDataDir = pathJoin(tmpdir(), 'pw-1c-test-' + Date.now());
mkdirSync(persistentUserDataDir, { recursive: true });
browser = await chromium.launchPersistentContext(persistentUserDataDir, {
headless: false,
args: launchArgs,
viewport: null,
permissions: ['clipboard-read', 'clipboard-write'],
});
} else {
// Window mode: separate BrowserContext per slot, full cookie isolation
browser = await chromium.launch({ headless: false, args: launchArgs });
}
activeMode = isolation;
}
// Save current active before switching
_saveActiveSlot();
// Create slot — page differs by mode
let newCtx, newPage;
if (activeMode === 'tab') {
// Reuse the persistent context for all slots; each slot gets its own page (tab)
newCtx = browser;
if (isFirstContext) {
newPage = browser.pages()[0] || await browser.newPage();
} else {
newPage = await browser.newPage();
}
} else {
// Window mode: each slot owns its BrowserContext + page
newCtx = await browser.newContext({
viewport: null,
permissions: ['clipboard-read', 'clipboard-write'],
});
newPage = await newCtx.newPage();
}
const slot = {
context: newCtx,
page: newPage,
sessionPrefix: null,
seanceId: null,
highlightMode: false,
};
contexts.set(name, slot);
_attachSessionListeners(newPage, slot, name);
_activateSlot(name);
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: LOAD_TIMEOUT });
try { await page.waitForSelector('#themesCell_theme_0', { timeout: INIT_TIMEOUT }); }
catch { await page.waitForTimeout(5000); }
await closeModals();
return await getPageState();
}
/** Switch the active context. Subsequent browser API calls operate on this context's page. */
export async function setActiveContext(name) {
if (activeContextName === name) return;
if (!contexts.has(name)) throw new Error(`Context "${name}" not found. Available: [${[...contexts.keys()].join(', ')}]`);
// If a recording is active, flush the outgoing page's last frame so the gap is filled
// up to the moment of the switch (avoids a "jump" in video time).
if (recorder && recorder._flushFrames) recorder._flushFrames();
_saveActiveSlot();
_activateSlot(name);
// If the recording is still alive (it lives across slots — we keep the same ffmpeg/output),
// re-attach its screencast to the newly active page.
if (recorder && recorder._attachPage) {
await recorder._attachPage(page);
}
}
export function listContexts() {
return [...contexts.keys()];
}
export function getActiveContext() {
return activeContextName;
}
export function hasContext(name) {
return contexts.has(name);
}
/**
* Close a named context: logout, close its page (tab mode) or BrowserContext
* (window mode), remove from registry. Cannot close the currently active
* context caller must setActiveContext to another first. This keeps the
* recorder/page invariants simple: recorder is always attached to the
* active slot, which closeContext never touches.
*
* @throws if name is not registered or equals the active context.
*/
export async function closeContext(name) {
if (!contexts.has(name)) {
throw new Error(`Context "${name}" not found. Available: [${[...contexts.keys()].join(', ')}]`);
}
if (name === activeContextName) {
throw new Error(`closeContext: cannot close the active context "${name}". setActiveContext to another context first.`);
}
const slot = contexts.get(name);
await _logoutSlot(slot);
if (activeMode === 'tab') {
try { await slot.page.close(); } catch {}
} else {
try { await slot.context.close(); } catch {}
}
contexts.delete(name);
}
/**
* Close startup modals and guide tabs.
* Strategy: Escape click default buttons close extra tabs repeat.
@@ -4861,10 +5088,7 @@ export async function startRecording(outputPath, opts = {}) {
const resolvedPath = resolveProjectPath(outputPath);
mkdirSync(dirname(resolvedPath), { recursive: true });
// Create CDP session for screencast
const cdp = await page.context().newCDPSession(page);
// Spawn ffmpeg process
// Spawn ffmpeg process — single output file across context switches
const ffmpeg = spawn(ffmpegPath, [
'-y', // overwrite output
'-f', 'image2pipe', // input: piped images
@@ -4880,71 +5104,86 @@ export async function startRecording(outputPath, opts = {}) {
resolvedPath
], { stdio: ['pipe', 'ignore', 'pipe'] });
let ffmpegError = '';
ffmpeg.stderr.on('data', d => { ffmpegError += d.toString(); });
ffmpeg.on('error', err => { ffmpegError += err.message; });
ffmpeg.on('error', err => { if (recorder) recorder.ffmpegError += err.message; });
// Listen for screencast frames and pipe to ffmpeg
// CDP sends frames only on screen changes, so we duplicate frames
// to fill gaps and maintain real-time playback speed
const frameDuration = 1000 / fps;
let lastFrameTime = null;
let lastFrameBuf = null;
const speechRate = opts.speechRate || 70; // ms per character for smart TTS wait
cdp.on('Page.screencastFrame', async ({ data, sessionId }) => {
// Frame handler shared across CDP sessions (lives in recorder, not closure):
// when the active context switches, we attach a new CDP session and route its
// frames to the same ffmpeg pipe — preserving a single continuous timeline.
const frameHandler = async ({ data, sessionId }, cdp) => {
if (!recorder) return;
const buf = Buffer.from(data, 'base64');
const now = Date.now();
if (!ffmpeg.stdin.destroyed) {
let framesWritten = 0;
if (lastFrameTime && lastFrameBuf) {
// Fill the gap with duplicates of the previous frame
const gap = now - lastFrameTime;
if (recorder.lastFrameTime && recorder.lastFrameBuf) {
const gap = now - recorder.lastFrameTime;
const dupes = Math.round(gap / frameDuration) - 1;
for (let i = 0; i < dupes && i < fps * 30; i++) {
ffmpeg.stdin.write(lastFrameBuf);
ffmpeg.stdin.write(recorder.lastFrameBuf);
framesWritten++;
}
}
ffmpeg.stdin.write(buf);
framesWritten++;
// Track actual video timeline position (accounts for frame duplication)
if (recorder) recorder.videoTimeMs += framesWritten * frameDuration;
recorder.videoTimeMs += framesWritten * frameDuration;
}
lastFrameTime = now;
lastFrameBuf = buf;
recorder.lastFrameTime = now;
recorder.lastFrameBuf = buf;
try { await cdp.send('Page.screencastFrameAck', { sessionId }); } catch {}
});
// Start the screencast
await cdp.send('Page.startScreencast', {
format: 'jpeg',
quality,
everyNthFrame: 1
});
// Expose a frame-writing helper on the recorder object.
// During static periods (e.g. smart TTS pauses), CDP may not send screencast
// frames. Call _flushFrames() to fill the gap with duplicates of the last frame,
// keeping video timeline in sync with wall-clock time.
const _flushFrames = () => {
if (!lastFrameBuf || !lastFrameTime || ffmpeg.stdin.destroyed) return;
const now = Date.now();
const gap = now - lastFrameTime;
const dupes = Math.round(gap / frameDuration);
for (let i = 0; i < dupes; i++) {
ffmpeg.stdin.write(lastFrameBuf);
if (recorder) recorder.videoTimeMs += frameDuration;
}
if (dupes > 0) lastFrameTime = now;
};
const speechRate = opts.speechRate || 70; // ms per character for smart TTS wait
recorder = { cdp, ffmpeg, startTime: Date.now(), outputPath: resolvedPath, ffmpegError: '', captions: [], videoTimeMs: 0, _flushFrames, speechRate };
// Redirect stderr accumulation to the recorder object
ffmpeg.stderr.removeAllListeners('data');
// Duplicate the last frame to fill wall-clock gaps (static periods, context switches).
const _flushFrames = () => {
if (!recorder || !recorder.lastFrameBuf || !recorder.lastFrameTime || ffmpeg.stdin.destroyed) return;
const now = Date.now();
const gap = now - recorder.lastFrameTime;
const dupes = Math.round(gap / frameDuration);
for (let i = 0; i < dupes; i++) {
ffmpeg.stdin.write(recorder.lastFrameBuf);
recorder.videoTimeMs += frameDuration;
}
if (dupes > 0) recorder.lastFrameTime = now;
};
// Attach screencast to a specific page. Stops the old CDP first (if any).
// Called by startRecording for the initial page, and by setActiveContext when
// the active context changes mid-recording.
const _attachPage = async (targetPage) => {
if (recorder.cdp) {
_flushFrames(); // freeze the last frame of the outgoing page up to "now"
try { await recorder.cdp.send('Page.stopScreencast'); } catch {}
try { await recorder.cdp.detach(); } catch {}
recorder.cdp = null;
}
const cdp = await targetPage.context().newCDPSession(targetPage);
cdp.on('Page.screencastFrame', (ev) => frameHandler(ev, cdp));
await cdp.send('Page.startScreencast', { format: 'jpeg', quality, everyNthFrame: 1 });
recorder.cdp = cdp;
recorder.activePage = targetPage;
};
recorder = {
cdp: null,
activePage: null,
ffmpeg,
startTime: Date.now(),
outputPath: resolvedPath,
ffmpegError: '',
captions: [],
videoTimeMs: 0,
frameDuration,
lastFrameTime: null,
lastFrameBuf: null,
_flushFrames,
_attachPage,
speechRate,
};
ffmpeg.stderr.on('data', d => { recorder.ffmpegError += d.toString(); });
await _attachPage(page);
}
/**
File diff suppressed because it is too large Load Diff
+1
View File
@@ -49,3 +49,4 @@ __pycache__/
.opencode/
.roo/
.windsurf/
debug-templates.txt
+2
View File
@@ -74,6 +74,7 @@ python tools/cc-1c-skills/scripts/switch.py
| Веб-публикация (Web) | 4 навыка `/web-*` | Публикация баз через Apache, статус, остановка, удаление публикаций | [Подробнее](docs/web-guide.md) |
| Тестирование (Web) | `/web-test` | Взаимодействие с веб-клиентом 1С — навигация, формы, таблицы, отчёты, тестирование | [Подробнее](docs/web-test-guide.md) |
| Запись видео (Web) | `/web-test` | Запись видеоинструкций с субтитрами, подсветкой и TTS-озвучкой | [Подробнее](docs/web-test-recording-guide.md) |
| Регресс прикладного решения (Web) | `/web-test` | Автоматический регресс конфигурации: тесты, проверки, отчёты, прогон после правок | [Подробнее](docs/web-test-regression-guide.md) |
| Утилиты | `/img-grid` | Наложение сетки на изображение для определения пропорций колонок | — |
## Требования
@@ -255,6 +256,7 @@ docs/
├── web-guide.md # Гайд: веб-публикация через Apache
├── web-test-guide.md # Гайд: тестирование через веб-клиент
├── web-test-recording-guide.md # Гайд: запись видеоинструкций
├── web-test-regression-guide.md # Гайд: регресс прикладного решения
├── 1c-epf-spec.md # Спецификация XML-формата (EPF)
├── 1c-erf-spec.md # Спецификация XML-формата (ERF)
├── 1c-form-spec.md # Спецификация управляемых форм
+407 -46
View File
@@ -124,7 +124,8 @@
"Организация: CatalogRef.Организации @dimension",
"Служебное: string #noFilter #noOrder",
"Счёт: CatalogRef.Хозрасчетный @account",
"Сумма: decimal(15,2) @balance"
"Сумма: decimal(15,2) @balance",
"СуммаНач: decimal(15,2) @balance balanceGroupName=Сумма balanceType=OpeningBalance"
]
```
@@ -140,15 +141,24 @@
"restrict": ["noFilter", "noGroup"],
"attrRestrict": ["noFilter"],
"appearance": { "Формат": "ЧДЦ=2" },
"presentationExpression": "Формат(Сумма, \"ЧДЦ=2\")"
"presentationExpression": "Формат(Сумма, \"ЧДЦ=2\")",
"orderExpression": { "expression": "ЕстьNULL(Поле.Порядок, 10000)", "orderType": "Asc", "autoOrder": false },
// или массив (если на поле несколько <orderExpression> для multi-sort fallback):
// "orderExpression": [{...}, {...}]
"availableValues": [
{ "value": 1, "presentation": { "ru": "Доход", "en": "Income" } },
{ "value": 2, "presentation": { "ru": "Расход", "en": "Expense" } }
]
}
```
`availableValues` — список допустимых значений поля с (опциональной multilang) подписью. Типы значений автоопределяются (`bool`/`decimal`/`dateTime`/`string`); можно указать `valueType` явно. Аналогичное поле существует на `parameters` — см. раздел 6.
### Парсинг shorthand
1. Разделить по пробелам; найти `@`-роли и `#`-ограничения
1. Извлечь `@`-роли (regex `@(\w+)`), `#`-ограничения (`#(\w+)`), KV-пары роли (`(\w+)=(\S+)`)
2. Остаток до первого `:``dataPath``field` по умолчанию)
3. После `:` до `@`/`#` — тип
3. После `:` — тип
### Типы
@@ -166,9 +176,12 @@
| `EnumRef.XXX` | `d5p1:EnumRef.XXX` | inline xmlns:d5p1 |
| `ChartOfAccountsRef.XXX` | `d5p1:ChartOfAccountsRef.XXX` | inline xmlns:d5p1 |
| `StandardPeriod` | `v8:StandardPeriod` | — |
| `DocumentRef` (без `.XXX`) | `<v8:TypeSet xmlns:d5p1=...>d5p1:DocumentRef</v8:TypeSet>` | композитный тип-набор (все ссылки указанного класса) |
> **Ссылочные типы** (`CatalogRef.XXX`, `DocumentRef.XXX` и др.) эмитируются с inline namespace declaration: `<v8:Type xmlns:d5p1="http://v8.1c.ru/8.1/data/enterprise/current-config">d5p1:CatalogRef.XXX</v8:Type>`. Использование префикса `cfg:` вместо `d5p1:` с объявлением namespace приводит к ошибке XDTO. Сборка EPF со ссылочными типами требует базу с соответствующей конфигурацией (не пустую).
> **TypeSet (тип-набор)** — голое имя без точки (`CatalogRef`, `DocumentRef`, `EnumRef`, `ChartOfAccountsRef`, `ChartOfCharacteristicTypesRef`, `ChartOfCalculationTypesRef`, `BusinessProcessRef`, `TaskRef`, `ExchangePlanRef`, `InformationRegisterRef`, `AnyRef`) — указывает на **все** ссылки этого класса конфигурации (а не на конкретный объект). Эмитится как `<v8:TypeSet>` вместо `<v8:Type>`. Используется в параметрах типа «исключаемые документы» и подобных.
### Синонимы типов
Все имена типов регистронезависимые. Поддерживаются русские и альтернативные имена:
@@ -192,22 +205,36 @@
### Роли
| DSL shorthand | Объектная форма | XML |
|---------------|----------------|-----|
| `@dimension` | `"role": "dimension"` или `{"dimension": true}` | `<dcscom:dimension>true</dcscom:dimension>` |
| `@account` | `"role": "account"` или `{"account": true}` | `<dcscom:account>true</dcscom:account>` |
| `@balance` | `"role": "balance"` или `{"balance": true}` | `<dcscom:balance>true</dcscom:balance>` |
| `@period` | `"role": "period"` или `{"period": true}` | `<dcscom:periodNumber>1</dcscom:periodNumber>` + `<dcscom:periodType>Main</dcscom:periodType>` |
Принимаются четыре формы:
Объектная форма с доп. полями:
```json
"role": {
"account": true,
"accountTypeExpression": "Счёт.ВидСчёта",
"balanceGroup": "/Остатки"
}
"role": "dimension" // одиночный флаг
"role": ["dimension", "required"] // массив флагов
"role": "balance balanceGroupName=Сумма balanceType=OpeningBalance" // shorthand
"role": { "balance": true, "balanceGroupName": "Сумма", "balanceType": "OpeningBalance" }
```
Shorthand-формат может быть встроен прямо в shorthand поля:
```
"Сумма: decimal(15,2) @balance balanceGroupName=Сумма balanceType=OpeningBalance"
```
**Парсинг shorthand**: `@(\w+)` → boolean флаги; `(\w+)=(\S+)` → строковые KV; остаток — `dataPath[: type]`.
**Поддерживаемые ключи**:
| Категория | Ключи |
|-----------|-------|
| `@`-флаги (boolean) | `@dimension`, `@account`, `@balance`, `@period`, `@required`, `@autoOrder`, `@ignoreNullValues` |
| Строковые KV | `balanceGroupName`, `balanceType` (`OpeningBalance`/`ClosingBalance`), `parentDimension`, `accountTypeExpression`, `expression`, `orderType` (`Asc`/`Desc`), `periodNumber`, `periodType` |
Whitelist'а нет — любой `<dcscom:KEY>` принимается; перечисленные — типичные. `@period` — sugar для `periodNumber=1` + `periodType=Main` (можно переопределить явно).
**XML-выход**: `<dcscom:KEY>true</dcscom:KEY>` для флагов; `<dcscom:KEY>VALUE</dcscom:KEY>` для KV.
> Устаревший ключ `balanceGroup` в object-форме принимается как alias для `balanceGroupName` (имя элемента в реальном XML — `balanceGroupName`).
### Ограничения
| DSL shorthand | Объектная форма | XML useRestriction |
@@ -371,7 +398,7 @@ XML-маппинг — по `<group>` на каждый элемент:
| `name` | Имя параметра |
| `title` | Заголовок (умолч. = name) |
| `type` | Тип (см. таблицу типов) |
| `value` | Значение по умолчанию |
| `value` | Значение по умолчанию (скаляр; для `valueListAllowed=true` — массив значений по умолчанию: `[ "ПланСчетов.Хозрасчетный.X", "...Y", "...Z" ]`) |
| `expression` | Выражение для вычисления |
| `availableAsField` | `false` — скрыть из полей |
| `valueListAllowed` | `true` — разрешить список значений |
@@ -379,7 +406,9 @@ XML-маппинг — по `<group>` на каждый элемент:
| `useRestriction` | `true` — скрыть от пользователя |
| `use` | `"Always"`, `"Auto"` |
| `denyIncompleteValues` | `true` — запретить произвольные значения (только из availableValues) |
| `availableValues` | Массив `[{value, presentation}]` — допустимые значения с представлениями |
| `availableValues` | Массив `[{value, presentation}]` — допустимые значения с представлениями. Типы (bool/int/decimal) сохраняются нативно в JSON |
| `inputParameters` | Параметры ввода (например `ФорматРедактирования`) — массив `[{parameter, value, valueType?, choiceParameters?, choiceParameterLinks?}]`. `valueType: {uri, name}` сохраняет кастомный xsi:type с локальным xmlns (например `d6p1:FoldersAndItemsUse`). В `choiceParameters[i].values` элементы — bool/int/double/string; compile эмитит соответствующий xsi:type (`xs:boolean` / `xs:decimal` / `dcscor:DesignTimeValue`) |
| `nilValue` | `true` — эмитить `<value xsi:nil="true"/>` для параметров с явным типом (decimal/string/dateTime), где XML-сериализатор обычно ставит типизированный default. Bit-perfect round-trip |
### availableValues
@@ -463,11 +492,16 @@ XML-маппинг — по `<group>` на каждый элемент:
| Поле | XML |
|------|-----|
| `source` | `<sourceDataSet>` |
| `dest` | `<destinationDataSet>` |
| `sourceExpr` | `<sourceExpression>` |
| `destExpr` | `<destinationExpression>` |
| `source` / `sourceDataSet` | `<sourceDataSet>` |
| `dest` / `destinationDataSet` | `<destinationDataSet>` |
| `sourceExpr` / `sourceExpression` | `<sourceExpression>` |
| `destExpr` / `destinationExpression` | `<destinationExpression>` |
| `parameter` | `<parameter>` (опц.) |
| `parameterListAllowed` | `<parameterListAllowed>true</parameterListAllowed>` (опц., bool) |
| `startExpression` | `<startExpression>` (опц.) |
| `linkConditionExpression` | `<linkConditionExpression>` (опц.) |
decompile эмитит длинные имена (`sourceDataSet` и т.д.); compile принимает обе формы.
---
@@ -478,30 +512,38 @@ XML-маппинг — по `<group>` на каждый элемент:
"name": "Основной",
"presentation": "Основной вариант",
"settings": {
"userFields": [...],
"selection": [...],
"filter": [...],
"order": [...],
"conditionalAppearance": [...],
"outputParameters": {...},
"dataParameters": [...],
"structure": [...]
"structure": [...],
"additionalProperties": { "ВариантНаименование": "...", "Адрес": "..." }
}
}]
```
`additionalProperties` — словарь служебных свойств варианта (`<dcsset:additionalProperties>` в XML), значения сохраняются как `xs:string`. Платформа использует его для типа «имя варианта», URL временного хранилища и т.п. — для bit-perfect round-trip сохраняется как есть, обычно модели заполнять не нужно.
### selection
```json
"selection": [
"Наименование",
{ "field": "Количество", "title": "Кол-во" },
{ "field": "Контрагент", "viewMode": "Inaccessible" },
{ "field": "Скрытое", "use": false },
{ "auto": true, "use": false },
"Auto"
]
```
- Строка → `SelectedItemField`
- `"Auto"``SelectedItemAuto` (только на уровне группировок; на верхнем уровне settings игнорируется)
- Объект с `field`/`title``SelectedItemField` с `lwsTitle`
- Объект с `field` + опц. `title`/`viewMode`/`use``SelectedItemField`. `use: false` = поле выборки отключено (видно в UI, но не применяется)
- Объект `{ auto: true, use: false }` → отключённый `SelectedItemAuto`
- Объект с `folder`/`items``SelectedItemFolder` — группа полей с заголовком и `placement=Auto`:
```json
@@ -513,6 +555,12 @@ XML-маппинг — по `<group>` на каждый элемент:
]
```
Опциональное поле `placement` (`Auto` / `Horizontally` / `Vertically` / `Special`) задаёт расположение элементов внутри группы (по умолчанию `Auto`):
```json
{"folder": "Экономия ФОТ", "items": ["ЭкономияФОТ", "ЭкономияФОТПроцент"], "placement": "Horizontally"}
```
### filter
#### Shorthand-строка
@@ -532,8 +580,9 @@ XML-маппинг — по `<group>` на каждый элемент:
- `@off``use=false`
- `@user``userSettingID=auto` (генерировать GUID)
- `@quickAccess``viewMode=QuickAccess`
- `@normal``viewMode=Normal`
- `@normal``viewMode=Normal` (явный — для bit-perfect, см. [viewMode](#viewmode-режим-доступности))
- `@inaccessible``viewMode=Inaccessible`
- Типы значений автоопределяются: `true`/`false``xs:boolean`, дата `2024-01-01T00:00:00``xs:dateTime`, числа → `xs:decimal`, `Перечисление.X.Y`/`Справочник.X.Y`/`ПланСчетов.X.Y` и др. → `dcscor:DesignTimeValue`, остальное → `xs:string`
- Типы значений автоопределяются: `true`/`false` → boolean, `2024-01-01T00:00:00` → dateTime, числа → decimal, `Перечисление.*`/`Справочник.*`/`ПланСчетов.*`/`Документ.*` → DesignTimeValue, прочее → string
- OrGroup: `{"group": "Or", "items": ["условие1", "условие2"]}` — объединяет условия через ИЛИ
@@ -543,10 +592,13 @@ XML-маппинг — по `<group>` на каждый элемент:
"filter": [
{ "field": "Организация", "op": "=", "use": false, "userSettingID": "auto" },
{ "field": "Дата", "op": ">=", "value": "0001-01-01T00:00:00", "valueType": "xs:dateTime" },
{ "field": "СуммаДт", "op": "=", "value": "СуммаКт", "valueType": "dcscor:Field" },
{ "field": "Статус", "op": "in", "value": [1, 3, 5] },
{ "field": "Контрагенты", "op": "in", "value": [], "userSettingID": "auto" },
{ "group": "Or", "items": [
{ "field": "Статус", "op": "=", "value": true, "valueType": "xs:boolean" },
{ "field": "Пометка", "op": "filled" }
]}
], "userSettingID": "auto" }
]
```
@@ -554,14 +606,19 @@ XML-маппинг — по `<group>` на каждый элемент:
|------|----------|
| `field` | Имя поля |
| `op` | Оператор (см. таблицу) |
| `value` | Правая часть (опц.) |
| `valueType` | xsi:type для значения (опц.) |
| `value` | Правая часть (опц.). См. формы ниже |
| `valueType` | xsi:type для значения (опц.). `"dcscor:Field"` = field-to-field comparison (значение — имя другого поля). Для массива `value: [...]` применяется ко всем элементам — нужен когда auto-detect ошибается (например `Перечисление.X.Y` должно остаться `xs:string`, а не `dcscor:DesignTimeValue`) |
| `use` | Включён (`true` по умолчанию) |
| `presentation` | Текст подсказки |
| `viewMode` | `"Normal"`, `"QuickAccess"`, `"Inaccessible"` |
| `userSettingID` | `"auto"` → генерировать GUID |
| `userSettingPresentation` | Отображаемое имя настройки (LocalStringType) |
**Формы `value`:**
- Скаляр (`"X"`, `5`, `true`, `"2024-01-01T00:00:00"`) — single `<right>` (стандартный случай). Тип определяется автоматически: bool / число / дата / строка.
- Массив `[a, b, c]` — несколько `<right>` подряд (для `in`/`notIn` с конкретными значениями).
- Пустой массив `[]``<right xsi:type="v8:ValueListType">` placeholder (типичный паттерн для `in` с пользовательскими настройками — значения заполнит пользователь через UI).
Операторы:
| DSL | XML comparisonType |
@@ -581,18 +638,24 @@ XML-маппинг — по `<group>` на каждый элемент:
| `filled` | `Filled` |
| `notFilled` | `NotFilled` |
Группа условий: `{ "group": "And"|"Or"|"Not", "items": [...] }``FilterItemGroup` с `groupType`.
Группа условий: `{ "group": "And"|"Or"|"Not", "items": [...] }``FilterItemGroup` с `groupType`. Группа также принимает item-level поля `presentation`, `viewMode`, `userSettingID`, `userSettingPresentation` — для регистрации группы как пункта пользовательских настроек.
### order
```json
"order": ["Количество desc", "Наименование", "Auto"]
"order": [
"Количество desc",
"Наименование",
{ "field": "Контрагент", "direction": "desc", "viewMode": "Inaccessible" },
"Auto"
]
```
- `"Field"``OrderItemField`, `orderType=Asc`
- `"Field desc"``OrderItemField`, `orderType=Desc`
- `"Field asc"``OrderItemField`, `orderType=Asc`
- `"Auto"``OrderItemAuto` (только на уровне группировок; на верхнем уровне settings игнорируется)
- Объект `{ field, direction?, viewMode?, use? }` — нужен, когда требуется задать `viewMode`, или отключить сортировку через `use: false` (см. [viewMode](#viewmode-режим-доступности))
### conditionalAppearance
@@ -604,14 +667,16 @@ XML-маппинг — по `<group>` на каждый элемент:
"selection": ["Сумма"],
"filter": ["Сумма > 1000"],
"appearance": { "ЦветТекста": "style:ПросроченныеДанныеЦвет" },
"presentation": "Выделять крупные суммы",
"presentation": { "ru": "Выделять крупные суммы", "en": "Highlight large amounts" },
"viewMode": "Normal",
"userSettingID": "auto"
},
{
"filter": ["Статус notFilled"],
"appearance": { "Текст": "Не указано", "ЦветТекста": "web:Gray" },
"presentation": "Скрывать пустые статусы"
"presentation": "Скрывать пустые статусы",
"use": false,
"useInDontUse": ["group", "fieldsHeader"]
}
]
```
@@ -621,15 +686,25 @@ XML-маппинг — по `<group>` на каждый элемент:
| `selection` | Массив полей, к которым применяется. Пусто/опущено = все поля |
| `filter` | Условия (shorthand-строки или объекты, как в settings filter) |
| `appearance` | Объект `{ "Параметр": "Значение" }` |
| `presentation` | Описание правила |
| `use` | Включено (`true` по умолчанию) |
| `presentation` | Описание правила (строка или multilang dict `{ru, en}`) |
| `use` | Включено (`true` по умолчанию). `false` = правило отключено |
| `viewMode` | `"Normal"`, `"QuickAccess"`, `"Inaccessible"` |
| `userSettingID` | `"auto"` → генерировать GUID |
| `userSettingPresentation` | Имя в пользовательских настройках (string или multilang) |
| `useInDontUse` | Массив контекстов где правило **НЕ** применяется. Возможные имена: `group`, `hierarchicalGroup`, `overall`, `fieldsHeader`, `header`, `parameters`, `filter`, `resourceFieldsHeader`, `overallHeader`, `overallResourceFieldsHeader` |
**Типы значений appearance** определяются автоматически:
- `style:XXX`, `web:XXX`, `win:XXX``v8ui:Color`
- `style:XXX``v8ui:Color` (палитра темы платформы, namespace `http://v8.1c.ru/8.1/data/ui/style`)
- `web:XXX``v8ui:Color` (web-имена цветов, namespace `http://v8.1c.ru/8.1/data/ui/colors/web`)
- `win:XXX``v8ui:Color` (системные цвета Windows, namespace `http://v8.1c.ru/8.1/data/ui/colors/windows`)
- Ключи `ЦветТекста`/`ЦветФона`/`ЦветГраницы` со значениями типа `auto` или `#XXXXXX``v8ui:Color`
- Ключ `Размещение``dcscor:DataCompositionTextPlacementType`
- Ключи `ГоризонтальноеПоложение`/`ВертикальноеПоложение``v8ui:HorizontalAlign`/`VerticalAlign`
- Ключ `ТипМакета``dcsset:DataCompositionGroupTemplateType`
- Ключи `Текст`/`Заголовок`/`Формат``v8:LocalStringType` (если значение строка)
- Числовые строки (`"40"`, `"15"`) → `xs:decimal`
- `true`/`false``xs:boolean`
- Параметр `Формат`, `Текст` или `Заголовок``v8:LocalStringType`
- Multilang dict `{ru, en}` для любого ключа`v8:LocalStringType`
- Прочее → `xs:string`
Поддержка `use=false` на уровне параметра:
@@ -652,13 +727,72 @@ XML-маппинг — по `<group>` на каждый элемент:
Ключ → `dcscor:parameter`, значение → `dcscor:value`.
Типы значений определяются автоматически:
- `"Заголовок"``v8:LocalStringType`
- `"Заголовок"``v8:LocalStringType` (примет строку или multilang dict)
- `"ВыводитьЗаголовок"`, `"ВыводитьПараметрыДанных"`, `"ВыводитьОтбор"``dcsset:DataCompositionTextOutputType`
- `"РасположениеПолейГруппировки"``dcsset:DataCompositionGroupFieldsPlacement`
- `"РасположениеРеквизитов"``dcsset:DataCompositionAttributesPlacement`
- `"ГоризонтальноеРасположениеОбщихИтогов"`, `"ВертикальноеРасположениеОбщихИтогов"``dcscor:DataCompositionTotalPlacement`
- `"ГоризонтальноеРасположениеОбщихИтогов"`, `"ВертикальноеРасположениеОбщихИтогов"`, `"РасположениеОбщихИтогов"`, `"РасположениеИтогов"``dcscor:DataCompositionTotalPlacement`
- `"РасположениеГруппировки"``dcsset:DataCompositionFieldGroupPlacement`
- `"РасположениеРесурсов"``dcsset:DataCompositionResourcesPlacement`
- `"ТипМакета"``dcsset:DataCompositionGroupTemplateType`
- Multilang dict `{ru, en}` для любого ключа → `v8:LocalStringType`
- Прочие → `xs:string`
Значение можно обернуть в `{ "value": ..., "use": false }` — отключённый параметр (платформа эмитит `<dcscor:use>false</dcscor:use>`). Такая же форма доступна в `appearance` items (см. раздел conditionalAppearance).
#### Полная wrapper-форма (bit-perfect round-trip)
Decompile сохраняет всю периферию каждого outputParameter в wrapper'е:
```json
{
"value": "Custom",
"valueType": "v8:StandardPeriod", // полный xsi:type если не покрыт type-map'ом
"use": false, // <dcscor:use>false</dcscor:use>
"items": { // nested sub-параметры (ТипДиаграммы.ВидПодписей)
"ТипДиаграммы.ВидПодписей": { "value": "Value", "valueType": "v8ui:ChartLabelType" }
},
"viewMode": "Normal", // <dcsset:viewMode>Normal</dcsset:viewMode>
"userSettingID": "auto",
"userSettingPresentation": { "ru": "Тип" }
}
```
Wrapper эмитится только при наличии extra-полей; простое `"key": "value"` остаётся как есть.
#### Шрифт (v8ui:Font) в appearance
Шрифт — объект с маркером `@type: "Font"`:
```json
"Шрифт": { "@type": "Font", "ref": "sys:DefaultGUIFont", "height": 10, "bold": "true", "italic": "false", "underline": "false", "strikeout": "false", "kind": "WindowsFont" }
```
Все атрибуты исходного XML сохраняются — для bit-perfect.
#### Граница (v8ui:Line) в appearance
Граница — объект с маркером `@type: "Line"` (атрибуты `width`/`gap` и inner `<v8ui:style>` сериализуются inline):
```json
"СтильГраницы": { "@type": "Line", "width": 0, "gap": false, "style": "None" }
```
Стороны (`СтильГраницы.Сверху/.Снизу/.Слева/.Справа`) — nested SettingsParameterValue, кладутся в `items` (как у outputParameters wrapper):
```json
"СтильГраницы": {
"@type": "Line", "width": 0, "gap": false, "style": "None",
"items": {
"СтильГраницы.Сверху": {
"value": { "@type": "Line", "width": 1, "gap": false, "style": "Solid" },
"use": false
},
"СтильГраницы.Снизу": {
"value": { "@type": "Line", "width": 1, "gap": false, "style": "Double" }
}
}
}
```
Top-level Line хранится **плоско** (`@type`/`width`/`gap`/`style` + `use?`/`items?` на одном уровне). Nested items используют универсальный wrapper `{ value, use? }`у `value` тип любой (Line/Font/color/text). Значения `style`: `None`, `Solid`, `Double`, `LargeDashed`, `SmallDashed`, `Dotted` и т.п. (значения `v8ui:SpreadsheetDocumentCellLineType`).
### dataParameters
#### Автогенерация
@@ -696,11 +830,26 @@ XML-маппинг — по `<group>` на каждый элемент:
|------|----------|
| `parameter` | Имя параметра |
| `value` | Значение (объект `{ "variant": "LastMonth" }` для StandardPeriod, или скаляр) |
| `valueType` | Полный xsi:type если кастомный (например `dcsset:DataCompositionGroupUseVariant`). Для пустого значения с `use: false``"xs:string"` эмитит `<value xsi:type="xs:string"/>` (placeholder отключённого параметра типа DateTime, бит-перфектный аналог `xsi:nil`) |
| `use` | Включён (`true` по умолчанию) |
| `viewMode` | `"Normal"`, `"QuickAccess"`, `"Inaccessible"` |
| `userSettingID` | `"auto"` → генерировать GUID |
| `userSettingPresentation` | Отображаемое имя настройки (LocalStringType) |
#### StandardPeriod / StandardBeginningDate — shape inference
Compile различает варианты по форме `value`:
| Форма | xsi:type | Когда |
|---|---|---|
| `{variant, startDate, endDate}` | `v8:StandardPeriod` | Custom с явными датами |
| `{variant: "ThisMonth"}` (etc) | `v8:StandardPeriod` | без дат — non-Custom варианты SP |
| `{variant, date}` | `v8:StandardBeginningDate` | Custom с явной датой |
| `{variant: "BeginningOf*"}` | `v8:StandardBeginningDate` | без даты — variant'ы начинаются с `BeginningOf` |
| `"2024-01-15T10:00:00"` (string) | `xs:dateTime` | raw datetime без обёртки |
Platform-pattern: `startDate`/`endDate`/`date` эмитятся ТОЛЬКО для `variant=Custom`. Для `ThisMonth`/`LastYear`/`BeginningOfThisDay`/... — только `<v8:variant>`.
### structure
#### String shorthand (рекомендуется для типичных случаев)
@@ -735,16 +884,30 @@ XML-маппинг — по `<group>` на каждый элемент:
|------|----------|
| `type` | `"group"` |
| `name` | Имя группировки (опц.) |
| `groupBy` | Массив полей. Пусто/опущено = детальные записи |
| `groupBy` | Массив полей. Каждый элемент — строка (имя поля) или объект `{ field, groupType?, periodAdditionType?, periodAdditionBegin?, periodAdditionEnd? }`. Пусто/опущено = детальные записи. Object-форма нужна когда `groupType ≠ "Items"`, `periodAdditionType ≠ "None"` или задана `periodAdditionBegin/End` (см. ниже) |
| `groupType` | `"Items"` (умолч.), `"Hierarchy"`, `"HierarchyOnly"` |
| `selection` | Выборка (умолч. `["Auto"]`) |
| `filter` | Отборы (как в settings) |
| `order` | Сортировка (умолч. `["Auto"]`) |
| `outputParameters` | Параметры вывода (как в settings) |
| `conditionalAppearance` | Условное оформление группы (как в settings) |
| `use` | `false` = ветка структуры отключена (на самой группе) |
| `viewMode` | `"Normal"`, `"QuickAccess"`, `"Inaccessible"` — режим доступности группы в пользовательских настройках |
| `itemsViewMode` | `"Normal"`, `"QuickAccess"`, `"Inaccessible"` — режим доступности подэлементов группы |
| `userSettingID` | `"auto"` → генерировать GUID. Регистрирует группу как пункт пользовательских настроек |
| `userSettingPresentation` | Имя в пользовательских настройках (string или multilang dict) |
| `children` | Вложенные элементы структуры |
Пустой `groupBy` (или `[]`) = детальные записи (без `groupItems` в XML).
**`periodAdditionBegin` / `periodAdditionEnd`** на field-объекте — даты добавочного периода (`<dcsset:periodAdditionBegin>`/`<dcsset:periodAdditionEnd>`). Compile auto-определяет xsi:type значения: строка вида `2025-01-01T00:00:00``xs:dateTime`, иначе (путь к параметру, например `ПараметрыДанных.ДатаНачала`) → `dcscor:Field`.
```json
{ "field": "ПериодМесяц",
"periodAdditionBegin": "ПараметрыДанных.ДатаНачала",
"periodAdditionEnd": "ПараметрыДанных.ДатаОкончания" }
```
#### Таблица (table)
```json
@@ -755,22 +918,188 @@ XML-маппинг — по `<group>` на каждый элемент:
{ "groupBy": ["Номенклатура"], "selection": ["Auto"], "order": ["Auto"] }
],
"columns": [
{ "groupBy": ["Период"], "selection": ["Auto"], "order": ["Auto"] }
{
"name": "Период",
"groupBy": ["Период"],
"filter": ["Сумма > 0"],
"selection": ["Auto"],
"order": ["Auto"],
"outputParameters": { "РасположениеИтогов": "None" },
"userSettingID": "auto",
"userSettingPresentation": { "ru": "Колонка с периодом" }
}
]
}
```
Каждая `column`/`row` принимает те же поля что и `group`: `name`, `groupBy`/`groupFields`, `filter`, `order`, `selection`, `outputParameters`, `conditionalAppearance`, `children` (вложенные `StructureItemGroup`), плюс user-settings — `viewMode`, `userSettingID`, `userSettingPresentation`, `itemsViewMode` (регистрация column/row как пункта «Изменить вариант»).
На самой `table` (отдельно от column/row) также допустимы `selection`, `conditionalAppearance`, `outputParameters`, плюс user-settings: `viewMode`, `userSettingID`, `userSettingPresentation`, `itemsViewMode`, `columnsViewMode`, `rowsViewMode`, `use` (`false` = таблица отключена).
- `columnsViewMode` / `rowsViewMode` — режим доступности секции колонок / строк в пользовательских настройках (значения: `Normal` / `QuickAccess` / `Inaccessible`).
> **Внутренний паттерн**: `<dcsset:item xsi:type="dcsset:StructureItemGroup">` внутри `<dcsset:row>`/`<dcsset:column>`/`<dcsset:points>`/`<dcsset:series>` платформа всегда сериализует в **короткой форме** `<dcsset:item>` без `xsi:type`. Compile эмитит этот вариант автоматически для `children` table axis.
#### Диаграмма (chart)
```json
{
"type": "chart",
"points": { "groupBy": ["Организация"], "order": ["Auto"] },
"points": { "groupBy": ["Организация"], "order": ["Auto"], "filter": [...] },
"series": { "groupBy": ["Месяц"], "order": ["Auto"] },
"selection": ["Сумма"]
}
```
`points` и `series` принимают те же поля что table column/row (включая `name` и user-settings).
На самой chart-item: `viewMode`, `userSettingID`, `userSettingPresentation`, `itemsViewMode`, `pointsViewMode`, `seriesViewMode`, `use: false` (диаграмма отключена). `pointsViewMode`/`seriesViewMode` — аналоги `columnsViewMode`/`rowsViewMode` у таблицы.
**Multi-series / multi-points** — `points` и `series` могут быть массивом объектов, тогда генерируется несколько `<dcsset:point>` или `<dcsset:series>` подряд (каждый со своими `groupBy`, `filter`, user-settings). Используется например для разделения данных диаграммы на несколько серий по разным фильтрам:
```json
{
"type": "chart",
"points": { "groupBy": ["Период"] },
"series": [
{ "groupBy": ["Стадия"], "filter": ["Стадия = ЗНАЧЕНИЕ(Перечисление.X.A)"],
"viewMode": "Normal", "userSettingID": "auto",
"userSettingPresentation": { "ru": "Серия A" } },
{ "groupBy": ["Стадия"], "filter": ["Стадия = ЗНАЧЕНИЕ(Перечисление.X.B)"],
"viewMode": "Normal", "userSettingID": "auto",
"userSettingPresentation": { "ru": "Серия B" } }
]
}
```
### 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 |
> **Пустые значения**: XML всегда содержит все четыре элемента (`detailExpression`, `detailExpressionPresentation`, `totalExpression`, `totalExpressionPresentation`) — даже если без значения (`<dcsset:totalExpression/>`). decompile сохраняет ключ с пустой строкой, compile эмитит self-closing тег для пустых строк. Это нужно для bit-perfect round-trip.
**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` управляет доступностью элемента в **пользовательских настройках** отчёта («Изменить вариант…» / «Настройки»). Возможные значения:
| Значение | Семантика |
|----------|-----------|
| `"Normal"` | Пользователь видит и может править (default) |
| `"Inaccessible"` | Скрыто от пользователя, не редактируется |
| `"QuickAccess"` | Вынесено в быстрые настройки (на форму отчёта) |
| `"Auto"` | Автоматический режим (наследование от контейнера) |
Применяется в трёх контекстах:
**1. Item-level** — на отдельном элементе блока (см. описание объектной формы соответствующего раздела):
```json
"filter": [{ "field": "X", "op": "=", "value": "Y", "viewMode": "Inaccessible" }],
"selection": [{ "field": "X", "viewMode": "Inaccessible" }],
"order": [{ "field": "X", "viewMode": "Inaccessible" }],
"conditionalAppearance": [{ "filter": [...], "appearance": {...}, "viewMode": "Inaccessible" }],
"dataParameters": [{ "parameter": "X", "viewMode": "QuickAccess" }]
```
Shorthand-флаги `@inaccessible`, `@quickAccess` доступны для `filter` и `dataParameters` строковых форм.
**2. Block-level** — на самом блоке (внутри `settings`). Управляет доступностью всей группы как пункта пользовательских настроек:
```json
"settings": {
"selectionViewMode": "Inaccessible",
"filterViewMode": "Inaccessible",
"orderViewMode": "Inaccessible",
"conditionalAppearanceViewMode": "Inaccessible",
"itemsViewMode": "Inaccessible",
"selectionUserSettingID": "auto",
"filterUserSettingID": "auto",
"orderUserSettingID": "auto",
"conditionalAppearanceUserSettingID": "auto",
"selection": [...],
"filter": [...]
}
```
`itemsViewMode` на settings — общий режим для всех подэлементов варианта (`<dcsset:itemsViewMode>` в XML). `XxxUserSettingID` парят с `XxxViewMode` — platform пишет их в block-level пользовательских настроек. Пустые блоки (без items) тоже эмитятся, если есть block-level meta — например `<dcsset:conditionalAppearance><dcsset:viewMode>Normal</dcsset:viewMode></dcsset:conditionalAppearance>`.
Также `orderViewMode`/`orderUserSettingID` поддержаны на StructureItemGroup для случаев когда block-level meta лежит на nested `<dcsset:order>`.
**3. Structure item** — на элементе структуры (`group`):
```json
{ "type": "group", "groupBy": ["Организация"], "viewMode": "Inaccessible", "itemsViewMode": "Inaccessible" }
```
**4. Table axis / chart axis** — на самой `column`/`row`/`points`/`series`. Через те же поля `viewMode`, `userSettingID`, `userSettingPresentation` (см. раздел Таблица).
#### Стратегия сохранения
Платформа эмитит `viewMode` непоследовательно: в одних местах `<viewMode>Normal</viewMode>` присутствует явно (когда элемент — пункт пользовательских настроек), в других — нет. Для bit-perfect round-trip:
- `skd-decompile` сохраняет `viewMode` в JSON **точно как было в XML**, включая явный `"Normal"` если он физически присутствовал.
- `skd-compile` эмитит `<viewMode>` только если значение задано в JSON (без `implicit Normal`-подстановки).
При компиляции JSON, написанного с нуля моделью, `viewMode` опускается → платформа применит default `Normal` при загрузке схемы.
---
## 10. Макеты и привязки (templates, groupTemplates)
@@ -886,13 +1215,45 @@ XML-маппинг — по `<group>` на каждый элемент:
#### Расшифровка (drilldown) в параметрах шаблона
Ключ `drilldown` в параметре шаблона автоматически генерирует:
1. `DetailsAreaTemplateParameter` с именем `Расшифровка_<значение>`, `fieldExpression` по полю `ИмяРесурса`, `mainAction=DrillDown`
2. Привязку `Расшифровка` в appearance ячеек, ссылающихся на этот параметр через `{Имя}`
Ключ `drilldown` в параметре шаблона — три формы по типу значения:
**Форма A (без drilldown)** — обычный `ExpressionAreaTemplateParameter`:
```json
{ "name": "Дата", "expression": "Документ.Дата" }
```
**Форма B (строка, shortcut)** — `ExpressionAreaTemplateParameter` + автоматический `DetailsAreaTemplateParameter` с именем `Расшифровка_<value>`, `fieldExpression` по полю `ИмяРесурса` (`expression="<value>"`), `mainAction=DrillDown`. Ячейки `{name}` получают appearance `Расшифровка → Расшифровка_<value>` автоматически:
```json
{ "name": "Сырье", "expression": "ПоступлениеСырья", "drilldown": "ПоступлениеСырья" }
```
**Форма C (объект)** — самостоятельный `DetailsAreaTemplateParameter` с именем `name`, без `ExpressionAreaTemplateParameter`. Используется когда расшифровка ссылается на data-параметр (а не на ИмяРесурса) и/или нужен другой `mainAction` (например `OpenValue`):
```json
{ "name": "МаршрутныйЛист",
"drilldown": { "field": "МаршрутныйЛист",
"expression": "МаршрутныйЛист",
"action": "OpenValue" } }
```
`action` по умолчанию `DrillDown`.
**Override на уровне ячейки** — object-форма `{ value, drilldown }`. Используется когда несколько ячеек должны указывать на один и тот же параметр-расшифровку (объявленный формой C):
```json
"parameters": [
{ "name": "Сырье", "expression": "ПоступлениеСырья", "drilldown": "ПоступлениеСырья" }
"rows": [
[ { "value": "{Номер}", "drilldown": "МаршрутныйЛист" },
{ "value": "{Дата}", "drilldown": "МаршрутныйЛист" } ]
]
```
Значение `drilldown` в ячейке — это полное имя параметра-расшифровки (как объявлено в `parameters`). Для shortcut form B override не нужен — appearance подставляется автоматически.
### fieldTemplates
Привязка именованного area-template к полю — `<fieldTemplate><field>X</field><template>Y</template></fieldTemplate>`. Когда платформа выводит значение поля `X`, используется макет `Y`:
```json
"fieldTemplates": [
{ "field": "МаршрутныйЛист", "template": "Макет1" }
]
```
+11 -2
View File
@@ -8,6 +8,7 @@
|-------|-----------|----------|
| `/skd-info` | `<TemplatePath> [-Mode] [-Name]` | Анализ структуры СКД: наборы, поля, параметры, ресурсы, варианты (11 режимов, включая full) |
| `/skd-compile` | `[-DefinitionFile <json> \| -Value <json-string>] -OutputPath <Template.xml>` | Генерация Template.xml из JSON DSL: наборы, поля, итоги, параметры, варианты |
| `/skd-decompile` | `<TemplatePath> [-OutputPath <out.json>]` | Преобразование Template.xml в JSON-черновик в формате `/skd-compile` — для нового отчёта по образцу или структурной переработки существующего. Из соображений предосторожности исключён из автоматического подбора моделью — вызывается только явной командой |
| `/skd-edit` | `<TemplatePath> -Operation <op> -Value "<value>"` | Точечное редактирование: 26 атомарных операций (add/set/patch/modify/clear/remove) |
| `/skd-validate` | `<TemplatePath> [-MaxErrors 20]` | Валидация структурной корректности: ~30 проверок |
@@ -15,15 +16,23 @@
```
Описание отчёта (текст) → JSON DSL → /skd-compile → Template.xml → /skd-validate
↕ /skd-edit → /skd-info
↕ /skd-edit → /skd-info
└──── /skd-decompile ──────┘
```
1. Claude формирует JSON-определение СКД (shorthand-поля, параметры, итоги, варианты)
1. Claude формирует JSON-определение СКД (shorthand-поля, параметры, итоги, варианты) — либо с нуля по описанию, либо `/skd-decompile` готовит черновик по существующему Template.xml
2. `/skd-compile` генерирует Template.xml с корректными namespace, типами, группировками
3. `/skd-edit` вносит точечные изменения: добавление полей, фильтров, наборов данных, вариантов, условного оформления и т.д.
4. `/skd-validate` проверяет корректность XML
5. `/skd-info` выводит компактную сводку для визуальной проверки
### Когда `/skd-decompile`, а когда `/skd-edit`
- **`/skd-edit`** — точечные правки готового отчёта (добавить поле, фильтр, итог, переименовать параметр). Меняет XML адресно, без полной реконструкции, не задевает непокрытые конструкции.
- **`/skd-decompile` → правка JSON → `/skd-compile`** — сценарии, где правки структурны: новый отчёт по образцу существующего, переработка варианта, перерисовка макета, перебор набора полей. Цикл переписывает Template.xml целиком.
**Полнота не гарантируется.** Известные декомпилятору непокрытые конструкции явно отмечаются маркерами в JSON и собираются в файл предупреждений — компилятор откажется собирать такой черновик, пока маркеры не разрешены вручную или не удалены. Но возможны и **тихие потери** — мелкое оформление, редкие настройки, незнакомые декомпилятору расширения. Это даёт валидный XML без части функциональности, и Конфигуратор такой результат не отбракует. Именно поэтому навык не предназначен для точечных правок (для них есть `/skd-edit`) и исключён из автоматического подбора моделью — вызывается только явной командой пользователя. Решение использовать пересобранный Template.xml — на стороне пользователя, и сверка с оригиналом перед коммитом остаётся его ответственностью.
## JSON DSL — компактный формат
СКД описываются в JSON с двумя уровнями детализации для каждой секции:
+391
View File
@@ -0,0 +1,391 @@
# Регрессионное тестирование прикладного решения
Навык `/web-test` умеет не только разово выполнить сценарий в браузере, но и сопровождать прикладное решение полноценным набором автотестов: каждый тест — отдельный файл, с шагами, проверками, тегами, отчётом и видеозаписью падений. После каждой правки конфигурации модель прогоняет весь набор и показывает, что ожидаемо ведёт себя как раньше, а что сломалось.
```
правка конфигурации → загрузка → обновление → публикация → прогон тестов → отчёт
```
Это про прикладное решение в целом, не про разовую проверку одной формы. Для разовых сценариев («открой накладную, проверь сумму») по-прежнему удобнее интерактивный режим из [web-test-guide.md](web-test-guide.md).
## Предусловия
- База опубликована через Apache (`/web-publish`).
- Установлен Node.js 18+, зависимости подняты: `cd .claude/skills/web-test/scripts && npm install`.
- ffmpeg — нужен только если хотите видеозапись прогона как доказательство падения. Без него падения фиксируются скриншотами. Установка описана в [web-test-recording-guide.md](web-test-recording-guide.md).
## Как это устроено
Набор тестов живёт в каталоге `tests/` вашего проекта. Каждое прикладное решение — отдельная подпапка. Внутри подпапки:
- `_hooks.mjs` — подготовка стенда (восстановление базы, публикация) и общая очистка после прогона. Необязателен.
- `webtest.config.mjs` — адрес базы и набор пользователей (например, кладовщик и менеджер для процессов согласования). Необязателен — если в проекте один пользователь и один URL, можно обойтись без него.
- Сами тесты — файлы `*.test.mjs`, сгруппированные по функциональным папкам.
```
tests/
моя-конфигурация/
_hooks.mjs
webtest.config.mjs
01-вход/
01-открытие-базы.test.mjs
02-контрагенты/
01-создание.test.mjs
02-правка-телефона.test.mjs
03-поступление-товаров/
01-оформление.test.mjs
02-проведение.test.mjs
04-отчёт-остатки/
01-формирование.test.mjs
05-согласование/
01-полный-цикл.test.mjs
```
Порядок выполнения — по алфавиту, поэтому удобно префиксовать папки и файлы номерами. Это даёт предсказуемый сценарий: сначала вход, потом справочники, потом документы, потом отчёты, в конце — процессы с несколькими пользователями.
## Быстрый старт
Самый короткий путь от нуля до зелёного теста — попросить модель пройти ваш сценарий руками и зафиксировать его как тест:
```
> Покрой регрессом справочник Контрагенты в моей конфигурации.
> Нужны проверки: создание, правка телефона, удаление.
```
Что сделает модель:
1. Соберёт информацию о справочнике через `/meta-info` и `/form-info` — посмотрит реквизиты и форму элемента, чтобы знать правильные имена полей.
2. Подключится к опубликованной базе в интерактивном режиме и **руками пройдёт** каждый сценарий — создание, правка, удаление. Это нужно, чтобы зафиксировать настоящие имена кнопок, увидеть, какие диалоги показывает 1С, понять, требуется ли подтверждение сохранения.
3. Зафиксирует пройденный сценарий как файл `tests/<ваша-конфигурация>/02-контрагенты/01-создание.test.mjs`.
4. Запустит его и покажет результат.
При следующих прогонах ничего этого делать не нужно — модель просто запустит готовый набор.
## Сценарии работы с моделью
### Покрытие регрессом доработанного объекта
```
> Я добавил в справочник Номенклатура реквизит "Цена" и "Активен".
> Покрой это регрессом — создание, редактирование, фильтрация по активности
```
Модель:
- посмотрит структуру справочника и формы (через `/meta-info`, `/form-info`);
- интерактивно проверит, как ведут себя новые поля в браузере;
- сгенерирует 2-3 тестовых файла под папкой `02-номенклатура/`;
- прогонит — покажет, что зелёное, что красное.
### Тест процесса с несколькими пользователями
```
> Сделай тест для процесса согласования приходных накладных.
> Кладовщик создаёт накладную, менеджер утверждает,
> кладовщик видит обновлённый статус
```
Модель настроит в `webtest.config.mjs` двух пользователей (с разными URL базы — например, `app-clerk` и `app-manager`), напишет тест, который оркестрирует переключение между ними, и положит его в `05-согласование/`.
```js
export const contexts = ['кладовщик', 'менеджер'];
export default async function({ кладовщик, менеджер, step, assert }) {
await step('Кладовщик создаёт накладную', async () => {
await кладовщик.navigateSection('Склад');
await кладовщик.openCommand('Приходные накладные');
await кладовщик.clickElement('Создать');
// ...
});
await step('Менеджер утверждает', async () => {
await менеджер.navigateSection('Согласование');
// ...
});
// ...
}
```
Учтите ограничение по лицензиям 1С: каждый одновременно открытый пользователь — это занятая клиентская лицензия. Если в наборе много многопользовательских тестов, а на стенде лицензий впритык, прогоны начнут спотыкаться на «свободных лицензий не осталось». Модель освобождает сессии между тестами автоматически (закрывает контексты после процессного теста), но если стенд ограничен — закладывайте это в планирование набора: один-два многопользовательских сценария вместо десяти.
### Воспроизведение ошибки тестом
```
> При проведении накладной без заполненного контрагента у нас не появляется
> ошибка валидации, документ просто проводится с пустым контрагентом — это баг.
> Зафиксируй это падающим тестом
```
Модель воспроизведёт сценарий, напишет тест с проверкой «должна быть ошибка», получит красный — потом, когда вы поправите конфигурацию и попросите перепрогнать, тест станет зелёным. Это документирует ожидаемое поведение в виде кода.
### Прогон регресса после изменений
```
> Я обновил расширение, накатил в базу. Прогони регресс
```
Модель запустит весь набор, дождётся завершения и расскажет:
- сколько тестов прошло, сколько упало, сколько пропущено;
- по каждому упавшему — что именно сломалось (название шага, сообщение об ошибке, ссылка на скриншот);
- классифицирует падения: это ошибка в самом тесте (нужно поправить тест), ошибка в приложении (баг, который вы внесли изменением), или нестабильность стенда (Apache не ответил вовремя, лицензия не освободилась).
```
> Прогони только тесты по контрагентам с подробным отчётом
```
Запустит подмножество — фильтр по тегу или папке, с записью JSON-отчёта.
### Подготовка автономного стенда
Если вы хотите, чтобы регресс можно было запустить «с нуля» — даже на чистой машине без подготовленной базы, — модель настроит автоматическую подготовку стенда:
```
> Сделай, чтобы перед прогоном тестов база восстанавливалась из эталона,
> а после прогона публикация снималась
```
Это пишется один раз в файле `_hooks.mjs`: при запуске тестов запускается подготовка (через навыки `/db-create`, `/db-load-xml`, `/web-publish`), а после — очистка. Внутри предусмотрено кэширование: если ничего не менялось со прошлого прогона, повторная подготовка занимает доли секунды.
## Пример организации покрытия
Допустим, у нас условное прикладное решение «Учёт поступлений товаров» — справочники контрагентов и номенклатуры, документ приходной накладной, отчёт остатков, процесс согласования с двумя пользователями. Логично организовать набор так:
```
tests/учёт-поступлений/
_hooks.mjs # подготовка: восстановление базы + публикация
webtest.config.mjs # URL базы, контексты кладовщика и менеджера
01-вход/
01-открытие-базы.test.mjs # базовая работоспособность: вход проходит, разделы видны
02-навигация-по-разделам.test.mjs # обход всех разделов конфигурации
02-контрагенты/
01-создание.test.mjs # создание, проверка появления в списке
02-редактирование.test.mjs # правка реквизита, проверка сохранения
03-удаление.test.mjs # удаление с подтверждением
03-номенклатура/
01-создание.test.mjs
02-фильтр-по-активности.test.mjs # быстрая фильтрация списка
04-поступление-товаров/
01-оформление.test.mjs # заполнение шапки и табличной части
02-проведение.test.mjs # проведение документа, проверка движений
03-отмена-проведения.test.mjs
04-валидация-обязательных.test.mjs # негативный тест: пустой контрагент → ошибка
05-отчёт-остатки/
01-формирование.test.mjs
02-отбор-по-складу.test.mjs
03-расшифровка.test.mjs # переход из ячейки отчёта в исходный документ
06-согласование/
01-полный-цикл.test.mjs # многопользовательский тест
```
Принципы:
- **Папки — по бизнес-функции**, не по типу метаданных. Лучше `04-поступление-товаров/` (что делает пользователь), чем `документы/` (что лежит в дереве конфигурации).
- **Цифровые префиксы** — на папке и на файле. Гарантируют, что сначала отработают базовые проверки (вход, справочники), потом сложные (документы, отчёты, процессы). При падении базы остальное и так не пройдёт — нет смысла занимать стенд получасом.
- **Один файл — одна логически связанная история.** Не «всё про контрагентов в одном файле», а «отдельно создание, отдельно правка, отдельно удаление». Когда падает — сразу видно, какой именно сценарий сломан.
- **Негативные тесты тоже есть.** «Документ без контрагента не проводится» — такой же важный регресс, как и позитивный сценарий, особенно после правок в обработчиках проверки заполнения.
- **Процессные тесты — в конце.** Они самые хрупкие (зависят от двух сессий, лицензий, синхронизации) и самые длинные. Если упадут — у вас уже есть данные от предыдущих тестов.
## Анатомия одного теста
Пользователь, как правило, тест не пишет — генерирует модель. Но прочитать и поправить полезно уметь. Стандартный файл выглядит так:
```js
export const name = 'Создание контрагента';
export const tags = ['контрагенты', 'базовая-проверка'];
export const timeout = 60000;
export default async function({
navigateSection, openCommand, clickElement, fillFields,
readTable, closeForm, assert, step
}) {
await step('Открыть список контрагентов', async () => {
await navigateSection('Продажи');
await openCommand('Контрагенты');
});
await step('Создать нового контрагента', async () => {
await clickElement('Создать');
await fillFields({ 'Наименование': 'ТД Тест', 'ИНН': '7707083893' });
await clickElement('Записать и закрыть');
});
await step('Убедиться, что элемент появился в списке', async () => {
const t = await readTable();
assert.tableHasRow(t, r => r['Наименование'] === 'ТД Тест');
});
}
```
Что здесь есть:
- **`name`** — человекочитаемое имя теста. Появится в отчёте.
- **`tags`** — теги для фильтрации. Можно прогонять не весь набор, а только нужные: `--tags=контрагенты`.
- **`timeout`** — сколько максимум тест может идти. По умолчанию 30 секунд, для длинных сценариев увеличиваем.
- **Тело теста** — функция, которая получает API браузера (см. [SKILL.md](../.claude/skills/web-test/SKILL.md)) плюс `assert` и `step`.
- **`step('имя', async () => {...})`** — обёртка шага. Имена шагов попадают в отчёт, при падении видно, какой именно шаг сломался.
- **`assert.*`** — проверки. `assert.tableHasRow`, `assert.equal`, `assert.ok` и т.д. Если проверка не выполнилась — тест считается упавшим.
Имена шагов и теста — по-русски, описательные. Они показываются и в консоли, и в отчётах.
## Запуск и отчёты
### Простой прогон
```
> Прогони регресс
```
Модель запустит весь набор, дождётся, покажет сводку:
```
✓ Открытие базы (2.1s)
✓ Создание контрагента (8.4s)
✗ Проведение приходной накладной (12.7s)
└ Заполнить табличную часть (5.2s)
Не найден столбец "Цена" в табличной части "Товары"
скриншот: tests/учёт-поступлений/error-shot.png
23 пройдено, 1 упал, 0 пропущено (3 мин 42 с)
```
### Подробный отчёт
```
> Прогони регресс и сохрани подробный отчёт
```
Модель добавит флаг записи отчёта (JSON или Allure) — потом по нему можно листать историю прогонов, видеть длительности шагов, открывать прикреплённые скриншоты.
Allure — стандартный визуальный отчёт с категориями падений, графиками, таймлайном. Чтобы посмотреть отчёт после прогона:
```bash
# Allure CLI устанавливается отдельно (npm install -g allure-commandline)
allure serve allure-results
```
### Категории падений в Allure
Без дополнительной настройки Allure складывает все упавшие тесты в один общий список «Defects». Если в прогоне упало 15 тестов, не сразу понятно, что из этого — пятнадцать разных проблем или одна и та же ошибка (например, нехватка лицензии на стенде), которая зацепила пятнадцать тестов подряд.
Чтобы Allure группировал падения по причинам, рядом с тестами кладётся каталог `_allure/` с файлом `categories.json`. Подчёркивание в имени каталога — чтобы он не воспринимался как папка с тестами; раннер копирует его содержимое в отчёт.
```
tests/моя-конфигурация/
_allure/
categories.json # классификация падений
environment.properties # необязательно: URL, версия 1С, ветка git
executor.json # необязательно: метаданные сборки CI
_hooks.mjs
01-вход/
...
```
`categories.json` — это список регулярных выражений, по которым ошибка теста относится к той или иной группе:
```json
[
{ "name": "Нехватка лицензий 1С",
"matchedStatuses": ["failed", "broken"],
"messageRegex": ".*Не обнаружено свободной лицензии.*" },
{ "name": "Ошибка приложения 1С",
"matchedStatuses": ["failed"],
"messageRegex": ".*(ВызватьИсключение|В поле введены некорректные данные|Произошла ошибка).*" },
{ "name": "Элемент не найден",
"matchedStatuses": ["failed"],
"messageRegex": ".*(clickElement|fillFields|selectValue).*not found.*" },
{ "name": "Превышен лимит времени теста",
"matchedStatuses": ["failed", "broken"],
"messageRegex": "Timeout \\(\\d+ms\\)" },
{ "name": "Несовпадение ожидания и факта",
"matchedStatuses": ["failed"],
"messageRegex": "(Expected|AssertionError).*" }
]
```
Когда вы попросите модель в первый раз настроить регресс, она положит шаблонный `categories.json` со стандартными классами. По мере того как вы будете находить новые типичные причины падений (например, специфичные для вашего расширения тексты ошибок), категории дополняются.
В виджете «Categories» итогового отчёта вы увидите примерно так:
```
Нехватка лицензий 1С — 12 падений
Ошибка приложения 1С — 2 падения
Несовпадение ожидания и факта — 1 падение
```
— и сразу понятно, что 12 падений — это один стенд-баг, а двумя «ошибками приложения» нужно разобраться по существу.
Помимо `categories.json` в каталог `_allure/` можно положить ещё два стандартных файла:
- **`environment.properties`** — список `ключ=значение` (URL базы, версия платформы 1С, имя ветки git, номер сборки). Покажется в отчёте в виджете «Environment». Полезно, когда регресс гоняется на нескольких стендах или после каждого билда — видно, на чём именно был получен результат. Этот файл удобно генерировать прямо в подготовке стенда (`_hooks.mjs`), а не держать статичной копией.
- **`executor.json`** — метаданные системы сборки: ссылка на Jenkins-задачу, идентификатор запуска GitHub Actions и т.д. Нужен только если регресс запускается на сервере сборки. При локальном прогоне ничего класть не надо.
### Прогон части набора
```
> Прогони только тесты по поступлениям товаров
> Прогони только базовые проверки
> Прогони только упавший вчера тест с проведением накладной
```
Модель выберет нужное подмножество — по папке, по тегу или по имени теста.
### Принудительная пересборка стенда
Если хотите, чтобы перед прогоном база восстановилась с нуля:
```
> Прогони регресс с полной пересборкой стенда
```
Это передаст в подготовку флаг типа `--rebuild-stand``_hooks.mjs` пересоздаст базу из эталона. Полезно после крупных правок или если подозреваете, что предыдущие прогоны загрязнили данные.
## Что делать, когда тест упал
Модель проанализирует падение и отнесёт его к одной из трёх категорий:
1. **Ошибка в самом тесте.** Например, переименовали реквизит — тест ищет старое имя поля. Решение: модель обновит тест.
2. **Ошибка в приложении.** Это и есть то, ради чего регресс существует: что-то поменялось в конфигурации, и сценарий, который раньше работал, теперь не отрабатывает. Модель опишет, что именно произошло, со скриншотом и трассировкой стека 1С, если ошибка была серверной.
3. **Нестабильность стенда.** Apache не ответил, не освободилась лицензия, база отвалилась. Это лечится не правкой теста, а починкой подготовки стенда в `_hooks.mjs` или, реже, повторным прогоном с одним повтором.
Просите модель не «исправь упавший тест», а «разберись с падением» — иначе она может молча подкрутить ожидание под текущее поведение, замаскировав настоящий баг.
## Полезные подробности
### Тестовые данные
В прикладном решении обычно нужны какие-то стартовые данные: пара контрагентов, номенклатура, заведённые организации. Их кладём не в каждый тест, а один раз в подготовку стенда (`_hooks.mjs`) — после восстановления базы загружаются эталонные данные, на которых работают все тесты.
Если конкретному тесту нужны свои данные (например, документ, который мы будем редактировать), он создаёт их сам в начале и убирает в конце.
### Имена документов и уникальность
Тесты прогоняются многократно. Если тест создаёт документ «Накладная-Тест», следующий прогон может натолкнуться на старую запись. Решение — добавлять к имени метку времени:
```js
const метка = 'Тест-' + Date.now();
await fillFields({ 'Комментарий': метка });
// ...
const t = await readTable();
assert.tableHasRow(t, r => r['Комментарий'] === метка);
```
Модель это делает автоматически, но если правите тест руками — держите в голове.
### Видео при падении
Можно включить запись видео всех тестов — тогда при падении прикладывается не только скриншот, но и MP4 со всей сессией:
```
> Прогони регресс с записью видео
```
Размер прогона при этом растёт (на 2-3 минутах теста выходит 5-10 МБ), но при отладке сложного падения видео экономит кучу времени.
### Многоязычные конфигурации
Если у вас есть конфигурация с командами и реквизитами на нескольких языках, тесты пишутся под один язык (как правило, тот, в котором ведётся работа в проде). При смене языка интерфейса в браузере тесты не пройдут — модель видит другие подписи кнопок.
## Где смотреть дальше
- API браузера, которое вызывают тесты — [SKILL.md](../.claude/skills/web-test/SKILL.md).
- Подробная инструкция для модели по написанию тестов (на английском, технический документ) — [.claude/skills/web-test/regress.md](../.claude/skills/web-test/regress.md).
- Интерактивный режим без тестов — [web-test-guide.md](web-test-guide.md).
- Запись видеоинструкций — [web-test-recording-guide.md](web-test-recording-guide.md).
+978
View File
@@ -0,0 +1,978 @@
# Регрессионное тестирование — спецификация
Техническое описание движка регрессионных тестов: инструмент исполняет описанные кодом пользовательские сценарии в веб-клиенте прикладного решения на платформе 1С и сверяет результат с ожиданиями.
Смежные документы:
- [web-test-regression-guide.md](web-test-regression-guide.md) — пользовательский гайд с быстрым стартом.
- [web-test-guide.md](web-test-guide.md) — справочник по browser-API (`clickElement`, `getFormState`, `readTable`, …), который используется внутри тестов.
- [web-test-recording-guide.md](web-test-recording-guide.md) — видеозапись, озвучка, overlays.
---
## 1. Командная строка
```
node run.mjs test [url] <dir|file> [флаги]
```
| Флаг | По умолчанию | Описание |
|------|-------------|----------|
| `--tags=smoke,crud` | (все) | Фильтр тестов по тегам (пересечение) |
| `--grep=pattern` | (все) | Фильтр тестов по имени (регулярное выражение) |
| `--bail` | false | Остановиться при первом падении |
| `--retry=N` | 0 | Повторить упавшие тесты N раз |
| `--timeout=ms` | 30000 | Таймаут на тест (мс) |
| `--report=path` | (нет) | Записать отчёт в файл (JSON или XML для `--format=junit`) |
| `--format=fmt` | json | Формат отчёта: `json` / `allure` / `junit` |
| `--report-dir=path` | dirname(report) / testDir | Каталог для скриншотов, видео, Allure-результатов |
| `--screenshot=strategy` | on-failure | `on-failure` / `every-step` / `off` |
| `--record` | false | Записывать видео для каждого теста (mp4 в `--report-dir`) |
| `-- <hookArgs…>` | — | Всё после `--` пробрасывается в `_hooks.mjs` как `hookArgs` (см. §6.1) |
URL необязателен, если в каталоге тестов есть `webtest.config.mjs`. CLI URL переопределяет URL дефолтного контекста.
### Валидация CLI
- `--screenshot=<v>` принимается только `on-failure | every-step | off`; при невалидном значении движок выводит ошибку и завершается с ненулевым кодом до старта прогона.
- `--format=<v>` принимается только `json | allure | junit`; иначе — завершение с ошибкой.
- `--format=junit` требует `--report=<path>` (иначе некуда писать XML); иначе — завершение с ошибкой.
### Режим выполнения
1. Загружается `webtest.config.mjs` (если есть).
2. Обнаруживаются файлы `*.test.mjs`, читается каждый, извлекаются метаданные.
3. Применяются фильтры `--tags` / `--grep` / `only`. Параметризованные тесты разворачиваются.
4. Запускается браузер и default-контекст (`chromium.launch()` либо `launchPersistentContext` в зависимости от `isolation`).
5. Тесты выполняются последовательно **в алфавитном порядке относительного пути файла** (внутри файла — в порядке экспорта).
6. Для каждого теста: лениво создаются нужные `BrowserContext`-ы (`ensureContext`), переключается активный, прогоняются хуки и тело, выполняется встроенный сброс состояния.
7. По завершении: финальная очистка контекстов с `beforeCloseContext`-хуками, закрытие браузера, `cleanup()`.
---
## 2. Формат тест-модуля
Каждый файл `*.test.mjs` — ES-модуль.
### Экспорты
| Экспорт | Тип | Обязателен | По умолчанию | Описание |
|---------|-----|-----------|-------------|----------|
| `name` | `string` | да | — | Читаемое имя теста |
| `default` | `async function(ctx, param?)` | да | — | Тело теста |
| `tags` | `string[]` | нет | `[]` | Теги для фильтрации |
| `timeout` | `number` | нет | 30000 | Таймаут теста (мс) |
| `skip` | `boolean \| string` | нет | false | Пропустить тест (строка = причина) |
| `only` | `boolean` | нет | false | Запустить только этот тест (отладка) |
| `context` | `string` | нет | defaultContext | Имя контекста из файла конфигурации |
| `contexts` | `string[]` | нет | — | Мульти-пользовательский процессный тест |
| `severity` | `string` | нет | — | `blocker` / `critical` / `normal` / `minor` / `trivial` |
| `params` | `object[]` | нет | — | Параметризация (см. §13) |
| `setup` | `async function(ctx)` | нет | — | Подготовка перед тестом |
| `teardown` | `async function(ctx)` | нет | — | Очистка после теста (выполняется всегда) |
### Пример: тест с одним контекстом
```js
export const name = 'CRUD справочника Контрагенты';
export const tags = ['smoke', 'crud', 'catalog'];
export const timeout = 45000;
export default async function({ navigateSection, openCommand, clickElement,
fillFields, readTable, closeForm, getFormState, assert, step, log }) {
await step('Открыть список', async () => {
await navigateSection('Склад');
await openCommand('Контрагенты');
});
await step('Создать элемент', async () => {
await clickElement('Создать');
await fillFields({ 'Наименование': 'Тест-' + Date.now() });
await clickElement('Записать и закрыть');
});
await step('Проверить в списке', async () => {
const table = await readTable();
assert.tableHasRow(table, r => r['Наименование']?.startsWith('Тест-'));
log('Элемент найден в списке');
});
}
```
### Пример: мульти-контекстный процессный тест
Рекомендация: латинский ID контекста + кириллический `displayName` в `webtest.config.mjs.contexts.<id>.displayName` (см. §7).
```js
export const name = 'Согласование приходной накладной';
export const contexts = ['clerk', 'manager'];
export const tags = ['process'];
export default async function({ clerk, manager, step }) {
await step('Кладовщик создаёт накладную', async () => {
await clerk.navigateSection('Склад');
await clerk.openCommand('Приходные накладные');
await clerk.clickElement('Создать');
await clerk.fillFields({ 'Контрагент': 'ООО Поставщик' });
await clerk.clickElement('Записать');
});
await step('Менеджер утверждает', async () => {
await manager.navigateSection('Согласование');
await manager.openCommand('На утверждении');
await manager.clickElement('ООО Поставщик', { dblclick: true });
await manager.clickElement('Утвердить');
});
await step('Освобождаем контекст clerk', async () => {
await manager.closeContext('clerk'); // освободить лицензию 1С
});
}
```
---
## 3. Объект контекста
Каждая тестовая функция получает объект контекста `ctx`.
### API браузера (все экспорты browser.mjs)
Все функции обёрнуты авто-обнаружением 1С-ошибок (как в `executeScript`):
- При модальной/всплывающей ошибке 1С: скриншот → `fetchErrorStack` → исключение с заполненным `err.onecError`.
- Обёрнутые ACTION_FNS: `clickElement`, `fillFields`, `fillField`, `selectValue`, `fillTableRow`, `deleteTableRow`, `openCommand`, `navigateSection`, `navigateLink`, `openFile`, `closeForm`, `filterList`, `unfilterList`.
Полный список доступных функций (по группам, детальное описание — в [web-test-guide.md](web-test-guide.md)):
**Навигация:** `navigateSection`, `openCommand`, `switchTab`, `navigateLink`, `openFile`
**Состояние:** `getFormState`, `getPageState`, `getSections`, `getCommands`
**Таблицы:** `readTable`, `readSpreadsheet`, `fillTableRow`, `deleteTableRow`
**Поля:** `fillFields`, `fillField`, `selectValue`
**Действия:** `clickElement`, `closeForm`, `filterList`, `unfilterList`
**Ошибки:** `fetchErrorStack`
**Контексты:** `createContext`, `setActiveContext`, `closeContext`, `listContexts`, `hasContext`, `getActiveContext`
**Запись:** `startRecording`, `stopRecording`, `isRecording`, `addNarration`, `getCaptions`
**Презентация:** `showCaption`, `hideCaption`, `showTitleSlide`, `hideTitleSlide`, `showImage`, `hideImage`, `highlight`, `unhighlight`, `setHighlight`, `isHighlightMode`
**Утилиты:** `screenshot`, `wait`, `getPage`, `getSession`, `readFileSync`, `writeFileSync`
> `dismissPendingErrors` — внутренняя функция (browser.mjs), на `ctx` не публикуется. Тест её не вызывает напрямую: она срабатывает автоматически перед каждым ACTION_FN и внутри встроенного сброса.
### Тестовые утилиты
- `step(name, fn)` — обёртка шага (см. §4)
- `assert.*` — хелперы утверждений (см. §5)
- `log(...args)` — добавить строку в вывод теста. Строки накапливаются в массив, склеиваются и попадают в JSON `tests[].output`. В Allure-отчёте `output` пишется в `statusDetails.trace` **только для упавших тестов**; для успешных теряется (отдельного вложения не создаётся).
### Метаданные теста (`ctx.testInfo`)
Декларативная информация о текущем тесте. Движок выставляет `ctx.testInfo` перед каждой попыткой (до `beforeEach`); хук и тело теста могут читать. Изменять не следует — объект используется самим движком при сборке отчёта.
```js
ctx.testInfo = {
name, // 'Навигация по разделам' (с подставленными params)
file, // '01-navigation.test.mjs' (basename)
filePath, // '01-navigation.test.mjs' (relative к testDir, разделитель '/')
tags, // ['nav', 'smoke']
timeout, // 60000 (ms)
attempt, // 1..maxAttempts (1-based)
maxAttempts, // 1 + retry
param, // { ... } | undefined (для export const params)
contexts: { // объект, всегда 1+ ключей; зеркалит config.contexts
a: { url, isolation, ...customFields },
b: { ... },
},
primaryContext, // 'a' — имя контекста, активного на входе в тест
// (= t.context для single, t.contexts[0] для multi)
}
```
Доступ к специфике контекста: `testInfo.contexts[testInfo.primaryContext].displayName`. `primaryContext` — декларация теста; не зависит от текущего значения `getActiveContext()` (которое может меняться внутри теста).
### Результат теста в afterEach (`ctx.testResult`)
Только в `afterEach`. До запуска теста — `null`. После — заполняется движком перед вызовом хука:
```js
ctx.testResult = {
status, // 'passed' | 'failed'
duration, // ms
attempts, // фактически выполнено попыток (1..maxAttempts)
error, // { message, step?, screenshot?, onecError? } | null
steps, // массив step-результатов (структура — см. §4)
}
```
В итоговый JSON-отчёт (`tests[]`) добавляются ещё `name`, `file`, `tags`, `contexts`, `severity`, `start`, `stop`, `output`, `screenshot`, `video` (см. §9). В `afterEach` они недоступны — движок собирает финальную запись после хука.
### Мульти-контекст
При `export const contexts = ['a', 'b']`:
- `ctx.a` и `ctx.b` — отдельные scoped-объекты, каждый с полным API браузера. Перед каждым вызовом scoped-обёртка переключает активный контекст через `setActiveContext`.
- `ctx.step`, `ctx.assert`, `ctx.log`, `ctx.testInfo`, `ctx.testResult` остаются на верхнем уровне.
При single-context (`export const context = 'X'` или дефолт) API публикуется плоско на `ctx`.
---
## 4. step(name, fn) — обёртка шага
```js
await step('Имя шага', async () => {
// тело шага
});
```
Поведение:
- Записывает метку `start` перед `fn()`.
- Записывает метку `stop` после `fn()` (успех или ошибка).
- При ошибке: устанавливает `status: 'failed'`, прикрепляет сообщение, пробрасывает исключение.
- При успехе: устанавливает `status: 'passed'`.
- Если стратегия скриншотов `every-step` — делает скриншот после `fn()`.
- Вложенные шаги поддерживаются (шаг внутри шага).
- Напрямую маппится на шаги Allure.
Структура данных шага (для отчётов):
```js
{
name: 'Имя шага',
start: 1712345678000, // мс от эпохи
stop: 1712345679200,
status: 'passed' | 'failed',
error: 'сообщение' | undefined,
screenshot: 'путь' | undefined,
steps: [] // вложенные шаги
}
```
---
## 5. Утверждения (assertions)
Простые хелперы без зависимостей. Бросают `AssertionError` со свойствами `.message`, `.actual`, `.expected`.
### Общие
```js
assert.ok(value, msg?) // истинность
assert.equal(actual, expected, msg?) // ===
assert.notEqual(actual, expected, msg?) // !==
assert.deepEqual(actual, expected, msg?) // сравнение через JSON
assert.includes(haystack, needle, msg?) // string/array .includes()
assert.match(string, regex, msg?) // regex.test(string)
await assert.throws(asyncFn, msg?) // ожидает исключение из async fn
```
### Специфичные для 1С
```js
assert.formHasField(state, fieldName, msg?)
// проверяет наличие state.fields[fieldName]; в сообщении об ошибке
// перечисляются доступные поля для быстрой диагностики
assert.formTitle(state, expected, msg?)
// проверяет, что state.title содержит expected
assert.tableHasRow(table, predicate, msg?)
// predicate: объект (частичное совпадение по ===) или функция row => bool
// объект: assert.tableHasRow(table, { 'Наименование': 'Тест' })
// функция: assert.tableHasRow(table, r => r['Сумма'] > 100)
assert.tableRowCount(table, expected, msg?)
// проверяет table.rows.length === expected
assert.noErrors(state, msg?)
// проверяет !state.errors
```
Расширения assert API нет. Для нестандартных проверок — `throw new Error(...)` или комбинация существующих хелперов.
---
## 6. Хуки
Все хуки определяются в `_hooks.mjs` в корне каталога тестов.
### Три уровня
**Инфраструктурный уровень** (без браузера):
- `prepare({ hookArgs, log, config })` — до подключения (восстановление БД, публикация, загрузка данных).
- `cleanup({ hookArgs, log, config })` — после отключения (удаление публикации, очистка).
Поля параметра:
- `hookArgs: string[]` — всё, что в командной строке передано после разделителя `--`, без интерпретации со стороны движка. Хук парсит сам (см. §6.1).
- `log: (...args) => void` — функция логирования движка (структурированный вывод с префиксом `[hooks]`). Использовать вместо `console.log`, чтобы не ломать формат отчёта.
- `config: object` — разобранный `webtest.config.mjs` (URL контекстов, режим изоляции, правила severity и т.д.).
**Тестовый уровень** (с контекстом браузера):
- `beforeAll(ctx)` — после подключения, перед первым тестом.
- `afterAll(ctx)` — после последнего теста, до отключения.
- `beforeEach(ctx)` — перед каждым тестом. На входе уже доступен `ctx.testInfo` (см. §3).
- `afterEach(ctx)` — после каждого теста. Дополнительно доступен `ctx.testResult` с результатом завершившегося теста.
**Контекстный уровень** (на каждый browser-контекст, жизненный цикл = создан → удалён):
- `afterOpenContext(ctx, name, spec)` — сразу после успешного `createContext`. `spec` — запись из `config.contexts[name]` со всеми пользовательскими полями (`displayName`, `url`, `isolation`, …). Полезно: вставка постоянного DOM-оверлея/бейджа, предварительная навигация в контексте, регистрация телеметрии.
- `beforeCloseContext(ctx, name, spec)` — перед `closeContext` (контекст ещё активен и работает). Полезно: сохранение остатков буферов, сбор метрик, последний скриншот. Срабатывает и при явном `ctx.closeContext(name)` из теста, и в финальной очистке движка перед `disconnect`.
`closeContext(name)` валиден только когда `name !== getActiveContext()` — иначе бросается исключение. В scoped API (`ctx.a.closeContext('b')`) это естественно: scoped-обёртка сначала вызывает `setActiveContext('a')`, потом закрывает `'b'` — целевой контекст всегда неактивен.
### Подавление ошибок в хуках
Ошибки в `afterEach`, `teardown`, `afterAll` и `cleanup` ловятся и логируются движком, но не прерывают прогон и не помечают тест/прогон как failed. Логика: пост-хуки очистки должны быть устойчивы к собственным сбоям, чтобы один сломанный `teardown` не приводил к падению остальных тестов по цепочке. Если в этих хуках произошла фатальная для регресса проблема — бросайте отдельный `Error` в `beforeAll`/`beforeEach`, чтобы он прервал прогон, либо проверяйте состояние в самом тесте.
### Порядок выполнения
```
prepare() // без браузера (восстановление БД, публикация)
browser.launch() // запуск процесса браузера
createContext(default) // первый контекст создан
afterOpenContext(ctx, default) // hook: контекст готов
beforeAll(ctx) // браузер готов, default-контекст создан
[lazy ensureContext(name)] // для multi-context тестов
afterOpenContext(ctx, name)
beforeEach(ctx)
test.setup(ctx) // подготовка теста
test.default(ctx) // тело теста (может вызвать ctx.closeContext)
[при ctx.closeContext(x)]: beforeCloseContext(ctx, x) → close(x)
test.teardown(ctx) // очистка теста (всегда)
afterEach(ctx) // всегда
[встроенный сброс] // всегда (для каждого живого контекста теста)
…следующий тест…
afterAll(ctx)
[для каждого оставшегося контекста]: beforeCloseContext(ctx, name, spec)
browser.close() // финальный disconnect (без явных closeContext —
// контексты умирают вместе с браузером)
cleanup() // без браузера (удаление публикации)
```
### Встроенный сброс состояния
После каждого теста (после `afterEach`) движок гарантирует чистое состояние:
```js
async function resetState(ctx) {
try { await ctx.dismissPendingErrors(); } catch {} // no-op на ctx (не экспортируется);
// внутренний dismiss всё равно отработает
// через ACTION_FN-обёртки ниже
for (let i = 0; i < 10; i++) {
const state = await ctx.getFormState();
if (state.form == null) break; // важно: == null, не !state.form —
// form может быть 0 (валидный idx фоновой формы)
try { await ctx.closeForm({ save: false }); } catch { break; }
}
}
```
Гарантирует, что каждый тест стартует с чистого рабочего стола, независимо от того, как завершился предыдущий (падение, таймаут, ошибка утверждения). Реимплементировать это в пользовательском `afterEach` не нужно.
### Пример _hooks.mjs
```js
import { execSync } from 'child_process';
export async function prepare({ hookArgs, log, config }) {
const force = hookArgs.includes('--rebuild-stand');
const dataArg = hookArgs.find(a => a.startsWith('--data='))?.slice('--data='.length);
log('preparing stand, force=', force, 'data=', dataArg);
execSync('powershell.exe -File scripts/restore-db.ps1');
execSync('powershell.exe -File scripts/publish.ps1');
}
export async function cleanup({ log }) {
log('cleaning up stand');
execSync('powershell.exe -File scripts/unpublish.ps1');
}
export async function beforeAll(ctx) {
// По умолчанию 1С после входа уже показывает дефолтную секцию — навигация
// в beforeAll обычно не нужна. Хук удобен для общего setup'а который
// должен случиться один раз для всего прогона.
}
export async function afterEach(ctx) {
// Доступен ctx.testResult — { status, duration, attempts, error, steps }.
// Встроенный сброс состояния выполняется ПОСЛЕ afterEach автоматически.
}
export async function afterOpenContext(ctx, name, spec) {
// Удобно для persistent DOM-overlay'я с displayName (видно в видео,
// какая вкладка к какому пользователю относится).
}
export async function beforeCloseContext(ctx, name, spec) {
// Срабатывает и при ctx.closeContext из теста, и в финальной очистке.
}
```
### 6.1. Проброс пользовательских флагов через `--`
Движок не знает о пользовательских флагах хуков. Чтобы хуки получили разовые параметры без правки `webtest.config.mjs` или окружения, используется стандартная shell-конвенция `--` (как у `npm`, `cargo`, `pytest`): всё, что идёт после `--` в CLI движка, передаётся в `prepare` / `cleanup` через поле `hookArgs: string[]` без интерпретации.
```
node run.mjs test tests/myapp/ --bail -- --rebuild-stand --reload-data
└─ runner ─┘ └────── hookArgs ────────┘
```
В этом примере движок получает `--bail`, а `hookArgs` хуков становится `['--rebuild-stand', '--reload-data']`. Парсинг этого массива — ответственность хуков.
Если разделитель `--` не указан, `hookArgs` — пустой массив. Это позволяет движку и хукам развиваться независимо: новый встроенный флаг движка никогда не пересечётся с пользовательским.
---
## 7. Файл конфигурации
`webtest.config.mjs` в корне каталога тестов. Необязателен — если отсутствует, URL должен быть передан через CLI.
```js
export default {
// Контексты: именованные URL для разных пользователей/ролей.
// Рекомендация: латинский ID контекста (`clerk`, `manager`) + кириллический
// `displayName` для UI/слайдов. Любые пользовательские поля пробрасываются как есть
// и доступны хукам через `ctx.testInfo.contexts[name]` (см. §3).
contexts: {
clerk: { url: 'http://localhost/app-clerk/ru_RU', displayName: 'Кладовщик' },
manager: { url: 'http://localhost/app-manager/ru_RU', displayName: 'Менеджер' },
admin: { url: 'http://localhost/app-admin/ru_RU', displayName: 'Админ' },
},
defaultContext: 'clerk',
// Значения по умолчанию (переопределяются флагами CLI)
timeout: 30000,
retries: 0,
screenshot: 'on-failure', // 'every-step' | 'off'
record: false,
// Дефолтный тег-фильтр. Применяется только если CLI не передал --tags.
// Удобно для сценариев «прогон по умолчанию = smoke», при этом --tags=full
// (или --tags=) с CLI прозрачно перекрывает.
tags: ['smoke'],
// Дефолтный режим изоляции для контекстов, которые сами его не указали
// (config.contexts.<name>.isolation). См. §8.
isolation: 'tab', // 'tab' | 'window'
// Allure severity policy (опционально). Маппинг наоборот: уровень → [теги].
// Резолв см. §9 «Авто-эмиссия label-ов».
severity: {
critical: ['smoke', 'multi-context'],
minor: ['recording'],
// blocker / trivial — необязательны, можно опустить
},
defaultSeverity: 'normal',
};
```
**Упрощённая форма** (один контекст, без именованных):
```js
export default {
url: 'http://localhost/app/ru_RU',
timeout: 30000,
};
```
### Валидация файла конфигурации
`severity` валидируется при загрузке:
- ключи — только из `blocker | critical | normal | minor | trivial`;
- значение каждого ключа — массив тегов;
- тег не может одновременно состоять в двух уровнях severity (явная ошибка с указанием конфликта);
- `defaultSeverity` — из стандартного набора.
При нарушении любого правила движок выводит сообщение с указанием конфликта и завершается с ненулевым кодом до запуска тестов.
Кириллица в ID контекстов работает, но смешанный регистр снижает читаемость кода (`testInfo.contexts.кладовщик.displayName` рядом с `testInfo.contexts.clerk.displayName`). Рекомендуем разделять технический ID и человекочитаемое имя.
Флаги CLI всегда переопределяют значения из файла конфигурации.
---
## 8. Контексты
### Механизм: Playwright BrowserContext
Один процесс браузера (`chromium.launch()`), несколько изолированных контекстов. Каждый контекст — отдельная сессия (куки, авторизация, состояние страницы).
```
browser (один процесс chromium)
├─ BrowserContext "кладовщик" → page → http://localhost/app-clerk/ru_RU
├─ BrowserContext "менеджер" → page → http://localhost/app-mgr/ru_RU
└─ BrowserContext "админ" → page → http://localhost/app-admin/ru_RU
```
Преимущества:
- **Мгновенное переключение** между пользователями (смена активного `page`).
- **Состояние сохраняется** — переключились на менеджера и обратно, у кладовщика все формы остались открытыми.
- **Нет переподключений** — каждая сессия живёт независимо.
- **Один процесс** — экономия ресурсов по сравнению с несколькими браузерами.
### Одиночный контекст (по умолчанию)
Большинство тестов. Один BrowserContext, один пользователь. Тест получает плоский `ctx` со всем API.
```js
export const context = 'manager'; // необязательно, иначе defaultContext
export default async function({ clickElement, fillFields, … }) { }
```
### Порядок выполнения и переключение контекста
Движок НЕ группирует тесты по контексту. Порядок выполнения — алфавитный по полному относительному пути файла (плюс порядок экспорта внутри файла). Для каждого теста:
1. Через `ensureContext(name)` создаются BrowserContext-ы, упомянутые в `t.context` / `t.contexts` (если ещё не созданы).
2. `setActiveContext(primaryContext)` — активный контекст = первый объявленный (для single — `t.context || defaultContext`, для multi — `t.contexts[0]`).
3. После теста встроенный сброс пробегает по всем использованным контекстам.
Контексты живут между тестами: переключение через `setActiveContext` — дешёвое, повторный вход в 1С не требуется. Закрываются явно (`closeContext`) или финальной очисткой движка перед закрытием браузера.
### Мульти-контекст (процессные тесты)
```js
export const contexts = ['clerk', 'manager'];
export default async function({ clerk, manager, step, assert }) { … }
```
Каждый именованный контекст — полноценный scoped-объект API со своим `page`. Тест оркестрирует переключение между пользователями. Состояние каждого пользователя сохраняется между переключениями:
```js
await step('Кладовщик создаёт документ', async () => {
await clerk.openCommand('Приходные накладные');
await clerk.clickElement('Создать');
await clerk.fillFields({ 'Контрагент': 'ООО Поставщик' });
await clerk.clickElement('Записать');
// кладовщик стоит на форме документа
});
await step('Менеджер утверждает', async () => {
await manager.navigateSection('Согласование');
await manager.clickElement('Утвердить');
});
await step('Кладовщик проверяет статус', async () => {
// страница кладовщика ТА ЖЕ — форма открыта, навигация не нужна
const state = await clerk.getFormState();
assert.equal(state.fields['Статус']?.value, 'Утверждён');
});
```
### Публичный контекстный API
| Метод | Назначение |
|-------|-----------|
| `createContext(name, url, { isolation, extensionPath })` | Создаёт BrowserContext и переходит по URL. |
| `setActiveContext(name)` | Переключает активный слот; при активной записи дописывает последние кадры старой страницы и переподключает screencast. |
| `closeContext(name)` | Выход из 1С + закрытие (`page` для `tab`, `BrowserContext` для `window`), удаляет из реестра. Бросает исключение, если `name === active`. |
| `listContexts()` / `hasContext(name)` / `getActiveContext()` | Только для чтения. |
### Режимы изоляции
Поле `isolation` задаётся в двух местах:
- **На уровне контекста:** `config.contexts.<name>.isolation` — приоритет 1.
- **На уровне файла конфигурации:** `config.isolation` — применяется к контекстам, у которых своего значения нет. По умолчанию `'tab'`.
| Режим | Реализация | Окна | Cookies | 1С-расширение |
|-------|-----------|------|---------|---------------|
| `'tab'` (default) | `launchPersistentContext` + `newPage()` per context | 1 окно, N вкладок | общие по path | загружается надёжно |
| `'window'` | `chromium.launch()` + `newContext()` per context | N окон | полная изоляция | может не загружаться |
Смешивать режимы в одном прогоне нельзя — `createContext` бросает явную ошибку. То есть `config.isolation` фактически становится режимом всего прогона, если хотя бы один контекст явно не переопределил его на тот же режим.
### Закрытие неактивных контекстов
`closeContext(name)` нельзя вызвать на активном контексте — будет исключение. В scoped API это естественно: вызывать `manager.closeContext('clerk')` (scoped-обёртка сначала переключает активный на `manager`, потом закрывает `clerk`). Если контекст лишний (роль больше не нужна в рамках теста / прогона) — закрывайте его сразу: освобождает лицензию платформы и снимает нагрузку со следующих тестов.
---
## 9. Отчёты
### JSON (нативный, по умолчанию)
```json
{
"runner": "web-test",
"url": "http://localhost/app/ru_RU",
"startedAt": "2026-04-05T10:00:00.000Z",
"finishedAt": "2026-04-05T10:05:30.000Z",
"duration": 330.0,
"summary": {
"total": 25,
"passed": 23,
"failed": 1,
"skipped": 1
},
"tests": [
{
"name": "CRUD справочника Контрагенты",
"file": "02-catalog-crud.test.mjs",
"tags": ["smoke", "crud"],
"contexts": ["clerk"],
"severity": "critical",
"status": "passed",
"start": 1712345678000,
"stop": 1712345690300,
"duration": 12.3,
"attempts": 1,
"steps": [
{
"name": "Открыть список",
"start": 1712345678000,
"stop": 1712345679200,
"status": "passed",
"steps": []
}
],
"output": "Элемент найден в списке",
"error": null,
"screenshot": null,
"video": null
},
{
"name": "Обязательное поле",
"file": "10-validation.test.mjs",
"tags": ["validation"],
"contexts": ["clerk"],
"status": "failed",
"duration": 8.1,
"attempts": 2,
"steps": [
{
"name": "Сохранить пустую форму",
"start": 1712345700000,
"stop": 1712345708100,
"status": "failed",
"error": "Ожидалось модальное окно ошибки, но форма сохранилась"
}
],
"output": "",
"error": {
"message": "Ожидалось модальное окно ошибки, но форма сохранилась",
"step": "Сохранить пустую форму",
"screenshot": "error-shot-10.png"
},
"screenshot": "error-shot-10.png"
}
]
}
```
### Allure (`--format=allure --report-dir=allure-results/`)
Отдельные JSON-файлы для каждого теста в каталоге `allure-results/`:
```json
{
"uuid": "сгенерированный-uuid",
"name": "CRUD справочника",
"fullName": "02-catalog-crud.test.mjs",
"status": "passed",
"stage": "finished",
"start": 1712345678000,
"stop": 1712345690300,
"labels": [
{ "name": "tag", "value": "smoke" },
{ "name": "tag", "value": "crud" },
{ "name": "suite", "value": "root" },
{ "name": "severity", "value": "critical" }
],
"steps": [
{
"name": "Открыть список",
"status": "passed",
"start": 1712345678000,
"stop": 1712345679200,
"steps": []
}
],
"attachments": [
{
"name": "Скриншот при падении",
"source": "uuid-attachment.png",
"type": "image/png"
}
]
}
```
Скриншоты/видео копируются в `allure-results/` с уникальными именами.
#### Авто-эмиссия меток
Движок всегда заполняет следующие метки (`labels`):
- **`tag`** — по одному на каждый элемент `mod.tags[]`. Готовая фильтрация в Allure-отчёте без дополнительной разметки.
- **`suite`** — `dirname(t.filePath)`. Тесты в корне `testDir` идут под `'root'`, тесты в подкаталоге `sales/` — под `'sales'`. Это даёт левую группировку отчёта без ручной разметки.
- **`severity`** — резолв в порядке приоритета:
1. `export const severity = 'critical'` в самом тесте, **если значение валидное** (одно из `blocker | critical | normal | minor | trivial`). Если экспорт задан, но значение невалидное — пункт пропускается и идём в (3); резолв через теги (пункт 2) при этом **не выполняется** (хотел бы автор иначе — он бы не объявлял `severity`).
2. Иначе **максимальный ранг** среди тегов теста (стандартные имена `blocker | critical | normal | minor | trivial` напрямую, либо через `config.severity`-маппинг).
3. Иначе `config.defaultSeverity` или `'normal'`.
Ранги: `blocker(5) > critical(4) > normal(3) > minor(2) > trivial(1)`. Выбор по максимуму не зависит от порядка тегов в `mod.tags`.
Пример: `tags: ['smoke', 'recording']` + `severity: { critical: ['smoke'], minor: ['recording'] }` → severity = `critical` (5 > 2).
#### Доп. файлы Allure через `<testDir>/_allure/`
Движок ищет каталог `_allure/` рядом с тестами и копирует все его файлы в `reportDir` перед генерацией отчёта. Конвенция для статичной настройки Allure, для которой нет места внутри JSON-файла теста:
| Файл | Назначение |
|------|-----------|
| `categories.json` | Классификация падений по regex (группировка failed-тестов в виджете Categories — «timeout», «license-flake», «1C modal» и т.п.). |
| `environment.properties` | `key=value` строки в виджет Environment (URL, версия 1С, ветка git, номер сборки). Часто формируется динамически из `prepare()`. |
| `executor.json` | CI/CD-метаданные (Jenkins URL, GitHub run-id и т.п.). |
Подчёркивание в имени — параллель `_hooks.mjs` (инфраструктура, не тест). Сборщик тестов пропускает каталог `_allure/` по общему правилу (`startsWith('_')`). Если каталога нет — ничего не происходит, отчёт собирается обычным образом.
Пример `categories.json` (минимальный):
```json
[
{ "name": "Timeout", "messageRegex": "Timeout \\(\\d+ms\\)" },
{ "name": "Assertion", "messageRegex": "(Expected|AssertionError).*" }
]
```
### JUnit XML (`--format=junit`)
```xml
<?xml version="1.0" encoding="UTF-8"?>
<testsuites name="web-test" tests="25" failures="1" skipped="1" time="330.0">
<testsuite name="tests/myapp" tests="25" failures="1" skipped="1">
<testcase name="CRUD справочника" classname="02-catalog-crud.test.mjs" time="12.3"/>
<testcase name="Обязательное поле" classname="10-validation.test.mjs" time="8.1">
<failure message="Ожидалось модальное окно ошибки, но форма сохранилась">
Стек вызовов…
</failure>
<system-out>Скриншот: error-shot-10.png</system-out>
</testcase>
</testsuite>
</testsuites>
```
---
## 10. Консольный вывод
```
web-test — http://localhost/app/ru_RU
Запуск 25 тестов из tests/myapp/
✓ Навигация по разделам (2.1s)
✓ CRUD справочника Контрагенты (12.3s)
├ Открыть список (1.2s)
├ Создать элемент (8.0s)
└ Проверить в списке (3.1s)
✗ Обязательное поле (8.1s)
├ Открыть форму (2.0s)
└ ✗ Сохранить пустую форму (6.1s)
Ожидалось модальное окно ошибки, но форма сохранилась
скриншот: error-shot-10.png
○ Составной тип (skip: не реализовано)
23 passed, 1 failed, 1 skipped (2m 0.5s)
```
Для passed-тестов выводится одна строка `✓ name (duration)`. Шаги печатаются только для упавших — после строки `✗`, с отступом, плюс сообщение ошибки и путь к скриншоту. Полная картина по шагам — в JSON-отчёте (`--report=…`).
---
## 11. Скриншоты и видео
### Стратегия скриншотов
| Стратегия | Поведение |
|-----------|----------|
| `on-failure` (по умолчанию) | Скриншот при падении теста, прикрепляется к ошибке. |
| `every-step` | Скриншот в конце каждого `step()`, плюс при падении. |
| `off` | Без автоматических скриншотов. |
Скриншоты сохраняются в каталог отчёта по шаблону `{индекс-теста}-{имя-шага}.png`. В JSON-отчёте — путь относительно каталога отчёта.
### Видеозапись
При включённом `--record`:
- `startRecording()` перед каждым тестом.
- `stopRecording()` после каждого теста.
- Видео сохраняется как `{индекс-теста}-{имя-теста}.mp4`.
- Прикрепляется к отчёту (Allure: вложение видео).
Подробности по записи (overlays, captions, narration) — см. [web-test-recording-guide.md](web-test-recording-guide.md).
---
## 12. Сброс состояния
Встроенный механизм, выполняется после `afterEach``teardown`) каждого теста. Псевдокод и условие выхода — в §6 «Встроенный сброс состояния».
Для мульти-контекстных тестов сброс пробегает по всем живым контекстам, использованным тестом.
Гарантирует, что каждый тест стартует с чистого рабочего стола, независимо от того, как завершился предыдущий (падение, таймаут, ошибка утверждения).
---
## 13. Параметризация
```js
export const name = 'Заполнение поля {type}';
export const params = [
{ type: 'String', field: 'Наименование', value: 'Тест' },
{ type: 'Number', field: 'Цена', value: '100.50' },
{ type: 'Date', field: 'ДатаПоступления', value: '01.01.2024' },
{ type: 'Boolean',field: 'Активен', value: true },
];
export default async function({ fillFields, getFormState, assert }, { type, field, value }) {
await fillFields({ [field]: value });
const state = await getFormState();
assert.equal(state.fields[field]?.value, String(value));
}
```
Параметры разворачиваются в отдельные тесты на этапе discovery:
- Имя теста формируется подстановкой через шаблон `{key}` в `mod.name`; если шаблона нет — суффикс `[index]`.
- Тест получает `param` вторым аргументом (`default(ctx, param)`).
- В отчётах каждый набор — отдельная запись со своим `name` и `param` в `testInfo`.
- `ctx.testInfo.param` доступен в теле теста и хуках.
---
## 14. Обнаружение тестов
`testDir` (первый позиционный аргумент после URL) — каталог, в котором живут тесты. Сборщик рекурсивно обходит дерево и собирает файлы по правилам ниже.
```
tests/myapp/
_hooks.mjs # пропускается (префикс '_')
_allure/ # пропускается (префикс '_')
webtest.config.mjs # пропускается (не *.test.mjs)
sales/
01-order-create.test.mjs
02-order-post.test.mjs
warehouse/
01-receipt.test.mjs
```
### Правила
| Аспект | Поведение |
|--------|-----------|
| Обход | Рекурсивный; файлы и каталоги, имя которых начинается на `_` или `.`, пропускаются |
| Шаблон имени | Только `*.test.mjs` |
| Порядок | Сортировка по полному относительному пути (`sales/01` идёт до `warehouse/01`) |
| `file` в отчёте | `relative(testDir, file)` с разделителем `/`, например `sales/01-order-create.test.mjs` |
| Фильтр по пути с CLI | `node run.mjs test tests/myapp/sales/` запустит только подкаталог |
| Конкретный файл | `node run.mjs test tests/myapp/sales/01-order-create.test.mjs` |
### Чего НЕТ (сознательное упрощение)
- **`_hooks.mjs` на уровне подкаталога.** Движок ищет `_hooks.mjs` только в корне `testDir`. Подкаталоги свои хуки не получают.
- **`webtest.config.mjs` на уровне подкаталога.** Тоже только в корне.
- **Многоуровневой Suite-разметки из дерева каталогов.** Allure-метка `suite` строится только по первому уровню (`dirname(filePath)`); более глубокую группировку делайте через `tags`.
- **Контекста по умолчанию на уровне подкаталога.** Каждый тест объявляет `context` / `contexts` сам; от пути контексты не наследуются.
### Конвенции
1. **Папки — для организации**, не для механики. Общая подготовка — в глобальном `_hooks.mjs.beforeAll` или в `setup` / `teardown` конкретного теста.
2. **Группировку в отчётах** делайте через `tags: ['sales']`, не через путь. Это даёт фильтрацию (`--tags=sales`) и работает в Allure/JUnit без дополнительной разметки.
3. **«Запустить только sales»** — двумя путями: `tests/myapp/sales/` (по каталогу) или `--tags=sales` (по тегу).
4. **Сортировка по полному пути** означает, что `warehouse/01-x` запустится ПОСЛЕ `sales/02-y`. Для строгого глобального порядка используйте 3-значные префиксы (`010-`/`020-`/…) либо явные теги-фазы.
---
## 15. Ошибки и трассировка
### Авто-обнаружение 1С-ошибок
Все ACTION_FNS (`clickElement`, `fillFields`, `fillField`, `selectValue`, `fillTableRow`, `deleteTableRow`, `openCommand`, `navigateSection`, `navigateLink`, `openFile`, `closeForm`, `filterList`, `unfilterList`) обёрнуты. После каждого вызова:
1. Проверяется `state.errors.modal` / `balloon`.
2. Если есть — делается скриншот (до того, как `fetchErrorStack` закроет модалку).
3. Для модальных ошибок вызывается `fetchErrorStack` (две стратегии — Path 1 для платформенных исключений с кнопкой «Открыть отчёт», Path 2 для `ВызватьИсключение` через гамбургер-меню → О программе → Информация для тех. поддержки; см. [web-test-guide.md](web-test-guide.md)).
4. Бросается исключение со структурированным `err.onecError`:
```js
err.onecError = {
step, // имя действия (например 'clickElement')
args, // аргументы, с которыми вызывалось
errors, // { modal?, balloon? }
formState, // снапшот getFormState
stack, // { raw, entries: [{ location, code }], timestamp } | null
screenshot, // путь к скриншоту
};
```
В отчёте это превращается в `error.onecError.stack` для упавшего теста. Разбор причин падения и категории — см. §16.
### Платформенные модальные диалоги
`getFormState()` возвращает `platformDialogs` — массив платформенных диалогов (About, Support Info, Error Report). `closeForm()` закрывает их. `dismissPendingErrors()` чистит ожидающие модалки автоматически (вызывается перед каждым ACTION_FN, плюс в встроенном сбросе после теста).
Модальное окно платформенной ошибки сначала рендерится в переходном состоянии (~1 с), затем перерисовывается в стабильное. `fetchErrorStack` ждёт 1.5 с и перепроверяет `hasReport` перед выбором стратегии.
### Таймауты
- Глобальный таймаут теста: `mod.timeout` или `config.timeout` или CLI `--timeout=ms`.
- Таймаут срабатывает на уровне теста (`testFn()` + `setup` + `teardown`), не на уровне отдельного `step` или action.
- При таймауте: текущий step помечается failed, бросается ошибка с сообщением `Timeout (<N>ms)`, далее запускается `afterEach` и встроенный сброс.
### Повторы
При `--retry=N` (или `config.retries`) упавший тест повторяется до `1 + N` раз. Для каждой попытки:
- `beforeEach` / `setup` / `default` / `teardown` / `afterEach` + встроенный сброс выполняются заново.
- `ctx.testInfo.attempt` инкрементируется.
- В отчёте фиксируется `attempts` — фактически выполнено попыток.
- Считается passed, если последняя попытка зелёная; иначе failed.
`beforeAll` / `afterAll` / `prepare` / `cleanup` / `afterOpenContext` / `beforeCloseContext` не повторяются (это жизненный цикл всего прогона или контекста, не теста).
---
## 16. Анализ результатов
### Что лежит в записи об упавшем тесте
JSON-отчёт (`tests[]`, полная структура — §9) для каждого падения содержит:
- `error.message` — текст исключения.
- `error.step` — имя шага, на котором упало.
- `error.screenshot` — путь к скриншоту падения (если стратегия скриншотов не `off`).
- `error.onecError` (только для 1С-исключений) — структура с полями: `step` (имя действия, например `clickElement`), `args` (аргументы вызова), `errors` (модальное окно или balloon), `formState` (снимок формы на момент ошибки), `stack` — платформенный стек вызовов 1С с `entries[{location, code}]`.
- `steps[]` — пошаговая разбивка с метками времени, у каждого шага свой `status` и `error`.
В Allure-отчёте те же данные лежат в `statusDetails` (текст ошибки и трассировка), скриншоты и видео — во вложениях, автоматическая группировка по причинам — через `categories.json` (§9).
### Типовые причины падений
Большинство падений на 1С-стенде сводится к трём причинам, и их полезно различать при разборе отчёта:
- **Ошибка в тесте** — селектор не нашёл элемент, ожидание не сошлось, гонка без точки синхронизации. Признаки: падение стабильно повторяется на одном и том же шаге; после правки теста воспроизводимость исчезает. Действие — изменить тест.
- **Ошибка в прикладном решении** — реально воспроизведённое некорректное поведение конфигурации. Признаки: упал шаг, имитирующий пользовательскую операцию; в `error.onecError.stack` есть платформенный стек вызовов 1С с указанием на код решения. Действие — передать разработчику конфигурации, тест править не нужно.
- **Сбой стенда** — таймаут Apache, форма входа не загрузилась, не хватило веб-лицензий. Признаки: падение на навигации или входе; от прогона к прогону падает «то одно, то другое», без связи с содержанием теста. Действие — править инфраструктуру (`prepare()`, очистка сессий, идемпотентность хуков), не тесты.
`categories.json` Allure (§9) удобно настраивать именно под эти три категории — regex по `error.message` уже даёт первичную классификацию в виджете Categories.
---
## 17. Глоссарий
| Термин | Определение |
|--------|-------------|
| **testDir** | Каталог тестов, переданный позиционным аргументом движку. Корень для discovery, `_hooks.mjs`, `webtest.config.mjs`, `_allure/`. |
| **Context (BrowserContext)** | Изолированная сессия Playwright. Куки/состояние/страница независимы. В рамках одного теста используется один или несколько контекстов. |
| **Active context** | Контекст, на котором сейчас оперируют функции browser-API. Переключается `setActiveContext`. |
| **Primary context** | Контекст, активный на входе в тест. Декларация (`mod.context` или `mod.contexts[0]`). Зафиксирован в `testInfo.primaryContext`. |
| **Default context** | Контекст из `config.defaultContext` (или единственный URL в упрощённой конфигурации). Используется, если тест не указал `context` / `contexts`. |
| **Scoped API** | Объект на `ctx.<name>` в мульти-контекстных тестах — обёртки browser-функций, авто-переключающие контекст перед каждым вызовом. |
| **Action function (ACTION_FN)** | Browser-функция, обёрнутая авто-обнаружением 1С-ошибок. Список — в §3. |
| **Step** | Логический блок внутри теста, обёрнутый `step(name, fn)`. Маппится на Allure-step, попадает в `report.tests[].steps[]`. |
| **Reset state** | Встроенная пост-тестовая очистка: `dismissPendingErrors` + закрытие всех открытых форм до рабочего стола. Выполняется после `afterEach`. |
| **hookArgs** | Массив строк, переданных в `prepare` / `cleanup` после CLI-разделителя `--`. Движком не интерпретируются. |
| **Severity** | Уровень критичности теста (`blocker / critical / normal / minor / trivial`) для Allure. Резолвится из `mod.severity`, тегов, `config.severity`, `config.defaultSeverity`. |
---
## См. также
- [web-test-guide.md](web-test-guide.md) — browser API (`clickElement`, `getFormState`, `readTable`, …) и интерактивный режим.
- [web-test-recording-guide.md](web-test-recording-guide.md) — видеозапись, captions, narration, overlays.
- [web-test-regression-guide.md](web-test-regression-guide.md) — пользовательский гайд (на русском, с быстрым стартом).
- `/web-test` skill — `.claude/skills/web-test/SKILL.md`, `regress.md` (рабочая шпаргалка для модели).
+251
View File
@@ -0,0 +1,251 @@
#!/usr/bin/env node
// build-webtest-db v0.2 — Собирает синтетическую web-test конфигурацию в постоянные пути
// и накатывает её в зарегистрированную базу `webtest` (см. .v8-project.json).
//
// Двойной режим:
// - CLI: node tests/skills/build-webtest-db.mjs [--runtime ...] [--skip-platform]
// - Module: import { runSteps, execSkill, getProjectInfo, ... } from './build-webtest-db.mjs'
//
// CLI:
// node tests/skills/build-webtest-db.mjs # пересобрать с нуля
// node tests/skills/build-webtest-db.mjs --runtime python
// node tests/skills/build-webtest-db.mjs --skip-platform # только XML, без db-create/load/update
//
// После завершения база готова к /web-publish + web-test сессии.
import { execFile } from 'child_process';
import { existsSync, mkdirSync, rmSync, readFileSync, writeFileSync } from 'fs';
import { join, resolve, dirname } from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const ROOT = dirname(__filename);
const REPO_ROOT = resolve(ROOT, '../..');
const SKILLS = resolve(REPO_ROOT, '.claude/skills');
// ── Public API ────────────────────────────────────────────────────────────────
/**
* Reads .v8-project.json and locates webtest registration.
* @returns {{ v8path: string, v8exe: string, webtestDb: object, configSrc: string, dbPath: string }}
*/
export function getProjectInfo() {
const projectFile = join(REPO_ROOT, '.v8-project.json');
if (!existsSync(projectFile)) throw new Error('.v8-project.json not found');
const proj = JSON.parse(readFileSync(projectFile, 'utf8'));
const webtestDb = proj.databases?.find(d => d.id === 'webtest');
if (!webtestDb) throw new Error('Database "webtest" not registered in .v8-project.json');
const v8path = proj.v8path;
const v8exe = join(v8path, '1cv8.exe');
const dbPath = webtestDb.path;
const configSrc = resolve(REPO_ROOT, webtestDb.configSrc);
return { v8path, v8exe, webtestDb, configSrc, dbPath };
}
/**
* Resolves a skill script path to an absolute file (chooses .ps1 or .py based on runtime).
*/
export function resolveScript(scriptRelPath, runtime = 'powershell') {
const ext = runtime === 'python' ? '.py' : '.ps1';
const full = join(SKILLS, scriptRelPath + ext);
if (!existsSync(full)) throw new Error(`Script not found: ${full}`);
return full;
}
/**
* Executes a single skill script with provided arguments.
* @returns {Promise<string>} stdout
*/
export function execSkill(scriptPath, args, runtime = 'powershell') {
return new Promise((res, rej) => {
const cmd = runtime === 'python'
? [process.env.PYTHON || 'python', [scriptPath, ...args]]
: ['powershell.exe', ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-File', scriptPath, ...args]];
execFile(cmd[0], cmd[1], { encoding: 'utf8', timeout: 120_000, cwd: REPO_ROOT }, (err, stdout, stderr) => {
if (err) {
rej(new Error(stderr?.trim() || stdout?.trim() || err.message));
} else {
res(stdout);
}
});
});
}
/**
* Replaces {workDir}/{v8path}/{dbPath} placeholders in a string value.
*/
export function replacePlaceholders(s, paths) {
return String(s)
.replace('{workDir}', paths.workDir ?? '')
.replace('{v8path}', paths.v8path ?? '')
.replace('{dbPath}', paths.dbPath ?? '');
}
/**
* Executes an array of build steps.
*
* Each step: { name, script?, args?, input?, writeFile?, content? }
* - writeFile: write content to a file (relative to workDir or absolute), skip script call
* - script: relative path under .claude/skills (without extension)
* - args: { '-Flag': value | true }, value may contain {workDir}/{v8path}/{dbPath}/{inputFile}
* - input: JSON object written to __input.json (referenced by {inputFile} in args)
*
* @param {Array} steps
* @param {{ workDir: string, v8path: string, dbPath: string }} paths
* @param {string} runtime 'powershell' | 'python'
* @param {(line: string) => void} log
* @returns {Promise<{ ok: boolean, elapsed: number, failedAt?: number }>}
*/
export async function runSteps(steps, paths, runtime, log = console.log) {
const t0 = Date.now();
for (let i = 0; i < steps.length; i++) {
const step = steps[i];
const stepT0 = Date.now();
if (step.writeFile) {
try {
const target = replacePlaceholders(step.writeFile, paths);
const abs = target.includes(':') || target.startsWith('/') ? target : join(paths.workDir, target);
mkdirSync(dirname(abs), { recursive: true });
writeFileSync(abs, step.content ?? '', 'utf8');
const ms = Date.now() - stepT0;
log(` [${i + 1}/${steps.length}] OK ${step.name} (${(ms / 1000).toFixed(1)}s)`);
} catch (e) {
log(` [${i + 1}/${steps.length}] FAIL ${step.name}: ${e.message}`);
return { ok: false, elapsed: (Date.now() - t0) / 1000, failedAt: i };
}
continue;
}
let inputFile = null;
if (step.input) {
inputFile = join(paths.workDir, '__input.json');
writeFileSync(inputFile, JSON.stringify(step.input, null, 2), 'utf8');
}
const script = resolveScript(step.script, runtime);
const args = [];
for (const [flag, value] of Object.entries(step.args || {})) {
args.push(flag);
if (value === true) continue;
let v = String(value).replace('{inputFile}', inputFile || '');
v = replacePlaceholders(v, paths);
args.push(v);
}
try {
await execSkill(script, args, runtime);
if (inputFile && existsSync(inputFile)) rmSync(inputFile);
const ms = Date.now() - stepT0;
log(` [${i + 1}/${steps.length}] OK ${step.name} (${(ms / 1000).toFixed(1)}s)`);
} catch (e) {
if (inputFile && existsSync(inputFile)) rmSync(inputFile);
log(` [${i + 1}/${steps.length}] FAIL ${step.name}`);
log(` ${e.message.split('\n').join('\n ').substring(0, 1500)}`);
return { ok: false, elapsed: (Date.now() - t0) / 1000, failedAt: i };
}
}
return { ok: true, elapsed: (Date.now() - t0) / 1000 };
}
/**
* Returns the standard platform load steps (db-create + db-load-xml + db-update).
*/
export function platformLoadSteps() {
return [
{
name: 'db-create: создание файловой ИБ',
script: 'db-create/scripts/db-create',
args: { '-V8Path': '{v8path}', '-InfoBasePath': '{dbPath}' },
},
{
name: 'db-load-xml: загрузка конфигурации',
script: 'db-load-xml/scripts/db-load-xml',
args: { '-V8Path': '{v8path}', '-InfoBasePath': '{dbPath}', '-ConfigDir': '{workDir}' },
},
{
name: 'db-update: обновление БД',
script: 'db-update/scripts/db-update',
args: { '-V8Path': '{v8path}', '-InfoBasePath': '{dbPath}' },
},
];
}
/**
* Imports the build-webtest-config.test.mjs steps array.
*/
export async function loadBuildSteps() {
const buildModule = await import(`file://${join(ROOT, 'integration/build-webtest-config.test.mjs').replace(/\\/g, '/')}`);
return buildModule.steps;
}
// ── CLI ────────────────────────────────────────────────────────────────────────
async function runCli() {
const argv = process.argv.slice(2);
const opts = { runtime: 'powershell', skipPlatform: false };
for (let i = 0; i < argv.length; i++) {
const a = argv[i];
if (a === '--runtime' && argv[i + 1]) { opts.runtime = argv[++i]; continue; }
if (a === '--skip-platform') { opts.skipPlatform = true; continue; }
if (a === '-h' || a === '--help') {
console.log('Usage: build-webtest-db.mjs [--runtime powershell|python] [--skip-platform]');
process.exit(0);
}
}
const { v8path, v8exe, configSrc, dbPath } = getProjectInfo();
if (!opts.skipPlatform && !existsSync(v8exe)) {
console.error(`1cv8.exe not found at ${v8exe}`);
process.exit(1);
}
console.log(`[build-webtest-db] configSrc: ${configSrc}`);
console.log(`[build-webtest-db] dbPath: ${dbPath}`);
console.log(`[build-webtest-db] runtime: ${opts.runtime}`);
console.log('');
if (existsSync(configSrc)) {
console.log(`Removing existing configSrc...`);
rmSync(configSrc, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 });
}
mkdirSync(configSrc, { recursive: true });
if (!opts.skipPlatform && existsSync(dbPath)) {
console.log(`Removing existing IB...`);
rmSync(dbPath, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 });
}
const buildSteps = await loadBuildSteps();
const platformSteps = opts.skipPlatform ? [] : platformLoadSteps();
const allSteps = [...buildSteps, ...platformSteps];
const paths = { workDir: configSrc, v8path, dbPath };
const result = await runSteps(allSteps, paths, opts.runtime, console.log);
console.log('');
if (!result.ok) {
console.error(`Build FAILED after ${result.elapsed.toFixed(1)}s`);
process.exit(1);
}
console.log(`Build OK (${result.elapsed.toFixed(1)}s)`);
console.log('');
console.log(` configSrc: ${configSrc}`);
if (!opts.skipPlatform) {
console.log(` IB: ${dbPath}`);
console.log('');
console.log(` Next: /web-publish webtest → open in browser`);
}
}
// CLI guard: run only when invoked directly, not when imported.
const invokedDirectly = process.argv[1]
? fileURLToPath(import.meta.url) === resolve(process.argv[1])
: false;
if (invokedDirectly) {
runCli().catch(e => {
console.error(e.message);
process.exit(1);
});
}
@@ -0,0 +1,252 @@
<?xml version="1.0" encoding="utf-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Configuration uuid="UUID-001">
<InternalInfo>
<xr:ContainedObject>
<xr:ClassId>UUID-002</xr:ClassId>
<xr:ObjectId>UUID-003</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-004</xr:ClassId>
<xr:ObjectId>UUID-005</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-006</xr:ClassId>
<xr:ObjectId>UUID-007</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-008</xr:ClassId>
<xr:ObjectId>UUID-009</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-010</xr:ClassId>
<xr:ObjectId>UUID-011</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-012</xr:ClassId>
<xr:ObjectId>UUID-013</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-014</xr:ClassId>
<xr:ObjectId>UUID-015</xr:ObjectId>
</xr:ContainedObject>
</InternalInfo>
<Properties>
<Name>TestConfig</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>TestConfig</v8:content>
</v8:item>
</Synonym>
<Comment />
<NamePrefix />
<ConfigurationExtensionCompatibilityMode>Version8_3_24</ConfigurationExtensionCompatibilityMode>
<DefaultRunMode>ManagedApplication</DefaultRunMode>
<UsePurposes>
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
</UsePurposes>
<ScriptVariant>Russian</ScriptVariant>
<DefaultRoles />
<Vendor></Vendor>
<Version></Version>
<UpdateCatalogAddress />
<IncludeHelpInContents>false</IncludeHelpInContents>
<UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication>
<UseOrdinaryFormInManagedApplication>false</UseOrdinaryFormInManagedApplication>
<AdditionalFullTextSearchDictionaries />
<CommonSettingsStorage />
<ReportsUserSettingsStorage />
<ReportsVariantsStorage />
<FormDataSettingsStorage />
<DynamicListsUserSettingsStorage />
<URLExternalDataStorage />
<Content />
<DefaultReportForm />
<DefaultReportVariantForm />
<DefaultReportSettingsForm />
<DefaultReportAppearanceTemplate />
<DefaultDynamicListSettingsForm />
<DefaultSearchForm />
<DefaultDataHistoryChangeHistoryForm />
<DefaultDataHistoryVersionDataForm />
<DefaultDataHistoryVersionDifferencesForm />
<DefaultCollaborationSystemUsersChoiceForm />
<RequiredMobileApplicationPermissions />
<UsedMobileApplicationFunctionalities>
<app:functionality>
<app:functionality>Biometrics</app:functionality>
<app:use>true</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Location</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BackgroundLocation</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BluetoothPrinters</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>WiFiPrinters</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Contacts</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Calendars</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>PushNotifications</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>LocalNotifications</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>InAppPurchases</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>PersonalComputerFileExchange</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Ads</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>NumberDialing</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>CallProcessing</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>CallLog</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AutoSendSMS</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>ReceiveSMS</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>SMSLog</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Camera</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Microphone</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>MusicLibrary</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>PictureAndVideoLibraries</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AudioPlaybackAndVibration</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BackgroundAudioPlaybackAndVibration</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>InstallPackages</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>OSBackup</app:functionality>
<app:use>true</app:use>
</app:functionality>
<app:functionality>
<app:functionality>ApplicationUsageStatistics</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BarcodeScanning</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BackgroundAudioRecording</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AllFilesAccess</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Videoconferences</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>NFC</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>DocumentScanning</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>SpeechToText</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Geofences</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>IncomingShareRequests</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AllIncomingShareRequestsTypesProcessing</app:functionality>
<app:use>false</app:use>
</app:functionality>
</UsedMobileApplicationFunctionalities>
<StandaloneConfigurationRestrictionRoles />
<MobileApplicationURLs />
<AllowedIncomingShareRequestTypes />
<MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode>
<DefaultInterface />
<DefaultStyle />
<DefaultLanguage>Language.Русский</DefaultLanguage>
<BriefInformation />
<DetailedInformation />
<Copyright />
<VendorInformationAddress />
<ConfigurationInformationAddress />
<DataLockControlMode>Managed</DataLockControlMode>
<ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode>
<ModalityUseMode>DontUse</ModalityUseMode>
<SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode>
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
<CompatibilityMode>Version8_3_24</CompatibilityMode>
<DefaultConstantsForm />
</Properties>
<ChildObjects>
<Language>Русский</Language>
<DataProcessor>ЗапретРучногоВвода</DataProcessor>
</ChildObjects>
</Configuration>
</MetaDataObject>
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<DataProcessor uuid="UUID-001">
<InternalInfo>
<xr:GeneratedType name="DataProcessorObject.ЗапретРучногоВвода" category="Object">
<xr:TypeId>UUID-002</xr:TypeId>
<xr:ValueId>UUID-003</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="DataProcessorManager.ЗапретРучногоВвода" category="Manager">
<xr:TypeId>UUID-004</xr:TypeId>
<xr:ValueId>UUID-005</xr:ValueId>
</xr:GeneratedType>
</InternalInfo>
<Properties>
<Name>ЗапретРучногоВвода</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Запрет ручного ввода</v8:content>
</v8:item>
</Synonym>
<Comment />
<UseStandardCommands>false</UseStandardCommands>
<DefaultForm>DataProcessor.ЗапретРучногоВвода.Form.Форма</DefaultForm>
<AuxiliaryForm />
<IncludeHelpInContents>false</IncludeHelpInContents>
<ExtendedPresentation />
<Explanation />
</Properties>
<ChildObjects>
<Form>Форма</Form>
</ChildObjects>
</DataProcessor>
</MetaDataObject>
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Form uuid="UUID-001">
<Properties>
<Name>Форма</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Форма</v8:content>
</v8:item>
</Synonym>
<Comment/>
<FormType>Managed</FormType>
<IncludeHelpInContents>false</IncludeHelpInContents>
<UsePurposes>
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
<v8:Value xsi:type="app:ApplicationUsePurpose">MobilePlatformApplication</v8:Value>
</UsePurposes>
<ExtendedPresentation/>
</Properties>
</Form>
</MetaDataObject>
@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<Form xmlns="http://v8.1c.ru/8.3/xcf/logform" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcssch="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Title>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Запрет ручного ввода</v8:content>
</v8:item>
</Title>
<AutoTitle>false</AutoTitle>
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1"/>
<ChildItems>
<InputField name="ОбычноеПоле" id="1">
<DataPath>ОбычноеПоле</DataPath>
<Title>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Обычное поле</v8:content>
</v8:item>
</Title>
<ContextMenu name="ОбычноеПолеКонтекстноеМеню" id="2"/>
<ExtendedTooltip name="ОбычноеПолеРасширеннаяПодсказка" id="3"/>
</InputField>
<InputField name="ПолеБезРучногоВвода" id="4">
<DataPath>ПолеБезРучногоВвода</DataPath>
<Title>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Только через выбор</v8:content>
</v8:item>
</Title>
<TextEdit>false</TextEdit>
<ContextMenu name="ПолеБезРучногоВводаКонтекстноеМеню" id="5"/>
<ExtendedTooltip name="ПолеБезРучногоВводаРасширеннаяПодсказка" id="6"/>
</InputField>
</ChildItems>
<Attributes>
<Attribute name="Объект" id="7">
<Type>
<v8:Type>cfg:DataProcessorObject.ЗапретРучногоВвода</v8:Type>
</Type>
<MainAttribute>true</MainAttribute>
</Attribute>
<Attribute name="ОбычноеПоле" id="8">
<Title>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Обычное поле</v8:content>
</v8:item>
</Title>
<Type>
<v8:Type>xs:string</v8:Type>
<v8:StringQualifiers>
<v8:Length>100</v8:Length>
<v8:AllowedLength>Variable</v8:AllowedLength>
</v8:StringQualifiers>
</Type>
</Attribute>
<Attribute name="ПолеБезРучногоВвода" id="9">
<Title>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Поле без ручного ввода</v8:content>
</v8:item>
</Title>
<Type>
<v8:Type>xs:string</v8:Type>
<v8:StringQualifiers>
<v8:Length>100</v8:Length>
<v8:AllowedLength>Variable</v8:AllowedLength>
</v8:StringQualifiers>
</Type>
</Attribute>
</Attributes>
</Form>
@@ -0,0 +1,19 @@
#Область ОбработчикиСобытийФормы
#КонецОбласти
#Область ОбработчикиСобытийЭлементовФормы
#КонецОбласти
#Область ОбработчикиКомандФормы
#КонецОбласти
#Область ОбработчикиОповещений
#КонецОбласти
#Область СлужебныеПроцедурыИФункции
#КонецОбласти
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<ClientApplicationInterface xmlns="http://v8.1c.ru/8.2/managed-application/core" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="InterfaceLayouter">
<top>
<panel id="UUID-001">
<uuid>UUID-002</uuid>
</panel>
</top>
<left>
<panel id="UUID-003">
<uuid>UUID-004</uuid>
</panel>
</left>
<panelDef id="UUID-004"/>
<panelDef id="UUID-005"/>
<panelDef id="UUID-006"/>
<panelDef id="UUID-002"/>
<panelDef id="UUID-007"/>
</ClientApplicationInterface>
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Language uuid="UUID-001">
<Properties>
<Name>Русский</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Русский</v8:content>
</v8:item>
</Synonym>
<Comment/>
<LanguageCode>ru</LanguageCode>
</Properties>
</Language>
</MetaDataObject>
@@ -0,0 +1,28 @@
{
"name": "Поле ввода с textEdit:false (запрет ручного ввода)",
"preRun": [
{
"script": "meta-compile/scripts/meta-compile",
"input": { "type": "DataProcessor", "name": "ЗапретРучногоВвода" },
"args": { "-JsonPath": "{inputFile}", "-OutputDir": "{workDir}" }
},
{
"script": "form-add/scripts/form-add",
"args": { "-ObjectPath": "{workDir}/DataProcessors/ЗапретРучногоВвода.xml", "-FormName": "Форма" }
}
],
"params": { "outputPath": "DataProcessors/ЗапретРучногоВвода/Forms/Форма/Ext/Form.xml" },
"validatePath": "DataProcessors/ЗапретРучногоВвода/Forms/Форма/Ext/Form.xml",
"input": {
"title": "Запрет ручного ввода",
"elements": [
{ "input": "ОбычноеПоле", "path": "ОбычноеПоле", "title": "Обычное поле" },
{ "input": "ПолеБезРучногоВвода", "path": "ПолеБезРучногоВвода", "textEdit": false, "title": "Только через выбор" }
],
"attributes": [
{ "name": "Объект", "type": "DataProcessorObject.ЗапретРучногоВвода", "main": true },
{ "name": "ОбычноеПоле", "type": "string(100)" },
{ "name": "ПолеБезРучногоВвода", "type": "string(100)" }
]
}
}
@@ -0,0 +1,5 @@
{
"name": "DataPath с Items.<Table>.CurrentData и ~Атрибут не вызывают ложных ошибок",
"setup": "fixture:datapath-currentdata",
"params": { "formPath": "DataProcessors/Spec/Forms/Форма" }
}
@@ -0,0 +1,5 @@
{
"name": "Числовые и UUID DataPath не вызывают ложных ошибок",
"setup": "fixture:datapath-opaque-refs",
"params": { "formPath": "DataProcessors/Opaque/Forms/Форма" }
}
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<Form xmlns="http://v8.1c.ru/8.3/xcf/logform" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Title>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Тест</v8:content>
</v8:item>
</Title>
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1"/>
<ChildItems>
<Table name="Список" id="1">
<DataPath>Список</DataPath>
<ContextMenu name="СписокКонтекстноеМеню" id="2"/>
<AutoCommandBar name="СписокКоманднаяПанель" id="3"/>
<SearchStringAddition name="СписокСтрокаПоиска" id="4"/>
<ViewStatusAddition name="СписокСостояниеПросмотра" id="5"/>
<SearchControlAddition name="СписокУправлениеПоиском" id="6"/>
<ChildItems>
<InputField name="Ссылка" id="7">
<DataPath>Список.Ссылка</DataPath>
<ContextMenu name="СсылкаКонтекстноеМеню" id="8"/>
<ExtendedTooltip name="СсылкаРасширеннаяПодсказка" id="9"/>
</InputField>
</ChildItems>
</Table>
<InputField name="ТекущаяСсылка" id="10">
<DataPath>Items.Список.CurrentData.Ссылка</DataPath>
<ContextMenu name="ТекущаяСсылкаКонтекстноеМеню" id="11"/>
<ExtendedTooltip name="ТекущаяСсылкаРасширеннаяПодсказка" id="12"/>
</InputField>
<InputField name="ВыбраннаяСсылка" id="13">
<DataPath>~Список.Ссылка</DataPath>
<ContextMenu name="ВыбраннаяСсылкаКонтекстноеМеню" id="14"/>
<ExtendedTooltip name="ВыбраннаяСсылкаРасширеннаяПодсказка" id="15"/>
</InputField>
</ChildItems>
<Attributes>
<Attribute name="Список" id="16">
<Type>
<v8:Type>cfg:DynamicList</v8:Type>
</Type>
<MainAttribute>true</MainAttribute>
</Attribute>
</Attributes>
</Form>
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<Form xmlns="http://v8.1c.ru/8.3/xcf/logform" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Title>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Тест</v8:content>
</v8:item>
</Title>
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1"/>
<ChildItems>
<InputField name="ПривязкаПоUUID" id="1">
<DataPath>1/0:a917a122-f663-4c45-8de0-fd5104007de3</DataPath>
<ContextMenu name="ПривязкаПоUUIDКонтекстноеМеню" id="2"/>
<ExtendedTooltip name="ПривязкаПоUUIDРасширеннаяПодсказка" id="3"/>
</InputField>
<LabelField name="ЧисловаяПривязка" id="4">
<DataPath>10</DataPath>
<ContextMenu name="ЧисловаяПривязкаКонтекстноеМеню" id="5"/>
<ExtendedTooltip name="ЧисловаяПривязкаРасширеннаяПодсказка" id="6"/>
</LabelField>
<InputField name="Нормальное" id="7">
<DataPath>Объект.Наименование</DataPath>
<ContextMenu name="НормальноеКонтекстноеМеню" id="8"/>
<ExtendedTooltip name="НормальноеРасширеннаяПодсказка" id="9"/>
</InputField>
</ChildItems>
<Attributes>
<Attribute name="Объект" id="10">
<Type>
<v8:Type>cfg:DataProcessorObject.Opaque</v8:Type>
</Type>
<MainAttribute>true</MainAttribute>
</Attribute>
</Attributes>
</Form>
@@ -0,0 +1,29 @@
{
"name": "decimal — все формы квалификаторов (bare, (N), (N,M), nonneg, синонимы)",
"params": { "outputPath": "Template.xml" },
"input": {
"dataSets": [{
"name": "Основной",
"query": "ВЫБРАТЬ 1 КАК Поле1",
"fields": [
"ДеньгиПоУмолчанию: decimal",
"ЦелоеОдинАргумент: decimal(10)",
"ОбычныеДеньги: decimal(10,2)",
"Положительные: decimal(10,2,nonneg)",
"ЦелоеПоложительное: decimal(10,nonneg)",
"ЧислоСинонимБезАргументов: число",
"ЧислоСинонимЦелое: число(8)",
"ЧислоСинонимКоличество: число(15,3)"
]
}],
"parameters": [
"ПараметрДеньги: decimal",
"ПараметрЦелое: decimal(10)",
"ПараметрКоличество: decimal(15,3,nonneg)"
]
},
"validatePath": "Template.xml",
"expect": {
"files": ["Template.xml"]
}
}
@@ -0,0 +1,32 @@
{
"name": "Параметры с пустыми значениями (все типы, разные sentinel-формы)",
"params": { "outputPath": "Template.xml" },
"input": {
"dataSets": [{
"name": "Основной",
"query": "ВЫБРАТЬ 1 КАК Поле1",
"fields": ["Поле1: число(1,0)"]
}],
"parameters": [
"Параметр1",
"Параметр2: string =",
"ПараметрСписок: EnumRef.СтатусТеста @valueList = _",
"ПараметрСсылка: CatalogRef.ПлоскийПростой",
"ПараметрДата: date = null",
{ "name": "ПараметрЧисло", "type": "decimal", "value": null },
"ПараметрБулево: boolean = ",
"ПараметрСтандартныйПериод: StandardPeriod = _",
{ "name": "ПараметрТипНеЗадан", "value": null },
"ПараметрСписокСтрок: string @valueList",
"ПараметрВремяСЗначением: time = 0001-01-01T12:30:00",
"ПараметрВремяПусто: time",
"ПараметрСтрокаФиксСЗначением: string(10,fix) = АБВ",
"ПараметрСтрокаФиксПусто: string(10,fix)",
{ "name": "СоставнойТип", "type": ["string(10,fix)", "CatalogRef.ПлоскийПростой"], "value": null }
]
},
"validatePath": "Template.xml",
"expect": {
"files": ["Template.xml"]
}
}
@@ -1,12 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema"
xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common"
xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core"
xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings"
xmlns:v8="http://v8.1c.ru/8.1/data/core"
xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dataSource>
<name>ИсточникДанных1</name>
<dataSourceType>Local</dataSourceType>
@@ -17,7 +10,12 @@
<dataPath>Поле</dataPath>
<field>Поле</field>
<valueType>
<v8:Type>decimal</v8:Type>
<v8:Type>xs:decimal</v8:Type>
<v8:NumberQualifiers>
<v8:Digits>10</v8:Digits>
<v8:FractionDigits>2</v8:FractionDigits>
<v8:AllowedSign>Any</v8:AllowedSign>
</v8:NumberQualifiers>
</valueType>
</field>
<dataSource>ИсточникДанных1</dataSource>
@@ -30,9 +28,8 @@
</valueType>
<value xsi:type="v8:StandardPeriod">
<v8:variant xsi:type="v8:StandardPeriodVariant">LastMonth</v8:variant>
<v8:startDate>0001-01-01T00:00:00</v8:startDate>
<v8:endDate>0001-01-01T00:00:00</v8:endDate>
</value>
<useRestriction>false</useRestriction>
<denyIncompleteValues>true</denyIncompleteValues>
<use>Always</use>
</parameter>
@@ -47,7 +44,7 @@
<valueType>
<v8:Type>xs:dateTime</v8:Type>
<v8:DateQualifiers>
<v8:DateFractions>Date</v8:DateFractions>
<v8:DateFractions>DateTime</v8:DateFractions>
</v8:DateQualifiers>
</valueType>
<value xsi:type="xs:dateTime">0001-01-01T00:00:00</value>
@@ -65,7 +62,7 @@
<valueType>
<v8:Type>xs:dateTime</v8:Type>
<v8:DateQualifiers>
<v8:DateFractions>Date</v8:DateFractions>
<v8:DateFractions>DateTime</v8:DateFractions>
</v8:DateQualifiers>
</valueType>
<value xsi:type="xs:dateTime">0001-01-01T00:00:00</value>
@@ -77,6 +74,12 @@
<valueType>
<v8:Type>v8:StandardPeriod</v8:Type>
</valueType>
<value xsi:type="v8:StandardPeriod">
<v8:variant xsi:type="v8:StandardPeriodVariant">Custom</v8:variant>
<v8:startDate>0001-01-01T00:00:00</v8:startDate>
<v8:endDate>0001-01-01T00:00:00</v8:endDate>
</value>
<useRestriction>false</useRestriction>
</parameter>
<parameter>
<name>Флаг</name>
@@ -84,6 +87,7 @@
<v8:Type>xs:boolean</v8:Type>
</valueType>
<value xsi:type="xs:boolean">true</value>
<useRestriction>false</useRestriction>
</parameter>
<parameter>
<name>Сумма</name>
@@ -96,6 +100,7 @@
</v8:NumberQualifiers>
</valueType>
<value xsi:type="xs:decimal">0</value>
<useRestriction>false</useRestriction>
</parameter>
<parameter>
<name>Ставка</name>
@@ -108,6 +113,7 @@
</v8:NumberQualifiers>
</valueType>
<value xsi:type="xs:decimal">13.5</value>
<useRestriction>false</useRestriction>
</parameter>
<parameter>
<name>Метка</name>
@@ -119,6 +125,7 @@
</v8:StringQualifiers>
</valueType>
<value xsi:type="xs:string">ТестовоеЗначение</value>
<useRestriction>false</useRestriction>
</parameter>
<parameter>
<name>ПустаяСтрока</name>
@@ -129,6 +136,8 @@
<v8:AllowedLength>Variable</v8:AllowedLength>
</v8:StringQualifiers>
</valueType>
<value xsi:type="xs:string"/>
<useRestriction>false</useRestriction>
</parameter>
<parameter>
<name>Валюта</name>
@@ -136,6 +145,7 @@
<v8:Type xmlns:d5p1="http://v8.1c.ru/8.1/data/enterprise/current-config">d5p1:CatalogRef.Валюты</v8:Type>
</valueType>
<value xsi:type="dcscor:DesignTimeValue">Справочник.Валюты.EmptyRef</value>
<useRestriction>false</useRestriction>
</parameter>
<settingsVariant>
<dcsset:name>Основной</dcsset:name>
@@ -151,8 +161,6 @@
<dcscor:parameter>Период</dcscor:parameter>
<dcscor:value xsi:type="v8:StandardPeriod">
<v8:variant xsi:type="v8:StandardPeriodVariant">LastMonth</v8:variant>
<v8:startDate>0001-01-01T00:00:00</v8:startDate>
<v8:endDate>0001-01-01T00:00:00</v8:endDate>
</dcscor:value>
<dcsset:userSettingID>UUID-001</dcsset:userSettingID>
</dcscor:item>
@@ -199,12 +207,6 @@
</dcscor:item>
</dcsset:dataParameters>
<dcsset:item xsi:type="dcsset:StructureItemGroup">
<dcsset:order>
<dcsset:item xsi:type="dcsset:OrderItemAuto"/>
</dcsset:order>
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
</dcsset:item>
</dcsset:settings>
</settingsVariant>
@@ -1,12 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema"
xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common"
xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core"
xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings"
xmlns:v8="http://v8.1c.ru/8.1/data/core"
xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dataSource>
<name>ИсточникДанных1</name>
<dataSourceType>Local</dataSourceType>
@@ -93,6 +86,7 @@
<v8:Type xmlns:d5p1="http://v8.1c.ru/8.1/data/enterprise/current-config">d5p1:EnumRef.Округления</v8:Type>
</valueType>
<value xsi:type="dcscor:DesignTimeValue">Перечисление.Округления.Окр1_00</value>
<useRestriction>false</useRestriction>
<availableValue>
<value xsi:type="dcscor:DesignTimeValue">Перечисление.Округления.Окр1_00</value>
<presentation xsi:type="v8:LocalStringType">
@@ -133,6 +127,7 @@
</dcsset:presentation>
<dcsset:settings xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows">
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
<dcsset:item xsi:type="dcsset:SelectedItemField">
<dcsset:field>Счет</dcsset:field>
</dcsset:item>
@@ -181,9 +176,6 @@
<dcsset:periodAdditionEnd xsi:type="xs:dateTime">0001-01-01T00:00:00</dcsset:periodAdditionEnd>
</dcsset:item>
</dcsset:groupItems>
<dcsset:order>
<dcsset:item xsi:type="dcsset:OrderItemAuto"/>
</dcsset:order>
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
<dcsset:item xsi:type="dcsset:SelectedItemFolder">
@@ -1,12 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema"
xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common"
xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core"
xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings"
xmlns:v8="http://v8.1c.ru/8.1/data/core"
xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dataSource>
<name>ИсточникДанных1</name>
<dataSourceType>Local</dataSourceType>
@@ -34,7 +27,7 @@
</dataSet>
<calculatedField>
<dataPath>ИмяРесурса</dataPath>
<expression>&quot;&quot;</expression>
<expression>""</expression>
<title xsi:type="v8:LocalStringType">
<v8:item>
<v8:lang>ru</v8:lang>
@@ -58,6 +51,7 @@
</dcsset:presentation>
<dcsset:settings xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows">
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
<dcsset:item xsi:type="dcsset:StructureItemGroup">
<dcsset:order>
@@ -1,12 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema"
xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common"
xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core"
xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings"
xmlns:v8="http://v8.1c.ru/8.1/data/core"
xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dataSource>
<name>ИсточникДанных1</name>
<dataSourceType>Local</dataSourceType>
@@ -42,7 +35,7 @@
</dataSet>
<calculatedField>
<dataPath>ИмяРесурса</dataPath>
<expression>&quot;&quot;</expression>
<expression>""</expression>
<title xsi:type="v8:LocalStringType">
<v8:item>
<v8:lang>ru</v8:lang>
@@ -91,6 +84,7 @@
</dcsset:presentation>
<dcsset:settings xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows">
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
<dcsset:item xsi:type="dcsset:StructureItemGroup">
<dcsset:order>
@@ -1,12 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema"
xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common"
xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core"
xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings"
xmlns:v8="http://v8.1c.ru/8.1/data/core"
xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dataSource>
<name>ИсточникДанных1</name>
<dataSourceType>Local</dataSourceType>
@@ -86,6 +79,7 @@
</dcsset:presentation>
<dcsset:settings xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows">
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
<dcsset:item xsi:type="dcsset:StructureItemGroup">
<dcsset:order>
@@ -0,0 +1,169 @@
<?xml version="1.0" encoding="UTF-8"?>
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dataSource>
<name>ИсточникДанных1</name>
<dataSourceType>Local</dataSourceType>
</dataSource>
<dataSet xsi:type="DataSetQuery">
<name>Основной</name>
<field xsi:type="DataSetFieldField">
<dataPath>ДеньгиПоУмолчанию</dataPath>
<field>ДеньгиПоУмолчанию</field>
<valueType>
<v8:Type>xs:decimal</v8:Type>
<v8:NumberQualifiers>
<v8:Digits>10</v8:Digits>
<v8:FractionDigits>2</v8:FractionDigits>
<v8:AllowedSign>Any</v8:AllowedSign>
</v8:NumberQualifiers>
</valueType>
</field>
<field xsi:type="DataSetFieldField">
<dataPath>ЦелоеОдинАргумент</dataPath>
<field>ЦелоеОдинАргумент</field>
<valueType>
<v8:Type>xs:decimal</v8:Type>
<v8:NumberQualifiers>
<v8:Digits>10</v8:Digits>
<v8:FractionDigits>0</v8:FractionDigits>
<v8:AllowedSign>Any</v8:AllowedSign>
</v8:NumberQualifiers>
</valueType>
</field>
<field xsi:type="DataSetFieldField">
<dataPath>ОбычныеДеньги</dataPath>
<field>ОбычныеДеньги</field>
<valueType>
<v8:Type>xs:decimal</v8:Type>
<v8:NumberQualifiers>
<v8:Digits>10</v8:Digits>
<v8:FractionDigits>2</v8:FractionDigits>
<v8:AllowedSign>Any</v8:AllowedSign>
</v8:NumberQualifiers>
</valueType>
</field>
<field xsi:type="DataSetFieldField">
<dataPath>Положительные</dataPath>
<field>Положительные</field>
<valueType>
<v8:Type>xs:decimal</v8:Type>
<v8:NumberQualifiers>
<v8:Digits>10</v8:Digits>
<v8:FractionDigits>2</v8:FractionDigits>
<v8:AllowedSign>Nonnegative</v8:AllowedSign>
</v8:NumberQualifiers>
</valueType>
</field>
<field xsi:type="DataSetFieldField">
<dataPath>ЦелоеПоложительное</dataPath>
<field>ЦелоеПоложительное</field>
<valueType>
<v8:Type>xs:decimal</v8:Type>
<v8:NumberQualifiers>
<v8:Digits>10</v8:Digits>
<v8:FractionDigits>0</v8:FractionDigits>
<v8:AllowedSign>Nonnegative</v8:AllowedSign>
</v8:NumberQualifiers>
</valueType>
</field>
<field xsi:type="DataSetFieldField">
<dataPath>ЧислоСинонимБезАргументов</dataPath>
<field>ЧислоСинонимБезАргументов</field>
<valueType>
<v8:Type>xs:decimal</v8:Type>
<v8:NumberQualifiers>
<v8:Digits>10</v8:Digits>
<v8:FractionDigits>2</v8:FractionDigits>
<v8:AllowedSign>Any</v8:AllowedSign>
</v8:NumberQualifiers>
</valueType>
</field>
<field xsi:type="DataSetFieldField">
<dataPath>ЧислоСинонимЦелое</dataPath>
<field>ЧислоСинонимЦелое</field>
<valueType>
<v8:Type>xs:decimal</v8:Type>
<v8:NumberQualifiers>
<v8:Digits>8</v8:Digits>
<v8:FractionDigits>0</v8:FractionDigits>
<v8:AllowedSign>Any</v8:AllowedSign>
</v8:NumberQualifiers>
</valueType>
</field>
<field xsi:type="DataSetFieldField">
<dataPath>ЧислоСинонимКоличество</dataPath>
<field>ЧислоСинонимКоличество</field>
<valueType>
<v8:Type>xs:decimal</v8:Type>
<v8:NumberQualifiers>
<v8:Digits>15</v8:Digits>
<v8:FractionDigits>3</v8:FractionDigits>
<v8:AllowedSign>Any</v8:AllowedSign>
</v8:NumberQualifiers>
</valueType>
</field>
<dataSource>ИсточникДанных1</dataSource>
<query>ВЫБРАТЬ 1 КАК Поле1</query>
</dataSet>
<parameter>
<name>ПараметрДеньги</name>
<valueType>
<v8:Type>xs:decimal</v8:Type>
<v8:NumberQualifiers>
<v8:Digits>10</v8:Digits>
<v8:FractionDigits>2</v8:FractionDigits>
<v8:AllowedSign>Any</v8:AllowedSign>
</v8:NumberQualifiers>
</valueType>
<value xsi:type="xs:decimal">0</value>
<useRestriction>false</useRestriction>
</parameter>
<parameter>
<name>ПараметрЦелое</name>
<valueType>
<v8:Type>xs:decimal</v8:Type>
<v8:NumberQualifiers>
<v8:Digits>10</v8:Digits>
<v8:FractionDigits>0</v8:FractionDigits>
<v8:AllowedSign>Any</v8:AllowedSign>
</v8:NumberQualifiers>
</valueType>
<value xsi:type="xs:decimal">0</value>
<useRestriction>false</useRestriction>
</parameter>
<parameter>
<name>ПараметрКоличество</name>
<valueType>
<v8:Type>xs:decimal</v8:Type>
<v8:NumberQualifiers>
<v8:Digits>15</v8:Digits>
<v8:FractionDigits>3</v8:FractionDigits>
<v8:AllowedSign>Nonnegative</v8:AllowedSign>
</v8:NumberQualifiers>
</valueType>
<value xsi:type="xs:decimal">0</value>
<useRestriction>false</useRestriction>
</parameter>
<settingsVariant>
<dcsset:name>Основной</dcsset:name>
<dcsset:presentation xsi:type="v8:LocalStringType">
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Основной</v8:content>
</v8:item>
</dcsset:presentation>
<dcsset:settings xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows">
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
<dcsset:item xsi:type="dcsset:StructureItemGroup">
<dcsset:order>
<dcsset:item xsi:type="dcsset:OrderItemAuto"/>
</dcsset:order>
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
</dcsset:item>
</dcsset:settings>
</settingsVariant>
</DataCompositionSchema>
@@ -0,0 +1,199 @@
<?xml version="1.0" encoding="UTF-8"?>
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dataSource>
<name>ИсточникДанных1</name>
<dataSourceType>Local</dataSourceType>
</dataSource>
<dataSet xsi:type="DataSetQuery">
<name>Основной</name>
<field xsi:type="DataSetFieldField">
<dataPath>Поле1</dataPath>
<field>Поле1</field>
<valueType>
<v8:Type>xs:decimal</v8:Type>
<v8:NumberQualifiers>
<v8:Digits>1</v8:Digits>
<v8:FractionDigits>0</v8:FractionDigits>
<v8:AllowedSign>Any</v8:AllowedSign>
</v8:NumberQualifiers>
</valueType>
</field>
<dataSource>ИсточникДанных1</dataSource>
<query>ВЫБРАТЬ 1 КАК Поле1</query>
</dataSet>
<parameter>
<name>Параметр1</name>
<value xsi:nil="true"/>
<useRestriction>false</useRestriction>
</parameter>
<parameter>
<name>Параметр2</name>
<valueType>
<v8:Type>xs:string</v8:Type>
<v8:StringQualifiers>
<v8:Length>0</v8:Length>
<v8:AllowedLength>Variable</v8:AllowedLength>
</v8:StringQualifiers>
</valueType>
<value xsi:type="xs:string"/>
<useRestriction>false</useRestriction>
</parameter>
<parameter>
<name>ПараметрСписок</name>
<valueType>
<v8:Type xmlns:d5p1="http://v8.1c.ru/8.1/data/enterprise/current-config">d5p1:EnumRef.СтатусТеста</v8:Type>
</valueType>
<useRestriction>false</useRestriction>
<valueListAllowed>true</valueListAllowed>
</parameter>
<parameter>
<name>ПараметрСсылка</name>
<valueType>
<v8:Type xmlns:d5p1="http://v8.1c.ru/8.1/data/enterprise/current-config">d5p1:CatalogRef.ПлоскийПростой</v8:Type>
</valueType>
<value xsi:nil="true"/>
<useRestriction>false</useRestriction>
</parameter>
<parameter>
<name>ПараметрДата</name>
<valueType>
<v8:Type>xs:dateTime</v8:Type>
<v8:DateQualifiers>
<v8:DateFractions>Date</v8:DateFractions>
</v8:DateQualifiers>
</valueType>
<value xsi:type="xs:dateTime">0001-01-01T00:00:00</value>
<useRestriction>false</useRestriction>
</parameter>
<parameter>
<name>ПараметрЧисло</name>
<valueType>
<v8:Type>xs:decimal</v8:Type>
<v8:NumberQualifiers>
<v8:Digits>10</v8:Digits>
<v8:FractionDigits>2</v8:FractionDigits>
<v8:AllowedSign>Any</v8:AllowedSign>
</v8:NumberQualifiers>
</valueType>
<value xsi:type="xs:decimal">0</value>
<useRestriction>false</useRestriction>
</parameter>
<parameter>
<name>ПараметрБулево</name>
<valueType>
<v8:Type>xs:boolean</v8:Type>
</valueType>
<value xsi:type="xs:boolean">false</value>
<useRestriction>false</useRestriction>
</parameter>
<parameter>
<name>ПараметрСтандартныйПериод</name>
<valueType>
<v8:Type>v8:StandardPeriod</v8:Type>
</valueType>
<value xsi:type="v8:StandardPeriod">
<v8:variant xsi:type="v8:StandardPeriodVariant">Custom</v8:variant>
<v8:startDate>0001-01-01T00:00:00</v8:startDate>
<v8:endDate>0001-01-01T00:00:00</v8:endDate>
</value>
<useRestriction>false</useRestriction>
</parameter>
<parameter>
<name>ПараметрТипНеЗадан</name>
<value xsi:nil="true"/>
<useRestriction>false</useRestriction>
</parameter>
<parameter>
<name>ПараметрСписокСтрок</name>
<valueType>
<v8:Type>xs:string</v8:Type>
<v8:StringQualifiers>
<v8:Length>0</v8:Length>
<v8:AllowedLength>Variable</v8:AllowedLength>
</v8:StringQualifiers>
</valueType>
<useRestriction>false</useRestriction>
<valueListAllowed>true</valueListAllowed>
</parameter>
<parameter>
<name>ПараметрВремяСЗначением</name>
<valueType>
<v8:Type>xs:dateTime</v8:Type>
<v8:DateQualifiers>
<v8:DateFractions>Time</v8:DateFractions>
</v8:DateQualifiers>
</valueType>
<value xsi:type="xs:dateTime">0001-01-01T12:30:00</value>
<useRestriction>false</useRestriction>
</parameter>
<parameter>
<name>ПараметрВремяПусто</name>
<valueType>
<v8:Type>xs:dateTime</v8:Type>
<v8:DateQualifiers>
<v8:DateFractions>Time</v8:DateFractions>
</v8:DateQualifiers>
</valueType>
<value xsi:type="xs:dateTime">0001-01-01T00:00:00</value>
<useRestriction>false</useRestriction>
</parameter>
<parameter>
<name>ПараметрСтрокаФиксСЗначением</name>
<valueType>
<v8:Type>xs:string</v8:Type>
<v8:StringQualifiers>
<v8:Length>10</v8:Length>
<v8:AllowedLength>Fixed</v8:AllowedLength>
</v8:StringQualifiers>
</valueType>
<value xsi:type="xs:string">АБВ</value>
<useRestriction>false</useRestriction>
</parameter>
<parameter>
<name>ПараметрСтрокаФиксПусто</name>
<valueType>
<v8:Type>xs:string</v8:Type>
<v8:StringQualifiers>
<v8:Length>10</v8:Length>
<v8:AllowedLength>Fixed</v8:AllowedLength>
</v8:StringQualifiers>
</valueType>
<value xsi:type="xs:string"/>
<useRestriction>false</useRestriction>
</parameter>
<parameter>
<name>СоставнойТип</name>
<valueType>
<v8:Type>xs:string</v8:Type>
<v8:StringQualifiers>
<v8:Length>10</v8:Length>
<v8:AllowedLength>Fixed</v8:AllowedLength>
</v8:StringQualifiers>
<v8:Type xmlns:d5p1="http://v8.1c.ru/8.1/data/enterprise/current-config">d5p1:CatalogRef.ПлоскийПростой</v8:Type>
</valueType>
<value xsi:nil="true"/>
<useRestriction>false</useRestriction>
</parameter>
<settingsVariant>
<dcsset:name>Основной</dcsset:name>
<dcsset:presentation xsi:type="v8:LocalStringType">
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Основной</v8:content>
</v8:item>
</dcsset:presentation>
<dcsset:settings xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows">
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
<dcsset:item xsi:type="dcsset:StructureItemGroup">
<dcsset:order>
<dcsset:item xsi:type="dcsset:OrderItemAuto"/>
</dcsset:order>
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
</dcsset:item>
</dcsset:settings>
</settingsVariant>
</DataCompositionSchema>
@@ -1,12 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema"
xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common"
xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core"
xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings"
xmlns:v8="http://v8.1c.ru/8.1/data/core"
xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dataSource>
<name>ИсточникДанных1</name>
<dataSourceType>Local</dataSourceType>
@@ -32,11 +25,11 @@
<appearance>
<dcscor:item xsi:type="dcsset:SettingsParameterValue">
<dcscor:parameter>МинимальнаяШирина</dcscor:parameter>
<dcscor:value xsi:type="xs:string">100</dcscor:value>
<dcscor:value xsi:type="xs:decimal">100</dcscor:value>
</dcscor:item>
<dcscor:item xsi:type="dcsset:SettingsParameterValue">
<dcscor:parameter>РастягиватьПоГоризонтали</dcscor:parameter>
<dcscor:value xsi:type="xs:string">true</dcscor:value>
<dcscor:value xsi:type="xs:boolean">true</dcscor:value>
</dcscor:item>
</appearance>
</field>
@@ -73,6 +66,7 @@
</dcsset:presentation>
<dcsset:settings xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows">
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
<dcsset:item xsi:type="dcsset:StructureItemGroup">
<dcsset:order>
@@ -1,12 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema"
xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common"
xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core"
xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings"
xmlns:v8="http://v8.1c.ru/8.1/data/core"
xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dataSource>
<name>ИсточникДанных1</name>
<dataSourceType>Local</dataSourceType>
@@ -58,6 +51,7 @@
</dcsset:presentation>
<dcsset:settings xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows">
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
<dcsset:item xsi:type="dcsset:StructureItemGroup">
<dcsset:order>
@@ -1,12 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema"
xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common"
xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core"
xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings"
xmlns:v8="http://v8.1c.ru/8.1/data/core"
xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dataSource>
<name>ИсточникДанных1</name>
<dataSourceType>Local</dataSourceType>
@@ -91,6 +84,7 @@
</dcsset:presentation>
<dcsset:settings xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows">
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
<dcsset:item xsi:type="dcsset:StructureItemGroup">
<dcsset:order>
@@ -1,12 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema"
xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common"
xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core"
xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings"
xmlns:v8="http://v8.1c.ru/8.1/data/core"
xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dataSource>
<name>ИсточникДанных1</name>
<dataSourceType>Local</dataSourceType>
@@ -65,9 +58,8 @@
</valueType>
<value xsi:type="v8:StandardPeriod">
<v8:variant xsi:type="v8:StandardPeriodVariant">LastMonth</v8:variant>
<v8:startDate>0001-01-01T00:00:00</v8:startDate>
<v8:endDate>0001-01-01T00:00:00</v8:endDate>
</value>
<useRestriction>false</useRestriction>
<denyIncompleteValues>true</denyIncompleteValues>
<use>Always</use>
</parameter>
@@ -82,7 +74,7 @@
<valueType>
<v8:Type>xs:dateTime</v8:Type>
<v8:DateQualifiers>
<v8:DateFractions>Date</v8:DateFractions>
<v8:DateFractions>DateTime</v8:DateFractions>
</v8:DateQualifiers>
</valueType>
<value xsi:type="xs:dateTime">0001-01-01T00:00:00</value>
@@ -100,7 +92,7 @@
<valueType>
<v8:Type>xs:dateTime</v8:Type>
<v8:DateQualifiers>
<v8:DateFractions>Date</v8:DateFractions>
<v8:DateFractions>DateTime</v8:DateFractions>
</v8:DateQualifiers>
</valueType>
<value xsi:type="xs:dateTime">0001-01-01T00:00:00</value>
@@ -126,6 +118,7 @@
<dcsset:item xsi:type="dcsset:SelectedItemField">
<dcsset:field>Сумма</dcsset:field>
</dcsset:item>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
<dcsset:filter>
<dcsset:item xsi:type="dcsset:FilterItemComparison">
@@ -140,8 +133,6 @@
<dcscor:parameter>Период</dcscor:parameter>
<dcscor:value xsi:type="v8:StandardPeriod">
<v8:variant xsi:type="v8:StandardPeriodVariant">LastMonth</v8:variant>
<v8:startDate>0001-01-01T00:00:00</v8:startDate>
<v8:endDate>0001-01-01T00:00:00</v8:endDate>
</dcscor:value>
<dcsset:userSettingID>UUID-002</dcsset:userSettingID>
</dcscor:item>
@@ -156,19 +147,7 @@
<dcsset:periodAdditionEnd xsi:type="xs:dateTime">0001-01-01T00:00:00</dcsset:periodAdditionEnd>
</dcsset:item>
</dcsset:groupItems>
<dcsset:order>
<dcsset:item xsi:type="dcsset:OrderItemAuto"/>
</dcsset:order>
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
<dcsset:item xsi:type="dcsset:StructureItemGroup">
<dcsset:order>
<dcsset:item xsi:type="dcsset:OrderItemAuto"/>
</dcsset:order>
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
</dcsset:item>
</dcsset:item>
</dcsset:settings>
@@ -1,12 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema"
xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common"
xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core"
xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings"
xmlns:v8="http://v8.1c.ru/8.1/data/core"
xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dataSource>
<name>ИсточникДанных1</name>
<dataSourceType>Local</dataSourceType>
@@ -90,6 +83,7 @@
<dcsset:item xsi:type="dcsset:SelectedItemField">
<dcsset:field>Сумма</dcsset:field>
</dcsset:item>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
<dcsset:item xsi:type="dcsset:StructureItemGroup">
<dcsset:groupItems>
@@ -101,12 +95,6 @@
<dcsset:periodAdditionEnd xsi:type="xs:dateTime">0001-01-01T00:00:00</dcsset:periodAdditionEnd>
</dcsset:item>
</dcsset:groupItems>
<dcsset:order>
<dcsset:item xsi:type="dcsset:OrderItemAuto"/>
</dcsset:order>
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
<dcsset:item xsi:type="dcsset:StructureItemGroup">
<dcsset:groupItems>
<dcsset:item xsi:type="dcsset:GroupItemField">
@@ -117,19 +105,7 @@
<dcsset:periodAdditionEnd xsi:type="xs:dateTime">0001-01-01T00:00:00</dcsset:periodAdditionEnd>
</dcsset:item>
</dcsset:groupItems>
<dcsset:order>
<dcsset:item xsi:type="dcsset:OrderItemAuto"/>
</dcsset:order>
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
<dcsset:item xsi:type="dcsset:StructureItemGroup">
<dcsset:order>
<dcsset:item xsi:type="dcsset:OrderItemAuto"/>
</dcsset:order>
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
</dcsset:item>
</dcsset:item>
</dcsset:item>
File diff suppressed because it is too large Load Diff
@@ -1,12 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema"
xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common"
xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core"
xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings"
xmlns:v8="http://v8.1c.ru/8.1/data/core"
xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dataSource>
<name>ИсточникДанных1</name>
<dataSourceType>Local</dataSourceType>
@@ -30,6 +23,7 @@
</dcsset:presentation>
<dcsset:settings xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows">
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
<dcsset:item xsi:type="dcsset:StructureItemGroup">
<dcsset:order>
@@ -1,12 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema"
xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common"
xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core"
xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings"
xmlns:v8="http://v8.1c.ru/8.1/data/core"
xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dataSource>
<name>ИсточникДанных1</name>
<dataSourceType>Local</dataSourceType>
@@ -71,6 +64,12 @@
<valueType>
<v8:Type>v8:StandardPeriod</v8:Type>
</valueType>
<value xsi:type="v8:StandardPeriod">
<v8:variant xsi:type="v8:StandardPeriodVariant">Custom</v8:variant>
<v8:startDate>0001-01-01T00:00:00</v8:startDate>
<v8:endDate>0001-01-01T00:00:00</v8:endDate>
</value>
<useRestriction>false</useRestriction>
</parameter>
<settingsVariant>
<dcsset:name>Основной</dcsset:name>
@@ -92,6 +91,7 @@
<dcsset:item xsi:type="dcsset:SelectedItemField">
<dcsset:field>Маржа</dcsset:field>
</dcsset:item>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
</dcsset:settings>
</settingsVariant>
@@ -1,12 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema"
xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common"
xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core"
xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings"
xmlns:v8="http://v8.1c.ru/8.1/data/core"
xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dataSource>
<name>ИсточникДанных1</name>
<dataSourceType>Local</dataSourceType>
@@ -69,6 +62,7 @@
</dcsset:presentation>
<dcsset:settings xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows">
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
<dcsset:item xsi:type="dcsset:StructureItemGroup">
<dcsset:order>
@@ -1,12 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema"
xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common"
xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core"
xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings"
xmlns:v8="http://v8.1c.ru/8.1/data/core"
xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dataSource>
<name>ИсточникДанных1</name>
<dataSourceType>Local</dataSourceType>
@@ -17,14 +10,24 @@
<dataPath>Поле1</dataPath>
<field>Поле1</field>
<valueType>
<v8:Type>decimal</v8:Type>
<v8:Type>xs:decimal</v8:Type>
<v8:NumberQualifiers>
<v8:Digits>10</v8:Digits>
<v8:FractionDigits>2</v8:FractionDigits>
<v8:AllowedSign>Any</v8:AllowedSign>
</v8:NumberQualifiers>
</valueType>
</field>
<field xsi:type="DataSetFieldField">
<dataPath>Поле2</dataPath>
<field>Поле2</field>
<valueType>
<v8:Type>decimal</v8:Type>
<v8:Type>xs:decimal</v8:Type>
<v8:NumberQualifiers>
<v8:Digits>10</v8:Digits>
<v8:FractionDigits>2</v8:FractionDigits>
<v8:AllowedSign>Any</v8:AllowedSign>
</v8:NumberQualifiers>
</valueType>
</field>
<field xsi:type="DataSetFieldField">
@@ -94,12 +97,6 @@
</dcsset:item>
</dcsset:conditionalAppearance>
<dcsset:item xsi:type="dcsset:StructureItemGroup">
<dcsset:order>
<dcsset:item xsi:type="dcsset:OrderItemAuto"/>
</dcsset:order>
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
</dcsset:item>
</dcsset:settings>
</settingsVariant>
@@ -1,12 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema"
xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common"
xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core"
xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings"
xmlns:v8="http://v8.1c.ru/8.1/data/core"
xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dataSource>
<name>ИсточникДанных1</name>
<dataSourceType>Local</dataSourceType>
@@ -40,6 +33,7 @@
<v8:Type xmlns:d5p1="http://v8.1c.ru/8.1/data/enterprise/current-config">d5p1:EnumRef.Округления</v8:Type>
</valueType>
<value xsi:type="dcscor:DesignTimeValue">Перечисление.Округления.Окр1_00</value>
<useRestriction>false</useRestriction>
<availableValue>
<value xsi:type="dcscor:DesignTimeValue">Перечисление.Округления.Окр1_00</value>
<presentation xsi:type="v8:LocalStringType">
@@ -69,6 +63,7 @@
</dcsset:presentation>
<dcsset:settings xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows">
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
<dcsset:item xsi:type="dcsset:StructureItemGroup">
<dcsset:order>
@@ -1,12 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema"
xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common"
xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core"
xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings"
xmlns:v8="http://v8.1c.ru/8.1/data/core"
xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dataSource>
<name>ИсточникДанных1</name>
<dataSourceType>Local</dataSourceType>
@@ -26,6 +19,7 @@
</dcsset:presentation>
<dcsset:settings xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows">
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
<dcsset:item xsi:type="dcsset:StructureItemGroup">
<dcsset:order>
@@ -1,12 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema"
xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common"
xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core"
xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings"
xmlns:v8="http://v8.1c.ru/8.1/data/core"
xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dataSource>
<name>ИсточникДанных1</name>
<dataSourceType>Local</dataSourceType>
@@ -68,9 +61,6 @@
<dcsset:periodAdditionEnd xsi:type="xs:dateTime">0001-01-01T00:00:00</dcsset:periodAdditionEnd>
</dcsset:item>
</dcsset:groupItems>
<dcsset:order>
<dcsset:item xsi:type="dcsset:OrderItemAuto"/>
</dcsset:order>
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemField">
<dcsset:field>Организация</dcsset:field>
@@ -89,9 +79,6 @@
<dcsset:periodAdditionEnd xsi:type="xs:dateTime">0001-01-01T00:00:00</dcsset:periodAdditionEnd>
</dcsset:item>
</dcsset:groupItems>
<dcsset:order>
<dcsset:item xsi:type="dcsset:OrderItemAuto"/>
</dcsset:order>
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemField">
<dcsset:field>Номенклатура</dcsset:field>
@@ -1,12 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema"
xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common"
xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core"
xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings"
xmlns:v8="http://v8.1c.ru/8.1/data/core"
xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dataSource>
<name>ИсточникДанных1</name>
<dataSourceType>Local</dataSourceType>
@@ -34,7 +27,7 @@
</dataSet>
<calculatedField>
<dataPath>ИмяРесурса</dataPath>
<expression>&quot;&quot;</expression>
<expression>""</expression>
<title xsi:type="v8:LocalStringType">
<v8:item>
<v8:lang>ru</v8:lang>
@@ -58,6 +51,7 @@
</dcsset:presentation>
<dcsset:settings xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows">
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
<dcsset:item xsi:type="dcsset:StructureItemGroup">
<dcsset:order>
@@ -1,12 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema"
xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common"
xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core"
xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings"
xmlns:v8="http://v8.1c.ru/8.1/data/core"
xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dataSource>
<name>ИсточникДанных1</name>
<dataSourceType>Local</dataSourceType>
@@ -60,9 +53,8 @@
</valueType>
<value xsi:type="v8:StandardPeriod">
<v8:variant xsi:type="v8:StandardPeriodVariant">LastMonth</v8:variant>
<v8:startDate>0001-01-01T00:00:00</v8:startDate>
<v8:endDate>0001-01-01T00:00:00</v8:endDate>
</value>
<useRestriction>false</useRestriction>
<denyIncompleteValues>true</denyIncompleteValues>
<use>Always</use>
</parameter>
@@ -77,7 +69,7 @@
<valueType>
<v8:Type>xs:dateTime</v8:Type>
<v8:DateQualifiers>
<v8:DateFractions>Date</v8:DateFractions>
<v8:DateFractions>DateTime</v8:DateFractions>
</v8:DateQualifiers>
</valueType>
<value xsi:type="xs:dateTime">0001-01-01T00:00:00</value>
@@ -95,7 +87,7 @@
<valueType>
<v8:Type>xs:dateTime</v8:Type>
<v8:DateQualifiers>
<v8:DateFractions>Date</v8:DateFractions>
<v8:DateFractions>DateTime</v8:DateFractions>
</v8:DateQualifiers>
</valueType>
<value xsi:type="xs:dateTime">0001-01-01T00:00:00</value>
@@ -124,6 +116,7 @@
<dcsset:item xsi:type="dcsset:SelectedItemField">
<dcsset:field>Сумма</dcsset:field>
</dcsset:item>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
<dcsset:filter>
<dcsset:item xsi:type="dcsset:FilterItemComparison">
@@ -147,19 +140,11 @@
<dcscor:parameter>Период</dcscor:parameter>
<dcscor:value xsi:type="v8:StandardPeriod">
<v8:variant xsi:type="v8:StandardPeriodVariant">LastMonth</v8:variant>
<v8:startDate>0001-01-01T00:00:00</v8:startDate>
<v8:endDate>0001-01-01T00:00:00</v8:endDate>
</dcscor:value>
<dcsset:userSettingID>UUID-002</dcsset:userSettingID>
</dcscor:item>
</dcsset:dataParameters>
<dcsset:item xsi:type="dcsset:StructureItemGroup">
<dcsset:order>
<dcsset:item xsi:type="dcsset:OrderItemAuto"/>
</dcsset:order>
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
</dcsset:item>
</dcsset:settings>
</settingsVariant>
@@ -1,12 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema"
xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common"
xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core"
xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings"
xmlns:v8="http://v8.1c.ru/8.1/data/core"
xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dataSource>
<name>ИсточникДанных1</name>
<dataSourceType>Local</dataSourceType>
@@ -57,9 +50,8 @@
</valueType>
<value xsi:type="v8:StandardPeriod">
<v8:variant xsi:type="v8:StandardPeriodVariant">LastMonth</v8:variant>
<v8:startDate>0001-01-01T00:00:00</v8:startDate>
<v8:endDate>0001-01-01T00:00:00</v8:endDate>
</value>
<useRestriction>false</useRestriction>
<denyIncompleteValues>true</denyIncompleteValues>
<use>Always</use>
</parameter>
@@ -74,7 +66,7 @@
<valueType>
<v8:Type>xs:dateTime</v8:Type>
<v8:DateQualifiers>
<v8:DateFractions>Date</v8:DateFractions>
<v8:DateFractions>DateTime</v8:DateFractions>
</v8:DateQualifiers>
</valueType>
<value xsi:type="xs:dateTime">0001-01-01T00:00:00</value>
@@ -92,7 +84,7 @@
<valueType>
<v8:Type>xs:dateTime</v8:Type>
<v8:DateQualifiers>
<v8:DateFractions>Date</v8:DateFractions>
<v8:DateFractions>DateTime</v8:DateFractions>
</v8:DateQualifiers>
</valueType>
<value xsi:type="xs:dateTime">0001-01-01T00:00:00</value>
@@ -104,6 +96,8 @@
<valueType>
<v8:Type xmlns:d5p1="http://v8.1c.ru/8.1/data/enterprise/current-config">d5p1:CatalogRef.Организации</v8:Type>
</valueType>
<value xsi:nil="true"/>
<useRestriction>false</useRestriction>
</parameter>
<settingsVariant>
<dcsset:name>Основной</dcsset:name>
@@ -115,6 +109,7 @@
</dcsset:presentation>
<dcsset:settings xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows">
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
<dcsset:item xsi:type="dcsset:StructureItemGroup">
<dcsset:order>
@@ -0,0 +1,12 @@
{
"script": "skd-decompile/scripts/skd-decompile",
"setup": "none",
"args": [
{ "flag": "-TemplatePath", "from": "workPath", "field": "templatePath" },
{ "flag": "-OutputPath", "from": "outputPath" }
],
"snapshot": {
"root": "workDir",
"normalizeUuids": true
}
}
@@ -0,0 +1,42 @@
{
"name": "Appearance с multilang значением (Формат={ru,en}) — round-trip",
"preRun": [
{
"script": "skd-compile/scripts/skd-compile",
"input": {
"dataSets": [{
"name": "Тест",
"query": "ВЫБРАТЬ * ИЗ Справочник.Сотрудники",
"fields": [
{
"field": "ДатаДокумента",
"type": "date",
"appearance": {
"Формат": { "ru": "ДЛФ=D", "en": "DLF=D" }
}
}
]
}],
"settingsVariants": [
{
"name": "Основной",
"settings": {
"conditionalAppearance": [
{
"selection": ["ДатаДокумента"],
"appearance": {
"Формат": { "ru": "ДЛФ=DT", "en": "DLF=DT" }
}
}
]
}
}
]
},
"args": { "-DefinitionFile": "{inputFile}", "-OutputPath": "Template.xml" },
"cwd": "{workDir}"
}
],
"params": { "templatePath": "Template.xml" },
"outputPath": "decompiled.json"
}
@@ -0,0 +1,43 @@
{
"name": "calculatedFields, totalFields, parameters с autoDates/valueList/hidden/availableValues",
"preRun": [
{
"script": "skd-compile/scripts/skd-compile",
"input": {
"dataSets": [{
"name": "Тест",
"query": "ВЫБРАТЬ * ИЗ Справочник.Номенклатура",
"fields": ["Цена: decimal(15,2)", "Закупка: decimal(15,2)"]
}],
"calculatedFields": [
"Маржа = Цена - Закупка",
"Наценка [Наценка, %]: decimal(10,2) = Маржа / Закупка * 100",
"Служебное: string = \"\" #noField #noFilter #noGroup #noOrder"
],
"totalFields": [
"Цена: Сумма",
"Маржа: Сумма(Цена - Закупка)"
],
"parameters": [
"Период [Отчетный период]: StandardPeriod = LastMonth @autoDates",
"Организация: CatalogRef.Организации",
"СписокДокументов: CatalogRef.Документы @valueList",
"СлужебныйПар: string @hidden",
{
"name": "ПорядокОкругления",
"type": "EnumRef.Округления",
"value": "Перечисление.Округления.Окр1",
"availableValues": [
{"value": "Перечисление.Округления.Окр1_00", "presentation": "руб. коп"},
{"value": "Перечисление.Округления.Окр1", "presentation": "руб."}
]
}
]
},
"args": { "-DefinitionFile": "{inputFile}", "-OutputPath": "Template.xml" },
"cwd": "{workDir}"
}
],
"params": { "templatePath": "Template.xml" },
"outputPath": "decompiled.json"
}
@@ -0,0 +1,39 @@
{
"name": "DataSetFieldFolder (поле-папка) + GroupItemAuto в structure",
"preRun": [
{
"script": "skd-compile/scripts/skd-compile",
"input": {
"dataSets": [{
"name": "Тест",
"query": "ВЫБРАТЬ * ИЗ Справочник.Сотрудники",
"fields": [
"Период: date",
{ "field": "СальдоНаНачалоПериода", "folder": true, "title": "Сальдо на начало периода" },
"СальдоНаНачалоПериода.Дт: decimal(15,2)",
"СальдоНаНачалоПериода.Кт: decimal(15,2)"
]
}],
"settingsVariants": [
{
"name": "Основной",
"settings": {
"structure": [
{
"groupFields": ["Auto"],
"children": [
{ "groupFields": ["Период"] }
]
}
]
}
}
]
},
"args": { "-DefinitionFile": "{inputFile}", "-OutputPath": "Template.xml" },
"cwd": "{workDir}"
}
],
"params": { "templatePath": "Template.xml" },
"outputPath": "decompiled.json"
}
@@ -0,0 +1,23 @@
{
"name": "Многострочный query → внешний .sql файл (@<name>.sql)",
"preRun": [
{
"script": "skd-compile/scripts/skd-compile",
"input": {
"dataSets": [{
"name": "ПродажиПоПериодам",
"query": "ВЫБРАТЬ\n\tНоменклатура,\n\tКоличество,\n\tСумма\nИЗ\n\tРегистрНакопления.Продажи\nГДЕ\n\tПериод МЕЖДУ &НачалоПериода И &КонецПериода",
"fields": [
"Номенклатура: CatalogRef.Номенклатура @dimension",
"Количество: decimal(15,3)",
"Сумма: decimal(15,2)"
]
}]
},
"args": { "-DefinitionFile": "{inputFile}", "-OutputPath": "Template.xml" },
"cwd": "{workDir}"
}
],
"params": { "templatePath": "Template.xml" },
"outputPath": "decompiled.json"
}
@@ -0,0 +1,33 @@
{
"name": "DataSetQuery + DataSetObject + DataSetUnion в одной схеме",
"preRun": [
{
"script": "skd-compile/scripts/skd-compile",
"input": {
"dataSets": [
{
"name": "Запрос1",
"query": "ВЫБРАТЬ Номенклатура.Наименование КАК Имя ИЗ Справочник.Номенклатура КАК Номенклатура",
"fields": ["Имя: string"]
},
{
"name": "Журнал",
"objectName": "ЖурналОшибок",
"fields": ["Сообщение: string(150)", "Уровень: string"]
},
{
"name": "Объединение",
"items": [
{ "name": "Часть1", "query": "ВЫБРАТЬ 1 КАК Поле", "fields": ["Поле: decimal(10,2)"] },
{ "name": "Часть2", "query": "ВЫБРАТЬ 2 КАК Поле", "fields": ["Поле: decimal(10,2)"] }
]
}
]
},
"args": { "-DefinitionFile": "{inputFile}", "-OutputPath": "Template.xml" },
"cwd": "{workDir}"
}
],
"params": { "templatePath": "Template.xml" },
"outputPath": "decompiled.json"
}
@@ -0,0 +1,50 @@
{
"name": "Поле: inputParameters (ChoiceParameters, ChoiceParameterLinks, simple typed)",
"preRun": [
{
"script": "skd-compile/scripts/skd-compile",
"input": {
"dataSets": [{
"name": "Тест",
"query": "ВЫБРАТЬ * ИЗ Справочник.Сотрудники",
"fields": [
{
"field": "СегментНоменклатуры",
"type": "CatalogRef.СегментыНоменклатуры",
"inputParameters": [
{ "parameter": "ПараметрыВыбора", "choiceParameters": [
{ "name": "Отбор.СпособФормирования", "values": [
"Перечисление.СпособыФормированияСегментов.ФормироватьВручную",
"Перечисление.СпособыФормированияСегментов.ПериодическиОбновлять"
]}
]}
]
},
{
"field": "Сотрудник",
"type": "CatalogRef.Сотрудники",
"inputParameters": [
{ "parameter": "СвязиПараметровВыбора", "choiceParameterLinks": [
{ "name": "Отбор.ТекущаяОрганизация", "value": "Организация", "mode": "Clear" },
{ "name": "Отбор.ТекущееПодразделение", "value": "Подразделение", "mode": "Clear" }
]}
]
},
{
"field": "ПростоЕА",
"type": "CatalogRef.Сотрудники",
"inputParameters": [
{ "parameter": "ПараметрыВыбора", "choiceParameters": [] },
{ "parameter": "БыстрыйВыбор", "use": false, "value": true }
]
}
]
}]
},
"args": { "-DefinitionFile": "{inputFile}", "-OutputPath": "Template.xml" },
"cwd": "{workDir}"
}
],
"params": { "templatePath": "Template.xml" },
"outputPath": "decompiled.json"
}
@@ -0,0 +1,21 @@
{
"name": "Поле: orderExpression (сортировка по выражению)",
"preRun": [
{
"script": "skd-compile/scripts/skd-compile",
"input": {
"dataSets": [{
"name": "Тест",
"query": "ВЫБРАТЬ * ИЗ Справочник.ВидыРасчета",
"fields": [
{ "field": "ВидРасчета", "type": "CatalogRef.ВидыРасчета", "orderExpression": { "expression": "ЕстьNULL(ВидРасчета.Порядок, 10000)", "orderType": "Asc", "autoOrder": false } }
]
}]
},
"args": { "-DefinitionFile": "{inputFile}", "-OutputPath": "Template.xml" },
"cwd": "{workDir}"
}
],
"params": { "templatePath": "Template.xml" },
"outputPath": "decompiled.json"
}
@@ -0,0 +1,24 @@
{
"name": "Поле: shorthand-роль с @-флагами и KV (balanceGroupName/balanceType)",
"preRun": [
{
"script": "skd-compile/scripts/skd-compile",
"input": {
"dataSets": [{
"name": "Тест",
"query": "ВЫБРАТЬ * ИЗ РегистрНакопления.Остатки",
"fields": [
"Период: date @period",
"Контрагент: CatalogRef.Контрагенты @dimension @required",
"СуммаНач: decimal(15,2) @balance balanceGroupName=Сумма balanceType=OpeningBalance",
"СуммаКон: decimal(15,2) @balance balanceGroupName=Сумма balanceType=ClosingBalance"
]
}]
},
"args": { "-DefinitionFile": "{inputFile}", "-OutputPath": "Template.xml" },
"cwd": "{workDir}"
}
],
"params": { "templatePath": "Template.xml" },
"outputPath": "decompiled.json"
}
@@ -0,0 +1,31 @@
{
"name": "Поля: типы, роли, restrictions, multilang, appearance, composite, presentation",
"preRun": [
{
"script": "skd-compile/scripts/skd-compile",
"input": {
"dataSets": [{
"name": "Тест",
"query": "ВЫБРАТЬ * ИЗ Справочник.Номенклатура",
"fields": [
"Наименование",
"Количество: decimal(15,2)",
"Организация: CatalogRef.Организации @dimension",
"Служебное: string #noField #noFilter",
"СтрокаФикс: string(50,fix)",
"Положительное: decimal(10,nonneg)",
"Дата1: date",
{ "field": "СПояснением", "title": "Поле с пояснением", "type": "decimal(15,2)", "appearance": { "ГоризонтальноеПоложение": "Right", "МинимальнаяШирина": "80" } },
{ "field": "Многоязычное", "title": { "ru": "Русский", "en": "English" }, "type": "string" },
{ "field": "СоставноеПоле", "type": ["CatalogRef.Организации", "CatalogRef.Валюты"] },
{ "field": "СВыражениемПредставления", "type": "CatalogRef.Номенклатура", "presentationExpression": "Представление(СВыражениемПредставления)" }
]
}]
},
"args": { "-DefinitionFile": "{inputFile}", "-OutputPath": "Template.xml" },
"cwd": "{workDir}"
}
],
"params": { "templatePath": "Template.xml" },
"outputPath": "decompiled.json"
}
@@ -0,0 +1,19 @@
{
"name": "Минимальный СКД с одним DataSetQuery",
"preRun": [
{
"script": "skd-compile/scripts/skd-compile",
"input": {
"dataSets": [{
"name": "НаборДанных1",
"query": "ВЫБРАТЬ Номенклатура.Наименование КАК Наименование ИЗ Справочник.Номенклатура КАК Номенклатура",
"fields": ["Наименование"]
}]
},
"args": { "-DefinitionFile": "{inputFile}", "-OutputPath": "Template.xml" },
"cwd": "{workDir}"
}
],
"params": { "templatePath": "Template.xml" },
"outputPath": "decompiled.json"
}
@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dataSource>
<name>ИсточникДанных1</name>
<dataSourceType>Local</dataSourceType>
</dataSource>
<dataSet xsi:type="DataSetQuery">
<name>Тест</name>
<field xsi:type="DataSetFieldField">
<dataPath>ДатаДокумента</dataPath>
<field>ДатаДокумента</field>
<valueType>
<v8:Type>xs:dateTime</v8:Type>
<v8:DateQualifiers>
<v8:DateFractions>Date</v8:DateFractions>
</v8:DateQualifiers>
</valueType>
<appearance>
<dcscor:item xsi:type="dcsset:SettingsParameterValue">
<dcscor:parameter>Формат</dcscor:parameter>
<dcscor:value xsi:type="v8:LocalStringType">
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>ДЛФ=D</v8:content>
</v8:item>
<v8:item>
<v8:lang>en</v8:lang>
<v8:content>DLF=D</v8:content>
</v8:item>
</dcscor:value>
</dcscor:item>
</appearance>
</field>
<dataSource>ИсточникДанных1</dataSource>
<query>ВЫБРАТЬ * ИЗ Справочник.Сотрудники</query>
</dataSet>
<settingsVariant>
<dcsset:name>Основной</dcsset:name>
<dcsset:presentation xsi:type="v8:LocalStringType">
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Основной</v8:content>
</v8:item>
</dcsset:presentation>
<dcsset:settings xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows">
<dcsset:conditionalAppearance>
<dcsset:item>
<dcsset:selection>
<dcsset:item>
<dcsset:field>ДатаДокумента</dcsset:field>
</dcsset:item>
</dcsset:selection>
<dcsset:filter/>
<dcsset:appearance>
<dcscor:item xsi:type="dcsset:SettingsParameterValue">
<dcscor:parameter>Формат</dcscor:parameter>
<dcscor:value xsi:type="v8:LocalStringType">
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>ДЛФ=DT</v8:content>
</v8:item>
<v8:item>
<v8:lang>en</v8:lang>
<v8:content>DLF=DT</v8:content>
</v8:item>
</dcscor:value>
</dcscor:item>
</dcsset:appearance>
</dcsset:item>
</dcsset:conditionalAppearance>
</dcsset:settings>
</settingsVariant>
</DataCompositionSchema>
@@ -0,0 +1 @@
{ "dataSets": [{ "name": "Тест", "query": "ВЫБРАТЬ * ИЗ Справочник.Сотрудники", "fields": [{ "field": "ДатаДокумента", "type": "date", "appearance": { "Формат": { "ru": "ДЛФ=D", "en": "DLF=D" } } }] }], "settingsVariants": [{ "name": "Основной", "settings": { "conditionalAppearance": [{ "selection": ["ДатаДокумента"], "appearance": { "Формат": { "ru": "ДЛФ=DT", "en": "DLF=DT" } } }] } }] }
@@ -0,0 +1,214 @@
<?xml version="1.0" encoding="UTF-8"?>
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dataSource>
<name>ИсточникДанных1</name>
<dataSourceType>Local</dataSourceType>
</dataSource>
<dataSet xsi:type="DataSetQuery">
<name>Тест</name>
<field xsi:type="DataSetFieldField">
<dataPath>Цена</dataPath>
<field>Цена</field>
<valueType>
<v8:Type>xs:decimal</v8:Type>
<v8:NumberQualifiers>
<v8:Digits>15</v8:Digits>
<v8:FractionDigits>2</v8:FractionDigits>
<v8:AllowedSign>Any</v8:AllowedSign>
</v8:NumberQualifiers>
</valueType>
</field>
<field xsi:type="DataSetFieldField">
<dataPath>Закупка</dataPath>
<field>Закупка</field>
<valueType>
<v8:Type>xs:decimal</v8:Type>
<v8:NumberQualifiers>
<v8:Digits>15</v8:Digits>
<v8:FractionDigits>2</v8:FractionDigits>
<v8:AllowedSign>Any</v8:AllowedSign>
</v8:NumberQualifiers>
</valueType>
</field>
<dataSource>ИсточникДанных1</dataSource>
<query>ВЫБРАТЬ * ИЗ Справочник.Номенклатура</query>
</dataSet>
<calculatedField>
<dataPath>Маржа</dataPath>
<expression>Цена - Закупка</expression>
</calculatedField>
<calculatedField>
<dataPath>Наценка</dataPath>
<expression>Маржа / Закупка * 100</expression>
<title xsi:type="v8:LocalStringType">
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Наценка, %</v8:content>
</v8:item>
</title>
<valueType>
<v8:Type>xs:decimal</v8:Type>
<v8:NumberQualifiers>
<v8:Digits>10</v8:Digits>
<v8:FractionDigits>2</v8:FractionDigits>
<v8:AllowedSign>Any</v8:AllowedSign>
</v8:NumberQualifiers>
</valueType>
</calculatedField>
<calculatedField>
<dataPath>Служебное</dataPath>
<expression>""</expression>
<valueType>
<v8:Type>xs:string</v8:Type>
<v8:StringQualifiers>
<v8:Length>0</v8:Length>
<v8:AllowedLength>Variable</v8:AllowedLength>
</v8:StringQualifiers>
</valueType>
<useRestriction>
<field>true</field>
<condition>true</condition>
<group>true</group>
<order>true</order>
</useRestriction>
</calculatedField>
<totalField>
<dataPath>Цена</dataPath>
<expression>Сумма(Цена)</expression>
</totalField>
<totalField>
<dataPath>Маржа</dataPath>
<expression>Сумма(Цена - Закупка)</expression>
</totalField>
<parameter>
<name>Период</name>
<title xsi:type="v8:LocalStringType">
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Отчетный период</v8:content>
</v8:item>
</title>
<valueType>
<v8:Type>v8:StandardPeriod</v8:Type>
</valueType>
<value xsi:type="v8:StandardPeriod">
<v8:variant xsi:type="v8:StandardPeriodVariant">LastMonth</v8:variant>
</value>
<useRestriction>false</useRestriction>
<denyIncompleteValues>true</denyIncompleteValues>
<use>Always</use>
</parameter>
<parameter>
<name>НачалоПериода</name>
<title xsi:type="v8:LocalStringType">
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Начало периода</v8:content>
</v8:item>
</title>
<valueType>
<v8:Type>xs:dateTime</v8:Type>
<v8:DateQualifiers>
<v8:DateFractions>DateTime</v8:DateFractions>
</v8:DateQualifiers>
</valueType>
<value xsi:type="xs:dateTime">0001-01-01T00:00:00</value>
<useRestriction>true</useRestriction>
<expression>&amp;Период.ДатаНачала</expression>
</parameter>
<parameter>
<name>КонецПериода</name>
<title xsi:type="v8:LocalStringType">
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Конец периода</v8:content>
</v8:item>
</title>
<valueType>
<v8:Type>xs:dateTime</v8:Type>
<v8:DateQualifiers>
<v8:DateFractions>DateTime</v8:DateFractions>
</v8:DateQualifiers>
</valueType>
<value xsi:type="xs:dateTime">0001-01-01T00:00:00</value>
<useRestriction>true</useRestriction>
<expression>&amp;Период.ДатаОкончания</expression>
</parameter>
<parameter>
<name>Организация</name>
<valueType>
<v8:Type xmlns:d5p1="http://v8.1c.ru/8.1/data/enterprise/current-config">d5p1:CatalogRef.Организации</v8:Type>
</valueType>
<value xsi:nil="true"/>
<useRestriction>false</useRestriction>
</parameter>
<parameter>
<name>СписокДокументов</name>
<valueType>
<v8:Type xmlns:d5p1="http://v8.1c.ru/8.1/data/enterprise/current-config">d5p1:CatalogRef.Документы</v8:Type>
</valueType>
<useRestriction>false</useRestriction>
<valueListAllowed>true</valueListAllowed>
</parameter>
<parameter>
<name>СлужебныйПар</name>
<valueType>
<v8:Type>xs:string</v8:Type>
<v8:StringQualifiers>
<v8:Length>0</v8:Length>
<v8:AllowedLength>Variable</v8:AllowedLength>
</v8:StringQualifiers>
</valueType>
<value xsi:type="xs:string"/>
<useRestriction>true</useRestriction>
<availableAsField>false</availableAsField>
</parameter>
<parameter>
<name>ПорядокОкругления</name>
<valueType>
<v8:Type xmlns:d5p1="http://v8.1c.ru/8.1/data/enterprise/current-config">d5p1:EnumRef.Округления</v8:Type>
</valueType>
<value xsi:type="dcscor:DesignTimeValue">Перечисление.Округления.Окр1</value>
<useRestriction>false</useRestriction>
<availableValue>
<value xsi:type="dcscor:DesignTimeValue">Перечисление.Округления.Окр1_00</value>
<presentation xsi:type="v8:LocalStringType">
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>руб. коп</v8:content>
</v8:item>
</presentation>
</availableValue>
<availableValue>
<value xsi:type="dcscor:DesignTimeValue">Перечисление.Округления.Окр1</value>
<presentation xsi:type="v8:LocalStringType">
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>руб.</v8:content>
</v8:item>
</presentation>
</availableValue>
</parameter>
<settingsVariant>
<dcsset:name>Основной</dcsset:name>
<dcsset:presentation xsi:type="v8:LocalStringType">
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Основной</v8:content>
</v8:item>
</dcsset:presentation>
<dcsset:settings xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows">
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
<dcsset:item xsi:type="dcsset:StructureItemGroup">
<dcsset:order>
<dcsset:item xsi:type="dcsset:OrderItemAuto"/>
</dcsset:order>
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
</dcsset:item>
</dcsset:settings>
</settingsVariant>
</DataCompositionSchema>
@@ -0,0 +1,13 @@
{
"dataSets": [{ "name": "Тест", "query": "ВЫБРАТЬ * ИЗ Справочник.Номенклатура", "fields": ["Цена: decimal(15,2)", "Закупка: decimal(15,2)"] }],
"calculatedFields": ["Маржа = Цена - Закупка", "Наценка [Наценка, %]: decimal(10,2) = Маржа / Закупка * 100", "Служебное: string = \"\" #noField #noFilter #noGroup #noOrder"],
"totalFields": ["Цена: Сумма", "Маржа: Сумма(Цена - Закупка)"],
"parameters": [
"Период [Отчетный период]: StandardPeriod = LastMonth @autoDates",
"Организация: CatalogRef.Организации",
"СписокДокументов: CatalogRef.Документы @valueList",
"СлужебныйПар: string @hidden",
{ "name": "ПорядокОкругления", "type": "EnumRef.Округления", "value": "Перечисление.Округления.Окр1", "availableValues": [{ "value": "Перечисление.Округления.Окр1_00", "presentation": "руб. коп" }, { "value": "Перечисление.Округления.Окр1", "presentation": "руб." }] }
],
"settingsVariants": [{ "name": "Основной", "settings": { "selection": ["Auto"], "structure": [{ "selection": ["Auto"], "order": ["Auto"] }] } }]
}
@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8"?>
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dataSource>
<name>ИсточникДанных1</name>
<dataSourceType>Local</dataSourceType>
</dataSource>
<dataSet xsi:type="DataSetQuery">
<name>Тест</name>
<field xsi:type="DataSetFieldField">
<dataPath>Период</dataPath>
<field>Период</field>
<valueType>
<v8:Type>xs:dateTime</v8:Type>
<v8:DateQualifiers>
<v8:DateFractions>Date</v8:DateFractions>
</v8:DateQualifiers>
</valueType>
</field>
<field xsi:type="DataSetFieldFolder">
<dataPath>СальдоНаНачалоПериода</dataPath>
<title xsi:type="v8:LocalStringType">
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Сальдо на начало периода</v8:content>
</v8:item>
</title>
</field>
<field xsi:type="DataSetFieldField">
<dataPath>СальдоНаНачалоПериода.Дт</dataPath>
<field>СальдоНаНачалоПериода.Дт</field>
<valueType>
<v8:Type>xs:decimal</v8:Type>
<v8:NumberQualifiers>
<v8:Digits>15</v8:Digits>
<v8:FractionDigits>2</v8:FractionDigits>
<v8:AllowedSign>Any</v8:AllowedSign>
</v8:NumberQualifiers>
</valueType>
</field>
<field xsi:type="DataSetFieldField">
<dataPath>СальдоНаНачалоПериода.Кт</dataPath>
<field>СальдоНаНачалоПериода.Кт</field>
<valueType>
<v8:Type>xs:decimal</v8:Type>
<v8:NumberQualifiers>
<v8:Digits>15</v8:Digits>
<v8:FractionDigits>2</v8:FractionDigits>
<v8:AllowedSign>Any</v8:AllowedSign>
</v8:NumberQualifiers>
</valueType>
</field>
<dataSource>ИсточникДанных1</dataSource>
<query>ВЫБРАТЬ * ИЗ Справочник.Сотрудники</query>
</dataSet>
<settingsVariant>
<dcsset:name>Основной</dcsset:name>
<dcsset:presentation xsi:type="v8:LocalStringType">
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Основной</v8:content>
</v8:item>
</dcsset:presentation>
<dcsset:settings xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows">
<dcsset:item xsi:type="dcsset:StructureItemGroup">
<dcsset:groupItems>
<dcsset:item xsi:type="dcsset:GroupItemAuto"/>
</dcsset:groupItems>
<dcsset:item xsi:type="dcsset:StructureItemGroup">
<dcsset:groupItems>
<dcsset:item xsi:type="dcsset:GroupItemField">
<dcsset:field>Период</dcsset:field>
<dcsset:groupType>Items</dcsset:groupType>
<dcsset:periodAdditionType>None</dcsset:periodAdditionType>
<dcsset:periodAdditionBegin xsi:type="xs:dateTime">0001-01-01T00:00:00</dcsset:periodAdditionBegin>
<dcsset:periodAdditionEnd xsi:type="xs:dateTime">0001-01-01T00:00:00</dcsset:periodAdditionEnd>
</dcsset:item>
</dcsset:groupItems>
</dcsset:item>
</dcsset:item>
</dcsset:settings>
</settingsVariant>
</DataCompositionSchema>
@@ -0,0 +1 @@
{ "dataSets": [{ "name": "Тест", "query": "ВЫБРАТЬ * ИЗ Справочник.Сотрудники", "fields": ["Период: date", { "field": "СальдоНаНачалоПериода", "folder": true, "title": "Сальдо на начало периода" }, "СальдоНаНачалоПериода.Дт: decimal(15,2)", "СальдоНаНачалоПериода.Кт: decimal(15,2)"] }], "settingsVariants": [{ "name": "Основной", "settings": { "structure": "Auto > Период" } }] }
@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8"?>
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dataSource>
<name>ИсточникДанных1</name>
<dataSourceType>Local</dataSourceType>
</dataSource>
<dataSet xsi:type="DataSetQuery">
<name>ПродажиПоПериодам</name>
<field xsi:type="DataSetFieldField">
<dataPath>Номенклатура</dataPath>
<field>Номенклатура</field>
<role>
<dcscom:dimension>true</dcscom:dimension>
</role>
<valueType>
<v8:Type xmlns:d5p1="http://v8.1c.ru/8.1/data/enterprise/current-config">d5p1:CatalogRef.Номенклатура</v8:Type>
</valueType>
</field>
<field xsi:type="DataSetFieldField">
<dataPath>Количество</dataPath>
<field>Количество</field>
<valueType>
<v8:Type>xs:decimal</v8:Type>
<v8:NumberQualifiers>
<v8:Digits>15</v8:Digits>
<v8:FractionDigits>3</v8:FractionDigits>
<v8:AllowedSign>Any</v8:AllowedSign>
</v8:NumberQualifiers>
</valueType>
</field>
<field xsi:type="DataSetFieldField">
<dataPath>Сумма</dataPath>
<field>Сумма</field>
<valueType>
<v8:Type>xs:decimal</v8:Type>
<v8:NumberQualifiers>
<v8:Digits>15</v8:Digits>
<v8:FractionDigits>2</v8:FractionDigits>
<v8:AllowedSign>Any</v8:AllowedSign>
</v8:NumberQualifiers>
</valueType>
</field>
<dataSource>ИсточникДанных1</dataSource>
<query>ВЫБРАТЬ
Номенклатура,
Количество,
Сумма
ИЗ
РегистрНакопления.Продажи
ГДЕ
Период МЕЖДУ &amp;НачалоПериода И &amp;КонецПериода</query>
</dataSet>
<settingsVariant>
<dcsset:name>Основной</dcsset:name>
<dcsset:presentation xsi:type="v8:LocalStringType">
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Основной</v8:content>
</v8:item>
</dcsset:presentation>
<dcsset:settings xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows">
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
<dcsset:item xsi:type="dcsset:StructureItemGroup">
<dcsset:order>
<dcsset:item xsi:type="dcsset:OrderItemAuto"/>
</dcsset:order>
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
</dcsset:item>
</dcsset:settings>
</settingsVariant>
</DataCompositionSchema>
@@ -0,0 +1,8 @@
ВЫБРАТЬ
Номенклатура,
Количество,
Сумма
ИЗ
РегистрНакопления.Продажи
ГДЕ
Период МЕЖДУ &НачалоПериода И &КонецПериода
@@ -0,0 +1 @@
{ "dataSets": [{ "name": "ПродажиПоПериодам", "query": "@decompiled-ПродажиПоПериодам.sql", "fields": ["Номенклатура: CatalogRef.Номенклатура @dimension", "Количество: decimal(15,3)", "Сумма: decimal(15,2)"] }], "settingsVariants": [{ "name": "Основной", "settings": { "selection": ["Auto"], "structure": [{ "selection": ["Auto"], "order": ["Auto"] }] } }] }
@@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8"?>
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dataSource>
<name>ИсточникДанных1</name>
<dataSourceType>Local</dataSourceType>
</dataSource>
<dataSet xsi:type="DataSetQuery">
<name>Запрос1</name>
<field xsi:type="DataSetFieldField">
<dataPath>Имя</dataPath>
<field>Имя</field>
<valueType>
<v8:Type>xs:string</v8:Type>
<v8:StringQualifiers>
<v8:Length>0</v8:Length>
<v8:AllowedLength>Variable</v8:AllowedLength>
</v8:StringQualifiers>
</valueType>
</field>
<dataSource>ИсточникДанных1</dataSource>
<query>ВЫБРАТЬ Номенклатура.Наименование КАК Имя ИЗ Справочник.Номенклатура КАК Номенклатура</query>
</dataSet>
<dataSet xsi:type="DataSetObject">
<name>Журнал</name>
<field xsi:type="DataSetFieldField">
<dataPath>Сообщение</dataPath>
<field>Сообщение</field>
<valueType>
<v8:Type>xs:string</v8:Type>
<v8:StringQualifiers>
<v8:Length>150</v8:Length>
<v8:AllowedLength>Variable</v8:AllowedLength>
</v8:StringQualifiers>
</valueType>
</field>
<field xsi:type="DataSetFieldField">
<dataPath>Уровень</dataPath>
<field>Уровень</field>
<valueType>
<v8:Type>xs:string</v8:Type>
<v8:StringQualifiers>
<v8:Length>0</v8:Length>
<v8:AllowedLength>Variable</v8:AllowedLength>
</v8:StringQualifiers>
</valueType>
</field>
<dataSource>ИсточникДанных1</dataSource>
<objectName>ЖурналОшибок</objectName>
</dataSet>
<dataSet xsi:type="DataSetUnion">
<name>Объединение</name>
<item xsi:type="DataSetQuery">
<name>Часть1</name>
<field xsi:type="DataSetFieldField">
<dataPath>Поле</dataPath>
<field>Поле</field>
<valueType>
<v8:Type>xs:decimal</v8:Type>
<v8:NumberQualifiers>
<v8:Digits>10</v8:Digits>
<v8:FractionDigits>2</v8:FractionDigits>
<v8:AllowedSign>Any</v8:AllowedSign>
</v8:NumberQualifiers>
</valueType>
</field>
<dataSource>ИсточникДанных1</dataSource>
<query>ВЫБРАТЬ 1 КАК Поле</query>
</item>
<item xsi:type="DataSetQuery">
<name>Часть2</name>
<field xsi:type="DataSetFieldField">
<dataPath>Поле</dataPath>
<field>Поле</field>
<valueType>
<v8:Type>xs:decimal</v8:Type>
<v8:NumberQualifiers>
<v8:Digits>10</v8:Digits>
<v8:FractionDigits>2</v8:FractionDigits>
<v8:AllowedSign>Any</v8:AllowedSign>
</v8:NumberQualifiers>
</valueType>
</field>
<dataSource>ИсточникДанных1</dataSource>
<query>ВЫБРАТЬ 2 КАК Поле</query>
</item>
</dataSet>
<settingsVariant>
<dcsset:name>Основной</dcsset:name>
<dcsset:presentation xsi:type="v8:LocalStringType">
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Основной</v8:content>
</v8:item>
</dcsset:presentation>
<dcsset:settings xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows">
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
<dcsset:item xsi:type="dcsset:StructureItemGroup">
<dcsset:order>
<dcsset:item xsi:type="dcsset:OrderItemAuto"/>
</dcsset:order>
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
</dcsset:item>
</dcsset:settings>
</settingsVariant>
</DataCompositionSchema>
@@ -0,0 +1,8 @@
{
"dataSets": [
{ "name": "Запрос1", "query": "ВЫБРАТЬ Номенклатура.Наименование КАК Имя ИЗ Справочник.Номенклатура КАК Номенклатура", "fields": ["Имя: string"] },
{ "name": "Журнал", "objectName": "ЖурналОшибок", "fields": ["Сообщение: string(150)", "Уровень: string"] },
{ "name": "Объединение", "items": [{ "name": "Часть1", "query": "ВЫБРАТЬ 1 КАК Поле", "fields": ["Поле: decimal(10,2)"] }, { "name": "Часть2", "query": "ВЫБРАТЬ 2 КАК Поле", "fields": ["Поле: decimal(10,2)"] }] }
],
"settingsVariants": [{ "name": "Основной", "settings": { "selection": ["Auto"], "structure": [{ "selection": ["Auto"], "order": ["Auto"] }] } }]
}
@@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?>
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dataSource>
<name>ИсточникДанных1</name>
<dataSourceType>Local</dataSourceType>
</dataSource>
<dataSet xsi:type="DataSetQuery">
<name>Тест</name>
<field xsi:type="DataSetFieldField">
<dataPath>СегментНоменклатуры</dataPath>
<field>СегментНоменклатуры</field>
<valueType>
<v8:Type xmlns:d5p1="http://v8.1c.ru/8.1/data/enterprise/current-config">d5p1:CatalogRef.СегментыНоменклатуры</v8:Type>
</valueType>
<inputParameters>
<dcscor:item>
<dcscor:parameter>ПараметрыВыбора</dcscor:parameter>
<dcscor:value xsi:type="dcscor:ChoiceParameters">
<dcscor:item>
<dcscor:choiceParameter>Отбор.СпособФормирования</dcscor:choiceParameter>
<dcscor:value xsi:type="dcscor:DesignTimeValue">Перечисление.СпособыФормированияСегментов.ФормироватьВручную</dcscor:value>
<dcscor:value xsi:type="dcscor:DesignTimeValue">Перечисление.СпособыФормированияСегментов.ПериодическиОбновлять</dcscor:value>
</dcscor:item>
</dcscor:value>
</dcscor:item>
</inputParameters>
</field>
<field xsi:type="DataSetFieldField">
<dataPath>Сотрудник</dataPath>
<field>Сотрудник</field>
<valueType>
<v8:Type xmlns:d5p1="http://v8.1c.ru/8.1/data/enterprise/current-config">d5p1:CatalogRef.Сотрудники</v8:Type>
</valueType>
<inputParameters>
<dcscor:item>
<dcscor:parameter>СвязиПараметровВыбора</dcscor:parameter>
<dcscor:value xsi:type="dcscor:ChoiceParameterLinks">
<dcscor:item>
<dcscor:choiceParameter>Отбор.ТекущаяОрганизация</dcscor:choiceParameter>
<dcscor:value>Организация</dcscor:value>
<dcscor:mode xmlns:d8p1="http://v8.1c.ru/8.1/data/enterprise" xsi:type="d8p1:LinkedValueChangeMode">Clear</dcscor:mode>
</dcscor:item>
<dcscor:item>
<dcscor:choiceParameter>Отбор.ТекущееПодразделение</dcscor:choiceParameter>
<dcscor:value>Подразделение</dcscor:value>
<dcscor:mode xmlns:d8p1="http://v8.1c.ru/8.1/data/enterprise" xsi:type="d8p1:LinkedValueChangeMode">Clear</dcscor:mode>
</dcscor:item>
</dcscor:value>
</dcscor:item>
</inputParameters>
</field>
<field xsi:type="DataSetFieldField">
<dataPath>ПростоЕА</dataPath>
<field>ПростоЕА</field>
<valueType>
<v8:Type xmlns:d5p1="http://v8.1c.ru/8.1/data/enterprise/current-config">d5p1:CatalogRef.Сотрудники</v8:Type>
</valueType>
<inputParameters>
<dcscor:item>
<dcscor:parameter>ПараметрыВыбора</dcscor:parameter>
<dcscor:value xsi:type="dcscor:ChoiceParameters"/>
</dcscor:item>
<dcscor:item>
<dcscor:use>false</dcscor:use>
<dcscor:parameter>БыстрыйВыбор</dcscor:parameter>
<dcscor:value xsi:type="xs:boolean">true</dcscor:value>
</dcscor:item>
</inputParameters>
</field>
<dataSource>ИсточникДанных1</dataSource>
<query>ВЫБРАТЬ * ИЗ Справочник.Сотрудники</query>
</dataSet>
<settingsVariant>
<dcsset:name>Основной</dcsset:name>
<dcsset:presentation xsi:type="v8:LocalStringType">
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Основной</v8:content>
</v8:item>
</dcsset:presentation>
<dcsset:settings xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows">
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
<dcsset:item xsi:type="dcsset:StructureItemGroup">
<dcsset:order>
<dcsset:item xsi:type="dcsset:OrderItemAuto"/>
</dcsset:order>
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
</dcsset:item>
</dcsset:settings>
</settingsVariant>
</DataCompositionSchema>
@@ -0,0 +1,14 @@
{
"dataSets": [
{
"name": "Тест",
"query": "ВЫБРАТЬ * ИЗ Справочник.Сотрудники",
"fields": [
{ "field": "СегментНоменклатуры", "type": "CatalogRef.СегментыНоменклатуры", "inputParameters": [{ "parameter": "ПараметрыВыбора", "choiceParameters": [{ "name": "Отбор.СпособФормирования", "values": ["Перечисление.СпособыФормированияСегментов.ФормироватьВручную", "Перечисление.СпособыФормированияСегментов.ПериодическиОбновлять"] }] }] },
{ "field": "Сотрудник", "type": "CatalogRef.Сотрудники", "inputParameters": [{ "parameter": "СвязиПараметровВыбора", "choiceParameterLinks": [{ "name": "Отбор.ТекущаяОрганизация", "value": "Организация", "mode": "Clear" }, { "name": "Отбор.ТекущееПодразделение", "value": "Подразделение", "mode": "Clear" }] }] },
{ "field": "ПростоЕА", "type": "CatalogRef.Сотрудники", "inputParameters": [{ "parameter": "ПараметрыВыбора", "choiceParameters": [] }, { "parameter": "БыстрыйВыбор", "use": false, "value": true }] }
]
}
],
"settingsVariants": [{ "name": "Основной", "settings": { "selection": ["Auto"], "structure": [{ "selection": ["Auto"], "order": ["Auto"] }] } }]
}
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dataSource>
<name>ИсточникДанных1</name>
<dataSourceType>Local</dataSourceType>
</dataSource>
<dataSet xsi:type="DataSetQuery">
<name>Тест</name>
<field xsi:type="DataSetFieldField">
<dataPath>ВидРасчета</dataPath>
<field>ВидРасчета</field>
<orderExpression>
<dcscom:expression>ЕстьNULL(ВидРасчета.Порядок, 10000)</dcscom:expression>
<dcscom:orderType>Asc</dcscom:orderType>
<dcscom:autoOrder>false</dcscom:autoOrder>
</orderExpression>
<valueType>
<v8:Type xmlns:d5p1="http://v8.1c.ru/8.1/data/enterprise/current-config">d5p1:CatalogRef.ВидыРасчета</v8:Type>
</valueType>
</field>
<dataSource>ИсточникДанных1</dataSource>
<query>ВЫБРАТЬ * ИЗ Справочник.ВидыРасчета</query>
</dataSet>
<settingsVariant>
<dcsset:name>Основной</dcsset:name>
<dcsset:presentation xsi:type="v8:LocalStringType">
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Основной</v8:content>
</v8:item>
</dcsset:presentation>
<dcsset:settings xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows">
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
<dcsset:item xsi:type="dcsset:StructureItemGroup">
<dcsset:order>
<dcsset:item xsi:type="dcsset:OrderItemAuto"/>
</dcsset:order>
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
</dcsset:item>
</dcsset:settings>
</settingsVariant>
</DataCompositionSchema>
@@ -0,0 +1,4 @@
{
"dataSets": [{ "name": "Тест", "query": "ВЫБРАТЬ * ИЗ Справочник.ВидыРасчета", "fields": [{ "field": "ВидРасчета", "type": "CatalogRef.ВидыРасчета", "orderExpression": { "expression": "ЕстьNULL(ВидРасчета.Порядок, 10000)", "orderType": "Asc", "autoOrder": false } }] }],
"settingsVariants": [{ "name": "Основной", "settings": { "selection": ["Auto"], "structure": [{ "selection": ["Auto"], "order": ["Auto"] }] } }]
}
@@ -0,0 +1,93 @@
<?xml version="1.0" encoding="UTF-8"?>
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dataSource>
<name>ИсточникДанных1</name>
<dataSourceType>Local</dataSourceType>
</dataSource>
<dataSet xsi:type="DataSetQuery">
<name>Тест</name>
<field xsi:type="DataSetFieldField">
<dataPath>Период</dataPath>
<field>Период</field>
<role>
<dcscom:periodNumber>1</dcscom:periodNumber>
<dcscom:periodType>Main</dcscom:periodType>
</role>
<valueType>
<v8:Type>xs:dateTime</v8:Type>
<v8:DateQualifiers>
<v8:DateFractions>Date</v8:DateFractions>
</v8:DateQualifiers>
</valueType>
</field>
<field xsi:type="DataSetFieldField">
<dataPath>Контрагент</dataPath>
<field>Контрагент</field>
<role>
<dcscom:dimension>true</dcscom:dimension>
<dcscom:required>true</dcscom:required>
</role>
<valueType>
<v8:Type xmlns:d5p1="http://v8.1c.ru/8.1/data/enterprise/current-config">d5p1:CatalogRef.Контрагенты</v8:Type>
</valueType>
</field>
<field xsi:type="DataSetFieldField">
<dataPath>СуммаНач</dataPath>
<field>СуммаНач</field>
<role>
<dcscom:balance>true</dcscom:balance>
<dcscom:balanceGroupName>Сумма</dcscom:balanceGroupName>
<dcscom:balanceType>OpeningBalance</dcscom:balanceType>
</role>
<valueType>
<v8:Type>xs:decimal</v8:Type>
<v8:NumberQualifiers>
<v8:Digits>15</v8:Digits>
<v8:FractionDigits>2</v8:FractionDigits>
<v8:AllowedSign>Any</v8:AllowedSign>
</v8:NumberQualifiers>
</valueType>
</field>
<field xsi:type="DataSetFieldField">
<dataPath>СуммаКон</dataPath>
<field>СуммаКон</field>
<role>
<dcscom:balance>true</dcscom:balance>
<dcscom:balanceGroupName>Сумма</dcscom:balanceGroupName>
<dcscom:balanceType>ClosingBalance</dcscom:balanceType>
</role>
<valueType>
<v8:Type>xs:decimal</v8:Type>
<v8:NumberQualifiers>
<v8:Digits>15</v8:Digits>
<v8:FractionDigits>2</v8:FractionDigits>
<v8:AllowedSign>Any</v8:AllowedSign>
</v8:NumberQualifiers>
</valueType>
</field>
<dataSource>ИсточникДанных1</dataSource>
<query>ВЫБРАТЬ * ИЗ РегистрНакопления.Остатки</query>
</dataSet>
<settingsVariant>
<dcsset:name>Основной</dcsset:name>
<dcsset:presentation xsi:type="v8:LocalStringType">
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Основной</v8:content>
</v8:item>
</dcsset:presentation>
<dcsset:settings xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows">
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
<dcsset:item xsi:type="dcsset:StructureItemGroup">
<dcsset:order>
<dcsset:item xsi:type="dcsset:OrderItemAuto"/>
</dcsset:order>
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
</dcsset:item>
</dcsset:settings>
</settingsVariant>
</DataCompositionSchema>
@@ -0,0 +1,4 @@
{
"dataSets": [{ "name": "Тест", "query": "ВЫБРАТЬ * ИЗ РегистрНакопления.Остатки", "fields": ["Период: date @period", "Контрагент: CatalogRef.Контрагенты @dimension @required", "СуммаНач: decimal(15,2) @balance balanceGroupName=Сумма balanceType=OpeningBalance", "СуммаКон: decimal(15,2) @balance balanceGroupName=Сумма balanceType=ClosingBalance"] }],
"settingsVariants": [{ "name": "Основной", "settings": { "selection": ["Auto"], "structure": [{ "selection": ["Auto"], "order": ["Auto"] }] } }]
}
@@ -0,0 +1,173 @@
<?xml version="1.0" encoding="UTF-8"?>
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dataSource>
<name>ИсточникДанных1</name>
<dataSourceType>Local</dataSourceType>
</dataSource>
<dataSet xsi:type="DataSetQuery">
<name>Тест</name>
<field xsi:type="DataSetFieldField">
<dataPath>Наименование</dataPath>
<field>Наименование</field>
</field>
<field xsi:type="DataSetFieldField">
<dataPath>Количество</dataPath>
<field>Количество</field>
<valueType>
<v8:Type>xs:decimal</v8:Type>
<v8:NumberQualifiers>
<v8:Digits>15</v8:Digits>
<v8:FractionDigits>2</v8:FractionDigits>
<v8:AllowedSign>Any</v8:AllowedSign>
</v8:NumberQualifiers>
</valueType>
</field>
<field xsi:type="DataSetFieldField">
<dataPath>Организация</dataPath>
<field>Организация</field>
<role>
<dcscom:dimension>true</dcscom:dimension>
</role>
<valueType>
<v8:Type xmlns:d5p1="http://v8.1c.ru/8.1/data/enterprise/current-config">d5p1:CatalogRef.Организации</v8:Type>
</valueType>
</field>
<field xsi:type="DataSetFieldField">
<dataPath>Служебное</dataPath>
<field>Служебное</field>
<useRestriction>
<field>true</field>
<condition>true</condition>
</useRestriction>
<valueType>
<v8:Type>xs:string</v8:Type>
<v8:StringQualifiers>
<v8:Length>0</v8:Length>
<v8:AllowedLength>Variable</v8:AllowedLength>
</v8:StringQualifiers>
</valueType>
</field>
<field xsi:type="DataSetFieldField">
<dataPath>СтрокаФикс</dataPath>
<field>СтрокаФикс</field>
<valueType>
<v8:Type>xs:string</v8:Type>
<v8:StringQualifiers>
<v8:Length>50</v8:Length>
<v8:AllowedLength>Fixed</v8:AllowedLength>
</v8:StringQualifiers>
</valueType>
</field>
<field xsi:type="DataSetFieldField">
<dataPath>Положительное</dataPath>
<field>Положительное</field>
<valueType>
<v8:Type>xs:decimal</v8:Type>
<v8:NumberQualifiers>
<v8:Digits>10</v8:Digits>
<v8:FractionDigits>0</v8:FractionDigits>
<v8:AllowedSign>Nonnegative</v8:AllowedSign>
</v8:NumberQualifiers>
</valueType>
</field>
<field xsi:type="DataSetFieldField">
<dataPath>Дата1</dataPath>
<field>Дата1</field>
<valueType>
<v8:Type>xs:dateTime</v8:Type>
<v8:DateQualifiers>
<v8:DateFractions>Date</v8:DateFractions>
</v8:DateQualifiers>
</valueType>
</field>
<field xsi:type="DataSetFieldField">
<dataPath>СПояснением</dataPath>
<field>СПояснением</field>
<title xsi:type="v8:LocalStringType">
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Поле с пояснением</v8:content>
</v8:item>
</title>
<valueType>
<v8:Type>xs:decimal</v8:Type>
<v8:NumberQualifiers>
<v8:Digits>15</v8:Digits>
<v8:FractionDigits>2</v8:FractionDigits>
<v8:AllowedSign>Any</v8:AllowedSign>
</v8:NumberQualifiers>
</valueType>
<appearance>
<dcscor:item xsi:type="dcsset:SettingsParameterValue">
<dcscor:parameter>ГоризонтальноеПоложение</dcscor:parameter>
<dcscor:value xsi:type="v8ui:HorizontalAlign">Right</dcscor:value>
</dcscor:item>
<dcscor:item xsi:type="dcsset:SettingsParameterValue">
<dcscor:parameter>МинимальнаяШирина</dcscor:parameter>
<dcscor:value xsi:type="xs:decimal">80</dcscor:value>
</dcscor:item>
</appearance>
</field>
<field xsi:type="DataSetFieldField">
<dataPath>Многоязычное</dataPath>
<field>Многоязычное</field>
<title xsi:type="v8:LocalStringType">
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Русский</v8:content>
</v8:item>
<v8:item>
<v8:lang>en</v8:lang>
<v8:content>English</v8:content>
</v8:item>
</title>
<valueType>
<v8:Type>xs:string</v8:Type>
<v8:StringQualifiers>
<v8:Length>0</v8:Length>
<v8:AllowedLength>Variable</v8:AllowedLength>
</v8:StringQualifiers>
</valueType>
</field>
<field xsi:type="DataSetFieldField">
<dataPath>СоставноеПоле</dataPath>
<field>СоставноеПоле</field>
<valueType>
<v8:Type xmlns:d5p1="http://v8.1c.ru/8.1/data/enterprise/current-config">d5p1:CatalogRef.Организации</v8:Type>
<v8:Type xmlns:d5p1="http://v8.1c.ru/8.1/data/enterprise/current-config">d5p1:CatalogRef.Валюты</v8:Type>
</valueType>
</field>
<field xsi:type="DataSetFieldField">
<dataPath>СВыражениемПредставления</dataPath>
<field>СВыражениемПредставления</field>
<valueType>
<v8:Type xmlns:d5p1="http://v8.1c.ru/8.1/data/enterprise/current-config">d5p1:CatalogRef.Номенклатура</v8:Type>
</valueType>
<presentationExpression>Представление(СВыражениемПредставления)</presentationExpression>
</field>
<dataSource>ИсточникДанных1</dataSource>
<query>ВЫБРАТЬ * ИЗ Справочник.Номенклатура</query>
</dataSet>
<settingsVariant>
<dcsset:name>Основной</dcsset:name>
<dcsset:presentation xsi:type="v8:LocalStringType">
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Основной</v8:content>
</v8:item>
</dcsset:presentation>
<dcsset:settings xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows">
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
<dcsset:item xsi:type="dcsset:StructureItemGroup">
<dcsset:order>
<dcsset:item xsi:type="dcsset:OrderItemAuto"/>
</dcsset:order>
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
</dcsset:item>
</dcsset:settings>
</settingsVariant>
</DataCompositionSchema>
@@ -0,0 +1,22 @@
{
"dataSets": [
{
"name": "Тест",
"query": "ВЫБРАТЬ * ИЗ Справочник.Номенклатура",
"fields": [
"Наименование",
"Количество: decimal(15,2)",
"Организация: CatalogRef.Организации @dimension",
"Служебное: string #noField #noFilter",
"СтрокаФикс: string(50,fix)",
"Положительное: decimal(10,nonneg)",
"Дата1: date",
{ "field": "СПояснением", "title": "Поле с пояснением", "type": "decimal(15,2)", "appearance": { "ГоризонтальноеПоложение": "Right", "МинимальнаяШирина": "80" } },
{ "field": "Многоязычное", "title": { "ru": "Русский", "en": "English" }, "type": "string" },
{ "field": "СоставноеПоле", "type": ["CatalogRef.Организации", "CatalogRef.Валюты"] },
{ "field": "СВыражениемПредставления", "type": "CatalogRef.Номенклатура", "presentationExpression": "Представление(СВыражениемПредставления)" }
]
}
],
"settingsVariants": [{ "name": "Основной", "settings": { "selection": ["Auto"], "structure": [{ "selection": ["Auto"], "order": ["Auto"] }] } }]
}
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dataSource>
<name>ИсточникДанных1</name>
<dataSourceType>Local</dataSourceType>
</dataSource>
<dataSet xsi:type="DataSetQuery">
<name>НаборДанных1</name>
<field xsi:type="DataSetFieldField">
<dataPath>Наименование</dataPath>
<field>Наименование</field>
</field>
<dataSource>ИсточникДанных1</dataSource>
<query>ВЫБРАТЬ Номенклатура.Наименование КАК Наименование ИЗ Справочник.Номенклатура КАК Номенклатура</query>
</dataSet>
<settingsVariant>
<dcsset:name>Основной</dcsset:name>
<dcsset:presentation xsi:type="v8:LocalStringType">
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Основной</v8:content>
</v8:item>
</dcsset:presentation>
<dcsset:settings xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows">
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
<dcsset:item xsi:type="dcsset:StructureItemGroup">
<dcsset:order>
<dcsset:item xsi:type="dcsset:OrderItemAuto"/>
</dcsset:order>
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
</dcsset:item>
</dcsset:settings>
</settingsVariant>
</DataCompositionSchema>

Some files were not shown because too many files have changed in this diff Show More