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