В процессе разработки бизнес-логики в системе 1С:Предприятие часто возникает задача управления жизненным циклом документа. Документ может иметь множество различных состояний (статусов), выраженных через перечисление: например, «Новый», «В работе», «Отложен», «Выполнен» или «Отменен». Однако пользователю не должно быть дозволено переводить документ в любой статус хаотично. Логика процесса требует строгого ограничения доступных вариантов в зависимости от текущего состояния документа и прав самого пользователя.
В данной статье мы подробно разберем, как правильно реализовать ограничение списка статусов на форме документа. Мы проанализируем базовое решение, предложенное на форуме, выясним, почему опытные разработчики называют его «опасной игрой», и рассмотрим альтернативные, более надежные и архитектурно правильные подходы.
Рассмотрим классическое решение «в лоб», которое часто применяется на практике. Оно заключается в перехвате события начала выбора у поля ввода статуса на форме и динамической подмене параметров выбора.
Для реализации этого метода откроем форму документа, выберем элемент формы, связанный с реквизитом Статус, и создадим для него обработчик события НачалоВыбора. Напишем следующий код:
&НаКлиенте
Процедура СтатусНачалоВыбора(Элемент, ДанныеВыбора, СтандартнаяОбработка)
МассивСтатусов = Новый Массив;
// Если документ новый, разрешаем только начальный статус
Если Объект.Ссылка.Пустая() Тогда
МассивСтатусов.Добавить(ПредопределенноеЗначение("Перечисление.СУЗ_СтатусыЗаявок.Новая"));
Иначе
// Формируем список разрешенных переходов в зависимости от текущего статуса
Если Объект.Статус = ПредопределенноеЗначение("Перечисление.СУЗ_СтатусыЗаявок.Выполненная") Тогда
МассивСтатусов.Добавить(ПредопределенноеЗначение("Перечисление.СУЗ_СтатусыЗаявок.ВРаботе"));
ИначеЕсли Объект.Статус = ПредопределенноеЗначение("Перечисление.СУЗ_СтатусыЗаявок.ВРаботе") Тогда
МассивСтатусов.Добавить(ПредопределенноеЗначение("Перечисление.СУЗ_СтатусыЗаявок.Выполненная"));
МассивСтатусов.Добавить(PrematureЗначение("Перечисление.СУЗ_СтатусыЗаявок.Отложенная"));
МассивСтатусов.Добавить(ПредопределенноеЗначение("Перечисление.СУЗ_СтатусыЗаявок.Отмененная"));
ИначеЕсли Объект.Статус = ПредопределенноеЗначение("Перечисление.СУЗ_СтатусыЗаявок.Новая") Тогда
МассивСтатусов.Добавить(ПредопределенноеЗначение("Перечисление.СУЗ_СтатусыЗаявок.ВРаботе"));
МассивСтатусов.Добавить(ПредопределенноеЗначение("Перечисление.СУЗ_СтатусыЗаявок.Отложенная"));
МассивСтатусов.Добавить(ПредопределенноеЗначение("Перечисление.СУЗ_СтатусыЗаявок.Отмененная"));
ИначеЕсли Объект.Статус = ПредопределенноеЗначение("Перечисление.СУЗ_СтатусыЗаявок.Отложенная") Тогда
МассивСтатусов.Добавить(ПредопределенноеЗначение("Перечисление.СУЗ_СтатусыЗаявок.ВРаботе"));
МассивСтатусов.Добавить(ПредопределенноеЗначение("Перечисление.СУЗ_СтатусыЗаявок.Отмененная"));
ИначеЕсли Объект.Статус = ПредопределенноеЗначение("Перечисление.СУЗ_СтатусыЗаявок.Отмененная") Тогда
МассивСтатусов.Добавить(ПредопределенноеЗначение("Перечисление.СУЗ_СтатусыЗаявок.ВРаботе"));
КонецЕсли;
КонецЕсли;
// Передаем массив в параметры выбора элемента формы
НовыйПараметр = Новый ПараметрВыбора("Отбор.Ссылка", МассивСтатусов);
МассивПараметров = Новый Массив;
МассивПараметров.Добавить(НовыйПараметр);
Элемент.ПараметрыВыбора = Новый ФиксированныйМассив(МассивПараметров);
КонецПроцедуры
Как это работает: При нажатии кнопки выбора в поле ввода статуса срабатывает этот код. Он анализирует текущий статус документа и наполняет МассивСтатусов разрешенными значениями. Затем этот массив передается в свойство элемента формы ПараметрыВыбора в качестве отбора по ссылке. В результате пользователь видит в выпадающем списке только отфильтрованные значения.
Опытные разработчики не зря предупреждают об опасности такого подхода. Ограничение видимости элементов на уровне интерфейса конкретной формы — это лишь средство улучшения юзабилити (UX), но никак не надежная защита данных. Пользователь или внешняя система могут легко обойти это ограничение следующими путями:
Решение проблемы безопасности: Любые интерфейсные ограничения в форме должны дублироваться жесткой проверкой на сервере в модуле объекта документа. Напишем процедуру контроля переходов в модуле документа.
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
// Игнорируем проверку при загрузке данных (например, при обмене)
Если ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
// Проверяем корректность смены статуса для существующих документов
Если Не Ссылка.Пустая() Тогда
СтарыйСтатус = Ссылка.Статус;
НовыйСтатус = Статус;
Если СтарыйСтатус <> НовыйСтатус Тогда
ПроверитьКорректностьПерехода(СтарыйСтатус, НовыйСтатус, Отказ);
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Процедура ПроверитьКорректностьПерехода(СтарыйСтатус, НовыйСтатус, Отказ)
// Описываем разрешенные переходы (матрица переходов)
ТаблицаПереходов = Новый Соответствие;
МассивНовая = Новый Массив;
МассивНовая.Добавить(Перечисления.СУЗ_СтатусыЗаявок.ВРаботе);
МассивНовая.Добавить(Перечисления.СУЗ_СтатусыЗаявок.Отмененная);
ТаблицаПереходов.Вставить(Перечисления.СУЗ_СтатусыЗаявок.Новая, МассивНовая);
МассивВРаботе = Новый Массив;
МассивВРаботе.Добавить(Перечисления.СУЗ_СтатусыЗаявок.Выполненная);
МассивВРаботе.Добавить(Перечисления.СУЗ_СтатусыЗаявок.Отложенная);
ТаблицаПереходов.Вставить(Перечисления.СУЗ_СтатусыЗаявок.ВРаботе, МассивВРаботе);
// Выполняем проверку
ДоступныеСтатусы = ТаблицаПереходов.Получить(СтарыйСтатус);
Если ДоступныеСтатусы = Неопределено Или ДоступныеСтатусы.Найти(НовыйСтатус) = Неопределено Тогда
ТекстСообщения = СтрШаблон("Недопустимый переход статуса из %1 в %2!", СтарыйСтатус, НовыйСтатус);
ОбщегоНазначенияКлиентСервер.СообщитьПользователю(ТекстСообщения, ЭтотОбъект, "Статус", , Отказ);
КонецЕсли;
КонецПроцедуры
Для перечислений более лаконичным решением на форме является управление непосредственно списком выбора элемента управления, без использования механизмов отбора ПараметрыВыбора. Это гарантирует, что пользователь увидит ровно те элементы, которые мы добавим в список.
Рассмотрим реализацию этого метода. Код пишется в модуле формы документа:
&НаСервере
Процедура УстановитьОграниченияСтатусовНаСервере()
// Включаем режим выбора из списка для элемента формы
Элементы.Статус.РежимВыбораИзСписка = Истина;
СписокВыбора = Элементы.Статус.СписокВыбора;
СписокВыбора.Очистить();
// Всегда добавляем текущий статус, чтобы поле не казалось пустым
ТекущийСтатус = Объект.Статус;
Если ЗначениеЗаполнено(ТекущийСтатус) Тогда
СписокВыбора.Добавить(ТекущийСтатус);
КонецЕсли;
// Добавляем доступные для перехода статусы
Если ТекущийСтатус = ПредопределенноеЗначение("Перечисление.СУЗ_СтатусыЗаявок.Новая") Тогда
СписокВыбора.Добавить(ПредопределенноеЗначение("Перечисление.СУЗ_СтатусыЗаявок.ВРаботе"));
СписокВыбора.Добавить(ПредопределенноеЗначение("Перечисление.СУЗ_СтатусыЗаявок.Отмененная"));
ИначеЕсли ТекущийСтатус = ПредопределенноеЗначение("Перечисление.СУЗ_СтатусыЗаявок.ВРаботе") Тогда
СписокВыбора.Добавить(ПредопределенноеЗначение("Перечисление.СУЗ_СтатусыЗаявок.Выполненная"));
КонецЕсли;
КонецПроцедуры
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
УстановитьОграниченияСтатусовНаСервере();
КонецПроцедуры
Важная деталь: При заполнении СписокВыбора обязательно добавляйте в него текущее значение документа (даже если этот статус больше нельзя выбрать заново). Если текущего статуса документа не окажется в списке выбора, то при открытии формы поле элемента отобразится пустым, что запутает пользователя.
Еще один профессиональный способ ограничить список — перехватить событие НачалоВыбора, отключить стандартную обработку и подсунуть системе свой список значений через параметр ДанныеВыбора.
&НаКлиенте
Процедура СтатусНачалоВыбора(Элемент, ДанныеВыбора, СтандартнаяОбработка)
// Отменяем стандартное выпадающее меню
СтандартнаяОбработка = Ложь;
Список = Новый СписокЗначений;
// Пример заполнения списка в зависимости от условий
Список.Добавить(ПредопределенноеЗначение("Перечисление.СУЗ_СтатусыЗаявок.ВРаботе"), "В работу");
Список.Добавить(ПредопределенноеЗначение("Перечисление.СУЗ_СтатусыЗаявок.Отмененная"), "Отменить заявку");
// Возвращаем сформированный список в платформу
ДанныеВыбора = Список;
КонецПроцедуры
В первом сообщении темы автор упомянул задачу ограничения списка статусов «для пользователя». Часто бизнес-логика требует, чтобы обычный менеджер мог переводить документ только в статус «В работе», а утверждать документ (переводить в статус «Выполнен») мог только руководитель.
Для реализации ролевой модели мы можем использовать встроенную функцию платформы РольДоступна(). Изменим наш серверный метод фильтрации статусов:
&НаСервере
Процедура УстановитьСтатусыСУчетомРолей()
Элементы.Статус.РежимВыбораИзСписка = Истина;
СписокВыбора = Элементы.Статус.СписокВыбора;
СписокВыбора.Очистить();
// Добавляем текущий статус
СписокВыбора.Добавить(Объект.Статус);
// Проверяем роли текущего пользователя
ЭтоРуководитель = РольДоступна("РуководительОтделаПродаж");
ЭтоМенеджер = РольДоступна("МенеджерПродаж");
Если Объект.Статус = ПредопределенноеЗначение("Перечисление.СУЗ_СтатусыЗаявок.Новая") Тогда
СписокВыбора.Добавить(ПредопределенноеЗначение("Перечисление.СУЗ_СтатусыЗаявок.ВРаботе"));
КонецЕсли;
Если Объект.Статус = ПредопределенноеЗначение("Перечисление.СУЗ_СтатусыЗаявок.ВРаботе") Тогда
// Только руководитель может согласовать (выполнить) заявку
Если ЭтоРуководитель Тогда
СписокВыбора.Добавить(ПредопределенноеЗначение("Перечисление.СУЗ_СтатусыЗаявок.Выполненная"));
КонецЕсли;
СписокВыбора.Добавить(ПредопределенноеЗначение("Перечисление.СУЗ_СтатусыЗаявок.Отложенная"));
КонецЕсли;
КонецПроцедуры
Рекомендация по архитектуре: Если ролей и статусов в вашей системе много, избегайте прописывания имен ролей прямо в коде (жесткое кодирование/hardcode). Вместо этого рекомендуется создать регистр сведений «Доступные переходы статусов», где в разрезе ролей или пользователей указать разрешенные исходные и конечные статусы. При открытии формы достаточно будет сделать запрос к этому регистру и динамически построить список выбора.