Как спроектировать регистр для учета графиков платежей и рассрочки в 1С:Розница

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

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

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

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

---

Критическая ошибка: Регистр остатков с датой платежа в измерениях

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

Рассмотрим подробнее, почему этот подход является фатальным для производительности базы данных в среднесрочной перспективе:

  1. Проблема пересчета итогов при будущих датах. Когда мы проводим документ текущей датой (например, 01.10.2023) и делаем движения по регистру остатков будущей датой (например, 15.10.2024), платформа 1С вынуждена пересчитать и записать промежуточные итоги за каждый месяц на весь этот период вперед. При высокой интенсивности продаж в розничной точке это вызовет критические блокировки и замедление работы системы.
  2. Проблема «незакрывающихся» остатков. Регистр остатков корректно работает только тогда, когда записи в него полностью закрываются в ноль по всем измерениям. Если мы запланировали платеж на 15.10.2023 (измерение ДатаПлатежа = 15.10.2023), а покупатель фактически принес деньги 20.10.2023, то при проведении оплаты мы обязаны указать в расходе точно такую же дату платежа (15.10.2023). На практике клиенты платят раньше, позже, частями или вообще без привязки к конкретным этапам. Если логика распределения оплаты ошибется хотя бы на секунду или один день, в базе возникнет «разрыв»:
    
    На 15.10.2023: +1000 руб. (Приход, не закрыт)
    На 20.10.2023: -1000 руб. (Расход, не закрыт)
    
    В итоге общий баланс по контрагенту будет нулевым, но в таблице итогов СУБД (_AccumRegTotals) останутся висеть две записи. Через 2–3 года работы база данных переполнится такими «виртуальными» незакрытыми остатками, и любые запросы к регистру начнут выполняться недопустимо медленно.

---

Вариант 1. Архитектура из двух оборотных регистров (План и Факт)

Этот паттерн часто применяется в сложных системах бюджетирования. Мы полностью отказываемся от идеи использовать регистр остатков для хранения плановых дат и создаем два оборотных регистра накопления:

  1. Регистр накопления «Планируемые платежи» (оборотный).
    • Измерения: Контрагент, Договор (или документ рассрочки), ПериодОплаты (ссылка на справочник!).
    • Ресурсы: Сумма.
  2. Регистр накопления «Фактические платежи» (оборотный).
    • Измерения: Аналогично первому регистру.
    • Ресурсы: Сумма.

Обратите внимание на измерение ПериодОплаты. Нам ни в коем случае нельзя использовать здесь простой тип Дата. Мы создаем вспомогательный справочник (например, ПериодыПланирования с элементами «Октябрь 2023», «Ноябрь 2023» и т.д.). Использование ссылочного типа данных в качестве измерения полностью решает проблему пересчета итогов СУБД, так как для платформы это обычная ссылка, а не временная шкала.

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


ВЫБРАТЬ
    План.Контрагент КАК Контрагент,
    План.Договор КАК Договор,
    СУММА(План.СуммаОборот) - СУММА(ЕСТЬNULL(Факт.СуммаОборот, 0)) КАК Просрочка
ИЗ
    РегистрНакопления.ПланируемыеПлатежи.Обороты(,, , ) КАК План
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ФактическиеПлатежи.Обороты(,, , ) КАК Факт
    ПО План.Контрагент = Факт.Contragent
    И План.Договор = Факт.Договор
    И План.ПериодОплаты = Факт.ПериодОплаты
ГДЕ
    План.ПериодОплаты.ДатаОкончания < &ТекущаяДата
СГРУППИРОВАТЬ ПО
    План.Контрагент,
    План.Договор
ХАВИНГ
    СУММА(План.СуммаОборот) - СУММА(ЕСТЬNULL(Факт.СуммаОборот, 0)) > 0

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

Недостатки: Со временем (через 5–7 лет) объем таблицы оборотов вырастет до миллионов записей. Чтобы посчитать просрочку по конкретному контрагенту, системе придется сложить абсолютно весь его исторический план и вычесть весь факт. Для динамических списков это может стать узким местом.

---

Вариант 2. Гибридная схема: Регистр сведений (План) + Регистр остатков (Факт)

Этот вариант является наиболее элегантным и производительным для розничной торговли. Мы разделяем хранение графика и учет реального долга покупателя.

1. Регистр сведений «ГрафикиПлатежей» (непериодический):

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

2. Регистр накопления «ВзаиморасчетыСПокупателями» (остатки):

Обычный классический регистр остатков. Он хранит только фактический текущий долг покупателя без привязки к конкретным датам графика. При продаже в рассрочку мы делаем Приход на общую сумму долга. При оплате покупателем — Расход. Этот регистр гарантированно быстро и без проблем закрывается в ноль.

Давайте разберем по шагам, как при такой структуре получить все необходимые автору данные с помощью простых и быстрых запросов.

Запрос 1. Получение общей задолженности клиентов

Это самый простой запрос, который мгновенно отрабатывает по виртуальной таблице остатков:


ВЫБРАТЬ
    ВзаиморасчетыОстатки.Контрагент,
    ВзаиморасчетыОстатки.ДокументПродажи,
    ВзаиморасчетыОстатки.СуммаОстаток КАК ОбщаяЗадолженность
ИЗ
    РегистрНакопления.ВзаиморасчетыСПокупателями.Остатки КАК ВзаиморасчетыОстатки

Запрос 2. Получение планируемых платежей в текущем месяце

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


ВЫБРАТЬ
    График.ДокументПродажи,
    График.ДатаПлатежа,
    График.СуммаПлатежа
ИЗ
    РегистрСведений.ГрафикиПлатежей КАК График
ГДЕ
    График.ДатаПлатежа МЕЖДУ &НачалоМесяца И &КонецМесяца

Запрос 3. Расчет просроченной задолженности

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

Проанализируем этот алгоритм на примере: общий долг по рассрочке был 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), при выводе в динамический список на кассовом узле с сотнями тысяч клиентов может снизить скорость прокрутки экрана. Если мы хотим добиться мгновенного отклика интерфейса, применим паттерн кэширования показателей.

Для этого создадим вспомогательный непериодический регистр сведений СостояниеВзаиморасчетовПоРассрочке со следующей структурой:

Как организовать работу кэша:

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

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

← На главную