Каждый разработчик 1С рано или поздно сталкивается с бизнес-требованием: заблокировать документ от изменений после того, как он перешел в определенный статус (например, «Согласован», «Оплачен» или «Услуга оказана»). При этом важно сохранить возможность редактирования для администраторов (пользователей с полными правами) и оставить доступными для обычных пользователей некоторые отдельные поля или кнопки (например, кнопку отправки на согласование или поле для комментариев).
В рамках этой статьи мы подробно разберем, как решить эту задачу на управляемых формах в платформе «1С:Предприятие 8.3». Мы рассмотрим несколько подходов: от простых и быстрых до профессиональных и максимально безопасных.
Перед тем как настраивать интерфейс управляемой формы, мы должны уяснить важнейшее правило разработки в 1С: интерфейсная блокировка формы не гарантирует безопасность данных! Если мы просто отключим элементы на форме, опытный пользователь или сторонний разработчик сможет изменить статус и любые другие реквизиты документа в обход формы. Это можно сделать через обработку «Групповое изменение реквизитов», внешние обработки, API (Web/HTTP-сервисы) или программно из другого кода.
Поэтому основным контуром защиты всегда выступает проверка в модуле объекта. Давайте реализуем эту логику в событии ПередЗаписью модуля документа. Нам необходимо сравнить старый статус документа, записанный в базе данных, с новым статусом, который пытается записать пользователь, а также проверить наличие у пользователя полных прав.
Проанализируем пример кода, который нужно разместить в модуле объекта документа:
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
// Предотвращаем бесконечный цикл и проверяем, что документ не новый
Если ЭтоНовый() Тогда
Возврат;
КонецЕсли;
// Если у пользователя есть полные права, проверка не требуется
Если РольДоступна("ПолныеПрава") Тогда
Возврат;
КонецЕсли;
// Получаем текущее значение статуса непосредственно из базы данных
СтарыйСтатус = Ссылка.Статус;
// Проверяем, находится ли документ в заблокированном статусе
Если СтарыйСтатус = Перечисления.СтатусыДокументов.Согласован
ИЛИ СтарыйСтатус = Перечисления.СтатусыДокументов.НаСогласовании Тогда
// Если статус изменился, разрешаем запись только при определенных условиях,
// иначе полностью блокируем изменение документа
Отказ = Истина;
ВызватьИсключение "Редактирование документа в текущем статусе запрещено для Вашей роли!";
КонецЕсли;
КонецПроцедуры
Использование свойства Ссылка.Статус позволяет получить значение реквизита непосредственно из базы данных, минуя текущие изменения в оперативной памяти (в переменной Статус). Если в базе документ уже зафиксирован в закрытом статусе, любые попытки его перезаписи пользователем без роли ПолныеПрава будут пресечены платформой на уровне транзакции базы данных.
Теперь, когда наши данные защищены на сервере, перейдем к реализации удобного интерфейса для пользователя. Пользователь не должен заполнять поля документа, чтобы в самом конце при нажатии кнопки «Записать» получить отказ. Наша задача — наглядно показать ему, что документ закрыт для редактирования.
Рассмотрим два основных подхода к блокировке элементов управляемой формы.
Самый простой способ закрыть форму — установить системное свойство формы ТолькоПросмотр. Мы можем сделать это при открытии формы или при изменении статуса документа.
Давайте посмотрим, как реализовать это в модуле управляемой формы:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
УстановитьДоступностьФормыНаСервере();
КонецПроцедуры
&НаСервере
Процедура УстановитьДоступностьФормыНаСервере()
// Проверяем наличие роли ПолныеПрава у текущего пользователя
Если РольДоступна("ПолныеПрава") Тогда
Возврат;
КонецЕсли;
// Проверяем статус документа
Если Объект.Статус = ПредопределенноеЗначение("Перечисление.СтатусыДокументов.Согласован") Тогда
ЭтаФорма.ТолькоПросмотр = Истина;
КонецЕсли;
КонецПроцедуры
Важная деталь: Несмотря на свою простоту, метод ЭтаФорма.ТолькоПросмотр = Истина имеет существенный недостаток. Платформа 1С полностью блокирует интерактивность всей формы. Это означает, что пользователи больше не смогут нажимать на кнопки выбора и открытия («лупы» или гиперссылки) в полях ввода. Они лишатся возможности «провалиться» в карточку контрагента, открыть связанный договор или просмотреть прикрепленные файлы. Пользователи начнут жаловаться на крайнее неудобство работы.
Чтобы избежать проблем с юзабилити, мы можем обойти элементы формы программно и заблокировать только возможность изменения данных, оставив доступными гиперссылки и кнопки просмотра. Для этого мы будем использовать свойство ТолькоПросмотр на уровне каждого конкретного элемента (поля ввода), а для кнопок будем изменять свойство Доступность.
Разберем эффективный алгоритм обхода элементов формы в модуле управляемой формы:
&НаКлиенте
Процедура ПриОткрытии(Отказ)
УстановитьДоступностьЭлементов();
EndПроцедуры
&НаСервере
Функция ПользовательИмеетДоступ()
// Проверяем роли через БСП или напрямую
Возврат РольДоступна("ПолныеПрава") ИЛИ РольДоступна("РедактированиеУтвержденныхДокументов");
КонецФункции
&НаКлиенте
Процедура УстановитьДоступностьЭлементов()
// Администраторам разрешено всё
Если ПользовательИмеетДоступ() Тогда
Возврат;
КонецЕсли;
// Условие блокировки (например, статус документа)
ДокументЗаблокирован = (Объект.Статус = ПредопределенноеЗначение("Перечисление.СтатусыДокументов.Согласован"));
Если Не ДокументЗаблокирован Тогда
Возврат;
КонецЕсли;
// Обходим все элементы формы
Для каждого Элемент Из ЭтаФорма.Элементы Цикл
ИмяЭлемента = ВРег(Элемент.Имя);
// Задаем список исключений (элементы, которые должны остаться доступными)
Если СтрНайти(ИмяЭлемента, ВРег("ФормаЗаписать")) > 0
Или СтрНайти(ИмяЭлемента, ВРег("Комментарий")) > 0
Или СтрНайти(ИмяЭлемента, ВРег("ПерейтиКИстории")) > 0 Тогда
Продолжить;
КонецЕсли;
// Пропускаем декоративные элементы и группы формы
Если ТипЗнч(Элемент) = Тип("ДекорацияФормы") Или ТипЗнч(Элемент) = Тип("ГруппаФормы") Тогда
Продолжить;
КонецЕсли;
// Блокируем кнопки, а поля ввода переводим в режим ТолькоПросмотр
Если ТипЗнч(Элемент) = Тип("КнопкаФормы") Тогда
Элемент.Доступность = Ложь;
Иначе
Попытка
Элемент.ТолькоПросмотр = Истина;
Исключение
// Обработка элементов, не поддерживающих данное свойство
КонецПопытки;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Этот подход значительно дружелюбнее к пользователю: все ссылки остаются кликабельными, можно просматривать связанные справочники, но изменить данные в самом документе невозможно.
Если форма вашего документа перегружена реквизитами, табличными частями и кнопками (содержит сотни элементов), то полный обход коллекции ЭтаФорма.Элементы в цикле при каждом открытии может вызвать микросекундные задержки интерфейса. Это особенно заметно на слабых клиентских компьютерах.
Для оптимизации процесса мы рекомендуем использовать группировку элементов формы. Объединяйте логически связанные поля в группы элементов (например, ГруппаОсновное, ГруппаТабличныеЧасти). На управляемой форме свойства доступности наследуются подчиненными элементами от родительского контейнера. Вместо обхода сотен полей достаточно изменить свойства всего нескольких родительских групп:
// Пример быстрой блокировки целых разделов формы
Элементы.ГруппаОсновное.ТолькоПросмотр = Истина;
Элементы.ГруппаТабличныеЧасти.ТолькоПросмотр = Истина;
Элементы.ГруппаКнопкиДействий.Доступность = Ложь;
Этот метод не только повышает скорость работы интерфейса, но и делает программный код значительно чище, лаконичнее и проще для дальнейшей поддержки другими разработчиками.
Если ваша конфигурация разработана на базе Библиотеки стандартных подсистем (БСП), то изобретать велосипед с ручным обходом элементов формы не нужно. В БСП встроен мощный инструмент — подсистема «Запрет редактирования реквизитов объектов». Он позволяет стандартизировать поведение интерфейса во всей системе.
Преимущество БСП заключается в том, что для пользователей с полными правами в меню «Ещё» автоматически выводится стандартная кнопка «Разрешить редактирование реквизитов», которая безопасно разблокирует поля формы после предупреждения о возможных последствиях.
Разберем шаги по интеграции этого механизма:
ЗапретРедактированияРеквизитовОбъектовПереопределяемый и в процедуре ПриОпределенииОбъектовСЗаблокированнымиРеквизитами добавим имя нашего документа:
СписокОбъектов.Добавить(Метаданные.Документы.НашДокумент.Имя);
Функция ПолучитьБлокируемыеРеквизитыОбъекта() Экспорт
БлокируемыеРеквизиты = Новый Массив;
БлокируемыеРеквизиты.Добавить("Контрагент");
БлокируемыеРеквизиты.Добавить("Договор");
БлокируемыеРеквизиты.Добавить("СуммаДокумента");
Возврат БлокируемыеРеквизиты;
КонецФункции
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Проверяем условия блокировки (например, статус)
Если Объект.Статус = ПредопределенноеЗначение("Перечисление.СтатусыДокументов.Согласован") Тогда
ЗапретРедактированияРеквизитовОбъектов.ЗаблокироватьРеквизиты(ЭтотОбъект);
КонецЕсли;
КонецПроцедуры
&НаСервере
Процедура ПослеЗаписиНаСервере(ПараметрыЗаписи)
Если Объект.Статус = ПредопределенноеЗначение("Перечисление.СтатусыДокументов.Согласован") Тогда
ЗапретРедактированияРеквизитовОбъектов.ЗаблокироватьРеквизиты(ЭтотОбъект);
КонецЕсли;
КонецПроцедуры
Использование встроенных механизмов БСП — это наиболее предпочтительный, правильный путь разработки в современных конфигурациях 1С, соответствующий стандартам фирмы «1С».