门店接口同步创建 BP 新增移动电话需求

346 阅读10分钟

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_SITEZ_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 总结

自此,满足业务需求,也将历史遗留问题解决,希望今后不要出现其他错误。但是你会发现,一个简单的需求变化,需要层层剥笋才能发现并解决问题,更证明了好的软件需要设计,也最好用开发文档去把开发的细节写出来,方便他人后期维护。

虽然过程繁琐,但的确也在这一段排查问题的过程中,思考并学习今后如何在自己的代码:在实现业务的时候,如何进行有效的封装和解耦。