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