При разработке отчетов на Системе Компоновки Данных (СКД) часто возникает задача: вывести сводные данные по менеджерам (например, сумму заказов и сумму выручки), а при нажатии на конкретную цифру "проваливаться" в список документов, которые эту цифру сформировали. При этом для колонки "Сумма заказа" должны открываться документы "Заказ клиента", а для "Выручки" — документы реализации.
В этой статье мы подробно разберем, почему стандартный подход с соединением таблиц часто приводит к ошибкам, как правильно спроектировать запрос и как реализовать "умную" расшифровку ячеек, что в итоге позволит ускорить формирование отчета "Валовая прибыль предприятия".
Рассмотрим ситуацию, описанную автором. Имеется отчет, сгруппированный по Менеджерам. В запросе используются две виртуальные таблицы оборотов: РегистрНакопления.ВыручкаИСебестоимостьПродаж.Обороты и РегистрНакопления.ЗаказыКлиентов.Обороты.
Посмотрим на исходный запрос, который вызывает проблемы (включая потенциальное дублирование данных):
ВЫБРАТЬ
...
ИЗ
РегистрНакопления.ВыручкаИСебестоимостьПродаж.Обороты(...) КАК ВыручкаИСебестоимостьПродажОбороты
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ЗаказыКлиентов.Обороты(...) КАК ЗаказыКлиентовОбороты
ПО ВыручкаИСебестоимостьПродажОбороты.ПериодМесяц = ЗаказыКлиентовОбороты.ПериодМесяц
В чем здесь критическая ошибка?
Соединение двух таблиц с детализацией (в данном случае по ПериодМесяц) через ЛЕВОЕ СОЕДИНЕНИЕ неизбежно приводит к так называемому "Декартову произведению", если в одном месяце у менеджера было несколько продаж и несколько заказов.
Допустим, в январе было:
При соединении по условию "Месяц = Месяц" и "Менеджер = Менеджер" база данных соединит каждую строку заказов с каждой строкой реализаций. В итоге мы получим 2 * 3 = 6 строк. Суммы в отчете задвоятся или утроятся. Именно поэтому отчет показывает некорректные данные, и стандартная расшифровка работает непредсказуемо.
Чтобы избежать дублирования и подготовить почву для корректной расшифровки, нам нужно отказаться от соединений (JOIN) в пользу объединения (UNION ALL). Этот метод актуален и для движения товаров с резервами (СКД по двум регистрам накопления). Мы должны выбрать данные из первой таблицы, затем из второй, и "сложить" их в один набор данных.
Перепишем запрос правильно. Мы будем выбирать отдельно заказы и отдельно продажи, приводя их к единой структуре полей.
ВЫБРАТЬ
ТаблицаПродаж.Менеджер КАК Менеджер,
ТаблицаПродаж.Регистратор КАК Регистратор,
ТаблицаПродаж.СуммаВыручкиОборот КАК СуммаВыручки,
0 КАК СуммаЗаказа,
"Продажа" КАК ТипОперации
ИЗ
РегистрНакопления.ВыручкаИСебестоимостьПродаж.Обороты(&НачалоПериода, &КонецПериода, Авто, ) КАК ТаблицаПродаж
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
ТаблицаЗаказов.Менеджер,
ТаблицаЗаказов.Регистратор,
0, -- СуммаВыручки здесь равна 0
ТаблицаЗаказов.СуммаОборот,
"Заказ"
ИЗ
РегистрНакопления.ЗаказыКлиентов.Обороты(&НачалоПериода, &КонецПериода, Авто, ) КАК ТаблицаЗаказов
Преимущества такого подхода:
Регистратор, которая хранит ссылку либо на Заказ, либо на Реализацию. Это ключевой момент для работы расшифровки.СуммаВыручки и СуммаЗаказа просто суммируются по группировкам (также можно реализовать условие: нет данных — нет столбцов в СКД).Теперь, когда у нас есть правильный запрос, разберем, как сделать так, чтобы при клике на сумму заказа открывались заказы, а на выручку — продажи.
В СКД расшифровка работает контекстно. Если вы кликаете на ячейку в группировке "Менеджер", платформа "знает" только о Менеджере. Она не знает, какой регистратор вы хотите увидеть, потому что в одной строке менеджера их сотни.
Благодаря тому, что мы переписали запрос через ОБЪЕДИНИТЬ ВСЕ, у нас есть поле Регистратор в детальных записях.
Если пользователь дважды кликнет на ячейку с ресурсом (например, Сумма Заказа), платформа предложит стандартное меню выбора поля для расшифровки.
Если пользователь выберет поле Регистратор, СКД автоматически построит отчет, где под выбранным Менеджером покажется список регистраторов. Поскольку мы разнесли суммы по разным колонкам (0 или Значение), то:
Чтобы сделать интерфейс максимально удобным (без лишних вопросов пользователю и лишних строк с нулями), мы перехватим событие клика.
1. В форме отчета создайте обработчик события ОбработкаРасшифровки элемента формы Результат (Табличный документ).
2. В коде модуля формы мы проанализируем, на какую колонку нажал пользователь.
&НаКлиенте
Процедура РезультатОбработкаРасшифровки(Элемент, Расшифровка, СтандартнаяОбработка)
// Получаем данные о том, куда кликнули
Перем Данные;
ЭтотОбъект.ДанныеРасшифровки.Элементы.Получить(Расшифровка).ПолучитьПоля(Данные);
// Ищем поле, по которому кликнули
ВыбраноПолеЗаказов = Ложь;
ВыбраноПолеВыручки = Ложь;
Для Каждого Поле Из Данные Цикл
Если Поле.Поле = "СуммаЗаказа" Тогда
ВыбраноПолеЗаказов = Истина;
Прервать;
ИначеЕсли Поле.Поле = "СуммаВыручки" Тогда
ВыбраноПолеВыручки = Истина;
Прервать;
КонецЕсли;
КонецЦикла;
// Если кликнули не по ресурсам, а по группировке Менеджер - оставляем стандартное поведение
Если НЕ ВыбраноПолеЗаказов И НЕ ВыбраноПолеВыручки Тогда
Возврат;
КонецЕсли;
// Отменяем стандартную обработку, будем делать свою
СтандартнаяОбработка = Ложь;
// Формируем настройки для нового отчета-расшифровки
// Нам нужно "провалиться" до регистратора
ПараметрыОткрытия = Новый Структура;
ПараметрыОткрытия.Вставить("СформироватьПриОткрытии", Истина);
ПараметрыОткрытия.Вставить("Расшифровка", Новый ОписаниеОбработкиРасшифровки(Расшифровка, ЭтотОбъект.ДанныеРасшифровки, , ));
// Здесь мы программно можем добавить группировку "Регистратор" и отбор
// Но проще всего открыть этот же отчет с новым вариантом настроек или
// использовать механизм "ОткрытьЗначение", если мы хотим открыть конкретный список.
// Более простой вариант для начинающих - открыть форму списка документов с отбором
// Но для отчета правильнее модифицировать компоновщик настроек.
// Пример логики "на лету" (упрощенно):
Оповещение = Новый ОписаниеОповещения("ОбработкаВыбораРасшифровки", ЭтотОбъект);
СписокДоступныхДействий = Новый СписокЗначений;
СписокДоступныхДействий.Добавить("Документы", "Показать документы");
ПоказатьВыборИзМеню(Оповещение, СписокДоступныхДействий);
КонецПроцедуры
Для реализации полноценного "проваливания" в список документов внутри отчета, в процедуре обработки часто используют программную модификацию настроек компоновщика.
Рассмотрим алгоритм действий для реализации "умного" перехода:
НастройкиКомпоновкиДанных на основании текущих.Регистратор.ТипОперации = "Заказ" (поле из нашего исправленного запроса).ТипОперации = "Продажа".Главная проблема, с которой столкнулся автор темы, заключалась не столько в механизме расшифровки, сколько в неправильной архитектуре запроса. Использование ЛЕВОЕ СОЕДИНЕНИЕ для оборотных таблиц недопустимо, если нет гарантии уникальности записей в периоде.
Порядок решения задачи:
ОБЪЕДИНИТЬ ВСЕ. Это создаст единый поток данных с полем Регистратор.Регистратор доступно (но не обязательно выбрано в основной группировке).ОбработкаРасшифровки в модуле формы, где программно накладывать отбор на тип документа или специальное поле-признак.