При разработке документов и обработок в 1С часто возникает задача реализации иерархической связи «один ко многим». Например, в главной таблице содержится перечень номенклатуры (товаров или труб), а для каждой конкретной позиции требуется вести список дополнительных услуг, параметров или серийных номеров. Попытка хранить подчиненные данные внутри ячейки главной таблицы через ХранилищеЗначений или многострочный текст через разделители приводит к неэффективной архитектуре, усложняет запросы и делает невозможным типовой анализ данных.
Рассмотрим, как правильно организовать архитектуру документа с двумя табличными частями и синхронизировать их отображение на управляемой форме с помощью программных отборов платформы 1С:Предприятие 8.3.
Для корректного реляционного связывания создадим две табличные части в объекте метаданных (например, в документе):
НомераТруб или Товары) — содержит реквизиты основной строки и ключевое поле связи (например, реквизит ИД или КлючСвязи).Услуги) — содержит реквизит связи ИД (или КлючСвязи), а также реквизиты подчиненных записей (номенклатуру услуги, количество, стоимость и т.д.).Важно: Не используйте стандартный реквизит НомерСтроки в качестве ключа связи! При удалении, сортировке или перемещении строк главной таблицы платформа автоматически пересчитывает номера строк, что приведет к полной рассинхронизации данных. В качестве ключа связи рекомендуется использовать УникальныйИдентификатор (или строковый GUID фиксированной длины), либо целочисленный реквизит (тип Число(10, 0)).
Разберем по шагам логику клиентского кода для синхронизации отображения главной и подчиненной таблиц.
Шаг 1. Фильтрация подчиненной таблицы при смене строки в главной таблице
При переходе пользователя по строкам главной таблицы в подчиненной таблице должны отображаться только те строки, у которых ключ связи совпадает с ключом текущей главной строки. Для этого используем свойство элемента формы ОтборСтрок, которому присваивается ФиксированнаяСтруктура.
&НаКлиенте
Процедура НомераТрубПриАктивизацииСтроки(Элемент)
Данные = Элементы.НомераТруб.ТекущиеДанные;
Если Данные = Неопределено Тогда
// Если главная таблица пуста, скрываем все строки подчиненной таблицы
Элементы.Услуги.ОтборСтрок = Новый ФиксированнаяСтруктура("ИД", "");
Возврат;
КонецЕсли;
// Устанавливаем отбор по идентификатору текущей строки
Элементы.Услуги.ОтборСтрок = Новый ФиксированнаяСтруктура("ИД", Данные.ИД);
КонецПроцедуры
Шаг 2. Генерация ключа при создании новой строки в главной таблице
В событии ПриНачалеРедактирования главной таблицы проверяем, является ли строка новой. Если строка только создается, генерируем для нее уникальный идентификатор.
&НаКлиенте
Процедура НомераТрубПриНачалеРедактирования(Элемент, НоваяСтрока, Копирование)
Данные = Элементы.НомераТруб.ТекущиеДанные;
Если НоваяСтрока Тогда
Данные.ИД = Новый УникальныйИдентификатор;
КонецЕсли;
КонецПроцедуры
Шаг 3. Заполнение ключа связи в подчиненной таблице
Когда пользователь добавляет строку в подчиненную таблицу, в нее необходимо автоматически подставить ИД текущей активной строки из главной таблицы. Если на подчиненную таблицу уже наложен ОтборСтрок, платформа может заполнить поле автоматически, однако для гарантии целостности перестрахуемся в событии начала редактирования:
&НаКлиенте
Процедура УслугиПриНачалеРедактирования(Элемент, НоваяСтрока, Копирование)
ДанныеГлавной = Элементы.НомераТруб.ТекущиеДанные;
ТекДанныеПодчиненной = Элементы.Услуги.ТекущиеДанные;
Если НоваяСтрока И ДанныеГлавной <> Неопределено Тогда
ТекДанныеПодчиненной.ИД = ДанныеГлавной.ИД;
КонецЕсли;
КонецПроцедуры
Шаг 4. Каскадное удаление подчиненных строк
При интерактивном удалении строки из главной таблицы все связанные с ней записи из подчиненной таблицы также должны быть удалены, чтобы избежать появления «мусорных» строк. Реализуем это в событии ПередУдалением главной таблицы:
&НаКлиенте
Процедура НомераТрубПередУдалением(Элемент, Отказ)
Данные = Элементы.НомераТруб.ТекущиеДанные;
Если Данные = Неопределено Тогда
Возврат;
КонецЕсли;
// Ищем все строки в подчиненной ТЧ с таким же идентификатором
Отбор = Новый Структура("ИД", Данные.ИД);
НайденныеСтроки = Объект.Услуги.НайтиСтроки(Отбор);
Для Каждого СтрокаТЧ Из НайденныеСтроки Цикл
Объект.Услуги.Удалить(СтрокаТЧ);
КонецЦикла;
КонецПроцедуры
Проанализируем тонкости, которые необходимо учесть для обеспечения надежной работы интерфейса:
ПередНачаломДобавления подчиненной таблицы обязательно проверяйте условие Элементы.НомераТруб.ТекущиеДанные = Неопределено. Если в главной таблице не выделено ни одной строки, следует установить Отказ = Истина и выдать предупреждение пользователю.Копирование = Истина в ПриНачалеРедактирования), платформенный механизм продублирует старый ИД. В этом случае необходимо сформировать новый уникальный ключ для копии и программно продублировать все связанные строки подчиненной ТЧ, присвоив им сгенерированный ключ.ПередЗаписью полезно реализовать серверную очистку: запросом выгрузить все уникальные ключи из главной ТЧ и удалить из подчиненной ТЧ строки с ключами, которых нет в главной.В зависимости от бизнес-требований можно рассмотреть и другие решения:
ДеревоЗначений: На форму выводится один элемент с типом ДеревоЗначений, где верхний уровень — это товары, а вложенные узлы — услуги. При открытии дерево заполняется из двух табличных частей документа, а при записи программно выгружается обратно.