Разработка подсистем казначейства, бюджетирования или учета договоров займов и аренды в 1С практически всегда сталкивается с необходимостью планирования платежей. На первый взгляд задача кажется тривиальной: нужно хранить периодический план-график выплат с возможностью отслеживания его изменений во времени (например, при подписании дополнительных соглашений). Однако стандартные механизмы платформы 1С, такие как виртуальная таблица СрезПоследних периодического регистра сведений, при наивном проектировании могут преподнести неприятный сюрприз.
В этой статье мы подробно разберем, почему классический периодический регистр сведений не подходит для хранения изменяющихся графиков платежей, проанализируем возникающие проблемы и рассмотрим четыре профессиональных архитектурных паттерна для реализации этой задачи.
Представим стандартный периодический регистр сведений ГрафикиПлатежей со следующей структурой:
Контрагент, Проект, Месяц (дата начала месяца начисления)СуммаПлатежа, ДатаПлатежаДопустим, 1 января стороны подписали договор на 12 месяцев с ежемесячной выплатой 10 000 рублей. Документ-регистратор делает 12 записей в регистр с периодом 01.01.2024 на каждый месяц года (с января по декабрь).
Через полгода, 1 июня, стороны подписывают дополнительное соглашение, согласно которому срок выплат сокращается до 6 месяцев (то есть выплаты должны завершиться в июне), а сумма ежемесячного платежа за оставшиеся месяцы (май и июнь) меняется. Новый документ-регистратор делает движения от 01.06.2024, но уже только на 6 месяцев (с января по июнь).
Давайте проанализируем, что произойдет, если мы попытаемся получить актуальный график платежей на дату 15.06.2024, используя виртуальную таблицу СрезПоследних:
Контрагент, Проект и Месяц).Контрагент = "Ромашка", Проект = "Проект 1", Месяц = 01.07.2024) в последний раз менялось 01.01.2024.СрезПоследних «стянет» старые записи за июль-декабрь из первой версии графика.Наш итоговый актуальный график на середину июня будет состоять из 6 новых записей и 6 старых, неактуальных «хвостов». Это грубейшая ошибка, которая приведет к неверному планированию бюджетов и ложным начислениям задолженности.
Выясним, как решить эту проблему с помощью правильного проектирования структуры данных.
---
Это наиболее гибкое, производительное и масштабируемое решение, которое активно применяется в современных типовых конфигурациях фирмы 1С (таких как 1С:ERP Управление предприятием и 1С:Управление холдингом). Мы разделяем сущность «График» на описание его версий и фактическое содержимое строк.
Рассмотрим структуру метаданных, которую нам необходимо создать:
ВерсииГрафиков
АктивныеВерсииГрафиков
Контрагент, Проект.АктивнаяВерсия (тип: СправочникСсылка.ВерсииГрафиков).ДетализацияГрафиков
ВерсияГрафика (тип: СправочникСсылка.ВерсииГрафиков), Месяц.Сумма, ПлановаяДатаПлатежа.Разберем по шагам, как работает данная схема при проведении документов:
ВерсииГрафиков (например, "Версия 1"). В непериодический регистр ДетализацияГрафиков записываются 12 строк, жестко привязанных к "Версии 1". В периодический регистр АктивныеВерсииГрафиков пишется запись: с 01.01.2024 активной версией является "Версия 1".ДетализацияГрафиков пишутся только 6 строк нового графика, привязанных к "Версии 2". В регистр АктивныеВерсииГрафиков записывается строка: с 01.06.2024 активной версией становится "Версия 2".Посмотрим на пример запроса, который позволяет быстро и безболезненно получить гарантированно актуальный график платежей на любую дату (например, на входящую переменную &Период):
ВЫБРАТЬ
АктивныеВерсии.АктивнаяВерсия КАК АктивнаяВерсия
ПОМЕСТИТЬ ВТ_АктивнаяВерсия
ИЗ
РегистрСведений.АктивныеВерсииГрафиков.СрезПоследних(&Период, Контрагент = &Контрагент И Проект = &Проект) КАК АктивныеВерсии
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
Детализация.Месяц КАК Месяц,
Детализация.Сумма КАК Сумма,
Детализация.ПлановаяДатаПлатежа КАК ПлановаяДатаПлатежа
ИЗ
ВТ_АктивнаяВерсия КАК АктивнаяВерсия
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ДетализацияГрафиков КАК Детализация
ПО АктивнаяВерсия.АктивнаяВерсия = Детализация.ВерсияГрафика
Преимущества метода:
ВерсияГрафика в регистре детализации.---
Если плодить дополнительные справочники не входит в ваши планы, можно обойтись одним регистром сведений, но при этом отказаться от стандартной виртуальной таблицы СрезПоследних, заменив ее кастомным запросом.
Спроектируем структуру регистра сведений ИсторияГрафиковПлатежей:
КонтрагентПроектДокументВерсии (тип: ссылка на документ, который установил данный график - Договор, Доп. соглашение, Изменение условий)МесяцСумма, ПлановаяДатаПлатежа.В этой схеме каждый документ, утверждающий график, записывает свои строки под своей собственной ссылкой в измерении ДокументВерсии. Чтобы получить актуальный график на определенную дату, нам необходимо сначала вычислить, какой документ-версия является последним на шкале времени, а затем выбрать строки только по этому документу.
Проанализируем текст запроса, реализующий эту логику:
ВЫБРАТЬ
ВЫРАЗИТЬ(ДокументВерсии КАК Документ.ДопСоглашение).Дата КАК ДатаВерсии,
ДокументВерсии КАК ДокументВерсии
ПОМЕСТИТЬ ВТ_ВсеВерсии
ИЗ
РегистрСведений.ИсторияГрафиковПлатежей КАК История
ГДЕ
История.Контрагент = &Контрагент
И История.Проект = &Проект
И ВЫРАЗИТЬ(История.ДокументВерсии КАК Документ.ДопСоглашение).Дата <= &Период
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
МАКСИМУМ(ВТ_ВсеВерсии.ДатаВерсии) КАК МаксДата
ПОМЕСТИТЬ ВТ_МаксДата
ИЗ
ВТ_ВсеВерсии КАК ВТ_ВсеВерсии
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ ПЕРВЫЕ 1
ВТ_ВсеВерсии.ДокументВерсии КАК АктуальныйДокументВерсии
ПОМЕСТИТЬ ВТ_АктуальнаяВерсия
ИЗ
ВТ_ВсеВерсии КАК ВТ_ВсеВерсии
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_МаксДата КАК ВТ_МаксДата
ПО ВТ_ВсеВерсии.ДатаВерсии = ВТ_МаксДата.МаксДата
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
История.Месяц КАК Месяц,
История.Сумма КАК Сумма,
История.ПлановаяДатаПлатежа КАК ПлановаяДатаПлатежа
ИЗ
ВТ_АктуальнаяВерсия КАК АктуальнаяВерсия
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ИсторияГрафиковПлатежей КАК История
ПО АктуальнаяВерсия.АктуальныйДокументВерсии = История.ДокументВерсии
ГДЕ
История.Контрагент = &Контрагент
И История.Проект = &Проект
Важное примечание разработчику: Использование приведения типов через функцию ВЫРАЗИТЬ() для получения даты регистратора оправдано только в том случае, если у вас ограниченное число возможных типов документов-регистраторов. Если видов документов много, лучше добавить в регистр служебное измерение (или реквизит) ДатаВерсии (тип: Дата) и индексировать его. Это существенно повысит производительность СУБД.
---
Рассмотрим ситуацию, когда архитектура системы жестко требует использования стандартного периодического регистра сведений с измерением Месяц и получения данных через стандартную виртуальную таблицу СрезПоследних. Чтобы избежать эффекта «хвостов», нам придется переложить всю аналитическую работу на программный код проведения документов.
Разберем по шагам алгоритм проведения документа-корректировки (например, Доп. соглашения, сокращающего график):
Активность = Ложь.СрезПоследних мы накладываем условие отсечения пустых или неактивных строк: ГДЕ Сумма > 0.Посмотрим на пример кода формирования движений в модуле проведения документа:
// 1. Получаем старый график
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Графики.Месяц КАК Месяц
|ИЗ
| РегистрСведений.ГрафикиПлатежей.СрезПоследних(&МоментВремени, Контрагент = &Контрагент И Проект = &Проект) КАК Графики";
Запрос.УстановитьПараметр("МоментВремени", МоментВремени);
Запрос.УстановитьПараметр("Контрагент", Контрагент);
Запрос.УстановитьПараметр("Проект", Проект);
ТаблицаСтарогоГрафика = Запрос.Выполнить().Выгрузить();
// 2. Создаем набор записей
НаборЗаписей = РегистрыСведений.ГрафикиПлатежей.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(Ссылка);
// 3. Заполняем новые (актуальные) строки из табличной части документа
Для Каждого СтрокаНовогоГрафика Из ТабличнаяЧастьЦикла Цикл
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.Период = ДатаДокумента;
НоваяЗапись.Контрагент = Контрагент;
НоваяЗапись.Проект = Проект;
НоваяЗапись.Месяц = СтрокаНовогоГрафика.Месяц;
НоваяЗапись.Сумма = СтрокаНовогоГрафика.Сумма;
КонецЦикла;
// 4. Сторнируем "хвосты" старого графика
Для Каждого СтрокаСтарого Из ТаблицаСтарогоГрафика Цикл
Если ТабличнаяЧастьДокумента.Найти(СтрокаСтарого.Месяц, "Месяц") = Неопределено Тогда
// Этого месяца больше нет в новом графике, записываем пустую строку для перекрытия среза
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.Период = ДатаДокумента;
НоваяЗапись.Контрагент = Контрагент;
НоваяЗапись.Проект = Проект;
НоваяЗапись.Месяц = СтрокаСтарого.Месяц;
НоваяЗапись.Сумма = 0; // Сторно суммы
КонецЕсли;
КонецЦикла;
НаборЗаписей.Записать();
Недостатки метода:
---
Если график платежей используется исключительно для сопоставления с фактическими выплатами (план-фактный анализ), то отличным решением станет использование оборотного регистра накопления ПланыПлатежей.
Структура регистра накопления (вид «Обороты»):
Контрагент, Проект, ПлановаяДата.Сумма (плановое начисление).При такой схеме логика изменения графика выглядит следующим образом:
Этот подход сложнее в отладке, но гарантирует, что при получении плановых оборотов за период мы никогда не получим некорректных данных, так как все отмененные плановые суммы будут физически компенсированы сторнировочными записями.
---
Чтобы определиться, какой вариант реализации выбрать для вашего проекта, проанализируем сравнительную таблицу:
| Критерий оценки | Паттерн №1: Справочник версий (ERP) | Паттерн №2: Измерение в РС + кастомный запрос | Паттерн №3: Явное сторнирование в РС | Паттерн №4: Оборотный регистр накопления |
|---|---|---|---|---|
| Сложность разработки | Средняя (требуется создать 3 объекта метаданных) | Низкая (один регистр, но сложный запрос) | Высокая (сложный код проведения документов) | Средняя (требуется реализация механизма сторно) |
| Производительность СУБД | Очень высокая (выборка строго по индексу версии) | Средняя (требуются вложенные запросы поиска макс. даты) | Низкая (база растет из-за пустых строк-заглушек) | Высокая (использование готовых итогов платформы) |
| Надежность данных | Абсолютная (ошибки исключены структурой БД) | Высокая | Низкая (риск сбоя логики при перепроведении документов) | Высокая |
| Простота анализа истории | Очень простая (каждая версия — элемент справочника) | Простая (версии привязаны к регистраторам) | Сложная (придется восстанавливать картину по логам) | Сложная (история размыта в оборотах) |
Если вы разрабатываете крупную корпоративную систему или тиражное решение, где графики платежей могут содержать сотни строк и часто меняться — используйте Паттерн №1 (Справочник версий). Это промышленный стандарт разработки, который сбережет ресурсы сервера и нервы сопровождающих программистов.
Если задача локальная, а количество графиков невелико — оптимальным компромиссом по времени разработки станет Паттерн №2 (Измерение версии в РС с кастомным запросом). Главное — не забудьте добавить реквизит ДатаВерсии для индексации, чтобы не перегружать СУБД тяжелыми функциями приведения типов в запросах.