Как сохранить реквизит документа при проведении в 1С 8.3

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

В процессе разработки в среде «1С:Предприятие 8» программисты часто сталкиваются с задачей, когда при проведении документа необходимо зафиксировать какое-то значение в его собственном реквизите. Типичный пример — запись текущего времени отправки, фиксация пользователя, выполнившего проведение, или запись рассчитанного статуса.

Однако попытка напрямую присвоить значение реквизиту внутри обработчика ОбработкаПроведения (например, написать ЭтотОбъект.ВремяВыезда = ТекущаяДата()) приводит к тому, что данные просто не сохраняются в базе данных. В этой статье мы подробно разберем, почему платформа 1С ведет себя именно так, изучим внутренние механизмы транзакций и рассмотрим правильные архитектурные решения этой задачи.

Почему реквизит не сохраняется в «ОбработкеПроведения» (Разбор матчасти)

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

  1. Вызывается обработчик события «ПередЗаписью» в модуле документа. На этом этапе объект находится исключительно в оперативной памяти сервера, его можно изменять.
  2. Платформа осуществляет физическую запись реквизитов и табличных частей документа в таблицы базы данных (SQL).
  3. Вызывается обработчик события «ПриЗаписи» в модуле документа. Здесь документ уже физически изменен в таблицах БД, но транзакция еще не зафиксирована (ее можно отменить).
  4. Вызывается обработчик «ОбработкаПроведения» в модуле документа. На данном этапе платформа ожидает от разработчика формирования движений по регистрам.

Главный вывод: К моменту, когда управление передается в процедуру ОбработкаПроведения, сам документ уже записан в базу данных. Любое изменение реквизита объекта в этой процедуре поменяет значение только в оперативной памяти текущего сеанса. Платформа не станет повторно перезаписывать документ в базу данных после завершения проведения — она запишет только сформированные движения по регистрам (наборы записей).

Возникает логичный вопрос: почему бы просто не вызвать метод ЭтотОбъект.Записать() прямо внутри процедуры проведения? Делать это категорически запрещено по следующим причинам:

Решение 1. Классический подход через обработчик «ПередЗаписью»

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

В этом обработчике нам доступен параметр РежимЗаписи, который позволяет определить, какое именно действие сейчас выполняется над документом. Разберем по шагам, как это реализовать:


Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    
    // Проверяем, что документ записывается именно в режиме проведения
    Если РежимЗаписи = РежимЗаписиДокумента.Проведение Тогда
        
        // Заполняем наш реквизит текущей датой и временем
        ЭтотОбъект.ВремяВыезда = ТекущаяДата();
        
    КонецЕсли;
    
КонецПроцедуры

При таком подходе платформа запишет измененное значение реквизита ВремяВыезда в базу данных в штатном режиме, за один проход транзакции записи, не создавая избыточной нагрузки на сервер СУБД.

Решение 2. Использование регистра сведений (для сложных расчетов)

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

Пытаться сохранить такие расчетные данные в реквизиты самого документа — методологически неверно. Для таких задач идеально подходит использование подчиненного регистра сведений:

  1. Создайте новый Регистр сведений (например, ПараметрыПроведенияДокументов).
  2. Установите режим записи регистра в значение «Подчинен регистратору», а в качестве регистратора укажите ваш документ.
  3. Добавьте в регистр необходимое измерение (например, Документ) и ресурс (например, ВремяВыезда или РассчитаннаяСебестоимость).
  4. В процедуре ОбработкаПроведения запишите рассчитанные данные как обычное движение документа:

Процедура ОбработкаПроведения(Отказ, РежимПроведения)
    
    // Включаем запись движений по нашему регистру сведений
    Движения.ПараметрыПроведенияДокументов.Записывать = Истина;
    
    // Добавляем новую строку в движения документа
    НоваяСтрока = Движения.ПараметрыПроведенияДокументов.Добавить();
    НоваяСтрока.Период = Дата;
    
    // Заполняем данные, полученные в результате расчетов при проведении
    НоваяСтрока.ВремяВыезда = ПолучитьТекущееВремяВыезда();
    
КонецПроцедуры

Чтобы пользователь видел эту информацию на форме документа, достаточно вывести туда поле надписи (или поле ввода в режиме "Только просмотр") и динамически считывать значение из регистра сведений при открытии или чтении формы.

Проблема обновления формы на клиенте

Часто разработчики сталкиваются с ситуацией: код в ПередЗаписью написан верно, документ успешно проводится, данные в базе данных обновляются, но на экране пользователя в управляемой форме реквизит остается пустым до тех пор, пока форму не переоткроют. Это происходит потому, что управляемая форма на клиенте не всегда автоматически синхронизирует все измененные на сервере реквизиты после завершения транзакции.

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


&НаКлиенте
Процедура ПослеЗаписи(ПараметрыЗаписи)
    
    // Принудительно перечитываем данные объекта из базы данных в форму
    ЭтаФорма.Прочитать();
    
КонецПроцедуры

Метод Прочитать() заново считывает состояние объекта из базы данных, обновляя все реквизиты формы актуальными значениями, которые были записаны в транзакции на сервере.

Использование механизма «ДополнительныеСвойства»

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

Для обмена информацией между различными этапами транзакции записи (из формы в ПередЗаписью, а оттуда в ОбработкуПроведения) используется системное свойство объекта ДополнительныеСвойства, представляющее собой структуру.

Посмотрим на пример передачи параметров. В модуле формы или в обработчике ПередЗаписью мы помещаем данные в структуру:

ЭтотОбъект.ДополнительныеСвойства.Вставить("ФлагОсобогоРежима", Истина);

Затем в процедуре ОбработкаПроведения мы можем легко проанализировать этот параметр:


Процедура ОбработкаПроведения(Отказ, РежимПроведения)
    
    Если ЭтотОбъект.ДополнительныеСвойства.Свойство("ФлагОсобогоРежима") Тогда
        // Выполняем специфические действия при проведении
    КонецЕсли;
    
КонецПроцедуры

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

← На главную