Как правильно обновить данные документа при его проведении и почему нельзя использовать метод Записать() в ОбработкаПроведения?

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

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

Анализ проблемы: почему возникает желание вызвать Записать()?

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

На первый взгляд кажется, что вызов ЭтотОбъект.Записать() решит проблему. Но это заблуждение ведет к каскаду технических проблем.

Технические риски и транзакционность

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

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

Использование флага ОбменДанными.Загрузка = Истина

Иногда программисты пытаются обойти рекурсию, используя конструкцию:


ОбменДанными.Загрузка = Истина;
Записать(РежимЗаписиДокумента.Запись);
ОбменДанными.Загрузка = Ложь;

Выясним, чем это грозит. Установка флага Загрузка = Истина отключает выполнение большинства прикладных проверок в модуле объекта и подписок на события. Это действительно позволяет избежать рекурсивного вызова ОбработкаПроведения, но порождает новые угрозы:

Проблема «замораживания» данных (Snapshot-эффект)

Рассмотрим ситуацию с точки зрения архитектуры данных. Попытка хранить результаты проведения (остатки или списания) в самом документе — это классический антипаттерн. В 1С источником истины об остатках являются регистры, а не документы.

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

Рекомендуемые альтернативные решения

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

1. Использование динамического вывода на форме

Если данные нужны только для того, чтобы пользователь их видел, не нужно сохранять их в реквизиты документа. Создадим на форме документа Реквизит формы (например, таблицу с типом ТаблицаЗначений). В событии формы ПослеЗаписи на сервере мы можем запросом получить актуальные движения документа по регистрам и заполнить эту таблицу.


&НаСервере
Процедура ОбновитьОтображениеДвижений()
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   Номенклатура,
        |   Количество
        |ИЗ
        |   РегистрНакопления.Запасы.Обороты(, , Регистратор, Регистратор = &Ссылка)";
    Запрос.УстановитьПараметр("Ссылка", Объект.Ссылка);
    // ... заполнение реквизита формы
КонецПроцедуры

2. Использование коллекции ДополнительныеСвойства

Если нам нужно передать вычисленные в ходе проведения данные в другие процедуры (например, для печати или вывода сообщения пользователю), используем объект ДополнительныеСвойства. Это коллекция, которая «живет» в памяти в течение всего цикла записи объекта.


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

3. Использование виртуальных таблиц в алгоритмах

Если другие 30 процедур документа используют данные из этой «злополучной» табличной части, их следует переписать. Проанализируем логику: вместо обращения к Объект.ТабличнаяЧастьСписания, эти процедуры должны выполнять запрос к виртуальным таблицам регистров накопления (Остатки, Обороты или ДвиженияССубконто). Это гарантирует, что система всегда работает с актуальными данными, независимо от того, проводились ли документы задним числом.

Итоги

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

← На главную