Всем привет! Меня зовут Олег Усов, я работаю старшим консультантом 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, который запускает следующие методы:
Обновление заявки SAP полученными данными от ЭТП B2B.
Для получения данных ЭТП 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).
Создание контракта (-ов) на основе заявки SAP.
Используется стандартный функциональный модуль BAPI_REQUISITION_GETDETAIL для считывания данных заявки, затем на основе данных заявки используется BATCH INPUT для вызова транзакции ME31K, создается (-ются) контракт (-ы) и обновляется объект согласования СЭД (заполняется созданный номер контракта).
На этом всё, а если вас интересуют какие-то детали, не стесняйтесь спрашивать об этом в комментариях.

