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

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

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

Рассмотрим подробнее, как мы можем организовать проверку на дубликаты и избежать их появления при загрузке данных из Excel.

Определение уникальности документа

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

  1. Контрагент: Магазин, для которого делается заказ.
  2. Дата: Дата доставки или дата создания заказа (в нашем случае, "ТекущаяДата() + 86400" для даты документа и "ДатаПоставки" для запроса).
  3. Водитель: Водитель, ответственный за доставку.
  4. Признак "Авто" = Истина: Реквизит, указывающий на то, что документ был создан автоматически. Если этот признак становится Ложь, это означает, что документ был отредактирован вручную, и его новую версию при повторной загрузке нужно создавать отдельно, а не считать дубликатом.

Мы также уточнили, что состав номенклатуры (табличная часть документа) не участвует в определении уникальности самого документа в целом. То есть, даже если номенклатурный состав заказа изменится, но ключевые реквизиты шапки (Контрагент, Дата, Водитель, Авто=Истина) останутся теми же, мы будем считать документ дубликатом и не создавать его заново.

Метод 1: Проверка уникальности с помощью запроса в цикле

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

Разберем этот процесс по шагам:

  1. Мы итерируем по каждой строке или блоку данных из файла Excel, который соответствует одному документу.
  2. Для каждой такой строки мы извлекаем значения ключевых полей, по которым будем проверять уникальность: Контрагент, ДатаДоставки (или просто Дата документа), Водитель.
  3. Затем мы формируем объект Запрос в 1С и пишем текст запроса к нашей информационной базе. Этот запрос должен искать документ (например, Документ.ЗаказБулки) по комбинации наших ключевых реквизитов, а также учитывать признаки Авто = Истина и ПометкаУдаления = ЛОЖЬ.
  4. Мы устанавливаем параметры запроса, передавая в них извлеченные значения из Excel.
  5. Выполняем запрос. Если результат запроса пуст (то есть метод Выборка.Следующий() возвращает Ложь), это означает, что документа с такими параметрами еще нет в базе, и мы можем безопасно создать новый документ.
  6. Если же запрос находит документ (Выборка.Следующий() возвращает Истина), мы понимаем, что такой документ уже существует, и по условию задачи не создаем новый, а просто пропускаем или информируем пользователя.

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


// Предполагаем, что у вас есть данные из Excel в каком-то источнике, например, в таблице значений или напрямую из файла
// В этом примере мы будем использовать гипотетический объект 'СтрокаДанныхЭксель', 
// который представляет одну запись из Excel, соответствующую документу.
// А также предполагаем, что существует документ 'ЗаказБулки' с реквизитами Контрагент, Дата, Водитель, Авто.

Для Каждого СтрокаДанныхЭксель Из ДанныеИзЭксель Цикл
    
    // 1. Извлекаем ключевые параметры из текущей строки Excel
    // Необходимо убедиться, что Контрагент и Водитель найдены в справочниках 1С
    // перед использованием в запросе.
    ПеременнаяКонтрагент = Справочники.Контрагенты.НайтиПоРеквизиту("Код", СтрокаДанныхЭксель.КодКонтрагентаИзЭксель); 
    ПеременнаяДатаДоставки = СтрокаДанныхЭксель.ДатаДоставкиИзЭксель; // Предполагаем, что дата доставки есть в Excel
    ПеременнаяВодитель = Справочники.Водители.НайтиПоРеквизиту("Код", СтрокаДанныхЭксель.КодВодителяИзЭксель); 

    // Если какой-либо из ключевых справочных реквизитов не найден, пропускаем текущую строку
    Если ПеременнаяКонтрагент = Неопределено ИЛИ ПеременнаяВодитель = Неопределено Тогда
        Сообщить("Не найден контрагент или водитель по данным Excel. Документ не будет создан.");
        Продолжить;
    КонецЕсли;

    // 2. Создаем объект запроса
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ ПЕРВЫЕ 1
        |    ДокументЗаказ.Ссылка
        |ИЗ
        |    Документ.ЗаказБулки КАК ДокументЗаказ
        |ГДЕ
        |    ДокументЗаказ.Контрагент = &Контрагент
        |    И ДокументЗаказ.Дата = &ДатаДоставки // Предполагаем, что реквизит документа называется Дата
        |    И ДокументЗаказ.Водитель = &Водитель
        |    И ДокументЗаказ.Авто = ИСТИНА
        |    И ДокументЗаказ.ПометкаУдаления = ЛОЖЬ";
    
    // 3. Устанавливаем параметры запроса
    Запрос.УстановитьПараметр("Контрагент", ПеременнаяКонтрагент);
    Запрос.УстановитьПараметр("ДатаДоставки", ПеременнаяДатаДоставки);
    Запрос.УстановитьПараметр("Водитель", ПеременнаяВодитель);
    
    // 4. Выполняем запрос и проверяем результат
    РезультатЗапроса = Запрос.Выполнить();
    Выборка = РезультатЗапроса.Выбрать();
    
    Если НЕ Выборка.Следующий() Тогда
        // 5. Документ с такими параметрами не найден, создаем новый
        НовыйДокумент = Документы.ЗаказБулки.СоздатьДокумент();
        НовыйДокумент.Дата = ПеременнаяДатаДоставки; // Дата документа устанавливается из даты доставки
        НовыйДокумент.Контрагент = ПеременнаяКонтрагент;
        НовыйДокумент.Водитель = ПеременнаяВодитель;
        НовыйДокумент.Авто = Истина; // Устанавливаем признак "Авто", как определено в условиях уникальности
        // ... здесь мы могли бы заполнить другие реквизиты шапки документа из Excel ...
        
        // 6. Заполнение табличной части документа.
        // Предполагаем, что данные для табличной части также находятся в 'СтрокаДанныхЭксель' 
        // или в связанном с ней объекте.
        // Пример из сообщения 52, адаптированный:
        Для Каждого СтрокаДанныхТЧИзЭксель Из СтрокаДанныхЭксель.ТабличнаяЧастьДанные Цикл 
            // 'Проба' - это условное обозначение для строк, которые нужно пропустить
            Если СтрокаДанныхТЧИзЭксель.Проба = 0 ИЛИ СтрокаДанныхТЧИзЭксель.Проба = 1 
                ИЛИ СтрокаДанныхТЧИзЭксель.Проба = 2 ИЛИ СтрокаДанныхТЧИзЭксель.Проба = 3 Тогда
                Продолжить;
            Иначе
                СтрТЧ = НовыйДокумент.ТЧР.Добавить(); // Предполагаем, что табличная часть называется ТЧР
                // Находим номенклатуру по коду из Excel
                СтрТЧ.ТМЦ = Справочники.Булки.НайтиПоРеквизиту("Код", СтрокаДанныхТЧИзЭксель.КодТМЦИзЭксель);
                СтрТЧ.Количество = СтрокаДанныхТЧИзЭксель.КоличествоБулки; 
            КонецЕсли;
        КонецЦикла;
        
        // 7. Записываем и проводим документ
        Попытка
            НовыйДокумент.Записать(РежимЗаписиДокумента.Проведение);
            Сообщить("Создан и проведен документ: " + НовыйДокумент.Ссылка);
        Исключение
            Сообщить("Ошибка при записи документа: " + ОписаниеОшибки());
        КонецПопытки;
    Иначе
        // 8. Документ найден, сообщаем или пропускаем
        Сообщить("Документ с контрагентом " + ПеременнаяКонтрагент.Наименование + 
                 ", датой " + ПеременнаяДатаДоставки + " и водителем " + ПеременнаяВодитель.Наименование + 
                 " уже существует: " + Выборка.Ссылка);
        // Если требуется обновить существующий документ (например, добавить новые строки в ТЧ),
        // логика будет значительно сложнее, и запрос нужно будет изменить. 
        // Но по условию задачи "Если есть такой документ, тогда не создавать".
    КонецЕсли;
КонецЦикла;

Этот подход хорошо работает для небольших объемов данных. Однако, если количество записей в Excel исчисляется тысячами или десятками тысяч, выполнение отдельного запроса к базе данных для каждой строки Excel может сильно замедлить процесс загрузки. Это приведет к ситуации "цикл в цикле", что неэффективно с точки зрения производительности.

Метод 2: Оптимизация с использованием временных таблиц

Когда мы сталкиваемся с большими объемами данных из Excel, метод с запросом в цикле становится неэффективным. В таких случаях мы применяем более продвинутый подход, использующий временные таблицы (ВТ) в 1С. Этот метод позволяет выполнить проверку уникальности за один или несколько пакетных запросов, значительно ускоряя процесс.

Разберем процесс по шагам:

  1. Мы сначала загружаем все данные из файла Excel, которые соответствуют будущим документам и их ключевым полям, во временную таблицу в 1С. Для этого мы создаем объект МенеджерВременныхТаблиц.
  2. Затем мы формируем один пакетный запрос. Этот запрос состоит из двух частей:
    • Первая часть (ПОМЕСТИТЬ) создает временную таблицу на основе нашей таблицы значений с данными из Excel.
    • Вторая часть выполняет ЛЕВОЕ СОЕДИНЕНИЕ нашей временной таблицы с таблицей документов в информационной базе (например, Документ.ЗаказБулки) по всем ключевым полям уникальности (Контрагент, Дата, Водитель, Авто = Истина, ПометкаУдаления = ЛОЖЬ).
    • Ключевым моментом здесь является условие ГДЕ СуществующиеДокументы.Ссылка ЕСТЬ NULL. Это условие отбирает только те строки из временной таблицы (то есть те потенциальные документы из Excel), для которых не найдено соответствующих существующих документов в базе данных. Таким образом, мы получаем список только тех документов, которые действительно нужно создать.
  3. После выполнения пакетного запроса мы получаем результат, который содержит только данные для новых, еще не существующих в базе документов.
  4. Далее мы итерируем по этому результату запроса и создаем новые документы, заполняя их реквизиты данными из выборки.

Посмотрим на концептуальный пример кода для этого метода. Полная реализация требует более детального описания структуры данных Excel и способа их преобразования в ТаблицуЗначений 1С:


// 1. Предполагаем, что у вас есть ТаблицаЗначений с данными из Excel: ТЗ_ДанныеИзЭксель.
// Колонки ТЗ_ДанныеИзЭксель должны содержать: 
//   - КодКонтрагентаИзЭксель (для поиска контрагента)
//   - ДатаДоставкиИзЭксель
//   - КодВодителяИзЭксель (для поиска водителя)
//   - А также данные для табличной части документа.

// Создаем менеджер временных таблиц
МВТ = Новый МенеджерВременныхТаблиц;

// 2. Формируем пакетный запрос
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МВТ;

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

// Устанавливаем параметры запроса. &ТЗ_ДанныеИзЭксель должна быть ТаблицейЗначений,
// содержащей КодКонтрагентаИзЭксель, ДатаДоставкиИзЭксель, КодВодителяИзЭксель и другие данные.
Запрос.УстановитьПараметр("ТЗ_ДанныеИзЭксель", ТЗ_ДанныеИзЭксель); 

// Выполняем запрос
Результат = Запрос.Выполнить();
ВыборкаНовыхДокументов = Результат.Выбрать();

// 3. Итерируем по новым данным и создаем документы
Пока ВыборкаНовыхДокументов.Следующий() Цикл
    НовыйДокумент = Документы.ЗаказБулки.СоздатьДокумент();
    НовыйДокумент.Дата = ВыборкаНовыхДокументов.ДатаДоставки;
    НовыйДокумент.Контрагент = ВыборкаНовыхДокументов.Контрагент;
    НовыйДокумент.Водитель = ВыборкаНовыхДокументов.Водитель;
    НовыйДокумент.Авто = Истина;
    // ... Заполняем остальные реквизиты шапки из ВыборкаНовыхДокументов
    
    // Если табличная часть также формируется из Excel, 
    // потребуется дополнительная логика для получения строк ТЧ. 
    // Это может быть отдельная временная таблица с данными ТЧ,
    // которую нужно соединить с этой выборкой по уникальному ключу строки Excel.
    // Для простоты примера, здесь это опущено, но в реальной задаче это важный аспект.

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

// МенеджерВременныхТаблиц будет уничтожен автоматически при выходе из области видимости,
// или можно вызвать МВТ.Закрыть();

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

Метод 3: Использование контрольной суммы файла-источника

Еще один эффективный подход к предотвращению повторной загрузки идентичных файлов — это использование контрольной суммы файла. Принцип работы следующий:

  1. При первой загрузке файла Excel мы рассчитываем его контрольную сумму (например, MD5). Это уникальный "отпечаток" файла.
  2. Эту контрольную сумму мы сохраняем в одном из реквизитов создаваемого документа (например, КонтрольнаяСуммаФайла) или в отдельном регистре сведений, связанном с загруженными документами.
  3. При любой последующей попытке загрузить файл мы снова рассчитываем его контрольную сумму.
  4. Затем мы выполняем запрос к базе данных, ища документы (или записи в регистре) с такой же сохраненной контрольной суммой.
  5. Если документ с этой контрольной суммой найден, это означает, что файл уже был загружен. Мы можем пропустить его обработку или выдать соответствующее сообщение. Если контрольная сумма не найдена, это либо совершенно новый файл, либо ранее загруженный файл был изменен, и его можно обрабатывать.

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

Рассмотрим пример функции для получения контрольной суммы (MD5) файла и ее использования в логике загрузки:


// Функция для расчета MD5 хеша файла
Функция ПолучитьКонтрольнуюСуммуФайлаMD5(ПолноеИмяФайла)
    Попытка
        Файл = Новый Файл(ПолноеИмяФайла);
        Если Не Файл.Существует() Тогда
            Возврат "";
        КонецЕсли;
        
        // Читаем содержимое файла в двоичные данные
        ДвоичныеДанныеФайла = Новый ДвоичныеДанные(ПолноеИмяФайла);
        
        // Создаем объект для хеширования по алгоритму MD5
        Хеш = Новый ХешированиеДанных(МетодХеширования.MD5);
        Хеш.Обновить(ДвоичныеДанныеФайла); // Обновляем хеш данными файла
        
        // Получаем хеш в виде строки
        Возврат Хеш.ПолучитьХешСтрока();
    Исключение
        Сообщить("Ошибка при расчете контрольной суммы файла " + ПолноеИмяФайла + ": " + ОписаниеОшибки());
        Возврат "";
    КонецПопытки;
КонецФункции

// Пример использования в коде загрузки документов
ИмяФайлаЭксель = "C:\МоиЗаказы\ЗаказНаБулки_15032024.xlsx";
КонтрольнаяСумма = ПолучитьКонтрольнуюСуммуФайлаMD5(ИмяФайлаЭксель);

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

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

Сохранение признака Авто = Истина

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

Когда документ создается автоматически из Excel, мы устанавливаем для него реквизит Авто = Истина. Если же в дальнейшем пользователь открывает этот документ и вносит в него изменения, мы изменяем этот реквизит на Ложь. Таким образом, при повторной загрузке файла, наша система не будет считать вручную измененный документ дубликатом для *создания новой версии*, а либо пропустит его, либо создаст новый документ, если это необходимо по бизнес-логике, не затрагивая уже отредактированный.

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

Мы видим, что существует несколько подходов к решению проблемы дублирования документов при загрузке из Excel. Выбор конкретного метода зависит от объема загружаемых данных, требований к производительности, а также от сложности бизнес-логики, определяющей уникальность документа и его жизненный цикл. Мы рассмотрели и базовый метод с запросом в цикле, и оптимизированный подход с временными таблицами, а также дополнительный контроль по контрольной сумме файла-источника. Каждый из этих методов имеет свои преимущества и может быть применен в зависимости от вашей конкретной ситуации.

← На главную