В процессе разработки и кастомизации старых конфигураций, таких как 1С:Зарплата и Управление Персоналом 2.5, программисты часто сталкиваются с необходимостью реализовать сложные алгоритмы расчета. Одна из таких задач — включение начисления в расчетную базу самого себя, при условии, что база берется за предыдущий месяц. На первый взгляд кажется, что если периоды разнесены во времени, то конфликта возникнуть не должно. Однако платформа 1С накладывает жесткое ограничение на такие действия. Разберем подробно, почему это ограничение существует, какие «подводные камни» ожидают разработчика при его обходе и как правильно реализовать подобную логику.
В системе 1С:Предприятие 8 реализован механизм ведущих видов расчета. Это фундаментальное понятие для планов видов расчета (ПВР), которое определяет зависимости между различными начислениями. Когда мы добавляем начисление «А» в расчетную базу начисления «Б», начисление «А» автоматически становится ведущим для «Б».
Проанализируем ситуацию, когда начисление «А» становится базовым (и ведущим) для самого себя. В регистре расчета существует механизм автоматической регистрации записей, требующих пересчета. Если вы измените результат начисления «А» за январь, система, обнаружив зависимость, пометит запись «А» за февраль как требующую пересчета. Рассмотрим риски этого процесса:
При расчете сумм система обращается к виртуальным таблицам, таким как РегистрРасчета.ОсновныеНачисления.База. Эти таблицы оптимизированы платформой для работы с иерархическими, нецикличными структурами данных. Разберем, что происходит «под капотом» при попытке получить базу «самого себя»:
Во время выполнения запроса к виртуальной таблице База, платформа строит дерево зависимостей. Если в этом дереве обнаруживается ссылка на тот же вид расчета, алгоритм определения очередности может отработать некорректно. Выясним возможные последствия:
В 1С:ЗУП 2.5 алгоритм расчета строится на очередях. Система анализирует все начисления и распределяет их по этапам. На первом этапе считаются те, которые не зависят ни от чего (фиксированные суммы), на втором — те, что зависят от первого этапа, и так далее. Посмотрим, как циклическая зависимость ломает эту логику:
Если начисление зависит само от себя, алгоритм определения очередности (построение дерева зависимостей в программном коде 1С) может либо выдать ошибку «Циклическая зависимость», либо поместить начисление в неочевидный этап. Это критично, если на это же начисление завязаны другие зависимые расчеты (например, районный коэффициент или северная надбавка). В итоге общая сумма заработной платы может оказаться неверной из-за нарушения порядка выполнения функций в общем модуле ПроцедурыУправленияРасчетами.
Проанализируем ситуацию, которая чаще всего приводит к ошибкам в учете — сторно-записи и документы «Исправление». В 1С:ЗУП 2.5 механизм вытеснения и получения базы тесно связан с периодами действия и периодами регистрации.
Если начисление за прошлый месяц будет сторнировано, механизм формирования базы для текущего месяца должен решить: брать ли исходный результат, результат с учетом сторно или новый результат из документа исправления. Поскольку объект ссылается сам на себя, стандартные механизмы платформы часто «запутываются». Это может привести к удвоению сумм (если система посчитает и старую, и новую запись) или к их полному обнулению в базе текущего месяца. Такие ошибки крайне сложно локализовать в общем своде начислений предприятия.
Если бизнес-логика требует расчета суммы на основании аналогичного начисления за прошлый период, рекомендуется использовать один из следующих архитектурно верных подходов:
Создадим два вида расчета в плане видов расчета Основные начисления:
Начисление_Техническое — копия основного начисления, которая не имеет базы и рассчитывается первой.Начисление_Основное — основное начисление, которое в свою расчетную базу включает только Начисление_Техническое.В этом случае циклическая связь разрывается. Начисление текущего месяца будет брать данные технического начисления прошлого месяца, и платформа не увидит конфликта интересов.
Вместо использования виртуальной таблицы База регистра расчета, можно написать собственный алгоритм получения данных в модуле расчета. Разберем шаги реализации:
В процедуре расчета начисления (обычно это происходит в специализированных модулях или обработках расчета) мы можем напрямую обратиться к физической таблице регистра расчета или к регистру накопления ЗарплатаЗаМесяц (или аналогичному в вашей конфигурации).
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| СУММА(ОсновныеНачисления.Результат) КАК РезультатПрошлогоМесяца
|ИЗ
| РегистрРасчета.ОсновныеНачисления КАК ОсновныеНачисления
|ГДЕ
| ОсновныеНачисления.ВидРасчета = &ТекущийВидРасчета
| И ОсновныеНачисления.ФизЛицо = &ФизЛицо
| И ОсновныеНачисления.ПериодРегистрации = &ПрошлыйМесяц";
Запрос.УстановитьПараметр("ТекущийВидРасчета", ПланВидовРасчета.ОсновныеНачисления.НайтиПоНаименованию("ВашеНачисление"));
Запрос.УстановитьПараметр("ФизЛицо", ТекущееФизЛицо);
Запрос.УстановитьПараметр("ПрошлыйМесяц", ДобавитьМесяц(НачалоМесяца(ТекущаяДата), -1));
Преимущество: Мы полностью обходим механизмы ведущих начислений платформы и контролируем выборку данных самостоятельно. Недостаток: Придется вручную обрабатывать ситуации со сторно и перерасчетами, так как платформа не будет автоматически предлагать пересчитать текущий месяц при изменении прошлого.
Снятие запрета на включение начисления в собственную базу в коде — это рискованный шаг, который может привести к деградации целостности данных при перерасчетах. В ЗУП 3.1 эта проблема решена за счет иной архитектуры регистров и алгоритмов (использование других типов зависимостей), но в ЗУП 2.5 лучше придерживаться проверенных методов: разделения видов расчета на «ведущий» и «ведомый» или прямого запроса к данным регистра, минуя механизм виртуальных таблиц базы.