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

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

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

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

Выясняем причину: особенности интерактивных событий табличной части

Для начала проанализируем поведение платформы 1С при работе с табличными частями на управляемой форме. Когда разработчик пытается встроить логику открытия подбора в событие ПриНачалеРедактирования табличной части, он сталкивается со следующей цепочкой событий:

  1. Пользователь интерактивно нажимает кнопку «Добавить» (или клавишу Insert) в табличной части.
  2. Срабатывает обработчик ПриНачалеРедактирования. Строка таблицы переходит в режим интерактивного редактирования.
  3. Внутри этого обработчика программно открывается форма подбора, где пользователь выбирает несколько значений.
  4. После закрытия формы подбора возвращается массив выбранных элементов.
  5. Для первого элемента данные записываются в Элемент.ТекущиеДанные (то есть в ту саму строку, которую пользователь начал редактировать интерактивно).
  6. Для всех остальных выбранных элементов новые строки создаются программно с помощью метода Добавить() коллекции данных формы.

Главная ошибка кроется в дальнейшей синхронизации данных. Очень часто логику заполнения связанной общей таблицы (например, Объект.ОбщиеДанные) разработчики помещают в событие ПередОкончаниемРедактирования или ПриОкончанииРедактирования. Эти события являются интерактивными. Они срабатывают исключительно для той строки, которую пользователь редактировал вручную в интерфейсе.

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

Решение 1. Быстрое исправление в рамках существующего кода

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

Давайте посмотрим на пример реализации такого обхода. В процедуре ПриНачалеРедактирования мы будем разделять логику для первой (интерактивной) строки и всех последующих (программных) строк:


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

Этот подход решает проблему "потери" строк, однако использование события ПриНачалеРедактирования для инициации подбора, равно как и использование модальных окон (ОткрытьФормуМодально), считается устаревшим и не рекомендуется стандартами разработки 1С.

Решение 2. Правильный архитектурный паттерн (Асинхронный подбор)

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

Разберем по шагам реализацию этого паттерна:

Шаг 1. Создание отдельной команды подбора

Добавим команду формы (например, КомандаПодбор) и разместим кнопку для нее на командной панели табличной части Машины. Снимем у этой кнопки свойство автозаполнения, чтобы управлять процессом добавления строк полностью программно.

Шаг 2. Асинхронный вызов формы подбора

В обработчике команды вызовем форму подбора асинхронно, используя объект ОписаниеОповещения:


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

Шаг 3. Реализация возврата данных из формы подбора

В форме подбора по нажатию кнопки «Добавить в документ» мы должны сформировать простой массив выбранных ссылок (машин) и передать его в метод Закрыть():


&НаКлиенте
Процедура ДобавитьВДокумент(Команда)
    
    МассивВыбранныхМашин = ПолучитьМассивВыбранныхМашинНаСервере();
    Закрыть(МассивВыбранныхМашин);
    
КонецПроцедуры

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

Шаг 4. Обработка результатов подбора и атомарная запись в связанные таблицы

Теперь рассмотрим процедуру-обработчик оповещения в основной форме документа. Здесь мы получаем готовый массив и в едином цикле программно заполняем как таблицу Машины, так и общую связанную таблицу ОбщиеДанные. Нам больше не нужно беспокоиться об интерактивных событиях окончания редактирования строки.


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

Преимущества асинхронного паттерна подбора

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

← На главную