Как организовать хранение графика платежей в 1С с сохранением истории изменений и без «хвостов» в срезе последних

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

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

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

Суть проблемы: почему стандартный «Срез последних» ломает измененный график

Представим стандартный периодический регистр сведений ГрафикиПлатежей со следующей структурой:

Допустим, 1 января стороны подписали договор на 12 месяцев с ежемесячной выплатой 10 000 рублей. Документ-регистратор делает 12 записей в регистр с периодом 01.01.2024 на каждый месяц года (с января по декабрь).

Через полгода, 1 июня, стороны подписывают дополнительное соглашение, согласно которому срок выплат сокращается до 6 месяцев (то есть выплаты должны завершиться в июне), а сумма ежемесячного платежа за оставшиеся месяцы (май и июнь) меняется. Новый документ-регистратор делает движения от 01.06.2024, но уже только на 6 месяцев (с января по июнь).

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

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

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

Выясним, как решить эту проблему с помощью правильного проектирования структуры данных.

---

Решение №1: Двухэтапный регистр с версионированием через справочник (Подход 1С:ERP)

Это наиболее гибкое, производительное и масштабируемое решение, которое активно применяется в современных типовых конфигурациях фирмы 1С (таких как 1С:ERP Управление предприятием и 1С:Управление холдингом). Мы разделяем сущность «График» на описание его версий и фактическое содержимое строк.

Рассмотрим структуру метаданных, которую нам необходимо создать:

  1. Справочник ВерсииГрафиков
    • Владелец или родитель: Договор контрагента / Проект.
    • Реквизиты: Данные о документе-основании (например, Доп. соглашение).
  2. Периодический регистр сведений АктивныеВерсииГрафиков
    • Периодичность: Позиция регистратора.
    • Измерения: Контрагент, Проект.
    • Ресурсы: АктивнаяВерсия (тип: СправочникСсылка.ВерсииГрафиков).
  3. Непериодический регистр сведений ДетализацияГрафиков
    • Измерения: ВерсияГрафика (тип: СправочникСсылка.ВерсииГрафиков), Месяц.
    • Ресурсы: Сумма, ПлановаяДатаПлатежа.

Разберем по шагам, как работает данная схема при проведении документов:

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


ВЫБРАТЬ
    АктивныеВерсии.АктивнаяВерсия КАК АктивнаяВерсия
ПОМЕСТИТЬ ВТ_АктивнаяВерсия
ИЗ
    РегистрСведений.АктивныеВерсииГрафиков.СрезПоследних(&Период, Контрагент = &Контрагент И Проект = &Проект) КАК АктивныеВерсии
;

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

Преимущества метода:

---

Решение №2: Регистр сведений с измерением «Версия» и ручным срезом через запрос

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

Спроектируем структуру регистра сведений ИсторияГрафиковПлатежей:

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

Проанализируем текст запроса, реализующий эту логику:


ВЫБРАТЬ
    ВЫРАЗИТЬ(ДокументВерсии КАК Документ.ДопСоглашение).Дата КАК ДатаВерсии,
    ДокументВерсии КАК ДокументВерсии
ПОМЕСТИТЬ ВТ_ВсеВерсии
ИЗ
    РегистрСведений.ИсторияГрафиковПлатежей КАК История
ГДЕ
    История.Контрагент = &Контрагент
    И История.Проект = &Проект
    И ВЫРАЗИТЬ(История.ДокументВерсии КАК Документ.ДопСоглашение).Дата <= &Период
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    МАКСИМУМ(ВТ_ВсеВерсии.ДатаВерсии) КАК МаксДата
ПОМЕСТИТЬ ВТ_МаксДата
ИЗ
    ВТ_ВсеВерсии КАК ВТ_ВсеВерсии
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ ПЕРВЫЕ 1
    ВТ_ВсеВерсии.ДокументВерсии КАК АктуальныйДокументВерсии
ПОМЕСТИТЬ ВТ_АктуальнаяВерсия
ИЗ
    ВТ_ВсеВерсии КАК ВТ_ВсеВерсии
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_МаксДата КАК ВТ_МаксДата
        ПО ВТ_ВсеВерсии.ДатаВерсии = ВТ_МаксДата.МаксДата
;

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

Важное примечание разработчику: Использование приведения типов через функцию ВЫРАЗИТЬ() для получения даты регистратора оправдано только в том случае, если у вас ограниченное число возможных типов документов-регистраторов. Если видов документов много, лучше добавить в регистр служебное измерение (или реквизит) ДатаВерсии (тип: Дата) и индексировать его. Это существенно повысит производительность СУБД.

---

Решение №3: Сторнирование (закрытие) неактуальных периодов в стандартном РС

Рассмотрим ситуацию, когда архитектура системы жестко требует использования стандартного периодического регистра сведений с измерением Месяц и получения данных через стандартную виртуальную таблицу СрезПоследних. Чтобы избежать эффекта «хвостов», нам придется переложить всю аналитическую работу на программный код проведения документов.

Разберем по шагам алгоритм проведения документа-корректировки (например, Доп. соглашения, сокращающего график):

  1. При проведении документ считывает из регистра сведений предыдущую актуальную версию графика (все записи по данному контрагенту и проекту).
  2. Документ формирует новые записи на актуальные периоды (например, новые суммы на январь-июнь).
  3. Для всех периодов, которые присутствовали в старой версии графика, но отсутствуют в новой (в нашем примере — с июля по декабрь), документ обязан сделать «закрывающие» (сторно) движения. Это могут быть записи с нулевой суммой или со специальным флагом Активность = Ложь.
  4. При выборке данных запросом из виртуальной таблицы СрезПоследних мы накладываем условие отсечения пустых или неактивных строк: ГДЕ Сумма > 0.

Посмотрим на пример кода формирования движений в модуле проведения документа:


// 1. Получаем старый график
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	Графики.Месяц КАК Месяц
    |ИЗ
    |	РегистрСведений.ГрафикиПлатежей.СрезПоследних(&МоментВремени, Контрагент = &Контрагент И Проект = &Проект) КАК Графики";
Запрос.УстановитьПараметр("МоментВремени", МоментВремени);
Запрос.УстановитьПараметр("Контрагент", Контрагент);
Запрос.УстановитьПараметр("Проект", Проект);
ТаблицаСтарогоГрафика = Запрос.Выполнить().Выгрузить();

// 2. Создаем набор записей
НаборЗаписей = РегистрыСведений.ГрафикиПлатежей.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(Ссылка);

// 3. Заполняем новые (актуальные) строки из табличной части документа
Для Каждого СтрокаНовогоГрафика Из ТабличнаяЧастьЦикла Цикл
    НоваяЗапись = НаборЗаписей.Добавить();
    НоваяЗапись.Период = ДатаДокумента;
    НоваяЗапись.Контрагент = Контрагент;
    НоваяЗапись.Проект = Проект;
    НоваяЗапись.Месяц = СтрокаНовогоГрафика.Месяц;
    НоваяЗапись.Сумма = СтрокаНовогоГрафика.Сумма;
КонецЦикла;

// 4. Сторнируем "хвосты" старого графика
Для Каждого СтрокаСтарого Из ТаблицаСтарогоГрафика Цикл
    Если ТабличнаяЧастьДокумента.Найти(СтрокаСтарого.Месяц, "Месяц") = Неопределено Тогда
        // Этого месяца больше нет в новом графике, записываем пустую строку для перекрытия среза
        НоваяЗапись = НаборЗаписей.Добавить();
        НоваяЗапись.Период = ДатаДокумента;
        НоваяЗапись.Контрагент = Контрагент;
        НоваяЗапись.Проект = Проект;
        НоваяЗапись.Месяц = СтрокаСтарого.Месяц;
        НоваяЗапись.Сумма = 0; // Сторно суммы
    КонецЕсли;
КонецЦикла;

НаборЗаписей.Записать();

Недостатки метода:

---

Решение №4: Оборотный регистр накопления с полной очисткой планов

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

Структура регистра накопления (вид «Обороты»):

При такой схеме логика изменения графика выглядит следующим образом:

  1. Первичный документ делает движения со знаком «плюс» (приход плана) на все 12 месяцев.
  2. Документ корректировки при проведении сначала делает полное сторнирование движений предыдущего документа (записывает те же суммы с противоположным знаком на те же даты), а затем делает новые движения на новые актуальные периоды.
  3. Актуальный план на любую дату — это просто нарастающий итог (сумма оборотов) по регистру. Любые корректировки и изменения условий автоматически учитываются кумулятивным итогом.

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

---

Сравнение архитектурных паттернов

Чтобы определиться, какой вариант реализации выбрать для вашего проекта, проанализируем сравнительную таблицу:

Критерий оценки Паттерн №1: Справочник версий (ERP) Паттерн №2: Измерение в РС + кастомный запрос Паттерн №3: Явное сторнирование в РС Паттерн №4: Оборотный регистр накопления
Сложность разработки Средняя (требуется создать 3 объекта метаданных) Низкая (один регистр, но сложный запрос) Высокая (сложный код проведения документов) Средняя (требуется реализация механизма сторно)
Производительность СУБД Очень высокая (выборка строго по индексу версии) Средняя (требуются вложенные запросы поиска макс. даты) Низкая (база растет из-за пустых строк-заглушек) Высокая (использование готовых итогов платформы)
Надежность данных Абсолютная (ошибки исключены структурой БД) Высокая Низкая (риск сбоя логики при перепроведении документов) Высокая
Простота анализа истории Очень простая (каждая версия — элемент справочника) Простая (версии привязаны к регистраторам) Сложная (придется восстанавливать картину по логам) Сложная (история размыта в оборотах)

Рекомендации по выбору решения

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

Если задача локальная, а количество графиков невелико — оптимальным компромиссом по времени разработки станет Паттерн №2 (Измерение версии в РС с кастомным запросом). Главное — не забудьте добавить реквизит ДатаВерсии для индексации, чтобы не перегружать СУБД тяжелыми функциями приведения типов в запросах.

← На главную