0 需求背景
目前门店发货小票上面的联系方式为负责人的联系电话,业务需求更新为店铺座机电话,同时不影响仓库的发货单上面的联系负责人手机信息。
1 问题描述
先找到程序用于抓取 MDC 的门店数据,然后在 SAP 中创建对应的 BP,目前抓取的数据能维护上电话这个字段。在 MDC JSON 这个字段为 phone, 在 SAP 为 TEL_NUMBER,这个是能成功获取。
但是 MDC 新增了一个字段用于维护移动电话,命名为 storeHeadPhone,想要把这个字段维护到 SAP 的移动电话字段 MOB_NUMBER。首先想到的问题就是接口新增了字段没有做对应的映射关系,进入程序定位问题。
2 分析 ZQMMMR012 程序
第一步:新增移动电话字段映射关系
发现这段程序为了做通用一点,的确做了一个配置表 ZTQMMD001B (MDC接口字段映射配置表)
REFRESH: gt_namemapping.
SELECT *
FROM ztqmmd001b
WHERE syntype = @gv_synobject
ORDER BY mdcview,syntype,mdcfield
INTO CORRESPONDING FIELDS OF TABLE @gt_namemapping.
然后进去这个表里进行查询 PHONE 字段是怎么维护的:
结果如下,可以看到这个字段属于 SHOP 视图,然后的确是将 MDC 字段名 PHONE 对应到 SAP 字段名称 TEL_NUMBER :
因此,我们在这个表中搜一下 SAP 字段名称 MOB_NUMBER,发现确实没有这个字段,因此参考 PHONE 字段,进行数据新增:
编辑成功后,进行保存:
第二步:数据接收和处理逻辑
新增了字段映射发现程序没有报错,但是移动电话还是没有成功在 BP 界面维护进去。
此时,再到程序中进行 Debug,发现这段程序的 ls_json_out 是能成功接收到数据的:
双击进去 FORM frm_convert_data 程序,发现里面对 mdc_json 字段有段逻辑进行了处理。
所以就点进去 FORM frm_fill_data :
FORM frm_fill_data USING ps_data
CHANGING c_sap_field TYPE ty_store
c_rfc_field TYPE zsup_md
c_rfc_field2 TYPE zsup_mdx.
DATA:descr_ref TYPE REF TO cl_abap_structdescr,
lv_field TYPE string,
lv_value TYPE string.
descr_ref ?= cl_abap_typedescr=>describe_by_data( p_data = ps_data ).
LOOP AT descr_ref->components ASSIGNING FIELD-SYMBOL(<fs_component>) .
ASSIGN COMPONENT <fs_component>-name OF STRUCTURE ps_data TO FIELD-SYMBOL(<fs_value>).
READ TABLE gt_namemapping INTO DATA(ls_namemapping) WITH KEY
mdcfield = <fs_component>-name
BINARY SEARCH.
IF sy-subrc = 0.
IF ls_namemapping-sapfield IS NOT INITIAL.
ASSIGN COMPONENT ls_namemapping-sapfield OF STRUCTURE c_sap_field TO FIELD-SYMBOL(<fs_sapvalue>).
IF <fs_value> IS ASSIGNED AND <fs_sapvalue> IS ASSIGNED.
<fs_sapvalue> = <fs_value>.
* ** 这边需要转换
IF ls_namemapping-zconvert IS NOT INITIAL AND <fs_value> IS NOT INITIAL.
<fs_sapvalue> = zcl_abap_common_convert=>convert( iv_in = <fs_value> iv_convert = ls_namemapping-zconvert ).
ENDIF.
* 去补0
CASE ls_namemapping-zconvert.
WHEN 'ALIN'.
CALL METHOD zcl_common=>alpha_input
CHANGING
pcv_dat = <fs_sapvalue>.
WHEN 'ALOU'.
CALL METHOD zcl_common=>alpha_output
CHANGING
pcv_dat = <fs_sapvalue>.
ENDCASE.
"rfc字段
IF ls_namemapping-rfcfield IS NOT INITIAL.
ASSIGN COMPONENT ls_namemapping-rfcfield OF STRUCTURE c_rfc_field TO FIELD-SYMBOL(<fs_rfcvalue>).
CLEAR lv_field.
lv_field = ls_namemapping-rfcfield && '_CON'.
ASSIGN COMPONENT lv_field OF STRUCTURE c_rfc_field2 TO FIELD-SYMBOL(<fs_rfcvalue2>).
IF <fs_sapvalue> IS NOT INITIAL.
IF <fs_rfcvalue> IS ASSIGNED.
<fs_rfcvalue> = <fs_sapvalue>.
ENDIF.
IF <fs_rfcvalue2> IS ASSIGNED.
<fs_rfcvalue2> = abap_true.
ENDIF.
ENDIF.
ENDIF.
ENDIF.
IF ls_namemapping-reqfield IS NOT INITIAL .
IF <fs_value> IS ASSIGNED.
IF <fs_value> IS INITIAL.
MESSAGE e015 WITH '' ls_namemapping-sapfield INTO gv_message.
go_message->set_return( ).
ENDIF.
ELSE.
MESSAGE e015 WITH '' ls_namemapping-sapfield INTO gv_message.
go_message->set_return( ).
ENDIF.
ENDIF.
ENDIF.
ENDIF.
UNASSIGN: <fs_value>,<fs_sapvalue>.
ENDLOOP.
ENDFORM.
发现此处确实是通过动态获取组件属性来赋值的。既然已经看到 JSON 数据接收过来了,这里的 gt_namemapping 也有对应的值了。但是还是没有看到 mob_number 封装到结构中,发现虽然映射关系是有配置表,但这里的 ty_store 是单独定义的,如下,往其中添加 mob_number 字段:
而门店通用结构 ZSUP_MD 是有移动电话的缩写的:
修改确认 ZSUP_MDX 结构中也有对应的修改待确认组件 YDDH_CON,因此这两处都不需要变动:
思考一
因此,这里有了一个思考:既然有了配置表,此处的代码是否也应该将 ty_store 类型的数据封装为门店创建 BP 需要的通用表,这样今后任何时候需要新增字段,也不需要改对应的代码,只需要维护整个通用结构存在的配置表即可。
执行 BAPI 有报错:“将用途 AD_NMBDEFA 分配到通讯类型 TEL 不是唯一”
经过前面两步之后,电话和移动电话数据都能封装到结构当中:
但是跑完整个程序有报错:将用途 AD NMBDEFA 分配到通讯类型 TEL 不是唯一
因此,继续研究代码,到 FORM PERFORM frm_call_rfc. 当中去看程序是怎么调用的 BAPI,发现前面的开发已经将 BAPI 封装为自定义的函数,估计这样处理是方便保存调用日志:
CALL FUNCTION 'Z_IF_SD112'
EXPORTING
it_itab1 = gt_rfc_field
it_itab2 = gt_rfc_field2
it_itab = it_itab
is_stru = is_stru
* iv_modflag = 'X'
IMPORTING
es_msg = es_msg
es_stru = es_stru
et_itab = et_itab
et_itab1 = et_itab1.
3 研究函数模块 Z_IF_SD112
将程序继续执行,进入这段代码之后,可以着重看跟 BAPI 相关的代码,发现是如下的包含文件:
在这段包含文件 ZIF_SD112_BAPI 当中,可以看到门店的通用上传结构 ZSUP_SITE 是有移动电话的字段的:
思考二
这里引发了我的第二个思考:好的程序讲究低耦合。在这里显然程序是为了满足业务需求打包到了一块,我估计是门店数据创建需要同步创建 BP(修改也是如此),但是从长期维护来看,真不应该将创建和修改门店数据和创建和修改 BP 进行代码如此高度耦合。
在这段代码里,就分别有电话和移动电话的赋值,因此这里的代码无需更改:
这里的代码无需修改之后,有发现程序会进入 Z_BC_MODIFY_SITE 函数模块,部分代码如下:
CALL FUNCTION 'Z_BC_MODIFY_SITE'
EXPORTING
iv_partner = ls_site-partner
iv_zrefbp = ls_site-zrefbp
is_site = ls_site
it_cust_knvv = lt_cust_knvv
it_cust_knb1 = lt_cust_knb1
it_cust_knvi = lt_cust_knvi
it_cust_adrc = lt_cust_adrc
it_vend_lfm1 = lt_vend_lfm1
it_vend_lfb1 = lt_vend_lfb1
iv_commit_bp = abap_true
iv_commit = abap_true
IMPORTING
ev_error = lv_error
ev_site_bp = ev_site_bp
TABLES
et_return = et_return.
LOOP AT et_return ASSIGNING FIELD-SYMBOL(<fs_return_site1>) WHERE type CA 'AEX'.
es_itab1-bpnum = <fs_itab1_bapi>-ywhb.
es_itab1-type = 'E'.
es_itab1-message = <fs_return_site1>-message.
APPEND es_itab1 TO et_itab1.
EXIT.
ENDLOOP.
4 研究函数模块 Z_BC_MODIFY_SITE
代码如下:
FUNCTION z_bc_modify_site.
*"----------------------------------------------------------------------
*"*"本地接口:
*" IMPORTING
*" VALUE(IV_PARTNER) TYPE BU_PARTNER OPTIONAL
*" VALUE(IV_ZREFBP) TYPE ZE_REFBP OPTIONAL
*" VALUE(IS_SITE) TYPE ZSUP_SITE OPTIONAL
*" VALUE(IT_CUST_KNVV) TYPE ZSUP_CUST_KNVV_T OPTIONAL
*" VALUE(IT_CUST_KNB1) TYPE ZSUP_CUST_KNB1_T OPTIONAL
*" VALUE(IT_CUST_KNVI) TYPE ZSUP_CUST_KNVI_T OPTIONAL
*" VALUE(IT_CUST_ADRC) TYPE ZSUP_CUST_ADRC_T OPTIONAL
*" VALUE(IT_VEND_LFM1) TYPE ZSUP_VEND_LFM1_T OPTIONAL
*" VALUE(IT_VEND_LFB1) TYPE ZSUP_VEND_LFB1_T OPTIONAL
*" VALUE(IV_COMMIT_BP) TYPE FLAG OPTIONAL
*" VALUE(IV_COMMIT) TYPE FLAG OPTIONAL
*" EXPORTING
*" REFERENCE(EV_ERROR) TYPE FLAG
*" REFERENCE(EV_SITE_BP) TYPE BU_PARTNER
*" TABLES
*" ET_RETURN STRUCTURE BAPIRET2
*"----------------------------------------------------------------------
DATA lv_partner TYPE bu_partner.
DATA lv_zrefbp TYPE ze_refbp.
* 判断客户是否存在
CLEAR:lv_partner,lv_zrefbp,gv_op_type.
REFRESH:et_return[].
lv_partner = iv_partner. "业务伙伴编号
lv_zrefbp = iv_zrefbp.
gv_op_type = gc_op_site.
* 检查业务伙伴编号
PERFORM frm_check_site_bp TABLES et_return
USING is_site
CHANGING lv_partner
lv_zrefbp.
CHECK et_return[] IS INITIAL.
SELECT COUNT(*)
FROM but000
WHERE partner EQ lv_partner.
IF sy-subrc <> 0.
* 创建
CALL FUNCTION 'Z_BC_CREATE_SITE'
EXPORTING
iv_partner = lv_partner
is_site = is_site
it_cust_knvv = it_cust_knvv
it_cust_knb1 = it_cust_knb1
it_cust_knvi = it_cust_knvi
it_cust_adrc = it_cust_adrc
it_vend_lfm1 = it_vend_lfm1
it_vend_lfb1 = it_vend_lfb1
iv_commit_bp = iv_commit_bp
iv_commit = iv_commit
IMPORTING
ev_error = ev_error
ev_site_bp = ev_site_bp
TABLES
et_return = et_return.
ELSE.
* 修改
CALL FUNCTION 'Z_BC_CHANGE_SITE'
EXPORTING
iv_partner = lv_partner
is_site = is_site
it_cust_knvv = it_cust_knvv
it_cust_knb1 = it_cust_knb1
it_cust_knvi = it_cust_knvi
it_cust_adrc = it_cust_adrc
it_vend_lfm1 = it_vend_lfm1
it_vend_lfb1 = it_vend_lfb1
iv_commit_bp = iv_commit_bp
iv_commit = iv_commit
IMPORTING
ev_error = ev_error
TABLES
et_return = et_return.
IF ev_error IS INITIAL .
ev_site_bp = lv_partner.
ENDIF.
ENDIF.
ENDFUNCTION.
然后发现真正的将接口字段赋值给 BAPI 需要的结构是分别在 Z_BC_CREATE_SITE 和 Z_BC_CHANGE_SITE 写的,然后又转而研究这两个函数模块。
5 研究 Z_BC_CHANGE_SITE 函数模块
核心调用的 BAPI 是 CL_MD_BP_MAINTAIN=>MAINTAIN ,如下:
CALL METHOD CL_MD_BP_MAINTAIN=>MAINTAIN
EXPORTING
I_DATA = lt_data
* I_TEST_RUN =
IMPORTING
E_RETURN = lt_return
.
READ TABLE lt_return INTO ls_return INDEX 1.
lt_omsg[] = ls_return-object_msg.
LOOP AT lt_omsg INTO ls_omsg WHERE type CA 'AEX'.
EXIT.
ENDLOOP.
IF sy-subrc EQ 0.
ev_error = abap_true.
CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'.
ELSE.
IF iv_commit EQ abap_true.
CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'
EXPORTING
wait = 'X'.
ENDIF.
ENDIF.
* 返回消息
LOOP AT lt_omsg INTO ls_omsg .
CLEAR ls_return2.
MOVE-CORRESPONDING ls_omsg TO ls_return2.
APPEND ls_return2 TO et_return.
ENDLOOP.
找到对应的填充移动电话 FORM frm_fill_bp_cust_address:
发现这里面关于电话和移动电话的逻辑已经写好了,但是有一个注释特别醒目,刚好就是刚刚的 BAPI 报错的场景。
意味着,在这段逻辑当中,针对新建的时候,如果同时维护了电话和移动电话是 OK 的,但是如果一开始只维护了固定电话,后期修改的操作新增了移动电话 BAPI 就会报错。
思考三
第三个思考:如果针对是新业务场景必须都要求的这两个字段,第一种方法就是在前台设置电话和移动电话必填,让创建的时候同时创建上,不需要后期来补维护。
看这样子又是一个历史遗留问题,要求用户必填涉及的部门和组太多,那今天的需求不就是要解决这个业务场景为什么会出现 BAPI 报错。
6 问题解决
那么就只能搜索相应的错误信息,查找合适的解决方案了。
首先就是直接搜 ABAP AM605,发现网上也有人出现过类似问题,然后有两个 Note 与此相关:
- 2798715 - Assignment of usage * to communication type * not unique - AM605
- 3515088 - AM605 error occurs when CL_MD_BP_MAINTAIN is called
2798715 - 用途 * 到通信类型 * 的分配不唯一 - AM605
先用的第一个 notes,首先看我们的测试环境并没有超过 ADR2 号码表格超过 999 个信息,然后执行 Z_OSSNOTE_1070798_NEW 程序:
我猜想这个程序大概就是随着修改次数的增加,后台表的地址的编号逐步增多,但是实际上很多都已经失效了,需要进行重新编号。
我在测试环境运行这段代码发现也还是报错,说明不是这个问题。
3515088 - 调用 CL_MD_BP_MAINTAIN 时出现 AM605 错误
然后看第二个 notes,解决方案为,当 CL_MD_BP_MAINTAIN 调用时,通讯 communication details 在当前状态必须设置为 X:
I_DATA-PARTNER-CENTRAL_DATA-ADDRESS-ADDRESSES[1]-DATA-COMMUNICATION-PHONE-CURRENT_STATE = 'X'.
因此,将我们的代码中,增加如下语句:
BAPI返回成功,门店主数据接口成功返回。
7 总结
自此,满足业务需求,也将历史遗留问题解决,希望今后不要出现其他错误。但是你会发现,一个简单的需求变化,需要层层剥笋才能发现并解决问题,更证明了好的软件需要设计,也最好用开发文档去把开发的细节写出来,方便他人后期维护。
虽然过程繁琐,但的确也在这一段排查问题的过程中,思考并学习今后如何在自己的代码:在实现业务的时候,如何进行有效的封装和解耦。