Как заполнить табличную часть одного документа из табличной части другого документа в 1С 8.3

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

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

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

---

Разбор типичных ошибок: почему код не работает на клиенте

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


&НаКлиенте
Процедура ОбработкаВыбора(ВыбранноеЗначение, ИсточникВыбора)
    Если ТипЗнч(ВыбранноеЗначение) = Тип("ДокументСсылка.Сортировка") Тогда
        // Ошибка №1: Попытка обхода ТЧ на клиенте
        Для Каждого СтрокаТЧ Из ВыбранноеЗначение.Товары Цикл 
            НоваяСтрока = Объект.Товары.Добавить();
            // Ошибка №2: Непонимание работы ЗаполнитьЗначенияСвойств
            ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаТЧ); 
        КонецЦикла;
    КонецЕсли;
КонецПроцедуры

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

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

---

Решение 1: Базовый рабочий вариант с переходом на сервер

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

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


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

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

Этот код является абсолютно рабочим. Мы успешно перенесли выполнение на сервер, получили доступ к табличной части Товары документа-источника через объект ссылки и скопировали строки при помощи сопоставления их свойств.

---

Почему обращение «через точку» на сервере — это плохая практика?

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

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

---

Решение 2: Профессиональный подход с использованием Запроса

Для того чтобы прочитать из базы данных только те колонки и строки табличной части, которые нам действительно необходимы, самым эффективным инструментом в 1С являются запросы. Запрос обращается напрямую к таблицам СУБД и возвращает исключительно затребованную информацию, минуя дорогостоящую операцию получения тяжелого объекта в память сервера.

Проанализируем оптимальный вариант реализации заполнения табличной части с использованием запроса:


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

Используя данный метод, мы существенно сокращаем объем передаваемых данных между СУБД и сервером приложений 1С, повышая общую скорость работы интерфейса и стабильность системы при одновременной работе многих пользователей.

---

Решение 3: Оптимизация с использованием метода Загрузить()

Если перед нами стоит задача **полностью перезаписать** табличную часть нашего документа данными из документа-источника, то построчный обход выборки запроса в цикле с вызовом метода Добавить() также можно оптимизировать.

Для этого в платформе 1С существует встроенный высокопроизводительный метод Загрузить(), доступный у табличных частей. Он позволяет загрузить данные из готовой таблицы значений за одну команду. Платформа выполняет это действие на уровне внутренних оптимизированных C++ механизмов, что работает в разы быстрее интерпретируемого кода на языке 1С.

Разберем, как применить этот метод на практике:


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

Важное примечание: метод Загрузить() полностью очищает ТЧ перед заполнением. Если вам требуется не заменить данные, а добавить новые строки к уже существующим в документе, то этот метод не подойдет — в этом случае используйте построчный обход из Решения 2.

---

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

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

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


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

---

Продвинутый подход: Использование Временного хранилища

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

Схема взаимодействия форм в данном случае выглядит следующим образом:

  1. В открываемой форме выбора пользователь отмечает галочками строки табличной части.
  2. При подтверждении выбора в модуле формы выбора программно формируется таблица значений, которая помещается во временное хранилище с помощью функции ПоместитьВоВременноеХранилище(). Платформа возвращает уникальный адрес (строку вида e1cib/tempstorage/...).
  3. В вызывающую форму через процедуру ОповеститьОВыборе() передается только этот строковый адрес.
  4. Основная форма документа принимает этот адрес, переходит на сервер, извлекает таблицу значений методом ПолучитьИзВременногоХранилища() и быстро загружает данные в документ.

Этот подход минимизирует объем передаваемых данных между клиентом и сервером и является стандартом разработки в современных типовых конфигурациях 1С, построенных на базе БСП (Библиотеки Стандартных Подсистем).

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

← На главную