声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将追究法律责任!原文链接:http://www.cnblogs.com/jiangzhengjun/p/4291296.html

第三代:基于类的增强(BADI

BADI新方式实现

1-构建BADI

1,SAP BADI的由来

大家都知道SAP在ERP行业中,应用最广的是财务领域。由于各个国家财务制度以及税务制度的差异,SAP希望在自己的程序开发平台中引入BADI,能够让开发人员自己编写业务插件,系统会自动调用这些插件程序来完成某种业务运算。本文中的举例是计算不同国家的税率。

2,创建一个Enhancement Spot

Enhancement Spot是作为一个BADI的容器在这个容器里面,我们可以定义自己的多个BADI

  • 在TCode SE18中

3,定义一个BADI

4,定义BADI接口

接下来我们需要一个接口来定义这个BADI所需要用的方法

  • 双击接口,此时可以选择或者输入一个新的接口名

  • 为接口Z_IF_CALC_VAT创建一个方法get_vat,并设置参数

至此,我们已经建立了一个enhancement spot而且带有一个BADI和一个接口。仅仅如此是不能使用这个BADI的,我们需要一个BADI实例来在程序中被调用。

5,现在我们写一小段程序来调用这个BADI方法get_vat,系统有两个关键字用来得到BADI实例和调用BADI,分别是GET BADI和CALL BADI(也可直接调用接口与实现类,请参考前面实例最后部分:直接调用BAPI接口与类)

.

GET BADI handle.

CALL BADI handle->get_vat
  EXPORTING
    im_amount      = sum
  IMPORTING
    ex_amount_vat  = vat
    ex_percent_vat = percent.

WRITE: 'percentage:', percent, 'VAT:', vat.

由于还没有实现,所以编译出错

2-实现BADI

一个Enhancement Spot可以定义多个BADI,每个BADI又是由一个接口与多个实例类组成的。Enhancement Spot相当于容器概念,用来存储多个BADI,而每一个BADI必须定义一个接口,该接口可以有一个或多个实现类,BADI实质上就是将接口与实现类组织(打包、捆绑)在一起了,而BADI本身又可以代表接口的概念(因为一个BADI只有一个接口)。

1,建立BADI增强实现容器

由于一个BADI的实现可以有多个类,这些多个实现类需要组织(打包、捆绑)在一起(与多个BADI放在一个Enhancement Spot容器中是一个概念),所以需要先创建一个新的BADI增强实现容器,如图:

2,BADI类实现

紧接着要求输入BADI实现名及实现类名:

当保存后,会自动跳转到 BADI的增强实现界面(因为一个BADI的实现类可以有多个,所以新开一个界面来专门来进行BADI的实现过程):

一个增强实现(Enhancement Implementation)可以有多个BADI Implementations(相当于多个版本),但起作用的同时只能有一个,有多个版本时需要进行设置:

两个实现版本类所现实接口GET_VAT方法如下:

上面虽然创建了两个BADI Implementation(Z_BADI_CALC_IMPL、Z_BADI_CALC_IMPL2),或者说两个实现类(Z_CL_CALC_IMPL、Z_CL_CALC_IMPL2),但这些都是属于同一个Enhancement Implementation增强实现(Z_BADI_CALC_IMPL_C)的,到目前此,对于BAdI Definition(BADI 定义)Z_BADI_CALC_VAT来说,只有一个Enhancement Implementation(增强实现)Z_BADI_CALC_IMPL_C,而一个Enhancement Implementation(增强实现)里虽然创建了两个两个实现类(Z_CL_CALC_IMPL、Z_CL_CALC_IMPL2),但同时只有一个起作用,所以目前最终只有一个BadI Implementation,如果想要达到像Java中多态的话,需要创建多个不同的Enhancement Implementation增强实现,BADI中的多态就是通过不同的Enhancement Implementation增强实现来实现的。

现在我们还可以创建第二个增强实现,如下面:

紧接着创建BADI 实例及对应的实例类:

再实现GET_VAT方法:

此时如果激活方法时,会出错,原因就是目前面有两个BADI的实现Z_BADI_CALC_IMPL_C、Z_BADI_CALC_IMPL_C2,所以需要把其中一个的Implementation is active前的钩去掉才能被激活:

当有多个BADI实现时,需要增加过滤器来选择使用哪个实现(类)

3-使用BADI过滤器

比如Z_CL_CALC_VAT_GB,但是当运行程序时,系统会dump,这是因为我们定义BADI时,是采用了默认的单一使用(single-use),没有选中复合使用选项(Multiple Use Option),单一使用的限制是只能有一个实现类。如何解决这个问题,请看本系列的最后一篇文章,如何使用过滤器。

注意:上面过滤值一定要大写,否则运行时匹配不到。

使用下面测试程序进行测试:

.

GET BADI handle
  FILTERS"SE18中定义的过滤器名作为这里的参数名
    filter1 = 'C'.

CALL BADI handle->get_vat
  EXPORTING
    im_amount      = sum
  IMPORTING
    ex_amount_vat  = vat
    ex_percent_vat = percent.

WRITE: / 'percentage:', percent, 'VAT:' ,vat.

4-多个实现时究竟调谁

在同一Enhancement Implementation中(如下图中的Z_BADI_CALC_IMPL_C),不同的BADI Implementations(Z_BADI_CALC_IMPL、Z_BADI_CALC_IMPL2)之间究竟选谁的问题,是由 Default Implementation、Implementation is active选项共同来决定的,且在同一时间内只能有一个BADI Implementations能被激活调用,所以要通过这两个选项来控制究竟谁被用来当作当前实现被使用,是否被使用也可通过图中的 Runtime Behavior说明文字来查看:

不同的Enhancement Implementation之间(Z_BADI_CALC_IMPL、Z_BADI_CALC_IMPL2)调用谁,则是由过滤器来决定的:

但前提是该实现要被激活:

查找系统中的BADI

在SAP源码中,BADI增强都是通过方法CL_EXITHANDLER=>GET_INSTANCE来调用的,所以可以在主程序代码中查找“CL_EXITHANDLER=>GET_INSTANCE”这样的字符串,如查找到的:

CALL METHOD CL_EXITHANDLER=>GET_INSTANCE
      exporting                                             " \TP 563352
          exit_name              = 'CUSTOMER_ADD_DATA'      " \TP 563352
          null_instance_accepted = 'X'                      " \TP 563352
      CHANGING
          INSTANCE = G_ADDITIONAL_DATA.

其中exit_name参数指定的值就是 BADI对象名,然后再通过SE18来查看这个BADI对象,则可以看到其接口与实现类

另外,由于SAP在开发时习惯将相关的东西放在同一包中,所以可以根据主程序所在的开发包在SE80中来查找相应的BADI

BADI详细说明文档

示例:通过BADI实现采购订单屏幕增强

主要用到两个BADI: ME_GUI_PO_CUST和ME_PROCESS_PO_CUST

这两个BADI都是有例子的, 可以在se18那里输入BADI名进入后,按GoTo->Sample code->Display来查看, 也可以直接在SE24查看实例类CL_EXM_IM_ME_GUI_PO_CUST和CL_EXM_IM_ME_PROCESS_PO_CUST,实例类代码中有很详细的注释:

现在我们对PO header加上自己的subscreen, SAP的例子提供的是对item增加subscreen

需求说明

最后做出的效果图:

本示例对标准表的扩展方法使用的是 SMOD中对其预留的扩展结构CI_EKKODB、CI_EKPODB来做的,该方法使用的是Include对标准表进行扩展,但用户自己不能直接对标准表采用Include方式来对其扩展(而IncludeCI_EKKODB、CI_EKPODB又可以是因为这两个结构是系统预留好的扩展结构),本来想通过Append Stucture来对EKKO或EKPO进行扩展的,但最后经过测试,经过Append Stucture方式扩展EKPO 后,数据读取存储都正常,但EKKO死也不行,无奈之下,放弃了Append Stucture方式扩展标准表,而是采用了对系统预留的标准扩展结构CI_EKKODB 、CI_EKPODB修改来完成,这两个预留结构可以通过SMOD来查看MM06E005增强点得到,具体请参考前面示例

而另一种扩展方式就是自创建一张表,此种方式的数据在屏幕与数据库之间的传递比起直接对标准表字段进行扩充,实现起来困难许多,但因不影响标准表,所以不失为好的扩展方法。这里只为EKPO创建了自定义表,我想EKKO是一样的,这里就不再对EKKO进行自定义扩展了

Step 1: 标准表EKKO、EKPO结构扩展

本示例的表扩展分为两种,一种就是直接扩展标准表,第二种就是自已创建一个自定义数据库。这里就是介绍怎样直接扩展标准表。

激活后,发现EKKO与EKPO标准表都Include这两个结构了:

这里使用到的CI_EKKODB以及CI_EKPODB可能刚开始不存在,它们分别为SAP提供的用来扩展标准表EKKO、EKPO结构的增强结构,为SAP所预留,这两个预留结构的创建需通过SMOD来操作(直接通过SE11双击表结构里以 CI_ 打头的 .INCLUDE 也可创建或修改),具体还可以参考SMOD采购订单屏幕增强章节示例

Step 2: 创建自定义表

上一步就已说明,本示例中的另一种表扩展就是创建自己的表,而不是直接对EKKO、EKPO标准表进结构修改。创建的自己定义表如下:

Step 3: Create Function Group

从MEPOBADIEX函数拷贝出新的函数组Z_PO_SUBSCREEN_GRP,MEPOBADIEX为BADI  ME_GUI_PO_CUST的实现示例所用到的函数组,这可以从ME_GUI_PO_CUST实现类CL_EXM_IM_ME_GUI_PO_CUST的SUBSCRIBE方法示例代码中查找出所使用到的示例函数组为MEPOBADIEX(该函数组已搭好了架子,包含了subroutine、Function等,所以需要从此拷贝,拷贝后修改修改即可使用):

功能函数

标准表扩展所涉及的函数

下面这些函数都是用在标准扩展方式(即通过CI_EKKODB、CI_EKPODB结构对表EKKO、EKPO进行的扩展)下

Z_PO_SUBSCREEN_GRP_POP_HEAD

FUNCTION Z_PO_SUBSCREEN_GRP_POP_HEAD .
*"----------------------------------------------------------------------
*"*"Local Interface:
*"  EXPORTING
*"     REFERENCE(EX_DYNP_DATA) TYPE  CI_EKKODB
*"----------------------------------------------------------------------
* get dynpro data 将屏幕上的数据读取到BADI内存中
  "ci_ekKodb已与Head增强子屏幕绑定,所以这里实质上是将屏幕中的
  "数据读取到BADI ME_GUI_PO_CUST实现类ZCL_IM__JZJ_BADI_IMPL_PO
  "的私有属性dynp_data_pai_head里。该函数在PAI事件后调用
  ex_dynp_data = ci_ekkodb.
ENDFUNCTION.

Z_PO_SUBSCREEN_GRP_POP_ITEM

FUNCTION Z_PO_SUBSCREEN_GRP_POP_ITEM .
*"----------------------------------------------------------------------
*"*"Local Interface:
*"  EXPORTING
*"     REFERENCE(EX_DYNP_DATA) TYPE  CI_EKPODB
*"----------------------------------------------------------------------
* get dynpro data 将屏幕上的数据读取到BADI内存中
  "ci_ekpodb已与Item增强子屏幕绑定,所以这里实质上是将屏幕中的
  "数据读取到BADI ME_GUI_PO_CUST实现类ZCL_IM__JZJ_BADI_IMPL_PO
  "的私有属性dynp_data_pai_item里。该函数在PAI事件后调用
  ex_dynp_data = ci_ekpodb.
ENDFUNCTION.

Z_PO_SUBSCREEN_GRP_PUSH_HEAD

FUNCTION Z_PO_SUBSCREEN_GRP_PUSH_HEAD .
*"----------------------------------------------------------------------
*"*"Local Interface:
*"  IMPORTING
*"     REFERENCE(IM_DYNP_DATA) TYPE  CI_EKKODB
*"----------------------------------------------------------------------
* set dynpro data 将BADI内存中的数据读取到屏幕上,在屏幕PBO前调用
  ci_ekkodb = im_dynp_data.
ENDFUNCTION.

Z_PO_SUBSCREEN_GRP_PUSH_ITEM

FUNCTION Z_PO_SUBSCREEN_GRP_PUSH_ITEM .
*"----------------------------------------------------------------------
*"*"Local Interface:
*"  IMPORTING
*"     REFERENCE(IM_DYNP_DATA) TYPE  CI_EKPODB
*"----------------------------------------------------------------------
* set dynpro data 将BADI内存中的数据读取到屏幕上,在屏幕PBO前调用
  ci_ekpodb = im_dynp_data.
ENDFUNCTION.

自建表扩展所涉及的函数
Z_PO_SUBSCREEN_GRP_POP_ITEM_2

FUNCTION Z_PO_SUBSCREEN_GRP_POP_ITEM_2 .
*"----------------------------------------------------------------------
*"*"Local Interface:
*"  EXPORTING
*"     REFERENCE(EX_DYNP_DATA) TYPE  ZEKPO_DB
*"----------------------------------------------------------------------
  ex_dynp_data = ZEKPO_DB.
ENDFUNCTION.

Z_PO_SUBSCREEN_GRP_PUSH_ITEM_2

FUNCTION Z_PO_SUBSCREEN_GRP_PUSH_ITEM_2 .
*"----------------------------------------------------------------------
*"*"Local Interface:
*"  IMPORTING
*"     REFERENCE(IM_DYNP_DATA) TYPE  ZEKPO_DB
*"----------------------------------------------------------------------
* set dynpro data将BADI内存中的数据读取到屏幕上
  ZEKPO_DB = im_dynp_data.
ENDFUNCTION.

Z_PO_SUBSCREEN_GRP_INIT

FUNCTION Z_PO_SUBSCREEN_GRP_INIT.
*"----------------------------------------------------------------------
*"*"Local Interface:
*"----------------------------------------------------------------------
"初始化时清除持久数据与界面操作数据。这里好像没有必要,虽然这里的gt_persistent_data、 gt_data
"虽然是全局内表,但每次被调用(如打一个Tcode、运行一个报表等 调用此函数组)时,gt_persistent_data、 gt_data
"是不会共用的,也就是说不会在不同的程序会话中共享,它们只在同一运行程序中共享,直到程序结束时,它们
"所占内存才会被释放
  CLEAR: gt_persistent_data[], gt_data[].
ENDFUNCTION.

Z_PO_SUBSCREEN_GRP_OPEN

FUNCTION Z_PO_SUBSCREEN_GRP_OPEN.
*"----------------------------------------------------------------------
*"*"Local Interface:
*"  IMPORTING
*"     REFERENCE(IM_EBELN) TYPE  EBELN
*"----------------------------------------------------------------------
* read customer data from database 根据单号从自定义扩展数据库表读取数据
  CHECK NOT im_ebeln IS INITIAL.
  SELECT * FROM ZEKPO_DB INTO TABLE gt_persistent_data
                                 WHERE ebeln = im_ebeln.
"刚读出来时,将界面数据与持久数据设置成一样
  gt_data = gt_persistent_data.
ENDFUNCTION.

Z_PO_SUBSCREEN_GRP_GET_DATA

FUNCTION Z_PO_SUBSCREEN_GRP_GET_DATA.
*"----------------------------------------------------------------------
*"*"Local Interface:
*"  IMPORTING
*"     REFERENCE(IM_EBELN) TYPE  EBELN
*"     REFERENCE(IM_EBELP) TYPE  EBELP
*"  EXPORTING
*"     VALUE(EX_DATA) TYPE  ZEKPO_DB
*"----------------------------------------------------------------------
  CLEAR ex_data.
  CHECK NOT im_ebelp IS INITIAL.
"从界面操作数据内表中读取
  READ TABLE gt_data INTO ex_data WITH TABLE KEY mandt = sy-mandt
                                                 ebeln = im_ebeln
                                                 ebelp = im_ebelp.
  "如果没有查到,则新增一条后返回
  IF NOT sy-subrc IS INITIAL.
    ex_data-mandt = sy-mandt.
    ex_data-ebeln = im_ebeln.
    ex_data-ebelp = im_ebelp.
    INSERT ex_data INTO TABLE gt_data.
  ENDIF.
ENDFUNCTION.

Z_PO_SUBSCREEN_GRP_SET_DATA

FUNCTION Z_PO_SUBSCREEN_GRP_SET_DATA.
*"----------------------------------------------------------------------
*"*"Local Interface:
*"  IMPORTING
*"     REFERENCE(IM_DATA) TYPE  ZEKPO_DB
*"     REFERENCE(IM_PHYSICAL_DELETE_REQUEST) TYPE  MMPUR_BOOL OPTIONAL
*"----------------------------------------------------------------------
* update customers data
**********该函数就是用于界面操作数据后,同步更新内表 gt_data**************
  DATA: ls_data LIKE LINE OF gt_data.

FIELD-SYMBOLS: <data> LIKE LINE OF gt_data.

CHECK NOT im_data-ebelp IS INITIAL.
"如果是要删除数据操作时
  IF NOT im_physical_delete_request IS INITIAL.
* delete a line from gt_data
    DELETE TABLE gt_data WITH TABLE KEY mandt = sy-mandt
                                        ebeln = im_data-ebeln
                                        ebelp = im_data-ebelp.
  ELSE."否则是更新或新增数据
* update customer data
    READ TABLE gt_data ASSIGNING <data> WITH TABLE KEY
                                        mandt = sy-mandt
                                        ebeln = im_data-ebeln
                                        ebelp = im_data-ebelp.
    IF sy-subrc IS INITIAL."更新数据
* update existing data
      <data>-field1 = im_data-field1.
      <data>-field2 = im_data-field2.
    ELSE."新增数据
* make a new entry into the data table
      ls_data = im_data.
      ls_data-mandt = sy-mandt.
      INSERT ls_data INTO TABLE gt_data.
    ENDIF.
  ENDIF.
ENDFUNCTION.

Z_PO_SUBSCREEN_GRP_POST

FUNCTION Z_PO_SUBSCREEN_GRP_POST.
*"----------------------------------------------------------------------
*"*"Local Interface:
*"  IMPORTING
*"     VALUE(IM_EBELN) TYPE  EBELN
*"----------------------------------------------------------------------
  DATA: ls_data LIKE LINE OF gt_data,
        lt_data_new TYPE STANDARD TABLE OF ZEKPO_DB,
        lt_data_old TYPE STANDARD TABLE OF ZEKPO_DB.
* prepare customers data for posting 数据存储到数据库中前准备
  CHECK NOT im_ebeln IS INITIAL.
  lt_data_new[] = gt_data."当前界面操作后的数据
  lt_data_old[] = gt_persistent_data."界面操作之前的数据
  ls_data-mandt = sy-mandt.
  ls_data-ebeln = im_ebeln.
  "单号为空时需要设置单号
  MODIFY lt_data_new FROM ls_data TRANSPORTING mandt ebeln WHERE ebeln IS initial.
  "提交数据库
  CALL FUNCTION 'Z_PO_SUBSCREEN_GRP_COMMIT' IN UPDATE TASK
    TABLES
      imt_data_new = lt_data_new
      imt_data_old = lt_data_old.
ENDFUNCTION.

Z_PO_SUBSCREEN_GRP_COMMIT

.
      MESSAGE a809(me) WITH 'zekpo_db'.
    ENDIF.
  ENDIF.
ENDFUNCTION.

全局数据定义

FUNCTION-POOL z_po_subscreen_grp.                   "MESSAGE-ID ..

* persistent data 已持久化的数据,即当前数据库中目前所拥有的数据
DATA: gt_persistent_data TYPE SORTED TABLE OF zekpo_db
                         WITH UNIQUE KEY mandt ebeln ebelp,

* actual data 用于存储当前界面操作之后的数据,与数据库中的实际数据有所不同了
      gt_data            TYPE SORTED TABLE OF zekpo_db
                         WITH UNIQUE KEY mandt ebeln ebelp.
* dynpro output structure
TABLES: zekpo_db.
**========上面是自建表所需用到的变量,下面是标准表扩展所需用到的变量========**
* dynpro output structure
TABLES: ci_ekkodb,ci_ekpodb.
* definitions required for dynpro/framework integration

DATA: ok-code TYPE sy-ucomm.
INCLUDE lmeviewsf01.

LMEVIEWSF01

此Include没有修改过,目前只用到event_pbo 与event_pai两个Module

.
END-ENHANCEMENT-SECTION.

ENDMODULE.                 " VAL_REQ_VORNR  INPUT
*<<< OLC Project

子屏幕设计

注:这此屏幕的属性都要设置成子屏幕

注:所有的屏幕都需要调用event_pbo 与event_pai两个Module: 如果不调用这两个module, BADI ME_GUI_PO_CUST下面的4个方法都不会触发:

TRANSPORT_FROM_MODEL

TRANSPORT_TO_DYNP

TRANSPORT_FROM_DYNP

TRANSPORT_TO_MODEL

Step 4: BADI ME_GUI_PO_CUST的实现,子屏幕数据传递处理

点击“Source Code-Base”进入到整个类代码编辑界面(如果不是自定义类,是SAP系统提供的标准类时,是没有这个按钮的,即不能进入类整体代码编辑器的):

ZCL_IM__JZJ_BADI_IMPL_PO类的属性设计

IF_EX_ME_GUI_PO_CUST~SUBSCRIBE,引用自定义子屏幕

.
      APPEND ls_subscriber TO re_subscribers.
    ENDIF.
  ENDMETHOD.

IF_EX_ME_GUI_PO_CUST~MAP_DYNPRO_FIELDS屏幕字段编号

.
      ENDCASE.
    ENDLOOP.
  ENDMETHOD.

从Type Group MMMFD来看,Custom的field好像最多只能10个,但可以自己编号(如程序中的99000001、99000002):

经过上面步骤, 我们可以在ME23N看到custom subscreen, 但在ME21N和ME22N依然是看不到的,这个是为什么,还搞不清楚,只知道在实现BADI  ME_PROCESS_PO_CUST中的IF_EX_ME_PROCESS_PO_CUST~FIELDSELECTION_HEADER/ITEM方法后,三个界面中的子屏幕都才显示出来

IF_EX_ME_GUI_PO_CUST~TRANSPORT_FROM_MODEL,从业务模型中读取数据到BADI属性

METHOD if_ex_me_gui_po_cust~transport_from_model.
    DATA:     lw_header       TYPE REF TO if_purchase_order_mm,
              lw_mepoheader   TYPE mepoheader.

DATA: l_item       TYPE REF TO if_purchase_order_item_mm,
      ls_mepoitem  TYPE mepoitem.
*--------------------------------------------------------------------*
* system asks to transport data from the business logic into the view
*--------------------------------------------------------------------*
**********将业务数据转存到BADI相应属性里************
    IF im_name = 'H_SUBSCREEN_1' OR im_name = 'H_SUBSCREEN_2'.
* is it an Header? im_model can be header or item.
      mmpur_dynamic_cast lw_header im_model."强制向下转型
      CHECK NOT lw_header IS INITIAL."如果强转不出错
* transport standard fields 与EKKO在同一表中的扩展字段
      lw_mepoheader = lw_header->get_data( ).
* store info for later use将初始数据暂存起来过后使用
      MOVE-CORRESPONDING lw_mepoheader TO dynp_data_pbo_head.

ELSEIF im_name = 'I_SUBSCREEN_1'.
* is it an item? im_model can be header or item.
      mmpur_dynamic_cast l_item im_model.
      CHECK NOT l_item IS INITIAL.
* transport standard fields
      ls_mepoitem = l_item->get_data( ).
* store info for later use
      MOVE-CORRESPONDING ls_mepoitem TO dynp_data_pbo_item.

ELSEIF im_name = 'I_SUBSCREEN_2'.
      mmpur_dynamic_cast l_item im_model.
      CHECK NOT l_item IS INITIAL.
* transport standard fields
      ls_mepoitem = l_item->get_data( ).

* transport customer fields
      CALL FUNCTION 'Z_PO_SUBSCREEN_GRP_GET_DATA'
        EXPORTING
          im_ebeln = ls_mepoitem-ebeln
          im_ebelp = ls_mepoitem-ebelp
        IMPORTING
          ex_data  = zekpo_db_pbo.
    ENDIF.
  ENDMETHOD.

IF_EX_ME_GUI_PO_CUST~TRANSPORT_TO_DYNP,将BADI属性中的数据传到屏幕中显示

METHOD if_ex_me_gui_po_cust~transport_to_dynp.
***********将BADI属性中的数据显示到屏幕上,需要调用前面创建的函数中交互完成****************
    IF im_name = 'H_SUBSCREEN_1' OR im_name = 'H_SUBSCREEN_2' .
      CALL FUNCTION 'Z_PO_SUBSCREEN_GRP_PUSH_HEAD'
        EXPORTING
          im_dynp_data = dynp_data_pbo_head.

ELSEIF im_name = 'I_SUBSCREEN_1'  .
      CALL FUNCTION 'Z_PO_SUBSCREEN_GRP_PUSH_ITEM'
        EXPORTING
          im_dynp_data = dynp_data_pbo_item.
    ELSEIF im_name = 'I_SUBSCREEN_2'  .
      CALL FUNCTION 'Z_PO_SUBSCREEN_GRP_PUSH_ITEM_2'
        EXPORTING
          im_dynp_data = zekpo_db_pbo.
    ENDIF.
  ENDMETHOD.

IF_EX_ME_GUI_PO_CUST~TRANSPORT_FROM_DYNP,将屏幕字段值传到BADI属性中

METHOD if_ex_me_gui_po_cust~transport_from_dynp.
    "当PAI事件发生时,将屏幕上的数据转存到BADI属性中
    IF im_name = 'H_SUBSCREEN_1' OR im_name = 'H_SUBSCREEN_2'  .
      CALL FUNCTION 'Z_PO_SUBSCREEN_GRP_POP_HEAD'
        IMPORTING
          ex_dynp_data = dynp_data_pai_head.
    ELSEIF im_name = 'I_SUBSCREEN_1'  .
      CALL FUNCTION 'Z_PO_SUBSCREEN_GRP_POP_ITEM'
        IMPORTING
          ex_dynp_data = dynp_data_pai_item.
    ELSEIF im_name = 'I_SUBSCREEN_2'  .
      CALL FUNCTION 'Z_PO_SUBSCREEN_GRP_POP_ITEM_2'
        IMPORTING
          ex_dynp_data = zekpo_db_pai.
    ENDIF.
    "发生PAI后,判断数据是否发生变化
    IF dynp_data_pai_head <> dynp_data_pbo_head
      OR dynp_data_pai_item <> dynp_data_pbo_item
       OR zekpo_db_pai <> zekpo_db_pbo.
* something has changed therefor we have to notify the framework
* to transport data to the model
      "只有re_changed为X时,F_EX_ME_PROCESS_PO_CUST~PROCESS_HEADER/ITEM方法才会触发
      re_changed = mmpur_yes.
    ENDIF.
ENDMETHOD.

IF_EX_ME_GUI_PO_CUST~TRANSPORT_TO_MODEL,将BADI属性中的数据传到业务数据模型中

METHOD if_ex_me_gui_po_cust~transport_to_model.
    DATA: lw_header            TYPE REF TO if_purchase_order_mm,
          lw_mepoheader        TYPE mepoheader.
    DATA: l_item       TYPE REF TO if_purchase_order_item_mm,
        ls_mepoitem  TYPE mepoitem,
        ls_customer  TYPE zekpo_db.
*--------------------------------------------------------------------*
* data have to be transported to business logic
*--------------------------------------------------------------------*
*********将屏幕字段保存到业务模型中********************
    IF im_name = 'H_SUBSCREEN_1' OR im_name = 'H_SUBSCREEN_2'.
* is it an item? im_model can be header or item.
      mmpur_dynamic_cast lw_header im_model.
      CHECK NOT lw_header IS INITIAL.
      lw_mepoheader = lw_header->get_data( ).
* standard fields changed?标准表EKKO扩展字段(通过Include  CI_EKKO增强扩展的字段)数据修改
      IF dynp_data_pbo_head-zz_head_f1 <> dynp_data_pai_head-zz_head_f1
        OR dynp_data_pbo_head-zz_head_f2 <> dynp_data_pai_head-zz_head_f2.
* update standard fields将屏幕上数据存储到最终业务内表中
        "以下mepoheader结构中的两个字段是向其Include  CMOD预留表结构CI_EKKO而具有的
        lw_mepoheader-zz_head_f1 = dynp_data_pai_head-zz_head_f1.
        lw_mepoheader-zz_head_f2 = dynp_data_pai_head-zz_head_f2.
        CALL METHOD lw_header->set_data( lw_mepoheader ).
      ENDIF.

ELSEIF im_name = 'I_SUBSCREEN_1' .
* is it an item? im_model can be header or item.
      mmpur_dynamic_cast l_item im_model.
      CHECK NOT l_item IS INITIAL.
      ls_mepoitem = l_item->get_data( ).
* standard fields changed? 标准表EKPO扩展字段(通过Include  CI_EKPO增强扩展的字段)数据修改
      IF dynp_data_pbo_item-zz_item_f1 NE dynp_data_pai_item-zz_item_f1 OR
         dynp_data_pbo_item-zz_item_f2 NE dynp_data_pai_item-zz_item_f2.
* update standard fields
        ls_mepoitem-zz_item_f1 = dynp_data_pai_item-zz_item_f1.
        ls_mepoitem-zz_item_f2 = dynp_data_pai_item-zz_item_f2.
        CALL METHOD l_item->set_data( ls_mepoitem ).
      ENDIF.
    ELSEIF im_name = 'I_SUBSCREEN_2' .

* is it an item? im_model can be header or item.
      mmpur_dynamic_cast l_item im_model.
      CHECK NOT l_item IS INITIAL.
      ls_mepoitem = l_item->get_data( ).
* customer fields changed?扩展表ZEKPO_DB数据修改
      IF zekpo_db_pbo-field1 NE zekpo_db_pai-field1 OR
         zekpo_db_pbo-field2 NE zekpo_db_pai-field2.

CALL FUNCTION 'Z_PO_SUBSCREEN_GRP_GET_DATA'
          EXPORTING
            im_ebeln = ls_mepoitem-ebeln
            im_ebelp = ls_mepoitem-ebelp
          IMPORTING
            ex_data  = ls_customer.
        ls_customer-field1 = zekpo_db_pai-field1.
        ls_customer-field2 = zekpo_db_pai-field2.
        CALL FUNCTION 'Z_PO_SUBSCREEN_GRP_SET_DATA'
          EXPORTING
            im_data = ls_customer.
      ENDIF.
    ENDIF.
  ENDMETHOD.

Step 4: BADI ME_PROCESS_PO_CUST的实现,数据处理

IF_EX_ME_PROCESS_PO_CUST~FIELDSELECTION_HEADER,(头)字段可见性、可输入状态设置

'-'代表hidden, '+'或'.'表示editable, '*'代表display

METHOD if_ex_me_process_po_cust~fieldselection_header.
**********Header增强子屏幕字段可输入性处理******************
    DATA: lv_persistent TYPE mmpur_bool.
    FIELD-SYMBOLS: <fs> LIKE LINE OF ch_fieldselection.

LOOP AT ch_fieldselection ASSIGNING <fs>.
      CASE <fs>-metafield.
        WHEN OTHERS."下面看似无意义,但如果不经过下面处理,有时子屏幕又显示不出来,不知道为什么?
          IF  <fs>-fieldstatus = '+' .
            <fs>-fieldstatus = '.'.
          ELSEIF <fs>-fieldstatus = '.'.
            <fs>-fieldstatus = '+'.
          ELSEIF <fs>-fieldstatus IS INITIAL.
            <fs>-fieldstatus = '+'.
          ENDIF.
      ENDCASE.
    ENDLOOP.
  ENDMETHOD.

IF_EX_ME_PROCESS_PO_CUST~FIELDSELECTION_ITEM,(Item)字段可见性、可输入状态设置

."需特殊处理的字段
          l_persistent = im_item->is_persistent( )."当前Item是否已持久化过
* if the item is already on the database, we disallow to change field badi_bsgru
          IF l_persistent EQ mmpur_yes."如果该Item已在数据库保存过了,则将Field1扩展字段不能再被修改
            <fs>-fieldstatus = '*'. " Display
          ENDIF.
        WHEN OTHERS."其他无需特殊处理的字段,但如果不经过下面处理,有时子屏幕又显示不出来,不知道为什么?
          IF  <fs>-fieldstatus = '+' .
            <fs>-fieldstatus = '.'.
          ELSEIF <fs>-fieldstatus = '.'.
            <fs>-fieldstatus = '+'.
          ELSEIF <fs>-fieldstatus IS INITIAL.
            <fs>-fieldstatus = '+'.
          ENDIF.
      ENDCASE.
    ENDLOOP.
"使用下面方式方式会出问题:在Item打上删除标识后,系统会将Item相关屏幕中的字段都设置为
"不可编辑状态,如是经过下面处理,则会将字段又重设回可编辑,这样与系统所设置的矛盾,原因
"是这里使用的是 im_header->is_changeable( ) 头来判断的,但在编辑状态下头肯定是可编辑的
",所以拿头的可编辑状态来判断Item是否处于可编辑状态是错误的,但发现 im_item又没有
"is_changeable( )方法,所以只能采用上面方式
*    DEFINE set_input.
*      read table ch_fieldselection assigning <fs> with table key metafield = &1.
*      if sy-subrc = 0.
*        if im_header->is_changeable( ) = mmpur_yes.
*          <fs>-fieldstatus = '+'.
*        else.
*          <fs>-fieldstatus = '*'.
*        endif.
*      endif.
*    END-OF-DEFINITION.
*    set_input mmmfd_cust_01.
*    ...
ENDMETHOD.

IF_EX_ME_PROCESS_PO_CUST~PROCESS_HEADER,头数据处理,如校验

!' '' '' ''.
* invalidate the object
      CALL METHOD im_header->invalidate( ).
    ENDIF.
  ENDMETHOD.

这里出错提示好你有点问题:

如果按回车来check的话,它会显示error message,一次改变后按一次回车有反应,第二次就没反应:

这里有一个缺陷,当我们按save里,程序中设置的错误message是不会显示到下面message框中的(研究了很久, 没研究出来怎么搞),幸好这里有个功能十分不错, 选中PO header data still faulty这个message,按Edit, 就可以定位到出错的字段:.

但Item中的字段出错后,会显示到提示框中:

IF_EX_ME_PROCESS_PO_CUST~PROCESS_ITEM,头数据处理,如校验

!' space.
      ENDIF.
    ENDIF.
  ENDMETHOD.

IF_EX_ME_PROCESS_PO_CUST~INITIALIZE,程序运行时初始化

METHOD if_ex_me_process_po_cust~initialize.
* initializations 在第一次打开采购单主界面(ME21N/ME22N/ME23N)时,会调用,但
* 在通过主界面上的编辑按钮在显示与编辑模式之间切换时,不会再调用,即在只在程序
* 启动(如打开一个Tcode)时才调用
    CALL FUNCTION 'Z_PO_SUBSCREEN_GRP_INIT'.
ENDMETHOD.

IF_EX_ME_PROCESS_PO_CUST~OPEN,读取自建表中的数据

METHOD if_ex_me_process_po_cust~open.
    DATA: ls_mepoheader TYPE mepoheader.
*---------------------------------------------------------------------*
* read customer data
*---------------------------------------------------------------------*
*****在第一次打开采购单主界面时 或在通过主界面(ME22N/ME23N)上的编辑
* 按钮在显示与编辑模式之间切换时,或者在不同的PO之间进行切换时,就会调用一次
* this has to be done when we open a persistent object只有为修改或显示模式下才需要读取数据库表
    CHECK im_trtyp EQ 'V' OR im_trtyp EQ 'A'.
    ls_mepoheader = im_header->get_data( ).
* read customer data from database
    CALL FUNCTION 'Z_PO_SUBSCREEN_GRP_OPEN'
      EXPORTING
        im_ebeln = ls_mepoheader-ebeln.
ENDMETHOD.

IF_EX_ME_PROCESS_PO_CUST~POST,保存数据到自建表中

METHOD if_ex_me_process_po_cust~post.
    "将数据更新到数据库表中
    CALL FUNCTION 'Z_PO_SUBSCREEN_GRP_POST'
      EXPORTING
        im_ebeln = im_ebeln.
ENDMETHOD.

通过程序查找出口对象或BADI

可以通过数据库表中的信息来查找某事务所有相关的出口信息

在SAP中,所有程序名及事务码,以及程序中所包括的对象信息都会被保存在表TADIR中:

OBJECT:对象类型,如PROG(程序)、TRAN(事务码)、SMOD(SMOD增加)等

OBJ_NAME:对象名称

DEVCLASS:开发包

在SAP提示的标准程序中,所有的程序、事务及增强都使用了同一开发类,所以可以先根据程序或事务先找到它定义的开发类,再根据开发类来查找其对就的 SMOD 增强出口对象,至于出口对象的描述,则可以从数据表MODSAPT中来获取。

本例将根据指定的事务码,来查出所有相关的增强出口对象(要开发时,可以通过该实例快速查找事务相关的SMOD出口对象,但要注意,这个程序只能找出相应事务码绝大多数的出口对象,其他找不着的可以调试MODX_FUNCTION_ACTIVE_CHECK函数来获取):

).
      CALL TRANSACTION 'SE18' AND SKIP FIRST SCREEN.
  ENDCASE.

Enhancement Framework 基本概念

Enhancement Framework的目的:在不改变(或尽量少改变)SAP标准程序的情况下满足客户的定制开发需求。Keep less Modification.

Enhancement Framework的基本概念:

Ehancement Spot: 用来组织Enhancement options,it's a container of Enhancement options.

Enhancement Implementation:用来组织Enhancement options的实现代码。

ENHANCEMENT-POINT是在程序中直接插入代码,其概念与BADI的USER_EXIT类似,标准程序预留了部分已定义好的增强点可以让ABAP做插入代码来实现这个增强(也可以自定义增强点,但不能自定义增强选项,增强选项一定是系统预留下来的,如果没有增强选项则该处不可做增强),但是不能做屏幕和菜单增强。

其最大的优势在于方便,可以使用程序中已定义的变量,不像BTE和USER_EXIT中只能使用函数接口传过来看参数。

一般增强步骤:

1.         DEBUG标准程序找到需要增强的位置,点EDIT->SHOW IMPLICIT ENHANCEMENT OPTIONS查看是否有预留增强选项。(标准程序不能自己创建enhancement option ,只能使用系统预留的)

2.         创建增强点实现

为自己程序创建显示增强Explicit Enhancement spot

进入创建增强选项界面,输入增强点名及增强容器名(以Z开头),确认回车。

注:Enhancement Spot 就是SE18中的Enhancement Spot

随后Editor上会多出一条语句,然后转到增强模式

注:

Enhancement Spot相当于一个容器,创建一个增强点的必要条件是要有一个容器。每个增强点(如ZENH_POINT_01)都可以创建到这个容器当中,也可以再创建一个容器。删除这个容器的方法:在本地对象或它的包中删除或在SE18中删除,激活程序,退出再进。

对于ENHANCEMENT-SECTION, 定义和实现的方法与ENHANCEMENT-POINT一样。

两者的区别是:enhancement-point没有代码,只有一个预留点,允许在这个位置插入新代码(implementation).而nhancement-section和end-enhancement-section.之间有代码,implementation之后,替换旧代码,只执行新代码,原来的代码不再执行。

隐式与显示增强

隐式增强就是系统内置的Enhancement options,有一点AOP的味道,但只能针对单个对象。Implicit enhancements comprise class enhancements, function group enhancements and predefined enhancement points at particular predefined positions such as the end of a report, a function module, an include or a structure and the beginning and the end of a method.

显式增强就是我们人工加入到程序中的Enhancement options,有两种显式增强:

ENHANCEMENT-POINT ,用来插入新的功能代码,没有代码,只有一个预留点

Defines a position in an ABAP program as an enhancement option, at which one or more source code plug-ins can be inserted.

ENHANCEMENT-POINT Syntax:

ENHANCEMENT-POINT enh_id SPOTS spot1 spot2 ...
                   [STATIC]
                   [INCLUDE BOUND].

ENHANCEMENT-SECTION ,用例替换原有的功能代码,ENHANCEMENT-SECTION 和 END-ENHANCEMENT-SECTION. 之间有代码, implementation 之后,替换旧代码,只执行新代码,原来的代码不再执行.

Defines a section of an ABAP program as an enhancement option, which can can be replaced by one or more source code plug-ins.

ENHANCEMENT-SECTION Syntax:

ENHANCEMENT-SECTION enh_id SPOTS spot1 spot2 ...
                     [STATIC]
                     [INCLUDE BOUND].
   ...
END-ENHANCEMENT-SECTION.

隐式增强是系统本身就预留的,如在:执行程序,包含程序,函数组,对话模块的结尾;Form例程,函数模块,方法等的开始和结尾;结构的结尾这些地方都会有

显示增强:需要在编辑器中创建,可参考上面

系统标准表结构增强

一般对于用户自己创建的表就没有必要采用增加了,直接修改即可。但对于SAP系统提供的表,如果需要扩展字段的话,则需要采用增加的方式来扩展。

SAP中一般是不允许直接修改系统标准表的,但是SAP提供了标准表的增加功能,允许用户在原有表字段的基础上增加一些自定义的字段,通常称为表结构的增加。

表结构的增加并不是直接修改系统表,而在表中预留一个可以修改的结构体,该结构体再被系统表直接引用作为扩充的表字段。

表增强可以解决部分业务开展问题,但是同进也会增加对系统资源的消耗。SAP已经预留置了很多字段给用户作业务扩展用,在追加表字段前,应该先尽量了解系统中已有功能是否能满足目前业务的需要,避免造成一些不必要的资源浪费

SAP R/3系统提供了两种方式对表或结构体进行增强:

l  Customizing includes(CL includes):使用Include Struture对表结构进行增强

l  使用Append Strutures对表结构进行增强

通过这两种方式可以使我们在不真正修改SAP的标准透明表结构的基础上,对表的字段进行添加。

Include Struture

只有扁平的结构体才能被包含

包含可以被嵌套,最多九层

只有结构体才可以被包含在透明表的定义中。但透明表、视图、结构体可以被包含到结构体中。

当多个表有相同的几个字段时,这时可以将这些相同的字段抽出来形成一个结构,然后再将这个结构Include到表结构中。

注:Include严格来讲,不属于表增强,因为在使用此功能时,需要切换到编辑模式,这样就直接使用表结构了,标准表是不允许的,但Append就不需要切换到编辑模式就可以实现扩展字段

Append Strutures

Append与Include两种方式的区别:

l  Include方式时,会在透明表中增加一行名为“.INCLUDE”的列,而Append时,会在末尾增加一列名为“.APPEND”的列

l  Include可以插入到任何位置,但Append每次只能附加到当前表结构的末尾(可以附加多个)(但经过多次的修改,最后Append进来的结构也可能位于当前表结构的中间)

l  Include时,结构要事先创建好,但Append时,不能引用事先创建好的结构,只能在Append过程中创建。

l  与Include不同的是,Append可以在不修改SAP系统表(编辑状态)的情况下,可以给系统表新增一字段,对现有使用该表的程序影响很小。

l  不能够为Pooled与Cluster表进行Append。

l  如果某个表中有长文本字段(类型为LCHR或LRAW)的表,不能够在使用Append对它进行扩展,因为长文本字段通常也是只能放在表结构的最后面。

l  Append的结构名需要以Z或Y打头,结构中的字段名要使用YY或ZZ打头。

l  Append时,SE11不需要切换到编辑模式,但Include需要切换到编辑模式下才能使用

l  Append后,被Append的结构中所字段会全部紧跟着显示在“.APPEND”行后面,但Include时是不会将被Include里的字段显示出来 不是没显示出来,是没有展开(AppendInclude其实都是可以展开的):

l  在复制表时,“.Append”会丢失,但Append中的字段会被拷贝过来,但Include与之不同,包括 .INCLUDE 与其字段都会被拷贝过来,这进一步证实了 .INCLUDE 结构是可以重复使用的,但Append结构不能

点新增按钮,出现新建Append结构对话框:

CURR类型的字段还需要参照表与字段:

SE14调整表

如果表的结构修改后,不能激活或激活失败,此时可以使用SE14重新对表进行调整即可:

若选择“删除数据”,会运载整个数据库表进行清空,包括其他Client端的数据,请谨慎选择

最新文章

  1. 转载:log4j.properties log4j.xml 路径问题
  2. C语言细节——献给入门者(一)
  3. mysql根据时间查询前一天数据
  4. &lt;一道题&gt;abc+cba=1333,求满足条件的abc的值,隐含条件a!=0,c!=0
  5. shell脚本应用(5)--实用脚本片段
  6. 跨终端Web之Hybrid App
  7. C# 中的协变和逆变
  8. linq中日期格式转换或者比较,程序报错说不支持方法的解决办法
  9. python性能分析之cProfile模块
  10. Cocos2dx制作帧动画
  11. linux基础实操四
  12. 浏览器iscroll
  13. myeclipse编译弹框:The builder launch configuration could not be found
  14. 多线程(threading)示例
  15. Android GUI架构之MVC模式
  16. CTF竞赛(简介)
  17. java模拟网页http-url访问
  18. TFS使用笔记
  19. Elastic_Terms 内容分类统计
  20. visual studio 安装与sqlserver 安装

热门文章

  1. JSON转换类
  2. Elasticsearch--数据索引
  3. netsh修改IP及DNS
  4. windows prompt personalize 设置cmd提示的相关
  5. bodybuilding
  6. Composer使用中常见的问题
  7. php const define 区别有那些呢?
  8. HDU 4635:Strongly connected(强连通)
  9. POJ 3903:Stock Exchange(裸LIS + 二分优化)
  10. nodepad + 插件