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

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

В практике разработки на платформе «1С:Предприятие 8» часто возникает классическая задача: при проведении одного документа (например, ПоступлениеТоваров) необходимо автоматически создать, заполнить и провести другой, зависимый документ (например, ПересортицаТоваров или СписаниеТоваров).

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

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

---

Почему нельзя просто провести второй документ в обработчике «ОбработкаПроведения»?

Для того чтобы понять, почему стандартные методы не работают, давайте разберем жизненный цикл транзакции записи документа в 1С. Когда пользователь или внешняя обработка нажимает кнопку «Провести», платформа открывает единую транзакцию СУБД. Внутри этой транзакции последовательно выполняются следующие события:

  1. ПередЗаписью (модуль документа / подписка).
  2. Запись самого документа в базу данных.
  3. ПриЗаписи (модуль документа / подписка).
  4. ОбработкаПроведения (модуль документа, где формируются движения).
  5. ПриЗаписи (повторно, уже для записи сформированных движений по регистрам).
  6. Фиксация (коммит) транзакции в СУБД.

Только после успешного завершения шага 6 данные физически записываются в таблицы базы данных и становятся доступными для других сеансов и запросов.

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

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

---

Решение 1. Очередь отложенных операций через регламентное задание (Рекомендуемый метод)

Это классический и самый отказоустойчивый подход, который используется в крупных тиражных конфигурациях (например, в решениях линейки ERP / КА / УТ для отражения документов в регламентированном учете).

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

Шаг 1. Создание регистра очереди

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

Шаг 2. Регистрация документа в очереди

В подписке на событие ПриЗаписи для документа ПоступлениеТоваров (или непосредственно в модуле объекта) пишем код, который добавляет документ в очередь, если он проводится:


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

Шаг 3. Создание регламентного задания для обработки очереди

Создаем общий модуль (например, ОчередьПроведенияСервер) и пишем метод, который будет регулярно запускаться регламентным заданием, обрабатывать очередь, создавать документы пересортицы и проводить их:


Процедура ОбработатьОчередьПересортицы() Экспорт
    
    // Выбираем все документы из очереди
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	Очередь.Документ КАК Документ
        |ИЗ
        |	РегистрСведений.ОчередьОтложенныхОперацийПересортицы КАК Очередь";
    
    РезультатЗапроса = Запрос.Выполнить();
    Если РезультатЗапроса.Пустой() Тогда
        Возврат;
    КонецЕсли;
    
    Выборка = РезультатЗапроса.Выбрать();
    
    Пока Выборка.Следующий() Цикл
        
        НачатьТранзакцию();
        Попытка
            
            // Получаем объект исходного документа
            ДокПоступление = Выборка.Документ.ПолучитьОбъект();
            
            Если ДокПоступление = Неопределено Тогда
                ВызватьИсключение "Документ не найден в базе данных.";
            КонецЕсли;
            
            // Логика создания и заполнения документа "Пересортица"
            НоваяПересортица = Документы.ПересортицаТоваров.СоздатьДокумент();
            НоваяПересортица.Дата = ТекущаяДатаСеанса();
            НоваяПересортица.Организация = ДокПоступление.Организация;
            НоваяПересортица.Склад = ДокПоступление.Склад;
            
            // Заполняем табличную часть на основании поступления
            Для Каждого СтрокаТЧ Из ДокПоступление.Товары Цикл
                // Здесь ваша специфика общепита/производства по замене товара
                Если ТребуетсяПересортица(СтрокаТЧ.Номенклатура) Тогда
                    НоваяСтрока = НоваяПересортица.Товары.Добавить();
                    НоваяСтрока.НоменклатураСписание = СтрокаТЧ.Номенклатура;
                    НоваяСтрока.НоменклатураПриход = НайтиАналогДляПересортицы(СтрокаТЧ.Номенклатура);
                    НоваяСтрока.Количество = СтрокаТЧ.Количество;
                КонецЕсли;
            КонецЦикла;
            
            // Если документ заполнился, записываем и проводим его
            Если НоваяПересортица.Товары.Количество() > 0 Тогда
                НоваяПересортица.Записать(РежимЗаписиДокумента.Проведение);
            КонецЕсли;
            
            // Удаляем успешно обработанный документ из очереди
            НаборЗаписей = РегистрыСведений.ОчередьОтложенныхОперацийПересортицы.СоздатьНаборЗаписей();
            НаборЗаписей.Отбор.Документ.Установить(Выборка.Документ);
            НаборЗаписей.Записать();
            
            ЗафиксироватьТранзакцию();
            
        Исключение
            ОтменитьТранзакцию();
            
            // Логируем ошибку, чтобы не зациклить регламентное задание
            ЗаписьЖурналаРегистрации(
                "ОбработкаОчередиПересортицы",
                УровеньЖурналаРегистрации.Ошибка,,,
                "Ошибка обработки документа " + Выборка.Документ + ": " + ОписаниеОшибки()
            );
        КонецПопытки;
        
    КонецЦикла;
    
КонецПроцедуры

---

Решение 2. Мгновенный запуск через Фоновое задание (Асинхронный запуск без ожидания)

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

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


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

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


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

---

Решение 3. Интерактивный метод через событие формы «ПослеЗаписиНаСервере»

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

Разберем по шагам логику этого метода:

  1. Пользователь нажимает кнопку «Провести и закрыть» или «Провести».
  2. Документ проводится, транзакция закрывается.
  3. Платформа вызывает обработчик формы ПослеЗаписиНаСервере. На этом этапе остатки по регистру от Поступления уже актуальны.
  4. Мы программно создаем, заполняем и проводим Пересортицу прямо из этого обработчика.

Пример реализации в модуле формы документа ПоступлениеТоваров:


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

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

---

Решение 4. Использование временного контейнера данных через «ДополнительныеСвойства»

Если вам все же необходимо передать рассчитанные данные из этапа проведения на этап после проведения, не делая лишних запросов к базе данных, используйте свойство объекта ДополнительныеСвойства.

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

Разберем пример:

Шаг 1. Наполнение структуры данных в модуле проведения поступления


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

Шаг 2. Извлечение данных в подписке «ПриЗаписи»

Подписка на событие ПриЗаписи выполняется в самом конце транзакции, когда основные движения уже сформированы:


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

Внимание: Этот метод по-прежнему оставляет создание подчиненного документа внутри единой транзакции записи основного. Используйте его только тогда, когда вы на 100% уверены в отсутствии конфликтов блокировок и уверены, что подчиненный документ гарантированно проведется без ошибок.

---

Решение 5. Архитектурный паттерн «Документ-контейнер»

Часто программисты пытаются решить техническую проблему там, где требуется изменение бизнес-логики и архитектуры. Если в вашей системе поступление товара и его пересортица (выпуск продукции, разукомплектация) всегда происходят одновременно и не могут существовать друг без друга, лучшим решением будет создание **одного единого документа** (документа-контейнера).

Вместо создания двух документов (Поступление + Пересортица) спроектируйте один документ (например, ПоступлениеСПереработкой):

---

Резюме: какой метод выбрать?

Выбор оптимального метода зависит от требований вашего бизнес-процесса:

  1. Если критически важна производительность системы и работа в многопользовательском режиме (много пользователей одновременно проводят документы) — используйте Решение 1 (Очередь + регламентное задание). Это стандарт разработки 1С.
  2. Если пользователю необходимо увидеть результат проведения пересортицы моментально, но сохраняется риск блокировок при общей транзакции — используйте Решение 2 (Фоновое задание).
  3. Если проведение документов происходит только вручную операторами из формы и вам нужно максимально простое решение без создания очередей — используйте Решение 3 (ПослеЗаписиНаСервере в форме).
  4. Если бизнес-логика требует жесткой связки «все или ничего» и операции выполняются неразрывно — пересмотрите архитектуру в пользу Решения 5 (Документ-контейнер).
← На главную