Как правильно изменять данные документа при проведении и избежать ошибок обновления формы в 1С?

Программист 1С v8.3 (Обычные формы) IT и автоматизация бизнеса
← На главную

В процессе разработки конфигураций на платформе 1С:Предприятие разработчики иногда сталкиваются с ситуацией, когда при проведении документа требуется рассчитать и заполнить некоторые его реквизиты. Если выполнять модификацию и запись документа непосредственно внутри обработчика ОбработкаПроведения, пользователи при работе с открытой формой начинают получать системные предупреждения о необходимости перечитать данные или о том, что данные были изменены в другой сессии. Разберем, почему возникает такое поведение, какие риски оно несет и как правильно организовать архитектуру кода.

Почему нельзя изменять и записывать документ в обработке проведения

Проанализируем жизненный цикл записи документа в платформе 1С. Когда инициируется проведение, платформа запускает системную транзакцию и выполняет события в строгой последовательности:

  1. Вызывается обработчик ПередЗаписью модуля объекта.
  2. Вызывается обработчик ПриЗаписи модуля объекта, и сам документ фиксируется в базе данных.
  3. Вызывается обработчик ОбработкаПроведения, предназначенный исключительно для формирования движений по регистрам.

Попытка модифицировать реквизиты объекта или вызывать Записать() внутри ОбработкаПроведения приводит к серьезным проблемам:

  1. Конфликт версионирования данных: при повторной записи объекта в процессе проведения изменяется системный реквизит ВерсияДанных. Открытая форма документа сохраняет старую версию объекта. При попытке пользователя нажать «Записать» или «ОК» платформа фиксирует несовпадение версий и выдает ошибку оптимистической блокировки: «Данные были изменены или удалены в другой сессии».
  2. Избыточные блокировки и риск Deadlock: повторное обращение к объекту через Ссылка.ПолучитьОбъект() и его сохранение внутри открытой транзакции создают вложенные блокировки, что при одновременной работе нескольких пользователей неизбежно ведет к взаимным блокировкам базы данных.
  3. Рекурсивные вызовы: прямой вызов метода ЭтотОбъект.Записать() внутри обработчика проведения может спровоцировать рекурсию событий.

Правильное решение: перенос логики в событие «ПередЗаписью» модуля объекта

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

Разберем пример реализации в модуле объекта:


Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    
    // Проверяем, что документ именно проводится
    Если РежимЗаписи = РежимЗаписиДокумента.Проведение Тогда
        
        // Модифицируем необходимые реквизиты напрямую
        ЭтотОбъект.Комментарий = "Проведен: " + Строка(ТекущаяДата());
        
        // Если требуется пересчитать данные табличной части
        Для Каждого СтрТовары Из ЭтотОбъект.Товары Цикл
            Если СтрТовары.Количество > 0 Тогда
                СтрТовары.Сумма = СтрТовары.Количество * СтрТовары.Цена;
            КонецЕсли;
        КонецЦикла;
        
    КонецЕсли;
    
    // Метод Записать() вызывать НЕ нужно — платформа сделает это сама
КонецПроцедуры

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

Временное решение: обновление данных в событии формы «ПослеЗаписи»

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

Рассмотрим реализацию для обычных форм. В модуле формы документа переопределим обработчик ПослеЗаписи:


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

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


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

Альтернативный архитектурный подход: использование регистров сведений

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

Рекомендуется выносить подобные показатели в непериодический или периодический Регистр сведений, подчиненный регистратору (самому документу):

  1. В обработчике ОбработкаПроведения заполняются движения по набору записей регистра сведений.
  2. Реквизиты шапки и табличных частей документа остаются неизменными, сохраняя исходный ввод пользователя.
  3. Форма документа при необходимости считывает рассчитанные данные из регистра сведений только для отображения, что полностью исключает блокировки версий.
← На главную