Как предотвратить задвоение контрагентов при обмене данными через HTTP-сервис в 1С и отладить процесс?

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

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

Настройка и использование отладки HTTP-сервисов

Для эффективного выявления причины задвоений критически важно иметь возможность отлаживать код, который обрабатывает входящие HTTP-запросы. Если вы ранее не работали с веб-сервисами, мы рассмотрим процесс по шагам.

  1. Подключение отладки HTTP-сервиса:

    Для того чтобы отладить код, выполняющийся на сервере 1С при обращении к HTTP-сервису, необходимо использовать автоматическое подключение отладчика. Для этого:

    В конфигураторе перейдите в меню Отладка -> Подключение -> Автоматическое подключение -> HTTP-сервисы. Убедитесь, что отладка на сервере 1С включена. Если отладка на сервере не включена, этот метод не сработает.

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

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

  2. Просмотр входящего JSON-сообщения:

    Одна из первых задач при отладке — понять, какие данные фактически "прилетают" с HTTP-запросом. Входящие данные JSON не являются "файлом, лежащим где-то", а передаются в теле HTTP-запроса.

    Мы можем вывести содержимое JSON-сообщения, например, сразу после получения тела запроса. Рассмотрим пример, где сообщение получено в переменную Сообщение:

    
    // Предположим, что Запрос это объект входящего HTTP-запроса,
    // а Сообщение - переменная для хранения тела запроса.
    // В вашем коде это может быть что-то вроде:
    // Сообщение = Запрос.ПолучитьТелоКакСтроку();
    
    // Чтобы увидеть содержимое JSON:
    НовоеСообщение = Новый СообщениеПользователю;
    НовоеСообщение.Текст = Сообщение;
    НовоеСообщение.Сообщить();
    

    Этот код выведет полное содержимое JSON-строки в окно сообщений 1С, что позволит вам убедиться в корректности передаваемых данных, их структуре и заполненности ключевых полей, таких как ИНН, КПП, наименование и дата рождения.

  3. Использование внешних инструментов для анализа HTTP-запросов:

    Если отладка на сервере 1С недоступна или вызывает трудности, существуют альтернативные методы для анализа входящих HTTP-запросов. Мы можем использовать прокси-серверы, такие как Fiddler или Charles Proxy, для перехвата и анализа HTTP-запросов и ответов. Это позволяет увидеть "сырые" данные JSON, которые приходят в 1С, до того как они будут обработаны. Такой подход помогает убедиться, что передаваемые данные корректны и соответствуют ожиданиям. Вы настраиваете эти инструменты как прокси-сервер между вашей системой-источником и сервером 1С, и они показывают весь трафик.

  4. Ведение подробного лога:

    В дополнение к Сообщить() или системному ЗаписьЖурналаРегистрации(), мы можем создать собственный механизм логирования в текстовый файл или отдельный регистр сведений. Это позволяет записывать полный текст входящего JSON-сообщения, а также результаты промежуточных проверок и параметры поиска перед выполнением запроса. Такой подход особенно полезен, когда отладка на сервере невозможна или нежелательна из-за производительности.

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

    
    ЗаписьЖурналаРегистрации(
        "DocumentExchange",
        УровеньЖурналаРегистрации.Информация,
        , // Метаданные - можно указать, если есть привязка к конкретному объекту
        , // Объект - можно указать ссылку на объект
        СтрШаблон(
            "Начало поиска контрагента. Наименование: %1, ИсточникИНН: %2, ИсточникКПП: %3, ДатаРождения: %4",
            ИсточникНаименованиеКонтрагента,
            ИсточникИНН,
            ИсточникКПП,
            ДатаРождения
        )
    );
    

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

    
    Попытка
        // Ваш код обработки Json и поиска контрагента
    Исключение
        ЗаписьЖурналаРегистрации(
            "ЗагрузкаКонтрагента",
            УровеньЖурналаРегистрации.Ошибка,
            Метаданные.Справочники.Контрагенты, // Пример привязки к метаданным
            ,
            "Исключение при обработке контрагента: " + ОписаниеОшибки()
        );
    КонецПопытки;
    
  5. Тестовые запросы с POST-клиентами:

    Для отладки логики поиска и обработки данных в HTTP-сервисе, мы можем использовать POST-клиенты, такие как Postman или Insomnia. Эти инструменты позволяют отправлять тестовые запросы с заведомо известными данными JSON. Это позволяет воспроизводить различные сценарии (например, передача контрагента с полными данными, с отсутствующим ИНН, с измененным наименованием) и отлаживать код без постоянного обращения к системе-источнику. Вы можете задать URL вашего HTTP-сервиса, выбрать метод POST, указать тип контента как application/json и ввести вручную тело JSON-сообщения.

Анализ логики поиска существующих контрагентов

Основной причиной задвоения контрагентов является некорректная или недостаточная логика поиска существующего элемента перед созданием нового. Мы проанализируем ваш код поиска и предложим улучшения.

Рассмотрим предоставленный вами код запроса на поиск контрагента:


Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ ПЕРВЫЕ 1
| Контрагенты.Ссылка
|ИЗ
| Справочник.Контрагенты КАК Контрагенты
|ГДЕ
| НЕ Контрагенты.ПометкаУдаления
| И (&НеИспИНН
| ИЛИ Контрагенты.ИНН = &ИНН)
| И (&НеИспКПП
| ИЛИ Контрагенты.КПП = &КПП)
| И (&НеИспНаименование
| ИЛИ Контрагенты.Наименование = &Наименование)
| И (&НеИспДатуРождения
| ИЛИ Контрагенты.ДатаРождения = &ДатаРождения)";

Запрос.УстановитьПараметр("НеИспИНН" , обЗначениеНеЗаполнено(ИсточникИНН));
Запрос.УстановитьПараметр("НеИспКПП" , обЗначениеНеЗаполнено(ИсточникКПП));

Если ЗначениеЗаполнено(ИсточникИНН) И ЗначениеЗаполнено(ИсточникКПП) Тогда
    Запрос.УстановитьПараметр("НеИспНаименование" , Истина);
    Запрос.УстановитьПараметр("НеИспДатуРождения" , Истина);
Иначе
    Запрос.УстановитьПараметр("НеИспНаименование" , обЗначениеНеЗаполнено(ИсточникНаименованиеКонтрагента));
    Запрос.УстановитьПараметр("НеИспДатуРождения" , обЗначениеНеЗаполнено(ДатаРождения));
КонецЕсли;

Запрос.УстановитьПараметр("ИНН", ИсточникИНН);
Запрос.УстановитьПараметр("КПП", ИсточникКПП);
Запрос.УстановитьПараметр("Наименование", ИсточникНаименованиеКонтрагента);
Запрос.УстановитьПараметр("ДатаРождения", ДатаРождения);

РезультатЗапроса = Запрос.Выполнить().Выбрать();

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

  1. Логика установки флагов НеИспНаименование и НеИспДатуРождения:

    В вашем коде, если заполнены ИНН и КПП, то поиск по наименованию и дате рождения отключается (Истина). Это правильно для юридических лиц, но может быть проблематично, если, например, ИНН и КПП уникальны, но в базе уже есть контрагент с этими данными, но другим наименованием (возможно, из-за ошибки ввода или переименования). В таком случае, если новый контрагент приходит с ИНН+КПП, но другим наименованием, он будет найден по ИНН+КПП, но не будет проверен на совпадение наименования. Это не приведет к задвоению, но может быть нежелательно, если наименование также должно быть идентичным.

    Однако, если не заполнены ИНН и КПП, то флаги НеИспНаименование и НеИспДатуРождения устанавливаются по принципу обЗначениеНеЗаполнено(Параметр). Это означает, что если ИсточникНаименованиеКонтрагента или ДатаРождения не заполнены, то поиск по этим параметрам также будет проигнорирован. Если ИНН, КПП, Наименование и ДатаРождения все незаполнены, то запрос вернет любого первого контрагента из базы, что гарантированно приведет к ошибкам.

  2. Точное совпадение наименования и даты рождения:

    Поиск по Контрагенты.Наименование = &Наименование требует строгого совпадения. Любые расхождения (лишний пробел, другой регистр, кавычки, аббревиатуры) приведут к тому, что контрагент не будет найден, и создастся новый дубликат. Аналогично с датой рождения, если формат или значение немного отличаются.

  3. Обработка частных лиц без ИНН и юр. лиц с ИНН:

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

Стратегии улучшения поиска существующих контрагентов

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

  1. Приоритизация по уникальным идентификаторам:

    Всегда начинайте поиск с наиболее уникальных идентификаторов. Для юридических лиц и индивидуальных предпринимателей это ИНН и КПП (для юр. лиц) или ИНН (для ИП). Если ИНН отсутствует, только тогда переходите к менее надежным параметрам.

    Принцип: чем более уникален параметр, тем выше его приоритет в поиске.

  2. Последовательный поиск (многоступенчатый):

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

    
    Функция НайтиКонтрагента(ИсточникИНН, ИсточникКПП, ИсточникНаименованиеКонтрагента, ДатаРождения)
        Перем РезультатПоиска;
    
        // Шаг 1: Поиск по ИНН + КПП (наиболее надежный для Юр.Лиц)
        Если ЗначениеЗаполнено(ИсточникИНН) И ЗначениеЗаполнено(ИсточникКПП) Тогда
            Запрос = Новый Запрос;
            Запрос.Текст = "ВЫБРАТЬ ПЕРВЫЕ 1 Контрагенты.Ссылка ИЗ Справочник.Контрагенты КАК Контрагенты
                          |ГДЕ НЕ Контрагенты.ПометкаУдаления
                          | И Контрагенты.ИНН = &ИНН
                          | И Контрагенты.КПП = &КПП";
            Запрос.УстановитьПараметр("ИНН", ИсточникИНН);
            Запрос.УстановитьПараметр("КПП", ИсточникКПП);
            РезультатПоиска = Запрос.Выполнить().Выбрать();
            Если РезультатПоиска.Следующий() Тогда
                Возврат РезультатПоиска.Ссылка;
            КонецЕсли;
        КонецЕсли;
    
        // Шаг 2: Поиск по ИНН (для ИП или если КПП отсутствует/не заполнено)
        Если ЗначениеЗаполнено(ИсточникИНН) Тогда
            Запрос = Новый Запрос;
            Запрос.Текст = "ВЫБРАТЬ ПЕРВЫЕ 1 Контрагенты.Ссылка ИЗ Справочник.Контрагенты КАК Контрагенты
                          |ГДЕ НЕ Контрагенты.ПометкаУдаления
                          | И Контрагенты.ИНН = &ИНН";
            Запрос.УстановитьПараметр("ИНН", ИсточникИНН);
            РезультатПоиска = Запрос.Выполнить().Выбрать();
            Если РезультатПоиска.Следующий() Тогда
                Возврат РезультатПоиска.Ссылка;
            КонецЕсли;
        КонецЕсли;
    
        // Шаг 3: Поиск по Наименованию (ФИО) + Дата рождения (для Физ.Лиц без ИНН)
        // Здесь можем использовать "ПОДОБНО" для более гибкого поиска наименования,
        // но с осторожностью, чтобы не объединить разных контрагентов.
        Если ЗначениеЗаполнено(ИсточникНаименованиеКонтрагента) И ЗначениеЗаполнено(ДатаРождения) Тогда
            Запрос = Новый Запрос;
            Запрос.Текст = "ВЫБРАТЬ ПЕРВЫЕ 1 Контрагенты.Ссылка ИЗ Справочник.Контрагенты КАК Контрагенты
                          |ГДЕ НЕ Контрагенты.ПометкаУдаления
                          | И Контрагенты.Наименование = &Наименование
                          | И Контрагенты.ДатаРождения = &ДатаРождения";
            Запрос.УстановитьПараметр("Наименование", ИсточникНаименованиеКонтрагента);
            Запрос.УстановитьПараметр("ДатаРождения", ДатаРождения);
            РезультатПоиска = Запрос.Выполнить().Выбрать();
            Если РезультатПоиска.Следующий() Тогда
                Возврат РезультатПоиска.Ссылка;
            КонецЕсли;
        КонецЕсли;
    
        // Шаг 4: Поиск только по Наименованию (только как крайняя мера и с очень большой осторожностью)
        // Этот шаг может быть очень опасным и привести к массовым задвоениям/некорректным привязкам.
        // Рекомендуется его исключить или использовать с дополнительными проверками.
        // Если все предыдущие шаги не дали результата, возможно, контрагента нужно создавать.
    
        Возврат Неопределено; // Контрагент не найден
    КонецФункции
    
  3. "Нечеткий" поиск и сравнение:

    В некоторых случаях (особенно для наименований) может потребоваться "нечеткий" поиск (fuzzy matching) или нормализация данных (удаление лишних пробелов, приведение к одному регистру, удаление кавычек) перед сравнением. Однако это следует использовать с осторожностью, чтобы избежать ошибочного объединения разных контрагентов. Например, вместо = &Наименование можно использовать ПОДОБНО &Наименование, но это увеличивает риск ложных срабатываний. Перед использованием ПОДОБНО, мы должны очистить и нормализовать наименование, например, удалив все знаки препинания, применив функцию СокрЛП() и применив к верхнему регистру.

  4. Внешний идентификатор:

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

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

    Разделение контрагентов на группы (например, "Юридические лица", "Физические лица", "ИП") и поиск внутри соответствующей группы может повысить точность. Мы можем добавить проверку на тип контрагента (например, по длине ИНН) и направлять запрос в соответствующую логику поиска.

Анализ входящих данных JSON

Даже самая совершенная логика поиска будет бесполезна, если входящие данные некорректны или неполны. Мы должны тщательно проверять Json-файл.

  1. Проверка на заполненность параметров:

    Перед выполнением запроса на поиск убедитесь, что все используемые параметры (ИНН, КПП, Наименование, ДатаРождения) действительно заполнены в Json-файле. Частой причиной задвоения является отсутствие или некорректная передача ключевых данных из системы-источника. Мы должны использовать ЗначениеЗаполнено() для всех критически важных параметров.

  2. Корректность типов данных:

    Убедитесь, что типы данных в JSON-файле соответствуют ожидаемым типам в 1С. Например, дата должна быть в правильном формате (часто ISO 8601), числа — без лишних символов, логические значения — true/false. Неправильное преобразование типов может привести к тому, что параметры запроса будут пустыми или некорректными.

Меры при обнаружении потенциального дубля

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

  1. Ручное разрешение:

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

  2. Детальное журналирование:

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

Таким образом, для успешного предотвращения задвоений контрагентов при обмене через HTTP-сервисы в 1С, нам необходимо комплексно подходить к проблеме: настроить эффективную отладку, тщательно анализировать структуру и полноту входящих данных JSON, а также разработать многоступенчатую, приоритетную и гибкую логику поиска существующих контрагентов, учитывающую различные сценарии и типы объектов.

← На главную