При разработке конфигураций с нуля разработчики 1С часто сталкиваются с задачей организации очереди обработки документов. Классический сценарий: менеджер создает документ ЗаявкаКурьеру, а затем диспетчер отдельным документом ЗаданиеНаСбор подтягивает открытые заявки и распределяет их по курьерам. Возникает вопрос: какой объект метаданных использовать для хранения списка активных заявок, чтобы их было удобно получать в форме выборки или отчете?
Проанализируем основные архитектурные варианты решения этой задачи, их плюсы, минусы и потенциальные подводные камни с точки зрения производительности и удобства поддержки.
Разберем логику данного подхода: при проведении документа ЗаявкаКурьеру делается приход в регистр накопления остатков с измерением Заявка и ресурсом Количество = 1. При проведении документа ЗаданиеНаСбор делается расход на ту же сумму. В результате в виртуальной таблице Остатки остаются только открытые (нераспределенные) заявки.
Преимущества:
Недостатки и технические риски:
Рассмотрим два разных подхода к использованию регистров сведений для управления очередью задач.
Подход А: Периодический регистр сведений для хранения истории статусов
В регистр с измерением Заявка и ресурсом Состояние (типа ПеречислениеСсылка.СостоянияЗаявок) при каждом изменении статуса записывается новая строка. Для получения текущих нераспределенных заявок выполняется запрос к виртуальной таблице СрезПоследних.
Минус: По мере накопления истории за годы работы обращение к виртуальной таблице СрезПоследних без отбора по конкретной заявке будет замедляться, так как СУБД приходится выполнять подзапросы для поиска максимального периода по каждому измерению.
Подход Б: Непериодический регистр сведений «Очередь обработки» (Паттерн «Очередь»)
Создается независимый непериодический регистр сведений, например, ЗаявкиКРаспределению, с единственным измерением Заявка. При создании или открытии заявки в регистр добавляется запись. При назначении заявки курьеру запись из регистра физически удаляется.
Плюс: В таблице регистра хранятся только те записи, которые требуют внимания диспетчера. Запрос к основной таблице такого регистра выполняется мгновенно вне зависимости от общего объема базы данных.
Проанализируем применение штатного механизма платформы — объекта метаданных Задача. Этот объект специально спроектирован для реализации бизнес-процессов и операционных очередей.
При создании заявки программно или через бизнес-процесс формируется объект Задача. У задач из коробки есть:
Исполнена (булево).Этот подход идеально подходит, если цепочка обработки заявок со временем будет усложняться (появятся этапы согласования, отмены, проверки оплаты и т.д.).
Если объем создаваемых документов в системе не превышает сотни тысяч в месяц, архитектуру можно максимально упростить и отказаться от использования дополнительных регистров.
Выясним, как это реализовать: в документ ЗаявкаКурьеру добавляется реквизит СостояниеЗаявки. В свойствах этого реквизита в конфигураторе устанавливается значение Индексировать (или Индексировать с доп. упорядочиванием по дате).
Для получения списка нераспределенных заявок выполняем простой запрос к таблице документов:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЗаявкаКурьеру.Ссылка КАК Заявка,
| ЗаявкаКурьеру.Дата КАК Дата
|ИЗ
| Документ.ЗаявкаКурьеру КАК ЗаявкаКурьеру
|ГДЕ
| ЗаявкаКурьеру.СостояниеЗаявки = Значение(Перечисление.СостоянияЗаявок.ОжидаетРаспределения)
|УПОРЯДОЧИТЬ ПО
| Дата ВОЗР";
Благодаря индексу СУБД мгновенно выбирает документы с нужным статусом без полного сканирования таблицы документов.
Вне зависимости от того, какой объект выбран (регистр сведений, реквизит документа или регистр накопления), при одновременной работе нескольких диспетчеров возникает риск транзакционной ошибки — два пользователя могут параллельно выбрать одну и ту же заявку в разные документы ЗаданиеНаСбор.
Чтобы избежать этой проблемы, перед чтением и проверкой статуса заявки в транзакции проведения документа необходимо устанавливать явную управляемую блокировку:
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.ЗаявкиКРаспределению");
ЭлементБлокировки.УстановитьЗначение("Заявка", ВыбраннаяЗаявка);
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
Блокировка.Заблокировать();