При разработке сложных отчетов в системе компоновки данных (СКД) программисты часто сталкиваются с необходимостью программного формирования результата. Типичный сценарий: получение данных запросом, их дополнительная обработка в коде (например, расчет сложных показателей, которые трудно реализовать в языке запросов) и последующая передача обратно в СКД через внешний набор данных. В процессе реализации такой логики в процедуре ПриКомпоновкеРезультата легко допустить ряд ошибок, которые приводят к неинформативным сообщениям системы или некорректным данным в отчете. Рассмотрим детально, как избежать этих проблем и оптимизировать ваш код.
Одной из самых частых причин падения кода на строке запрос.Выполнить() является передача некорректного значения в параметры виртуальных таблиц, таких как Остатки или ОстаткиИОбороты. В приведенном примере автор использует параметр &ПарамПамПам для определения периода получения остатков. Ошибка возникает в следующих случаях:
ДатаОтчета попадет Неопределено. Виртуальная таблица не может обработать такой тип.Дата(1, 1, 1) иногда может интерпретироваться СУБД неоднозначно.Дата, Граница или МоментВремени. Если в настройках по ошибке выбран другой тип, запрос выдаст исключение.Проанализируем ситуацию с передачей момента времени. Для получения максимально точных остатков на конкретную секунду, включая документы этой секунды, рекомендуется использовать объект Граница. Разберем на примере, как правильно подготовить параметр:
Если ТипЗнч(ДатаОтчета) = Тип("Дата") Тогда
// Если нужно включить документы, проведенные в ту же секунду
ПараметрПериод = Новый Граница(ДатаОтчета, ВидГраницы.Включая);
Иначе
ПараметрПериод = ДатаОтчета; // Ожидаем, что там уже может быть Граница или Дата
КонецЕсли;
Запрос.УстановитьПараметр("ПарамПамПам", ПараметрПериод);
Автор кода использует обращение к элементам настроек по индексу: ПользовательскиеНастройки.Элементы[0]. Это критическая ошибка, которую нельзя допускать в промышленной разработке. Пользовательские настройки — это динамическая коллекция. Если пользователь в режиме «1С:Предприятие» добавит новый отбор, изменит порядок полей или группировок, индексы сместятся. Параметр, который был под номером 0, может стать номером 5, и ваш код начнет получать Склад вместо Даты, что и приведет к несоответствию типов.
Рассмотрим, как правильно находить нужный параметр по его идентификатору. В схеме компоновки данных у каждого параметра, вынесенного в пользовательские настройки, есть свойство ИдентификаторПользовательскойНастройки. Мы должны использовать именно его для поиска.
Функция ПолучитьЗначениеНастройки(КомпоновщикНастроек, ИмяПараметра)
Схема = ЭтотОбъект.СхемаКомпоновкиДанных;
ПараметрСхемы = Схема.Параметры.Найти(ИмяПараметра);
Если ПараметрСхемы = Неопределено Тогда
Возврат Неопределено;
КонецЕсли;
ИдНастройки = ПараметрСхемы.ИдентификаторПользовательскойНастройки;
// Ищем в пользовательских настройках по идентификатору
ЭлементНастройки = КомпоновщикНастроек.ПользовательскиеНастройки.Элементы.Найти(ИдНастройки);
Если ЭлементНастройки <> Неопределено Тогда
Возврат ЭлементНастройки.Значение;
КонецЕсли;
Возврат Неопределено;
КонецФункции
Используя такой подход, мы гарантируем, что получим именно Склад или Дату, независимо от того, как пользователь перемешал настройки в интерфейсе.
Разберем алгоритмическую ошибку в цикле обработки таблицы значений. В исходном коде используется конструкция Для каждого Строка Из ТЗ Цикл ... ТЗ.Удалить(Строка). Это классическая «ловушка» для начинающих. При удалении элемента из коллекции, по которой идет итерация, индексы оставшихся элементов сдвигаются. В результате итератор пропускает следующую за удаленной строку, и часть данных остается необработанной.
Выясним причину, почему это опасно: если условию удаления соответствуют две идущие подряд строки, вторая строка никогда не будет проверена. Рассмотрим два правильных способа удаления:
Способ 1: Обход с конца (рекомендуется)
КолличествоСтрок = ТЗ.Количество();
Для Индекс = 1 По КолличествоСтрок Цикл
ТекущийИндекс = КолличествоСтрок - Индекс;
Строка = ТЗ[ТекущийИндекс];
Если Процент > ПроцентСрокаГодности Тогда
ТЗ.Удалить(ТекущийИндекс);
КонецЕсли;
КонецЦикла;
Способ 2: Использование массива для удаления
СтрокиНаУдаление = Новый Массив;
Для Каждого Строка Из ТЗ Цикл
Если Процент > ПроцентСрокаГодности Тогда
СтрокиНаУдаление.Добавить(Строка);
КонецЕсли;
КонецЦикла;
Для Каждого Строка Из СтрокиНаУдаление Цикл
ТЗ.Удалить(Строка);
КонецЦикла;
При передаче таблицы значений в ПроцессорКомпоновкиДанных через структуру ВнешниеНаборыДанных, крайне важно следить за именованием полей. Рассмотрим основные правила:
ТаблицеЗначений должны в точности (включая регистр букв в некоторых версиях платформы) совпадать с именами полей, описанными в Наборе данных - объект внутри схемы СКД.ОписаниеТипов). Если в колонке будет перемешка типов, СКД может выдать ошибку при попытке применения функций агрегации (СУММА) или группировки.ВнешниеНаборыДанных.Вставить("ИмяНабора", ТЗ) должен совпадать с именем объекта, указанным в поле «Имя объекта» в настройках набора данных СКД.Проанализируем ситуацию с расчетом срока годности. Автор парсит строку Серия.Номер, чтобы получить дату. Это очень хрупкое решение. Если формат номера серии изменится, отчет упадет. Правильнее хранить дату изготовления в отдельном реквизите серии. Однако, если расчет все же необходим, его часто можно перенести в Вычисляемые поля самой СКД.
Почему СКД лучше ручного перебора? Во-первых, это быстрее работает на больших объемах данных. Во-вторых, СКД автоматически применяет группировки и итоги к вычисляемым полям. В-третьих, это позволяет избежать сложной инициализации процессора вывода вручную, если логика фильтрации может быть описана формулой.
Например, выражение для вычисляемого поля может выглядеть так (если мы все же парсим строку):
ДОБАВИТЬКДАТЕ(ДАТАВРЕМЯ(2000, 1, 1), "ГОД", ЧИСЛО(ПОДСТРОКА(СерияНомер, 3, 2))). Хотя лучше использовать готовые реквизиты объекта.
Если вы программно перехватываете компоновку в ПриКомпоновкеРезультата, не забывайте про ДанныеРасшифровки. Если в процедуре инициализируется ПроцессорКомпоновкиДанных, объект ДанныеРасшифровки должен быть передан во все звенья цепи. Если вы просто выводите результат в табличный документ, убедитесь, что вы не потеряли связь с формой отчета, иначе пользователь не сможет «провалиться» в документ или справочник из отчета.
Посмотрим на правильную финальную последовательность действий:
// 1. Создаем макет компоновки
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, КомпоновщикНастроек.ПолучитьНастройки(), ДанныеРасшифровки);
// 2. Инициализируем процессор компоновки
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, ВнешниеНаборыДанных, ДанныеРасшифровки, Истина);
// 3. Выводим результат
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
ПроцессорВывода.Вывести(ПроцессорКомпоновки);
Обратите внимание на четвертый параметр в методе Инициализировать — Истина (ВозможностьИспользованияВнешнихФункций). Он необходим, если в вашей схеме используются функции общих модулей или сложные выражения, требующие доступа к контексту сервера.
Соблюдение этих правил превращает «трэш от 1С» в стабильно работающий и легко поддерживаемый инструмент управленческой отчетности.