Как реализовать редактирование зависимой табличной части (вложенных строк) в отдельном окне или на одной форме?

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

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

В этой статье мы подробно разберем, как архитектурно правильно реализовать связь "Master-Detail" (Главная-Подчиненная таблица) и рассмотрим два основных способа редактирования таких данных: на одной форме и в отдельном окне.

Архитектурная основа: Связь двух таблиц

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

  1. Основная таблица (например, Товары).
  2. Подчиненная таблица (например, СоставНаборов или ДополнительныеРаботы).

Самый важный вопрос — как связать строки между собой? Использование номера строки (НомерСтроки) — плохая практика, так как при сортировке или удалении строк в основной таблице номера сдвинутся, и связь разрушится. Мы применим более надежный подход — связь по уникальному идентификатору (Ключу связи).

Для этого нам необходимо добавить служебные колонки:

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

Способ 1: Две таблицы на одной форме (Master-Detail)

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

Рассмотрим алгоритм реализации:

  1. Размещаем на форме таблицу Товары (сверху) и таблицу СоставНаборов (снизу).
  2. В событии ПриАктивизацииСтроки верхней таблицы мы считываем текущий КлючСтроки.
  3. Устанавливаем отбор на нижнюю таблицу, чтобы в ней отображались только записи, относящиеся к текущей строке верха.

Посмотрим, как это выглядит в коде формы:


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

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

    // Устанавливаем визуальный отбор в подчиненной таблице
    Отбор = Новый ФиксированнаяСтруктура("КлючСвязи", ТекущиеДанные.КлючСтроки);
    Элементы.СоставНаборов.ОтборСтрок = Отбор;

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

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


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

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

Способ 2: Редактирование в отдельном окне

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

Разберем процесс пошагово.

Шаг 1: Подготовка данных и открытие формы

В основной форме документа создаем команду (кнопку) "Редактировать состав". При нажатии мы должны:

  1. Найти все строки в подчиненной ТЧ, относящиеся к текущей строке.
  2. Выгрузить их в ТаблицаЗначений или массив структур.
  3. Поместить эти данные во временное хранилище.
  4. Открыть вспомогательную форму, передав ей адрес хранилища.

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

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

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

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

Шаг 2: Работа во вспомогательной форме

Во вспомогательной форме у нас должен быть реквизит формы типа ТаблицаЗначений (назовем его ТаблицаРедактирования), выведенный на форму. Пользователь работает с ним стандартными кнопками "Добавить", "Удалить".

При создании формы на сервере мы читаем данные из адреса:


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

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

Когда пользователь нажимает "ОК" (Записать и закрыть), мы помещаем измененную таблицу обратно в хранилище и оповещаем владельца:


&НаКлиенте
Процедура КомандаОК(Команда)
    
    АдресРезультата = ПоместитьРезультатВХранилище();
    // Возвращаем адрес с новыми данными
    Закрыть(АдресРезультата);

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

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

Шаг 3: Обработка результата в основной форме

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


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

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

Альтернативный вариант: Дерево значений

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

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

Каскадное удаление

Независимо от выбранного способа (на форме или в отдельном окне), не забывайте про логику удаления. Если пользователь удаляет строку из основной таблицы Товары, в базе данных могут остаться "сироты" в таблице СоставНаборов.

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

Выводы

Проанализируем, какой метод лучше выбрать:

← На главную