Как мы автоматизировали тендерные процедуры за счет интеграции SAP с ЭТП

Всем привет! Меня зовут Олег Усов, я работаю старшим консультантом SAP MM в Fix Price. Хочу рассказать, как мы проводили интеграцию SAP с электронной торговой площадкой (ЭТП) и системой электронного документооборота (СЭД) для автоматизации ряда процессов по участию в закупках услуг. Точнее: расскажу о части автоматизации сквозного процесса поиска поставщиков и заведения в системе контрактов. Всё было сделано силами нашей команды без привлечения сторонних разработчиков.

Как всё работает

Если коротко, создается заявка на закупку в SAP, проверяется и при необходимости корректируется. На основе данных заявки SAP через API электронной торговой площадки https://www.b2b-center.ru (далее ЭТП B2B) автоматически формируется процедура на ЭТП B2B с подгрузкой реквизитов и другой документации. Следующие этапы происходят уже полностью на ЭТП B2B:

  • объявляются торги;

  • определяется победитель;

  • процедура закрывается со статусом «Торги окончены».

Упрощенная схема цепочки от заведения заявки до создания контракта выглядит так:

8d5b676c783db407952145d0e8ade9d3.png

Обычно мы имеем дело с однолотовыми многопозиционными процедурами. После выбора победителя (-ей)  на ЭТП процедуре присваивается статус «Торги окончены», далее из SAP сотрудником инициируется запрос на создание объекта согласования в нашей системе электронного документооборота (далее СЭД). Затем загружаются документы,   и в СЭД запускается процедура согласования контрагентов — одного или нескольких победителей торгов. На этом этапе руководитель соответствующего департамента, сотрудники, контролирующие договорные процедуры и, при необходимости, генеральный директор, согласовывают выбранных поставщиков.

На финальном шаге у объекта согласования меняется статус на «Победитель утвержден». После этого финальный статус согласования автоматически направляется из СЭД в SAP. Затем инициируется запрос на создание контракта (-ов) в системе SAP c выбранными поставщиками-победителями. Если контрагент (поставщик) уже был заведен в системе, то контракт по позициям автоматически создается через соответствующий функциональный модуль SAP. Если же поставщик в системе отсутствует, в бухгалтерию автоматически направляется рассылка на создание нового контрагента. После создания нового контрагента и привязывания его к заявке также автоматически создается контракт в SAP.

Согласование поставщиков-победителей по заявке может быть отклонено одним из контролирующих сотрудников по каким-либо причинам. В этом случае процедура согласования вернется на исходный (первоначальный) шаг согласования. Соответствующий статус отобразится в СЭДе и в SAP. В таком случае запрос уходит в SAP для внесения изменений ответственным специалистом — он получает об этом оповещение по электронной почте. В заявку вносятся изменения, после чего производится повторное согласование и создание контракта с помощью функционального модуля SAP.

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

Заявка одна — победителей много

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

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

63d91a15e797ee52b72fc156c732bd7c.png

Контракт 1 для поставщика с ИНН 6454065338:

6a6b0bed90520a1d4d495d783b3a97f5.png

Контракт 2 для поставщика с ИНН 5024139339:

ca0bfb9126ec13938770c7a382f67b45.png

Техническая реализация

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

После того как процедура B2B завершена (статус «Торги завершены»), из СЭД инициируется запрос в SAP, который запускает следующие методы:

Для получения данных ЭТП B2B используем следующие методы API RemoteAuction.getPositions, RemoteAuction.getResultsData, RemoteMarket.getFirmInfo.

Пример работы с методом RemoteAuction.getResultsData:

формируем xml
  TRY .
      CALL TRANSFORMATION zb2b_auction_get_result_data
        OPTIONS  initial_components = 'suppress'
        SOURCE  zauth      = ms_auth
                auction_id = iv_auction_id
        RESULT XML lv_xml.
    CATCH cx_st_error .
      add_message 'ZB2B' 'E' 000 'Не удалось сформировать XML' 'GET_URL' '' ''.
      RETURN.
  ENDTRY.

  REPLACE FIRST OCCURRENCE OF 'utf-16' IN lv_xml WITH 'utf-8'.
  REPLACE FIRST OCCURRENCE OF 'RemoteAuction.getResultsData' IN lv_xml WITH 'RemoteAuction.getResultsData xmlns=""'.

  " формируем запрос
  cl_http_client=>create_by_destination(
    EXPORTING
      destination              = mv_dest
    IMPORTING
      client                   = lo_client
    EXCEPTIONS
      argument_not_found       = 1
      destination_not_found    = 2
      destination_no_authority = 3
      plugin_not_active        = 4
      internal_error           = 5
      OTHERS                   = 6
  ).
  IF sy-subrc <> 0.
    add_message sy-msgid sy-msgty sy-msgno sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
    RETURN.
  ENDIF.

  lv_len = strlen( lv_xml ).
  CALL METHOD lo_client->request->set_cdata
    EXPORTING
      data   = lv_xml
      offset = 0
      length = lv_len.

  " Устанавливаем заголовок
  ls_header_field-name = 'User-Agent'.
  ls_header_field-value = 'NuSOAP/0.7.3 (1.43)'.
  APPEND ls_header_field TO lt_header_fields.

  ls_header_field-name = 'SOAPAction'.
  ls_header_field-value = '"urn:ws#getStatus"'.
  APPEND ls_header_field TO lt_header_fields.

  lo_client->request->set_header_fields( fields = lt_header_fields ).
  CLEAR ls_header_field.

  " Метод POST
  CALL METHOD lo_client->request->set_method
    EXPORTING
      method = if_http_request=>co_request_method_post.

  CALL METHOD lo_client->request->set_content_type
    EXPORTING
      content_type = 'text/xml; charset=UTF-8'.

  " отправка запроса
  CALL METHOD lo_client->send
*  EXPORTING
*    timeout                    = CO_TIMEOUT_DEFAULT
    EXCEPTIONS
      http_communication_failure = 1
      http_invalid_state         = 2
      http_processing_failed     = 3
      http_invalid_timeout       = 4
      OTHERS                     = 5
    .
  IF sy-subrc <> 0.

    add_message_exceptions( EXPORTING io_object = lo_client iv_method = 'SEND' iv_ind = sy-subrc
                            CHANGING ct_return = et_return ).
    CALL METHOD lo_client->refresh_request.
    CALL METHOD lo_client->close.
    RETURN.
  ENDIF.

  "получение ответа
  CALL METHOD lo_client->receive
    EXCEPTIONS
      http_communication_failure = 1
      http_invalid_state         = 2
      http_processing_failed     = 3
      OTHERS                     = 4.

  IF sy-subrc <> 0.
    add_message_exceptions( EXPORTING io_object = lo_client iv_method = 'RECEIVE' iv_ind = sy-subrc
                            CHANGING ct_return = et_return ).
  ELSE.
    lo_client->response->get_status( IMPORTING code = lv_http_rc reason = lv_reason ).
    IF lv_http_rc <> 200.
      add_message 'ZB2B' 'E' 000 lv_reason '' '' ''.
    ELSE.
      lv_xml_r = lo_client->response->get_cdata( ).
      IF lv_xml_r IS NOT INITIAL.
        TRY .
            CALL TRANSFORMATION zb2b_auction_get_result_data_r
              SOURCE XML lv_xml_r
              RESULT resp = es_response
                     winners = lt_winners
                     reason  = lv_reason_results
                     date_end = lv_date_end.
          CATCH cx_st_error INTO lcx_error .
            lv_error = lcx_error->get_text( ).
            add_message 'ZB2B' 'E' 000 'Не удалось разобрать XML-ответ' 'GET_RESULTS_DATA' '' ''.
        ENDTRY.
        IF es_response-error_code <> 0.
          IF es_response-error_message IS NOT INITIAL.
            lv_message = es_response-error_message.
          ELSE.
            lv_message = es_response-message.
          ENDIF.
          convert_response_message( EXPORTING iv_response_message = lv_message
                                    IMPORTING es_return = ls_return ).
          APPEND ls_return TO et_return.
        ELSE.
          SORT lt_winners by position_number.
          LOOP AT lt_winners INTO ls_winner.
            CLEAR ls_winner_ret.
            ls_winner_ret-number_pos = ls_winner-position_number.
            LOOP AT ls_winner-participant INTO ls_participant.
              ls_winner_ret-participant = ls_participant-firm_id.
              ls_winner_ret-offer_num   = ls_participant-alternate_num.
              TRY.                                              
                IF ls_participant-unit = 'percent'.
                  ls_winner_ret-percent = ls_participant-part.
                ELSE.
                  ls_winner_ret-quantity = ls_participant-part.
                ENDIF.
              CATCH cx_sy_conversion_error INTO lcx_conversion_error.
                lv_error = lcx_conversion_error->get_text( ).
                add_message 'ZB2B' 'E' 000 lv_error '' '' ''.
              ENDTRY.                                         
              APPEND ls_winner_ret to et_winners.
            ENDLOOP.
            IF sy-subrc <> 0.
              APPEND ls_winner_ret to et_winners.
            ENDIF.
          ENDLOOP.
        ENDIF.
      ENDIF.
    ENDIF.
  ENDIF.

Затем заявка наполняется полученными данными (цены, ИНН победителей). Цены заносятся в стандартные поля заявки SAP, ИНН победителей — в стандартное пользовательское поле с техническим названием ESLL-USERF1_TXT (заявки ведутся в транзакции ME53N).

Используется стандартный функциональный модуль BAPI_REQUISITION_GETDETAIL для считывания данных заявки, затем на основе данных заявки используется BATCH INPUT для вызова транзакции ME31K, создается (-ются) контракт (-ы) и обновляется объект согласования СЭД (заполняется созданный номер контракта).

На этом всё, а если вас интересуют какие-то детали, не стесняйтесь спрашивать об этом в комментариях.

© Habrahabr.ru