引言:
RAP行为增强的本质,是一个基于“声明-实现”分离模型的协同系统。RAP为开发者定义了行为的每个构件,并提供了从BDL(行为定义)到行为池(实现),再到EML(操作API)的清晰实现路径
这种将“做什么”与“怎么做”清晰分离的设计,正是RAP框架构建可维护、可扩展应用的基石。
1. RAP 行为增强架构概述
RAP应用的行为定义,核心在于实现“关注点分离”(Separation of Concerns):通过BDL(行为定义语言)声明式地描述业务意图,再通过ABAP行为池(Behavior Pool)中的代码来实现这些逻辑。为 RAP 业务对象增加校验、自动计算、用户动作等业务逻辑,并最终投射到 Fiori UI 上,实现“后端→前端”的完整增强链路。
更具体地,它展示了三个核心:
行为定义语言(BDL):行为建模的源头是行为定义语言(Behavior Definition Language, BDL)源文件(
.bdef)。它是位于RAP架构“接口层”的专用语言,负责以声明式语法描述业务对象对各种操作请求的响应。这比传统ABAP编程更关注“是什么”而非“怎么做”,从而保证了行为的一致性。BDL之下的行为实现:如果BDL是“图纸”,那ABAP行为池(Behavior Pool)就是施工队。它是一个全局ABAP类,包含了所有被声明行为的实现方法。开发者就在这里编写
MODIFY或FOR INSTANCE FEATURES等具体逻辑。EML:行为实现的“通用语”,为了连接“意图”和“实现”,RAP提供了实体操作语言(Entity Manipulation Language, EML)。它是行为池内读取、修改业务对象的标准API。它同时支持开发(在自身行为池内可使用
IN LOCAL MODE绕过管控)和消费(跨BO或测试时不可使用)等场景。
2.行为定义增强构件详解
ZI_RAP_ATRAV_KJ的Travel 实体的行为定义:
2.1 指定行为实现类(Behavior Pool)
关键代码:
implementation in class zbp_i_rap_travel_#### unique
解析:设计意图
implementation in class将 BO 的声明与实现分离。声明在 BDEF 中(本文件),实现在 ABAP 行为池类中,unique表示该类是该实体行为实现的唯一提供者,避免冲突。为什么这么设计?
符合“声明式编程”范式:你只需告诉框架“这个实体有这些行为”,具体如何实现放在另一个关注点。这使得行为定义清晰、可读,且能被工具(如 Fiori 元素)直接解析。
2.2 静态字段控制 – readonly
操作:
field ( readonly ) TravelID, TotalPrice, TravelStatus; field ( readonly ) LastChangedAt, LastChangedBy, CreatedAt, CreatedBy, LocalLastChangedAt;
解析:核心概念 – 静态特性控制
readonly是编译时生效的字段控制。RAP 运行时自动生成 OData 注解@UI.identification: [ { type: #READ_ONLY } ]。为什么不用在 EML 中判断?
在普通消费场景(Consume),EML 的MODIFY会忽略这些字段;但在 BO 内部(IN LOCAL MODE)可以绕过。这保证了业务核心字段不会被外部随意篡改,同时内部逻辑仍有灵活性。
2.3 静态字段控制 – mandatory
操作:
field ( mandatory ) AgencyID, CustomerID;
解析:注意陷阱
mandatory仅影响 UI 表现(红星标记),不生成后台校验。你必须实现对应的 validation(本例中
validateAgency/validateCustomer),否则空值仍可通过 EML 或 OData 保存。为什么这么设计?
将“前端提示”与“后台强校验”分离,给开发者更精细的控制:有些字段可能只在特定场景下才真正必须校验。
2.4 添加 actions(动作)
关键代码:
action ( features : instance ) acceptTravel result [1] $self; action ( features : instance ) rejectTravel result [1] $self; internal action recalcTotalPrice;
解析:Action 的设计哲学
对外 action:
acceptTravel/rejectTravel会作为 OData 操作暴露,供 UI 或外部系统调用。( features : instance )表示动态特性控制,运行时通过get_instance_features方法判断该实例上此动作是否可用(例如:已审批的 Travel 不应再次显示“审批”按钮)。result [1] $self:返回当前操作后的实例,便于前端刷新数据。这是遵循 RESTful 原则:操作后返回资源状态。internal action:不对外暴露,只能由 BO 内部其他 determination/action 通过 EML
EXECUTE调用。用于封装可复用的内部计算逻辑(如重算总价)。为什么区分 internal action?避免将内部实现细节暴露给外部,保持 API 干净,同时允许内部模块化。
2.5 添加 determinations(确定)
关键代码:
determination setInitialStatus on modify { create; } determination calculateTotalPrice on modify { field BookingFee, CurrencyCode; } determination calculateTravelID on save { create; }解析:Determination 的触发机制
on modify:在 EMLMODIFY操作后立即在事务缓冲区中执行(还未提交)。适用于依赖当前请求数据的派生逻辑。on save:在COMMIT ENTITIES的 Save Sequence 中执行。适用于需要最终持久化前的一次性设置(如生成主键)。为什么有两种触发时机?
性能与正确性的权衡:on modify可反复触发,适合增量更新;on save只在最后触发一次,减少不必要的计算。字段触发条件:
field BookingFee, CurrencyCode表示只有当这两个字段被修改时才触发,避免全量扫描。
2.6 添加 validations(校验)
关键代码:
validation validateAgency on save { field AgencyID; create; } validation validateCustomer on save { field CustomerID; create; } validation validateDates on save { field BeginDate, EndDate; create; }解析:校验的“守门人”角色
所有校验在
on save触发,且仅当指定字段被修改或实例为新建时执行。校验失败会通过
REPORTED结构返回消息,并阻止提交。为什么不在
on modify时校验?
用户可能在一次编辑中先后修改多个字段,中间状态可能暂时不合法(如先改结束日期再改开始日期)。on save保证了最终提交时的一致性。
ZC_RAP_ATRAV_KJ的行为定义:
增加代码:
use action acceptTravel; use action rejectTravel;解析:服务隔离与安全边界
基础 BO(
ZI_*)定义了完整的业务行为,可能包含内部使用或高权限的动作。投影 BO(
ZC_*)是面向特定消费场景的视图,通过use选择性暴露行为。为什么 actions 需要显式投射,而 determinations/validations 不需要?
Determinations/validations 是后台自动执行的,不直接暴露给消费者,因此自动继承。
Actions 是消费者主动调用的接口,必须显式声明以确保安全与可理解性。
3.CDS 元数据扩展添加 UI 按钮
替换元数据拓展的代码
替换前:
替换后:
关键代码:
@UI: { lineItem: [ { position: 90 }, { type: #FOR_ACTION, dataAction: 'acceptTravel', label: 'Accept Travel' }, { type: #FOR_ACTION, dataAction: 'rejectTravel', label: 'Reject Travel' } ], identification: [ { position: 90 }, { type: #FOR_ACTION, dataAction: 'acceptTravel', label: 'Accept Travel' }, { type: #FOR_ACTION, dataAction: 'rejectTravel', label: 'Reject Travel' } ] }解析:
在列表报表(lineItem)中,为每条旅行记录增加“接受旅行”(Accept Travel)和“拒绝旅行”(Reject Travel)两个按钮。
在对象页面(identification)的头部区域,同样增加这两个按钮。
4.实现行为池
将光标置于行为实现类名称zbp_i_rap_travel_####上,使用快捷键Ctrl+1打开快速修复对话框,然后选择create…为旅行实体生成行为实现。
一直next->finsh
行为实现的框架会生成并显示在编辑器中。在全局类选项卡上,你可以看到新增的FOR BEHAVIOR OF,这表明在本示例中,该类为指定业务对象——旅行实体提供了行为实现。
正确的实现是在本地类型选项卡上完成的,在该选项卡中,向导已生成了继承自 cl_abap_behavior_handler 的本地处理程序类lhc_hanler。
替换代码:
CLASS lhc_Travel DEFINITION INHERITING FROM cl_abap_behavior_handler. PRIVATE SECTION. CONSTANTS: BEGIN OF travel_status, open TYPE c LENGTH 1 VALUE 'O', " Open accepted TYPE c LENGTH 1 VALUE 'A', " Accepted canceled TYPE c LENGTH 1 VALUE 'X', " Cancelled END OF travel_status. METHODS calculateTotalPrice FOR DETERMINE ON MODIFY IMPORTING keys FOR Travel~calculateTotalPrice. METHODS CalculateTravelID FOR DETERMINE ON SAVE IMPORTING keys FOR travel~CalculateTravelID. METHODS setInitialStatus FOR DETERMINE ON MODIFY IMPORTING keys FOR Travel~setInitialStatus. METHODS validateAgency FOR VALIDATE ON SAVE IMPORTING keys FOR Travel~validateAgency. METHODS validatecustomer FOR VALIDATE ON SAVE IMPORTING keys FOR travel~validatecustomer. METHODS validateDates FOR VALIDATE ON SAVE IMPORTING keys FOR Travel~validateDates. METHODS acceptTravel FOR MODIFY IMPORTING keys FOR ACTION Travel~acceptTravel RESULT result. METHODS rejectTravel FOR MODIFY IMPORTING keys FOR ACTION Travel~rejectTravel RESULT result. METHODS get_features FOR FEATURES IMPORTING keys REQUEST requested_features FOR Travel RESULT result. METHODS get_authorizations FOR AUTHORIZATION IMPORTING keys REQUEST requested_authorizations FOR Travel RESULT result. METHODS recalctotalprice FOR MODIFY IMPORTING keys FOR ACTION travel~recalcTotalPrice. METHODS is_update_granted IMPORTING has_before_image TYPE abap_bool overall_status TYPE /dmo/overall_status RETURNING VALUE(update_granted) TYPE abap_bool. METHODS is_delete_granted IMPORTING has_before_image TYPE abap_bool overall_status TYPE /dmo/overall_status RETURNING VALUE(delete_granted) TYPE abap_bool. METHODS is_create_granted RETURNING VALUE(create_granted) TYPE abap_bool. ENDCLASS. CLASS lhc_Travel IMPLEMENTATION. METHOD calculateTotalPrice. MODIFY ENTITIES OF zi_rap_atrav_kj IN LOCAL MODE ENTITY travel EXECUTE recalcTotalPrice FROM CORRESPONDING #( keys ) REPORTED DATA(execute_reported). reported = CORRESPONDING #( DEEP execute_reported ). ENDMETHOD. METHOD CalculateTravelID. " Please note that this is just an example for calculating a field during onSave. " This approach does NOT ensure for gap free or unique travel IDs! It just helps to provide a readable ID. " The key of this business object is a UUID, calculated by the framework. " check if TravelID is already filled READ ENTITIES OF zi_rap_atrav_kj IN LOCAL MODE ENTITY Travel FIELDS ( TravelID ) WITH CORRESPONDING #( keys ) RESULT DATA(travels). " remove lines where TravelID is already filled. DELETE travels WHERE TravelID IS NOT INITIAL. " anything left ? CHECK travels IS NOT INITIAL. " Select max travel ID SELECT SINGLE FROM zi_rap_atrav_kj FIELDS MAX( travelID ) AS travelID INTO @DATA(max_travelid). " Set the travel ID MODIFY ENTITIES OF zi_rap_atrav_kj IN LOCAL MODE ENTITY Travel UPDATE FROM VALUE #( FOR travel IN travels INDEX INTO i ( %tky = travel-%tky TravelID = max_travelid + i %control-TravelID = if_abap_behv=>mk-on ) ) REPORTED DATA(update_reported). reported = CORRESPONDING #( DEEP update_reported ). ENDMETHOD. METHOD setInitialStatus. " Read relevant travel instance data READ ENTITIES OF zi_rap_atrav_kj IN LOCAL MODE ENTITY Travel FIELDS ( TravelStatus ) WITH CORRESPONDING #( keys ) RESULT DATA(travels). " Remove all travel instance data with defined status DELETE travels WHERE TravelStatus IS NOT INITIAL. CHECK travels IS NOT INITIAL. " Set default travel status MODIFY ENTITIES OF zi_rap_atrav_kj IN LOCAL MODE ENTITY Travel UPDATE FIELDS ( TravelStatus ) WITH VALUE #( FOR travel IN travels ( %tky = travel-%tky TravelStatus = travel_status-open ) ) REPORTED DATA(update_reported). reported = CORRESPONDING #( DEEP update_reported ). ENDMETHOD. METHOD validateAgency. " Read relevant travel instance data READ ENTITIES OF zi_rap_atrav_kj IN LOCAL MODE ENTITY Travel FIELDS ( AgencyID ) WITH CORRESPONDING #( keys ) RESULT DATA(travels). " DATA agencies TYPE SORTED TABLE OF /dmo/agency WITH UNIQUE KEY agency_id. " Optimization of DB select: extract distinct non-initial agency IDs "agencies = CORRESPONDING #( travels DISCARDING DUPLICATES MAPPING agency_id = AgencyID EXCEPT * ). "DELETE agencies WHERE agency_id IS INITIAL. "IF agencies IS NOT INITIAL. " Check if agency ID exist " SELECT FROM /dmo/agency FIELDS agency_id " FOR ALL ENTRIES IN @agencies " WHERE agency_id = @agencies-agency_id " INTO TABLE @DATA(agencies_db). "ENDIF. " Raise msg for non existing and initial agencyID LOOP AT travels INTO DATA(travel). " Clear state messages that might exist APPEND VALUE #( %tky = travel-%tky %state_area = 'VALIDATE_AGENCY' ) TO reported-travel. "IF travel-AgencyID IS INITIAL OR NOT line_exists( agencies_db[ agency_id = travel-AgencyID ] ). APPEND VALUE #( %tky = travel-%tky ) TO failed-travel. APPEND VALUE #( %tky = travel-%tky %state_area = 'VALIDATE_AGENCY' %msg = NEW zcm_rap_kj( severity = if_abap_behv_message=>severity-error textid = zcm_rap_kj=>agency_unknown agencyid = travel-AgencyID ) %element-AgencyID = if_abap_behv=>mk-on ) TO reported-travel. " ENDIF. ENDLOOP. ENDMETHOD. METHOD validatecustomer. " Read relevant travel instance data READ ENTITIES OF zi_rap_atrav_kj IN LOCAL MODE ENTITY Travel FIELDS ( CustomerID ) WITH CORRESPONDING #( keys ) RESULT DATA(travels). DATA customers TYPE SORTED TABLE OF /dmo/customer WITH UNIQUE KEY customer_id. " Optimization of DB select: extract distinct non-initial customer IDs customers = CORRESPONDING #( travels DISCARDING DUPLICATES MAPPING customer_id = CustomerID EXCEPT * ). DELETE customers WHERE customer_id IS INITIAL. IF customers IS NOT INITIAL. " Check if customer ID exist SELECT FROM /dmo/customer FIELDS customer_id FOR ALL ENTRIES IN @customers WHERE customer_id = @customers-customer_id INTO TABLE @DATA(customers_db). ENDIF. " Raise msg for non existing and initial customerID LOOP AT travels INTO DATA(travel). " Clear state messages that might exist APPEND VALUE #( %tky = travel-%tky %state_area = 'VALIDATE_CUSTOMER' ) TO reported-travel. IF travel-CustomerID IS INITIAL OR NOT line_exists( customers_db[ customer_id = travel-CustomerID ] ). APPEND VALUE #( %tky = travel-%tky ) TO failed-travel. APPEND VALUE #( %tky = travel-%tky %state_area = 'VALIDATE_CUSTOMER' %msg = NEW zcm_rap_kj( severity = if_abap_behv_message=>severity-error textid = zcm_rap_kj=>customer_unknown customerid = travel-CustomerID ) %element-CustomerID = if_abap_behv=>mk-on ) TO reported-travel. ENDIF. ENDLOOP. ENDMETHOD. METHOD validateDates. " Read relevant travel instance data READ ENTITIES OF zi_rap_atrav_kj IN LOCAL MODE ENTITY Travel FIELDS ( TravelID BeginDate EndDate ) WITH CORRESPONDING #( keys ) RESULT DATA(travels). LOOP AT travels INTO DATA(travel). " Clear state messages that might exist APPEND VALUE #( %tky = travel-%tky %state_area = 'VALIDATE_DATES' ) TO reported-travel. IF travel-EndDate < travel-BeginDate. APPEND VALUE #( %tky = travel-%tky ) TO failed-travel. APPEND VALUE #( %tky = travel-%tky %state_area = 'VALIDATE_DATES' %msg = NEW zcm_rap_kj( severity = if_abap_behv_message=>severity-error textid = zcm_rap_kj=>date_interval begindate = travel-BeginDate enddate = travel-EndDate travelid = travel-TravelID ) %element-BeginDate = if_abap_behv=>mk-on %element-EndDate = if_abap_behv=>mk-on ) TO reported-travel. ELSEIF travel-BeginDate < cl_abap_context_info=>get_system_date( ). APPEND VALUE #( %tky = travel-%tky ) TO failed-travel. APPEND VALUE #( %tky = travel-%tky %state_area = 'VALIDATE_DATES' %msg = NEW zcm_rap_kj( severity = if_abap_behv_message=>severity-error textid = zcm_rap_kj=>begin_date_before_system_date begindate = travel-BeginDate ) %element-BeginDate = if_abap_behv=>mk-on ) TO reported-travel. ENDIF. ENDLOOP. ENDMETHOD. METHOD acceptTravel. " Set the new overall status MODIFY ENTITIES OF zi_rap_atrav_kj IN LOCAL MODE ENTITY Travel UPDATE FIELDS ( TravelStatus ) WITH VALUE #( FOR key IN keys ( %tky = key-%tky TravelStatus = travel_status-accepted ) ) FAILED failed REPORTED reported. " Fill the response table READ ENTITIES OF zi_rap_atrav_kj IN LOCAL MODE ENTITY Travel ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(travels). result = VALUE #( FOR travel IN travels ( %tky = travel-%tky %param = travel ) ). ENDMETHOD. METHOD rejectTravel. " Set the new overall status MODIFY ENTITIES OF zi_rap_atrav_kj IN LOCAL MODE ENTITY Travel UPDATE FIELDS ( TravelStatus ) WITH VALUE #( FOR key IN keys ( %tky = key-%tky TravelStatus = travel_status-canceled ) ) FAILED failed REPORTED reported. " Fill the response table READ ENTITIES OF zi_rap_atrav_kj IN LOCAL MODE ENTITY Travel ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(travels). result = VALUE #( FOR travel IN travels ( %tky = travel-%tky %param = travel ) ). ENDMETHOD. METHOD get_features. " Read the travel status of the existing travels READ ENTITIES OF zi_rap_atrav_kj IN LOCAL MODE ENTITY Travel FIELDS ( TravelStatus ) WITH CORRESPONDING #( keys ) RESULT DATA(travels) FAILED failed. result = VALUE #( FOR travel IN travels LET is_accepted = COND #( WHEN travel-TravelStatus = travel_status-accepted THEN if_abap_behv=>fc-o-disabled ELSE if_abap_behv=>fc-o-enabled ) is_rejected = COND #( WHEN travel-TravelStatus = travel_status-canceled THEN if_abap_behv=>fc-o-disabled ELSE if_abap_behv=>fc-o-enabled ) IN ( %tky = travel-%tky %action-acceptTravel = is_accepted %action-rejectTravel = is_rejected ) ). ENDMETHOD. METHOD get_authorizations. DATA: has_before_image TYPE abap_bool, is_update_requested TYPE abap_bool, is_delete_requested TYPE abap_bool, update_granted TYPE abap_bool, delete_granted TYPE abap_bool. DATA: failed_travel LIKE LINE OF failed-travel. " Read the existing travels READ ENTITIES OF zi_rap_atrav_kj IN LOCAL MODE ENTITY Travel FIELDS ( TravelStatus ) WITH CORRESPONDING #( keys ) RESULT DATA(travels) FAILED failed. CHECK travels IS NOT INITIAL. * In this example the authorization is defined based on the Activity + Travel Status * For the Travel Status we need the before-image from the database. We perform this for active (is_draft=00) as well as for drafts (is_draft=01) as we can't distinguish between edit or new drafts SELECT FROM zrap_atrav_kj FIELDS travel_uuid,overall_status FOR ALL ENTRIES IN @travels WHERE travel_uuid EQ @travels-TravelUUID ORDER BY PRIMARY KEY INTO TABLE @DATA(travels_before_image). is_update_requested = COND #( WHEN requested_authorizations-%update = if_abap_behv=>mk-on OR requested_authorizations-%action-acceptTravel = if_abap_behv=>mk-on OR requested_authorizations-%action-rejectTravel = if_abap_behv=>mk-on OR * requested_authorizations-%action-Prepare = if_abap_behv=>mk-on OR * requested_authorizations-%action-Edit = if_abap_behv=>mk-on OR requested_authorizations-%assoc-_Booking = if_abap_behv=>mk-on THEN abap_true ELSE abap_false ). is_delete_requested = COND #( WHEN requested_authorizations-%delete = if_abap_behv=>mk-on THEN abap_true ELSE abap_false ). LOOP AT travels INTO DATA(travel). update_granted = delete_granted = abap_false. READ TABLE travels_before_image INTO DATA(travel_before_image) WITH KEY travel_uuid = travel-TravelUUID BINARY SEARCH. has_before_image = COND #( WHEN sy-subrc = 0 THEN abap_true ELSE abap_false ). IF is_update_requested = abap_true. " Edit of an existing record -> check update authorization IF has_before_image = abap_true. update_granted = is_update_granted( has_before_image = has_before_image overall_status = travel_before_image-overall_status ). IF update_granted = abap_false. APPEND VALUE #( %tky = travel-%tky %msg = NEW zcm_rap_kj( severity = if_abap_behv_message=>severity-error textid = zcm_rap_kj=>unauthorized ) ) TO reported-travel. ENDIF. " Creation of a new record -> check create authorization ELSE. update_granted = is_create_granted( ). IF update_granted = abap_false. APPEND VALUE #( %tky = travel-%tky %msg = NEW zcm_rap_kj( severity = if_abap_behv_message=>severity-error textid = zcm_rap_kj=>unauthorized ) ) TO reported-travel. ENDIF. ENDIF. ENDIF. IF is_delete_requested = abap_true. delete_granted = is_delete_granted( has_before_image = has_before_image overall_status = travel_before_image-overall_status ). IF delete_granted = abap_false. APPEND VALUE #( %tky = travel-%tky %msg = NEW zcm_rap_kj( severity = if_abap_behv_message=>severity-error textid = zcm_rap_kj=>unauthorized ) ) TO reported-travel. ENDIF. ENDIF. APPEND VALUE #( %tky = travel-%tky %update = COND #( WHEN update_granted = abap_true THEN if_abap_behv=>auth-allowed ELSE if_abap_behv=>auth-unauthorized ) %action-acceptTravel = COND #( WHEN update_granted = abap_true THEN if_abap_behv=>auth-allowed ELSE if_abap_behv=>auth-unauthorized ) %action-rejectTravel = COND #( WHEN update_granted = abap_true THEN if_abap_behv=>auth-allowed ELSE if_abap_behv=>auth-unauthorized ) * %action-Prepare = COND #( WHEN update_granted = abap_true THEN if_abap_behv=>auth-allowed ELSE if_abap_behv=>auth-unauthorized ) * %action-Edit = COND #( WHEN update_granted = abap_true THEN if_abap_behv=>auth-allowed ELSE if_abap_behv=>auth-unauthorized ) %assoc-_Booking = COND #( WHEN update_granted = abap_true THEN if_abap_behv=>auth-allowed ELSE if_abap_behv=>auth-unauthorized ) %delete = COND #( WHEN delete_granted = abap_true THEN if_abap_behv=>auth-allowed ELSE if_abap_behv=>auth-unauthorized ) ) TO result. ENDLOOP. ENDMETHOD. METHOD is_update_granted. IF has_before_image = abap_true. AUTHORITY-CHECK OBJECT 'ZOSTATKJ' ID 'ZOSTATKJ' FIELD overall_status ID 'ACTVT' FIELD '02'. ELSE. AUTHORITY-CHECK OBJECT 'ZOSTATKJ' ID 'ZOSTATKJ' DUMMY ID 'ACTVT' FIELD '02'. ENDIF. update_granted = COND #( WHEN sy-subrc = 0 THEN abap_true ELSE abap_false ). " Simulate full access - for testing purposes only! Needs to be removed for a productive implementation. update_granted = abap_true. ENDMETHOD. METHOD is_delete_granted. IF has_before_image = abap_true. AUTHORITY-CHECK OBJECT 'ZOSTATKJ' ID 'ZOSTATKJ' FIELD overall_status ID 'ACTVT' FIELD '06'. ELSE. AUTHORITY-CHECK OBJECT 'ZOSTATKJ' ID 'ZOSTATKJ' DUMMY ID 'ACTVT' FIELD '06'. ENDIF. delete_granted = COND #( WHEN sy-subrc = 0 THEN abap_true ELSE abap_false ). " Simulate full access - for testing purposes only! Needs to be removed for a productive implementation. delete_granted = abap_true. ENDMETHOD. METHOD is_create_granted. AUTHORITY-CHECK OBJECT 'ZOSTATKJ' ID 'ZOSTATKJ' DUMMY ID 'ACTVT' FIELD '01'. create_granted = COND #( WHEN sy-subrc = 0 THEN abap_true ELSE abap_false ). " Simulate full access - for testing purposes only! Needs to be removed for a productive implementation. create_granted = abap_true. ENDMETHOD. METHOD recalctotalprice. TYPES: BEGIN OF ty_amount_per_currencycode, amount TYPE /dmo/total_price, currency_code TYPE /dmo/currency_code, END OF ty_amount_per_currencycode. DATA: amount_per_currencycode TYPE STANDARD TABLE OF ty_amount_per_currencycode. " Read all relevant travel instances. READ ENTITIES OF zi_rap_atrav_kj IN LOCAL MODE ENTITY Travel FIELDS ( BookingFee CurrencyCode ) WITH CORRESPONDING #( keys ) RESULT DATA(travels). DELETE travels WHERE CurrencyCode IS INITIAL. LOOP AT travels ASSIGNING FIELD-SYMBOL(<travel>). " Set the start for the calculation by adding the booking fee. amount_per_currencycode = VALUE #( ( amount = <travel>-BookingFee currency_code = <travel>-CurrencyCode ) ). " Read all associated bookings and add them to the total price. READ ENTITIES OF zi_rap_atrav_kj IN LOCAL MODE ENTITY Travel BY \_Booking FIELDS ( FlightPrice CurrencyCode ) WITH VALUE #( ( %tky = <travel>-%tky ) ) RESULT DATA(bookings). LOOP AT bookings INTO DATA(booking) WHERE CurrencyCode IS NOT INITIAL. COLLECT VALUE ty_amount_per_currencycode( amount = booking-FlightPrice currency_code = booking-CurrencyCode ) INTO amount_per_currencycode. ENDLOOP. CLEAR <travel>-TotalPrice. LOOP AT amount_per_currencycode INTO DATA(single_amount_per_currencycode). " If needed do a Currency Conversion IF single_amount_per_currencycode-currency_code = <travel>-CurrencyCode. <travel>-TotalPrice += single_amount_per_currencycode-amount. ELSE. /dmo/cl_flight_amdp=>convert_currency( EXPORTING iv_amount = single_amount_per_currencycode-amount iv_currency_code_source = single_amount_per_currencycode-currency_code iv_currency_code_target = <travel>-CurrencyCode iv_exchange_rate_date = cl_abap_context_info=>get_system_date( ) IMPORTING ev_amount = DATA(total_booking_price_per_curr) ). <travel>-TotalPrice += total_booking_price_per_curr. ENDIF. ENDLOOP. ENDLOOP. " write back the modified total_price of travels MODIFY ENTITIES OF zi_rap_atrav_kj IN LOCAL MODE ENTITY travel UPDATE FIELDS ( TotalPrice ) WITH CORRESPONDING #( travels ). ENDMETHOD. ENDCLASS.同理,创建class zbp_i_rap_booking_kj
替换代码:
CLASS lhc_Booking DEFINITION INHERITING FROM cl_abap_behavior_handler. PRIVATE SECTION. METHODS calculateBookingID FOR DETERMINE ON MODIFY IMPORTING keys FOR Booking~CalculateBookingID. METHODS calculateTotalPrice FOR DETERMINE ON MODIFY IMPORTING keys FOR Booking~calculateTotalPrice. ENDCLASS. CLASS lhc_Booking IMPLEMENTATION. METHOD calculateBookingID. DATA max_bookingid TYPE /dmo/booking_id. DATA update TYPE TABLE FOR UPDATE zi_rap_atrav_kj\\Booking. " Read all travels for the requested bookings. " If multiple bookings of the same travel are requested, the travel is returned only once. READ ENTITIES OF zi_rap_atrav_kj IN LOCAL MODE ENTITY Booking BY \_Travel FIELDS ( TravelUUID ) WITH CORRESPONDING #( keys ) RESULT DATA(travels). " Process all affected Travels. Read respective bookings, determine the max-id and update the bookings without ID. LOOP AT travels INTO DATA(travel). READ ENTITIES OF zi_rap_atrav_kj IN LOCAL MODE ENTITY Travel BY \_Booking FIELDS ( BookingID ) WITH VALUE #( ( %tky = travel-%tky ) ) RESULT DATA(bookings). " Find max used BookingID in all bookings of this travel max_bookingid ='0000'. LOOP AT bookings INTO DATA(booking). IF booking-BookingID > max_bookingid. max_bookingid = booking-BookingID. ENDIF. ENDLOOP. " Provide a booking ID for all bookings that have none. LOOP AT bookings INTO booking WHERE BookingID IS INITIAL. max_bookingid += 10. APPEND VALUE #( %tky = booking-%tky BookingID = max_bookingid ) TO update. ENDLOOP. ENDLOOP. " Update the Booking ID of all relevant bookings MODIFY ENTITIES OF zi_rap_atrav_kj IN LOCAL MODE ENTITY Booking UPDATE FIELDS ( BookingID ) WITH update REPORTED DATA(update_reported). reported = CORRESPONDING #( DEEP update_reported ). ENDMETHOD. METHOD calculateTotalPrice. " Read all travels for the requested bookings. " If multiple bookings of the same travel are requested, the travel is returned only once. READ ENTITIES OF zi_rap_atrav_kj IN LOCAL MODE ENTITY Booking BY \_Travel FIELDS ( TravelUUID ) WITH CORRESPONDING #( keys ) RESULT DATA(travels) FAILED DATA(read_failed). " Trigger calculation of the total price MODIFY ENTITIES OF zi_rap_atrav_kj IN LOCAL MODE ENTITY Travel EXECUTE recalcTotalPrice FROM CORRESPONDING #( travels ) REPORTED DATA(execute_reported). reported = CORRESPONDING #( DEEP execute_reported ). ENDMETHOD. ENDCLASS.5. 预览增强版旅行应用并进行试用
首先你会看到在创建、更新和删除操作旁边新增了两个操作Accept Travel和Reject Travel行程。这两个操作会根据所选行程实例的status进行切换显示。
可以随意试用APP。
例如:
- 创建一条新的旅行记录。为机构ID、客户ID和开始日期和/或结束日期提供一些不一致的值。点击保存后,会显示相应的错误提示信息。
- 修正这些值,然后点击保存。现在验证已成功通过。你还可以看到决策已执行。行程ID已完成计算,预订状态已设置为默认值。
- 使用接受和拒绝操作来验证实例特定功能控制。
- 在旅行对象页面上:在行程对象页面的预订表中,点击创建为此行程新建一个预订实例。填写一些有效信息,然后点击保存。返回导航会显示标头实例未重新加载,因此会显示旧的总价。这需要定义一个副作用,本课程不涉及这部分内容。手动刷新用户界面即可看到更新后的总价。
- 你可以点击删除来删除旅行实例。
总结:
RAP 行为增强通过声明式 BDEF + EML 实现的行为池,为业务对象提供了自动派生、校验、动作、动态特性和授权等企业级能力,并且与投影、UI 无缝集成,是实现“干净核心”的关键技术。