Разработка механизма рассрочки платежей в конфигурациях 1С (особенно в рознице, где критически важна скорость работы системы на кассовых узлах) требует тщательно продуманной архитектуры хранения данных. Нам необходимо не просто хранить график платежей на год вперед, но и оперативно выводить в динамические списки общую задолженность, просроченные платежи и планы на текущий месяц.
Если спроектировать эту систему некорректно, база данных быстро переполнится неактуальными итогами, а динамические списки начнут «зависать» при прокрутке. В этой статье мы подробно разберем, почему классические подходы с регистрами остатков часто не работают, и рассмотрим наиболее эффективные архитектурные паттерны решения этой задачи.
Давайте пошагово проанализируем возможные варианты проектирования системы и выберем лучшее решение для реализации механизма рассрочки.
---
Первое решение, которое часто приходит в голову разработчикам — создать классический регистр накопления остатков (например, ВзаиморасчетыПоРассрочке) и добавить в его измерения реквизит ДатаПлатежа (тип Дата), записывая движения будущей датой платежа.
Рассмотрим подробнее, почему этот подход является фатальным для производительности базы данных в среднесрочной перспективе:
ДатаПлатежа = 15.10.2023), а покупатель фактически принес деньги 20.10.2023, то при проведении оплаты мы обязаны указать в расходе точно такую же дату платежа (15.10.2023). На практике клиенты платят раньше, позже, частями или вообще без привязки к конкретным этапам. Если логика распределения оплаты ошибется хотя бы на секунду или один день, в базе возникнет «разрыв»:
На 15.10.2023: +1000 руб. (Приход, не закрыт)
На 20.10.2023: -1000 руб. (Расход, не закрыт)
_AccumRegTotals) останутся висеть две записи. Через 2–3 года работы база данных переполнится такими «виртуальными» незакрытыми остатками, и любые запросы к регистру начнут выполняться недопустимо медленно.---
Этот паттерн часто применяется в сложных системах бюджетирования. Мы полностью отказываемся от идеи использовать регистр остатков для хранения плановых дат и создаем два оборотных регистра накопления:
Контрагент, Договор (или документ рассрочки), ПериодОплаты (ссылка на справочник!).Сумма.Сумма.Обратите внимание на измерение ПериодОплаты. Нам ни в коем случае нельзя использовать здесь простой тип Дата. Мы создаем вспомогательный справочник (например, ПериодыПланирования с элементами «Октябрь 2023», «Ноябрь 2023» и т.д.). Использование ссылочного типа данных в качестве измерения полностью решает проблему пересчета итогов СУБД, так как для платформы это обычная ссылка, а не временная шкала.
Для получения просроченной задолженности нам потребуется написать запрос, который вычтет фактические обороты из плановых оборотов по всем периодам, начиная с начала ведения учета до текущего момента:
ВЫБРАТЬ
План.Контрагент КАК Контрагент,
План.Договор КАК Договор,
СУММА(План.СуммаОборот) - СУММА(ЕСТЬNULL(Факт.СуммаОборот, 0)) КАК Просрочка
ИЗ
РегистрНакопления.ПланируемыеПлатежи.Обороты(,, , ) КАК План
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ФактическиеПлатежи.Обороты(,, , ) КАК Факт
ПО План.Контрагент = Факт.Contragent
И План.Договор = Факт.Договор
И План.ПериодОплаты = Факт.ПериодОплаты
ГДЕ
План.ПериодОплаты.ДатаОкончания < &ТекущаяДата
СГРУППИРОВАТЬ ПО
План.Контрагент,
План.Договор
ХАВИНГ
СУММА(План.СуммаОборот) - СУММА(ЕСТЬNULL(Факт.СуммаОборот, 0)) > 0
Преимущества подхода: Система абсолютно устойчива к датам проведения документов. Записи плана и факта могут делаться в любой последовательности, итоги никогда не раздуются, а сторнирование записей выполняется простым обратным движением.
Недостатки: Со временем (через 5–7 лет) объем таблицы оборотов вырастет до миллионов записей. Чтобы посчитать просрочку по конкретному контрагенту, системе придется сложить абсолютно весь его исторический план и вычесть весь факт. Для динамических списков это может стать узким местом.
---
Этот вариант является наиболее элегантным и производительным для розничной торговли. Мы разделяем хранение графика и учет реального долга покупателя.
1. Регистр сведений «ГрафикиПлатежей» (непериодический):
Он хранит эталонный график платежей, который создается в момент продажи в рассрочку. Здесь нет движений, это просто плоская таблица.
ДокументПродажи, ДатаПлатежа (тип Дата), НомерЭтапа.СуммаПлатежа.2. Регистр накопления «ВзаиморасчетыСПокупателями» (остатки):
Обычный классический регистр остатков. Он хранит только фактический текущий долг покупателя без привязки к конкретным датам графика. При продаже в рассрочку мы делаем Приход на общую сумму долга. При оплате покупателем — Расход. Этот регистр гарантированно быстро и без проблем закрывается в ноль.
Контрагент, Договор, ДокументПродажи.Сумма.Давайте разберем по шагам, как при такой структуре получить все необходимые автору данные с помощью простых и быстрых запросов.
Это самый простой запрос, который мгновенно отрабатывает по виртуальной таблице остатков:
ВЫБРАТЬ
ВзаиморасчетыОстатки.Контрагент,
ВзаиморасчетыОстатки.ДокументПродажи,
ВзаиморасчетыОстатки.СуммаОстаток КАК ОбщаяЗадолженность
ИЗ
РегистрНакопления.ВзаиморасчетыСПокупателями.Остатки КАК ВзаиморасчетыОстатки
Для получения плановых платежей мы просто обращаемся к нашему плоскому регистру сведений по условию даты платежа:
ВЫБРАТЬ
График.ДокументПродажи,
График.ДатаПлатежа,
График.СуммаПлатежа
ИЗ
РегистрСведений.ГрафикиПлатежей КАК График
ГДЕ
График.ДатаПлатежа МЕЖДУ &НачалоМесяца И &КонецМесяца
А как же рассчитать просрочку? Очень просто. Мы берем общую сумму плановых платежей, которые покупатель должен был совершить к текущему моменту, и сравниваем ее с фактически оставшимся долгом. Если остаток долга больше, чем сумма будущих (еще не наступивших) платежей, то разница и является нашей просрочкой.
Проанализируем этот алгоритм на примере: общий долг по рассрочке был 12 000 руб. на 12 месяцев (по 1000 руб. в месяц). Прошло 3 месяца. Клиент должен был заплатить 3000 руб., а заплатил только 1000 руб. (остаток долга на регистре остатков равен 11 000 руб.).
Сумма будущих платежей (с 4 по 12 месяц) составляет 9 000 руб. Расчет просрочки: ОстатокДолга (11 000) - БудущиеПлатежи (9 000) = 2 000 руб. просрочки.
Выясним, как выглядит такой запрос в 1С:
ВЫБРАТЬ
ВзаиморасчетыОстатки.Контрагент КАК Контрагент,
ВзаиморасчетыОстатки.ДокументПродажи КАК ДокументПродажи,
ВзаиморасчетыОстатки.СуммаОстаток КАК ОстатокДолга,
ЕСТЬNULL(БудущиеПлатежи.СуммаБудущая, 0) КАК СуммаБудущихПлатежей,
ВЫБОР
КОГДА ВзаиморасчетыОстатки.СуммаОстаток > ЕСТЬNULL(БудущиеПлатежи.СуммаБудущая, 0)
ТОГДА ВзаиморасчетыОстатки.СуммаОстаток - ЕСТЬNULL(БудущиеПлатежи.СуммаБудущая, 0)
ИНАЧЕ 0
КОНЕЦ КАК ПросроченнаяЗадолженность
ИЗ
РегистрНакопления.ВзаиморасчетыСПокупателями.Остатки КАК ВзаиморасчетыОстатки
ЛЕВОЕ СОЕДИНЕНИЕ (
ВЫБРАТЬ
График.ДокументПродажи КАК ДокументПродажи,
СУММА(График.СуммаПлатежа) КАК СуммаБудущая
ИЗ
РегистрСведений.ГрафикиПлатежей КАК График
ГДЕ
График.ДатаПлатежа > &ТекущаяДата
СГРУППИРОВАТЬ ПО
График.ДокументПродажи
) КАК БудущиеПлатежи
ПО ВзаиморасчетыОстатки.ДокументПродажи = БудущиеПлатежи.ДокументПродажи
Этот запрос является чрезвычайно быстрым. Он не перебирает всю историю продаж и оплат за 5 лет. Он берет только текущие действующие остатки взаиморасчетов (которых всегда относительно немного) и соединяет их с будущими записями графика платежей. Данное решение идеально подходит для работы в динамическом списке без потери производительности.
---
Даже самый оптимальный запрос, выполняющий соединение таблиц (как в Варианте 2), при выводе в динамический список на кассовом узле с сотнями тысяч клиентов может снизить скорость прокрутки экрана. Если мы хотим добиться мгновенного отклика интерфейса, применим паттерн кэширования показателей.
Для этого создадим вспомогательный непериодический регистр сведений СостояниеВзаиморасчетовПоРассрочке со следующей структурой:
ДокументПродажи.ОбщийДолг, ПросроченныйДолг, БлижайшийПлатежСумма, БлижайшийПлатежДата.Как организовать работу кэша:
ДокументПродажи в регистр сведений ОчередьПересчетаРасчетов).СостояниеВзаиморасчетовПоРассрочке.СостояниеВзаиморасчетовПоРассрочке. Поиск, сортировка и отборы по колонкам «Просроченная задолженность» или «Ближайший платеж» будут отрабатывать мгновенно, так как СУБД не нужно выполнять никаких математических расчетов и соединений таблиц в момент прокрутки экрана.Именно такой гибридный подход с разделением планового графика и фактических взаиморасчетов, поддержанный механизмом фонового кэширования показателей, используется в крупных ERP-системах и гарантирует максимальную стабильность и скорость работы вашей конфигурации.