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

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

В процессе разработки в среде 1С:Предприятие 8.3 программисты часто сталкиваются с задачей заполнения или перезаполнения табличных частей (ТЧ) документов и справочников на управляемой форме. Классический пример — заполнение табличной части документа «Реализация товаров и услуг» на основании выбранного «Заказа покупателя». На первый взгляд задача кажется простой, но при её решении легко допустить грубые архитектурные ошибки, которые приведут к некорректному поведению интерфейса, потере связанных данных или игнорированию бизнес-логики конфигурации.

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

Проблема наивного подхода: Почему метод Загрузить() — это антипаттерн?

Очень часто начинающие разработчики используют простой и лаконичный путь заполнения табличной части — прямую выгрузку результата запроса в таблицу значений с последующей загрузкой в реквизит объекта. На сервере управляемой формы этот код выглядит следующим образом:


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

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

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

Способ 1. Использование объектной техники (Реквизит в Значение)

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

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


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

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

Способ 2. Заполнение «На основании» и обработчик ОбработкаЗаполнения

Если логика вашей конфигурации предполагает, что один документ создается строго на основании другого (в нашем примере — «Реализация» создается на основании «Заказа»), то правильнее всего перенести весь код заполнения табличной части в модуль объекта создаваемого документа в предопределенный обработчик ОбработкаЗаполнения.

Рассмотрим пример реализации такого заполнения в модуле объекта документа «РеализацияТоваровУслуг»:


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

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

Способ 3. Передача подобранных данных через Временное хранилище

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

Давайте разберем схему работы этого механизма:

  1. В форме выбора (подбора) формируется таблица значений с необходимыми данными.
  2. Эта таблица помещается во временное хранилище на сервере, и адрес хранилища передается обратно в форму документа в процедуру ОбработкаВыбора.
  3. В форме документа по полученному адресу извлекается таблица значений, и строки переносятся в табличную часть документа с обходом строк.

Посмотрим на практический пример кода обработчика выбора в модуле формы документа:


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

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

Способ 4. Стандарты БСП (Библиотека стандартных подсистем)

Если вы ведете разработку в современных типовых конфигурациях (таких как ERP, КА, УТ или БП), то самым правильным решением будет использование встроенного механизма БСП «Заполнение объектов». Он позволяет добавлять команды заполнения как в виде кнопок на форме, так и в виде подключаемых внешних обработок заполнения табличных частей.

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

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

Использование БСП позволяет свести к минимуму написание собственного «велосипедного» кода и гарантирует, что при обновлении конфигурации логика заполнения табличных частей не сломается.

Организация кода пересчета полей табличной части

Чтобы программное заполнение ТЧ всегда отрабатывало корректно, необходимо правильно спроектировать вызов формул расчета зависимых реквизитов (например, когда Сумма рассчитывается как Цена * Количество). Ошибкой является привязка логики расчетов исключительно к клиентским событиям элементов формы (таким как ТоварыКоличествоПриИзменении).

Рекомендуется выносить все расчетные формулы в отдельные неконтекстные процедуры общего модуля или модуля менеджера объекта. Пример правильной организации вызовов:


// В общем модуле (например, РасчетыКлиентСервер)
Процедура РассчитатьСуммуСтроки(СтрокаТЧ) Экспорт
    СтрокаТЧ.Сумма = СтрокаТЧ.Количество * СтрокаТЧ.Цена;
КонецПроцедуры

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

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

← На главную