SAP学习笔记 - 开发56 - RAP开发 Managed App RAP action: Processor/ Approver,Copy Travel 功能实现

上一章讲了Numbering:Booking ID/Booking Supplement ID,ABAP不太懂 的朋友还需要点儿时间理解一下。

SAP学习笔记 - 开发53 - RAP开发 Managed App Numbering 实战 Numbering- Booking ID/Booking Supplement ID-CSDN博客

SAP学习笔记 - 开发54 - RAP开发 Managed App Modify Entity - Short Form,Long Form,Dynamic Form-CSDN博客

SAP学习笔记 - 开发55 - RAP开发 Managed App 到目前为止的代码总结,Trial 代码恢复-CSDN博客

本章继续将RAP的知识。

目录

1,Processor 和 Approver 是什么?

1-1,Approver

1-2,Processor

2,RAP actions

2-1. Factory Action

2-2. Non-factory Action

a. 基本修饰符

(1) internal

(2) static

(3) repeatable

b. 括号内选项

(1) features: {instance | global}

(2) precheck

(3) authorization 选项组

(4) lock:none

c. 完整示例

e. 最佳实践建议

2-3. Save Action

2-4.总结

3,定义4个 RAP actions

4,RAP action - copyTravel

4-1,Travel_M 的Copy

a),定义变量

b),根据 keys 内表,查询关联数据

c),赋值取到的关联数据到变量

d),Modify 更新到DB

e),设定 mapped 变量

f),完整的 copyTravel 代码

g),use action copyTravel - Z04_PV_Travel_M

h),画面表示 - Metadata

4-2,debug - Copy Travel 按钮

a),确认一下拷贝元数据

b),选中拷贝元,点Copy Travel 按钮

1),keys

2),Read Entity

3),变数 - it_travel

4),变数 - it_booking

5),变数 - it_booking_cba

6),变数 - it_booksuppl_cba

7),执行结果


下面是详细内容。

1,Processor 和 Approver 是什么?

- Processor:Create individual travel instances,creates and manages individual flights,

                      and adds supplements to flight bookings.

- Approver:Verification of the recorded travel data entered by the processor

也就是说之前讲的那些章节,都是属于 Processor 范围。

接下来来看看 Approver部分。

1-1,Approver

具体来说是什么呢?

- Accept Travel Action:比如将 Overall status 设为 A:Accepted

- Reject Travel Action:比如将 Overall status 设为 X:Rejected

这两个动作是属于在某一个既存的instance上进行操作,属于 Non-Factory。

其实就是在List Report画面上加两个按钮,一个叫 Accept Travel,一个叫 Reject Travel。

1-2,Processor

这里要列举一个之前没涉及到操作,就是行拷贝,因为产生了新 instance,属于 Factory。

当然其实也就是在List Report上面加一个 Copy Travel 按钮。

当然拷贝的时候,它会连同association 的Booking,Booking Supplement 一起拷贝了。

上面说到了 Factory,Non-Factory 这两个概念,它们都是RAP actions。

下面来具体看一下什么是RAP actions。

2,RAP actions

RAP (Restful Application Programming) 开发中,Actions(动作)是用于处理用户交互逻辑的核心机制。RAP 主要支持三种类型的 Actions:

- Non-factory actions(非工厂动作)

  Custom logic that changes existing entity instances.

- Factory actions(工厂动作)

  It can be used to create RAP BO entity instances.

- Save actions(保存动作)

  It can be non-factory actions or factory actions executed during the RAP save sequence.

在RAP(RESTful Application Programming Model)开发中,action是用于执行特定业务逻辑的操作,它们定义在行为定义(Behavior Definition)中,并集成到业务对象(Business Object)中。RAP action主要分为三种类型:Factory、Non-factory和Save。每种类型都有其特定用途和实现方式。下面逐一说明它们的定义、使用场景、示例代码(使用ABAP语言),以及注意事项。

2-1. Factory Action

  • 定义:Factory Action用于创建新的业务对象实例。它类似于“工厂模式”,在运行时动态生成一个新实体(entity)。例如,创建一个新的订单或客户记录。Factory Action通常返回新创建的实例标识符(如键值)。
  • 使用场景
    • 当需要从零开始创建一个新的业务对象时。
    • 适用于初始化复杂对象,例如在用户界面中点击“新建”按钮时触发。
    • 不能用于修改现有实例;只专注于创建。
  • 示例代码: 在行为定义文件中定义Factory Action:
define behavior for ZOrder  " 定义业务对象行为
...
action factory Create;  " 声明一个名为Create的Factory Action
...
enddefine.

在行为实现中编写逻辑:

method Create.  " 实现Create方法
  " 创建新订单实例的逻辑
  data: ls_order type ZOrder.  " 假设ZOrder是业务对象结构
  ls_order-order_id = cl_system_uuid=>create_uuid_c32( ).  " 生成唯一ID
  ls_order-status = 'New'.
  " 将新实例保存到临时缓冲区
  modify entities of ZOrder in local mode
    create set FIELDS with value #( ( %cid = 'NEW_ORDER' %data = ls_order ) ).
  " 返回新创建的实例
  mapped-order = value #( ( %cid = 'NEW_ORDER' %key = ls_order-order_id ) ).
endmethod.
  • 注意事项
    • Factory Action必须与create操作关联,不能用于更新或删除。
    • 在RAP框架中,新实例在事务提交前存在于临时存储中,需要结合Save Action来持久化。

下面是 Factory 的语法结构,跟 Non-Factory很相似(各选项详情请看 Non-Factory Section)

[internal][static [default]] factory action
    {
    [features: {instance | global}]
    [precheck]
    [authorization:none]
    [authorization:update]
    [authorization:global]
    [authorization:instance]
    [lock:none]
    }
    ActionName [external 'ExternalName']
    [InputParameter]
    [cardinality]

[cardinality]:作用:指定创建实体的数量

   - TO 1:创建单个实体(默认)

   - TO MANY:创建多个实体

2-2. Non-factory Action

  • 定义:Non-factory Action用于执行非创建性的业务操作,例如更新、删除、状态变更或自定义逻辑。它不生成新实例,而是作用于现有业务对象。Non-factory Action更灵活,可以处理多种业务规则。
  • 使用场景
    • 修改现有实例的属性,如更新订单状态或删除记录。
    • 执行自定义业务逻辑,如审批流程、计算或验证。
    • 当操作不需要创建新实体时使用。
  • 示例代码: 在行为定义中声明Non-factory Action:
define behavior for ZOrder
...
action non_factory UpdateStatus;  " 声明一个名为UpdateStatus的Non-factory Action
...
enddefine.

在行为实现中编写逻辑:

method UpdateStatus.  " 实现UpdateStatus方法
  " 更新订单状态的逻辑
  data: lt_keys type table for update ZOrder.  " 输入参数:待更新的键值
  data: ls_data type ZOrder.
  " 从输入中获取键值
  read entities of ZOrder in local mode
    entity Order
      fields ( status ) with lt_keys
    result data(lt_orders).
  " 更新状态
  loop at lt_orders assigning field-symbol(<fs_order>).
    <fs_order>-status = 'Approved'.  " 设置新状态
  endloop.
  " 应用更新
  modify entities of ZOrder in local mode
    update set fields with value lt_orders.
  " 返回成功消息
  reported-order = value #( for order in lt_orders ( %key = order-order_id %msg = new_message( id 'ZMESSAGE' number '001' severity 'success' ) ) ).
endmethod.
  • 注意事项
    • Non-factory Action可以接收参数,并在运行时验证输入。
    • 它通常与updatedelete操作结合,但不限于此。
    • 在UI层,Non-factory Action可通过OData服务暴露为API端点。

以下是 RAP 开发中 Non-factory Action 的语法结构:

[internal][static] [repeatable] action
    [(
    [features: {instance | global}]
    [precheck]
    [authorization:none]
    [authorization:update]
    [authorization:global]
    [authorization:instance]
    [lock:none]
    )]

a. 基本修饰符

(1) internal
  • 含义:标记为内部使用的 Action,不会暴露给 OData 服务

  • 用途:仅在业务对象内部调用(如其他 Action 或方法中),外部 API 无法访问。

  • 示例

  • INTERNAL ACTION validate_data...
(2) static
  • 含义:静态 Action,不依赖特定实体实例

  • 用途:无需实体键值就能调用,适合全局操作(如计算、配置读取)。

  • 示例:abap

  • STATIC ACTION calculate_tax...
(3) repeatable
  • 含义:允许重复执行,即使业务对象状态未改变。

  • 用途:适用于幂等操作(如刷新数据、重新计算)。

  • 对比:默认情况下,RAP 会阻止重复执行相同操作。


b. 括号内选项

(1) features: {instance | global}
  • 作用:定义 Action 的作用范围

    • instance:操作绑定到特定实体实例(默认值)。

  • " 需要传入实体键(如 order_id)
    POST /Orders(123)/approve
  • global跨实例操作,无需指定实体键。

  • " 无需实体键
    POST /batch_approve
    (2) precheck
    • 含义:启用预检查机制

    • 用途:在正式执行前运行验证逻辑(如权限/数据校验)。

    • 实现

    • METHODS approve_precheck FOR PRECHECK...
    (3) authorization 选项组
    • 作用:控制权限验证级别

      选项含义适用场景
      none跳过所有权限检查公开操作(如查询汇率)
      update检查更新权限修改数据的操作(如状态变更)
      global检查全局权限跨实例操作(如批量处理)
      instance检查实例级权限(默认)单实体操作(如审批订单)
    (4) lock:none
    • 含义禁用自动锁机制

    • 用途:避免框架自动锁定实体,适用于只读操作。

    • 对比:默认情况下,RAP 会锁定相关实体防止并发冲突。


    c. 完整示例

    INTERNAL STATIC REPEATABLE ACTION features: global precheck authorization:none lock:none
    RecalculateAllTax [external 'RecalcTax']:
      IMPORTING
        iv_tax_rate TYPE buzei,
      EXPORTING
        ev_count    TYPE i.

    d. 选项解析

    1. INTERNAL:仅内部调用,不暴露 API

    2. STATIC:无需实体键

    3. REPEATABLE:允许重复执行

    4. features: global:跨实例操作

    5. precheck:启用预检查

    6. authorization:none:跳过权限验证

    7. lock:none:禁用自动锁


    e. 最佳实践建议

        e-1. 安全敏感操作:

           使用 authorization:instance + precheck 双重验证

        ACTION approve_order features: instance precheck authorization:update...

        e-2. 高性能只读操作:

            组合 lock:none + authorization:none

        STATIC ACTION get_config lock:none authorization:none...

        e-3. 批量处理:

            使用 features:global + static

            STATIC ACTION bulk_update features: global...

           e-4. 免重复提交:

           默认禁用 repeatable,除非明确需要(如刷新操作)

    这些选项提供了细粒度的控制,使 Non-factory Actions 能灵活适应不同业务场景的需求。

    2-3. Save Action

    • 定义:Save Action用于在事务结束时持久化所有更改(包括Factory和Non-factory Action的修改)到数据库。它是RAP框架的核心部分,确保数据一致性。Save Action不是直接由开发者定义的操作,而是由RAP引擎自动处理,开发者只需在行为定义中启用它。
    • 使用场景
      • 当需要提交事务时,例如用户点击“保存”按钮后。
      • 将临时缓冲区中的创建、更新或删除操作写入数据库。
      • 在事务提交前执行最终验证或后处理逻辑。
    • 示例代码: 在行为定义中启用Save Action:
    define behavior for ZOrder
    persistent table ZORDER_TABLE  " 指定持久化表
    ...
    save;  " 启用Save Action
    ...
    enddefine.
    

    在行为实现中,开发者可以定义save_modified方法处理自定义逻辑(可选):

    method save_modified.  " 覆盖RAP的保存方法
      " 自定义验证或后处理逻辑
      data(lt_changes) = get_changes( ).  " 获取所有待保存的更改
      " 检查数据一致性
      loop at lt_changes assigning field-symbol(<fs_change>).
        if <fs_change>-status = 'Invalid'.
          " 如果无效,回滚事务
          failed = value #( ( %key = <fs_change>-order_id ) ).
          reported = value #( ( %key = <fs_change>-order_id %msg = new_message( id 'ZMESSAGE' number '002' severity 'error' ) ) ).
        endif.
      endloop.
      " 如果无错误,调用父类方法执行实际保存
      if failed is initial.
        super->save_modified( ).
      endif.
    endmethod.
    
    • 注意事项
      • Save Action是自动触发的,开发者不能直接调用它;它由RAP框架在事务提交时执行。
      • 它确保了ACID属性(原子性、一致性、隔离性、持久性)。
      • 如果未启用Save Action,所有修改只存在于内存中,不会持久化。

    2-4.总结

    • 关系与区别:Factory Action专注于创建新实例,Non-factory Action处理修改和自定义逻辑,而Save Action负责最终保存所有更改。三者协作实现完整的业务事务:Factory和Non-factory在临时缓冲区操作,Save在提交时持久化。
    • 最佳实践
      • 在设计时,明确每种action的职责:Factory用于创建,Non-factory用于业务规则,Save用于持久化。
      • 使用行为定义工具(如ADT)来定义action,确保与OData服务集成。
      • 测试时,注意事务边界:Save Action触发后,更改不可逆。
    • 附加说明:在RAP开发中,这些action通过CDS行为定义暴露,支持Fiori应用的无缝集成。如果有具体场景或代码问题,可以提供更多细节,我会进一步解释!

    下面在系统上做一下操作,看怎么实现这个RAP actions。

    3,定义4个 RAP actions

    - acceptTravel/ rejectTravel :在既存 instance上操作,属于Non-factory Actions(非工厂动作)。

    - copyTravel :虽然参照了既存 instance,但是会产生新 instance,属于 Factory Action

    - reCalcTotalPrice:动态计算总价,这个不是谁调用的,而实价格变了之后就会调用来重新计算

      它也是 Non-Factory,而且也不会开放给外部,所以是Internal 的

      action acceptTravel result[1] $self;
      action rejectTravel result[1] $self;
      
      factory action copyTravel [1];
      
      internal action reCalcTotalPrice;

    按下 Ctrl + 1,然后选择全部创建,就是一次性在Behavior Pool 里面全部创建该4个方法

        METHODS acceptTravel FOR MODIFY
          IMPORTING keys FOR ACTION Z04_DV_Travel_M~acceptTravel RESULT result.
    
        METHODS copyTravel FOR MODIFY
          IMPORTING keys FOR ACTION Z04_DV_Travel_M~copyTravel.
    
        METHODS reCalcTotalPrice FOR MODIFY
          IMPORTING keys FOR ACTION Z04_DV_Travel_M~reCalcTotalPrice.
    
        METHODS rejectTravel FOR MODIFY
          IMPORTING keys FOR ACTION Z04_DV_Travel_M~rejectTravel RESULT result.

    下面来实现这些个RAP actions

    4,RAP action - copyTravel

    因为 Travel_M,Booking_M,Booksuppl_M 这三个表是关联的,所以咱们这里的Copy Travel,实际上是连着子表一起拷贝的。

    4-1,Travel_M 的Copy

    流程如下:

    - 画面选择1条,然后点Copy Travel 按钮

    - 这样 keys 内置的变量(也是一个内表)就会传过来拷贝元的 Travel ID

    - 根据拷贝元的 Travel ID,就可以抽出 Travel_M,Booking_M,Booksuppl_M的关联数据

    - 设定抽出的数据到临时变量里,这里有几点要注意

      - 在新规一条数据的时候,key(比如Travel ID)尚未确定之前,RAP 会借用 cid,所以需要设定cid

      - 对于子对象(比如Booking_M),不仅需要cid,还需要 cid_ref,以关联父对象

    a),定义变量

    为了后面 Modify 的时候传入用:

    *  Define create table variable
        DATA: it_travel        TYPE TABLE FOR CREATE Z04_DV_Travel_M,
              it_booking_cba   TYPE TABLE FOR CREATE Z04_DV_Travel_M\_Booking,
              it_booksuppl_cba TYPE TABLE FOR CREATE Z04_DV_Booking_M\_BookingSupplement.

    b),根据 keys 内表,查询关联数据

    *  Read original keys which need to copy
        ==>这2行是为了判断keys 内表中,不应该有cid 为空的数据,因为拷贝元是既存数据嘛,肯定有 cid的。
        READ TABLE keys ASSIGNING FIELD-SYMBOL(<ls_keys>) WITH KEY %cid = ' '.  
        ==》Assert 判断是否未取到值,如果不幸keys内表里有cid未赋值的数据,就Dump(挂掉)
        ASSERT <ls_keys> IS NOT ASSIGNED.  
    
        READ ENTITIES OF Z04_DV_Travel_M IN LOCAL MODE
          ENTITY Z04_DV_Travel_M
          ALL FIELDS WITH CORRESPONDING #( keys )
          RESULT DATA(lt_travel_r)
          FAILED DATA(lt_travel_failed).
    
        READ ENTITIES OF Z04_DV_Travel_M IN LOCAL MODE
          ENTITY Z04_DV_Travel_M BY \_Booking
          ALL FIELDS WITH CORRESPONDING #( lt_travel_r )
          RESULT DATA(lt_booking_r).  ==> 这里没有加 Failed 的原因是有可能数据只有 Travel,没有Booking,所以不能算错;下面的Booksuppl 也是一样的。
    
        READ ENTITIES OF Z04_DV_Travel_M IN LOCAL MODE
          ENTITY Z04_DV_Booking_M BY \_BookingSupplement
          ALL FIELDS WITH CORRESPONDING #( lt_booking_r )
          RESULT DATA(lt_booksuppl_r).

    c),赋值取到的关联数据到变量

    总体来说就是3重循环,对,就是那个循环套循环,那好像也没办法,不能怎么出力所有数据呢?

    *  Loop the lt_travel_r, lt_booking_r, lt_booksuppl_r and append data to create table variable
        LOOP AT lt_travel_r ASSIGNING FIELD-SYMBOL(<ls_travel_r>).
    *      APPEND INITIAL LINE TO it_travel ASSIGNING FIELD-SYMBOL(<ls_travel>).
    *      <ls_travel>-%cid = keys[ KEY entity TravelId = <ls_travel_r>-TravelId ]-%cid.
    *      <ls_travel>-%data = CORRESPONDING #( <ls_travel_r> EXCEPT TravelId ).
    
          APPEND VALUE #( %cid = keys[ KEY entity TravelId = <ls_travel_r>-TravelId ]-%cid
                          %data = CORRESPONDING #( <ls_travel_r> EXCEPT TravelId ) )
                 TO it_travel ASSIGNING FIELD-SYMBOL(<ls_travel>).
          
          ==》下面这几行的意思是,copy 过来之后,你也不一定全要一样的是吧,要想改变个别字段值也是可以的。
          <ls_travel>-BeginDate = cl_abap_context_info=>get_system_date(  ).    
          <ls_travel>-EndDate = cl_abap_context_info=>get_system_date(  ) + 30.
          <ls_travel>-OverallStatus = 'O'.
    
          ==》这行就是给 cid_ref 赋值,看着好像就只append 一行的样子,
              但是其实它的结构是 %target 成员本身是一个内表,里面装的就是 Booking_M 的数据
          APPEND VALUE #( %cid_ref = <ls_travel>-%cid )
                 TO it_booking_cba ASSIGNING FIELD-SYMBOL(<it_booking>).  
    
          LOOP AT lt_booking_r ASSIGNING FIELD-SYMBOL(<ls_booking_r>)
                               USING KEY entity
                               WHERE TravelId = <ls_travel_r>-TravelId.
            APPEND VALUE #( %cid = <ls_travel>-%cid && <ls_booking_r>-BookingId
                            %data = CORRESPONDING #( <ls_booking_r> EXCEPT TravelId ) )
                   TO <it_booking>-%target ASSIGNING FIELD-SYMBOL(<ls_booking_n>).
    
            <ls_booking_n>-BookingStatus = 'N'.
    
    
            APPEND VALUE #( %cid_ref = <ls_booking_n>-%cid )
                   TO it_booksuppl_cba ASSIGNING FIELD-SYMBOL(<ls_booksuppl>).
    
            LOOP AT lt_booksuppl_r ASSIGNING FIELD-SYMBOL(<ls_booksuppl_r>)
                               USING KEY entity
                               WHERE TravelId = <ls_travel_r>-TravelId
                               AND   BookingId = <ls_booking_r>-BookingId.
    
              APPEND VALUE #( %cid = <ls_travel>-%cid && <ls_booking_r>-BookingId && <ls_booksuppl_r>-BookingSupplementId
                              %data = CORRESPONDING #( <ls_booksuppl_r> EXCEPT TravelId BookingId ) )
                   TO <ls_booksuppl>-%target.
            ENDLOOP.
    
          ENDLOOP.
    
        ENDLOOP.

    d),Modify 更新到DB

    * Modify Data to DB
        MODIFY ENTITIES OF Z04_DV_Travel_M IN LOCAL MODE
          ENTITY Z04_DV_Travel_M
          CREATE FIELDS ( AgencyId CustomerId BeginDate EndDate BookingFee TotalPrice CurrencyCode OverallStatus Description )
          WITH it_travel
            ENTITY Z04_DV_Travel_M
            CREATE BY \_Booking
            FIELDS ( BookingId BookingDate CustomerId CarrierId ConnectionId FlightDate FlightPrice CurrencyCode BookingStatus )
            WITH it_booking_cba
              ENTITY Z04_DV_Booking_M
              CREATE BY \_BookingSupplement
              FIELDS ( BookingSupplementId SupplementId Price CurrencyCode )
              WITH it_booksuppl_cba
          MAPPED DATA(it_mapped).

    e),设定 mapped 变量

    点 copyTravel 函数,然后按 F2,能看出来 Changing 部分,就是输出变量

    传出去之后,前端就可以接收到。

    其实除了mapped变量,还有failed, reported 变量,咱们这里就割爱了,可以根据需要设定。

    f),完整的 copyTravel 代码

      METHOD copyTravel.
    *  Define create table variable
        DATA: it_travel        TYPE TABLE FOR CREATE Z04_DV_Travel_M,
              it_booking_cba   TYPE TABLE FOR CREATE Z04_DV_Travel_M\_Booking,
              it_booksuppl_cba TYPE TABLE FOR CREATE Z04_DV_Booking_M\_BookingSupplement.
    
    *  Read original keys which need to copy
        READ TABLE keys ASSIGNING FIELD-SYMBOL(<ls_keys>) WITH KEY %cid = ' '.
        ASSERT <ls_keys> IS NOT ASSIGNED.
    
        READ ENTITIES OF Z04_DV_Travel_M IN LOCAL MODE
          ENTITY Z04_DV_Travel_M
          ALL FIELDS WITH CORRESPONDING #( keys )
          RESULT DATA(lt_travel_r)
          FAILED DATA(lt_travel_failed).
    
        READ ENTITIES OF Z04_DV_Travel_M IN LOCAL MODE
          ENTITY Z04_DV_Travel_M BY \_Booking
          ALL FIELDS WITH CORRESPONDING #( lt_travel_r )
          RESULT DATA(lt_booking_r).
    
        READ ENTITIES OF Z04_DV_Travel_M IN LOCAL MODE
          ENTITY Z04_DV_Booking_M BY \_BookingSupplement
          ALL FIELDS WITH CORRESPONDING #( lt_booking_r )
          RESULT DATA(lt_booksuppl_r).
    
    *  Loop the lt_travel_r, lt_booking_r, lt_booksuppl_r and append data to create table variable
        LOOP AT lt_travel_r ASSIGNING FIELD-SYMBOL(<ls_travel_r>).
    *      APPEND INITIAL LINE TO it_travel ASSIGNING FIELD-SYMBOL(<ls_travel>).
    *      <ls_travel>-%cid = keys[ KEY entity TravelId = <ls_travel_r>-TravelId ]-%cid.
    *      <ls_travel>-%data = CORRESPONDING #( <ls_travel_r> EXCEPT TravelId ).
    
          APPEND VALUE #( %cid = keys[ KEY entity TravelId = <ls_travel_r>-TravelId ]-%cid
                          %data = CORRESPONDING #( <ls_travel_r> EXCEPT TravelId ) )
                 TO it_travel ASSIGNING FIELD-SYMBOL(<ls_travel>).
    
          <ls_travel>-BeginDate = cl_abap_context_info=>get_system_date(  ).
          <ls_travel>-EndDate = cl_abap_context_info=>get_system_date(  ) + 30.
          <ls_travel>-OverallStatus = 'O'.
    
          APPEND VALUE #( %cid_ref = <ls_travel>-%cid )
                 TO it_booking_cba ASSIGNING FIELD-SYMBOL(<it_booking>).
    
          LOOP AT lt_booking_r ASSIGNING FIELD-SYMBOL(<ls_booking_r>)
                               USING KEY entity
                               WHERE TravelId = <ls_travel_r>-TravelId.
            APPEND VALUE #( %cid = <ls_travel>-%cid && <ls_booking_r>-BookingId
                            %data = CORRESPONDING #( <ls_booking_r> EXCEPT TravelId ) )
                   TO <it_booking>-%target ASSIGNING FIELD-SYMBOL(<ls_booking_n>).
    
            <ls_booking_n>-BookingStatus = 'N'.
    
    
            APPEND VALUE #( %cid_ref = <ls_booking_n>-%cid )
                   TO it_booksuppl_cba ASSIGNING FIELD-SYMBOL(<ls_booksuppl>).
    
            LOOP AT lt_booksuppl_r ASSIGNING FIELD-SYMBOL(<ls_booksuppl_r>)
                               USING KEY entity
                               WHERE TravelId = <ls_travel_r>-TravelId
                               AND   BookingId = <ls_booking_r>-BookingId.
    
              APPEND VALUE #( %cid = <ls_travel>-%cid && <ls_booking_r>-BookingId && <ls_booksuppl_r>-BookingSupplementId
                              %data = CORRESPONDING #( <ls_booksuppl_r> EXCEPT TravelId BookingId ) )
                   TO <ls_booksuppl>-%target.
            ENDLOOP.
    
          ENDLOOP.
    
        ENDLOOP.
    
    * Modify Data to DB
        MODIFY ENTITIES OF Z04_DV_Travel_M IN LOCAL MODE
          ENTITY Z04_DV_Travel_M
          CREATE FIELDS ( AgencyId CustomerId BeginDate EndDate BookingFee TotalPrice CurrencyCode OverallStatus Description )
          WITH it_travel
            ENTITY Z04_DV_Travel_M
            CREATE BY \_Booking
            FIELDS ( BookingId BookingDate CustomerId CarrierId ConnectionId FlightDate FlightPrice CurrencyCode BookingStatus )
            WITH it_booking_cba
              ENTITY Z04_DV_Booking_M
              CREATE BY \_BookingSupplement
              FIELDS ( BookingSupplementId SupplementId Price CurrencyCode )
              WITH it_booksuppl_cba
          MAPPED DATA(it_mapped).
    
        mapped-z04_dv_travel_m = it_mapped-z04_dv_travel_m.
    
      ENDMETHOD.

    后面都弄好了,但是要想给前端用,还需要在Behavior Projection里面公开。

    g),use action copyTravel - Z04_PV_Travel_M

    use action copyTravel;

    后台都搞好了,画面端也要给加上,显示出来,客户才可以使用。

    h),画面表示 - Metadata

    做法是很简单,在Travel_M Metadata文件中的 任意 lineItem 里面添加下面这行代码

    { type:#FOR_ACTION, dataAction: 'copyTravel', label: 'Copy Travel' }

    注意这里是要在 List Report 页面上显示 Copy Travel,所以要在 lineItem上加

    如果要在 Object Page上显示该按钮,就要在 identification 上加。 

    以上代码准备好了,现在在 debug 模式下看一看内表里都有什么数据吧。

    4-2,debug - Copy Travel 按钮

    a),确认一下拷贝元数据

    Travel ID:2

    Booking_M:2条

    Booksuppl_M:Booking 的1条,没有Booking Supplement;Booking 的另一条 有1条 Book Suppl

    回到 List Report,点一下Copy Travel 按钮试试,别忘了打断点啊

    b),选中拷贝元,点Copy Travel 按钮

    崩了😓,去掉断点,然后重新加,再试一次

    就可以了哈~😓

    1),keys

    好像没啥问题, cid 也有,Travel ID也有

    Row  %CID                  %CID_REF  TRAVELID
    =============================================
    1    id-1754805330140-145            00000002
    
    2),Read Entity

    跟上面a 确认拷贝元数据 里面得到的结果相同

    Travel_M

    Row  TRAVELID  AGENCYID  CUSTOMERID  BEGINDATE  ENDDATE   BOOKINGFEE  TOTALPRICE  CURRENCYCODE  DESCRIPTION                          OVERALLSTATUS  CREATEDBY  CREATEDAT               LASTCHANGEDBY  LASTCHANGEDAT
    ============================================================================================================================================================================================================================
    1    00000002  070007    000608      20250517   20250518  20.00       900.00      USD           Business Trip for Christine, Pierre  X              Meier      20250430205413.0000000  CB9980000888   20250805092951.4655290
    

    Booking_M

    Row  TRAVELID  BOOKINGID  BOOKINGDATE  CUSTOMERID  CARRIERID  CONNECTIONID  FLIGHTDATE  FLIGHTPRICE  CURRENCYCODE  BOOKINGSTATUS  LASTCHANGEDAT
    ========================================================================================================================================================
    1    00000002  0001       20250515     000099      UA         1537          20250517    438.00       USD           N              20250502210758.0000000
    2    00000002  0002       20250515     000660      UA         1537          20250517    438.00       USD           N              20250502210758.0000000
    

    Booksuppl_M

    Row  TRAVELID  BOOKINGID  BOOKINGSUPPLEMENTID  SUPPLEMENTID  PRICE  CURRENCYCODE  LASTCHANGEDAT
    ========================================================================================================
    1    00000002  0002       01                   ML-0002       4.00   EUR           20250502210758.0000000
    
    3),变数 - it_travel

    - cid:使用拷贝元的cid

    - TravelId:为空,因为这里还用到了early numbering,稍后会自动採番

    Row  %CID                  TRAVELID  AGENCYID  CUSTOMERID  BEGINDATE  ENDDATE   BOOKINGFEE  TOTALPRICE  CURRENCYCODE  DESCRIPTION                          OVERALLSTATUS  CREATEDBY  CREATEDAT               LASTCHANGEDBY  LASTCHANGEDAT           %CONTROL
    =================================================================================================================================================================================================================================================================================
    1    id-1754805330140-145  00000000  070007    000608      20250810   20250909  20.00       900.00      USD           Business Trip for Christine, Pierre  O              Meier      20250430205413.0000000  CB9980000888   20250805092951.4655290  Structure: flat, not charlike
    
    4),变数 - it_booking

    Row  %CID_REF              TRAVELID  %TARGET
    ==============================================================
    1    id-1754805330140-145  00000000  Standard Table[0x13(136)]
    
    5),变数 - it_booking_cba

    中间出了点儿错,再试一遍cid 好像有点儿不一样了

    Row  %CID_REF              TRAVELID  %TARGET
    ==============================================================
    1    id-1754808184397-144  00000000  Standard Table[2x13(136)]
    

    双击target,显示内表

    Row  %CID                      TRAVELID  BOOKINGID  BOOKINGDATE  CUSTOMERID  CARRIERID  CONNECTIONID  FLIGHTDATE  FLIGHTPRICE  CURRENCYCODE  BOOKINGSTATUS  LASTCHANGEDAT           %CONTROL
    =================================================================================================================================================================================================================
    1    id-1754808184397-1440001  00000000  0001       20250515     000099      UA         1537          20250517    438.00       USD           N              20250502210758.0000000  Structure: flat, not charlike
    2    id-1754808184397-1440002  00000000  0002       20250515     000660      UA         1537          20250517    438.00       USD           N              20250502210758.0000000  Structure: flat, not charlike
    

    6),变数 - it_booksuppl_cba
    Row  %CID_REF                  TRAVELID  BOOKINGID  %TARGET
    ===========================================================================
    1    id-1754808184397-1440001  00000000  0000       Standard Table[0x9(96)]
    2    id-1754808184397-1440002  00000000  0000       Standard Table[1x9(96)]
    

    分别双击 target 内表

    Row  %CID                        TRAVELID  BOOKINGID  BOOKINGSUPPLEMENTID  SUPPLEMENTID  PRICE  CURRENCYCODE  LASTCHANGEDAT           %CONTROL
    ===================================================================================================================================================================
    1    id-1754808184397-144000201  00000000  0000       01                   ML-0002       4.00   EUR           20250502210758.0000000  Structure: flat, not charlike
    

    出了个错误

    通信エラー<?xml version="1.0" encoding="utf-8"?><errorxmlns="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"><code>RAISE_SHORTDUMP</code><message>実行時エラー: 'RAISE_SHORTDUMP'。OData 要求処理が異常終了しました。Eclipse 向け ABAP 開発ツールのフィードリーダを使用するか、トランザクション /IWFND/ERROR_LOG、/IWBEP/ERROR_LOG、または ST22 を使用して、実行時エラーを分析してください。OData サーブすのアプリケーションコンポーネントで、SAP 提供のサービスで発生したエラーについてサポートチケットを作成してください。</message><timestamp>20250810065047</timestamp></error>

    看了半天,说 key重复

    也不知道是什么原因,DB里面的数据,现在有点儿乱,我把採番表最大採番号之后的那些数据给删了,然后再试就OK了。

    就比如最大是4271,下一个是4272,那4272 已经存在了的话,你执行的可不就出重复key错误?

    7),执行结果

    执行完成之后,大概效果如下:

    本章讲了RAP actions,主要是实现了 Copy 功能。步骤就是

    - 后台拿到 keys,然后循环根据每个key,去拷贝关联表(Travel_M,Bookng_M,Booksuppl_M)

    - 其中还用到 early numbering来进行採番

    - 注意点就是 cid,cid_ref,因为是新规,尚未产生Key ID,所以必须用它们来做表关联

    以上就是本篇的全部内容。

    如果大家觉得还行,希望大家多点赞,收藏,转发,让更多热爱技术的人看到,感谢!

    更多SAP顾问业务知识请点击下面目录链接或东京老树根的博客主页

    https://blog.csdn.net/shi_ly/category_12216766.html

    东京老树根-CSDN博客

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值