Как исправить ошибку передачи недопустимого типа (ПолеФормы) с клиента на сервер и правильно настроить валидацию в 1С?

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

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

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

---

Часть 1. Ошибка передачи недопустимого типа данных (ПолеФормы)

Проанализируем классическую ситуацию. У нас есть обработчик события изменения поля в табличной части на клиенте. Мы хотим рассчитать среднюю цену запасов на сервере с помощью запроса к регистру накопления и вернуть полученную сумму обратно на клиент. Рассмотрим исходный (ошибочный) код, который часто пишут начинающие разработчики:


&НаСервере
Функция СредняяЦенаЗапасов(Идентификатор)
    // Ошибочная попытка получить строку по элементу формы
    ТекСтрока = Объект.Товары.НайтиПоИдентификатору(Идентификатор);
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	ОстаткиНоменклатурыОстаткиИОбороты.Номенклатура КАК Номенклатура,
        |	СУММА(ОстаткиНоменклатурыОстаткиИОбороты.СуммаРасход) КАК СредняяЦена
        |ИЗ
        |	РегистрНакопления.ОстаткиНоменклатуры КАК ОстаткиНоменклатурыОстаткиИОбороты
        |ГДЕ
        |	ОстаткиНоменклатурыОстаткиИОбороты.Номенклатура = &Номенклатура
        |СГРУППИРОВАТЬ ПО
        |	ОстаткиНоменклатурыОстаткиИОбороты.Номенклатура";
        
    Запрос.УстановитьПараметр("Номенклатура", ТекСтрока.Номенклатура);
    Выборка = Запрос.Выполнить().Выбрать();
    
    Если Выборка.Следующий() Тогда
        Возврат ТекСтрока.Количество * Выборка.СредняяЦена;
    КонецЕсли;
    
    Возврат 0;
КонецФункции

&НаКлиенте
Процедура ТоварыКоличествоПриИзменении(Элемент)
    ТекСтрока = Элементы.Товары.ТекущиеДанные;
    Сумма = 0;
    
    // КРИТИЧЕСКАЯ ОШИБКА: передаем сам элемент формы "ПолеВвода"
    Сумма = СредняяЦенаЗапасов(Элемент); 
    
    ТекСтрока.Сумма = Сумма;
КонецПроцедуры

Выясним причину падения системы. В процедуре ТоварыКоличествоПриИзменении(Элемент) в качестве параметра Элемент выступает объект клиентской платформы с типом ПолеФормы. Этот объект отвечает исключительно за визуальное отображение элемента управления на экране пользователя. Он не содержит бизнес-данных и существует только в оперативной памяти клиентского приложения.

Когда управление передается серверной функции СредняяЦенаЗапасов, платформа инициирует механизм XDTO-сериализации для преобразования параметров вызова. Однако объекты интерфейса (такие как ПолеФормы, ТаблицаФормы, КнопкаФормы) не подлежат сериализации. Их невозможно передать на сервер. Отсюда и возникает ошибка недопустимого типа.

---

Часть 2. Как правильно передавать параметры на сервер?

Для решения этой проблемы нам необходимо передавать не элементы управления интерфейсом, а конкретные значения реквизитов или уникальные идентификаторы данных. Рассмотрим два наиболее эффективных и архитектурно правильных способа решения задачи.

Способ 1. Передача уникального идентификатора строки

Если на сервере нам требуется работать непосредственно с данными строки табличной части формы, мы должны передать числовой идентификатор этой строки. Обратите внимание на разницу между индексом строки и её идентификатором:

  1. Индекс строки — это её порядковый номер в таблице (начиная с нуля). Индексы динамически меняются при сортировке, добавлении или удалении других строк. Использовать индекс для поиска строки крайне ненадежно.
  2. Идентификатор строки — это постоянное числовое значение, уникальное для конкретной строки таблицы формы в рамках текущего сеанса работы. Он не меняется при интерактивных манипуляциях пользователя с табличной частью.

Для получения идентификатора текущей строки таблицы используется свойство ТекущаяСтрока нашей таблицы формы (Элементы.Товары.ТекущаяСтрока). Перепишем код правильно:


&НаСервере
Функция СредняяЦенаЗапасов(ИдентификаторСтроки)
    // Получаем безопасную ссылку на строку данных формы по идентификатору
    ТекСтрока = Объект.Товары.НайтиПоИдентификатору(ИдентификаторСтроки);
    
    // Обязательно проверяем, найдена ли строка
    Если ТекСтрока = Неопределено Тогда
        Возврат 0;
    КонецЕсли;
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	ОстаткиНоменклатурыОстаткиИОбороты.Номенклатура КАК Номенклатура,
        |	СУММА(ОстаткиНоменклатурыОстаткиИОбороты.СуммаРасход) КАК СредняяЦена
        |ИЗ
        |	РегистрНакопления.ОстаткиНоменклатуры КАК ОстаткиНоменклатурыОстаткиИОбороты
        |ГДЕ
        |	ОстаткиНоменклатурыОстаткиИОбороты.Номенклатура = &Номенклатура
        |СГРУППИРОВАТЬ ПО
        |	ОстаткиНоменклатурыОстаткиИОбороты.Номенклатура";
        
    Запрос.УстановитьПараметр("Номенклатура", ТекСтрока.Номенклатура);
    Выборка = Запрос.Выполнить().Выбрать();
    
    Если Выборка.Следующий() Тогда
        Возврат ТекСтрока.Количество * Выборка.СредняяЦена;
    КонецЕсли;
    
    Возврат 0;
КонецФункции

&НаКлиенте
Процедура ТоварыКоличествоПриИзменении(Элемент)
    ТекСтрока = Элементы.Товары.ТекущиеДанные;
    
    Если ТекСтрока <> Неопределено Тогда
        // Получаем идентификатор текущей строки таблицы формы
        ИдентификаторСтроки = Элементы.Товары.ТекущаяСтрока;
        
        // Передаем на сервер только числовой идентификатор
        ТекСтрока.Сумма = СредняяЦенаЗапасов(ИдентификаторСтроки);
    КонецЕсли;
КонецПроцедуры

Способ 2. Оптимальный вариант с передачей структуры данных

В целях оптимизации клиент-серверного взаимодействия и уменьшения объёма передаваемых данных («тяжелый» контекст формы на сервере при вызовах без контекста &НаСервереБезКонтекста), рекомендуется собирать параметры в легковесный объект Структура прямо на клиенте и передавать его на сервер. Рассмотрим этот пример подробнее:


&НаСервереБезКонтекста
Функция ПолучитьСреднююЦенуЗапасов(ПараметрыСтроки)
    // ПараметрыСтроки — это Структура, переданная с клиента.
    // Обращение к серверной базе данных происходит быстро и без передачи всей формы.
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	ОстаткиНоменклатурыОстаткиИОбороты.Номенклатура КАК Номенклатура,
        |	СУММА(ОстаткиНоменклатурыОстаткиИОбороты.СуммаРасход) КАК СредняяЦена
        |ИЗ
        |	РегистрНакопления.ОстаткиНоменклатуры КАК ОстаткиНоменклатурыОстаткиИОбороты
        |ГДЕ
        |	ОстаткиНоменклатурыОстаткиИОбороты.Номенклатура = &Номенклатура
        |СГРУППИРОВАТЬ ПО
        |	ОстаткиНоменклатурыОстаткиИОбороты.Номенклатура";
        
    Запрос.УстановитьПараметр("Номенклатура", ПараметрыСтроки.Номенклатура);
    Выборка = Запрос.Выполнить().Выбрать();
    
    Если Выборка.Следующий() Тогда
        Возврат ПараметрыСтроки.Количество * Выборка.СредняяЦена;
    КонецЕсли;
    
    Возврат 0;
КонецФункции

&НаКлиенте
Процедура ТоварыКоличествоПриИзменении(Элемент)
    ТекСтрока = Элементы.Товары.ТекущиеДанные;
    
    Если ТекСтрока <> Неопределено Тогда
        // Формируем легкую структуру с нужными для расчета свойствами
        ПараметрыСтроки = Новый Структура;
        ПараметрыСтроки.Вставить("Номенклатура", ТекСтрока.Номенклатура);
        ПараметрыСтроки.Вставить("Количество", ТекСтрока.Количество);
        
        // Передаем структуру в контекстно-независимую функцию
        ТекСтрока.Сумма = ПолучитьСреднююЦенуЗапасов(ПараметрыСтроки);
    КонецЕсли;
КонецПроцедуры

Использование директивы компиляции &НаСервереБезКонтекста существенно повышает производительность приложения, так как при вызове такой функции на сервер не отправляются все данные формы (объект РеквизитФормыВЗначение не сериализуется неявно).

---

Часть 3. Механизм проверки заполнения реквизитов (Валидация)

Теперь разберем вторую ошибку, допущенную автором темы. Разработчик попытался вручную запустить валидацию реквизитов, вызвав системный обработчик ОбработкаПроверкиЗаполненияНаСервере непосредственно из клиентской процедуры ПриИзменении, снова передав туда интерфейсный Элемент.

Помните: Процедура ОбработкаПроверкиЗаполненияНаСервере — это стандартный предопределенный обработчик событий платформы 1С. Платформа вызывает её автоматически при записи или проведении документа, а также при явном вызове метода формы ПроверитьЗаполнение().

Вызывать системный обработчик событий напрямую из кода вручную — это грубое нарушение стандартов разработки 1С!

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

  1. На клиенте в обработчике изменения поля вызываем стандартный метод валидации управляемой формы ПроверитьЗаполнение().
  2. Платформа сама перенаправит выполнение на сервер и вызовет предопределенный обработчик ОбработкаПроверкиЗаполненияНаСервере(Отказ, ПроверяемыеРеквизиты).
  3. Внутри этого обработчика мы можем динамически управлять составом проверяемых реквизитов, удаляя или добавляя их имена в массив ПроверяемыеРеквизиты.

Посмотрим на практический пример реализации:


&НаСервере
Процедура ОбработкаПроверкиЗаполненияНаСервере(Отказ, ПроверяемыеРеквизиты)
    // Проверяем тип номенклатуры.
    // На сервере можно использовать прямое обращение к менеджеру перечислений:
    Если Объект.ВидНоменклатуры = Перечисления.ВидыНоменклатуры.Услуга Тогда
        // Если это услуга, то заполнять "Базовую единицу" не требуется.
        // Удаляем реквизит из списка обязательных к заполнению.
        НайденныйРеквизит = ПроверяемыеРеквизиты.Найти("Объект.БазоваяЕдиница");
        Если НайденныйРеквизит <> Неопределено Тогда
            ПроверяемыеРеквизиты.Удалить(НайденныйРеквизит);
        КонецЕсли;
    КонецЕсли;
КонецПроцедуры

&НаКлиенте
Процедура ВидНоменклатурыПриИзменении(Элемент)
    // Инициируем стандартный системный механизм валидации формы.
    // Платформа сама вызовет серверный обработчик проверки заполнения.
    ПроверитьЗаполнение();
КонецПроцедуры

Обратите внимание на важную деталь при обращении к предопределенным значениям метаданных на клиенте и сервере:

Соблюдение этих простых правил проектирования интерфейсов и клиент-серверного взаимодействия позволит вам навсегда избавиться от ошибок типов данных и сделать ваши прикладные решения быстрыми и отказоустойчивыми!

← На главную