Почему в тонком клиенте 1С не открывается форма заказа, а в толстом клиенте все работает?

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

Каждый начинающий разработчик в среде «1С:Предприятие» рано или поздно сталкивается с загадочным поведением платформы: один и тот же код прекрасно функционирует в режиме толстого клиента, но вызывает фатальную ошибку при запуске в тонком клиенте. Обычно это происходит при попытке модификации существующих форм документов — например, при добавлении расчета веса и объема в табличную часть документа Заказ клиента.

В этой статье мы подробно разберем, почему возникает ошибка «Поле объекта не обнаружено» при переходе на тонкий клиент, проанализируем архитектурные различия между клиентами и рассмотрим несколько правильных и производительных способов решения этой задачи согласно стандартам разработки фирмы «1С».

Выясняем причину: почему тонкий клиент не прощает обращений «через точку»?

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


Для Каждого Строка Из Объект.Товары Цикл
    Объем1 = Объем1 + Строка.Количество * Строка.Номенклатура.ОбъемЧислитель;
    Вес1 = Вес1 + Строка.Количество * Строка.Номенклатура.ВесЧислитель;
КонецЦикла;

В режиме толстого клиента этот код успешно отрабатывает. Дело в том, что толстый клиент работает в едином адресном пространстве с базой данных (или ее локальным кэшем). Когда платформа видит конструкцию Строка.Номенклатура.ВесЧислитель, она выполняет неявное («ленивое») чтение объекта из базы данных. Платформа самостоятельно обращается к СУБД, считывает все реквизиты справочника Номенклатура и подставляет нужное значение. Это работает крайне неэффективно и медленно, но не вызывает ошибок у пользователя.

В режиме тонкого клиента ситуация кардинально меняется. Тонкий клиент оптимизирован для работы по слабым каналам связи (включая веб-соединения). Данные на клиент передаются в усеченном виде. Ссылка на номенклатуру (СправочникСсылка.Номенклатура) на клиенте — это всего лишь легковесный идентификатор (UUID). Он не содержит внутри себя никаких реквизитов, кроме ссылки и представления. Попытка обратиться к реквизиту ссылки «через точку» на клиенте вызывает мгновенный отказ платформы, так как неявные сетевые запросы к базе данных из клиентского контекста строго запрещены.

Разбор ошибок в «решении» с форума

В ходе обсуждения участники форума предложили автору создать вспомогательную функцию на сервере, которая извлекает нужные реквизиты с помощью библиотеки стандартных подсистем (БСП):


&НаСервереБезКонтекста
Функция ПолучитьСведенияНоменклатуры(Номенклатура)
    Возврат ОбщегоНазначения.ЗначенияРеквизитовОбъекта(Номенклатура, "ОбъемЧислитель, ВесЧислитель");
КонецФункции

А затем вызывать её в процедуре ПриОткрытии на клиенте внутри цикла:


Для Каждого Строка Из Объект.Товары Цикл
    СведенияНоменклатуры = ПолучитьСведенияНоменклатуры(Строка.Номенклатура);
    Объем1 = Объем1 + Строка.Количество * СведенияНоменклатуры.ОбъемЧислитель;
    // ... дальнейший расчет
КонецЦикла;

Внимание! Хотя этот код решает проблему с ошибкой открытия формы в тонком клиенте, он является грубейшим антипаттерном разработки (ошибкой производительности). Такой подход называют «серверным вызовом в цикле».

Давайте посчитаем: если в табличной части заказа клиента будет 100 строк, программа совершит 100 последовательных клиент-серверных вызовов. В клиент-серверном варианте работы (особенно при подключении через интернет или VPN) каждый такой вызов создает сетевую задержку (ping). В итоге открытие простого документа может занять от 5 до 30 секунд, что вызовет сильное раздражение пользователей и перегрузит сервер.

Как решить задачу правильно: три профессиональных варианта

Рассмотрим по шагам, как реализовать эту задачу грамотно, быстро и в соответствии со стандартами разработки фирмы «1С».

Вариант 1. Перенос расчетов на сервер при создании формы (Рекомендуемый)

Если нам требуется рассчитать вес и объем товаров исключительно в момент открытия документа, нет абсолютно никакой необходимости выполнять этот цикл на стороне клиента. Гораздо правильнее перенести всю логику в серверный обработчик ПриСозданииНаСервере. На сервере мы имеем прямой доступ к данным, и весь цикл выполнится мгновенно за одну транзакцию без каких-либо сетевых задержек.

Давайте посмотрим на пример правильной реализации в модуле формы документа:


&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    
    // Выполняем расчет на сервере — это быстро и безопасно
    ИтоговыйВес = 0; 
    ИтоговыйОбъем = 0;
    
    Для Каждого Строка Из Объект.Товары Цикл
        // На сервере мы можем безопасно использовать функцию БСП
        Сведения = ОбщегоНазначения.ЗначенияРеквизитовОбъекта(Строка.Номенклатура, "ВесЧислитель, ОбъемЧислитель");
        
        // Заполняем колонки веса и объема для текущей строки
        Строка.ВесТовара = Строка.Количество * ?(ЗначениеЗаполнено(Сведения.ВесЧислитель), Сведения.ВесЧислитель, 0);
        Строка.ОбъемТовара = Строка.Количество * ?(ЗначениеЗаполнено(Сведения.ОбъемЧислитель), Сведения.ОбъемЧислитель, 0);
        
        // Накапливаем итоги
        ИтоговыйВес = ИтоговыйВес + Строка.ВесТовара;
        ИтоговыйОбъем = ИтоговыйОбъем + Строка.ОбъемТовара;
    КонецЦикла;
    
    // Записываем полученные итоги в реквизиты формы, связанные с подвалом таблицы
    ИтогоВес = ИтоговыйВес; 
    ИтогоОбъем = ИтоговыйОбъем;
    
КонецПроцедуры

Вариант 2. Групповое получение реквизитов (Если расчет необходим на клиенте)

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

Для этого мы применим метод групповой выборки реквизитов БСП. Сначала мы соберем все уникальные ссылки на номенклатуру из табличной части в массив, передадим этот массив на сервер, получим данные по всем позициям одним пакетом, а затем обработаем их локально на клиенте.

Давайте разберем этот алгоритм на примере кода:


&НаКлиенте
Процедура ПриОткрытии(Отказ)
    
    // 1. Собираем уникальные ссылки на номенклатуру в массив
    МассивНоменклатуры = Новый Массив;
    Для Каждого Строка Из Объект.Товары Цикл
        Если ЗначениеЗаполнено(Строка.Номенклатура) 
           И МассивНоменклатуры.Найти(Строка.Номенклатура) = Неопределено Тогда
            МассивНоменклатуры.Добавить(Строка.Номенклатура);
        КонецЕсли;
    КонецЦикла;
    
    Если МассивНоменклатуры.Количество() = 0 Тогда
        Возврат;
    КонецЕсли;
    
    // 2. Делаем только ОДИН вызов сервера для получения данных по всему массиву
    СведенияВсехТоваров = ПолучитьСведенияМассиваНоменклатуры(МассивНоменклатуры); 
    
    // 3. Выполняем расчет на клиенте без повторных обращений к серверу
    СуммаВеса = 0; 
    СуммаОбъема = 0;
    
    Для Каждого Строка Из Объект.Товары Цикл
        // Извлекаем структуру показателей из возвращенного соответствия по ключу-ссылке
        Сведения = СведенияВсехТоваров[Строка.Номенклатура];
        Если Сведения <> Неопределено Тогда
            Строка.ВесТовара = Строка.Количество * ?(ЗначениеЗаполнено(Сведения.ВесЧислитель), Сведения.ВесЧислитель, 0);
            Строка.ОбъемТовара = Строка.Количество * ?(ЗначениеЗаполнено(Сведения.ОбъемЧислитель), Сведения.ОбъемЧислитель, 0);
            
            СуммаВеса = СуммаВеса + Строка.ВесТовара;
            СуммаОбъема = СуммаОбъема + Строка.ОбъемТовара;
        КонецЕсли;
    КонецЦикла;
    
    // Выводим итоги на форму
    ИтогоВес = СуммаВеса;
    ИтогоОбъем = СуммаОбъема;
    
КонецПроцедуры

&НаСервереБезКонтекста
Функция ПолучитьСведенияМассиваНоменклатуры(МассивНоменклатуры)
    // Функция БСП ЗначенияРеквизитовОбъектов возвращает Соответствие, где ключ — ссылка, а значение — Структура реквизитов
    Возврат ОбщегоНазначения.ЗначенияРеквизитовОбъектов(МассивНоменклатуры, "ВесЧислитель, ОбъемЧислитель");
КонецФункции

Вариант 3. Использование стандартных итогов табличной части (Самый лаконичный)

Если колонки ВесТовара и ОбъемТовара добавлены как полноценные реквизиты табличной части документа, то платформа «1С» позволяет полностью отказаться от ручного суммирования в коде. Мы можем задействовать встроенные механизмы расчета итогов:

  1. Откройте форму документа в конфигураторе.
  2. В свойствах таблицы формы Товары установите флаг Отображать подвал в значение Истина.
  3. Выберите колонку таблицы ВесТовара, найдите свойство ПутьКДаннымПодвала и укажите значение Объект.Товары.ИтогВесТовара.
  4. Проделайте то же самое для колонки ОбъемТовара, указав путь к данным подвала как Объект.Товары.ИтогОбъемТовара.

При таком подходе платформа будет рассчитывать и отображать итоги автоматически в подвале таблицы прямо «на лету» силами самого клиентского приложения, не требуя от разработчика написания лишних строк кода и серверных вызовов.

Если же значение автоматического итога все-таки понадобится получить в кодовой логике на клиенте, достаточно обратиться к встроенному методу коллекции: Объект.Товары.Итог("ВесТовара").

Применяя данные подходы, вы гарантируете стабильную и быструю работу конфигурации как в тонком, так и в веб-клиенте, избегая зависаний интерфейса и ошибок доступа к данным.

← На главную