Как правильно изменить реквизиты документа или выполнить внешние действия при проведении в 1С

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

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

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

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

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

  1. Начинается транзакция.
  2. Выполняется процедура ПередЗаписью.
  3. Данные документа записываются в основную таблицу базы данных.
  4. Выполняется процедура ПриЗаписи.
  5. Выполняется процедура ОбработкаПроведения.
  6. Транзакция фиксируется (Commit), и изменения становятся доступны другим пользователям.

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

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

Способ 1. Использование очереди через Регистр Сведений (Лучшая практика)

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

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

Во-вторых, в процедуре ОбработкаПроведения мы добавляем запись в этот регистр. Это происходит очень быстро и не нагружает транзакцию. Посмотрим на пример кода:


Процедура ОбработкаПроведения(Отказ, РежимПроведения)
    
    // Выполняем стандартные движения документа
    // ...

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

В-третьих, создадим РегламентноеЗадание, которое будет запускаться раз в минуту. Оно будет выбирать необработанные записи из регистра, формировать XML и обновлять статус в регистре (или удалять запись). Такой подход гарантирует, что даже если сервер формирования XML будет недоступен, проведение документа не прервется ошибкой.

Способ 2. Использование фоновых заданий

Если XML нужно сформировать максимально быстро после проведения, но мы не хотим блокировать транзакцию, можно использовать механизм фоновых заданий. Рассмотрим, как это реализовать.

В конце процедуры ОбработкаПроведения (или в подписке на событие) мы запускаем фоновое задание, передавая ему ссылку на наш документ. Выясним, как это выглядит в коде:


Процедура ПриПроведенииЗапуститьВыгрузку(СсылкаНаДокумент) Экспорт
    // Параметры для фонового задания
    ПараметрыЗадания = Новый Массив;
    ПараметрыЗадания.Добавить(СсылкаНаДокумент);
    
    // Запуск асинхронного процесса
    ФоновыеЗадания.Выполнить("МойОбщийМодуль.СформироватьXMLПоДокументу", ПараметрыЗадания);
КонецПроцедуры

Важно помнить, что внутри фонового задания вы работаете с новым контекстом. Чтобы установить флаг XMLсформирован в самом документе, вам придется использовать метод "тихой записи". Проанализируем, как безопасно обновить реквизит документа из фонового задания:


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

Способ 3. Использование события формы ПослеЗаписи

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

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

  1. В модуле формы в процедуре ПослеЗаписи проверяем, был ли документ проведен.
  2. Вызываем процедуру формирования XML.
  3. Если нужно обновить реквизит на форме, делаем это программно.

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

Важный нюанс: Этот метод не сработает, если документ проводится программно (например, обработкой или в рамках обмена данными), так как форма при этом не открывается.

Способ 4. Передача флага через ДополнительныеСвойства

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

Рассмотрим пример. В ОбработкаПроведения мы ставим пометку:

ЭтотОбъект.ДополнительныеСвойства.Вставить("НужнаВыгрузкаXML", Истина);

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

Резюме и рекомендации

Подводя итог, выделим ключевые правила для разработчика:

  1. Никогда не вызывайте Записать() для того же самого объекта внутри его модуля в процедурах проведения или записи.
  2. Избегайте длительных операций (I/O, COM, HTTP) внутри транзакций. Проведение должно быть мгновенным.
  3. Для изменения реквизитов документа "по факту" проведения используйте отложенные механизмы: регламентные задания или фоновые процессы.
  4. Для фиксации самого факта необходимости выгрузки используйте Регистры Сведений — это обеспечит надежность системы (транзакционность: если проведение отменится, запись в регистре тоже исчезнет автоматически).

Соблюдение этих архитектурных принципов сделает вашу систему на базе 1С устойчивой к высоким нагрузкам и предотвратит трудноуловимые ошибки блокировок данных.

← На главную