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