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

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

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

Разбор механизма связи через UUID

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

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


Процедура ЗаявкаПриНачалеРедактирования(Элемент, НоваяСтрока, Копирование)
    Данные = ЭлементыФормы.Заявка.ТекущаяСтрока;
    Если НоваяСтрока Тогда 
        Данные.ИД = Новый УникальныйИдентификатор;
    КонецЕсли;
КонецПроцедуры

Процедура МатериалыПриНачалеРедактирования(Элемент, НоваяСтрока, Копирование)
    ДанныеГлавнойТаблицы = ЭлементыФормы.Заявка.ТекущаяСтрока;
    ТекДанные = ЭлементыФормы.Материалы.ТекущаяСтрока;
    Если НоваяСтрока И ДанныеГлавнойТаблицы <> Неопределено Тогда
        ТекДанные.ИД = ДанныеГлавнойТаблицы.ИД;
    КонецЕсли;
КонецПроцедуры

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


Процедура ЗаявкаПриАктивизацииСтроки(Элемент)
    Данные = ЭлементыФормы.Заявка.ТекущаяСтрока;
    Если Данные = Неопределено Тогда
        ЭлементыФормы.Материалы.ОтборСтрок.ИД.Использование = Ложь;
        Возврат;
    КонецЕсли;
    
    // Устанавливаем фильтр по ИД для подчиненной таблицы
    ЭлементыФормы.Материалы.ОтборСтрок.ИД.Значение = Данные.ИД;
    ЭлементыФормы.Материалы.ОтборСтрок.ИД.Использование = Истина;
КонецПроцедуры

Проблема переноса данных в другой документ

Основная сложность возникает, когда нам нужно перенести эти данные, например, из «Заявки» в «Расчет». Если мы просто переносим «полезные» колонки (количество, номенклатуру, цену), но забываем перенести колонку ИД, связь в новом документе перестает работать. Фильтр в документе «Расчет» будет пытаться найти строки с идентификатором, который есть в его главной таблице, но в подчиненных таблицах поле ИД будет пустым или некорректным.

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


// ОШИБОЧНЫЙ ВАРИАНТ
Для Каждого ТекСтрокаЗаявка Из Источник.Заявка Цикл
    НоваяСтрока = Получатель.ЗаявкаТЧ.Добавить();
    НоваяСтрока.Продукция = ТекСтрокаЗаявка.Продукция;
    НоваяСтрока.Количество = ТекСтрокаЗаявка.Количество;
    // Здесь пропущен перенос ИД!
КонецЦикла;

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

  1. Очищаем табличные части документа-получателя.
  2. Переносим строки главной таблицы, включая поле ИД.
  3. Переносим строки всех подчиненных таблиц, также сохраняя их оригинальный ИД.

Разберем исправленный код для процедуры ЗаявкаПриИзменении в документе «Расчет»:


Процедура ЗаявкаПриИзменении(Элемент)
    ОбъектЗаявка = ЭлементыФормы.Заявка.Значение; // Ссылка на выбранный документ
    Если ОбъектЗаявка.Пустая() Тогда
        Возврат;
    КонецЕсли;

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

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

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

Оптимизация: использование методов Выгрузить и Загрузить

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

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


// Оптимизированный перенос
ТЗ_Главная = ОбъектЗаявка.Заявка.Выгрузить();
ЗаявкаТЧ.Загрузить(ТЗ_Главная);

ТЗ_Материалы = ОбъектЗаявка.Материалы.Выгрузить();
Материалы.Загрузить(ТЗ_Материалы);

ТЗ_Технологии = ОбъектЗаявка.Технологии.Выгрузить();
Технологии.Загрузить(ТЗ_Технологии);

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

Критическая ошибка работы с данными в циклах

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


// КРАЙНЕ ОПАСНЫЙ КОД
ВыбД = Документы.Заявка.Выбрать();
Пока ВыбД.Следующий() Цикл
    ЭлементыФормы.Клиент.Заголовок = "Клиент: " + Строка(ВыбД.Клиент);
КонецЦикла; 

Этот код заставляет программу перебирать все заявки в базе данных при каждом выборе документа. В итоге в заголовок попадут данные последней заявки из выборки, а не той, которую выбрал пользователь. Вместо этого следует обращаться напрямую к реквизитам выбранной ссылки: Заявка.Клиент.

Рекомендации по производительности в обычных формах

Работа с ОтборСтрок в обычных формах происходит на стороне клиента. Чтобы интерфейс не «тормозил», придерживайтесь следующих правил:

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

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

← На главную