При разработке бизнес-процессов в платформе "1С:Предприятие" программисты часто сталкиваются с необходимостью связать вновь создаваемые задачи с другими объектами системы еще до того, как эти задачи будут окончательно записаны в базу данных. Типичный пример такой задачи — ведение специализированного журнала согласования или заполнение табличной части бизнес-процесса ссылками на формируемые задачи прямо в момент их генерации.
Попытка обратиться к реквизиту Ссылка у объектов из коллекции ФормируемыеЗадачи в обработчике ПриСозданииЗадач возвращает пустую ссылку. В этой статье мы подробно разберем, почему так происходит, как работает жизненный цикл объектов в 1С, как правильно использовать механизм предварительного выделения ссылок и где архитектурно вернее реализовывать журнализацию действий.
Для того чтобы понять суть проблемы, проанализируем жизненный цикл задачи внутри бизнес-процесса. Когда бизнес-процесс переходит на точку действия, платформа автоматически инициирует генерацию задач. На этом этапе вызывается обработчик события точки маршрута ПриСозданииЗадач.
В этот обработчик передается параметр ФормируемыеЗадачи, представляющий собой массив еще не записанных в базу данных объектов типа ЗадачаОбъект. Физически в СУБД этих записей еще не существует.
В платформе "1С:Предприятие" любой объект (будь то справочник, документ или задача) получает свою постоянную уникальную ссылку (UUID) только в момент первой успешной физической записи в базу данных (метод Записать()). До этого момента обращение к свойству Ссылка нового объекта всегда возвращает пустую ссылку соответствующего типа (например, Задачи.Адресация.ПустаяСсылка).
Именно поэтому следующий код, написанный интуитивно, не решает задачу:
Для Каждого ТекЗадача Из ФормируемыеЗадачи Цикл
НоваяСтрокаТЧ = РезультатыСогласования.Добавить;
// Ошибка! ТекЗадача.Ссылка на данном этапе пустая
НоваяСтрокаТЧ.Задача = ТекЗадача.Ссылка;
НоваяСтрокаТЧ.Сотрудник = ТекЗадача.Исполнитель;
КонецЦикла;
Иногда разработчики пытаются "выкрутиться", вызывая метод Записать() для задачи вручную прямо внутри обработчиков создания задач (например, в ПередФормированиемЗадач), чтобы принудительно получить ссылку. Рассмотрим, почему это крайне плохое решение.
Принудительный вызов записи нарушает транзакционную целостность платформенного механизма бизнес-процессов. Формирование задач происходит внутри общей транзакции запуска или продвижения бизнес-процесса. Если на одном из последующих этапов генерации задач или при записи самого бизнес-процесса произойдет ошибка, транзакция откатится.
Если же вы принудительно записывали задачи вне общего транзакционного контекста или искусственно фиксировали транзакции, вы рискуете получить:
Для решения подобных задач платформа 1С предоставляет встроенный безопасный механизм предварительного резервирования ссылки для нового, еще не записанного объекта. За это отвечают два метода объекта:
УстановитьСсылкуНового(<Ссылка>) — указывает платформе, какую именно ссылку необходимо присвоить объекту при его последующей записи в базу данных.ПолучитьСсылкуНового() — позволяет получить эту зарезервированную ссылку в коде до того, как объект будет физически записан.Рассмотрим классическую ошибку, которую совершают разработчики при попытке использовать этот механизм:
НоваяСсылкаЗадачи = Задачи.АИС_АПК_ДО_ЗадачиИсполнителей.ПолучитьСсылку(Новый УникальныйИдентификатор);
НоваяЗадача.УстановитьСсылкуНового(НоваяСсылкаЗадачи);
// Ошибка! Попытка прочитать НоваяЗадача.Ссылка всё равно вернет пустую ссылку!
НужнаяСсылка = НоваяЗадача.Ссылка;
Важное правило: Вызов метода УстановитьСсылкуНового() не заполняет свойство Ссылка мгновенно! Свойство Ссылка останется пустым до момента физического выполнения метода Записать(). Но платформа "запоминает" это сопоставление. Чтобы обратиться к этой зарезервированной ссылке прямо сейчас, необходимо вызвать метод ПолучитьСсылкуНового().
Рассмотрим, как должен выглядеть рабочий и безопасный код в обработчике бизнес-процесса, который заполняет табличную часть ссылками на будущие задачи:
Для Каждого ТекЗадача Из ФормируемыеЗадачи Цикл
// 1. Генерируем новый уникальный идентификатор (UUID)
НовыйУИД = Новый УникальныйИдентификатор;
// 2. Получаем ссылку для конкретного типа задач на основании созданного UUID
НоваяСсылкаЗадачи = Задачи.АИС_АПК_ДО_ЗадачиИсполнителей.ПолучитьСсылку(НовыйУИД);
// 3. Резервируем эту ссылку в объекте задачи
ТекЗадача.УстановитьСсылкуНового(НоваяСсылкаЗадачи);
// 4. Заполняем табличную часть бизнес-процесса.
// Вместо ТекЗадача.Ссылка используем метод ПолучитьСсылкуНового()!
НоваяСтрокаТЧ = РезультатыСогласования.Добавить;
НоваяСтрокаТЧ.Задача = ТекЗадача.ПолучитьСсылкуНового();
НоваяСтрокаТЧ.Сотрудник = ТекЗадача.Исполнитель;
НоваяСтрокаТЧ.ДатаСтарта = ТекЗадача.Дата;
НоваяСтрокаТЧ.НаРассмотрении = Истина;
// 5. Формируем уведомление, также используя зарезервированную ссылку
Текст = "Необходимо назначить исполнителя.";
Пояснение = "По " + ТекЗадача.Предмет;
ВидУведомления = Перечисления.АИС_АПК_ВидУведомленияПользователя.Объект;
АИС_АПК_УведомлениеПользователейСервер.ЗаписатьНовоеУведомление(
ТекЗадача.Исполнитель,
ТекЗадача.ПолучитьСсылкуНового(), // Здесь передаем подготовленную ссылку!
Текст,
Пояснение,
,
ВидУведомления
);
КонецЦикла;
Этот подход гарантирует, что при физической записи задач платформой они получат именно те идентификаторы, которые мы уже сохранили в табличной части бизнес-процесса и в уведомлениях. При этом транзакционная целостность полностью сохраняется.
Если вашей основной задачей является логирование факта создания задач в зависимости от конкретных точек маршрута бизнес-процесса, делать это в обработчиках самого бизнес-процесса — не самое лучшее архитектурное решение. Код получается перегруженным и размазанным по различным точкам маршрута.
Гораздо эффективнее использовать событийную модель самой задачи. Каждая задача, как стандартный объект базы данных, при записи проходит через обработчик модуля объекта ПриЗаписи.
В этот момент ссылка на задачу уже гарантированно существует, физически записана в БД и доступна через ЭтотОбъект.Ссылка. При этом у задачи есть реквизиты, связывающие ее с контекстом бизнес-процесса:
ЭтотОбъект.БизнесПроцесс — ссылка на родительский бизнес-процесс.ЭтотОбъект.ТочкаМаршрута — конкретная точка схемы, которая породила данную задачу.Разберем пример реализации централизованной журнализации в модуле объекта задачи (или через общую Подписку на событие ПриЗаписи для класса задач):
Процедура ПриЗаписи(Отказ)
// Если запись по какой-то причине отклонена, логирование не выполняем
Если Отказ Тогда
Возврат;
КонецЕсли;
// Ссылка на задачу на данном этапе гарантированно заполнена и валидна
СсылкаНаЗадачу = ЭтотОбъект.Ссылка;
БизнесПроцессСсылка = ЭтотОбъект.БизнесПроцесс;
ТекущаяТочкаМаршрута = ЭтотОбъект.ТочкаМаршрута;
// Фильтруем логирование в зависимости от того, какая точка маршрута создала задачу
Если ТекущаяТочкаМаршрута = БизнесПроцессы.АИС_АПК_Согласование.ТочкиМаршрута.НазначениеИсполнителя Тогда
// Вызываем логику журнализации для конкретной точки
ЖурналДействийСервер.ЗаписатьСобытие(
СсылкаНаЗадачу,
БизнесProcessСсылка,
"Создана задача назначения исполнителя для сотрудника: " + ЭтотОбъект.Исполнитель
);
ИначеЕсли ТекущаяТочкаМаршрута = БизнесПроцессы.АИС_АПК_Согласование.ТочкиМаршрута.СогласованиеРуководителем Тогда
ЖурналДействийСервер.ЗаписатьСобытие(
СсылкаНаЗадачу,
БизнесProcessСсылка,
"Создана задача согласования руководителем."
);
КонецЕсли;
КонецПроцедуры
Преимущества переноса журнализации в событие ПриЗаписи задачи:
Выделим основные правила работы со ссылками новых объектов в 1С:
ПриСозданииЗадач бизнес-процесса задачи еще не записаны в базу данных, поэтому их свойство Ссылка пустое.Записать() внутри генерации задач — критическая ошибка, нарушающая транзакционность платформы.УстановитьСсылкуНового(), а затем получайте ее значение с помощью метода ПолучитьСсылкуНового().ПриЗаписи в модуле объекта задачи или через механизм подписок на события платформы 1С.