При проектировании бизнес-процессов в среде 1С:Предприятие 8 начинающие разработчики часто сталкиваются с фундаментальным ограничением платформы при работе с задачами. Типичный сценарий: в рамках бизнес-процесса создается задача (например, проверка контрагента юристом). Требуется динамически менять статус этого контрагента в информационной базе в зависимости от действий пользователя: когда задача просто "висит" в списке, когда ее приняли к исполнению, когда отменили принятие и когда, наконец, выполнили.
Попытка реализовать эту логику напрямую через карту маршрута бизнес-процесса (например, используя точки выбора вариантов сразу после задачи) приводит к тому, что при нажатии кнопки "Принять к исполнению" в интерфейсе ничего не происходит, а программный код в обработчиках карты маршрута не вызывается. Давайте подробно разберем, почему возникает эта проблема, как устроена механика задач в 1С и как правильно реализовать отслеживание всех состояний задачи с технической точки зрения.
Для начала проанализируем поведение платформы 1С. Бизнес-процесс — это жестко регламентированная цепочка взаимосвязанных шагов. Продвижение по карте маршрута бизнес-процесса происходит строго в момент выполнения задачи. Технически это означает, что реквизит объекта Задача под названием Выполнена принимает значение Истина (обычно это происходит при вызове метода ВыполнитьЗадачу()).
Когда пользователь выполняет действия "Принять к исполнению" или "Отменить принятие к исполнению", карта маршрута бизнес-процесса "молчит". С точки зрения платформы, бизнес-процесс все еще находится в той же самой точке действия. Изменяются лишь реквизиты самой задачи (заполняется или очищается реквизит Исполнитель, меняются параметры адресации). Никакого перехода к следующему блоку маршрута (включая "Точку выбора вариантов" или "Условие") не происходит и происходить не должно. Пытаться выстроить маршрут БП из точек, реагирующих на принятие задачи — это грубая архитектурная ошибка, которая перегружает схему и делает систему неработоспособной.
Чтобы корректно решить задачу и своевременно менять статусы связанных объектов (в нашем примере — контрагентов), мы должны перенести логику отслеживания состояний непосредственно в модуль самой задачи. Нам не нужно менять карту маршрута бизнес-процесса. Она должна оставаться максимально простой: одна точка действия "Проверить контрагента". Все промежуточные статусы мы будем перехватывать программно.
Рассмотрим подробнее, как это сделать. Мы будем использовать предопределенный обработчик события ПередЗаписью в модуле объекта нашей задачи. Этот обработчик идеален, так как он срабатывает при любом интерактивном или программном изменении задачи.
Принятие задачи к исполнению конкретным пользователем технически выражается в том, что реквизит Исполнитель заполняется ссылкой на пользователя (ранее задача могла быть адресована роли, например "Юристы", и персональный исполнитель был пуст). Отмена принятия к исполнению — это очистка реквизита Исполнитель. Выполнение задачи — это изменение флага Выполнена с Ложь на Истина.
Разберем программный код, который необходимо разместить в модуле объекта задачи (например, Задача.ЗадачаИсполнителя):
Процедура ПередЗаписью(Отказ)
// Исключаем обработку для новых, еще не записанных в базу данных задач
Если ЭтоНовый() Тогда
Возврат;
КонецЕсли;
// Получаем текущие значения реквизитов из базы данных ДО момента записи изменений
СтруктураПолей = "Исполнитель, Выполнена, БизнесПроцесс";
СтарыеДанные = ОбщегоНазначения.ЗначенияРеквизитовОбъекта(Ссылка, СтруктураПолей);
СтарыйИсполнитель = СтарыеДанные.Исполнитель;
НовыйИсполнитель = ЭтотОбъект.Исполнитель;
СтароеВыполнение = СтарыеДанные.Выполнена;
НовоеВыполнение = ЭтотОбъект.Выполнена;
// Получаем ссылку на связанного контрагента через наш бизнес-процесс
// Предполагается, что в бизнес-процессе есть реквизит "Предмет" или "Контрагент"
СсылкаНаБП = СтарыеДанные.БизнесПроцесс;
Если НЕ ЗначениеЗаполнено(СсылкаНаБП) Тогда
Возврат;
КонецЕсли;
КонтрагентСсылка = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(СсылкаНаБП, "Контрагент");
Если НЕ ЗначениеЗаполнено(КонтрагентСсылка) Тогда
Возврат;
КонецЕсли;
// Определяем изменения состояний задачи
// Сценарий 1: Задача ПРИНЯТА к исполнению (Исполнитель назначен, задача не выполнена)
Если ЗначениеЗаполнено(НовыйИсполнитель) И НЕ ЗначениеЗаполнено(СтарыйИсполнитель) И НЕ НовоеВыполнение Тогда
УстановитьСтатусКонтрагента(КонтрагентСсылка, ПредопределенноеЗначение("Перечисление.СтатусыКонтрагентов.НаПроверке"));
// Сценарий 2: ОТМЕНА принятия задачи (Исполнитель был очищен обратно)
ИначеЕсли НЕ ЗначениеЗаполнено(НовыйИсполнитель) И ЗначениеЗаполнено(СтарыйИсполнитель) И НЕ НовоеВыполнение Тогда
УстановитьСтатусКонтрагента(КонтрагентСсылка, ПредопределенноеЗначение("Перечисление.СтатусыКонтрагентов.Новый"));
// Сценарий 3: Задача ВЫПОЛНЕНА
ИначеЕсли НовоеВыполнение И НЕ СтароеВыполнение Тогда
УстановитьСтатусКонтрагента(КонтрагентСсылка, ПредопределенноеЗначение("Перечисление.СтатусыКонтрагентов.Проверен"));
КонецЕсли;
КонецПроцедуры
Обратите внимание на использование функции ОбщегоНазначения.ЗначенияРеквизитовОбъекта. Это стандартная функция из Библиотеки Стандартных Подсистем (БСП), которая позволяет быстро получить значения реквизитов из базы данных без явного обращения к транзакционному объекту, что существенно повышает производительность системы.
Для непосредственного изменения статуса контрагента напишем вспомогательную процедуру. Ее можно разместить в этом же модуле или вынести в общий серверный модуль:
Процедура УстановитьСтатусКонтрагента(КонтрагентСсылка, НовыйСтатус) Экспорт
// Создаем объект для записи изменений в базу данных
КонтрагентОбъект = КонтрагентСсылка.ПолучитьОбъект();
Если КонтрагентОбъект <> Неопределено Тогда
// Проверяем, отличается ли статус, чтобы избежать лишней перезаписи
Если КонтрагентОбъект.Статус <> НовыйСтатус Тогда
КонтрагентОбъект.Статус = НовыйСтатус;
Попытка
КонтрагентОбъект.Записать();
Исключение
ВызватьИсключение "Не удалось обновить статус контрагента: " + КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
КонецПопытки;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Некоторые разработчики предпочитают решать подобные задачи через изменение кода форм. Например, прописывать вызов обработки изменения статусов при нажатии кнопок в форме задачи или в форме списка задач. Рассмотрим, почему этот подход имеет существенные недостатки:
Именно поэтому перехват событий на уровне модуля объекта (подписка на событие или предопределенные процедуры модуля) является единственным надежным промышленным стандартом разработки в среде 1С.
Если ваша конфигурация разработана на основе БСП (что является стандартом для большинства современных конфигураций 1С, таких как ERP, КА, УТ, ЗУП), то в ней уже развернута подсистема "Бизнес-процессы и задачи".
В БСП предусмотрены специальные переопределяемые модули. Проанализируйте общий модуль БизнесПроцессыИЗадачиПереопределяемый. В нем содержатся обработчики, которые вызываются при изменении состояния задач стандартными механизмами БСП. Интеграция вашего кода в эти обработчики гарантирует корректное обновление статусов связанных сущностей при использовании любых стандартных интерфейсных окон подсистемы задач.
Подведем итог нашему разбору. При проектировании систем с использованием бизнес-процессов придерживайтесь следующих правил:
Применяя данные подходы, вы получите чистую, легко читаемую карту маршрута бизнес-процесса и надежно работающий программный код, устойчивый к изменениям интерфейса конфигурации.