よく使用される SAP インターフェイスは 5 つあります: PI、リモート RFC、API、Webservice、IDOC
1. 背景の紹介
1.1 RFCリモート機能ユニットの特徴
-
汎用モジュール: SAP システムでは、汎用モジュールは、他のプログラムから呼び出すことができる事前定義されたビジネス ロジック ユニットです。RFC メカニズムにより、リモートシステムの汎用モジュールをネットワーク経由で呼び出すことができます。
-
リモート呼び出し: RFC を使用すると、ある SAP システムが別の SAP システムの関数モジュールを呼び出すことができます (これらのシステムが異なるサーバー上にある場合でも)。
-
同期呼び出しと非同期呼び出し: RFC 呼び出しは、同期 (呼び出しが結果を返すのを待機する) または非同期 (結果が返されるのを待機しない) にすることができます。同期呼び出しは結果を待つ必要がある状況に適しており、非同期呼び出しは結果をすぐに取得する必要がない状況に適しています。
-
RFC ターゲット: RFC 汎用モジュールを呼び出す前に、RFC ターゲットを定義する必要があります。RFC ターゲットは、リモート システムと呼び出されるターゲット汎用モジュールを記述します。これらのターゲットは
SM59
トランザクション コードで構成できます。 -
トランザクションとバッチ処理: RFC 呼び出しは、トランザクション内またはバッチ ジョブ内で実行できます。これにより、異なるシステム間でのデータ転送や業務プロセスの処理が可能になります。
-
セキュリティ: RFC 呼び出しはネットワーク経由で実行できるため、セキュリティが重要な問題になります。SAP は、データの安全な送信と処理を保証するために、さまざまな認証および認可メカニズムを提供します。
2. 導入手順
2.1 具体的な実装手順
2.1.1 SE37-->関数の作成-->入出力パラメータの作成-->コードの作成
2.1.2 リモート機能モジュールの有効化
2.1.3 コードサンプル
FUNCTION zfm_mm_140.
*"----------------------------------------------------------------------
*"*"本地接口:
*" IMPORTING
*" VALUE(IH) TYPE ZSMM140HEADER
*" EXPORTING
*" VALUE(STATUS) TYPE CHAR2
*" VALUE(MESSAGE) TYPE CHAR255
*" TABLES
*" ITAB STRUCTURE ZSMM140ITEM
*"----------------------------------------------------------------------
*程序名:
*程序描述:MM-140_采购订单收货退货接口
*----------------------------------------------------------------------
*创建日期 ABAP开发顾问 业务顾问
*2019.09.17.
zlog_save1 'ZFM_MM_330'.
zlog_save2 'B'.
*-----------------------------------------------------------------------
DATA ls_gh TYPE bapi2017_gm_head_01.
DATA ls_gi TYPE bapi2017_gm_item_create.
DATA lt_gi TYPE TABLE OF bapi2017_gm_item_create.
DATA ls_poitem TYPE bapimepoitem.
DATA lt_poitem TYPE TABLE OF bapimepoitem.
DATA ls_poitemx TYPE bapimepoitemx.
DATA lt_poitemx TYPE TABLE OF bapimepoitemx.
DATA ls_return TYPE bapiret2.
DATA lt_return TYPE TABLE OF bapiret2.
DATA gr_ebeln TYPE RANGE OF ekpo-ebeln. "采购订单号
DATA gw_ebeln LIKE LINE OF gr_ebeln.
DATA lv_message_str(255) TYPE c.
IF itab[] IS NOT INITIAL.
"1.==========为检查逻辑所需要准备的数据源
CLEAR itab.
LOOP AT itab[] INTO itab.
gw_ebeln-sign = 'I'.
gw_ebeln-option = 'EQ'.
gw_ebeln-low = itab-po_number.
gw_ebeln-high = ''.
APPEND gw_ebeln TO gr_ebeln.
ENDLOOP.
DELETE ADJACENT DUPLICATES FROM gr_ebeln COMPARING low.
SELECT ekpo~ebeln,"采购订单号
ekpo~ebelp,"项次
ekpo~retpo,"采购退货订单标识 X:采购退货订单
ekpo~werks "工厂
FROM ekpo
WHERE ekpo~ebeln IN @gr_ebeln
INTO TABLE @DATA(lt_ekpo).
"查找未打删除标识的来料单号
SELECT
ztmm_incom_list~incom, "来料单号
ztmm_incom_list~item, "来料单项次
ztmm_incom_list~ebeln, "采购凭证
ztmm_incom_list~ebelp, "项目
ztmm_incom_list~loekz
FROM ztmm_incom_list WHERE ztmm_incom_list~loekz IS INITIAL AND ztmm_incom_list~incom = @ih-incom
INTO TABLE @DATA(lt_incom_list).
"取工厂和库存地点
SELECT
t001l~werks,
t001l~lgort
FROM t001l
INTO TABLE @DATA(lt_t001l).
"取物料过账日志表
SELECT
ztmm_140~incom,
ztmm_140~item,
ztmm_140~mblnr "物料凭证编号
FROM ztmm_140 WHERE ztmm_140~incom = @ih-incom
INTO TABLE @DATA(lt_ztmm_140).
"2.==========接口传入参数进行检查
LOOP AT itab[] ASSIGNING FIELD-SYMBOL(<ls_itab>).
" 传入的采购订单及项次若为空,则返回错消息“采购订单、项次不能为空”;
IF <ls_itab>-po_number IS INITIAL OR <ls_itab>-po_item IS INITIAL.
<ls_itab>-type = 'E'.
<ls_itab>-message = '采购订单、项次不能为空'.
CONTINUE.
ENDIF.
"根据传入采购订单及项次取退货标识(EKPO-RETPO),EKPO-RETPO =‘X’为退货采购订单;若采购订单及项次不是退货采购订单,
"则传入的来料单(INCOM)及来料单项次ITEM不为空,否则提示“非采购退货单,来料单及项次不能为空”
READ TABLE lt_ekpo INTO DATA(ls_ekpo) WITH KEY ebeln = <ls_itab>-po_number ebelp = <ls_itab>-po_item.
IF sy-subrc = 0.
IF ls_ekpo-retpo = 'X'.
"EKPO-RETPO =‘X’退货采购订单
<ls_itab>-retpo = 'X'.
"根据传入的的采购订单及项次从EKPO取RETPO,若RETPO = ‘X’,则给161;其他情况给101
<ls_itab>-move_type = '161'."移动类型
ELSE.
"采购订单
<ls_itab>-move_type = '101'."移动类型
"若采购订单及项次不是退货采购订单,"则传入的来料单(INCOM)及来料单项次ITEM不为空,否则提示“非采购退货单,来料单及项次不能为空”
IF <ls_itab>-item IS INITIAL OR ih-incom IS INITIAL .
<ls_itab>-type = 'E'.
<ls_itab>-message = '及来料单以及项次ITEM不为空'.
CONTINUE.
ENDIF.
ENDIF.
ELSE.
<ls_itab>-type = 'E'.
<ls_itab>-message = '采购订单以及行项目不存在'.
CONTINUE.
ENDIF.
"若库存地点为空,则返回错误消息“库存地点不能为空;
IF <ls_itab>-stge_loc IS INITIAL.
<ls_itab>-type = 'E'.
<ls_itab>-message = '库存地点不能为空'.
CONTINUE.
ENDIF.
"根据传入的采购订单号及项次从EKPO取 工厂(EKPO-WERKS);根据传入的库存地点及刚取到的PO 工厂判断在table T001l 是否存在,不存在返回错误消息“库存地XXX在工厂YYY 不存”
READ TABLE lt_ekpo INTO DATA(ls_ekpo_werks) WITH KEY ebeln = <ls_itab>-po_number ebelp = <ls_itab>-po_item.
IF sy-subrc = 0.
READ TABLE lt_t001l INTO DATA(ls_t001l) WITH KEY werks = ls_ekpo_werks-werks lgort = <ls_itab>-stge_loc.
IF sy-subrc <> 0.
<ls_itab>-type = 'E'.
<ls_itab>-message = '库存地'&& <ls_itab>-stge_loc &&'在工厂'&& ls_ekpo_werks-werks &&'不存'.
CONTINUE.
ENDIF.
ELSE.
<ls_itab>-type = 'E'.
<ls_itab>-message = '传入的采购订单和项次在采购订单表 ekpo中,不存在'.
CONTINUE.
ENDIF.
"若未税价格(NETPR)为空或价格基数PEINH为空或购买税代码(MWSKZ)空,则返回错误消息“未税价格,价格基数及税代码都不能为空”;
IF <ls_itab>-netpr IS INITIAL OR <ls_itab>-peinh IS INITIAL OR <ls_itab>-mwskz IS INITIAL.
<ls_itab>-type = 'E'.
<ls_itab>-message = '未税价格,价格基数及税代码都不能为空'.
CONTINUE.
ENDIF.
"若传入的来料单(INCOM)及来料单项次ITEM不为空,则根据来料单、项次 及ZTMM_INCOM_LIST-LOEKZ 等于空,判断来料单及项次在ztable ZTMM_INCOM_LIST 是否存在,不存在,则返回错误消息“来料单XXXXX 项次YY在SAP 不存在”
READ TABLE lt_incom_list INTO DATA(ls_incom_list) WITH KEY incom = ih-incom item = <ls_itab>-item.
IF sy-subrc <> 0.
<ls_itab>-type = 'E'.
<ls_itab>-message = '传入的来料单号已经项次在 ZTMM_INCOM_LIST表中不存在'.
CONTINUE.
ELSE.
"根据传入的来料单(INCOM)及来料单项次ITEM 及ZTMM_INCOM_LIST-LOEKZ 等于空条件,
"从table ZTMM_INCOM_LIST取采购订单号(EBELN)及项次(EBELP),如果取到的采购订单号及项次与接口传入的订单号及项次不一致,
"则返回错误消息“来料单XXXXX 项次YY 对应的采购订单号及项次不匹配”;
IF ls_incom_list-ebeln = <ls_itab>-po_number AND ls_incom_list-ebelp = <ls_itab>-po_item.
"相等通过
ELSE.
"不相等
<ls_itab>-type = 'E'.
<ls_itab>-message = '来料单'&& ih-incom &&'项次' && <ls_itab>-po_item && '对应的采购订单号及项次不匹配'.
CONTINUE.
ENDIF.
ENDIF.
"若传入的来料单(INCOM)及来料单项次ITEM不为空,根据传入的来料单(INCOM)及来料单项次和上述新建ztable Zpo_gr
"能取到不为空的物料凭证(MBLNR),则提示“来料单XXX 项次之前已经入库”的错误消息
READ TABLE lt_ztmm_140 INTO DATA(ls_ztmm_140) WITH KEY incom = ih-incom item = <ls_itab>-item.
IF sy-subrc = 0.
IF ls_ztmm_140-mblnr IS NOT INITIAL.
<ls_itab>-type = 'E'.
<ls_itab>-message = '来料单'&& ih-incom &&'项次'&& <ls_itab>-item &&'之前已经入库'.
CONTINUE.
ENDIF.
ENDIF.
ENDLOOP.
"3.==========对检查通过的记录进行业务处理
LOOP AT itab[] ASSIGNING FIELD-SYMBOL(<ls_tb>) WHERE type <> 'E' .
IF <ls_tb>-retpo = 'X'. "退货订单,直接过账
ELSE. "非采购订单,更改PO价格在过账
ls_poitem-po_item = <ls_tb>-po_item." 行项目
ls_poitemx-po_item = <ls_tb>-po_item." 行项目
ls_poitemx-po_itemx = 'X'." 行项目
ls_poitem-net_price = <ls_tb>-netpr ."净价
ls_poitemx-net_price = 'X'.
ls_poitem-price_unit = <ls_tb>-peinh."价格单位
ls_poitemx-price_unit = 'X'.
ls_poitem-tax_code = <ls_tb>-mwskz."税码
ls_poitemx-tax_code = 'X'.
APPEND ls_poitem TO lt_poitem.
APPEND ls_poitemx TO lt_poitemx.
CALL FUNCTION 'BAPI_PO_CHANGE'
EXPORTING
purchaseorder = <ls_tb>-po_number
TABLES
return = lt_return
poitem = lt_poitem
poitemx = lt_poitemx.
IF sy-subrc = 0.
READ TABLE lt_return INTO ls_return INDEX 1.
IF sy-subrc = 0.
<ls_tb>-type = ls_return-type.
<ls_tb>-id = ls_return-id.
<ls_tb>-number = ls_return-number.
<ls_tb>-message = ls_return-message.
<ls_tb>-log_no = ls_return-log_no.
<ls_tb>-log_msg_no = ls_return-log_msg_no.
<ls_tb>-message_v1 = ls_return-message_v1.
<ls_tb>-message_v2 = ls_return-message_v2.
<ls_tb>-message_v3 = ls_return-message_v3.
<ls_tb>-message_v4 = ls_return-message_v4.
<ls_tb>-parameter = ls_return-parameter.
<ls_tb>-row = ls_return-row.
<ls_tb>-field = ls_return-field.
<ls_tb>-system = ls_return-system.
ENDIF.
CLEAR ls_return.
IF lines( lt_return ) > 1.
lv_message_str = ''.
LOOP AT lt_return INTO ls_return.
lv_message_str = lv_message_str && ls_return-type && ls_return-message && ','.
ENDLOOP.
<ls_tb>-message = lv_message_str.
ENDIF.
IF <ls_tb>-type = 'S'.
CALL FUNCTION 'BAPI_TRANSACTION_COMMIT' "BAPI事务提交
EXPORTING
wait = 'X'.
ELSE.
CONTINUE.
ENDIF.
"20190927 zhongjz add : ---------------------------------------------------------------------------------------------strat
" 根据当前记录的采购订单及行项目从EKPO取最新的价格(EKPO-NETPR)、价格基数(EKPO-PEINH)和税码(EKPO-MWSKZ),
"只要有一个字段的值和对应传入的值不相等,则说明价格更新更新失败,继续下一条,否则继续当前采购订单收货
SELECT ekpo~ebeln FROM ekpo
WHERE ekpo~ebeln = @<ls_tb>-po_number
AND ekpo~ebelp = @<ls_tb>-po_item
AND ekpo~netpr = @<ls_tb>-netpr
AND ekpo~peinh = @<ls_tb>-peinh
AND ekpo~mwskz = @<ls_tb>-mwskz
INTO TABLE @DATA(lt_ekpo_update_success).
IF sy-subrc = 0 AND lt_ekpo_update_success IS NOT INITIAL.
"po价格等更新检查通过
clear lt_ekpo_update_success.
ELSE.
"po价格等更新检查未通过
<ls_tb>-type = 'E'.
<ls_tb>-message = '价格更新失败'.
CONTINUE.
ENDIF.
"20190927 add : ---------------------------------------------------------------------------------------------end.
ELSE.
<ls_tb>-type = 'E'.
<ls_tb>-message = '价格更新失败'.
CLEAR ls_poitem.
REFRESH lt_poitem.
CLEAR ls_poitemx.
REFRESH lt_poitemx.
CONTINUE.
ENDIF.
CLEAR ls_poitem.
REFRESH lt_poitem.
CLEAR ls_poitemx.
REFRESH lt_poitemx.
ENDIF.
"过账。
CLEAR ls_return.
REFRESH lt_return.
ls_gh-pstng_date = ih-pstng_date."过帐日期
ls_gh-doc_date = ih-doc_date."凭证日期
ls_gh-ref_doc_no = ih-incom."参考凭证编号 -来料单号
ls_gh-bill_of_lading = ih-bill_of_lading."收货时提单号
ls_gh-header_txt = ih-header_txt."凭证抬头文本
ls_gi-material_long = <ls_tb>-material."物料编号
ls_gi-plant = <ls_tb>-plant."工厂
ls_gi-stge_loc = <ls_tb>-stge_loc."库存地点
ls_gi-batch = <ls_tb>-batch. "批号
ls_gi-move_type = <ls_tb>-move_type. "移动类型
ls_gi-entry_qnt = <ls_tb>-entry_qnt."以录入项单位表示的数量
CALL FUNCTION 'CONVERSION_EXIT_CUNIT_INPUT'
EXPORTING
input = <ls_tb>-entry_uom
language = sy-langu
IMPORTING
output = <ls_tb>-entry_uom.
ls_gi-entry_uom = <ls_tb>-entry_uom."条目单位
ls_gi-po_number = <ls_tb>-po_number. "采购订单号
ls_gi-po_item = <ls_tb>-po_item. "采购凭证的项目编号
ls_gi-item_text = <ls_tb>-item."项目文本
ls_gi-mvt_ind = 'B'. "移动标识 fix value B 表示采购订单收货
APPEND ls_gi TO lt_gi.
CALL FUNCTION 'BAPI_GOODSMVT_CREATE'
EXPORTING
goodsmvt_header = ls_gh
goodsmvt_code = '01'
IMPORTING
materialdocument = <ls_tb>-materialdocument "物料凭证编号
matdocumentyear = <ls_tb>-matdocumentyear "物料凭证年度
TABLES
goodsmvt_item = lt_gi
return = lt_return.
IF sy-subrc = 0.
IF <ls_tb>-materialdocument IS NOT INITIAL.
<ls_tb>-type = 'S'.
ENDIF.
READ TABLE lt_return INTO ls_return INDEX 1.
IF sy-subrc = 0.
<ls_tb>-type = ls_return-type.
<ls_tb>-id = ls_return-id.
<ls_tb>-number = ls_return-number.
<ls_tb>-message = ls_return-message.
<ls_tb>-log_no = ls_return-log_no.
<ls_tb>-log_msg_no = ls_return-log_msg_no.
<ls_tb>-message_v1 = ls_return-message_v1.
<ls_tb>-message_v2 = ls_return-message_v2.
<ls_tb>-message_v3 = ls_return-message_v3.
<ls_tb>-message_v4 = ls_return-message_v4.
<ls_tb>-parameter = ls_return-parameter.
<ls_tb>-row = ls_return-row.
<ls_tb>-field = ls_return-field.
<ls_tb>-system = ls_return-system.
CLEAR ls_return.
IF lines( lt_return ) > 1.
lv_message_str = ''.
LOOP AT lt_return INTO ls_return.
lv_message_str = lv_message_str && ls_return-type && ls_return-message && ','.
ENDLOOP.
<ls_tb>-message = lv_message_str.
ENDIF.
ENDIF.
IF <ls_tb>-type = 'S'.
CALL FUNCTION 'BAPI_TRANSACTION_COMMIT' "BAPI事务提交
EXPORTING
wait = 'X'.
"20190927 zhongjz update :注释ELSE.CONTINUE. 代码如下:
* ELSE.
* CONTINUE.
ENDIF.
ELSE.
<ls_tb>-type = 'E'.
<ls_tb>-message = '过账失败'.
CLEAR ls_gh.
CLEAR ls_gi.
REFRESH lt_gi.
CLEAR ls_return.
REFRESH lt_return.
CONTINUE.
ENDIF.
CLEAR ls_gh.
CLEAR ls_gi.
REFRESH lt_gi.
CLEAR ls_return.
REFRESH lt_return.
ENDLOOP.
"==============4.对过账成功的记录 记录log到ztmm_140.
DATA ls_tlog TYPE ztmm_140.
DATA lt_tlog TYPE TABLE OF ztmm_140.
DATA lv_date LIKE sy-datum.
DATA lv_time LIKE sy-uzeit.
DATA lv_tzone LIKE tzonref-tzone.
DATA lv_tstamp LIKE tzonref-tstamps.
lv_date = sy-datum.
lv_time = sy-uzeit.
CONVERT DATE lv_date TIME lv_time INTO TIME STAMP lv_tstamp TIME ZONE lv_tzone.
"20190927 zhongjz upate : "不仅仅是成功的记录存入log table, 所有成功或者失败的都需要存入log table"
* LOOP AT itab ASSIGNING FIELD-SYMBOL(<ls_itab_success>) WHERE materialdocument IS NOT INITIAL AND type = 'S'.
LOOP AT itab ASSIGNING FIELD-SYMBOL(<ls_itab_success>).
ls_tlog-z_tiwmstamp = lv_tstamp. "时间搓
ls_tlog-incom = ih-incom."来料单号
ls_tlog-item = <ls_itab_success>-item."项次
ls_tlog-ebeln = <ls_itab_success>-po_number."采购凭证编号
ls_tlog-ebelp = <ls_itab_success>-po_item."采购凭证的项目编号
ls_tlog-budat = ih-pstng_date."凭证中的过账日期
ls_tlog-bldat = ih-doc_date."凭证中的凭证日期
ls_tlog-frbnr = ih-bill_of_lading."收货时提单号
ls_tlog-bktxt = ih-header_txt."凭证抬头文本
ls_tlog-werks = <ls_itab_success>-plant."工厂
ls_tlog-matnr = <ls_itab_success>-material."物料编号
ls_tlog-lgort = <ls_itab_success>-stge_loc."库存地点
ls_tlog-charg = <ls_itab_success>-batch."批号
ls_tlog-erfmg = <ls_itab_success>-entry_qnt."以录入项单位表示的数量
ls_tlog-z_erfme = <ls_itab_success>-entry_uom."条目单位
ls_tlog-netpr = <ls_itab_success>-netpr."净价
ls_tlog-mwskz = <ls_itab_success>-mwskz."销售/购买税代码
ls_tlog-peinh = <ls_itab_success>-peinh."价格单位
ls_tlog-mblnr = <ls_itab_success>-materialdocument."物料凭证编号
ls_tlog-mjahr = <ls_itab_success>-matdocumentyear."物料凭证的年份
ls_tlog-z_type = <ls_itab_success>-type."单字符标记
ls_tlog-z_wmssage = <ls_itab_success>-message."Char255
APPEND ls_tlog TO lt_tlog.
ENDLOOP.
MODIFY ztmm_140 FROM TABLE lt_tlog.
CLEAR ls_tlog.
REFRESH lt_tlog.
* status = 'S'.
* message = ''.
ELSE.
status = 'E'.
message = '行项目不能为空!'.
ENDIF.
*-----------------------------------------------------------------------
zlog_save2 'R'.
ENDFUNCTION.
3. 3 番目のシステム要求 RFC
3.1 手順と主要な概念
SAP システムの RFC (Remote Function Call) リモート機能をサードパーティ システムから要求するには、SAP が提供する RFC SDK (RFC Software Development Kit) を使用するか、SAP Gateway を介して実装する必要があります。いくつかの手順と重要な概念を次に示します。
-
RFC リモート関数: まず、SAP システムで呼び出したい RFC リモート関数が定義されていることを確認してください。RFC は、SAP でのリモート通信と統合を実装する標準的な方法です。呼び出す RFC 関数名と、必要な入力パラメータと出力パラメータを知っておく必要があります。
-
SAP RFC SDK : SAP は、SAP 以外のシステムで SAP システムと通信するためのライブラリとツールのセットである RFC SDK を提供します。サードパーティ システムで使用するには、RFC SDK を入手してインストールする必要があります。
-
RFC 接続構成: サードパーティ システムでは、ホスト名、ポート番号、ログイン資格情報、および SAP システムのその他の情報を含む RFC 接続パラメーターを構成する必要があります。これらのパラメータは、SAP システムへの接続を確立するために使用されます。
-
RFC 呼び出し: RFC SDK または RFC 関連のライブラリ関数を使用して、サードパーティ システムで RFC 呼び出しを実行するコードを作成します。RFC 関数を呼び出すときは、必要な入力パラメータを指定し、返された結果を処理します。
-
エラー処理とログ: RFC を呼び出すときに、呼び出しのステータスを追跡し、潜在的なエラー状態を処理するために、適切なエラー処理とログのメカニズムが実装されていることを確認します。
-
セキュリティ: RFC 呼び出し中のデータ セキュリティを確保し、適切な認証および認可メカニズムを使用して RFC リクエストの正当性を検証します。
-
テストと監視: 実際のアプリケーションで RFC 呼び出しを使用する前に、適切なテストを実行して、RFC 呼び出しが適切に機能することを確認します。RFC 呼び出しを監視して、運用環境での信頼性とパフォーマンスを確認します。
-
SAP ゲートウェイ: もう 1 つのアプローチは、SAP ゲートウェイを使用して RFC リモート機能の RESTful サービスを提供することです。SAP ゲートウェイを使用すると、HTTP または HTTPS プロトコル経由で外部システムに RFC 機能を提供できます。これには、SAP システムでの構成と開発作業が必要ですが、より柔軟な方法で SAP システムと統合できます。