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