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