Как запретить повторный выбор строк с одинаковым идентификатором в табличной части 1С?

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

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

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

Анализ типичных ошибок при проверке строк

Проанализируем распространенную ошибку, возникающую при попытке выполнить проверку полным перебором всей таблицы в цикле:

  1. Избыточный цикл: организация цикла Для каждого по всей коллекции Объект.Накладные с вызовом метода НайтиСтроки() на каждой итерации приводит к квадратичной сложности алгоритма и зависанию интерфейса при сотнях записей.
  2. Ложное срабатывание на текущей строке: если проверять условие стр.ВыбратьСтроку = Истина без учета того, сколько именно строк с данным идентификатором уже выбрано, система будет блокировать даже самый первый выбор элемента. Так как пользователь только что установил флаг, текущая строка уже находится в состоянии Истина.
  3. Ошибки инициализации структуры: создание структуры поиска внутри тела цикла с перезаписью переменных часто приводит к исключениям вида «Значение не является значением объектного типа (Вставить)».

Решение через поиск по составному условию в событии ПриИзменении

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

Разберем рабочий алгоритм:

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

Посмотрим на пример клиентской процедуры:


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

Альтернативный вариант: перенос выбора (поведение переключателя)

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


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

Оптимизация производительности и важные нюансы

Рассмотрим дополнительные технические аспекты, которые необходимо учитывать при реализации подобной логики:

  1. Отсутствие индексов на клиенте: коллекция ДанныеФормыКоллекция (в которой хранятся строки таблицы на клиенте) не поддерживает индексирование, в отличие от серверной таблицы значений. Метод НайтиСтроки() выполняет линейный перебор элементов. Для 400–500 строк это выполняется мгновенно, но при объемах в десятки тысяч строк для мгновенного отклика можно использовать объект Соответствие в реквизитах формы для кэширования выбранных идентификаторов со сложностью доступа $O(1)$.
  2. Очистка строк: строковые идентификаторы рекомендуется предварительно обрабатывать функцией СокрЛП(), чтобы случайные концевые или начальные пробелы не приводили к ложным несовпадениям.
  3. Серверная валидация: событие ПриИзменении вызывается только при интерактивном клике пользователя. При программном заполнении таблицы или пакетной обработке строк клиентские события формы не отрабатывают, поэтому финальную проверку уникальности всегда следует дублировать на сервере (например, в обработчике ПередЗаписью или перед запуском результирующей обработки данных).
← На главную