Нередко возникает задача — при создании нового документа проверить, не был ли точно такой же документ уже введен в базу ранее. Сложность в том, что уникальность документа может определяться не только полями в "шапке" (например, контрагент, дата, номер), но и полным совпадением состава его табличной части. При этом порядок строк в табличной части часто не имеет значения.
Проанализируем ситуацию на конкретном примере. Нам нужно реализовать проверку на дубли для документов, у которых должны совпадать:
Рассмотрим несколько подходов к решению этой задачи, от самого оптимального до более простых в реализации.
Этот метод является самым производительным и правильным, если проверка на дубли выполняется регулярно. Идея заключается в том, чтобы для каждого документа создать уникальный "отпечаток" — хеш-сумму, рассчитанную на основе всех его ключевых данных. Поиск по такому отпечатку будет практически мгновенным.
Разберем процесс по шагам.
Формирование уникальной строки для хеширования
Чтобы хеш был действительно уникальным, нужно собрать все ключевые данные в одну строку по строгим правилам.
Данные из шапки: Собираем значения реквизитов. Важный момент: для реквизитов ссылочного типа (Организация, Партнер) следует использовать их уникальный идентификатор (УИД), а не наименование или код. Это защитит от проблем, если пользователя переименуют элемент справочника.
Данные из табличной части: Это самый важный этап. Поскольку порядок строк не имеет значения, мы должны привести их к единому виду.
"Содержание документа|1500.50". Числа нужно форматировать, чтобы избежать проблем с представлением (например, 1500.5 и 1500.50).Итоговая строка: Объединяем строку, полученную из шапки, и строку, полученную из табличной части.
Вычисление хеш-суммы
Используем встроенные в платформу 1С механизмы для расчета хеша по алгоритму, например, MD5.
Посмотрим на пример кода:
// Функция для получения хеш-суммы документа
&НаСервере
Функция ПолучитьХешДокумента(СсылкаНаДокумент)
ОбъектДокумента = СсылкаНаДокумент.ПолучитьОбъект();
// 1. Формируем строку из шапки
СтрокаШапки = ""
+ Формат(ОбъектДокумента.Организация.УникальныйИдентификатор(), "S") + ";"
+ Формат(ОбъектДокумента.Партнер.УникальныйИдентификатор(), "S") + ";"
+ Формат(ОбъектДокумента.ДатаВходящегоДокумента, "ДФ=yyyyMMdd") + ";"
+ ОбъектДокумента.НомерВходящегоДокумента;
// 2. Формируем строку из ТЧ
МассивСтрокТЧ = Новый Массив;
Для Каждого Стр Из ОбъектДокумента.Расходы Цикл
СтрокаТЧ = Стр.Содержание + "|" + Формат(Стр.Сумма, "ЧРД=.; ЧГ=; ЧН=0.00");
МассивСтрокТЧ.Добавить(СтрокаТЧ);
КонецЦикла;
// 3. Сортируем строки ТЧ
МассивСтрокТЧ.Сортировать();
// 4. Объединяем все в одну строку
ИтоговаяСтрока = СтрокаШапки + "|" + СтрСоединить(МассивСтрокТЧ, ";");
// 5. Вычисляем хеш
Хеширование = Новый ХешированиеДанных(ХешФункция.MD5);
Хеширование.Добавить(ИтоговаяСтрока);
Возврат Хеширование.ХешСумма;
КонецФункции
Хранение и поиск хеша
Где хранить полученный хеш? Есть несколько вариантов:
ХешиДокументов. У него должно быть одно измерение ХешСумма (строка, индексируемое) и один ресурс Документ (ссылка на ваш документ). Это самое производительное решение.Поиск дубля по регистру сведений будет выглядеть очень просто:
ПроверяемыйХеш = ПолучитьХешДокумента(СсылкаНаНовыйДокумент);
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ХешиДокументов.Документ
|ИЗ
| РегистрСведений.ХешиДокументов КАК ХешиДокументов
|ГДЕ
| ХешиДокументов.ХешСумма = &ХешСумма";
Запрос.УстановитьПараметр("ХешСумма", ПроверяемыйХеш);
Результат = Запрос.Выполнить();
Если Не Результат.Пустой() Тогда
Сообщить("Найден дубликат: " + Результат.Выгрузить()[0].Документ);
КонецЕсли;
Автоматизация процесса
Чтобы хеш всегда был актуальным, его расчет и запись в регистр нужно автоматизировать. Лучший способ — создать подписку на событие ПриЗаписи для вашего типа документа. В обработчике подписки нужно вызывать функцию расчета хеша и записывать результат в регистр сведений.
Если задача разовая или производительность не является критичной, можно обойтись без создания дополнительных объектов в конфигурации. Этот метод состоит из двух этапов: сначала максимально сужаем круг "подозреваемых" документов запросом, а затем в цикле сравниваем их табличные части.
Отбор кандидатов запросом
Формируем запрос, который отбирает из базы документы, совпадающие по реквизитам шапки. Чтобы еще сильнее сократить выборку, можно добавить в запрос условия на агрегированные данные из табличной части: количество строк и итоговую сумму.
// Получаем данные нового документа
КоличествоСтрокНового = ЭтотОбъект.Расходы.Количество();
СуммаНового = ЭтотОбъект.Расходы.Итог("Сумма");
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ПоступлениеУслуг.Ссылка,
| КОЛИЧЕСТВО(ПоступлениеУслугРасходы.Ссылка) КАК КоличествоСтрок,
| СУММА(ПоступлениеУслугРасходы.Сумма) КАК СуммаДокумента
|ИЗ
| Документ.ПоступлениеУслуг КАК ПоступлениеУслуг
| ЛЕВОЕ СОЕДИНЕНИЕ Документ.ПоступлениеУслуг.Расходы КАК ПоступлениеУслугРасходы
| ПО ПоступлениеУслуг.Ссылка = ПоступлениеУслугРасходы.Ссылка
|ГДЕ
| ПоступлениеУслуг.Организация = &Организация
| И ПоступлениеУслуг.Партнер = &Партнер
| И ПоступлениеУслуг.ДатаВходящегоДокумента = &ДатаВходящегоДокумента
| И ПоступлениеУслуг.НомерВходящегоДокумента = &НомерВходящегоДокумента
| И ПоступлениеУслуг.Ссылка <> &ТекущийДокумент
|
|СГРУППИРОВАТЬ ПО
| ПоступлениеУслуг.Ссылка
|
|ИМЕЮЩИЕ
| КОЛИЧЕСТВО(ПоступлениеУслугРасходы.Ссылка) = &КоличествоСтрок
| И СУММА(ПоступлениеУслугРасходы.Сумма) = &СуммаДокумента";
// Установка параметров ...
// ...
ВыборкаКандидатов = Запрос.Выполнить().Выбрать();
Сравнение ТЧ в цикле
Теперь обходим в цикле полученные ссылки на документы-кандидаты и детально сравниваем их табличные части с табличной частью нового документа. Для корректного сравнения (без учета порядка строк) нужно выгрузить обе ТЧ в таблицы значений, отсортировать их по колонкам Содержание и Сумма, а затем сравнивать.
Как выяснилось в ходе обсуждения, часто подобная задача возникает из-за особенностей бизнес-процесса. Например, пользователи вручную создают документ "Поступление услуг", чтобы быстро сформировать заявку на оплату поставщику, а позже эти же документы массово загружаются из внешней системы. Это порождает дубли.
В таких случаях, помимо технического решения, стоит задуматься об изменении самого процесса:
Решение организационной проблемы часто оказывается более надежным и правильным, чем создание сложных технических механизмов для "ловли" последствий.