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