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

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

В практике разработки на платформе 1С:Предприятие 8.3 одной из классических задач является заполнение или обновление значений в определенной колонке уже существующих строк табличной части (ТЧ) документа. Обычно данные для этого берутся из базы данных (регистров накопления, сведений или справочников) с помощью запроса.

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

Анализ типичных ошибок из практики разработчиков

Прежде чем перейти к правильным алгоритмам, давайте разберем три главные ошибки, которые часто совершают при решении этой задачи:

  1. Использование метода Добавить() вместо изменения существующих строк. Попытка заполнить ТЧ кодом вида НоваяСтрока = Объект.ТЧ.Добавить() приводит к тому, что в документ добавляются новые пустые строки с заполненным единственным полем, в то время как исходные строки остаются незаполненными. Если строки в ТЧ уже существуют, нам нужно модифицировать именно их, а не создавать новые.
  2. Несоответствие имен параметров. При передаче параметров в запрос легко ошибиться в их именах. Например, если в коде написано Запрос.УстановитьПараметр("Массив", ...), а в тексте запроса используется переменная &Услуга, платформа выдаст критическую ошибку выполнения: «Не задано значение параметра "Услуга"». Всегда тщательно проверяйте соответствие имен в коде и в тексте запроса.
  3. Прямая передача табличной части управляемой формы в запрос. На сервере управляемой формы табличная часть документа представлена типом ДанныеФормыКоллекция. Этот тип данных оптимизирован для передачи между клиентом и сервером, но СУБД ничего о нем не знает. Его нельзя напрямую передать в параметры запроса или использовать в качестве источника данных. Предварительно коллекцию необходимо выгрузить.

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

---

Способ 1. Использование Временных Таблиц и Левого Соединения (Рекомендуемый)

Этот способ является наиболее профессиональным и производительным. Он идеально подходит для документов с большим количеством строк (от 100 и более). Вместо того чтобы выполнять ресурсоемкий поиск строк в цикле, мы полностью переносим сопоставление данных на сторону СУБД с помощью временных таблиц.

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

Разберем по шагам реализацию этого механизма в серверной процедуре формы:


&НаСервере
Процедура ЗаполнитьПоле1НаСервере()

    // Шаг 1. Выгружаем ТЧ документа в Таблицу Значений.
    // Нам обязательно нужны колонка связи (например, Услуга) и НомерСтроки для точной идентификации.
    ТаблицаТЧ = Объект.Услуги.Выгрузить(, "НомерСтроки, Услуга");
    
    // Шаг 2. Создаем запрос и инициализируем Менеджер Временных Таблиц
    Запрос = Новый Запрос;
    Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
    
    // Шаг 3. Помещаем выгруженную Таблицу Значений во временную таблицу запроса
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   ТЗ.НомерСтроки КАК НомерСтроки,
        |   ТЗ.Услуга КАК Услуга
        |ПОМЕСТИТЬ ВТ_ТабличнаяЧасть
        |ИЗ
        |   &ТаблицаТЧ КАК ТЗ";
        
    Запрос.УстановитьПараметр("ТаблицаТЧ", ТаблицаТЧ);
    Запрос.Выполнить();
    
    // Шаг 4. Соединяем временную таблицу с регистром (например, остатков)
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   ВТ.НомерСтроки КАК НомерСтроки,
        |   ЕСТЬNULL(РасчетыОстатки.СуммаОстаток, 0) КАК ЗначениеДляПоля1
        |ИЗ
        |   ВТ_ТабличнаяЧасть КАК ВТ
        |   ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.РасчетыСальдо.Остатки(, Услуга В (ВЫБРАТЬ Услуга ИЗ ВТ_ТабличнаяЧасть)) КАК РасчетыОстатки
        |       ПО ВТ.Услуга = РасчетыОстатки.Услуга";
        
    РезультатЗапроса = Запрос.Выполнить();
    Выборка = РезультатЗапроса.Выбрать();
    
    // Шаг 5. Обходим выборку и заполняем данные в ТЧ напрямую по индексу
    Пока Выборка.Следующий() Цикл
        // Номер строки в ТЧ начинается с 1, а индексы в коллекции — с 0.
        ИндексСтроки = Выборка.НомерСтроки - 1;
        СтрокаТЧ = Объект.Услуги[ИндексСтроки];
        СтрокаТЧ.Поле1 = Выборка.ЗначениеДляПоля1;
    КонецЦикла;

КонецПроцедуры

Почему это работает быстро? Мы полностью избегаем линейного поиска строк программными средствами 1С. СУБД самостоятельно производит сопоставление (соединение) данных на уровне SQL. Нам остается лишь обратиться к конкретным строкам ТЧ по их системным индексам, что происходит мгновенно.

---

Способ 2. Запрос по массиву ключей и метод НайтиСтроки()

Этот способ проще в написании и понимании. Его целесообразно использовать для небольших документов (до 50-100 строк), где накладные расходы на создание временных таблиц не окупаются, а также в случаях, когда одна и та же номенклатура/услуга может часто дублироваться в разных строках документа.

Алгоритм строится на получении массива уникальных значений ключевого поля, получении по ним данных из базы и последующем поиске строк ТЧ методом НайтиСтроки().

Рассмотрим детальный пример реализации данного подхода:


&НаСервере
Процедура ЗаполнитьПоле1ЧерезПоискНаСервере()

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

КонецПроцедуры

Особенности метода: Если в табличной части содержатся дублирующиеся строки с одинаковыми ключевыми значениями, метод НайтиСтроки() найдет и обновит их все за один проход цикла. Это делает код надежным и защищает от пропусков строк.

---

Особенности обновления интерфейса после серверного вызова

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

Платформа 1С самостоятельно отслеживает изменения в объекте ДанныеФормыСтруктура (которым является Объект) на сервере и транслирует их обратно на клиентскую сторону при завершении серверного вызова. Поэтому разработчику не требуется писать дополнительный код на клиенте для обновления табличного поля — формы обновятся автоматически.

Рекомендуется вызывать эти серверные методы из обработчика команды (кнопки на форме) следующим образом:


&НаКлиенте
Процедура КомандаЗаполнить(Команда)
    
    // Вызываем контекстный серверный метод
    ЗаполнитьПоле1НаСервере();
    
    // Сообщаем пользователю об успешном завершении операции
    ПоказатьОповещениеПользователя("Успешно", , "Поля табличной части успешно обновлены!");
    
КонецПроцедуры

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

← На главную