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