引言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 中判断在普通消费场景ConsumeEML 的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 的设计哲学对外 actionacceptTravel/rejectTravel会作为 OData 操作暴露供 UI 或外部系统调用。( features : instance )表示动态特性控制运行时通过get_instance_features方法判断该实例上此动作是否可用例如已审批的 Travel 不应再次显示“审批”按钮。result [1] $self返回当前操作后的实例便于前端刷新数据。这是遵循 RESTful 原则操作后返回资源状态。internal action不对外暴露只能由 BO 内部其他 determination/action 通过 EMLEXECUTE调用。用于封装可复用的内部计算逻辑如重算总价。为什么区分 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;解析服务隔离与安全边界基础 BOZI_*定义了完整的业务行为可能包含内部使用或高权限的动作。投影 BOZC_*是面向特定消费场景的视图通过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_####上使用快捷键Ctrl1打开快速修复对话框然后选择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_behvmk-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_messageseverity-error textid zcm_rap_kjagency_unknown agencyid travel-AgencyID ) %element-AgencyID if_abap_behvmk-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_messageseverity-error textid zcm_rap_kjcustomer_unknown customerid travel-CustomerID ) %element-CustomerID if_abap_behvmk-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_messageseverity-error textid zcm_rap_kjdate_interval begindate travel-BeginDate enddate travel-EndDate travelid travel-TravelID ) %element-BeginDate if_abap_behvmk-on %element-EndDate if_abap_behvmk-on ) TO reported-travel. ELSEIF travel-BeginDate cl_abap_context_infoget_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_messageseverity-error textid zcm_rap_kjbegin_date_before_system_date begindate travel-BeginDate ) %element-BeginDate if_abap_behvmk-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_behvfc-o-disabled ELSE if_abap_behvfc-o-enabled ) is_rejected COND #( WHEN travel-TravelStatus travel_status-canceled THEN if_abap_behvfc-o-disabled ELSE if_abap_behvfc-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_draft00) as well as for drafts (is_draft01) as we cant 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_behvmk-on OR requested_authorizations-%action-acceptTravel if_abap_behvmk-on OR requested_authorizations-%action-rejectTravel if_abap_behvmk-on OR * requested_authorizations-%action-Prepare if_abap_behvmk-on OR * requested_authorizations-%action-Edit if_abap_behvmk-on OR requested_authorizations-%assoc-_Booking if_abap_behvmk-on THEN abap_true ELSE abap_false ). is_delete_requested COND #( WHEN requested_authorizations-%delete if_abap_behvmk-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_messageseverity-error textid zcm_rap_kjunauthorized ) ) 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_messageseverity-error textid zcm_rap_kjunauthorized ) ) 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_messageseverity-error textid zcm_rap_kjunauthorized ) ) TO reported-travel. ENDIF. ENDIF. APPEND VALUE #( %tky travel-%tky %update COND #( WHEN update_granted abap_true THEN if_abap_behvauth-allowed ELSE if_abap_behvauth-unauthorized ) %action-acceptTravel COND #( WHEN update_granted abap_true THEN if_abap_behvauth-allowed ELSE if_abap_behvauth-unauthorized ) %action-rejectTravel COND #( WHEN update_granted abap_true THEN if_abap_behvauth-allowed ELSE if_abap_behvauth-unauthorized ) * %action-Prepare COND #( WHEN update_granted abap_true THEN if_abap_behvauth-allowed ELSE if_abap_behvauth-unauthorized ) * %action-Edit COND #( WHEN update_granted abap_true THEN if_abap_behvauth-allowed ELSE if_abap_behvauth-unauthorized ) %assoc-_Booking COND #( WHEN update_granted abap_true THEN if_abap_behvauth-allowed ELSE if_abap_behvauth-unauthorized ) %delete COND #( WHEN delete_granted abap_true THEN if_abap_behvauth-allowed ELSE if_abap_behvauth-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_amdpconvert_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_infoget_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 无缝集成是实现“干净核心”的关键技术。
SAP-BTP :(8)RAP-BO(业务对象)行为定义增强
引言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 中判断在普通消费场景ConsumeEML 的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 的设计哲学对外 actionacceptTravel/rejectTravel会作为 OData 操作暴露供 UI 或外部系统调用。( features : instance )表示动态特性控制运行时通过get_instance_features方法判断该实例上此动作是否可用例如已审批的 Travel 不应再次显示“审批”按钮。result [1] $self返回当前操作后的实例便于前端刷新数据。这是遵循 RESTful 原则操作后返回资源状态。internal action不对外暴露只能由 BO 内部其他 determination/action 通过 EMLEXECUTE调用。用于封装可复用的内部计算逻辑如重算总价。为什么区分 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;解析服务隔离与安全边界基础 BOZI_*定义了完整的业务行为可能包含内部使用或高权限的动作。投影 BOZC_*是面向特定消费场景的视图通过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_####上使用快捷键Ctrl1打开快速修复对话框然后选择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_behvmk-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_messageseverity-error textid zcm_rap_kjagency_unknown agencyid travel-AgencyID ) %element-AgencyID if_abap_behvmk-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_messageseverity-error textid zcm_rap_kjcustomer_unknown customerid travel-CustomerID ) %element-CustomerID if_abap_behvmk-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_messageseverity-error textid zcm_rap_kjdate_interval begindate travel-BeginDate enddate travel-EndDate travelid travel-TravelID ) %element-BeginDate if_abap_behvmk-on %element-EndDate if_abap_behvmk-on ) TO reported-travel. ELSEIF travel-BeginDate cl_abap_context_infoget_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_messageseverity-error textid zcm_rap_kjbegin_date_before_system_date begindate travel-BeginDate ) %element-BeginDate if_abap_behvmk-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_behvfc-o-disabled ELSE if_abap_behvfc-o-enabled ) is_rejected COND #( WHEN travel-TravelStatus travel_status-canceled THEN if_abap_behvfc-o-disabled ELSE if_abap_behvfc-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_draft00) as well as for drafts (is_draft01) as we cant 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_behvmk-on OR requested_authorizations-%action-acceptTravel if_abap_behvmk-on OR requested_authorizations-%action-rejectTravel if_abap_behvmk-on OR * requested_authorizations-%action-Prepare if_abap_behvmk-on OR * requested_authorizations-%action-Edit if_abap_behvmk-on OR requested_authorizations-%assoc-_Booking if_abap_behvmk-on THEN abap_true ELSE abap_false ). is_delete_requested COND #( WHEN requested_authorizations-%delete if_abap_behvmk-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_messageseverity-error textid zcm_rap_kjunauthorized ) ) 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_messageseverity-error textid zcm_rap_kjunauthorized ) ) 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_messageseverity-error textid zcm_rap_kjunauthorized ) ) TO reported-travel. ENDIF. ENDIF. APPEND VALUE #( %tky travel-%tky %update COND #( WHEN update_granted abap_true THEN if_abap_behvauth-allowed ELSE if_abap_behvauth-unauthorized ) %action-acceptTravel COND #( WHEN update_granted abap_true THEN if_abap_behvauth-allowed ELSE if_abap_behvauth-unauthorized ) %action-rejectTravel COND #( WHEN update_granted abap_true THEN if_abap_behvauth-allowed ELSE if_abap_behvauth-unauthorized ) * %action-Prepare COND #( WHEN update_granted abap_true THEN if_abap_behvauth-allowed ELSE if_abap_behvauth-unauthorized ) * %action-Edit COND #( WHEN update_granted abap_true THEN if_abap_behvauth-allowed ELSE if_abap_behvauth-unauthorized ) %assoc-_Booking COND #( WHEN update_granted abap_true THEN if_abap_behvauth-allowed ELSE if_abap_behvauth-unauthorized ) %delete COND #( WHEN delete_granted abap_true THEN if_abap_behvauth-allowed ELSE if_abap_behvauth-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_amdpconvert_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_infoget_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 无缝集成是实现“干净核心”的关键技术。