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