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

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

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

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

Метод 1. Исправление ошибки метода Свернуть() при постобработке данных

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


ТабЧастьВся = Объект.СписокДоговоров.Выгрузить();
ТабЧасть = ТабЧастьВся.Свернуть("Организация, ОсновнойДоговор, СвязующийДоговор, Подразделение, ВидОбъектаАренды, ОбъектПраваАренды");

Выясним причину этой ошибки. В платформе «1С:Предприятие» метод Свернуть() объекта ТаблицаЗначений является процедурой, а не функцией. Данный метод не возвращает новую таблицу значений, а преобразует исходную таблицу "на месте" (in-place). Попытка присвоить результат работы этой процедуры в переменную вызывает ошибку времени выполнения.

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


// 1. Выгружаем данные табличной части в таблицу значений на сервере
ТабЧастьВся = Объект.СписокДоговоров.Выгрузить();

// 2. Сворачиваем строки по ключевым реквизитам. Метод вызывается как процедура.
ТабЧастьВся.Sвернуть("Организация, ОсновнойДоговор, СвязующийДоговор, Подразделение, ВидОбъектаАренды, ОбъектПраваАренды");

// 3. Загружаем очищенные уникальные строки обратно в табличную часть документа
Объект.СписокДоговоров.Загрузить(ТабЧастьВся);

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

Метод 2. Интерактивная проверка "на лету" в событии ПередОкончаниемРедактирования

Пользователи не любят, когда программа молча удаляет их строки (как в случае с методом Свернуть()) или сообщает об ошибке только в самый последний момент — при сохранении всего документа. Намного удобнее предупреждать дублирование в момент непосредственного ввода данных.

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

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


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

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

Метод 3. Надежная серверная проверка в событии ОбработкаПроверкиЗаполнения

Хотя интерактивная проверка на клиенте удобна для конечного пользователя, полагаться исключительно на нее нельзя. Данные в табличную часть документа могут быть загружены программно (например, через внешнюю обработку, API, распределенную базу данных или механизм копирования документов), минуя интерактивные события формы.

Для обеспечения гарантированной чистоты данных в базе 1С необходимо использовать серверный обработчик ОбработкаПроверкиЗаполнения в модуле объекта документа. Этот механизм отрабатывает автоматически при записи и проведении документа.

Проанализируем оптимальный алгоритм проверки уникальности строк на сервере с использованием вспомогательной свертки-счетчика:


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

Этот способ является индустриальным стандартом разработки в 1С. Он предотвращает запись некорректных данных в базу при любых сценариях работы программы.

Метод 4. Оптимизация производительности для больших таблиц с помощью Соответствия

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

Рассмотрим пример быстрой серверной проверки с использованием объекта Соответствие:


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

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

Метод 5. Использование механизма условного оформления для визуального выделения дублей

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

Для реализации этого механизма выполним следующие шаги:

  1. Добавим в табличную часть документа служебный реквизит ЭтоДубль с типом данных Булево. Данный реквизит не обязательно сохранять в базе данных, его можно оставить "виртуальным".
  2. Настроим условное оформление на форме документа. Если значение реквизита ЭтоДубль равно Истина, цвет фона строки должен становиться светло-красным.
  3. Будем запускать быструю проверку при изменении данных в табличной части, чтобы динамически обновлять признак дублирования.

Рассмотрим пример кода для программной настройки условного оформления в обработчике формы ПриСозданииНаСервере:


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

Теперь достаточно написать простую процедуру на сервере, которая при любом изменении таблицы будет обходить строки, выявлять дубликаты с помощью рассмотренного выше метода с Соответствием, и проставлять флаг ЭтоДубль = Истина в повторяющихся строках. Пользователь мгновенно увидит проблемные области интерфейса визуально без прерывания ввода данных.

← На главную