Как добавить фильтрацию по дате в нетиповом отчете 1С: от быстрого обхода до правильной оптимизации

Программист 1С v8.3 (Управляемые формы) 1C:Бухгалтерия Бухгалтерский учет IT и автоматизация бизнеса
← На главную

При работе с унаследованным кодом программисты 1С часто сталкиваются с самописными отчетами, построенными на ручном обходе выборок и выводе областей макета. В таких отчетах стандартные механизмы фильтрации платформы не работают автоматически, и добавление простого отбора (например, по плановой дате погашения задолженности) превращается в нетривиальную задачу. Рассмотрим детально, как решить эту проблему, исправить логические ошибки исходного кода и оптимизировать отчет.

Анализ исходной ситуации и проблемы

Проанализируем структуру отчета, представленного автором темы. Логика работы алгоритма выглядит следующим образом:

  1. Программа вручную перебирает справочники контрагентов и договоров в циклах.
  2. Для каждого договора в цикле выполняется запрос к виртуальной таблице остатков РегистрБухгалтерии.Хозрасчетный.Остатки для получения текущего долга по счету 62.
  3. Если у контрагента обнаружен долг, программа выполняет второй запрос в цикле к документам РеализацияТоваровУслуг, сортируя их по убыванию даты, чтобы распределить сумму долга по документам методом FIFO.
  4. В процессе вывода строк рассчитывается плановая дата погашения по формуле: ДатаРеализации + СрокОплатыДоговора.

Перед нами стоит задача: дать возможность руководству задавать на форме период (интервал дат), чтобы в отчет попадали только те документы долга, плановая дата погашения которых входит в этот период.

Мы разберем два решения: быстрое (компромиссное), которое позволяет оперативно решить задачу "здесь и сейчас", и правильное (архитектурное), устраняющее критические ошибки производительности.

---

Решение 1. Быстрая фильтрация при выводе строк макета

Если отчет нужно сдать срочно, а времени на переписывание чужого сложного кода нет, можно применить программный фильтр непосредственно в цикле вывода данных в табличный документ. Суть метода заключается в том, что мы рассчитываем плановую дату погашения прямо в коде и выводим область макета только тогда, когда дата попадает в заданный пользователем интервал.

Выполним это решение по шагам:

Шаг 1. Добавление реквизитов на форму отчета

Добавим на форму отчета два новых реквизита: ДатаНачалаФильтра и ДатаОкончанияФильтра (тип Дата, состав даты — только дата). Вынесем их на форму, чтобы пользователь мог задавать интересующий его период.

Шаг 2. Модификация алгоритма вывода областей

В исходном коде вывод детальных записей по документам происходит в цикле обхода выборки реализации товаров и услуг. Найдем место, где заполняются параметры области ОблДокум и выводится строка:


// Исходный фрагмент кода:
ОблДокум.Параметры.ПлановаяДатаПогашения = формат(СтрДокДоговор.ДокументДолга.Дата+(ТекСтрДоговор.СрокЧисло * 24*60*60),"ДЛФ=Д");
ОблДокум.Параметры.НепрДолг = СтрДокДоговор.ДокументСумма - СтрДокДоговор.ПросроченоСумма;
Таб.Вывести(ОблДокум);

Заметим, что в предложенном на форуме варианте (сообщение 24) содержится синтаксическая и логическая ошибка в условиях сравнения дат. Напишем корректное, отказоустойчивое условие с учетом возможных незаполненных дат начала или окончания периода:


// Оптимизированный и исправленный код вывода с фильтрацией:
ПлановаяДата = СтрДокДоговор.ДокументДолга.Дата + (ТекСтрДоговор.SрокЧисло * 24 * 60 * 60);

// Приводим границы периода к началу и концу дня для корректного сравнения
ГраницаНачала = ?(ЗначениеЗаполнено(ДатаНачалаФильтра), НачалоДня(ДатаНачалаФильтра), '00010101');
ГраницаОкончания = ?(ЗначениеЗаполнено(ДатаОкончанияФильтра), КонецДня(ДатаОкончанияФильтра), '39991231');

Если (ПлановаяДата >= ГраницаНачала) И (ПлановаяДата <= ГраницаОкончания) Тогда
    ОблДокум.Параметры.ПлановаяДатаПогашения = Формат(ПлановаяДата, "ДЛФ=Д");
    ОблДокум.Параметры.НепрДолг = СтрДокДоговор.ДокументСумма - СтрДокДоговор.ПросроченоСумма;
    Таб.Вывести(ОблДокум);
КонецЕсли;

Плюсы этого подхода: решение внедряется за 5 минут, не требует переписывания сложных запросов и гарантирует, что в отчет визуально попадут только нужные строки.

Минусы: итоговые показатели (общий долг, общая просрочка), которые рассчитываются по всей таблице значений ТабРезВсе, не пересчитаются автоматически, так как мы скрыли строки только на этапе вывода в макет, оставив их в исходной таблице данных.

---

Решение 2. Перенос расчета и фильтрации в запрос СУБД (Правильный подход)

Чтобы итоговые суммы в отчете сходились, фильтрацию необходимо делать на уровне базы данных — до того, как данные попадут в алгоритмы распределения долга. Для этого нам нужно научить СУБД самостоятельно рассчитывать плановую дату погашения.

В языке запросов 1С для работы с датами предусмотрена функция ДОБАВИТЬКДАТЕ(). Мы можем рассчитать плановую дату платежа, добавив к дате документа количество дней отсрочки, указанное в договоре контрагента.

Модернизируем второй запрос отчета, добавив вычисление плановой даты и наложив на нее условия:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	РеализацияТоваровУслуг.Ссылка КАК Ссылка,
    |	РеализацияТоваровУслуг.Дата КАК Дата,
    |	ДОБАВИТЬКДАТЕ(РеализацияТоваровУслуг.Дата, ДЕНЬ, ЕСТЬNULL(РеализацияТоваровУслуг.ДоговорКонтрагента.СрокОплаты, 30)) КАК ПлановаяДатаПогашения
    |ИЗ
    |	Документ.РеализацияТоваровУслуг КАК РеализацияТоваровУслуг
    |ГДЕ
    |	РеализацияТоваровУслуг.ДоговорКонтрагента = &ДоговорКонтрагента
    |	И РеализацияТоваровУслуг.Проведен = ИСТИНА
    |	И (ДОБАВИТЬКДАТЕ(РеализацияТоваровУслуг.Дата, ДЕНЬ, ЕСТЬNULL(РеализацияТоваровУслуг.ДоговорКонтрагента.СрокОплаты, 30)) МЕЖДУ &ДатаНач И &ДатаКон)
    |УПОРЯДОЧИТЬ ПО
    |	Дата УБЫВ";

Запрос.УстановитьПараметр("ДоговорКонтрагента", СпрДоговор.Ссылка);
Запрос.УстановитьПараметр("ДатаНач", ?(ЗначениеЗаполнено(ДатаНачалаФильтра), НачалоДня(ДатаНачалаФильтра), '00010101'));
Запрос.УстановитьПараметр("ДатаКон", ?(ЗначениеЗаполнено(ДатаОкончанияФильтра), КонецДня(ДатаОкончанияФильтра), '39991231'));

В данном запросе мы использовали конструкцию ЕСТЬNULL(..., 30). Она подстрахует алгоритм, если в договоре контрагента не заполнен срок оплаты — в этом случае система по умолчанию применит отсрочку в 30 дней.

---

Критическая проблема исходного кода: "Запросы в цикле"

Проанализировав исходный код отчета, нельзя не отметить критическую архитектурную ошибку: выполнение запросов внутри циклов обхода справочников. С ростом информационной базы этот отчет будет работать экспоненциально медленнее, вызывая серьезные просадки производительности и блокировки СУБД. На каждый договор выполняется минимум два обращения к базе данных.

Выясним, как должен выглядеть правильный алгоритм получения таких данных:

Примерная схема единого оптимизированного запроса для получения данных по дебиторской задолженности выглядит так:


ВЫБРАТЬ
    ХозрасчетныйОстатки.Субконто1 КАК Контрагент,
    ХозрасчетныйОстатки.Субконто2 КАК ДоговорКонтрагента,
    ХозрасчетныйОстатки.СуммаОстатокДт КАК СуммаДолга
ПОМЕСТИТЬ ВТ_ОстаткиДолгов
ИЗ
    РегистрБухгалтерии.Хозрасчетный.Остатки(&Период, Счет = &Счет62, , Организация = &Организация) КАК ХозрасчетныйОстатки
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ_ОстаткиДолгов.Контрагент КАК Контрагент,
    ВТ_ОстаткиДолгов.ДоговорКонтрагента КАК ДоговорКонтрагента,
    ВТ_ОстаткиДолгов.СуммаДолга КАК СуммаДолга,
    Реализация.Ссылка КАК ДокументРеализации,
    Реализация.Дата КАК ДатаРеализации,
    Реализация.СуммаДокумента КАК СуммаДокумента,
    ДОБАВИТЬКДАТЕ(Реализация.Дата, ДЕНЬ, ЕСТЬNULL(ВТ_ОстаткиДолгов.ДоговорКонтрагента.СрокОплаты, 30)) КАК ПлановаяДатаПогашения
ИЗ
    ВТ_ОстаткиДолгов КАК ВТ_ОстаткиДолгов
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг КАК Реализация
        ПО ВТ_ОстаткиДолгов.Контрагент = Реализация.Контрагент
        И ВТ_ОстаткиДолгов.ДоговорКонтрагента = Реализация.ДоговорКонтрагента
ГДЕ
    Реализация.Проведен = ИСТИНА
    И ДОБАВИТЬКДАТЕ(Реализация.Дата, ДЕНЬ, ЕСТЬNULL(ВТ_ОстаткиДолгов.ДоговорКонтрагента.СрокОплаты, 30)) МЕЖДУ &ДатаНач И &ДатаКон
УПОРЯДОЧИТЬ ПО
    Контрагент,
    ДоговорКонтрагента,
    ДатаРеализации УБЫВ

Выполнив этот запрос один раз, мы получаем готовую плоскую таблицу данных. Далее на стороне сервера нам останется лишь обойти эту таблицу в один проход, распределить остаток долга по документам (FIFO) и вывести результат в макет. Скорость работы отчета увеличится в десятки и сотни раз.

---

Альтернатива: перевод отчета на Систему компоновки данных (СКД)

Наиболее современным и технологичным решением в экосистеме 1С является отказ от ручного вывода макетов в пользу СКД. Переведя данный отчет на схему компоновки данных, вы получаете следующие преимущества:

← На главную