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