При работе с унаследованным кодом программисты 1С часто сталкиваются с самописными отчетами, построенными на ручном обходе выборок и выводе областей макета. В таких отчетах стандартные механизмы фильтрации платформы не работают автоматически, и добавление простого отбора (например, по плановой дате погашения задолженности) превращается в нетривиальную задачу. Рассмотрим детально, как решить эту проблему, исправить логические ошибки исходного кода и оптимизировать отчет.
Проанализируем структуру отчета, представленного автором темы. Логика работы алгоритма выглядит следующим образом:
РегистрБухгалтерии.Хозрасчетный.Остатки для получения текущего долга по счету 62.РеализацияТоваровУслуг, сортируя их по убыванию даты, чтобы распределить сумму долга по документам методом FIFO.ДатаРеализации + СрокОплатыДоговора.Перед нами стоит задача: дать возможность руководству задавать на форме период (интервал дат), чтобы в отчет попадали только те документы долга, плановая дата погашения которых входит в этот период.
Мы разберем два решения: быстрое (компромиссное), которое позволяет оперативно решить задачу "здесь и сейчас", и правильное (архитектурное), устраняющее критические ошибки производительности.
---
Если отчет нужно сдать срочно, а времени на переписывание чужого сложного кода нет, можно применить программный фильтр непосредственно в цикле вывода данных в табличный документ. Суть метода заключается в том, что мы рассчитываем плановую дату погашения прямо в коде и выводим область макета только тогда, когда дата попадает в заданный пользователем интервал.
Выполним это решение по шагам:
Шаг 1. Добавление реквизитов на форму отчета
Добавим на форму отчета два новых реквизита: ДатаНачалаФильтра и ДатаОкончанияФильтра (тип Дата, состав даты — только дата). Вынесем их на форму, чтобы пользователь мог задавать интересующий его период.
Шаг 2. Модификация алгоритма вывода областей
В исходном коде вывод детальных записей по документам происходит в цикле обхода выборки реализации товаров и услуг. Найдем место, где заполняются параметры области ОблДокум и выводится строка:
// Исходный фрагмент кода:
ОблДокум.Параметры.ПлановаяДатаПогашения = формат(СтрДокДоговор.ДокументДолга.Дата+(ТекСтрДоговор.СрокЧисло * 24*60*60),"ДЛФ=Д");
ОблДокум.Параметры.НепрДолг = СтрДокДоговор.ДокументСумма - СтрДокДоговор.ПросроченоСумма;
Таб.Вывести(ОблДокум);
Заметим, что в предложенном на форуме варианте (сообщение 24) содержится синтаксическая и логическая ошибка в условиях сравнения дат. Напишем корректное, отказоустойчивое условие с учетом возможных незаполненных дат начала или окончания периода:
// Оптимизированный и исправленный код вывода с фильтрацией:
ПлановаяДата = СтрДокДоговор.ДокументДолга.Дата + (ТекСтрДоговор.SрокЧисло * 24 * 60 * 60);
// Приводим границы периода к началу и концу дня для корректного сравнения
ГраницаНачала = ?(ЗначениеЗаполнено(ДатаНачалаФильтра), НачалоДня(ДатаНачалаФильтра), '00010101');
ГраницаОкончания = ?(ЗначениеЗаполнено(ДатаОкончанияФильтра), КонецДня(ДатаОкончанияФильтра), '39991231');
Если (ПлановаяДата >= ГраницаНачала) И (ПлановаяДата <= ГраницаОкончания) Тогда
ОблДокум.Параметры.ПлановаяДатаПогашения = Формат(ПлановаяДата, "ДЛФ=Д");
ОблДокум.Параметры.НепрДолг = СтрДокДоговор.ДокументСумма - СтрДокДоговор.ПросроченоСумма;
Таб.Вывести(ОблДокум);
КонецЕсли;
Плюсы этого подхода: решение внедряется за 5 минут, не требует переписывания сложных запросов и гарантирует, что в отчет визуально попадут только нужные строки.
Минусы: итоговые показатели (общий долг, общая просрочка), которые рассчитываются по всей таблице значений ТабРезВсе, не пересчитаются автоматически, так как мы скрыли строки только на этапе вывода в макет, оставив их в исходной таблице данных.
---
Чтобы итоговые суммы в отчете сходились, фильтрацию необходимо делать на уровне базы данных — до того, как данные попадут в алгоритмы распределения долга. Для этого нам нужно научить СУБД самостоятельно рассчитывать плановую дату погашения.
В языке запросов 1С для работы с датами предусмотрена функция ДОБАВИТЬКДАТЕ(). Мы можем рассчитать плановую дату платежа, добавив к дате документа количество дней отсрочки, указанное в договоре контрагента.
Модернизируем второй запрос отчета, добавив вычисление плановой даты и наложив на нее условия:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| РеализацияТоваровУслуг.Ссылка КАК Ссылка,
| РеализацияТоваровУслуг.Дата КАК Дата,
| ДОБАВИТЬКДАТЕ(РеализацияТоваровУслуг.Дата, ДЕНЬ, ЕСТЬNULL(РеализацияТоваровУслуг.ДоговорКонтрагента.СрокОплаты, 30)) КАК ПлановаяДатаПогашения
|ИЗ
| Документ.РеализацияТоваровУслуг КАК РеализацияТоваровУслуг
|ГДЕ
| РеализацияТоваровУслуг.ДоговорКонтрагента = &ДоговорКонтрагента
| И РеализацияТоваровУслуг.Проведен = ИСТИНА
| И (ДОБАВИТЬКДАТЕ(РеализацияТоваровУслуг.Дата, ДЕНЬ, ЕСТЬNULL(РеализацияТоваровУслуг.ДоговорКонтрагента.СрокОплаты, 30)) МЕЖДУ &ДатаНач И &ДатаКон)
|УПОРЯДОЧИТЬ ПО
| Дата УБЫВ";
Запрос.УстановитьПараметр("ДоговорКонтрагента", СпрДоговор.Ссылка);
Запрос.УстановитьПараметр("ДатаНач", ?(ЗначениеЗаполнено(ДатаНачалаФильтра), НачалоДня(ДатаНачалаФильтра), '00010101'));
Запрос.УстановитьПараметр("ДатаКон", ?(ЗначениеЗаполнено(ДатаОкончанияФильтра), КонецДня(ДатаОкончанияФильтра), '39991231'));
В данном запросе мы использовали конструкцию ЕСТЬNULL(..., 30). Она подстрахует алгоритм, если в договоре контрагента не заполнен срок оплаты — в этом случае система по умолчанию применит отсрочку в 30 дней.
---
Проанализировав исходный код отчета, нельзя не отметить критическую архитектурную ошибку: выполнение запросов внутри циклов обхода справочников. С ростом информационной базы этот отчет будет работать экспоненциально медленнее, вызывая серьезные просадки производительности и блокировки СУБД. На каждый договор выполняется минимум два обращения к базе данных.
Выясним, как должен выглядеть правильный алгоритм получения таких данных:
Примерная схема единого оптимизированного запроса для получения данных по дебиторской задолженности выглядит так:
ВЫБРАТЬ
ХозрасчетныйОстатки.Субконто1 КАК Контрагент,
ХозрасчетныйОстатки.Субконто2 КАК ДоговорКонтрагента,
ХозрасчетныйОстатки.СуммаОстатокДт КАК СуммаДолга
ПОМЕСТИТЬ ВТ_ОстаткиДолгов
ИЗ
РегистрБухгалтерии.Хозрасчетный.Остатки(&Период, Счет = &Счет62, , Организация = &Организация) КАК ХозрасчетныйОстатки
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ_ОстаткиДолгов.Контрагент КАК Контрагент,
ВТ_ОстаткиДолгов.ДоговорКонтрагента КАК ДоговорКонтрагента,
ВТ_ОстаткиДолгов.СуммаДолга КАК СуммаДолга,
Реализация.Ссылка КАК ДокументРеализации,
Реализация.Дата КАК ДатаРеализации,
Реализация.СуммаДокумента КАК СуммаДокумента,
ДОБАВИТЬКДАТЕ(Реализация.Дата, ДЕНЬ, ЕСТЬNULL(ВТ_ОстаткиДолгов.ДоговорКонтрагента.СрокОплаты, 30)) КАК ПлановаяДатаПогашения
ИЗ
ВТ_ОстаткиДолгов КАК ВТ_ОстаткиДолгов
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг КАК Реализация
ПО ВТ_ОстаткиДолгов.Контрагент = Реализация.Контрагент
И ВТ_ОстаткиДолгов.ДоговорКонтрагента = Реализация.ДоговорКонтрагента
ГДЕ
Реализация.Проведен = ИСТИНА
И ДОБАВИТЬКДАТЕ(Реализация.Дата, ДЕНЬ, ЕСТЬNULL(ВТ_ОстаткиДолгов.ДоговорКонтрагента.СрокОплаты, 30)) МЕЖДУ &ДатаНач И &ДатаКон
УПОРЯДОЧИТЬ ПО
Контрагент,
ДоговорКонтрагента,
ДатаРеализации УБЫВ
Выполнив этот запрос один раз, мы получаем готовую плоскую таблицу данных. Далее на стороне сервера нам останется лишь обойти эту таблицу в один проход, распределить остаток долга по документам (FIFO) и вывести результат в макет. Скорость работы отчета увеличится в десятки и сотни раз.
---
Наиболее современным и технологичным решением в экосистеме 1С является отказ от ручного вывода макетов в пользу СКД. Переведя данный отчет на схему компоновки данных, вы получаете следующие преимущества:
ПлановуюДатуПогашения), пользователь сможет перетащить в настройки отбора на пользовательском интерфейсе без написания кода.СтандартныйПериод, и пользователи смогут выбирать интервалы в один клик ("Этот месяц", "Прошлый квартал" и т.д.).