Как реализовать связь «многие-ко-многим» между двумя справочниками в 1С: выбор архитектуры и проектирование интерфейса

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

При разработке конфигураций на платформе 1С:Предприятие 8.3 программисты регулярно сталкиваются с необходимостью связать два независимых объекта. Классический пример — справочники Файлы и Папки. Ситуация усложняется, когда один и тот же файл должен находиться в нескольких папках одновременно (например, скан-копия общего договора относится и к папке контрагента, и к папке конкретного проекта). Это классическая связь вида «многие-ко-многим».

В этой статье мы подробно разберем, как правильно спроектировать структуру хранения таких данных в СУБД, почему использование табличных частей здесь неэффективно и как реализовать современный, интуитивно понятный пользовательский интерфейс с поддержкой механизма Drag-and-Drop на управляемых формах.

---

Анализ архитектурных решений: Табличная часть или Регистр сведений?

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

  1. Хранение списка ссылок в табличной части (ТЧ) справочника Папки (или наоборот, в справочнике Файлы).
  2. Использование независимого регистра сведений (РС) в качестве таблицы связей.

Выясним причину, почему первый вариант (использование ТЧ) крайне нежелателен в реальных высоконагруженных системах:

Проанализируем ситуацию с использованием независимого регистра сведений. Создание регистра с измерениями Папка и Файл решает все эти проблемы:

---

Проектирование структуры метаданных

Рассмотрим подробнее процесс создания объектов в конфигураторе. Нам понадобятся три основных объекта:

  1. Справочник Файлы — хранит информацию о файлах (имя, путь к файлу на сервере или двоичные данные).
  2. Справочник Папки — хранит информацию о папках (наименование, описание, владелец).
  3. Регистр сведений СвязиПапокИФайлов (независимый, непериодический). В нем создаем два измерения:
    • Папка (тип: СправочникСсылка.Папки, свойство Ведущее включено).
    • Файл (тип: СправочникСсылка.Файлы, свойство Ведущее включено).

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

---

Создание интерфейса по паттерну Master-Detail (Главный-Подчиненный)

Посмотрим на пример построения удобной управляемой формы. Лучший вариант — спроектировать единую форму распределения файлов, которая разделена на три функциональные зоны:

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


ВЫБРАТЬ
    СвязиПапокИФайлов.Файл КАК Файл,
    СвязиПапокИФайлов.Файл.Наименование КАК Наименование
ИЗ
    РегистрСведений.СвязиПапокИФайлов КАК СвязиПапокИФайлов
ГДЕ
    СвязиПапокИФайлов.Папка = &ТекущаяПапка

Для того чтобы список привязанных файлов обновлялся автоматически при перемещении по списку папок, создадим обработчик события ПриАктивизацииСтроки у таблицы папок:


&НаКлиенте
Процедура СписокПапокПриАктивизацииСтроки(Элемент)
    
    ТекущиеДанные = Элементы.СписокПапок.ТекущиеДанные;
    Если ТекущиеДанные <> Неопределено Тогда
        УстановитьОтборФайловНаСервере(ТекущиеДанные.Ссылка);
    КонецЕсли;
    
КонецПроцедуры

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

---

Реализация механизма Drag-and-Drop (перетаскивания) мышью

Чтобы сделать интерфейс интерактивным, добавим возможность перетаскивать файлы из нижнего списка «Свободные файлы» прямо на нужную папку в левом списке или в верхнюю правую таблицу. Настроим свойства элементов формы:

  1. Для таблицы-источника (СвободныеФайлы) установите свойство РазрешитьНачалоПеретаскивания в значение Истина.
  2. Для таблицы-приемника (СписокПапок) установите свойство РазрешитьПеретаскивание в значение Истина.

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


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

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

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

---

Альтернативные решения

В зависимости от требований бизнеса и используемой конфигурации, можно рассмотреть еще два интересных подхода к проектированию:

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

← На главную