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

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

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

Разбор механизма: почему ОбработкаПроведения не подходит для записи реквизитов

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

Когда мы пишем ЭтотОбъект.ИмяРеквизита = Значение внутри ОбработкаПроведения, мы действительно меняем значение в оперативной памяти (в объекте ЭтотОбъект). Но платформа после завершения этой процедуры не выполняет повторную запись «шапки» документа в базу. Она фиксирует только сформированные движения и завершает транзакцию. Именно поэтому изменения «пропадают».

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

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

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

  1. Рекурсия: Метод Записать() инициирует весь цикл событий заново: ПередЗаписью, ПриЗаписи и снова ОбработкаПроведения. Это гарантированно приведет к бесконечному циклу и аварийному завершению работы системы с ошибкой переполнения стека.
  2. Блокировки: Объект уже заблокирован в текущей транзакции. Попытка вызвать запись внутри записи может вызвать конфликт блокировок и зависание сеанса.
  3. Нарушение целостности: Если в процессе проведения выяснится, что остатков нет, и платформа должна отменить транзакцию, принудительный вызов записи может привести к непредсказуемому поведению системы.

Решение №1: Использование события ПередЗаписью

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

Посмотрим на пример реализации:


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

КонецПроцедуры

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

Решение №2: Использование подчиненного регистра сведений

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

В этом случае в ОбработкаПроведения мы просто записываем данные в этот регистр. Это не нарушает логику 1С, так как запись в регистры — это и есть прямая обязанность данной процедуры. Посмотрим на пример кода:


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

КонецПроцедуры

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

Решение №3: Оптимизация через Динамический список

Как справедливо замечено в сообщении №8, хранение флага в документе часто бывает избыточным. Если ваша цель — просто вывести информацию в список документов, мы можем вычислить её «на лету» в запросе динамического списка.

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

  1. Откройте форму списка документа.
  2. Перейдите в свойства динамического списка и выберите «Произвольный запрос».
  3. В тексте запроса добавьте левое соединение с таблицей остатков или вашим расчетным регистром.

Пример запроса для динамического списка:


ВЫБРАТЬ
    ДокументПродажа.Ссылка,
    ДокументПродажа.Дата,
    ДокументПродажа.Номер,
    ВЫБОР
        КОГДА ОстаткиТоваров.ВНаличииОстаток > 0 ТОГДА Истина
        ИНАЧЕ Ложь
    КОНЕЦ КАК ЕстьНаОстатке
ИЗ
    Документ.Продажа КАК ДокументПродажа
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, ) КАК ОстаткиТоваров
    ПО ДокументПродажа.Товар = ОстаткиТоваров.Номенклатура

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

← На главную