В практике разработки на платформе 1С:Предприятие часто возникает задача: при проведении или записи одного документа автоматически создавать и проводить другой (например, подчиненный документ в бухгалтерском или налоговом учете). При этом разработчики сталкиваются с классической архитектурной дилеммой: как связать проведение двух документов в единую цепочку событий и в каком порядке должны выполняться транзакции.
В этой статье мы подробно разберем, почему стандартный порядок вызова событий в 1С изменить невозможно, проанализируем последовательность выполнения процедур в форме и объекте, а также рассмотрим корректные и безопасные способы решения этой задачи согласно стандартам разработки фирмы "1С".
Часто у начинающих разработчиков возникает путаница из-за непонимания разницы между контекстом модуля формы и модуля объекта. Давайте разберем по шагам, что происходит на платформе, когда пользователь нажимает кнопку «Провести и закрыть» (или «ОК» в обычных формах):
ПередЗаписью — самое первое событие. Здесь транзакция еще не началась. Мы можем задать вопрос пользователю (например, через диалоговое окно) или отменить запись.ПередЗаписьюНаСервере — подготовка данных формы к передаче в объект. Транзакция всё ещё не открыта.ОбработкаПроверкиЗаполнения — проверка обязательных реквизитов на уровне платформы.ПередЗаписью — первая точка расширения внутри транзакции. Здесь можно дозаполнить реквизиты объекта перед его физической записью в СУБД.ПриЗаписи — в этот момент объект физически записывается в базу данных. Ссылка для нового документа уже создана и доступна (ЭтотОбъект.Ссылка), но транзакция еще не зафиксирована.ОбработкаПроведения — выполняется только в том случае, если документ записывается в режиме проведения. Здесь формируются движения по регистрам.ПриЗаписиНаСервере — событие формы, выполняющееся внутри транзакции, но уже после того, как объект записан и проведен.ПослеЗаписиНаСервере — транзакция завершена, данные в безопасности.ПослеЗаписи — окончание интерактивного цикла.Важное примечание: Изменить этот порядок на уровне платформы невозможно. Событие ПриЗаписи в модуле объекта всегда выполняется до ОбработкаПроведения. Если вам кажется, что у вас сначала срабатывает проведение, а потом запись — скорее всего, вы перепутали событие модуля формы ПриЗаписиНаСервере (или ПослеЗаписи) с аналогичными событиями модуля объекта.
Представим ситуацию: вы хотите провести Документ №1 (основной) только тогда, когда успешно провелся созданный на его основании Документ №2 (подчиненный). Вы решаете разместить код создания и проведения Документа №2 внутри события ПриЗаписи или ОбработкаПроведения Документа №1.
С технической точки зрения это сработает: если при проведении Документа №2 возникнет ошибка (например, не хватит остатков на складе), система взведет флаг Отказ = Истина, транзакция откатится, и Документ №1 тоже не запишется и не проведется. Однако такой подход грубо нарушает стандарты разработки "1С" по следующим причинам:
Выяснив причины, перейдем к правильным способам реализации этой задачи.
Чтобы не изменять типовую конфигурацию и не перегружать модуль объекта, мы можем использовать Подписку на событие. Это позволяет выполнить наш код в нужный момент транзакции, сохранив конфигурацию на поддержке.
Выполним следующие шаги для реализации:
ОтражениеДокументовСобытия. Установим свойства: Сервер, Вызов сервера.СоздатьДокументБУПриПроведенииДокументОбъект.ИмяВашегоОсновногоДокументаОбработкаПроведенияПроанализируем пример кода для обработчика подписки:
Процедура СоздатьДокументБУПриПроведенииОбработкаПроведения(Источник, Отказ, РежимПроведения) Экспорт
// Если проведение основного документа уже отменено на предыдущих этапах, ничего не делаем
Если Отказ Тогда
Возврат;
КонецЕсли;
// Проверяем, не был ли подчиненный документ создан ранее
ПодчиненныйДокСсылка = НайтиПодчиненныйДокумент(Источник.Ссылка);
Если ПодчиненныйДокСсылка.Пустая() Тогда
ПодчиненныйДокОбъект = Документы.РегистрацияПрочихОпераций.СоздатьДокумент();
Иначе
ПодчиненныйДокОбъект = ПодчиненныйДокСсылка.ПолучитьОбъект();
КонецЕсли;
// Заполняем реквизиты нового документа на основании источника
ПодчиненныйДокОбъект.Дата = Источник.Дата;
ПодчиненныйДокОбъект.Организация = Источник.Организация;
// [Заполнение остальных реквизитов...]
Попытка
// Записываем и проводим подчиненный документ
ПодчиненныйДокОбъект.Записать(РежимЗаписиДокумента.Проведение);
Исключение
// Если подчиненный документ не провелся, отменяем проведение основного
Отказ = Истина;
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Не удалось провести связанный документ БУ! Ошибка: " + ОписаниеОшибки();
Сообщение.Сообщить();
КонецПопытки;
КонецПроцедуры
В данном примере, благодаря использованию подписки на событие ОбработкаПроведения, подчиненный документ создается только при успешном заполнении и проведении основного. Если в блоке Попытка...Исключение подчиненный документ вызывает ошибку проведения, мы устанавливаем Отказ = Истина, что автоматически прекращает транзакцию и отменяет проведение основного документа.
Если ваша база данных работает в режиме высокой интенсивности и проведение документов БУ занимает значительное время, идеальным решением будет **асинхронное разделение процессов**. Это полностью исключит взаимные блокировки таблиц.
Рассмотрим этот паттерн по шагам:
ОчередьОтраженияВУчете с измерениями ДокументСсылка (Тип: ДокументСсылка) и ресурсом Состояние (Тип: ПеречислениеСсылка).ПриЗаписи или ОбработкаПроведения основного документа просто делайте быструю запись в этот регистр:
Движение = РегистрыСведений.ОчередьОтраженияВУчете.СоздатьМенеджерЗаписи();
Движение.ДокументСсылка = Источник.Ссылка;
Движение.Состояние = Перечисления.СостоянияОтражения.ВКлюченВОчередь;
Движение.Записать(Истина);
ВКлюченВОчередь, программно создавать подчиненные документы, проводить их и менять статус в регистре на Отражен.Этот подход гарантирует стабильность работы СУБД: пользователи проводят первичные документы мгновенно, а подчиненные документы формируются в фоновом режиме с задержкой в несколько секунд, не мешая оперативной работе.
Мы выяснили, что изменить зашитый в платформу 1С порядок вызова процедур невозможно. Однако, понимая жизненный цикл транзакции записи и проведения объекта, вы можете элегантно решить любую задачу. Для простых конфигураций и нечастых операций отлично подойдет подписка на событие проведения с контролем параметра Отказ. Для крупных и масштабируемых систем следует отдавать предпочтение асинхронной обработке через очереди задач.