Формат в двух местах, пустые ресурсы и схема, которую EDT пропускает: грабли СКД, невидимые в конструкторе
Это продолжение серии про разработку на БП КОРП в EDT с ИИ-агентом (первая статья: «Claude Code + EDT на БП КОРП: три месяца, 328 коммитов и 40 правил»). Здесь только отчеты на СКД. В наших блоках их около тридцати, часть схем собрана скриптами-генераторами, часть поправлена правкой Template.dcs мимо конструктора. Каждый случай ниже стоил минимум одного обновления тестовой базы и одного «а почему тут пусто» от пользователя. Имена отчетов обезличены, префиксы заменены на ПРЕФ_.
Формат числа живет в двух местах
Число знаков после запятой в схеме задается на двух уровнях:
- У поля набора данных (
<field>, блок<appearance>). Действует во всех вариантах сразу. - В условном оформлении варианта настроек (правило с пустым отбором). Действует только в своем варианте, но перебивает формат поля.
Пока вариантов нет или в их оформлении нет правил формата, работает первый уровень, и про второй никто не вспоминает. Проблема начинается, когда в отчете уже есть готовые варианты с правилами. Мы поменяли полю КоличествоВСутки формат на ЧДЦ=3 в наборе данных, обновили базу и увидели прежние два знака. Поле оставалось в общем правиле варианта вместе с остальными числовыми полями, и правило с ЧДЦ=2 выигрывало.

Порядок действий при смене формата одному полю:
- Поменять формат у поля набора данных.
- Пройти условное оформление каждого варианта.
- Если поле перечислено в правиле с другим форматом, убрать его оттуда и добавить ему отдельное правило с нужным форматом и пустым отбором.
Так выглядит отдельное правило в варианте:
<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>ЧЦ=15; ЧДЦ=3</v8:content>
</v8:item>
</dcscor:value>
</dcscor:item>
</dcsset:appearance>
</dcsset:item>
Итоги (<totalField>) своего формата не имеют и берут его у поля с тем же dataPath. Отдельно форматировать ресурс не нужно.
В наших схемах встречаются два синтаксиса значения формата, оба валидны. В одних отчетах это локализованная строка, как выше, в других простая строка:
<dcscor:value xsi:type="xs:string">ЧДЦ=2</dcscor:value>
Отсюда практическое следствие: формат в файле ищите по подстроке ЧДЦ=, а не по конкретной разметке, иначе половину полей не найдете.
Порядок внутри блока поля
<appearance> должен стоять последним в блоке <field>: после <dataPath>, <field>, <title> и <valueType>, если он есть.
<field xsi:type="DataSetFieldField">
<dataPath>ПлановыйРасходМ3НаРяд</dataPath>
<field>ПлановыйРасходМ3НаРяд</field>
<title xsi:type="v8:LocalStringType">
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Плановый расход, м3, на ряд</v8:content>
</v8:item>
</title>
<appearance>
<dcscor:item xsi:type="dcsset:SettingsParameterValue">
<dcscor:parameter>Формат</dcscor:parameter>
<dcscor:value xsi:type="xs:string">ЧДЦ=3</dcscor:value>
</dcscor:item>
</appearance>
</field>
Вставка между <dataPath> и <field> ломает соответствие XSD. Если правите форматы скриптом, там две ловушки. Первая: внутренний <field>Имя</field> содержит подстроку </field>, и наивный поиск конца блока срабатывает на нем. Конец блока ищется условием «строка, где </field> стоит одна»: ^[ \t]*</field>[ \t]*$. Вторая: <dataPath>Имя</dataPath> есть не только у полей набора, но и в <totalField>. Обрабатывать надо только блоки, открытые тегом <field xsi:type=.
Сколько знаков: два, а удельным величинам три
У нас правило простое: во всех отчетах блоков числа выводятся с двумя знаками после запятой, включая целочисленные поля вроде количества или флага превышения порога. Единый вид чисел во всех отчетах оказался важнее, чем «12» вместо «12,00». Это решение заказчика, а не платформы.
Исключение: удельные величины, которые дальше суммируются или умножаются. Округление такого поля до двух знаков накапливается по строкам и уводит итог. Пример из отчета по поливу: расход на ряд 4,1084747 суммируется по 59 рядам в расход крана.
| Знаков | Расчет | Итог | Факт |
|---|---|---|---|
| 2 | 4,11 × 59 | 242,49 | 242,4 |
| 3 | 4,108 × 59 | 242,37 | 242,4 |
Пользователь видит в строках 4,11, умножает в уме на 59 и не сходится с итогом. С тремя знаками расхождение уходит в сотые. Три знака у нас получили расход на ряд, норма расхода на гектар (умножается на площадь) и среднесуточное количество (усредняется по периодам). Все остальное, включая площади, стоимости и температуры, осталось с двумя.
Ресурс, который нельзя складывать
Отчет по работам: объем работы в соседней колонке с единицей измерения. Объем объявлен ресурсом с Сумма(ОбъемРаботы). На группировке «Работа» все верно, единица одна. На группировке «Сад» в итог попадают штуки, часы и гектары, и пользователь видит по саду 459 345,10 непонятно чего.
Первая мысль: снять поле с ресурсов. Тогда на группировках колонка пустеет совсем, а в варианте, где нет детальных записей, поле исчезает отовсюду. Рабочий прием другой: ресурс отдает значение только там, где единица одна.
ВЫБОР КОГДА КОЛИЧЕСТВО(Различные ЕдиницаИзмеренияРаботы) > 1
ТОГДА NULL
ИНАЧЕ Сумма(ОбъемРаботы)
КОНЕЦ
Выражение пишется в <expression> у <totalField>. Где разрез по единице есть, получается обычная сумма. Где единицы смешались, пусто вместо бессмысленного числа. Структуру вариантов трогать не нужно.
Прием не наш. Тот же признак КОЛИЧЕСТВО(Различные Валюта) > 1 стоит в отчетах БИТ.ФИНАНС (план-факт по заявкам) для сумм в разных валютах. Там при нескольких валютах итог подменяется разбивкой по валютам. Валюта там играет ту же роль, что у нас единица измерения.
Где искать такой дефект: вариант, в котором ресурс выводится, а поля-разреза среди группировок нет. Проверять все варианты схемы. В нашем отчете из трех вариантов два были корректны, ошибка жила только в сводном.

Ресурс в детальных записях пуст
Вторая сторона того же. Поле, объявленное ресурсом, считается только на группировках. Если структура варианта представляет собой плоский список детальных записей, колонка ресурса пуста во всех строках. Поле в выборке есть, формат задан, в отчете пусто. Лечится либо группировкой, либо снятием поля с ресурсов. Ресурсом стоит объявлять только то, что осмысленно складывать.
Параметр есть, а в шапке его нет
Параметр из dataParameters варианта попадает в быстрые настройки формы, только если у него есть <dcsset:userSettingID> с GUID. Без него отчет работает, но полей ввода в шапке нет, и задать период или отбор пользователь может только через «Настройки».
<dcsset:dataParameters>
<dcscor:item xsi:type="dcsset:SettingsParameterValue">
<dcscor:parameter>ГодС</dcscor:parameter>
<dcscor:value xsi:type="xs:decimal">0</dcscor:value>
<dcsset:userSettingID>a7f2c8b1-4e3d-4f9a-9b2c-1d4e5f607182</dcsset:userSettingID>
</dcscor:item>
</dcsset:dataParameters>
При переносе схемы из эталона сверяйте наличие userSettingID у каждого параметра с уже принятым отчетом, а не только состав параметров.
Рядом стоит <use>Always</use> у необязательного параметра-отбора. В запросе такой параметр написан с пустой веткой:
ГДЕ
(&СадОтбор = ЗНАЧЕНИЕ(Справочник.ПРЕФ_Сады.ПустаяСсылка)
ИЛИ Краны.Сад = &СадОтбор)
Без use=Always пользователь снимает флажок отбора, и отчет падает с «Не задано значение параметра».
valueListAllowed только под «В (&Параметр)»
<valueListAllowed>true</valueListAllowed> разрешает передать в параметр список значений. Это нужно только параметрам, которые в запросе стоят под В (&Параметр). У нас флаг стоял у границ диапазона лет, которые используются так:
ГДЕ
Р.День >= ДОБАВИТЬКДАТЕ(ДАТАВРЕМЯ(1, 1, 1), ГОД, &ГодС - 1)
При восстановлении настроек платформа подставляла такому параметру пустой список значений, и запрос падал: «Нельзя сравнивать поля неограниченной длины и поля несовместимых типов». Модуль отчета у нас подменял пустое значение, но это не спасает, когда схему компонуют мимо объекта отчета или когда пользователь сам ввел список. Исправление: valueListAllowed=false у границ.
Виртуальные таблицы с параметром периода
В двух отчетах по урожаю конструкции Остатки(&КонецПериода) регистра накопления и СрезПоследних(&КонецПериода, ) регистра сведений падали при компоновке:
Несоответствие типов (Параметр номер "1")
Общее у этих схем: НачалоПериода и КонецПериода в них не самостоятельные параметры, а выражения от стандартного периода, например КонецПериода(&Период.ДатаОкончания, "День"). При этом Обороты(&НачалоПериода, &КонецПериода, Месяц, ...) регистра бухгалтерии в тех же схемах работал. Почему остатки и срез последних ведут себя иначе, мы до конца не выясняли. Решение нашлось быстрее, чем причина: физическая таблица.
Остатки считаются суммой по виду движения с отбором по периоду и ИМЕЮЩИЕ:
ВЫБРАТЬ
Движения.Контейнер КАК Контейнер,
Движения.Место КАК Место,
СУММА(ВЫБОР
КОГДА Движения.ВидДвижения = ЗНАЧЕНИЕ(ВидДвиженияНакопления.Приход)
ТОГДА 1
ИНАЧЕ -1
КОНЕЦ) КАК Остаток
ПОМЕСТИТЬ ВТОстатки
ИЗ
РегистрНакопления.ПРЕФ_ОстаткиПоМестам КАК Движения
ГДЕ
Движения.Период <= &КонецПериода
И Движения.Активность
СГРУППИРОВАТЬ ПО
Движения.Контейнер,
Движения.Место
ИМЕЮЩИЕ
СУММА(ВЫБОР
КОГДА Движения.ВидДвижения = ЗНАЧЕНИЕ(ВидДвиженияНакопления.Приход)
ТОГДА 1
ИНАЧЕ -1
КОНЕЦ) <> 0
Срез последних: МАКСИМУМ(Период) по ключу во временную таблицу и обратное соединение с регистром по ключу и периоду. Не забудьте Движения.Активность: виртуальная таблица учитывает ее сама, физическая нет.
Схема чистая в EDT, а база ее не загружает
Схема, собранная скриптом, прошла revalidate_objects и get_project_errors без замечаний и упала при выгрузке в базу:
Исключение XDTO при чтении Template.xml
НачалоСвойства: title Тип: {http://www.w3.org/2001/XMLSchema}anyType
Причина: генератор писал <title> без типа. EDT такой узел пропускает, платформа на нем падает:
<title><v8:item><v8:lang>ru</v8:lang><v8:content>Текст</v8:content></v8:item></title>
Правильно с типом:
<title xsi:type="v8:LocalStringType"><v8:item>...</v8:item></title>
EDT проверяет модель, а не соответствие XDTO-схеме платформы. Поэтому новый узел мы больше не сочиняем, а копируем соседний такой же из этого же файла вместе со всеми атрибутами. Перед выгрузкой сверяем, что у однотипных узлов одинаковый набор атрибутов:
grep -o '<title[^>]*>' Template.dcs | sort | uniq -c
Разнобой в выводе означает найденный дефект. Эту проверку, проверку <title> без xsi:type и проверку порядка <appearance> в поле мы свели в скрипт check_dcs_nodes.py. Он приложен к первой статье серии (ссылка в начале).
EDT не видит правку файла
Вторая сторона правки мимо конструктора: EDT не знает, что Template.dcs изменился на диске. Без F5 (Refresh) на проекте в базу уйдет старая схема, и вы будете искать ошибку в правке, которой там нет.
Мелочь из той же области. Имя варианта в списке вариантов БСП задается в модуле менеджера отчета (НастройкиВарианта.Наименование). Без него все варианты показываются синонимом отчета и выглядят одинаково.
Проверка схемы из файла до обновления базы
Обновление серверной базы занимает минуты, и каждая ошибка в схеме стоит еще одного цикла. Поэтому схему мы проверяем прямо из файла, до выгрузки: читаем Template.dcs в объект СхемаКомпоновкиДанных и компонуем каждый вариант с тестовым периодом. Код выполняется в тестовой базе через внешнюю обработку, которая исполняет переданный текст.
Чтение = Новый ЧтениеXML;
Чтение.ОткрытьФайл(ПутьКФайлу); // ...\Templates\ОсновнаяСхемаКомпоновкиДанных\Template.dcs
Схема = СериализаторXDTO.ПрочитатьXML(Чтение, Тип("СхемаКомпоновкиДанных"));
Чтение.Закрыть();
Для Каждого Вариант Из Схема.ВариантыНастроек Цикл
Попытка
Компоновщик = Новый КомпоновщикНастроекКомпоновкиДанных;
Компоновщик.Инициализировать(Новый ИсточникДоступныхНастроекКомпоновкиДанных(Схема));
Компоновщик.ЗагрузитьНастройки(Вариант.Настройки);
Компоновщик.Восстановить();
Для Каждого П Из Компоновщик.Настройки.ПараметрыДанных.Элементы Цикл
Если Строка(П.Параметр) = "Период" Тогда
П.Значение = Новый СтандартныйПериод(Дата(2025, 8, 1), Дата(2026, 7, 31, 23, 59, 59));
П.Использование = Истина;
КонецЕсли;
КонецЦикла;
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
Макет = КомпоновщикМакета.Выполнить(Схема, Компоновщик.ПолучитьНастройки(), , ,
Тип("ГенераторМакетаКомпоновкиДанных"));
Процессор = Новый ПроцессорКомпоновкиДанных;
Процессор.Инициализировать(Макет);
Док = Новый ТабличныйДокумент;
Вывод = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
Вывод.УстановитьДокумент(Док);
Вывод.Вывести(Процессор);
Лог.Добавить(Вариант.Имя + ": строк " + Док.ВысотаТаблицы);
Исключение
Лог.Добавить(Вариант.Имя + ": ОШИБКА " + КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
КонецЦикла;
Критерий: все варианты компонуются и выводят строки. Пустой результат за период без данных допустим, исключение означает дефект схемы. Чтение через СериализаторXDTO заодно ловит ту самую ошибку XDTO с <title>, только раньше и без обновления базы. По этому же выводу удобно смотреть время каждого варианта.
Тяжелые пакеты запросов мы профилируем тем же способом, но по частям: пакет режется по ;, каждая временная таблица замеряется отдельно. Так в отчете контроля данных нашлись соединения с табличными частями документов по неиндексированным полям. После замены их на индексированные временные таблицы за период отчет перестал идти 300 секунд.
Сводка и чеклист
| Грабля | Симптом | Решение |
|---|---|---|
| Формат в поле и в оформлении варианта | правка формата поля не видна | убрать поле из правила варианта, дать ему свое правило |
<appearance> не последним в поле |
схема не соответствует XSD | порядок dataPath, field, title, valueType, appearance |
| Два знака у удельной величины | строки не сходятся с итогом | три знака полям, которые суммируются и умножаются |
| Ресурс по разным единицам | итог по группе складывает штуки и часы | КОЛИЧЕСТВО(Различные Единица) > 1 дает NULL |
| Ресурс в детальных записях | колонка пуста во всех строках | группировка или снятие с ресурсов |
Нет userSettingID |
параметра нет в шапке отчета | GUID у каждого параметра варианта |
Нет use=Always у отбора |
«Не задано значение параметра» | <use>Always</use> и пустая ветка в запросе |
valueListAllowed у границы |
«Нельзя сравнивать поля неограниченной длины...» | флаг только для параметров под В (&...) |
Остатки/СрезПоследних с параметром периода |
«Несоответствие типов (Параметр номер "1")» | физическая таблица |
<title> без xsi:type |
«Исключение XDTO при чтении Template.xml» | копировать соседний узел, сверять атрибуты |
Правка .dcs на диске |
в базе старая схема | F5 перед выгрузкой |
Перед обновлением базы после правки отчета:
- Формат числа поменян и у поля набора, и в правилах всех вариантов.
<appearance>последний в каждом блоке поля.- У каждого ресурса проверено, складываются ли его значения по всем вариантам, где он выводится.
- У каждого параметра варианта есть
userSettingID, у необязательных отборовuse=Always. valueListAllowed=trueтолько у параметров подВ (&...).check_dcs_nodes.pyиgrep | uniq -cпо<title>без замечаний.- F5 на проекте.
- Каждый вариант скомпонован из файла с тестовым периодом.
Общий вывод у меня тот же, что и в статье про роли: у схемы компоновки есть требования, которые не видны в конструкторе и не проверяются EDT, а видны только по тому, как устроены типовые схемы и как ведет себя платформа при загрузке. Поэтому проверку мы перенесли из «после обновления базы» в «до выгрузки», и большую часть этих граблей теперь ловит скрипт, а не пользователь. Если вы находили в СКД похожие вещи, которые конструктор прячет, напишите в комментариях симптом и решение. Хочу дополнить этот список.
Вступайте в нашу телеграмм-группу Инфостарт
Вы можете заказать платную адаптацию этой статьи под ваши задачи на «Бирже заказов».
- 0% комиссии — оплата напрямую исполнителю;
- Исполнители любого масштаба — от отдельных специалистов до команд под проект;
- Прямой обмен контактами между заказчиком и исполнителем;
- Безопасная сделка — при необходимости;
- Рейтинги, кейсы и прозрачная система откликов.