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