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

Программист 1С v8.3 (Обычные формы) 1C:Бухгалтерия Бухгалтерский учет IT и автоматизация бизнеса
← На главную

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

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

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

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

  1. Непонимание жизненного цикла объекта в памяти. Метод Скопировать() создает в оперативной памяти экземпляр типа ДокументОбъект. Этот объект еще не записан в базу данных. Вызов несуществующего метода ДокументКопия.Удалить() приводит к аварийному завершению программы. Чтобы отказаться от создания копии, достаточно просто не вызывать метод Записать(). Несохраненный объект будет автоматически удален сборщиком мусора платформы при выходе переменной из области видимости.
  2. Попытка работы с незаписанной ссылкой. Если объект не был сохранен через Записать(), обращение к его свойству ДокументКопия.Ссылка возвращает пустую ссылку. При последующей передаче этой ссылки в типовые процедуры формирования печатных форм или сохранения файлов (например, в PDF или Excel) внутренний запрос печатной формы не находит данных и возвращает NULL. В результате возникает ошибка вида:
    
    {ВнешняяОбработка.Счета.Форма.Форма(761)}: Преобразование значения к типу Булево не может быть выполнено
    СуммаКПрописи = Сумма + ?(Шапка.СуммаВключаетНДС, 0, СуммаНДС);
    
    Оператор ?() ожидает значение типа Булево, но получает NULL из-за пустой ссылки.
  3. Вложенные циклы и неявные SQL-запросы. Обращение к реквизитам через точку (ЭлементМассива.Контрагент, Счет.СуммаДокумента) в двойном цикле генерирует отдельный SQL-запрос к СУБД на каждое обращение, что многократно замедляет работу системы.

Способ 1. Проверка дубликатов запросом с временной таблицей (рекомендуемый)

Наиболее производительный и масштабируемый подход — переложить всю проверку на сторону СУБД с помощью единого пакетного запроса. Мы помещаем параметры отбираемых документов во временную таблицу и соединяем их с существующими документами в базе данных.

Разберем по шагам реализацию этой схемы:

  1. Формируем таблицу исходных данных для создания (контрагенты, суммы, договоры, период/комментарий).
  2. Передаем таблицу в запрос через МенеджерВременныхТаблиц.
  3. Выполняем ЛЕВОЕ СОЕДИНЕНИЕ с таблицей существующих счетов по ключевым полям и отбираем только те записи, для которых в базе еще нет созданных документов (где ссылка существующего документа ЕСТЬ NULL).
  4. Обязательно исключаем из проверки помеченные на удаление документы с помощью условия НЕ ДокументСчет.ПометкаУдаления.

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


// Формируем таблицу параметров для проверки
ТаблицаДляПроверки = Новый ТаблицаЗначений;
ТаблицаДляПроверки.Колонки.Добавить("ДокументОснование", Новый ОписаниеТипов("ДокументСсылка.СчетНаОплатуПокупателю"));
ТаблицаДляПроверки.Колонки.Добавить("Контрагент", Новый ОписаниеТипов("СправочникСсылка.Контрагенты"));
ТаблицаДляПроверки.Колонки.Добавить("Комментарий", Новый ОписаниеТипов("Строка", Новый КвалификаторыСтроки(100)));

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

// Выполняем запрос для отсечения дублей
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |    ТЗ.ДокументОснование КАК ДокументОснование,
    |    ТЗ.Контрагент КАК Контрагент,
    |    ТЗ.Комментарий КАК Комментарий
    |ПОМЕСТИТЬ ВТ_ИсходныеДанные
    |ИЗ
    |    &ТаблицаДляПроверки КАК ТЗ
    |;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |    ВТ.ДокументОснование КАК ДокументОснование
    |ИЗ
    |    ВТ_ИсходныеДанные КАК ВТ
    |    ЛЕВОЕ СОЕДИНЕНИЕ Документ.СчетНаОплатуПокупателю КАК СуществующиеСчета
    |        ПО ВТ.Контрагент = СуществующиеСчета.Контрагент
    |        И ВТ.Комментарий = СуществующиеСчета.Комментарий
    |        И (НЕ СуществующиеСчета.ПометкаУдаления)
    |ГДЕ
    |    СуществующиеСчета.Ссылка ЕСТЬ NULL";

Запрос.УстановитьПараметр("ТаблицаДляПроверки", ТаблицаДляПроверки);
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();

МассивСозданныхСсылок = Новый Массив;

// Создаем только те документы, которые прошли фильтр
Пока Выборка.Следующий() Цикл
    НовыйДокумент = Выборка.ДокументОснование.Скопировать();
    НовыйДокумент.Дата = НоваяДатаДок;
    НовыйДокумент.Комментарий = НовыйКомментарий;
    
    Попытка
        НовыйДокумент.Записать(РежимЗаписиДокумента.Запись);
        МассивСозданныхСсылок.Добавить(НовыйДокумент.Ссылка);
    Исключение
        Сообщить("Ошибка при записи документа: " + ОписаниеОшибки());
    КонецПопытки;
КонецЦикла;

Способ 2. Контроль уникальности в оперативной памяти через Таблицу Значений с индексами

Если требуется контролировать уникальность создаваемых данных «на лету» без постоянных обращений к базе данных (например, чтобы избежать создания повторяющихся строк внутри самого обрабатываемого пакета), целесообразно использовать коллекцию ТаблицаЗначений с предварительно построенными индексами по ключевым полям поиска.

Рассмотрим пример реализации такого алгоритма:


// Определяем поля, по которым контролируется уникальность
ПоляПоиска = "Контрагент, ДоговорКонтрагента, Комментарий";

// Инициализируем фильтр и индексированную таблицу уникальности
ФильтрПоиска = Новый Структура(ПоляПоиска);
ТаблицаУникальности = Новый ТаблицаЗначений;
ТаблицаУникальности.Колонки.Добавить("Контрагент");
ТаблицаУникальности.Колонки.Добавить("ДоговорКонтрагента");
ТаблицаУникальности.Колонки.Добавить("Комментарий");
ТаблицаУникальности.Индексы.Добавить(ПоляПоиска);

МассивСозданныхСсылок = Новый Массив;

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

Способ 3. Архитектурное решение через реквизит-основание или регистр сведений

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

  1. Реквизит «ДокументОснование» или «ДокументИсточник». В создаваемом документе заполняется реквизит, указывающий на исходный счет. Проверка сводится к простому запросу: ГДЕ Счет.ДокументОснование = &ИсходныйСчет И НЕ Счет.ПометкаУдаления. Также для анализа цепочек можно использовать типовой критерий отбора СвязанныеДокументы.
  2. Служебный регистр сведений. Если конфигурация находится на поддержке без возможности изменения структуры документов, создается непериодический независимый регистр сведений (например, ИсторияФормированияПериодическихСчетов). В измерения помещаются ДокументИсточник и ПериодНачисления, а в ресурс — СозданныйДокумент. Перед созданием документа выполняется быстрая проверка наличия записи в регистре методом СоздатьМенеджерЗаписи() или запросом к срезу.

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

← На главную