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

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

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

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

Анализ проблемы: почему возникает ошибка при прямом обращении к реквизитам ТЧ?

Рассмотрим ситуацию, с которой столкнулся автор темы. При попытке заполнить реквизиты табличной части «Затраты» документа «Прочие затраты» на основании документа «Платежное поручение исходящее» (ППИ) был написан следующий код:


Затраты.Аналитика = ДанныеЗаполнения.Контрагент;
Затраты.ДоговорКонтрагента = ДанныеЗаполнения.ДоговорКонтрагента;

При выполнении этого кода система предсказуемо выдает ошибку. Выясним причину такого поведения платформы. Дело в том, что Затраты — это не конкретная строка, а объект типа ТабличнаяЧасть, представляющий собой коллекцию строк. У самой коллекции нет и не может быть реквизитов Аналитика или ДоговорКонтрагента. Эти свойства принадлежат исключительно отдельным строкам этой коллекции.

Для того чтобы записать данные в табличную часть, сначала необходимо физически создать в ней строку (или строки), и уже у полученного объекта строки заполнять соответствующие реквизиты.

Решение 1. Простой способ — добавление строки вручную

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

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

Посмотрим на пример реализации данного подхода:


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

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

Решение 2. Архитектурно правильный способ — обход скрытой табличной части источника

Рассмотрим подробнее внутреннее устройство платежных документов в типовых конфигурациях 1С (таких как «Управление производственным предприятием 1.3» или «Комплексная автоматизация 1.1»). Начинающие разработчики часто полагают, что в «Платежном поручении исходящем» или «Расходном кассовом ордере» нет табличной части, так как на форме они видят простые поля для ввода одного договора и одной суммы.

Однако это визуальное заблуждение. На уровне метаданных эти документы практически всегда имеют табличную часть РасшифровкаПлатежа. Даже если пользователь заполняет документ в «однострочном» режиме, платформа на самом деле записывает эти данные в единственную строку ТЧ РасшифровкаПлатежа. Если же пользователь переключит режим редактирования («Редактировать списком»), он сможет разбить один платеж по нескольким договорам, статьям движения денежных средств или сделкам.

Если мы применим Решение 1 к документу, который был заполнен списком, мы перенесем только общие данные шапки и потеряем детализацию платежа. Чтобы избежать этой проблемы, проанализируем ситуацию и напишем правильный обход коллекции в цикле:


Процедура ОбработкаЗаполнения(ДанныеЗаполнения, СтандартнаяОбработка)
    
    Если ТипЗнч(ДанныеЗаполнения) = Тип("ДокументСсылка.ПлатежноеПоручениеИсходящее") Тогда
        
        // Заполнение реквизитов шапки
        ЭтотОбъект.Организация = ДанныеЗаполнения.Организация;
        ЭтотОбъект.Подразделение = ДанныеЗаполнения.Подразделение;
        ЭтотОбъект.ДокументОснование = ДанныеЗаполнения.Ссылка;
        ЭтотОбъект.Комментарий = "Заполнено на основании: " + СокрЛП(ДанныеЗаполнения);
        
        // Обходим строки табличной части документа-основания
        Для Каждого СтрокаИсточника Из ДанныеЗаполнения.РасшифровкаПлатежа Цикл
            
            // Для каждой строки источника создаем строку в приемнике
            НоваяСтрока = ЭтотОбъект.Затраты.Добавить();
            
            // Наполняем строку данными
            НоваяСтрока.Аналитика = ДанныеЗаполнения.Контрагент; // Берется из шапки источника
            НоваяСтрока.ДоговорКонтрагента = СтрокаИсточника.ДоговорКонтрагента; // Из строки источника
            НоваяСтрока.Сумма = СтрокаИсточника.СуммаПлатежа; // Из строки источника
            
        КонецЦикла;
        
    КонецЕсли;
    
КонецПроцедуры

Использование цикла гарантирует, что программа отработает корректно в 100% случаев — и когда у платежки всего одна строка расшифровки, и когда их несколько.

Решение 3. Ускоренное заполнение через выгрузку в Таблицу Значений

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

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


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

Этот метод незаменим, когда документов-оснований много или они содержат десятки и сотни строк. Метод Загрузить() работает на уровне платформы значительно быстрее поэлементного обхода коллекции.

Важное архитектурное правило: почему нельзя переносить код заполнения ТЧ в события формы?

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

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

  1. Программное создание документов: Если в будущем этот документ будет создаваться программно (например, при обмене данными, загрузке из внешних систем, выполнении регламентных заданий на сервере или через Web-сервисы), форма документа вообще не будет открываться. Если код заполнения написан в форме, то программно созданный документ останется абсолютно пустым, без заполненной ТЧ.
  2. Назначение события: Процедура формы ПриНачалеРедактирования предназначена для интерактивных действий пользователя непосредственно в момент начала изменения конкретной строки на форме. Помещение туда логики первичного автозаполнения ТЧ нарушит стандартное поведение платформы и приведет к зацикливанию или дублированию строк.

Использование метода ЗаполнитьЗначенияСвойств

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


// Заполняет все реквизиты с одинаковыми именами из источника в приемник
ЗаполнитьЗначенияСвойств(ЭтотОбъект, ДанныеЗаполнения);

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

← На главную