Как мы автоматизировали тендерные процедуры за счет интеграции SAP с ЭТП
Всем привет! Меня зовут Олег Усов, я работаю старшим консультантом SAP MM в Fix Price. Хочу рассказать, как мы проводили интеграцию SAP с электронной торговой площадкой (ЭТП) и системой электронного документооборота (СЭД) для автоматизации ряда процессов по участию в закупках услуг. Точнее: расскажу о части автоматизации сквозного процесса поиска поставщиков и заведения в системе контрактов. Всё было сделано силами нашей команды без привлечения сторонних разработчиков.
Как всё работает
Если коротко, создается заявка на закупку в SAP, проверяется и при необходимости корректируется. На основе данных заявки SAP через API электронной торговой площадки https://www.b2b-center.ru (далее ЭТП B2B) автоматически формируется процедура на ЭТП B2B с подгрузкой реквизитов и другой документации. Следующие этапы происходят уже полностью на ЭТП B2B:
объявляются торги;
определяется победитель;
процедура закрывается со статусом «Торги окончены».
Упрощенная схема цепочки от заведения заявки до создания контракта выглядит так:
Обычно мы имеем дело с однолотовыми многопозиционными процедурами. После выбора победителя (-ей) на ЭТП процедуре присваивается статус «Торги окончены», далее из SAP сотрудником инициируется запрос на создание объекта согласования в нашей системе электронного документооборота (далее СЭД). Затем загружаются документы, и в СЭД запускается процедура согласования контрагентов — одного или нескольких победителей торгов. На этом этапе руководитель соответствующего департамента, сотрудники, контролирующие договорные процедуры и, при необходимости, генеральный директор, согласовывают выбранных поставщиков.
На финальном шаге у объекта согласования меняется статус на «Победитель утвержден». После этого финальный статус согласования автоматически направляется из СЭД в SAP. Затем инициируется запрос на создание контракта (-ов) в системе SAP c выбранными поставщиками-победителями. Если контрагент (поставщик) уже был заведен в системе, то контракт по позициям автоматически создается через соответствующий функциональный модуль SAP. Если же поставщик в системе отсутствует, в бухгалтерию автоматически направляется рассылка на создание нового контрагента. После создания нового контрагента и привязывания его к заявке также автоматически создается контракт в SAP.
Согласование поставщиков-победителей по заявке может быть отклонено одним из контролирующих сотрудников по каким-либо причинам. В этом случае процедура согласования вернется на исходный (первоначальный) шаг согласования. Соответствующий статус отобразится в СЭДе и в SAP. В таком случае запрос уходит в SAP для внесения изменений ответственным специалистом — он получает об этом оповещение по электронной почте. В заявку вносятся изменения, после чего производится повторное согласование и создание контракта с помощью функционального модуля SAP.
Такая интеграция позволила нам упростить процессы по проведению тендерных процедур, составлению и передаче тендерной документации, согласованию выбора поставщиков и оформления контрактов. Теперь расскажу об интересных моментах, с которыми нам пришлось столкнуться при реализации интеграции.
Заявка одна — победителей много
При создании таких заявок иногда бывает так, что на ЭТП объявляются победителями несколько компаний по одной и той же позиции. Например, по результатам одной процедуры решено выбрать несколько победителей, т.е. по первой половине позиций выбран один поставщик, по второй половине — другой. Если не предусмотреть создание контрактов для нескольких победителей, может возникнуть проблема, однако мы это учли.
Мы решили данную проблему путем заполнения ИНН поставщика-победителя процедуры в каждой позиции заявки на закупку в SAP. Данные для создания контракта (цены, ИНН поставщиков-победителей) получаем через API ЭТП B2B. В результате появляется возможность создать несколько контрактов по одной тендерной процедуре в ЭТП B2B. Контракт в SAP создается на основе данных обновленной заявки SAP. Вот как это выглядит в системе. Ниже скрин заполненной заявки (тр. ME53N), красным выделено поле, в котором заполняется ИНН:
Контракт 1 для поставщика с ИНН 6454065338:
Контракт 2 для поставщика с ИНН 5024139339:
Техническая реализация
Поскольку готовых реализаций в нашей версии 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, создается (-ются) контракт (-ы) и обновляется объект согласования СЭД (заполняется созданный номер контракта).
На этом всё, а если вас интересуют какие-то детали, не стесняйтесь спрашивать об этом в комментариях.