Почему при переносе кода на управляемую форму Справочники и Документы определяются как неопределенные переменные?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При адаптации алгоритмов и внешних обработок с обычных форм на управляемые разработчики часто сталкиваются с ошибками вида Переменная не определена (Справочники) или Переменная не определена (Документы) при проверке модуля формы в контексте тонкого клиента. Разберем детально, почему возникает такое поведение платформы «1С:Предприятие 8.3», какие принципиальные архитектурные ограничения действуют на клиенте и как правильно переписать процедуру загрузки данных.

Причина ошибки: клиент-серверная архитектура и контекст исполнения

В обычных формах (толстый клиент) весь программный код по умолчанию выполнялся в едином контексте, где были доступны как элементы интерфейса, так и все объекты прикладного решения базы данных. В управляемом приложении действует строгое разделение логики на клиентскую и серверную части:

  1. Менеджеры метаданных на клиенте недоступны: глобальные коллекции Справочники, Документы, РегистрыСведений, Константы, Перечисления и ПланыВидовХарактеристик существуют исключительно на стороне сервера. Попытка обратиться к ним в процедуре с директивой компиляции &НаКлиенте всегда приводит к ошибке компиляции.
  2. Ошибочное обращение через Объект: попытки вызвать Объект.Справочники... также ошибочны. На управляемой форме свойство Объект является реквизитом формы с типом ДанныеФормыСтруктура, а не ссылкой на метаданные или модулем прикладного объекта.
  3. Ограничения типа XBase и коллекций: объект XBase, используемый для чтения DBF-файлов, а также универсальные коллекции ТаблицаЗначений и ДеревоЗначений не поддерживаются в тонком клиенте. Работа с ними должна выполняться на сервере.
  4. Доступ к файловой системе: при клиент-серверном варианте работы сервер 1С не имеет прямого доступа к локальным дискам клиентского компьютера, поэтому передавать путь к файлу напрямую на сервер нельзя.

Пошаговый алгоритм разделения кода на клиент и сервер

Проанализируем правильную архитектуру операции загрузки данных из внешнего файла:

  1. Клиентская часть: отвечает за проверку заполнения обязательных реквизитов на форме, интерактивный выбор файла пользователем и передачу двоичных данных файла во временное хранилище.
  2. Серверная часть: получает двоичные данные из хранилища, сохраняет их во временный файл на сервере, открывает через объект XBase, выполняет поиск, создание элементов справочников, документов и запись наборов записей регистров.

Разберем реализацию клиентской процедуры нажатия кнопки выполнения:


&НаКлиенте
Процедура ВыполнитьЗагрузку(Команда)
    
    // Проверяем заполнение реквизитов формы
    Если Не ЗначениеЗаполнено(Объект.Организация) Тогда
        Сообщить("Не выбрана организация!");
        Возврат;
    КонецЕсли;
    
    Если Не ЗначениеЗаполнено(Объект.Дата) Тогда
        Сообщить("Не выбрана дата!");
        Возврат;
    КонецЕсли;
    
    Если ПустаяСтрока(Объект.ФайлЗагрузки) Тогда
        Сообщить("Не выбран файл для загрузки!");
        Возврат;
    КонецЕсли;
    
    // Помещаем локальный файл во временное хранилище для передачи на сервер
    АдресФайлаВХранилище = "";
    ПоместитьФайл(АдресФайлаВХранилище, Объект.ФайлЗагрузки, , Ложь, УникальныйИдентификатор);
    
    Если ПустаяСтрока(АдресФайлаВХранилище) Тогда
        Сообщить("Не удалось прочитать файл!");
        Возврат;
    КонецЕсли;
    
    Сообщить("Загрузка начата: " + ТекущаяДата());
    
    // Вызываем серверную обработку
    ЗагрузитьДанныеНаСервере(АдресФайлаВХранилище);
    
    Сообщить("Загрузка успешно завершена: " + ТекущаяДата());
    
КонецПроцедуры

Реализация логики обработки данных на стороне сервера

Рассмотрим, как выглядит процедура выполнения на сервере. Обратите внимание: для вызова экспортных процедур модуля объекта внешней обработки (например, КВП_ЗаписатьОбъект) необходимо получить контекст объекта обработки через функцию РеквизитФормыВЗначение("Объект").


&НаСервере
Процедура ЗагрузитьДанныеНаСервере(АдресФайлаВХранилище)
    
    // Получаем экземпляр объекта обработки для доступа к его методам
    ОбработкаОбъект = РеквизитФормыВЗначение("Объект");
    
    // Извлекаем двоичные данные из хранилища и сохраняем во временный файл на сервере
    ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресФайлаВХранилище);
    ВременныйФайлСервера = ПолучитьИмяВременногоФайла("dbf");
    ДвоичныеДанные.Записать(ВременныйФайлСервера);
    
    ФайлДБФ = Новый XBase;
    ФайлДБФ.Кодировка = КодировкаТекста.UTF8;
    ФайлДБФ.ОткрытьФайл(ВременныйФайлСервера, , Истина);
    
    Если Не ФайлДБФ.Открыта() Тогда
        УдалитьФайлы(ВременныйФайлСервера);
        ВызватьИсключение "Не удалось открыть временный файл DBF на сервере!";
    КонецЕсли;
    
    // На сервере мы можем свободно использовать ТаблицуЗначений
    ТаблицаПлощадей = Новый ТаблицаЗначений;
    ТаблицаПлощадей.Колонки.Добавить("Помещение", Новый ОписаниеТипов("СправочникСсылка.УПЖКХ_Помещения"));
    ТаблицаПлощадей.Колонки.Добавить("ОбщаяПлощадь", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)));
    
    ТаблицаДолейЛС = Новый ТаблицаЗначений;
    ТаблицаДолейЛС.Колонки.Добавить("ЛицевойСчет", Новый ОписаниеТипов("СправочникСсылка.КВП_ЛицевыеСчета"));
    ТаблицаДолейЛС.Колонки.Добавить("Помещение", Новый ОписаниеТипов("СправочникСсылка.УПЖКХ_Помещения"));
    ТаблицаДолейЛС.Колонки.Добавить("Доля", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)));
    
    Пока Не ФайлДБФ.ВКонце() Цикл
        
        ЛицевойСчетСтрока = СокрЛП(ФайлДБФ.LS);
        ФИО = СокрЛП(ФайлДБФ.FIO);
        Корпус = СокрЛП(ФайлДБФ.KORP);
        Квартира = СокрЛП(ФайлДБФ.KV);
        ОбщаяПлощадь = ФайлДБФ.OB_PLOSH;
        НаселенныйПункт = СокрЛП(ФайлДБФ.NP);
        НаименованиеУлицы = СокрЛП(ФайлДБФ.NAME_UL);
        НомерДома = СокрЛП(ФайлДБФ.DOM);
        
        ФайлДБФ.Следующая();
        
        Если ПустаяСтрока(ЛицевойСчетСтрока) Или ПустаяСтрока(ФИО) Тогда
            Продолжить;
        КонецЕсли;
        
        // Работа со справочником Здания
        ПредставлениеЗдания = НаселенныйПункт + ", " + НаименованиеУлицы + ", дом № " + НомерДома;
        Здание = Справочники.КВП_Здания.НайтиПоНаименованию(ПредставлениеЗдания, Истина);
        
        Если Здание.Пустая() Тогда
            ЗданиеОбъект = Справочники.КВП_Здания.СоздатьЭлемент();
            ЗданиеОбъект.Наименование = ПредставлениеЗдания;
            ЗданиеОбъект.НомерДома = НомерДома;
            ЗданиеОбъект.Записать();
            Здание = ЗданиеОбъект.Ссылка;
        КонецЕсли;
        
        // Работа со справочником Помещения
        Помещение = Справочники.УПЖКХ_Помещения.НайтиПоКоду(Квартира, , , Здание);
        Если Помещение.Пустая() Тогда
            ПомещениеОбъект = Справочники.УПЖКХ_Помещения.СоздатьЭлемент();
            ПомещениеОбъект.Код = Квартира;
            ПомещениеОбъект.Владелец = Здание;
            ПомещениеОбъект.Наименование = "Кв. " + Квартира;
            ПомещениеОбъект.Записать();
            Помещение = ПомещениеОбъект.Ссылка;
        КонецЕсли;
        
        // Накопление данных по площадям
        НоваяСтрока = ТаблицаПлощадей.Добавить();
        НоваяСтрока.Помещение = Помещение;
        НоваяСтрока.ОбщаяПлощадь = ОбщаяПлощадь;
        
    КонецЦикла;
    
    ФайлДБФ.ЗакрытьФайл();
    УдалитьФайлы(ВременныйФайлСервера);
    
    // Запись наборов записей в регистры
    ТаблицаПлощадей.Свернуть("Помещение", "ОбщаяПлощадь");
    Для Каждого Стр Из ТаблицаПлощадей Цикл
        НаборЗаписей = РегистрыСведений.КВП_ПлощадьПомещения.СоздатьНаборЗаписей();
        НаборЗаписей.Отбор.Объект.Установить(Стр.Помещение);
        НаборЗаписей.Отбор.Период.Установить(Объект.Дата);
        
        НовЗапись = НаборЗаписей.Добавить();
        НовЗапись.Период = Объект.Дата;
        НовЗапись.Объект = Стр.Помещение;
        НовЗапись.Площадь = Стр.ОбщаяПлощадь;
        НаборЗаписей.Записать();
    КонецЦикла;
    
КонецПроцедуры

Рекомендации по оптимизации кода при адаптации под УФ

  1. Отказ от поиска в цикле: вызовы Справочники.НайтиПоНаименованию() и НайтиПоКоду() внутри итераций обхода большого файла создают множество одиночных запросов к СУБД. При больших объемах данных рекомендуется сначала загрузить строки файла во временную таблицу и найти ссылки одним пакетным запросом.
  2. Управление транзакциями: если выполняется массовое создание элементов и документов, объединяйте запись объектов в явные транзакции с помощью НачатьТранзакцию() и ЗафиксироватьТранзакцию(), чтобы избежать блокировок и ускорить запись на диск.
  3. Удаление временных файлов: всегда очищайте временные файлы на стороне сервера в блоке Попытка ... Исключение или после завершения чтения, чтобы не засорять дисковое пространство рабочего сервера.
← На главную