经过各种折腾,现在终于把我的采购收货的FIORI APP做成功,测试正常,并发布到了我的SAP 服务器的Gateway上面,配置好了FioriLaunchpad后台配置,可正常访问,手机,电脑上测试都还正常,在手机中用IE打开,感觉效果挺好的,可以进行测试一下试试。
账号: ZTEST01
密码:Ut163.com
测试时可以使用4500000700左右的订单,可能数据要完整些,其它的可能不一定能收货。
在这里主要介绍SAPUI5使用的SAP后台ODATA的源码,此ODATA主要使用CDS创建,其它SAPUI5源码参看这里。
1.创建CDS
在HANA Studio上创建CDS上创建需要登陆的CDS,设想是从创建订单的按计划收货行EKET汇总出计划收货数量、已收货数据,所以第一步的CDS:ZMM_B_GR_EKET先取出需要收货的EKET数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
@AbapCatalog.sqlViewName: 'ZMM_B_GR_EKETV' @AbapCatalog.compiler.compareFilter: true @AbapCatalog.preserveKey: true @AccessControl.authorizationCheck: #CHECK @EndUserText.label: '需收货明细' define view ZMM_B_GR_EKET as select from eket as _eket association [1..1] to ekpo as _ekpo on $projection.ebeln = _ekpo.ebeln and $projection.ebelp = _ekpo.ebelp association [1..1] to ekko as _ekko on $projection.ebeln = _ekko.ebeln { @ObjectModel.readOnly: true key _eket.ebeln , @ObjectModel.readOnly: true key _eket.ebelp , @ObjectModel.readOnly: true key _eket.etenr , @ObjectModel.readOnly: true _ekpo.matnr , @ObjectModel.readOnly: true _ekpo.txz01 , _ekpo.werks , _ekpo.lgort , _eket.charg , @ObjectModel.readOnly: true _eket.eindt , --atpdate, @ObjectModel.readOnly: true _eket.eldat , @ObjectModel.readOnly: true @Semantics.quantity.unitOfMeasure: 'meins' _eket.menge ,--计划数量 @ObjectModel.readOnly: true @Semantics.quantity.unitOfMeasure: 'meins' _eket.wemng ,--已收货数量 @ObjectModel.readOnly: true @Semantics.quantity.unitOfMeasure: 'meins' cast( ( _eket.menge - _eket.wemng ) as obmng ) as OBMNG,--未收货量 @Semantics.quantity.unitOfMeasure: 'meins' cast( ( _eket.menge - _eket.wemng ) as zshsl ) as zshsl,--收货数量,默认全量收未清数量。 @ObjectModel.readOnly: true _ekpo.meins, @ObjectModel.readOnly: true _ekko.lifnr, case when _eket.menge > 70 then 'C' else '' end as en_migo_ok, _ekpo, _ekko } where ( _ekpo.bstyp = 'F' or _ekpo.bstyp = 'L' ) and( _ekpo.loekz = '' or _ekpo.loekz = 'S' ) and _ekpo.elikz = '' and _ekpo.stapo = '' and( _ekpo.pstyp = '0' or _ekpo.pstyp = '1' or _ekpo.pstyp = '2' or _ekpo.pstyp = '3' or _ekpo.pstyp = '4' or _ekpo.pstyp = '6' or _ekpo.pstyp = '7' or _ekpo.pstyp = '8' or _ekpo.pstyp = '9' ) and( ( _eket.wemng < _eket.menge and _eket.dabmg < _eket.menge // open request lines ) or( _eket.menge = 0 and _eket.mng02 > 0 ) //confirmation lines ) and _eket.nodisp = '' and _ekpo.statu <> 'V' and _ekpo.arsnr = '0000000000' and _eket.eindt <> '00000000' |
第二个CDS按收货计划汇总到采购项目,生成明细行的CDS:ZMM_B_GR_EKPO
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
@AbapCatalog.sqlViewName: 'ZMM_B_GR_EKPOV' @AbapCatalog.compiler.compareFilter: true @AbapCatalog.preserveKey: true @AccessControl.authorizationCheck: #CHECK @EndUserText.label: 'EKPO FOR GR' define view ZMM_B_GR_EKPO as select from ZMM_B_GR_EKET as _eket { key _eket.ebeln, key _eket.ebelp, sum(menge) as menge, --订单量 = 计划数量 = wemng + obmng sum(wemng) as wemng , --已收货数量 sum(OBMNG) as obmng --未收货量 } group by _eket.ebeln, _eket.ebelp |
第三个CDS才是为前台最后使用的ODATA的CDS,取出相关前台需要用的数据信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
@AbapCatalog.sqlViewName: 'ZMM_C_POEKPO2V' @AbapCatalog.compiler.compareFilter: true @AbapCatalog.preserveKey: true @AccessControl.authorizationCheck: #CHECK @EndUserText.label: '无BOPF收货' @ObjectModel: {createEnabled: true, updateEnabled: true} define view ZMM_C_POEKPO2 as select from ZMM_B_GR_EKPO as _gr_ekpo left outer join ekko as _ekko on _ekko.ebeln = _gr_ekpo.ebeln left outer join ekpo as _ekpo on _gr_ekpo.ebeln = _ekpo.ebeln and _gr_ekpo.ebelp = _ekpo.ebelp left outer join makt as _makt on _ekpo.matnr = _makt.matnr left outer join lfa1 as _lfa1 on _lfa1.lifnr = _ekko.lifnr association [0..1] to I_Plant as _Plant on $projection.werks1 = _Plant.Plant association [0..1] to I_StorageLocation as _Storage on $projection.lgort1 = _Storage.StorageLocation and $projection.werks1 = _Storage.Plant { @ObjectModel.readOnly: true key _gr_ekpo.ebeln, @ObjectModel.readOnly: true key _gr_ekpo.ebelp, @ObjectModel.readOnly: true _ekpo.matnr, @ObjectModel.readOnly: true case _ekpo.matnr when '' then _ekpo.txz01 else _makt.maktx end as maktx, @ObjectModel.mandatory: true @ObjectModel.foreignKey.association: '_Plant' _ekpo.werks as werks1,--订单工厂 @ObjectModel.mandatory: true @ObjectModel.foreignKey.association: '_Storage' _ekpo.lgort as lgort1,--订单地点 @ObjectModel.readOnly: true _ekpo.werks as werks2,--收货工厂 @ObjectModel.readOnly: true _ekpo.lgort as lgort2,--收货地点 cast( '' as charg_d ) as charg, @ObjectModel.readOnly: true @Semantics.quantity.unitOfMeasure: 'meins' _gr_ekpo.menge ,--计划数量 @ObjectModel.readOnly: true @Semantics.quantity.unitOfMeasure: 'meins' _gr_ekpo.wemng ,--已收货数量 @ObjectModel.readOnly: true @Semantics.quantity.unitOfMeasure: 'meins' _gr_ekpo.obmng,--未收货量 @Semantics.quantity.unitOfMeasure: 'meins' cast( _gr_ekpo.obmng as zshsl ) as zshsl,--收货数量,默认全量收未清数量。 @ObjectModel.readOnly: true _ekpo.meins, @ObjectModel.readOnly: true // @ObjectModel.mandatory: true // @ObjectModel.foreignKey.association: '_supplier' _ekko.lifnr, @ObjectModel.readOnly: true _lfa1.name1 as name1, $session.system_date as budat, @ObjectModel.association: { type: [#TO_COMPOSITION_CHILD] } _Plant, _Storage } |
- 1.在此CDS中使用了:createEnabled: true,updateEnabled: true,原来设想是在CDS的POST,PUT方法中写确认收货的功能,但后面发现实现起来不是那么好,所以此功能没使用,此参数也就可以去除。
- 2.此CDS没有使用@OData.publish: true参数,所以此CDS没有直接生成ODATA所以需要在后面在SEGW中引用此CDS生成ODATA。
2.在SEGW中使用CDS
在事务码SEGW中使用已有的ODATA项目(也可以创建自己的新的ODATA项目),增加引用前面定义的CDS:ZMM_C_POEKPO2,更多学习可参考这里。
引用完成后记得要生成生成一下ODATA,生成完成后在测试时就能得到可收货贩采购订单明细数据了。
3.写采购收货功能函数
在SEGW中增加一个Function Imports,用于用户点收货时,执行GR的MIGO操作。
3.1.1.创建返回结构
SE11先创建一个结构,用于收货函数返回结果使用,并在SEGW中使用此结果生成Entity Types,如图
3.2.2.SEGW创建函数引用
在SEGW中创建一个Function Import,名为:ZFM_GR_MIGO,此名字可以自己随便时,后在后面函数中会使用到,其实这并不是真实的SAP函数名。并使用一个Function Import Parameters,名为data1,此名字在后面的函数及SAPUI5中会使用到。
- 在此定义Return type中使用前面定义的ZGR_RETURN,
- HTTP Method Type 使用GET ,这里也能使用POST,此值在SAPUI5调用时会需要使用要相同的方法,便我总觉得这里设置成POST与真正的POST使用感觉还是不太一样。具体的只自己感觉去吧。
- 返回参数的DATA1定义为STRING类型,其实在SAPUI5调用时,传入的数据内容是把JSON的结构数据转换为STRING传到这里,然后在后面的函数中会把此DATA1的值转转换为JOSN格式的结构化数据,感觉上Function Import在SAPUI5上使用时都是这样子使用的。
3.3.3.写函数的功能代码
在前面创建引用函数功能后,需要生成一下项目,转到项目的扩展类**_DPC_EXT,进入类中,找到/IWBEP/IF_MGW_APPL_SRV_RUNTIME~EXECUTE_ACTION方法,编辑类,并进行重定义此方法。
在类中写下收货的代码,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
METHOD /IWBEP/IF_MGW_APPL_SRV_RUNTIME~EXECUTE_ACTION. DATA: LS_PARAMETER TYPE /IWBEP/S_MGW_NAME_VALUE_PAIR. DATA: L_JSON TYPE STRING, LS_RECORD TYPE ZMM_C_POEKPO2V, LT_RECORD TYPE TABLE OF ZMM_C_POEKPO2V, LT_RETURN TYPE TABLE OF ZGR_RETURN, LS_RETURN TYPE ZGR_RETURN. * BREAK YANGSEN. IF IV_ACTION_NAME = 'ZFM_GR_MIGO'. " Check what action is being requested READ TABLE IT_PARAMETER INTO LS_PARAMETER WITH KEY NAME = 'data1'. IF SY-SUBRC = 0 . L_JSON = LS_PARAMETER-VALUE. /UI2/CL_JSON=>DESERIALIZE( EXPORTING JSON = L_JSON CHANGING DATA = LT_RECORD ). "收货过账。 IF LT_RECORD IS NOT INITIAL. CALL FUNCTION 'ZMM_PO_MIGO_GR2' TABLES LT_DATA = LT_RECORD LT_RETURN = LT_RETURN. ELSE. LS_RETURN-TYPE = 'E'. LS_RETURN-MESSAGE = '传入数据异常,请检查'. * LS_RETURN-Number = 1. APPEND LS_RETURN TO LT_RETURN. ENDIF. * set returning data COPY_DATA_TO_REF( EXPORTING IS_DATA = LT_RETURN CHANGING CR_DATA = ER_DATA ). ENDIF. ENDIF. ENDMETHOD. |
- 在代码中IV_ACTION_NAME = ‘ZFM_GR_MIGO’,就是的收货功能,与我们前面定义的函数名ZFM_GR_MIGO相同,在这里面的代码就是我们收货的功能代码,
- 在其中调用/UI2/CL_JSON=>DESERIALIZE把传入的DATA1的参数转换为JSON的结构化数据。最后再调用函数ZMM_PO_MIGO_GR2来进行数据的收货处理,
- 为了定义省事,在这里直接定义传入的结构数据就是前面步骤定义CSD的结构ZMM_C_POEKPO2V,可能有很多 码中IV_ACTION_NAME = ‘ZFM_GR_MIGO’,就是的收货功能,与我们前面定义的函数名ZFM_GR_MIGO相同,在这里面的代码就是我们收货的功能代码,
- 在其中调用/UI2/CL_JSON=>DESERIALIZE把传入的DATA1的参数转换为JSON的结构化数据。最后再调用函数ZMM_PO_MIGO_GR2来进行数据的收货处理,
- 为了定义省事,在这里直接定义传入的结构数据就是前面步骤定义CSD的结构ZMM_C_POEKPO2V,可能有很多 数据冗余,其中在这里可以使用自己定义的结构,只定义创建收货时需要的字段就行,当然SAPUI5中也要对应的修改
以下是ZMM_PO_MIGO_GR2函数代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
FUNCTION ZMM_PO_MIGO_GR2. *"---------------------------------------------------------------------- *"*"本地接口: *" TABLES *" LT_DATA STRUCTURE ZMM_C_POEKPO2V *" LT_RETURN STRUCTURE ZGR_RETURN *"---------------------------------------------------------------------- DATA: LT_DATA2 TYPE TABLE OF ZMM_C_POEKPO2V, LS_DATA TYPE ZMM_C_POEKPO2V, LS_RETURN TYPE ZGR_RETURN. DATA: BEGIN OF LS_HEAD. INCLUDE STRUCTURE BAPI2017_GM_HEAD_01. DATA: END OF LS_HEAD. DATA: BEGIN OF LS_CODE. INCLUDE STRUCTURE BAPI2017_GM_CODE. DATA: END OF LS_CODE. DATA: BEGIN OF LS_MTHEAD. INCLUDE STRUCTURE BAPI2017_GM_HEAD_RET. DATA: END OF LS_MTHEAD. DATA: BEGIN OF LT_TAB OCCURS 100. INCLUDE STRUCTURE BAPI2017_GM_ITEM_CREATE. DATA: END OF LT_TAB. DATA: BEGIN OF ERRMSG OCCURS 10. INCLUDE STRUCTURE BAPIRET2. DATA: END OF ERRMSG. DATA: ERRFLAG TYPE FLAG. DATA: L_ROW TYPE INT4. *--------------------------------------------------------------------* CHECK LT_DATA[] IS NOT INITIAL. READ TABLE LT_DATA INTO LS_DATA INDEX 1. *--------------------------------------------------------------------* LT_DATA2 = LT_DATA[]. SORT LT_DATA2 BY EBELN. DELETE ADJACENT DUPLICATES FROM LT_DATA2 COMPARING EBELN. L_ROW = LINES( LT_DATA2 ). IF L_ROW > 1. LS_RETURN-TYPE = 'E'. LS_RETURN-MESSAGE = '一次只能对一个采购单进行收货'. APPEND LS_RETURN TO LT_RETURN. RETURN. ENDIF. *--------------------------------------------------------------------* LS_HEAD-PSTNG_DATE = LS_DATA-BUDAT. LS_HEAD-DOC_DATE = SY-DATUM. LS_HEAD-PR_UNAME = SY-UNAME. LS_CODE-GM_CODE = '01'. "01 - MB01 - Goods Receipts for Purchase Order LOOP AT LT_DATA INTO LS_DATA. LT_TAB-MOVE_TYPE = '101'. LT_TAB-MVT_IND = 'B'. LT_TAB-PLANT = LS_DATA-WERKS1. LT_TAB-STGE_LOC = LS_DATA-LGORT1. LT_TAB-MATERIAL = LS_DATA-MATNR. LT_TAB-ENTRY_QNT = LS_DATA-ZSHSL. LT_TAB-PO_NUMBER = LS_DATA-EBELN . LT_TAB-PO_ITEM = LS_DATA-EBELP . APPEND LT_TAB. ENDLOOP. CALL FUNCTION 'BAPI_GOODSMVT_CREATE' EXPORTING GOODSMVT_HEADER = LS_HEAD GOODSMVT_CODE = LS_CODE * TESTRUN = ' ' IMPORTING GOODSMVT_HEADRET = LS_MTHEAD * MATERIALDOCUMENT = * MATDOCUMENTYEAR = TABLES GOODSMVT_ITEM = LT_TAB * GOODSMVT_SERIALNUMBER = RETURN = ERRMSG. CLEAR ERRFLAG. LOOP AT ERRMSG. IF ERRMSG-TYPE EQ 'E'. " WRITE:/'Error in function', ERRMSG-MESSAGE. ERRFLAG = 'X'. MOVE-CORRESPONDING ERRMSG TO LS_RETURN. APPEND LS_RETURN TO LT_RETURN. ELSE. " WRITE:/ ERRMSG-MESSAGE. ENDIF. ENDLOOP. CLEAR:LS_RETURN. IF ERRFLAG IS INITIAL. COMMIT WORK AND WAIT. LS_RETURN-MBLNR = LS_MTHEAD-MAT_DOC. LS_RETURN-MJAHR = LS_MTHEAD-DOC_YEAR. LS_RETURN-TYPE = 'S'. LS_RETURN-MESSAGE = '收货成功'. APPEND LS_RETURN TO LT_RETURN. ELSE. ROLLBACK WORK. ENDIF. ENDFUNCTION. |
4.扩展
1.到这里ODATA就算是完成了,激活后此ODATA就可以使用了,在现实使用中SAPUI5代码读取ODATA时默认就读取的CDS的数据(此应该也在UI5代码中应该也是可以修改了默认不读取的),但我们真实收货时,可能需要默认不显示需收货的数据,所以我重定义了ZMM_C_POEKPO2_GET_ENTITYSET方法,当查询条件为空时,不查询数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
method ZMM_C_POEKPO2_GET_ENTITYSET. IF IV_FILTER_STRING = ''. * IV_FILTER_STRING ( ebeln eq '22222222222' ) STRING CString 26 RETURN. ENDIF. TRY. CALL METHOD SUPER->ZMM_C_POEKPO2_GET_ENTITYSET EXPORTING IV_ENTITY_NAME = IV_ENTITY_NAME IV_ENTITY_SET_NAME = IV_ENTITY_SET_NAME IV_SOURCE_NAME = IV_SOURCE_NAME IT_FILTER_SELECT_OPTIONS = IT_FILTER_SELECT_OPTIONS IS_PAGING = IS_PAGING IT_KEY_TAB = IT_KEY_TAB IT_NAVIGATION_PATH = IT_NAVIGATION_PATH IT_ORDER = IT_ORDER IV_FILTER_STRING = IV_FILTER_STRING IV_SEARCH_STRING = IV_SEARCH_STRING IO_TECH_REQUEST_CONTEXT = IO_TECH_REQUEST_CONTEXT IMPORTING ET_ENTITYSET = ET_ENTITYSET ES_RESPONSE_CONTEXT = ES_RESPONSE_CONTEXT . CATCH /IWBEP/CX_MGW_BUSI_EXCEPTION. CATCH /IWBEP/CX_MGW_TECH_EXCEPTION. ENDTRY. endmethod. |
现在ODATA就完全完成后,后一步就是SAPUI5做FIORI APP了,可参考这里。