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

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

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

Попытки привязаться к номеру строки, индексу или результату метода ПолучитьИдентификатор() приводят к ошибкам: при удалении, сортировке или перемещении строк порядок сбивается, а метод ПолучитьИдентификатор() возвращает лишь временный идентификатор элемента в памяти клиента, который не сохраняется в базе данных и меняется при переоткрытии формы.

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

Почему нельзя использовать ПолучитьИдентификатор() и номер строки

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

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

Основное решение: использование числового ключа связи (паттерн типовых конфигураций)

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

  1. В основной табличной части (например, Услуги) создаем реквизит КлючСтроки с типом Число (длина 5–10, точность 0, неотрицательное).
  2. В подчиненной табличной части (например, Характеристики) создаем реквизит КлючСвязи с точно такими же параметрами типа Число.
  3. При добавлении новой строки в основную таблицу генерируем уникальное значение КлючСтроки.
  4. В подчиненную таблицу добавляем строки, заполняя реквизит КлючСвязи значением КлючСтроки родительской записи.

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


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

&НаКлиенте
Функция ПолучитьНовыйКлючСтроки()
    МаксимальныйКлюч = 0;
    Для Каждого Стр Из Объект.Услуги Цикл
        Если Стр.КлючСтроки > МаксимальныйКлюч Тогда
            МаксимальныйКлюч = Стр.КлючСтроки;
        КонецЕсли;
    КонецЦикла;
    Возврат МаксимальныйКлюч + 1;
КонецФункции

Фильтрация подчиненной таблицы в интерфейсе управляемой формы

Чтобы пользователь видел в подчиненной таблице только те строки, которые относятся к текущей выбранной строке основной таблицы, настроим фильтрацию через свойство ОтборСтрок.

Разберем обработчик события ПриАктивизацииСтроки для основной таблицы формы:


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

При добавлении строки непосредственно в подчиненную таблицу необходимо автоматически подставлять текущий КлючСтроки родителя:


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

Контроль целостности данных при удалении строк

Особое внимание уделим удалению строк из основной таблицы. Если пользователь удалит услугу, связанные с ней характеристики останутся в подчиненной табличной части («строки-сироты»), что приведет к засорению базы данных. Удалим зависимые строки в обработчике события ПередУдалением основной таблицы:


&НаКлиенте
Процедура УслугиПередУдалением(Элемент, Отказ)
    ТекущиеДанные = Элемент.ТекущиеДанные;
    Если ТекущиеДанные <> Неопределено И ЗначениеЗаполнено(ТекущиеДанные.КлючСтроки) Тогда
        УдалитьПодчиненныеСтроки(ТекущиеДанные.КлючСтроки);
    КонецЕсли;
КонецПроцедуры

&НаКлиенте
Процедура УдалитьПодчиненныеСтроки(КлючСвязи)
    Индекс = Объект.Характеристики.Количество() - 1;
    Пока Индекс >= 0 Цикл
        Если Объект.Характеристики[Индекс].КлючСвязи = КлючСвязи Тогда
            Объект.Характеристики.Удалить(Индекс);
        КонецЕсли;
        Индекс = Индекс - 1;
    КонецЦикла;
КонецПроцедуры

Альтернативные способы хранения подчиненных данных

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

  1. Хранение структуры в строке JSON. Если характеристики носят чисто описательный характер, не участвуют в движениях регистров и не требуют выборок в запросах, в основную таблицу можно добавить строковый реквизит неограниченной длины. Данные сериализуются в строку JSON и открываются во всплывающем диалоговом окне. Преимущество — абсолютная целостность данных без необходимости синхронизировать ключи при удалении или копировании строк.
  2. Связка через УникальныйИдентификатор (UUID). Вместо числа можно использовать строку, генерируемую методом Новый УникальныйИдентификатор(). Однако строковые UUID занимают больше места в базе данных, медленнее индексируются и усложняют визуальную отладку по сравнению с компактными целыми числами.
← На главную