В практике разработки на платформе 1С:Предприятие часто возникает задача выделить один "главный" или "особый" элемент среди списка однородных сущностей. Классический пример, который мы сегодня разберем: в базе есть справочник Контрагенты, и у каждого контрагента есть список контактных лиц (подчиненный справочник КонтактныеЛица). Требуется сделать так, чтобы среди всех контактных лиц одного контрагента только одно могло быть помечено как "Контактное лицо бухгалтерии".
На первый взгляд задача кажется простой: добавить булево реквизит (галочку) и следить, чтобы она стояла только у одного элемента. Однако, как мы увидим далее, "лобовое" решение этой задачи часто приводит к ошибкам блокировок и проблемам с производительностью. Давайте подробно проанализируем варианты решения, от наивных к архитектурно правильным.
Первая мысль, которая приходит в голову разработчику (и автору темы на форуме) — это создать в справочнике КонтактныеЛица реквизит ЭтоКонтактноеЛицоБухгалтерии (Тип: Булево). Логика реализации выглядит так: "Перед записью элемента, если пользователь поставил галочку, мы должны найти все остальные элементы этого владельца, у которых эта галочка тоже стоит, и снять её".
Попробуем представить, как бы выглядел код такого решения в событии ПередЗаписью:
Процедура ПередЗаписью(Отказ)
Если ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
// Если устанавливаем флаг в Истину
Если ЭтоКонтактноеЛицоБухгалтерии Тогда
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| КонтактныеЛица.Ссылка КАК Ссылка
|ИЗ
| Справочник.КонтактныеЛица КАК КонтактныеЛица
|ГДЕ
| КонтактныеЛица.Владелец = &Владелец
| И КонтактныеЛица.ЭтоКонтактноеЛицоБухгалтерии = ИСТИНА
| И КонтактныеЛица.Ссылка <> &ТекущаяСсылка";
Запрос.УстановитьПараметр("Владелец", Владелец);
Запрос.УстановитьПараметр("ТекущаяСсылка", Ссылка);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
// Внимание! Опасный момент
ОбъектСброс = Выборка.Ссылка.ПолучитьОбъект();
ОбъектСброс.ЭтоКонтактноеЛицоБухгалтерии = Ложь;
ОбъектСброс.Записать();
КонецЦикла;
КонецЕсли;
КонецПроцедуры
Почему этот подход считается плохой практикой?
Записать() внутри события ПередЗаписью (пусть и для другого объекта) вызывает цепочку подписок на события и проверок, что существенно замедляет работу системы.Ложь и единицы со значением Истина. Индексировать такое поле неэффективно (низкая селективность), что приведет к замедлению поисков при больших объемах данных.Давайте посмотрим на задачу под другим углом. Вместо того чтобы помечать элементы в списке, мы можем указать "Избранный" элемент в самом владельце. Это решение является стандартом для типовых конфигураций 1С (например, "Основной договор", "Основной банковский счет").
Суть решения:
КонтактныеЛица мы не создаем никаких булевых реквизитов.Контрагенты (Владелец) мы создаем реквизит КонтактноеЛицоБухгалтерии.СправочникСсылка.КонтактныеЛица.Преимущества данного метода:
// Получение данных мгновенно
Бухгалтер = Объект.Контрагент.КонтактноеЛицоБухгалтерии;
Контрагенты. Это никак не затрагивает записи в таблице КонтактныеЛица.Иногда задача бывает сложнее. Например, нам нужно хранить не просто "Контакт для бухгалтерии", а целый набор ролей: "Для счетов", "Для договоров", "Для доставки", причем один человек может совмещать роли, или роли могут меняться со временем.
В этом случае добавление множества реквизитов в справочник Контрагенты (Контакт1, Контакт2, Контакт3...) загромождает объект. Здесь на помощь приходят регистры сведений.
Реализация через периодический Регистр Сведений:
ВидыКонтактныхЛиц (значения: Бухгалтерия, Директор, Менеджер).ОтветственныеЛицаКонтрагентов.Контрагент (СправочникСсылка.Контрагенты) — Ведущее измерение.ВидРоли (Перечисление.ВидыКонтактныхЛиц).КонтактноеЛицо (СправочникСсылка.КонтактныеЛица).Такой подход позволяет хранить историю (если сделать регистр периодическим) и неограниченное количество ролей для одного контрагента. Уникальность обеспечивается структурой регистра: уникальным ключом записи является набор измерений (Контрагент + ВидРоли). То есть для одной роли у одного контрагента может быть только одна запись в конкретный момент времени.
Если специфика учета такова, что само контактное лицо может выступать в разных ипостасях, можно использовать подход, предложенный в обсуждении: создать справочник РолиКонтактныхЛиц, подчиненный справочнику КонтактныеЛица (или регистр сведений, где измерением будет КонтактноеЛицо). Однако это скорее решение задачи "Кем является этот человек", а не задачи "Кто главный бухгалтер у контрагента". Для быстрого поиска ответственного этот вариант менее удобен, так как требует запроса к подчиненным таблицам.
Если мы выбрали Вариант 2 (реквизит у владельца), возникает вопрос: как пользователю удобно назначать основное лицо? Заходить в карточку контрагента каждый раз неудобно.
Решение реализуется на форме списка или форме элемента Контрагента, где отображается список контактных лиц:
Таким образом, пользователь видит привычный интерфейс с "галочками" (через условное оформление), но "под капотом" работает надежная архитектура без лишних транзакций и запросов.
Подводя итог, можно уверенно сказать: хранение признака уникальности (булево) в подчиненном элементе — это антипаттерн в разработке на 1С. Наиболее правильным и производительным решением является перенос ссылки на "избранный" элемент в родительский объект (Владелец) или использование профильных регистров сведений для более сложных связей.