ABAP Enhancement:第二部分
BADI新方式实现
1-构建BADI
1,SAP BADI的由来
大家都知道SAP在ERP行业中,应用最广的是财务领域。由于各个国家财务制度以及税务制度的差异,SAP希望在自己的程序开发平台中引入BADI,能够让开发人员自己编写业务插件,系统会自动调用这些插件程序来完成某种业务运算。本文中的举例是计算不同国家的税率。
2,创建一个Enhancement Spot
Enhancement Spot是作为一个BADI的容器,在这个容器里面,我们可以定义自己的多个BADI。
- 在TCode SE18中
3,定义一个BADI
- 在新建立的enhancement spot中创建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接口与类)
DATA: handle TYPE REF TO z_badi_calc_vat,"z_badi_calc_vat为BADI定义,不是接口也不是类,但又好像能代表接口
sum TYPE p,
vat TYPE p,
percent TYPE p.
sum = 50.
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),单一使用的限制是只能有一个实现类。如何解决这个问题,请看本系列的最后一篇文章,如何使用过滤器。
注意:上面过滤值一定要大写,否则运行时匹配不到。
使用下面测试程序进行测试:
parameters: filter(2) type c.
DATA: handle TYPE REF TO z_badi_calc_vat,"z_badi_calc_vat为BADI定义,不是接口也不是类
sum TYPE p,
vat TYPE p,
percent TYPE p.
sum = 50.
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
FUNCTION Z_PO_SUBSCREEN_GRP_COMMIT.
*"----------------------------------------------------------------------
*"*"Update Function Module:
*"*"Local Interface:
*" TABLES
*" IMT_DATA_NEW STRUCTURE ZEKPO_DB
*" IMT_DATA_OLD STRUCTURE ZEKPO_DB
*"----------------------------------------------------------------------
DATA: ls_data_new LIKE LINE OF gt_data,
ls_data_old LIKE LINE OF gt_data,
data_ins TYPE STANDARD TABLE OF zekpo_db,
data_upd TYPE STANDARD TABLE OF zekpo_db,
data_del TYPE STANDARD TABLE OF zekpo_db.
*********将数据保存到数据库中************************
* new state
LOOP AT imt_data_new INTO ls_data_new.
READ TABLE imt_data_old INTO ls_data_old WITH KEY
mandt = sy-mandt
ebeln = ls_data_new-ebeln
ebelp = ls_data_new-ebelp.
"如果新数据在旧数据中查找得到
IF sy-subrc IS INITIAL.
"对比一条后,就将其删除,这样剩下没有被删除的就代表通过界面删除了
DELETE imt_data_old INDEX sy-tabix.
"如果新旧数据不同,则表示界面修改过数据了
IF ls_data_new NE ls_data_old.
* existing entry was changed
"将需要更新的数据暂存到data_upd表中
APPEND ls_data_new TO data_upd.
ENDIF.
ELSE."如果为新增数据
* a new entry was added
"将需要新增的数据暂存到data_ins表中
APPEND ls_data_new TO data_ins.
ENDIF.
ENDLOOP.
* remaining old state: can be deleted
"剩下的就是需要被删除的数据
APPEND LINES OF imt_data_old TO data_del.
*---------------------------------------------------------------------*
* actual update operations
*---------------------------------------------------------------------*
* insert
IF NOT data_ins[] IS INITIAL.
INSERT zekpo_db FROM TABLE data_ins.
IF sy-subrc NE 0.
MESSAGE a807(me) WITH 'zekpo_db'.
ENDIF.
ENDIF.
* update
IF NOT data_upd[] IS INITIAL.
UPDATE zekpo_db FROM TABLE data_upd.
IF sy-subrc NE 0.
MESSAGE a808(me) WITH 'zekpo_db'.
ENDIF.
ENDIF.
* delete
IF NOT data_del[] IS INITIAL.
DELETE zekpo_db FROM TABLE data_del.
IF sy-subrc NE 0.
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
*----------------------------------------------------------------------*
* INCLUDE LMEVIEWSF01 *
*----------------------------------------------------------------------*
DATA: call_subscreen TYPE sy-dynnr, "#EC NEEDED
call_prog TYPE sy-repid, "#EC NEEDED
call_view TYPE REF TO cl_screen_view_mm, "#EC NEEDED
call_view_stack TYPE REF TO cl_screen_view_mm OCCURS 0,"#EC NEEDED
global_framework TYPE REF TO cl_framework_mm, "#EC NEEDED
global_help_view TYPE REF TO cl_screen_view_mm, "#EC NEEDED
global_help_prog TYPE sy-repid. "#EC NEEDED
*---------------------------------------------------------------------*
* FORM CALL_SCREEN *
*---------------------------------------------------------------------*
* ........ *
*---------------------------------------------------------------------*
* --> P_SCREEN *
*---------------------------------------------------------------------*
FORM call_screen USING p_screen TYPE sy-dynnr.
CALL SCREEN p_screen.
ENDFORM.
*---------------------------------------------------------------------*
* FORM CALL_POPUP *
*---------------------------------------------------------------------*
* ........ *
*---------------------------------------------------------------------*
FORM call_popup USING p_screen TYPE sy-dynnr
p_starting_x TYPE sy-tabix
p_starting_y TYPE sy-tabix
p_ending_x TYPE sy-tabix
p_ending_y TYPE sy-tabix.
CALL SCREEN p_screen STARTING AT p_starting_x p_starting_y
ENDING AT p_ending_x p_ending_y.
ENDFORM.
*---------------------------------------------------------------------*
* FORM SET_SCREEN *
*---------------------------------------------------------------------*
* ........ *
*---------------------------------------------------------------------*
* --> P_SCREEN *
*---------------------------------------------------------------------*
FORM set_screen USING p_screen TYPE sy-dynnr.
SET SCREEN p_screen.
ENDFORM.
*---------------------------------------------------------------------*
* FORM PUSH_CALL_VIEW *
*---------------------------------------------------------------------*
* ........ *
*---------------------------------------------------------------------*
FORM push_call_view.
APPEND call_view TO call_view_stack.
ENDFORM.
*---------------------------------------------------------------------*
* FORM POP_CALL_VIEW *
*---------------------------------------------------------------------*
* ........ *
*---------------------------------------------------------------------*
FORM pop_call_view.
IF NOT call_view_stack[] IS INITIAL.
DATA: last TYPE sy-tabix.
DESCRIBE TABLE call_view_stack LINES last.
READ TABLE call_view_stack INTO call_view INDEX last.
DELETE call_view_stack INDEX last.
ENDIF.
ENDFORM.
*---------------------------------------------------------------------*
* FORM SET_VALUE *
*---------------------------------------------------------------------*
* ........ *
*---------------------------------------------------------------------*
* --> P_NAME *
* --> P_VALUE *
*---------------------------------------------------------------------*
FORM set_value USING p_name p_value.
FIELD-SYMBOLS <field>.
ASSIGN (p_name) TO <field>.
<field> = p_value.
ENDFORM.
*---------------------------------------------------------------------*
* FORM SET_SUBSCREEN_AND_PROG *
*---------------------------------------------------------------------*
* ........ *
*---------------------------------------------------------------------*
* --> DYNNR *
* --> PROG *
* --> VIEW *
* --> TO *
* --> CL_SCREEN_VIEW_MM *
*---------------------------------------------------------------------*
FORM set_subscreen_and_prog USING dynnr TYPE sy-dynnr
prog TYPE sy-repid
view TYPE REF TO cl_screen_view_mm.
call_subscreen = dynnr.
call_prog = prog.
call_view = view.
ENDFORM.
*---------------------------------------------------------------------*
* FORM GET_OK_CODE *
*---------------------------------------------------------------------*
* ........ *
*---------------------------------------------------------------------*
* --> FCODE *
*---------------------------------------------------------------------*
FORM get_ok_code USING fcode TYPE sy-ucomm.
fcode = ok-code.
ENDFORM.
*&---------------------------------------------------------------------*
*& Module EVENT_PBO OUTPUT
*&---------------------------------------------------------------------*
* text
*----------------------------------------------------------------------*
MODULE event_pbo OUTPUT.
CALL METHOD call_view->handle_event( 'PBO' ).
ENDMODULE. " EVENT_PBO OUTPUT
*&---------------------------------------------------------------------*
*& Module EVENT_PBO_TC OUTPUT
*&---------------------------------------------------------------------*
* text
*----------------------------------------------------------------------*
MODULE event_pbo_tc OUTPUT.
CALL METHOD call_view->handle_event( 'PBO_TC_LINE' ).
ENDMODULE. " EVENT_PBO_TC OUTPUT
*&---------------------------------------------------------------------*
*& Module EVENT_PBO_SUBSCREEN OUTPUT
*&---------------------------------------------------------------------*
* text
*----------------------------------------------------------------------*
MODULE event_pbo_subscreen OUTPUT.
PERFORM push_call_view.
CALL METHOD call_view->handle_event( 'PBO_SUBSCREEN' ).
ENDMODULE. " EVENT_PBO_SUBSCREEN OUTPUT
*&---------------------------------------------------------------------*
*& Module EVENT_PBO_POPSUBSCREEN
*&---------------------------------------------------------------------*
* text
*----------------------------------------------------------------------*
MODULE event_pbo_popsubscreen OUTPUT.
PERFORM pop_call_view.
ENDMODULE. " EVENT_PBO_SUBSCREEN OUTPUT
*&---------------------------------------------------------------------*
*& Module EVENT_PBO_FINISHED OUTPUT
*&---------------------------------------------------------------------*
* text
*----------------------------------------------------------------------*
MODULE event_pbo_finished OUTPUT.
CALL METHOD call_view->handle_event( 'PBO_FINISHED' ).
ENDMODULE. " EVENT_PBO_FINISHED OUTPUT
*&---------------------------------------------------------------------*
*& Module EVENT_PAI_POPSUBSCREEN
*&---------------------------------------------------------------------*
* text
*----------------------------------------------------------------------*
MODULE event_pai_popsubscreen INPUT.
PERFORM pop_call_view.
ENDMODULE. " EVENT_PBO_SUBSCREEN OUTPUT
*&---------------------------------------------------------------------*
*& Module EVENT_PAI_SUBSCREEN
*&---------------------------------------------------------------------*
* text
*----------------------------------------------------------------------*
MODULE event_pai_subscreen INPUT.
PERFORM push_call_view.
CALL METHOD call_view->handle_event( 'PAI_SUBSCREEN' ).
ENDMODULE. " EVENT_PBO_SUBSCREEN OUTPUT
*&---------------------------------------------------------------------*
*& Module EVENT_PAI INPUT
*&---------------------------------------------------------------------*
* text
*----------------------------------------------------------------------*
MODULE event_pai INPUT.
CALL METHOD call_view->handle_event( 'PAI' ).
ENDMODULE. " EVENT_PAI INPUT
*&---------------------------------------------------------------------*
*& Module EVENT_PAI_FINISHED INPUT
*&---------------------------------------------------------------------*
* text
*----------------------------------------------------------------------*
MODULE event_pai_finished INPUT.
ENHANCEMENT-POINT EVENT_PAI_FINISHED_01 SPOTS ES_LMEVIEWSF01 INCLUDE BOUND.
*$*$-Start: EVENT_PAI_FINISHED_01---------------------------------------------------------------$*$*
ENHANCEMENT 1 AD_MPN_PUR2_LMEVIEWSF01. "active version
* clear temporary MPN system messages "note 916061
perform mepo_pic_delete_message in program saplmepo.
*
* Addition by Roger <<< DI Note: 426616
if not ok-code is initial.
call method cl_framework_mm=>get_instance
IMPORTING ex_instance = global_framework.
CALL METHOD global_framework->set_fcode
EXPORTING im_fcode = sy-ucomm.
CLEAR ok-code.
endif.
ENDENHANCEMENT.
*$*$-End: EVENT_PAI_FINISHED_01---------------------------------------------------------------$*$*
CALL METHOD call_view->handle_event( 'BEFORE_TRANSPORT' ).
CALL METHOD call_view->handle_event( 'PAI_FINISHED' ).
ENDMODULE. " EVENT_PAI_FINISHED INPUT
*&---------------------------------------------------------------------*
*& Module EVENT_PAI_TC INPUT
*&---------------------------------------------------------------------*
* text
*----------------------------------------------------------------------*
MODULE event_pai_tc INPUT.
CALL METHOD call_view->handle_event( 'PAI_TC_LINE' ).
ENDMODULE. " EVENT_PAI_TC INPUT
*&---------------------------------------------------------------------*
*& Module EVENT_PBO_PREPARE OUTPUT
*&---------------------------------------------------------------------*
* text
*----------------------------------------------------------------------*
MODULE event_pbo_prepare OUTPUT.
CALL METHOD call_view->handle_event( 'PBO_PREPARE' ).
ENDMODULE. " EVENT_PBO_PREPARE OUTPUT
*&---------------------------------------------------------------------*
*& Module EVENT_PAI_PREPARE INPUT
*&---------------------------------------------------------------------*
* text
*----------------------------------------------------------------------*
MODULE event_pai_prepare INPUT.
CALL METHOD call_view->handle_event( 'PAI_PREPARE' ).
ENDMODULE. " EVENT_PAI_PREPARE INPUT
*&---------------------------------------------------------------------*
*& Module EVENT_POV_LIST INPUT
*&---------------------------------------------------------------------*
* text
*----------------------------------------------------------------------*
MODULE event_pov_list INPUT.
CALL METHOD cl_framework_mm=>get_instance
IMPORTING ex_instance = global_framework.
global_help_prog = sy-repid.
CALL METHOD global_framework->get_view
EXPORTING im_prog = global_help_prog
im_dynnr = sy-dynnr
IMPORTING ex_view = global_help_view.
IF NOT global_help_view IS INITIAL.
CALL METHOD global_help_view->handle_event( 'EVENT_POV_LIST' ).
ENDIF.
ENDMODULE.
*&---------------------------------------------------------------------*
*& Module EVENT_POH_LIST INPUT
*&---------------------------------------------------------------------*
* text
*----------------------------------------------------------------------*
MODULE event_poh_list INPUT.
CALL METHOD cl_framework_mm=>get_instance
IMPORTING ex_instance = global_framework.
global_help_prog = sy-repid.
CALL METHOD global_framework->get_view
EXPORTING im_prog = global_help_prog
im_dynnr = sy-dynnr
IMPORTING ex_view = global_help_view.
IF NOT global_help_view IS INITIAL.
CALL METHOD global_help_view->handle_event( 'EVENT_POH_LIST' ).
ENDIF.
ENDMODULE.
*&---------------------------------------------------------------------*
*& Module GET_FCODE INPUT
*&---------------------------------------------------------------------*
* text
*----------------------------------------------------------------------*
MODULE get_fcode INPUT.
CALL METHOD cl_framework_mm=>get_instance
IMPORTING ex_instance = global_framework.
CALL METHOD global_framework->set_fcode
EXPORTING im_fcode = sy-ucomm.
CLEAR ok-code.
ENDMODULE.
*&---------------------------------------------------------------------*
*& Module FCODE_EXIT INPUT
*&---------------------------------------------------------------------*
* text
*----------------------------------------------------------------------*
MODULE fcode_exit INPUT.
CALL METHOD call_view->handle_event( 'FCODE' ).
ENDMODULE. " FCODE_EXIT INPUT
*&---------------------------------------------------------------------*
*& Module BEFORE_TRANSPORT INPUT
*&---------------------------------------------------------------------*
* text
*----------------------------------------------------------------------*
MODULE before_transport INPUT.
CALL METHOD call_view->handle_event( 'BEFORE_TRANSPORT' ).
ENDMODULE. " BEFORE_TRANSPORT INPUT
*&---------------------------------------------------------------------*
*& Module CLEAR_OKCODE INPUT
*&---------------------------------------------------------------------*
* text
*----------------------------------------------------------------------*
MODULE clear_okcode INPUT.
CLEAR sy-ucomm.
ENDMODULE. " CLEAR_OKCODE INPUT
*>>> OLC Project
* Core adaptation for VORNR searchhelp
*&---------------------------------------------------------------------*
*& Module VAL_REQ_VORNR INPUT
*&---------------------------------------------------------------------*
* text
*----------------------------------------------------------------------*
MODULE val_req_vornr INPUT.
ENHANCEMENT-POINT LMEVIEWSF01_OLC_001 SPOTS ES_LMEVIEWSF01 STATIC INCLUDE BOUND .
ENHANCEMENT-SECTION LMEVIEWSF01_OLC_002 SPOTS ES_LMEVIEWSF01 INCLUDE BOUND .
* No OLC order => Ordinary F4 Help
CALL FUNCTION 'F4IF_FIELD_VALUE_REQUEST'
EXPORTING
TABNAME = 'MEACCT1000'
FIELDNAME = 'VORNR'
DYNPPROG = SY-REPID
DYNPNR = SY-DYNNR
DYNPROFIELD = 'MEACCT1000-VORNR'
EXCEPTIONS
OTHERS = 1.
END-ENHANCEMENT-SECTION.
ENDMODULE. " VAL_REQ_VORNR INPUT
*<<< OLC Project
子屏幕设计
注:这此屏幕的属性都要设置成子屏幕
0100
注:所有的屏幕都需要调用event_pbo 与event_pai两个Module: 如果不调用这两个module, BADI ME_GUI_PO_CUST下面的4个方法都不会触发:
TRANSPORT_FROM_MODEL
TRANSPORT_TO_DYNP
TRANSPORT_FROM_DYNP
TRANSPORT_TO_MODEL
0101
0102
0103
Step 4: BADI ME_GUI_PO_CUST的实现,子屏幕数据传递处理
点击“Source Code-Base”进入到整个类代码编辑界面(如果不是自定义类,是SAP系统提供的标准类时,是没有这个按钮的,即不能进入类整体代码编辑器的):
ZCL_IM__JZJ_BADI_IMPL_PO类的属性设计
IF_EX_ME_GUI_PO_CUST~SUBSCRIBE,引用自定义子屏幕
METHOD if_ex_me_gui_po_cust~subscribe.
****构建子屏幕*******
DATA: ls_subscriber LIKE LINE OF re_subscribers.
CHECK im_application = 'PO'.
* re_subscribers内表中的每一行就是一个子屏幕,每个子屏幕会形成一个Tab
* CLEAR re_subscribers[].
* CLEAR ls_subscriber.
"首次加载采购订界面时(打开ME21N、ME22N、ME23N)会调用两次:第一次为HEADER,第二次为ITEM
IF im_element = 'HEADER'."如果当前是加载Header屏幕时
* the name is a unique identifier for the subscreen and defined in this class definition
ls_subscriber-name = 'H_SUBSCREEN_1'."子屏幕标识名,用来唯一区分子屏幕,各子屏幕这个标识不能相同
* the dynpro number to use所需嵌入的子屏幕号
ls_subscriber-dynpro = '0100'.
* the program where the dynpro can be found子屏幕所在的主程序名
ls_subscriber-program = 'SAPLZ_PO_SUBSCREEN_GRP'.
* each subscreen needs his own DDIC-Structure子屏幕中的字段所绑定的结构名,即屏幕字段名的前缀“XXX-XXX”(减号前面)
ls_subscriber-struct_name = 'CI_EKKODB'.
* a label can be defined
ls_subscriber-label = 'CI_EKKODB增强子屏幕1'.
* the position within the tabstrib can be defined 新增Tab标签所在tabstrib中的位置
*如果不指定,则会放在最后
* ls_subscriber-position = 5.
* the height of the screen can be defined here. Currently we suport two screen sizes:
* value <= 7 a sevel line subscreen
* value > 7 a 16 line subscreen
ls_subscriber-height = 2.
APPEND ls_subscriber TO re_subscribers.
**Head第二个子屏幕
ls_subscriber-name = 'H_SUBSCREEN_2'.
ls_subscriber-dynpro = '0101'.
ls_subscriber-program = 'SAPLZ_PO_SUBSCREEN_GRP'.
ls_subscriber-struct_name = 'CI_EKKODB'.
ls_subscriber-label = 'CI_EKKODB增强子屏幕2'.
ls_subscriber-height = 3.
APPEND ls_subscriber TO re_subscribers.
ELSEIF im_element = 'ITEM'."如果当前是加载Item屏幕时
ls_subscriber-name = 'I_SUBSCREEN_1'.
ls_subscriber-dynpro = '0102'.
ls_subscriber-program = 'SAPLZ_PO_SUBSCREEN_GRP'.
ls_subscriber-struct_name = 'CI_EKPODB'.
ls_subscriber-label = 'CI_EKPODB增强子屏幕1'.
ls_subscriber-height = 8.
APPEND ls_subscriber TO re_subscribers.
ls_subscriber-name = 'I_SUBSCREEN_2'.
ls_subscriber-dynpro = '0103'.
ls_subscriber-program = 'SAPLZ_PO_SUBSCREEN_GRP'.
ls_subscriber-struct_name = 'ZEKPO_DB'.
ls_subscriber-label = 'ZEKPO_DB扩展'.
ls_subscriber-height = 8.
APPEND ls_subscriber TO re_subscribers.
ENDIF.
ENDMETHOD.
IF_EX_ME_GUI_PO_CUST~MAP_DYNPRO_FIELDS屏幕字段编号
METHOD if_ex_me_gui_po_cust~map_dynpro_fields.
********屏幕子字段编号*********
* ch_mapping结构如下
* BEGIN OF mmpur_dynpro_entry,
* screenname TYPE scrfname,
* fieldname TYPE fieldname,
* position TYPE sy-index,
* metafield TYPE mmpur_metafield,
* display_only TYPE mmpur_bool,
* initial_no_disp TYPE mmpur_bool,
* initial_is_inactive TYPE mmpur_bool,
* END OF mmpur_dynpro_entry,
FIELD-SYMBOLS: <mapping> LIKE LINE OF ch_mapping.
"给每个屏幕字段一个编号: mmmfd_cust_01...09
LOOP AT ch_mapping ASSIGNING <mapping>.
CASE <mapping>-fieldname.
WHEN 'ZZ_HEAD_F1'. <mapping>-metafield = mmmfd_cust_01.
WHEN 'ZZ_HEAD_F2'. <mapping>-metafield = mmmfd_cust_02.
WHEN 'ZZ_ITEM_F1'.
<mapping>-metafield = mmmfd_cust_03.
"也可使用自己的编号,不一定要使用预留的。此编号会在
"IF_EX_ME_PROCESS_PO_CUST~FIELDSELECTION_ITEM中使用到
WHEN 'ZZ_ITEM_F2'. <mapping>-metafield = 99000000.
WHEN 'FIELD1'. <mapping>-metafield = 99000001.
WHEN 'FIELD2'. <mapping>-metafield = 99000002.
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)字段可见性、可输入状态设置
METHOD if_ex_me_process_po_cust~fieldselection_item.
**********Item增强子屏幕字段可输入性设置*****************
DATA: l_persistent TYPE mmpur_bool.
FIELD-SYMBOLS: <fs> LIKE LINE OF ch_fieldselection.
LOOP AT ch_fieldselection ASSIGNING <fs>.
CASE <fs>-metafield.
WHEN 99000001."需特殊处理的字段
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,头数据处理,如校验
METHOD if_ex_me_process_po_cust~process_header.
*********Header头数据可以在此校验*****************
DATA: lw_mepoheader TYPE mepoheader.
DATA: lv_testflag TYPE c.
INCLUDE mm_messages_mac. "useful macros for message handling
lw_mepoheader = im_header->get_data( ).
IF lw_mepoheader-zz_head_f1 < 100.
* Place the cursor onto field zz_head_f1. The metafield was defined in BAdI ME_GUI_PO_CUST,
* Method MAP_DYNPRO_FIELDS.
mmpur_metafield mmmfd_cust_01.
mmpur_message_forced 'E' '00' '001' 'CI_EKKODB-ZZ_HEAD_F1 不能小于100!' '' '' ''.
* 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,头数据处理,如校验
METHOD if_ex_me_process_po_cust~process_item.
DATA: ls_mepoitem TYPE mepoitem,
ls_customer TYPE zekpo_db,
ls_tbsg TYPE tbsg,
lv_dummy TYPE c LENGTH 128.
INCLUDE mm_messages_mac. "useful macros for message handling
*---------------------------------------------------------------------*
* here we check customers data
*---------------------------------------------------------------------*
ls_mepoitem = im_item->get_data( ).
IF ls_mepoitem-loekz EQ 'D'."如果是要删除Item时,删除标识好像是 L ?
* a physical deletion of the item was carried out. therrefor we have to
* delete customer data on the level of the item
ls_customer-ebeln = ls_mepoitem-ebeln.
ls_customer-ebelp = ls_mepoitem-ebelp.
CALL FUNCTION 'Z_PO_SUBSCREEN_GRP_SET_DATA'
EXPORTING
im_data = ls_customer
im_physical_delete_request = 'X'.
ELSE."否则为更新与新增操作,则需进行数据有效检测
* update/insert operation
CALL FUNCTION 'Z_PO_SUBSCREEN_GRP_GET_DATA'
EXPORTING
im_ebeln = ls_mepoitem-ebeln
im_ebelp = ls_mepoitem-ebelp
IMPORTING
ex_data = ls_customer.
* check customers data
* check field field1. This should be carried out only for new items. Once the PO is posted the
* field should no longer be changeable. This is done in Method FIELDSELECTION_ITEM.
IF im_item->is_persistent( ) EQ mmpur_no."如果Item 未持久化过,才需要进行检测扩展字段 Field1(因为
"在IF_EX_ME_PROCESS_PO_CUST~FIELDSELECTION_ITEM方法中已经对该字段进行了设置:所在Item入库后就不能再修改)
IF ls_customer-field1 IS INITIAL."未持久化过,且扩展字段field1为空时
* Place the cursor onto field field1. The metafield was defined in BAdI ME_GUI_PO_CUST,
* Method MAP_DYNPRO_FIELDS.
mmpur_metafield 99000001."出错后定位到出错字段
MESSAGE e083(me) WITH 'ZEKPO_DB-FIELD1不能为空!' '' INTO lv_dummy.
mmpur_message_forced sy-msgty sy-msgid sy-msgno
sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
CALL METHOD im_item->invalidate( ).
ELSE."如果不为空,且未持久化过时
* check whether the field is valid 检测数据的合法性
"SELECT SINGLE * FROM tbsg INTO ls_tbsg WHERE bsgru EQ ls_customer-badi_bsgru.
IF ls_customer-field1< 100.
mmpur_metafield 99000001."出错后定位到出错字段
MESSAGE e083(me) WITH 'ZEKPO_DB-FIELD1不能小于100!' space INTO lv_dummy.
mmpur_message_forced sy-msgty sy-msgid sy-msgno
sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
* invalidate the object
CALL METHOD im_item->invalidate( ).
ENDIF.
ENDIF.
ENDIF.
* check field field2 扩展字段Field2不管是已持久化过都需要检测
IF ls_customer-field2IS INITIAL.
MESSAGE e083(me) WITH 'ZEKPO_DB-FIELD2不能为空!' space INTO lv_dummy.
mmpur_message_forced sy-msgty sy-msgid sy-msgno
sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
CALL METHOD im_item->invalidate( ).
mmpur_metafield 99000002.
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函数来获取):
TABLES : tstc,tadir,modsapt,modact,trdir,tfdir,enlfdir,sxs_attrt ,tstct.
DATA : jtab LIKE tadir OCCURS 0 WITH HEADER LINE.
DATA : field1(30).
DATA : v_devclass LIKE tadir-devclass.
PARAMETERS : p_tcode LIKE tstc-tcode,"根据Tcode来查
p_pgmna LIKE tstc-pgmna ."或直接指定主程序
DATA wa_tadir TYPE tadir.
START-OF-SELECTION.
IF NOT p_tcode IS INITIAL.
"根据Tcode查找对应的主程序
SELECT SINGLE * FROM tstc WHERE tcode EQ p_tcode.
ELSEIF NOT p_pgmna IS INITIAL.
tstc-pgmna = p_pgmna.
ENDIF.
IF sy-subrc EQ 0.
"查找程序所对应的开发类(包)
SELECT SINGLE * FROM tadir WHERE pgmid = 'R3TR' AND object = 'PROG' AND obj_name = tstc-pgmna.
v_devclass = tadir-devclass.
IF sy-subrc NE 0.
SELECT SINGLE * FROM trdir
WHERE name = tstc-pgmna.
IF trdir-subc EQ 'F'.
SELECT SINGLE * FROM tfdir WHERE pname = tstc-pgmna.
SELECT SINGLE * FROM enlfdir WHERE funcname = tfdir-funcname.
SELECT SINGLE * FROM tadir WHERE pgmid = 'R3TR' AND object = 'FUGR' AND obj_name EQ enlfdir-area.
MOVE : tadir-devclass TO v_devclass.
ENDIF.
ENDIF.
SELECT * FROM tadir INTO TABLE jtab WHERE pgmid = 'R3TR' AND object IN ('SMOD', 'SXSD') AND devclass = v_devclass.
SELECT SINGLE * FROM tstct WHERE sprsl EQ sy-langu AND tcode EQ p_tcode.
FORMAT COLOR COL_POSITIVE INTENSIFIED OFF.
WRITE:/(19) 'Transaction Code - ', 20(20) p_tcode, 45(50) tstct-ttext.
SKIP.
IF NOT jtab[] IS INITIAL.
WRITE:/(105) sy-uline.
FORMAT COLOR COL_HEADING INTENSIFIED ON.
* Sorting the internal Table
SORT jtab BY object.
DATA : wf_txt(60) TYPE c, wf_smod TYPE i , wf_badi TYPE i , wf_object2(30) TYPE c.
CLEAR : wf_smod, wf_badi , wf_object2.
* Get the total SMOD.
LOOP AT jtab INTO wa_tadir.
AT FIRST.
FORMAT COLOR COL_HEADING INTENSIFIED ON.
WRITE:/1 sy-vline,
2 'Enhancement/ Business Add-in',
41 sy-vline ,
42 'Description',
105 sy-vline.
WRITE:/(105) sy-uline.
ENDAT.
CLEAR wf_txt.
AT NEW object.
IF wa_tadir-object = 'SMOD'.
wf_object2 = 'Enhancement' .
ELSEIF wa_tadir-object = 'SXSD'.
wf_object2 = ' Business Add-in'.
ENDIF.
FORMAT COLOR COL_GROUP INTENSIFIED ON.
WRITE:/1 sy-vline,
2 wf_object2,
105 sy-vline.
ENDAT.
CASE wa_tadir-object.
WHEN 'SMOD'.
wf_smod = wf_smod + 1.
SELECT SINGLE modtext INTO wf_txt
FROM modsapt
WHERE sprsl = sy-langu
AND name = wa_tadir-obj_name.
FORMAT COLOR COL_NORMAL INTENSIFIED OFF.
WHEN 'SXSD'.
* For BADis
wf_badi = wf_badi + 1 .
SELECT SINGLE text INTO wf_txt
FROM sxs_attrt
WHERE sprsl = sy-langu
AND exit_name = wa_tadir-obj_name.
FORMAT COLOR COL_NORMAL INTENSIFIED ON.
ENDCASE.
WRITE:/1 sy-vline,
2 wa_tadir-obj_name HOTSPOT ON,
41 sy-vline ,
42 wf_txt,
105 sy-vline.
AT END OF object.
WRITE : /(105) sy-uline.
ENDAT.
ENDLOOP.
WRITE:/(105) sy-uline.
SKIP.
FORMAT COLOR COL_TOTAL INTENSIFIED ON.
WRITE:/ 'No.of Exits:' , wf_smod.
WRITE:/ 'No.of BADis:' , wf_badi.
ELSE.
FORMAT COLOR COL_NEGATIVE INTENSIFIED ON.
WRITE:/(105) 'No userexits or BADis exist'.
ENDIF.
ELSE.
FORMAT COLOR COL_NEGATIVE INTENSIFIED ON.
WRITE:/(105) 'Transaction does not exist'.
ENDIF.
AT LINE-SELECTION.
DATA : wf_object TYPE tadir-object.
CLEAR wf_object.
GET CURSOR FIELD field1.
CHECK field1(8) EQ 'WA_TADIR'.
READ TABLE jtab WITH KEY obj_name = sy-lisel+1(20).
MOVE jtab-object TO wf_object.
CASE wf_object.
WHEN 'SMOD'.
SET PARAMETER ID 'MON' FIELD sy-lisel+1(10).
CALL TRANSACTION 'SMOD' AND SKIP FIRST SCREEN.
WHEN 'SXSD'.
SET PARAMETER ID 'EXN' FIELD sy-lisel+1(20).
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里的字段显示出来 不是没显示出来,是没有展开(Append与Include其实都是可以展开的):
l 在复制表时,“.Append”会丢失,但Append中的字段会被拷贝过来,但Include与之不同,包括 .INCLUDE 与其字段都会被拷贝过来,这进一步证实了 .INCLUDE 结构是可以重复使用的,但Append结构不能
点新增按钮,出现新建Append结构对话框:
CURR类型的字段还需要参照表与字段:
SE14调整表
如果表的结构修改后,不能激活或激活失败,此时可以使用SE14重新对表进行调整即可:
若选择“删除数据”,会运载整个数据库表进行清空,包括其他Client端的数据,请谨慎选择