Статья актуальна для 8.3.22. Описанный в статье способ совместим с платформой 8.1 (немного отличается запись движений).
Статья НЕ рекомендуется к применению начинающими разработчиками, т.к. требует глубокого понимания рассмотренных механизмов платформы.
В управляемой форме объекта БД есть событие ПослеЗаписиНаСервере, но иногда хочется еще и обработчик ПослеЗаписи самого объекта. Он может быть полезен для выполнения необязательных действий, например оперативного запуска фонового задания обработки очереди, в которую записанный объект помещен и которая обрабатывается регламентным заданием.
Самым простым способом решения задачи является использование события ОбработкаПослеЗаписиВерсийИсторииДанных менеджера, которое выполняется в отдельном фоновом задании. Но он не дает доступа к не хранимому в БД состоянию объекта (свойства ОбменДанными и ДополнительныеСвойства) и требует включать историю данных для объекта. Поэтому рассмотрим более сложное решение.
Ранее в статье Безопасная работа с транзакциями во встроенном языке в разделе "Разрыв неявной транзакции" я описал как можно досрочно завершить транзакцию записи объекта, если она открыта неинтерактивно (не из формы объекта). Там я не рекомендовал применять этот прием в рабочем коде из-за ряда условий, которые нужно соблюдать для надежной работы такого кода. Теперь показываю как его применить для реализации обработчика ПослеЗаписи объекта. Наша цель - обеспечить выполнение кода
ЗафиксироватьТранзакцию(); // Фиксируем неявную транзакцию
// Делаем что то после транзакции записи
НачатьТранзакцию(); // Открывает фиктивную транзакцию, чтобы платформенная фиксация транзакции не вызвала ошибку
последним среди всех возможных обработчиков записи:
- обработчики событий записи
- обработчики подписок на события
- расширения обработчиков
- регистрация на узлах планов обмена (платформа)
- запись движений (платформа)
Тогда явное завершение транзакции не повлечет нарушения атомарности (неделимости) внесения изменений в БД.
Для достижений столь амбициозной цели нам понадобится механизм динамического подключения/отключения обработчиков объекта - операторы ДобавитьОбработчик и УдалитьОбработчик. Они легко находятся в синтакс-помощнике. Они по факту всегда добавляют обработчик в самый конец очереди обработчиков события конкретного объекта (проверено на 8.1, 8.2, 8.3.22), однако в документации это явно не обозначено. Вероятность изменения этого поведения в платформе представляется крайне низкой, т.к.
- платформа создает очереди обработчиков для всех событий объекта сразу при его создании
- если подключать обработчик не последним, то при подключении внутри обработки события будет риск что он не выполнится в текущем обходе очереди
У большинства типов метаданных последним в транзакции неинтерактивной записи выполняется событие ПриЗаписи. Поэтому нам нужно будет подключать обработчик на него. Но у документов это может быть и ОбработкаПроведения.
В транзакции интерактивной записи, т.е. открытой записью из формы объекта последним в транзакции всегда выполняется событие ПриЗаписиНаСервере. Подписаться на него невозможно. К тому же разорвать интерактивную транзакцию невозможно (вероятно ошибка платформы). Поэтому в транзакции интерактивной записи не будем разрывать транзакцию и будем вызывать обработчик ПослеЗаписи перед событием ПриЗаписиНаСервере. Такое решение кажется приемлемым, т.к. запись в форме выполняется реже, чем неинтерактивная запись объектов.
Создадим подписку на событие ПередЗаписью для документов и подписку на событие ПередЗаписью для остальных ссылочных типов объектов, у которых хотим создать обработчик ПослеЗаписи. Подключим внутри этих подписок динамический обработчик на последнее событие транзакции. А в самом динамическом обработчике последнего события выполним явно
- регистрацию на узлах планов обмена, т.к. платформа выполняет ее в самом конце неявной транзакции записи и потому теперь мы должны сделать это за нее
- запись наборов движений документа (начиная с 8.2 только с установленным свойством "Записывать"), т.к. платформа их записывает сразу после события ОбработкаПроведения
// Подписки ПередЗаписью
Процедура ОбъектПередЗаписью(Источник, Отказ) Экспорт
Сообщить("Подписка1 ПередЗаписью. Динамическое подключение обработчика");
УдалитьОбработчик Источник.ПриЗаписи, ПриЗаписиПоследний;
Добавитьобработчик Источник.ПриЗаписи, ПриЗаписиПоследний;
КонецПроцедуры
Процедура ДокументПередЗаписью(Источник, Отказ, РежимЗаписи, РежимПроведения) Экспорт
Сообщить("Подписка1 ПередЗаписью. Динамическое подключение обработчика");
УдалитьОбработчик Источник.ПриЗаписи, ПриЗаписиПоследний;
УдалитьОбработчик Источник.ОбработкаПроведения, ОбработкаПроведенияПоследний;
Если РежимЗаписи = РежимЗаписиДокумента.Запись Или РежимЗаписи = РежимЗаписиДокумента.ОтменаПроведения Тогда
Добавитьобработчик Источник.ПриЗаписи, ПриЗаписиПоследний;
ИначеЕсли РежимЗаписи = РежимЗаписиДокумента.Проведение Тогда
Добавитьобработчик Источник.ОбработкаПроведения, ОбработкаПроведенияПоследний;
КонецЕсли;
КонецПроцедуры
//////////////////////////
Процедура ПриЗаписиПоследний(Источник, Отказ) Экспорт
ПослеЗаписи(Источник, Отказ);
КонецПроцедуры
Процедура ОбработкаПроведенияПоследний(Знач Источник, Знач Отказ, Знач РежимПроведения)
Если Не Отказ Тогда
#Если Сервер И Не Сервер Тогда
Источник = Документы.Документ1.СоздатьДокумент();
#КонецЕсли
Для Каждого НаборДвижений Из Источник.Движения Цикл
#Если Сервер И Не Сервер Тогда
НаборДвижений = РегистрыНакопления.РегистрНакопления1.СоздатьНаборЗаписей();
#КонецЕсли
Если НаборДвижений.Записывать Тогда
НаборДвижений.Записать();
НаборДвижений.Записывать = Ложь;
КонецЕсли;
КонецЦикла;
КонецЕсли;
ПослеЗаписи(Источник, Отказ);
КонецПроцедуры
Процедура ПослеЗаписи(Источник, Отказ)
Если Не Отказ Тогда
ВыполнитьРегистрациюЕслиНадо(Источник);
Попытка
ЗафиксироватьТранзакцию();
ЭтоИнтерактивнаяЗапись = Ложь;
Исключение
ОписаниеОшибки = ОписаниеОшибки();
ЭтоИнтерактивнаяЗапись = Истина;
КонецПопытки;
Источник.ПослеЗаписи();
Если Не ЭтоИнтерактивнаяЗапись Тогда
НачатьТранзакцию();
КонецЕсли;
КонецЕсли;
КонецПроцедуры
// https://its.1c.ru/db/intgr83#content:121:hdoc
Процедура ВыполнитьРегистрациюЕслиНадо(Источник)
#Если Сервер И Не Сервер Тогда
Источник = Справочники.Справочник1.СоздатьЭлемент();
#КонецЕсли
Получатели = Источник.ОбменДанными.Получатели;
Если Получатели.Количество() > 0 Тогда
ПланыОбмена.ЗарегистрироватьИзменения(Получатели, Источник);
Сообщить(СтрШаблон("Регистрация на %1 узлах планов обмена", Получатели.Количество()));
Получатели.Очистить();
Источник.ДополнительныеСвойства.Вставить("ПолучателиОчищены");
ИначеЕсли Источник.ДополнительныеСвойства.Свойство("ПолучателиОчищены") Тогда
ВызватьИсключение "Необходимо заново заполнить получателей объекта или удалить доп. свойство ""ПолучателиОчищены""";
КонецЕсли;
КонецПроцедуры
// Демо подписка
Процедура ДемоПриЗаписи(Источник, Отказ) Экспорт
Сообщить("Подписка Демо ПриЗаписи");
КонецПроцедуры
Теперь вставим в модули объектов обработчик ПослеЗаписи
Процедура ПослеЗаписи() Экспорт
Сообщить("ПослеЗаписи. Транзакция = " + ТранзакцияАктивна());
КонецПроцедуры
Проверка
К статье приложена демонстрационная база с подсистемой СобытиеПослеЗаписи (общий модуль и 2 подписки). Запустив эту базу в обычном клиентском приложении, вы увидите форму с кнопкой "Тест" и формы модифицированных рассмотренным в статье образом справочника и документа. Кнопка "Тест" для выполнит следующий код
ф = Справочники.Справочник1.я.ПолучитьОбъект();
Сообщить(">>> Новая запись справочника");
ф.Записать();
Сообщить(">>> Повторная запись справочника");
ф.Записать();
ф = Справочники.Справочник1.я.ПолучитьОбъект().ПолучитьФорму();
Если ТипЗнч(Ф) = Тип("Форма") Тогда
Сообщить(">>> Новая запись справочника в форме");
ф.ЗаписатьВФорме();
Сообщить(">>> Повторная запись справочника в форме");
ф.ЗаписатьВФорме();
Иначе
Сообщить(">>> Новая запись справочника в форме");
ф.Записать();
Сообщить(">>> Повторная запись справочника в форме");
ф.Записать();
КонецЕсли;
ф = Документы.Документ1.СоздатьДокумент();
ф.Дата = ТекущаяДата();
Сообщить(">>> Новая запись документа");
ф.Записать(РежимЗаписиДокумента.Запись);
Сообщить(">>> Повторная запись документа с проведением");
ф.Записать(РежимЗаписиДокумента.Проведение);
Сообщить(">>> Повторная запись документа с отменой проведения");
ф.Записать(РежимЗаписиДокумента.ОтменаПроведения);
ф = Документы.Документ1.СоздатьДокумент().ПолучитьФорму();
ф.Дата = ТекущаяДата();
Если ТипЗнч(Ф) = Тип("Форма") Тогда
Сообщить(">>> Новая запись документа в форме");
ф.ЗаписатьВФорме(РежимЗаписиДокумента.Запись);
Сообщить(">>> Повторная запись документа с проведением в форме");
ф.ЗаписатьВФорме(РежимЗаписиДокумента.Проведение);
Сообщить(">>> Повторная запись документа с отменой проведения в форме");
ф.ЗаписатьВФорме(РежимЗаписиДокумента.ОтменаПроведения);
Иначе
Сообщить(">>> Новая запись документа в форме");
ф.Записать(РежимЗаписиДокумента.Запись);
Сообщить(">>> Повторная запись документа с проведением в форме");
ф.Записать(РежимЗаписиДокумента.Проведение);
Сообщить(">>> Повторная запись документа с отменой проведения в форме");
ф.Записать(РежимЗаписиДокумента.ОтменаПроведения);
КонецЕсли;
Получим результат:
>>> Новая запись справочника
Подписка1 ПередЗаписью. Динамическое подключение обработчика
ПриЗаписи модуля объекта
ПриЗаписи расширения
ПриЗаписи модуля объекта динамический
Подписка Демо ПриЗаписи
Регистрация на 1 узлах планов обмена
ПослеЗаписи. Транзакция = Нет
>>> Повторная запись справочника
Подписка1 ПередЗаписью. Динамическое подключение обработчика
ПриЗаписи модуля объекта
ПриЗаписи расширения
ПриЗаписи модуля объекта динамический
Подписка Демо ПриЗаписи
Регистрация на 1 узлах планов обмена
ПослеЗаписи. Транзакция = Нет
>>> Новая запись справочника в форме
Подписка1 ПередЗаписью. Динамическое подключение обработчика
ПриЗаписи модуля объекта
ПриЗаписи расширения
ПриЗаписи модуля объекта динамический
Подписка Демо ПриЗаписи
Регистрация на 1 узлах планов обмена
ПослеЗаписи. Транзакция = Да
При записи в форме
>>> Повторная запись справочника в форме
Подписка1 ПередЗаписью. Динамическое подключение обработчика
ПриЗаписи модуля объекта
ПриЗаписи расширения
ПриЗаписи модуля объекта динамический
Подписка Демо ПриЗаписи
Регистрация на 1 узлах планов обмена
ПослеЗаписи. Транзакция = Да
При записи в форме
>>> Новая запись документа
Подписка1 ПередЗаписью. Динамическое подключение обработчика
ПриЗаписи модуля объекта
ПриЗаписи расширения
Подписка Демо ПриЗаписи
Регистрация на 1 узлах планов обмена
ПослеЗаписи. Транзакция = Нет
>>> Повторная запись документа с проведением
Подписка1 ПередЗаписью. Динамическое подключение обработчика
ПриЗаписи модуля объекта
ПриЗаписи расширения
Подписка Демо ПриЗаписи
ОбработкаПроведения модуля объекта
ОбработкаПроведения расширения
Записан регистрРегистрНакопления1
Регистрация на 1 узлах планов обмена
ПослеЗаписи. Транзакция = Нет
>>> Повторная запись документа с отменой проведения
Подписка1 ПередЗаписью. Динамическое подключение обработчика
ОбработкаУдаленияПроведения модуля объекта
Записан регистрРегистрНакопления2
Записан регистрРегистрНакопления1
ПриЗаписи модуля объекта
ПриЗаписи расширения
Подписка Демо ПриЗаписи
Регистрация на 1 узлах планов обмена
ПослеЗаписи. Транзакция = Нет
>>> Новая запись документа в форме
Подписка1 ПередЗаписью. Динамическое подключение обработчика
ПриЗаписи модуля объекта
ПриЗаписи расширения
Подписка Демо ПриЗаписи
Регистрация на 1 узлах планов обмена
ПослеЗаписи. Транзакция = Да
При записи в форме
>>> Повторная запись документа с проведением в форме
Подписка1 ПередЗаписью. Динамическое подключение обработчика
ПриЗаписи модуля объекта
ПриЗаписи расширения
Подписка Демо ПриЗаписи
ОбработкаПроведения модуля объекта
ОбработкаПроведения расширения
Записан регистрРегистрНакопления1
Регистрация на 1 узлах планов обмена
ПослеЗаписи. Транзакция = Да
При записи в форме
>>> Повторная запись документа с отменой проведения в форме
Подписка1 ПередЗаписью. Динамическое подключение обработчика
ОбработкаУдаленияПроведения модуля объекта
Записан регистрРегистрНакопления2
Записан регистрРегистрНакопления1
ПриЗаписи модуля объекта
ПриЗаписи расширения
Подписка Демо ПриЗаписи
Регистрация на 1 узлах планов обмена
ПослеЗаписи. Транзакция = Да
При записи в форме