Как отправить строки табличной части формы в серверную процедуру 1С

· 9 мин

Содержание

Табличную часть с клиента на сервер в 1С 8.3 передают одним из четырёх способов: через контекстный вызов &НаСервере (вся форма уходит на сервер сама), через параметр процедуры &НаСервереБезКонтекста в виде массива структур, через временное хранилище по адресу или через ссылку на записанный объект. Для управляемых форм в большинстве задач лучше всего работает второй вариант: на клиенте собираете массив структур только с нужными колонками и делаете один серверный вызов.

Таблицу значений на тонком клиенте создать нельзя, а объект документа на клиент не попадает вообще. Поэтому всё, что едет между клиентом и сервером, должно быть сериализуемым: примитивы, ссылки, Массив, Структура, Соответствие и их фиксированные версии. Ниже — как каждый способ устроен, где он ломается и как не тормозить на больших таблицах.

Как устроен обмен данными между клиентом и сервером в 1С 8.3

На клиенте управляемая форма хранит не сам объект, а его копию — данные формы. Реквизит Объект имеет тип ДанныеФормыСтруктура, а его табличная часть Объект.Товары — тип ДанныеФормыКоллекция. Методы объекта (Записать(), Заполнить(), функции модуля объекта) на клиенте недоступны.

Каждый вызов процедуры с директивой &НаСервере или &НаСервереБезКонтекста — это сетевой запрос. Платформа сериализует параметры, отправляет их, выполняет код и везёт результат обратно. Отсюда три правила:

  • 📦 Передавать можно только то, что существует и на клиенте, и на сервере.
  • 🔁 Каждый вызов стоит времени. Пять вызовов в цикле по строкам — это пять обращений по сети.
  • 📏 Чем больше данных в вызове, тем дольше он идёт, особенно через веб-клиент и медленный канал.

Чем отличаются директивы при работе с табличной частью:

ДирективаЧто уходит на серверДоступ к формеКогда брать
&НаСервереВся форма с данными и параметрыЕсть: Объект, Элементы, реквизитыНужно изменить форму или вызвать метод объекта
&НаСервереБезКонтекстаТолько параметрыНетРасчёт, проверка, запрос по переданным строкам
Общий модуль с флагом «Вызов сервера»Только параметрыНетТот же код нужен в нескольких формах

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

Самый простой путь. Процедура с директивой &НаСервере видит Объект.Товары напрямую, ничего передавать не нужно. После выполнения изменённые данные формы возвращаются на клиент.

&НаКлиенте
Процедура ПересчитатьЦены(Команда)
    ПересчитатьЦеныНаСервере();
КонецПроцедуры

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

Если нужен метод модуля объекта, получите сам объект из данных формы и верните его обратно:

&НаСервере
Процедура ЗаполнитьНаСервере()
    ДокументОбъект = РеквизитФормыВЗначение("Объект");
    ДокументОбъект.ЗаполнитьТоварыПоОстаткам(); // экспортный метод модуля объекта
    ЗначениеВРеквизитФормы(ДокументОбъект, "Объект");
КонецПроцедуры

Минус способа — объём. На сервер уходят все реквизиты формы, включая другие табличные части и динамические списки. Для формы с 2 000 строк товаров и ещё тремя таблицами такой вызов заметно медленнее, чем передача пары колонок.

Контекстный вызов оправдан, когда сервер должен поменять саму форму: переписать строки, обновить реквизиты, заполнить табличную часть заново.

Способ 2. Передача табличной части параметром серверной процедуры

Это основной приём для управляемых форм. На клиенте вы обходите Объект.Товары, складываете нужные поля в структуры, а массив структур отправляете в процедуру &НаСервереБезКонтекста.

&НаКлиенте
Процедура ПроверитьОстатки(Команда)
    
    Строки = Новый Массив;
    Для Каждого Строка Из Объект.Товары Цикл
        Строки.Добавить(Новый Структура("НомерСтроки, Номенклатура, Количество",
            Строка.НомерСтроки, Строка.Номенклатура, Строка.Количество));
    КонецЦикла;
    
    Нехватка = ПроверитьОстаткиНаСервере(Строки, Объект.Склад);
    
    Для Каждого Описание Из Нехватка Цикл
        Сообщение = Новый СообщениеПользователю;
        Сообщение.Текст = Описание.Текст;
        Сообщение.Поле = "Объект.Товары[" + Формат(Описание.НомерСтроки - 1, "ЧГ=0") + "].Количество";
        Сообщение.Сообщить();
    КонецЦикла;
    
КонецПроцедуры

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

Что здесь важно:

  1. Только нужные колонки. Если для проверки хватает номенклатуры и количества, не тащите цену, сумму, ставку НДС и комментарий.
  2. Ключевое слово Знач. Параметры без Знач платформа после серверного вызова отправляет назад на клиент — на случай, если сервер их изменил. С Знач массив едет только в одну сторону.
  3. Номер строки в структуре. По нему потом удобно привязать сообщение об ошибке к конкретной ячейке.
  4. На сервере — таблица значений. Из массива структур её легко собрать через ЗаполнитьЗначенияСвойств и передать в запрос как параметр.

Как передать только выделенные строки

Если пользователь выделил строки в таблице формы, их идентификаторы лежат в Элементы.Товары.ВыделенныеСтроки. По идентификатору строка находится методом НайтиПоИдентификатору:

&НаКлиенте
Процедура ОбработатьВыделенные(Команда)
    Строки = Новый Массив;
    Для Каждого Идентификатор Из Элементы.Товары.ВыделенныеСтроки Цикл
        Строка = Объект.Товары.НайтиПоИдентификатору(Идентификатор);
        Строки.Добавить(Новый Структура("Номенклатура, Количество",
            Строка.Номенклатура, Строка.Количество));
    КонецЦикла;
    ОбработатьНаСервере(Строки);
КонецПроцедуры

Как вернуть изменённые строки обратно в форму

Сервер без контекста не может трогать форму. Поэтому он возвращает массив структур, а клиент раскладывает его по строкам:

Ответ = РассчитатьСкидкиНаСервере(Строки);
Для Каждого Данные Из Ответ Цикл
    Строка = Объект.Товары[Данные.НомерСтроки - 1];
    Строка.Скидка = Данные.Скидка;
КонецЦикла;

Индекс строки на единицу меньше её номера: первая строка — Объект.Товары[0], у неё НомерСтроки = 1.

Способ 3. Передача ссылки и чтение табличной части из базы

Если документ уже записан и форма не менялась, табличную часть передавать вообще не нужно. Достаточно ссылки: сервер сам прочитает строки запросом.

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

Вызов с клиента: Итог = ИтогПоДокументу(Объект.Ссылка);

Ограничения способа:

  • 📌 У нового, ещё не записанного документа ссылка пустая — в базе нет ни одной строки.
  • ✏️ Если пользователь изменил строки, но не записал документ, сервер увидит старую версию из базы, а не то, что на экране. Проверяйте Модифицированность формы перед таким вызовом.
  • 🔒 Сам объект (ДокументОбъект) на клиент вернуть нельзя: он существует только на сервере.

Этот способ хорош для печати, отчётов по документу и проверок, которые должны опираться на записанные данные, а не на черновик в форме.

Способ 4. Временное хранилище для больших таблиц

Когда строк тысячи, а данные нужны в нескольких серверных вызовах подряд, гонять их каждый раз параметром невыгодно. Удобнее один раз положить их во временное хранилище и дальше передавать только строку-адрес.

// На сервере: подготовили таблицу и сохранили её
&НаСервере
Процедура ПодготовитьДанные()
    Таблица = Объект.Товары.Выгрузить();
    АдресДанных = ПоместитьВоВременноеХранилище(Таблица, УникальныйИдентификатор);
КонецПроцедуры

// В следующем вызове: получили таблицу по адресу
&НаСервереБезКонтекста
Процедура ОбработатьПоАдресу(Знач Адрес)
    Таблица = ПолучитьИзВременногоХранилища(Адрес);
    // работа с таблицей значений
КонецПроцедуры

АдресДанных — строковый реквизит формы. Второй параметр УникальныйИдентификатор привязывает хранилище к форме: данные живут, пока форма открыта. Без него значение может удалиться уже после ближайшего серверного вызова, и следующая процедура получит пустоту.

Где временное хранилище действительно выручает:

  • ⏱️ Фоновые задания. Длительная обработка табличной части в фоне получает адрес и читает данные сама, форма не зависает.
  • 📂 Загрузка из файла. Файл с клиента помещается в хранилище, сервер разбирает его и возвращает адрес готовой таблицы.
  • 🔁 Многошаговые мастера. Данные одного шага переходят на следующий без повторной передачи.

Во временное хранилище помещайте только сериализуемые значения. Таблица значений подходит, ДокументОбъект или Запрос — нет.

Глобальные переменные и параметры сеанса: почему это не канал передачи

Иногда пытаются «положить таблицу в глобальную переменную» на клиенте и прочитать её на сервере. Так не работает .

  • 🖥️ Переменные модуля управляемого приложения (Перем Имя Экспорт) живут только на клиенте. На сервере их не видно.
  • 🗄️ Переменные модуля формы тоже клиентские. Видны на сервере только реквизиты формы — и то лишь при контекстном вызове.
  • 🔑 Параметры сеанса устанавливаются на сервере, предназначены для общих настроек сеанса (текущий пользователь, организация по умолчанию) и не подходят для таблиц строк документа.

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

Проверка данных и обработка ошибок на сервере

Всё, что пришло с клиента, сервер должен проверять заново. Клиентская проверка удобна пользователю, но не защищает: строки могли изменить, а тип значения в структуре мог оказаться не тем.

Что проверять в серверной процедуре:

  1. Тип и заполненность. ЗначениеЗаполнено(Данные.Номенклатура) и ТипЗнч(Данные.Количество) = Тип("Число").
  2. Наличие ключей структуры. Данные.Свойство("Количество") до обращения к полю — иначе исключение.
  3. Права. Запрос на сервере выполняется с правами пользователя, но привилегированный режим общего модуля снимает это ограничение. Не включайте его без необходимости.

Ошибки возвращайте структурой, а не одним исключением на всю таблицу. Тогда пользователь увидит все проблемные строки сразу:

Результат = Новый Массив;
Для Каждого Данные Из Строки Цикл
    Если Не ЗначениеЗаполнено(Данные.Номенклатура) Тогда
        Результат.Добавить(Новый Структура("НомерСтроки, Текст",
            Данные.НомерСтроки,
            СтрШаблон("Строка %1: не заполнена номенклатура", Данные.НомерСтроки)));
    КонецЕсли;
КонецЦикла;
Возврат Результат;

При записи документа обычная проверка уже есть: процедура ОбработкаПроверкиЗаполнения в модуле объекта плюс флаги «Проверка заполнения» у реквизитов табличной части. Не дублируйте её своим кодом в форме.

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

Как ускорить передачу и запись табличной части

Скорость упирается в число вызовов и объём данных. Несколько приёмов, которые дают заметный эффект:

ПриёмЧто даёт
Один вызов на всю таблицу вместо вызова на строкуУбирает сотни сетевых обращений
&НаСервереБезКонтекста вместо &НаСервереНе гоняет остальные реквизиты формы
Знач у параметровДанные не едут обратно на клиент
Только нужные колонки в структуреМеньше объём сериализации
Запрос с таблицей-параметром вместо обращения к реквизитам через точку в циклеОдно чтение из базы вместо N

Запись строк в документ на сервере

Если сервер должен сам записать документ по пришедшим строкам, заполняйте табличную часть объекта целиком и записывайте один раз:

&НаСервереБезКонтекста
Процедура ЗаписатьСтроки(Знач Ссылка, Знач Строки)
    ДокументОбъект = Ссылка.ПолучитьОбъект();
    ДокументОбъект.Товары.Очистить();
    Для Каждого Данные Из Строки Цикл
        ЗаполнитьЗначенияСвойств(ДокументОбъект.Товары.Добавить(), Данные);
    КонецЦикла;
    ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
КонецПроцедуры

Если на сервере уже есть таблица значений с совпадающими именами колонок, вместо цикла можно вызвать ДокументОбъект.Товары.Загрузить(Таблица).

Не вызывайте Записать() внутри цикла по строкам. Каждая запись — это транзакция, проверки и движения. Для документа на 500 строк это 500 записей вместо одной.

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

Как передать табличную часть в вашем случае?

Частые вопросы

Можно ли передать на сервер таблицу значений с клиента?
Нет. На тонком и веб-клиенте тип ТаблицаЗначений недоступен. Соберите массив структур на клиенте и преобразуйте его в таблицу на сервере.
Можно ли вернуть с сервера на клиент объект документа?
Нет, ДокументОбъект существует только на сервере. Верните ссылку, структуру с нужными полями или используйте ЗначениеВРеквизитФормы в контекстном вызове.
Чем `Соответствие` лучше массива структур?
Оно удобно, когда сервер должен быстро найти строку по ключу, например по номенклатуре. Для простого перебора строк массив структур проще и нагляднее.
Почему после вызова сервера строки на клиенте не изменились?
Процедура без контекста не видит форму, её изменения в данных формы не попадают. Верните результат функцией и разложите его по строкам на клиенте.
Нужно ли передавать табличную часть, если документ новый?
Да. У незаписанного документа в базе нет строк, ссылка пустая. Передавайте данные параметром или используйте контекстный вызов.