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端的數據,請謹慎選擇