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