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