При разработке сложных документов часто возникает задача связать одну строку основной табличной части со множеством строк другой (подчиненной) табличной части. Типичный пример: расшифровка состава услуг, список серийных номеров, детальные скидки или набор произвольных характеристик для каждой конкретной позиции номенклатуры.
Попытки привязаться к номеру строки, индексу или результату метода ПолучитьИдентификатор() приводят к ошибкам: при удалении, сортировке или перемещении строк порядок сбивается, а метод ПолучитьИдентификатор() возвращает лишь временный идентификатор элемента в памяти клиента, который не сохраняется в базе данных и меняется при переоткрытии формы.
Разберем, как правильно организовать архитектуру связывания строк двух табличных частей документа в 1С на управляемых формах.
Проанализируем поведение платформы. Свойство НомерСтроки и индекс элемента коллекции динамически пересчитываются платформой при любом изменении порядка записей в табличной части. Если пользователь переместит строку вверх или вниз, старый номер перестанет указывать на нужный элемент.
Метод ПолучитьИдентификатор() возвращает числовой идентификатор элемента объекта ДанныеФормыКоллекция. Он существует исключительно в оперативной памяти клиентского контекста формы для обслуживания визуальных элементов интерфейса. При передаче данных на сервер, перечитывании или повторном открытии документа платформа генерирует новые идентификаторы. Поэтому использовать его для хранения ссылочной связи между строками в базе данных нельзя.
В типовых конфигурациях 1С (таких как ERP, Управление торговлей или Бухгалтерия предприятия) связь «один ко многим» между табличными частями реализуется через реквизиты связи целочисленного типа. Выясним, как устроена эта схема по шагам.
Услуги) создаем реквизит КлючСтроки с типом Число (длина 5–10, точность 0, неотрицательное).Характеристики) создаем реквизит КлючСвязи с точно такими же параметрами типа Число.КлючСтроки.КлючСвязи значением КлючСтроки родительской записи.Посмотрим на пример генерации ключа строки в модуле формы документа при добавлении новой строки в основную табличную часть:
&НаКлиенте
Процедура УслугиПриНачалеРедактирования(Элемент, НоваяСтрока, Копирование)
Если НоваяСтрока Тогда
Если Не Копирование Тогда
Элемент.ТекущиеДанные.КлючСтроки = ПолучитьНовыйКлючСтроки();
Иначе
// При копировании строки генерируем новый ключ
СтарыйКлюч = Элемент.ТекущиеДанные.КлючСтроки;
НовыйКлюч = ПолучитьНовыйКлючСтроки();
Элемент.ТекущиеДанные.КлючСтроки = НовыйКлюч;
СкопироватьПодчиненныеСтроки(СтарыйКлюч, НовыйКлюч);
КонецЕсли;
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Функция ПолучитьНовыйКлючСтроки()
МаксимальныйКлюч = 0;
Для Каждого Стр Из Объект.Услуги Цикл
Если Стр.КлючСтроки > МаксимальныйКлюч Тогда
МаксимальныйКлюч = Стр.КлючСтроки;
КонецЕсли;
КонецЦикла;
Возврат МаксимальныйКлюч + 1;
КонецФункции
Чтобы пользователь видел в подчиненной таблице только те строки, которые относятся к текущей выбранной строке основной таблицы, настроим фильтрацию через свойство ОтборСтрок.
Разберем обработчик события ПриАктивизацииСтроки для основной таблицы формы:
&НаКлиенте
Процедура УслугиПриАктивизацииСтроки(Элемент)
ТекущиеДанные = Элемент.ТекущиеДанные;
Если ТекущиеДанные <> Неопределено И ЗначениеЗаполнено(ТекущиеДанные.КлючСтроки) Тогда
// Устанавливаем отбор подчиненных строк по ключу связи
Элементы.Характеристики.ОтборСтрок = Новый ФиксированнаяСтруктура("КлючСвязи", ТекущиеДанные.КлючСтроки);
Иначе
// Если строка не выбрана, скрываем все подчиненные строки
Элементы.Характеристики.ОтборСтрок = Новый ФиксированнаяСтруктура("КлючСвязи", -1);
КонецЕсли;
КонецПроцедуры
При добавлении строки непосредственно в подчиненную таблицу необходимо автоматически подставлять текущий КлючСтроки родителя:
&НаКлиенте
Процедура ХарактеристикиПриНачалеРедактирования(Элемент, НоваяСтрока, Копирование)
Если НоваяСтрока Тогда
ТекущиеДанныеУслуги = Элементы.Услуги.ТекущиеДанные;
Если ТекущиеДанныеУслуги <> Неопределено Тогда
Элемент.ТекущиеДанные.КлючСвязи = ТекущиеДанныеУслуги.КлючСтроки;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Особое внимание уделим удалению строк из основной таблицы. Если пользователь удалит услугу, связанные с ней характеристики останутся в подчиненной табличной части («строки-сироты»), что приведет к засорению базы данных. Удалим зависимые строки в обработчике события ПередУдалением основной таблицы:
&НаКлиенте
Процедура УслугиПередУдалением(Элемент, Отказ)
ТекущиеДанные = Элемент.ТекущиеДанные;
Если ТекущиеДанные <> Неопределено И ЗначениеЗаполнено(ТекущиеДанные.КлючСтроки) Тогда
УдалитьПодчиненныеСтроки(ТекущиеДанные.КлючСтроки);
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура УдалитьПодчиненныеСтроки(КлючСвязи)
Индекс = Объект.Характеристики.Количество() - 1;
Пока Индекс >= 0 Цикл
Если Объект.Характеристики[Индекс].КлючСвязи = КлючСвязи Тогда
Объект.Характеристики.Удалить(Индекс);
КонецЕсли;
Индекс = Индекс - 1;
КонецЦикла;
КонецПроцедуры
Рассмотрим другие варианты решения, когда создание второй табличной части не является оптимальным:
Новый УникальныйИдентификатор(). Однако строковые UUID занимают больше места в базе данных, медленнее индексируются и усложняют визуальную отладку по сравнению с компактными целыми числами.