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

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

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

В какой последовательности выполняются события при проведении документа

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

  1. Вызывается событие ПередЗаписью модуля документа и соответствующие подписки.
  2. Сам документ (его реквизиты и табличные части) записывается в базу данных.
  3. Срабатывает событие ПриЗаписи модуля документа и связанные с ним подписки. На этом этапе сам документ уже сохранен, но его движения еще не сформированы.
  4. Если у документа включено автоматическое удаление движений, старые движения очищаются.
  5. Вызывается процедура ОбработкаПроведения модуля документа, где формируются новые наборы записей регистров.
  6. Срабатывают внешние подписки на событие ОбработкаПроведения (параметр Источник передает ссылку на ДокументОбъект).
  7. Только после завершения всех процедур проведения платформа автоматически записывает сформированные наборы записей в базу данных (если для свойства документа установлено значение Записывать выбранные).
  8. Транзакция фиксируется, и данные становятся доступны другим сеансам.

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

Способ 1. Получение движений напрямую из оперативной памяти объекта

Если в подписке на событие требуется проанализировать движения, сформированные текущим документом, выполнять запрос к физической таблице базы данных вовсе не обязательно. Коллекция наборов записей уже заполнена и доступна через параметр подписки Источник.

Рассмотрим, как можно обойти строки нужного регистра непосредственно в памяти:


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

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


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

Способ 2. Принудительная запись набора движений в обработчике документа

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

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


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

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

Важное замечание: если в запросе обращаться к виртуальной таблице остатков (например, РегистрНакопления.ТоварыВРезерве.Остатки), передача стандартного МоментВремени() отсечет только что записанные движения, так как момент времени по умолчанию позиционирует остатки строго до движений документа. Чтобы учесть движения проводимого документа, используем объект Граница:


ГраницаПериода = Новый Граница(Источник.МоментВремени(), ВидГраницы.Включая);
Запрос.УстановитьПараметр("Период", ГраницаПериода);

Способ 3. Перенос логики в подписку на событие набора записей регистра

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

Создадим подписку на событие ПриЗаписи для объекта метаданных РегистрНакопленияНаборЗаписей.<ИмяРегистра>:

  1. В источнике подписки указываем соответствующий набор записей регистра.
  2. Событие: ПриЗаписи.
  3. В этом событии платформа гарантирует, что движения уже записаны в базу данных.
  4. Документ-регистратор получаем через отбор набора записей: Источник.Отбор.Регистратор.Значение.

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

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

← На главную