Интеграция с Росреестром через Web API: опыт автоматизации получения договоров в DirectumRX

3 0

Задача

Основная цель — получать зарегистрированные договоры напрямую из Росреестра через Web API СКБ Техно и создавать их в системе автоматически, без дублирования и с обработкой ошибок. Интеграция реализуется как фоновый процесс, который запускается ежедневно.

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

Настройки интеграции

Для удобства пользователей реализована клиентская функция, вызывающая диалог для настройки параметров интеграции.

Данные параметры сохраняются в таблице Sungero_Docflow_Params. Записать в нее значения можно через функцию: 

Sungero.Docflow.PublicFunctions.Module.InsertOrUpdateDocflowParam(key, value);

Это позволяет:

  • указывать дату, с которой нужно повторно запустить фоновую обработку (в случае ошибок или недоступности сервиса);

  • настраивать ID сотрудников, получающих задачи или уведомления о результатах интеграции;

  • централизованно управлять URL и API-ключами.

Основной алгоритм интеграции

  • Запуск фонового процесса;

  • Получение списка заявлений;

  • Обработка каждого заявления:

    • Проверка дублей;

    • Получение данных сторон, документа, подписей;

    • Создание договора и уведомлений.

Фоновый процесс

var baseUrl = PublicFunctions.Module.Remote.GetApiIntegrationParam(
    centrvd.DataBook.Constants.Module.IntegrationRosreestr
        .IntegrationBaseUrl);
var apiKey = PublicFunctions.Module.Remote.GetApiIntegrationParam(
    centrvd.DataBook.Constants.Module.IntegrationRosreestr
        .IntegrationApiKey);

if (string.IsNullOrEmpty(baseUrl) || string.IsNullOrEmpty(apiKey)) {
  Logger.Error("Отсутствуют параметры конфигурации API");
  return;
}

var statusDateFrom = PublicFunctions.Module.GetLastDocumentExchangeDate().Date;
var statusDateTo = Calendar.Today;

var registrationList = PublicFunctions.Module.GetRegistrationList(
    baseUrl, apiKey,
    centrvd.DataBook.Constants.Module.IntegrationRosreestr.CompletedReg,
    statusDateFrom, statusDateTo);

if (!registrationList.Result || registrationList.Data == null) {
  Logger.ErrorFormat("Не удалось получить список заявлений: {0}",
                     registrationList.Error);
  return;
}

Logger.DebugFormat("Получено из Росреестра {0} единиц заявлений",
                   registrationList.Data.Count);
foreach (var registration in registrationList.Data) {
  try {
    PublicFunctions.Module.ProcessSingleRegistration(registration, baseUrl,
                                                     apiKey, statusDateFrom);
  } catch (Exception ex) {
    Logger.ErrorFormat("Ошибка обработки заявления {0}: {1}", registration.Id,
                       ex.Message);
    continue;
  }
}

Взаимодействие с внешним API

Код интеграции построен на использовании HttpClient для выполнения запросов к Web API Росреестра. Запросы реализуются как GET-методы с параметрами в URL, авторизация — через API-ключ.

Пример запроса:

[Public]
public Structures.Module.IApiResponse GetRegistrationList(
    string host, string apiKey, int statuses, ateTime statusDateFrom,
    DateTime statusDateTo) {
  var apiResponse =
      centrvd.DataBook.Structures.Module.ApiResponse.Create();
  using (var client = new HttpClient()) {
    try {
      var url = string.Format(
          "{0}/Registrations/?api-key={1}&statuses={2}&dateFrom={3}&dateTo={4}",
          host, apiKey, statuses,
          statusDateFrom.Date.ToString("yyyy-MM-dd HH:mm:ss"),
          statusDateTo.Date.ToString("yyyy-MM-dd HH:mm:ss"));

      var response = client.GetAsync(url).Result;

      if (response.StatusCode == System.Net.HttpStatusCode.OK) 
      {
        var result = response.Content.ReadAsStringAsync().Result;
        apiResponse = JsonConvert.DeserializeObject<Structures.Module.IApiResponse>(result);
      }
    }
  }
  ///
  /// Обработка ошибок
  ///

  return apiResponse;
}

Обработка ответа:

  • Ответ читается как строка и десериализуется в структуру данных через JsonConvert.DeserializeObject;

  • Вся информация о попытке подключения (успех или ошибка) логируется через справочник CheckRosreestrAccess;

  • В случае ошибки HTTP или исключения формируется развёрнутая запись с причиной и сохраняется в историю проверок.

Это позволяет отследить все попытки обмена и вовремя выявить, если API недоступен или возвращает неожиданный ответ.

Получение данных по каждому заявлению

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

  • Получение полных данных о клиентах сделки;

  • Получение информации об объектах недвижимости;

  • Загрузка основного документа и подписей (в формате base64);

  • Загрузка итогового пакета результатов сделки (архив с выпиской ЕГРН и пр.).

В рамках данной интеграции выполняется несколько запросов к API, ниже приведен пример еще одного из запросов, возвращающий тело документа и массив подписей на документе: 

try {
  var url = string.Format(centrvd.DataBook.Constants.Module
                              .IntegrationRosreestr.DownloadDocument,
                          host, registrationId, documentId, apiKey);
  var response = client.GetAsync(url).Result;

  if (response.StatusCode == System.Net.HttpStatusCode.OK) {
    var json = response.Content.ReadAsStringAsync().Result;
    var jObject = Newtonsoft.Json.Linq.JObject.Parse(json);

    var documentWithSignatures = centrvd.DataBook.Structures.Module
                                     .DocumentWithSignatures.Create();

    documentWithSignatures.DocumentBase64 =
        jObject[centrvd.DataBook.Constants.Module.IntegrationRosreestr
                    .Data]?[centrvd.DataBook.Constants.Module
                                .IntegrationRosreestr.FileBase64]
            ?.ToString();

    documentWithSignatures.SignaturesBase64 =
        jObject[centrvd.DataBook.Constants.Module.IntegrationRosreestr
                    .Data]?[centrvd.DataBook.Constants.Module
                                .IntegrationRosreestr.SigDataBase64]
            ?.ToObject<List<string>>() ??
        new List<string>();

    return documentWithSignatures;
  }

  Logger.DebugFormat("Ошибка HTTP при получении документа {0}: {1}", documentId,
                     response.StatusCode);
}
///
/// Обработка ошибок
///

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

Перед созданием документов в RX особенно важно предотвращать повторное создание договора, если он уже был создан ранее — или если был создан, но затем удалён вручную.

Предотвращение дублирования договоров

При проектировании мы исходили из строгого правила: на каждое заявление в Росреестре должен приходиться только один договор в системе.

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

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

Алгоритм:

  1. Проверяем, есть ли запись в ExternalEntityLinks с нужным ExtEntityId (ID из Росреестра):
var existingLink = ExternalEntityLinks.GetAll(l => l.ExtEntityId == registration.Id).FirstOrDefault();
  1. Если ссылка есть — проверяем, существует ли сам договор:

var existingContract = Contracts.GetAll(c => c.Id == existingLink.EntityId).FirstOrDefault();
  1. Если договор существует — пропускаем обработку:

if (existingContract != null)
{
    Logger.DebugFormat("Договор для заявления {0} уже существует", registration.Id);
    return;
}
  1. Если ссылка есть, а договора нет — удаляем устаревшую ссылку и продолжаем создание.

if (existingContract == null)
    ExternalEntityLinks.Delete(existingLink);

       5. После создания договора, создаём ExternalEntityLink, чтобы зафиксировать связь с заявлением:

CreateExternalLink(contract.Id, contract.GetEntityMetadata().GetOriginal().NameGuid.ToString(), data.Id.ToString());

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

Логирование ошибок

Для записи всех попыток подключения создан справочник CheckRosreestrAccess. Каждая попытка обращения к API (в том числе успешная) фиксируется:

var checkRosreestrAccess = DataBook.CheckRosreestrAccesses.Create();
checkRosreestrAccess.Name = "Подключение к Росреестру. Запрос GET RegistrationList";
checkRosreestrAccess.Date = Calendar.Now;

try
{
  ...
  checkRosreestrAccess.Result = DataBook.CheckRosreestrAccess.Result.Success;
}
catch (Exception ex)
{
  checkRosreestrAccess.Result = DataBook.CheckRosreestrAccess.Result.Failure;
  checkRosreestrAccess.Comment = ex.Message;
}
finally
{
  checkRosreestrAccess.Save();
}

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

Создание договора в Directum RX

После получения всей необходимой информации по договору, его необходимо создать в нашей системе: 

public void CreateContractDocument(
    Structures.Module.IRegistrationData data,
    Structures.Module.IAppliedDocument doc, string documentContent,
    List<string> signaturesContent, Guid documentKindId, string registrationId,
    string baseUrl, string apiKey,
    List<Structures.Module.ICreationEntity> tasksQueue, DateTime dateFrom) {
  try {
    var contract = Contracts.Contracts.Create();

    FillCommonContractFields(contract, data, doc);
    if (!FillParties(contract, data, doc, tasksQueue, dateFrom)) {
      Logger.DebugFormat("Не удалось заполнить данные сторон для заявления {0}",
                         data.Id);
      return;
    }

    contract.DocumentKind =
        Sungero.Docflow.PublicFunctions.DocumentKind.GetNativeDocumentKind(documentKindId);
    contract.ContractualKind =
        Contracts.Contract.ContractualKind.WithBuyer;
    contract.Name =
        $"{contract.DocumentKind}, №{contract.RegistrationNumber} от {contract.DocumentDate}, контрагент: {contract.Counterparty}";

    AttachDocumentToContract(contract, documentContent, doc.Filename);
    CreateExternalLink(
        contract.Id,
        contract.GetEntityMetadata().GetOriginal().NameGuid.ToString(),
        data.Id.ToString());
    contract.LifeCycleState = Contracts.Contract.LifeCycleState.Active;

    contract.Save();
    AttachAddendum(contract, registrationId, baseUrl, apiKey);
    ImportSignatureToDocument(contract.Id, signaturesContent);
    SendTaskRosreestrDoc(contract);
  } catch (Exception ex) {
    Logger.ErrorFormat("Ошибка создания договора для заявления {0}: {1}",
                       data.Id, ex.Message);
  }
}

Дополнительные возможности: задачи и уведомления

  • Все отсутствующие справочные данные (контрагенты, НОРы, подразделения, сотрудники) не создаются автоматически, а формируются в очередь задач, направляемую ответственным пользователям. Это реализовано по требованию заказчика, так как по их регламенту все кроме персон должно создаваться ответственными людьми

  • После формирования договора формируется задача для бухгалтерии или другого отдела — договор прикрепляется автоматически, а сотрудник получает уведомление.

Итоги

Реализация интеграции с Росреестром через api от СКБ Техно позволила:

  • Автоматизировать процесс получения договоров;

  • Сократить время на занесение договоров в систему вручную;

  • Исключить дублирование информации;

  • Обеспечить устойчивую и контролируемую интеграцию с внешней системой.

Пока комментариев нет.

Авторизуйтесь, чтобы написать комментарий