mcpField
M
migrate-segw-to-rap

migrate-segw-to-rap

github.com/arc-mcp开发239238 次安装2 天前更新
代码仓库

关于 migrate-segw-to-rap

用于逆向分析经典 SEGW OData V2 服务并在同一 SAP 系统中重建为 RAP 服务的工作流技能。迁移后的对象放入独立包,旧服务继续运行。

主要功能

  1. 01检查 SEGW 模型和提供程序类
  2. 02将实体与关系转换为 RAP
  3. 03将旧操作映射到 RAP 行为
  4. 04创建 CDS 视图和服务对象

适用场景

  1. 01将旧版 OData V2 后端迁移到 RAP
  2. 02让新旧服务并行运行

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/arc-mcp/arc-1 --skill migrate-segw-to-rap

安装后在 Claude Code 中输入/migrate-segw-to-rap确认已加载。

详细介绍

将 SEGW OData V2 服务迁移为 RAP 服务

将经典的基于 SEGW 构建的 OData V2 服务(MPC/DPC/MPC_EXT/DPC_EXT)逆向工程为现代 RAP 服务(CDS 根视图 + 投影视图 + BDEF + SRVD + SRVB + 行为池),并部署到同一 SAP 系统中。 并行运行:旧服务继续保持在线;新的 RAP 服务部署到单独且可重置的软件包中。

领域示例。 此技能中的模板使用一个示例性的 Project → Tasks → TimeEntries 领域(实体名称如 ZR_DM_PROJECT、ZBP_DM_PROJECT、ZDM_PROJECT_D),以便具体说明结构。请将用户的实体贯穿全文进行替换——运行此技能的 LLM 应将每个实体标识符改写为与源服务相符的名称。

智能默认值(静默应用——研究前不要提问)

设置默认值原因
发现策略MPC 类源(Tier 1)ARC-1 可原生读取;比 $metadata 更丰富
目标包由用户提供。若未提供,则通过 SAPManage(action="create_package") 创建源包的子包;默认名称为 <source_package>_RAP。仅当用户明确要求时,才回退到 $TMP。确保迁移输出相互隔离且可重置。
目标传输用户提供的现有传输,或通过 SAPTransport(action="create", description="RAP migration of <legacy_service>", package="<target_package>") 自动创建。非 $TMP 包必需。
目标上的 OData 版本V4当前 SAP 标准;已适配 FE
RAP 场景托管,采用内部编号最简单的以读取为主的场景;与旧版 SEGW 行为相符
草稿以读取为主的实体关闭。若旧服务提供了 CUD(函数导入、深层插入或实体集上的 sap:updatable=true),且用户需要兼容 Fiori Elements,则开启。与旧服务的行为契约一致;启用草稿时,FE 列表和 OP 的效果最佳。
严格模式strict ( 2 )当前最佳实践
投影层必需——始终为每个 ZR_* 根视图创建一个 ZC_* 投影视图。 服务绑定公开投影视图,而非根视图。该技能的第 1 次运行因误读 CDS 错误而跳过了投影视图;这里明确说明,避免 LLM 遗漏。
投影上的 provider contract仅用于根投影,不得用于根视图或子投影。该契约只置于一层。7.58 上组合投影 BDEF + use draft 时,使用 transactional_query;以读取为主且不使用草稿时,使用 transactional_interface。用于根视图会导致 "only valid on projection views"。用于子投影会导致 "inappropriate provider contract on …"。子投影通过 redirected to parent 公开,并继承根投影的 BO 契约。第 3 次运行确认,在 7.58 上启用草稿需要 _query。
CDS 组合语法(托管)composition [0..*] of <child> as <name>——不带 on 子句。通过匹配键字段/子实体中的 with foreign key 隐式链接键7.58 不接受托管组合上的 on。正确的修复方式是删除 on,而不是改用 association to。
7.58 上的 @Semantics.*createdAt、lastChangedAt 和 localInstanceLastChangedAt 在 7.58 上均有效——将 localInstanceLastChangedAt 放在本地实例 etag 字段(abp_locinst_lastchange_tstmpl)上,与 SAP 自己的 RAP 生成器完全一致。@Semantics.businessDate.from/to 也有效。已在 S/4HANA 2023 上实时验证(758,2026-06-12):包含 localInstanceLastChangedAt 和 businessDate.from/to 的独立 CDS 视图均可激活。此前关于“7.58 上存在未知注解”的说明是误判——草稿注解确实仅在 7.55 之前的版本中缺失。
草稿表字段名(启用草稿时)使用 不带下划线 的 BO 别名大小写形式,例如 projectid、startdate,而不是 project_id、start_date。ABAP 会将 BDEF 别名(ProjectId、StartDate)规范化为 PROJECTID、STARTDATE——草稿表查找使用的是这个规范化后的名称,而不是活动表的 snake_case 名称。第 2 次运行:BDEF 激活失败,报错 "key field PROJECTID expected at position 2, found PROJECT_ID"。活动表可以保留 snake_case(BDEF 映射会处理);草稿表不行——它没有映射子句。
命名SAP 标准 Z<prefix>_<entity>:ZR_ 根视图、ZC_ 投影视图、ZI_<entity>_BEH BDEF、ZBP_ 行为池、ZUI_<service>_O4 V4 SRVB遵循 SAP 内部约定;前导 Z 是客户命名空间
写入前检查开启在 ARC-1 配置中设置 SAP_ABAP_RELEASE=<your_release>(PR #255),以确保检查预设与系统版本一致。无需再使用较旧的 SAP_LINT_BEFORE_WRITE=false 变通方法。
写入前 SAP 检查仅针对激活阻塞问题时开启我们依赖激活反馈,而非 --check-before-write

输入

用户必须至少提供以下信息:

  • 以下信息之一:SEGW 服务技术名称(例如 Z<SOMETHING>_SRV)、包含 SEGW 生成类的程序包,或 MPC 类名称。未提供时请询问。
  • 目标程序包,用于存放新的 RAP 对象。如果未提供且已知源程序包,技能会创建一个(<source_package>_RAP);否则会询问。只有用户明确要求时才默认使用 $TMP。
  • 非 $TMP 写入所需的 传输请求——现有传输请求或自动创建的传输请求(参见智能默认值)。

可选:

  • RAP 场景(默认:托管式,使用内部编号)。
  • 草稿开/关(技能根据旧版 CUD 接口推断)。
  • OData 版本(默认:V4)。
  • 命名覆盖项(默认:SAP 标准 Z<prefix>_<entity>)。

如果只提供旧版标识符,技能会应用智能默认值,并在第 5 阶段展示解析后的计划;用户确认 ok 后才会进行任何写入。


阶段 0 — 授权预检

在任何建模工作之前运行。如果缺少任何内容,则干净地中止,并指出用户需要的具体 SAP 授权对象。

0a. 服务端功能与授权探测
SAPManage(action="probe")

根据响应断言:

  • systemType ∈ {onprem, btp} — 命名和管理字段选择所需
  • rap.available == true — 失败时提示:"此系统未公开 RAP/CDS 端点。无法继续迁移。"
  • transport.available == true — 失败时提示:"传输 API 不可用;请检查 ICF /sap/bc/adt/cts/transports。"
  • authProbe.searchAccess == true — 失败时提示:"用户无法搜索 ADT 对象;需要 S_DEVELOP ACTVT=03 OBJTYPE=CLAS DEVCLASS=<source-pkg>。"
  • authProbe.transportAccess == true — 失败时提示:"用户无法读取传输请求;需要 S_TRANSPRT ACTVT=03。"
0b. 读取旧版 MPC 类 — 确认对源对象的读取权限
SAPRead(type="CLAS", name="<MPC>", method="*")

如果 HTTP 403:停止并提示 "无法读取 <MPC> — 需要 S_DEVELOP ACTVT=03 OBJTYPE=CLAS DEVCLASS=<source-pkg> P_GROUP=<auth-group>。请在 SAP GUI 中运行 SU53,查看缺少的字段。"

0c. 确认对目标程序包的写入权限
SAPTransport(action="check", objectType="DDLS", objectName="ZX_PRECHECK", package="<target_package>")

然后对临时 DDLS 执行写入并删除的试运行:

SAPWrite(action="create", type="DDLS", name="ZR_DM_PRECHECK",
         source="@AccessControl.authorizationCheck: #NOT_REQUIRED\ndefine root view entity ZR_DM_PRECHECK as select from t000 { client }",
         description="ARC-1 preflight",
         package="<target_package>",
         transport="<transport>")
SAPWrite(action="delete", type="DDLS", name="ZR_DM_PRECHECK")

创建时遇到 403:停止并提示 "无法写入 <target_package> — 需要 S_DEVELOP ACTVT=01,02,07 OBJTYPE=DDLS DEVCLASS=<target_package>。"

如果以上三项全部通过,则输出 "阶段 0 ✓ 授权正常 — 继续发现。" 并继续。


阶段 1 — 发现旧版服务

1a. 解析 MPC 类名称

如果用户提供了服务名称(例如 <legacy_service>):

  • 尝试按惯例处理:去掉 _SRV → 追加 _MPC → <MPC_class>。使用 SAPRead 验证。
  • 如果未找到:
    SAPQuery(action="sql", sql="SELECT obj_name, devclass FROM tadir
      WHERE pgmid = 'R3TR' AND object = 'CLAS'
        AND obj_name LIKE 'Z%_MPC' ORDER BY obj_name")
    
    并请用户选择。

如果用户提供了程序包(例如 <source_package>):

  • SAPRead(type="DEVC", name="<package>")
    
    Filter for classes ending _MPC.

If user gave an MPC class directly: use it as-is.

Determine the corresponding _MPC_EXT, _DPC, _DPC_EXT by name convention.

1b. Confirm the four classes exist + record metadata
SAPSearch(action="object", query="<base>_*", maxResults=10)

预期:4 条结果。记录每条结果的 objectName 和 packageName。

向用户输出:

Legacy service: <legacy_service>
  Package:      <source_package>
  MPC class:    <MPC_class>      (model)
  MPC_EXT:      <MPC_EXT_class>  (model overrides — usually empty)
  DPC class:    <DPC_class>      (data provider — generated)
  DPC_EXT:      <DPC_EXT_class>  (data provider — your custom code)

阶段 2 — 从 MPC 源代码提取 OData 模型

MPC 类的私有 DEFINE_* 方法包含完整模型。读取这些方法。

2a. 方法清单
SAPRead(type="CLAS", name="<MPC>", method="*")

需要查找:

  • DEFINE_<entity> — 每种实体类型一个
  • DEFINE_ASSOCIATIONS — 所有关联和导航属性
  • DEFINE_ACTIONS — 所有函数导入
2b. 逐个实体读取

对于每个 DEFINE_<entity> 方法:

SAPRead(type="CLAS", name="<MPC>", method="DEFINE_<ENTITY>")

解析方法体中的以下内容:

  • 实体类型名称和实体集名称(查找 create_entity_type(...) 和 create_entity_set(...))
  • 底层 ABAP DDIC 绑定(查找 bind_structure(...) 或 set_data_source(...))
  • 每个属性:名称、EDM 类型、最大长度、是否为键、是否可排序/筛选/创建/更新 (查找 create_property(...) 后跟随的 set_* 调用)
2c. 关联
SAPRead(type="CLAS", name="<MPC>", method="DEFINE_ASSOCIATIONS")

针对每个 create_association(...) 解析:

  • 关联名称
  • 主实体及其基数
  • 从属实体及其基数
  • 导航属性名称
  • 参照约束(主属性 ↔ 从属属性)
2d. 函数导入
SAPRead(type="CLAS", name="<MPC>", method="DEFINE_ACTIONS")

针对每个 create_action(...) 解析:

  • 操作 / 函数导入名称
  • HTTP 方法
  • 返回类型(实体 / 复杂类型 / 基本类型 / void)
  • 返回基数
  • 参数:名称、类型、长度、模式(in/out/inout)
2e. 编译为结构化模型

完成阶段 2 后,你应该能够打印出如下完整表格:

=== Extracted OData model ===

Entity types (3):
  Project    bound to ZDM_PROJECT     key: ProjectId       12 properties
  Task       bound to ZDM_TASK        key: TaskId          15 properties (FK ProjectId)
  TimeEntry  bound to ZDM_TIMEENTRY   key: EntryId         15 properties (FKs TaskId, ProjectId)

Entity sets (3):  ProjectSet, TaskSet, TimeEntrySet

Associations (2):
  Project_Tasks       Project [1] ↔ Task [0..n]            ref: ProjectId ↔ ProjectId   nav on Project: Tasks
  Task_TimeEntries    Task [1]    ↔ TimeEntry [0..n]       ref: TaskId    ↔ TaskId      nav on Task:    TimeEntries

Function imports (1):
  ApproveProject  POST  in: ProjectId (Edm.String, len 10)  return: Project (Entity, 1)

打印该表格,并在继续之前询问用户:“这符合你的预期吗?”


阶段 3 — 从 DPC_EXT 中提取行为

3a. 方法清单
SAPRead(type="CLAS", name="<DPC_EXT>", method="*")

对每个重新定义的方法进行分类:

方法模式对应的 RAP 行为
<EntitySet>_GET_ENTITYSETCDS 视图的读取访问权限——在托管场景中通常无需额外实现
<EntitySet>_GET_ENTITY同上——在托管场景中通常无需额外实现
<EntitySet>_CREATE_ENTITY启用 BDEF create(可选地配合确定和验证)
<EntitySet>_UPDATE_ENTITY启用 BDEF update
<EntitySet>_DELETE_ENTITY启用 BDEF delete
/IWBEP/IF_MGW_APPL_SRV_RUNTIME~EXECUTE_ACTION根据函数导入配置 BDEF 静态或实例 action(...)
3b. 逐个读取方法体(仅限包含逻辑的方法)

针对每个重新定义的方法,判断:

  • 简单 GET(仅包含 SELECT * FROM <table> WHERE <key> + MOVE-CORRESPONDING):完全省略 — RAP 托管场景会自动处理。
  • 带筛选条件的 GET(包含额外的 WHERE 子句、派生字段):转换为 CDS 视图筛选条件或字段别名。
  • 函数导入:读取方法体,识别业务效果(状态更新、计算、外部调用),并转换为带有相应行为池方法的 BDEF 操作。
SAPRead(type="CLAS", name="<DPC_EXT>", method="<METHOD_NAME>")

对于大多数简单的方法,读取 30 行即可。对于复杂业务逻辑,预计需要读取 100 行以上,并仔细规划重写工作。

3c. 编译为行为摘要
=== Extracted behavior ===

Read-only (drop, free in RAP managed):
  PROJECTSET_GET_ENTITYSET, PROJECTSET_GET_ENTITY,
  TASKSET_GET_ENTITYSET, TASKSET_GET_ENTITY,
  TIMEENTRYSET_GET_ENTITYSET, TIMEENTRYSET_GET_ENTITY

Function imports → BDEF actions:
  ApproveProject(ProjectId)     ➜  static action ApproveProject parameter $self
                                   sets Status='A' + admin fields, returns Project
                                   (legacy did UPDATE + COMMIT WORK + reread; RAP managed
                                    handles persistence — no manual COMMIT needed)

Custom create/update/delete: none (read-only service)

如果存在 _CREATE_ENTITY / _UPDATE_ENTITY / _DELETE_ENTITY,请列出这些方法,并注明哪些内容将成为 BDEF 确定或验证。


阶段 4 — 读取底层表

针对阶段 2 中识别出的每个表(例如 ZDM_PROJECT、ZDM_TASK、ZDM_TIMEENTRY):

SAPRead(type="TABL", name="<table>")

记录:

  • 字段列表及其 ABAP 类型和长度
  • 键字段
  • 管理字段惯例(经典 ERNAM/ERDAT/ERZET/AENAM/AEDAT/AEZET 与现代 abp_*)
  • 外键引用(with foreign key 子句)

这些表保持不变——新的 RAP CDS 视图将从中读取数据。


阶段 5 — 设计方案与用户审批

执行任何写入操作之前,先向用户展示完整的 RAP 设计方案并获得明确批准,然后再继续。

5a. 命名
Legacy → New RAP

ZDM_PROJECT (table, untouched)        ─► ZR_DM_PROJECT  (CDS root view entity)
                                        ZC_DM_PROJECT  (CDS projection view, exposed via service)
                                        ZI_DM_PROJECT  (BDEF for root)
ZDM_TASK    (table, untouched)        ─► ZR_DM_TASK    + ZC_DM_TASK
ZDM_TIMEENTRY (table, untouched)      ─► ZR_DM_TIMEENTRY + ZC_DM_TIMEENTRY

(no SEGW project on the new side)     ─► ZUI_DM_PROJECTS  (service definition — exposes ZC_*)
                                        ZUI_DM_PROJECTS_O4 (service binding, OData V4 UI)
                                        ZBP_DM_PROJECT  (behavior pool class for actions)
5b. 组合树
root: ZR_DM_PROJECT
  composition [0..*] of ZR_DM_TASK as _Tasks
                        composition [0..*] of ZR_DM_TIMEENTRY as _TimeEntries

该树与 SEGW 关联关系一一对应,只不过现在它们是组合关系(父级拥有子级),因此可直接获得 RAP 托管生命周期语义。

5c. 操作
ZI_DM_PROJECT (BDEF):
  define behavior for ZR_DM_PROJECT alias Project
    persistent table zdm_project
    lock master
    authorization master ( instance )
    etag master last_changed_at
    
    {
      field ( readonly ) ProjectId;
      action approve_project result [1] $self;        ← matches SEGW ApproveProject
    }

approve_project 操作 → 由 ZBP_DM_PROJECT 中的处理程序处理 → 通过框架的 MODIFY ENTITIES 接口设置 status='A' 和管理字段(无需手动执行 UPDATE + COMMIT)。

5d. 服务绑定 URL
Old (SEGW V2):  /sap/opu/odata/sap/<legacy_service>
New (RAP V4):   /sap/opu/odata4/sap/zui_dm_projects_o4/srvd_a2x/sap/zui_dm_projects/0001

(如果服务绑定发布为 UI,则使用 FLP 绑定的 URL。)

5e. 展示并询问

向用户打印完整方案。等待用户明确回复**“是,继续”**或提出修改。如果用户希望进行更改(例如更改命名、启用草稿或更改 RAP 场景),请修改方案并重新展示。

非交互模式。 如果用户的初始提示已提供阶段 5 所需的等效输入(旧版服务、目标包、传输、场景、是否启用草稿),并且表示“端到端运行”/“不要停下来等待审核”,则跳过形式上的*“是,继续?”*审批环节。将方案作为清单打印,并直接进入阶段 5f / 阶段 6。只有在阶段 5 发现实际冲突(缺少输入、场景标志相互矛盾等)时才暂停。 参见第 6 次运行的发现——否则,全链路自动化会遇到一个虚假的审批关卡。

5f. 将工件列表锁定为 Phase 6 合约

离开 Phase 5 前,技能 MUST 输出一份工件列表合约,Phase 6 将回显该列表并标注状态。请原样打印:

=== Phase 6 will create + activate these artifacts (Phase 5 → Phase 6 contract) ===

Roots (CDS):           [ ] ZR_DM_PROJECT  [ ] ZR_DM_TASK  [ ] ZR_DM_TIMEENTRY
Projections (CDS):     [ ] ZC_DM_PROJECT  [ ] ZC_DM_TASK  [ ] ZC_DM_TIMEENTRY
                       (projections are MANDATORY — service binding never exposes roots directly)
Draft tables (TABL):   [ ] ZDM_PROJECT_D  [ ] ZDM_TASK_D  [ ] ZDM_TIMEENTRY_D
                       (only when draft is on — based on Phase 5d)
BDEFs:                 [ ] ZR_DM_PROJECT (root behavior)  [ ] ZC_DM_PROJECT (projection behavior)
Behavior pool (CLAS):  [ ] ZBP_DM_PROJECT (empty shell — generate_behavior_implementation will populate)
Service definition:    [ ] ZUI_DM_PROJECTS  (exposes projections, NOT roots)
Service binding:       [ ] ZUI_DM_PROJECTS_O4 (OData V4 UI)

Manual steps remaining after Phase 6 build:
  [ ] /IWFND/MAINT_SERVICE — register V4 service group ZUI_DM_PROJECTS_O4 (else 403 at runtime)

Phase 6 必须在最后逐项检查此列表,并打印相同的复选框列表,每项标记为 ✓ 或 ✗(标记为 ✗ 时附上失败的工具调用参数和响应)。在每个方框都标记为 ✓ 或由用户明确豁免之前,切勿宣布 Phase 6 已完成。


Phase 6 — 构建 RAP 技术栈

6a. 重置现有工件(幂等地重新运行)

如果 <target_package> 包中包含上次运行留下的对象,请先删除:

SAPRead(type="DEVC", name="<target_package>")

按照依赖关系的逆序处理每个对象(SRVB → SRVD → BDEF → DDLS_C → DDLS → CLAS → TABL):

SAPWrite(action="delete", type="<type>", name="<name>", transport="<transport>")

(SAP 建议重新运行前先释放传输,但对于可重置的演示,我们只需重复使用 <transport>,没有问题。)

此外还要进行 TADIR 交叉检查(Run 1 发现一个存根草稿表位于不同的传输中,未被包扫描发现;Run 6 发现 ADT 404 错误可能与 TADIR 中的幽灵行并存,这就是“脑裂”故障模式)。在 ARC-1 ≥ 0.9.5(PR #270)上,规范的重置依据来自 source="both" 模式,该模式会在一次调用中查询 ADT 和 DB TADIR,并为所有不一致的名称输出 splitBrain 警告数组:

SAPSearch(searchType="tadir_lookup",
          source="both",
          names=["ZDM_PROJECT_D","ZDM_TASK_D","ZDM_TIMEENTRY_D",
                 "ZR_DM_PROJECT","ZR_DM_TASK","ZR_DM_TIMEENTRY",
                 "ZC_DM_PROJECT","ZC_DM_TASK","ZC_DM_TIMEENTRY",
                 "ZI_DM_PROJECT_BEH","ZI_DM_TASK_BEH","ZI_DM_TIMEENTRY_BEH",
                 "ZBP_DM_PROJECT","ZUI_DM_PROJECTS","ZUI_DM_PROJECTS_O4"])

按以下顺序读取响应:

  1. results — 通过 ADT 发现的名称(用于判断“对象是否实际存在”)。为空 = 从 ADT 的角度看,该包是干净的。
  2. splitBrain — ADT 和 DB 结果不一致的名称(“幽灵”情况)。每个数组条目都会告知你哪个来源发现了该名称。实际处理时:如果名称只出现在 DB 中(有 TADIR 行,但没有 ADT 对象),那么它就是幽灵——对于 SAPWrite create,将其视为已不存在。如果名称只出现在 ADT 中(较罕见),则说明 ADT 缓存已过期——几秒后再次运行 SAPSearch。

source="both" 需要 sql 范围(DB 部分使用自由 SQL 路径)。如果你的配置文件没有该范围,则回退到 source="adt"(默认值)进行实时检查,并使用 SAPQuery 更全面地扫描包:

SAPQuery(action="sql", sql="SELECT obj_name, object, devclass, korrnum FROM tadir
  WHERE devclass = '<target_package>' AND obj_name LIKE 'Z%'")

如果计划创建的名称出现在不同的包中,它就是遗留存根——请通过其实际所属的传输删除,然后再继续。

6a-gen.(可选)使用 SAP 官方 ABAP 生成器初始化根 BO

如果官方 SAP ABAP MCP 服务与 ARC-1 一同连接(随 VS Code 的 ABAP Development Tools 提供;在 Eclipse ADT 3.60+ 中启用;显示为 abap-mcp 服务),其 Generate ABAP Repository Objects 框架可以为你搭建根托管型 + 草稿型 BO。 它不能取代此技能——阶段 1–5(发现 + 转换)以及阶段 6 的大部分工作仍由 ARC-1 负责,因为该生成器是单实体且一次性的:

“最多只能生成一个实体,不支持组合或层级结构”,且*“不适用于生成后的修改——后续更改需要开发者手动处理。”*

因此,只有在遗留服务实际上是一个单根实体,采用托管型 + 草稿型,且其表包含现代时间戳字段 last_changed : abp_lastchange_tstmpl 和 local_last_changed : abp_locinst_lastchange_tstmpl 时,它才适用于 SEGW 迁移。大多数 SEGW 服务都包含多个实体、经典的 ERDAT/AEDAT 管理字段和函数导入 → 不适用此生成器;照常使用手动构建(6b)。如果适用,它也只会初始化根实体;之后仍由 ARC-1 添加子实体、组合、操作和字段映射(生成器无法完成这些工作,因为它是一次性的)。

如果使用此生成器:

  1. **探测。**没有 abap_generators-list_generators 工具(来自 abap-mcp 服务)→ 跳到 6b,手动构建。
  2. 解析 ID——因版本而异,绝不硬编码。调用 abap_generators-list_generators, 按显示名称匹配 "OData UI Service" 生成器,使用返回的 id(已在实际环境中验证: SAP_BASIS 758 / S4 2023 上为 uiservice;816 / ABAP Platform 2025 上为 ui-service;816 上还有 x-ui-service,用于“从头开始”)。未列出 → 跳到 6b。
  3. 获取架构 → 生成。调用 abap_generators-get_schema(generatorId, packageName="<target_package>", referencedObjectType="TABL", referencedObjectName="<root table, e.g. ZDM_PROJECT>"),根据返回的架构填写所有必填字段,然后调用 abap_generators-generate_objects(generatorId, …)。 这是一个变更操作——目标为 <target_package> + <transport>,遵循与任何写入操作相同的防护措施。
  4. 交回 ARC-1。从 6b 中的步骤 2 开始继续,处理子实体/组合、投影契约层级、操作(approve_project)+ 处理程序主体,以及激活——并适配生成器为根实体选择的名称。协调其根 CDS/BDEF 命名与此技能的 ZR_*/ZC_*/ZI_*_BEH 计划,或接受生成器的名称并更新阶段 5f 的契约。

说明你选择了哪条路径,以便运行过程可审计。如有疑问,优先选择手动构建(6b)—— 这是适用于多实体 SEGW 结构的成熟路径,且不依赖第二个 MCP 服务。

6b. 构建顺序——严格执行,不得自行调整

以下顺序是经过惨痛教训总结出来的(运行 1,调用 #22–33)。**不得调整顺序。**每一步都能避免下一步中的特定故障模式。

步骤 1 — 草稿表(仅当阶段 5d.draft = ON 时)
SAPWrite(action="batch_create", objects=[
  { type: "TABL", name: "ZDM_PROJECT_D",   source: "<draft table source — see template below>", package: "<target_package>", transport: "<transport>" },
  { type: "TABL", name: "ZDM_TASK_D",      source: "<...>", package: "<target_package>", transport: "<transport>" },
  { type: "TABL", name: "ZDM_TIMEENTRY_D", source: "<...>", package: "<target_package>", transport: "<transport>" }
])
SAPActivate(action="activate", objects=[ {type:"TABL", name:"ZDM_PROJECT_D"}, ... ])

草稿表模板——运行 2 总结出的三条规则:

  1. 字段名使用 BO 别名大小写格式,不带下划线(projectid、startdate, 而不是 project_id、start_date)。ABAP 会将 BDEF 别名规范化为大写且不带下划线; 草稿绑定会按该名称查找,而不是按活动表的蛇形命名法查找。
  2. 必须包含 creationtimestamp 和 lastchangedstamp 列,以便 BDEF 中的 total etag + etag master 子句能够绑定。
  3. 必须设置 @AbapCatalog.enhancement.category : #NOT_EXTENSIBLE——如果不设置, TABL 保存会在 7.58 上失败。
@EndUserText.label : 'Demo: Project (draft shadow)'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #ALLOWED
define table zdm_project_d {
  " Field names match the BO aliases (no underscore) — required by RAP draft binding.
  " The active table ZDM_PROJECT can keep its snake_case names; the BDEF mapping bridges
  " them. The draft table has no mapping clause, so its names must align with the BO directly.

  key client            : abap.clnt not null;
  key projectid         : abap.char(10) not null;
  title                 : abap.char(100);
  description           : abap.char(255);
  status                : abap.char(1);
  startdate             : abap.dats;
  enddate               : abap.dats;
  erdat                 : abap.dats;
  erzet                 : abap.tims;
  ernam                 : abap.char(12);
  aedat                 : abap.dats;
  aezet                 : abap.tims;
  aenam                 : abap.char(12);

  " Required for `total etag CreationTimeStamp` + `etag master LastChangedStamp` in BDEF.
  " Computed in CDS root view via dats_tims_to_tstmp(...); persisted on draft instances.
  creationtimestamp     : abap.dec(15,0);
  lastchangedstamp      : abap.dec(15,0);

  include sych_bdl_draft_admin_inc;
}

ZDM_TASK_D 以相同方式镜像,包含 taskid + projectid(外键)以及所有任务专属列(别名不带下划线)。 ZDM_TIMEENTRY_D 以相同方式镜像,包含 entryid + taskid + projectid(外键)。

步骤 2 — CDS 根视图(ZR_DM_*)

自上而下:先创建父级,再创建子级。使用 composition [0..*] of <child> as <name> — 托管场景不要使用 on 子句。

如果你的旧版 DPC_EXT 实现了 Aedat → Erdat 回退逻辑(运行 1 发现了这一点),请将其写入根视图的时间戳计算中。在 7.58 上,使用 @Semantics.systemDateTime.createdAt、 lastChangedAt,以及 localInstanceLastChangedAt — 最后一项用于标注本地实例 etag 字段(abp_locinst_lastchange_tstmpl),这是托管草稿 BO 所需的,并且也是 SAP 自己的 RAP 生成器在 758 上生成的内容。已在 S/4HANA 2023 上实测:同时包含 localInstanceLastChangedAt 和 @Semantics.businessDate.from/to 的独立视图都可以激活。只有真正旧的版本(< 7.55,即草稿功能推出之前)才不支持这些内容 — 在 7.58 上切勿移除它们。

@Semantics.systemDateTime.createdAt : true
dats_tims_to_tstmp(
  zdm_project.erdat,
  zdm_project.erzet,
  abap_system_timezone($session.client, 'NULL'),
  $session.client,
  'NULL'
) as CreationTimeStamp,

@Semantics.systemDateTime.lastChangedAt : true
dats_tims_to_tstmp(
  case zdm_project.aedat when '00000000' then zdm_project.erdat else zdm_project.aedat end,
  case zdm_project.aedat when '00000000' then zdm_project.erzet else zdm_project.aezet end,
  abap_system_timezone($session.client, 'NULL'),
  $session.client,
  'NULL'
) as LastChangedStamp

组合 CDS 的激活顺序。 默认情况下,batch_create 会按数组顺序逐个激活对象,因此父级 composition [0..*] of ZR_DM_TASK as _Tasks 会在 ZR_DM_TASK 存在之前激活,导致报错 "data source ZR_DM_TASK does not exist"(运行 6 的复现案例)。有三种安全方案,可根据实际运行情况选择:

  1. 使用 activateAtEnd: true 的 batch_create(ARC-1 ≥ 0.9.5,PR #270)。先为每个对象写入未激活的草稿,然后调用一次末尾的 activateBatch,让 SAP 激活器一次性解析交叉引用。这是 ARC-1 支持该功能时的推荐做法 — 只需调用一次工具,无需执行恢复步骤:
    SAPWrite(action="batch_create", activateAtEnd: true, objects=[...])
    
  2. 对每个根视图分别调用 SAPWrite(action="create"),然后在最后(所有 DDL 源都已写入之后)执行一次 SAPActivate(action="activate", objects=[...]) 批量激活。适用于所有 ARC-1 版本。
  3. 手动自底向上:先 SAPWrite create 父级 → 再用 SAPActivate 激活子级 → 最后激活父级。需要调用更多工具;很少需要用到。

同样的修复也适用于步骤 3 的投影视图(镜像组合链)。在最后一起激活三个投影视图,而不要逐个内联激活。

步骤 3 — CDS 投影视图(ZC_DM_*)— 切勿跳过

运行 1 在此处悄然遗漏了这一层。此处出现 provider contract 错误,意味着“创建投影视图”,而不是“移除 contract”。

**重要:provider contract transactional_interface 仅应放在根投影视图上(ZC_DM_PROJECT)。**子投影视图(ZC_DM_TASK、ZC_DM_TIMEENTRY)使用不带修饰的 as projection on …,并通过 redirected to parent 继承 BO contract。将 contract 放在子级上会在激活时导致 "inappropriate provider contract on …" 错误(运行 2)。

根投影视图 — 带 contract

根据场景选择 contract:

  • 托管 + 草稿 + CUD(本技能的默认场景):provider contract transactional_query (运行 3 在 7.58 上的证据:组合使用投影 BDEF 和 use draft 时,会拒绝 transactional_interface)。
  • 主要用于读取、无草稿、无 CUD:provider contract transactional_interface。
@AccessControl.authorizationCheck : #NOT_REQUIRED
@EndUserText.label : 'Demo project — UI projection (root)'
@Metadata.allowExtensions: true
define root view entity ZC_DM_PROJECT
  provider contract transactional_query
  as projection on ZR_DM_PROJECT
{
  key ProjectId,
      Title,
      Description,
      Status,
      StartDate,
      EndDate,
      Erdat, Erzet, Ernam, Aedat, Aezet, Aenam,
      CreationTimeStamp,
      LastChangedStamp,
      _Tasks : redirected to composition child ZC_DM_TASK
}
子投影视图 — 不带 contract,并使用 redirected to parent
@AccessControl.authorizationCheck : #NOT_REQUIRED
@EndUserText.label : 'Demo task — UI projection (child)'
@Metadata.allowExtensions: true
define view entity ZC_DM_TASK
  as projection on ZR_DM_TASK
{
  key TaskId,
      ProjectId,
      Title, Description, Status, Priority, DueDate, AssignedTo, EstimatedHours,
      Erdat, Erzet, Ernam, Aedat, Aezet, Aenam,
      _Project      : redirected to parent ZC_DM_PROJECT,
      _TimeEntries  : redirected to composition child ZC_DM_TIMEENTRY
}

ZC_DM_TIMEENTRY 使用相同结构(不带 contract,_Task : redirected to parent ZC_DM_TASK)。

请注意,子级使用 define view entity(而不是 define root view entity)— 只有 BO 根级才使用 define root view entity。

步骤 4 — 空行为池(由步骤 7 自动创建骨架)

仅编写空的全局类外壳。此处不再预先创建本地处理程序类(lhc_project、 lhc_task、lhc_timeentry)——步骤 7 的 SAPWrite action=generate_behavior_implementation(PR-C,ARC-1 ≥ post-2026-05-10) 会在缺少时自动创建这些类,然后在一次调用中注入所有必需的处理程序签名和存根。 运行 2 中 CCDEF/CCIMP 的强制 ADT 粘贴暂停已取消。

SAPWrite(action="create", type="CLAS", name="ZBP_DM_PROJECT",
         source="CLASS zbp_dm_project DEFINITION
                   PUBLIC ABSTRACT FINAL
                   FOR BEHAVIOR OF zr_dm_project.
                 ENDCLASS.

                 CLASS zbp_dm_project IMPLEMENTATION.
                 ENDCLASS.",
         description="Behavior pool for ZR_DM_PROJECT BO",
         package="<target_package>", transport="<transport>")
SAPActivate(type="CLAS", name="ZBP_DM_PROJECT")

预期的激活行为。 此处的 SAPActivate 可能会返回 “no behavior definition for zr_dm_project” ——这是预期且正常的。BDEF 尚不存在;它会在步骤 5 中创建。 不要将此视为真正的失败。BDEF 就位后,该类即可顺利激活;步骤 7(generate_behavior_implementation)也会连同处理程序一起重新激活 该类。可以:

  • 此处跳过激活(仅写入),依靠步骤 7 激活。
  • 或接受警告,并在步骤 10 的批量激活中重新激活该类。

就是这样。此处不写入 CCDEF/CCIMP。无需 ADT 暂停。继续步骤 5。

步骤 5 — 将托管草稿规则内置的根 BDEF

运行 1 的第 #22–31 次调用通过实际尝试发现了十条托管草稿规则。将它们内置:

managed implementation in class zbp_dm_project unique;
strict ( 2 );
with draft;

define behavior for ZR_DM_PROJECT alias Project
  persistent table zdm_project
  draft table zdm_project_d
  lock master
  total etag CreationTimeStamp                 " <- required by `with draft`
  etag master LastChangedStamp
  authorization master ( instance )
{
  create;
  update;
  delete;

  field ( readonly : update ) ProjectId;
  field ( readonly )          CreationTimeStamp, LastChangedStamp;

  " composition associations: only `create` is allowed in the inline block
  association _Tasks { create; with draft; }

  draft action Edit;
  draft action Activate optimized;
  draft action Discard;
  draft action Resume;
  draft determine action Prepare;              " <- 'determine' keyword required, not just 'draft action'

  action approve_project result [1] $self;     " <- DO NOT remove on activation failure;
                                               "    Step 7 (generate_behavior_implementation)
                                               "    auto-creates the lhc_project skeleton and
                                               "    injects the handler in one call.

  mapping for zdm_project { … all field maps … }
}

define behavior for ZR_DM_TASK alias Task
  persistent table zdm_task
  draft table zdm_task_d
  lock dependent by _Project                   " <- not _Project of root; use the cross-BO assoc you added in roots
  etag dependent by _Project
  authorization dependent by _Project
{
  update; delete;

  field ( readonly : update ) TaskId, ProjectId;   " <- lock-by reference fields MUST be readonly
  association _Project    { with draft; }
  association _TimeEntries { create; with draft; }

  " NO draft Edit/Activate/Discard/Resume — those belong on the root only.

  mapping for zdm_task { … }
}

define behavior for ZR_DM_TIMEENTRY alias TimeEntry
  persistent table zdm_timeentry
  draft table zdm_timeentry_d
  lock dependent by _Project                   " <- not by _Task — chain must reach a `lock master`
  etag dependent by _Project
  authorization dependent by _Project
{
  update; delete;

  field ( readonly : update ) EntryId, TaskId, ProjectId;
  association _Project { with draft; }
  association _Task    { with draft; }

  mapping for zdm_timeentry { … }
}

如果 TimeEntry 只能访问 _Task 而无法访问 _Project,则必须在 ZR_DM_TIMEENTRY(根视图)中添加一个跨 BO 的 association to ZR_DM_PROJECT as _Project——锁定链必须终止于持有 lock master 的实体。

步骤 6 — 投影 BDEF

为投影声明行为别名。运行 6 发现,7.58 会强制执行投影 BDEF 中两条不明显的语法规则——将它们内置:

  1. 当根 BDEF 声明了 with draft 时,use draft; 必须位于顶部(而非主体内部)。否则,use action Edit/Activate/Discard/Resume/Prepare 会被拒绝。
  2. 在 use association _X { ... } 块内,写裸操作名称(create;),不要写 use create;——use 关键字只能用于顶层。

根投影视图的规范投影 BDEF(托管 + 草稿 + CUD 场景):

projection;
strict ( 2 );
use draft;

define behavior for ZC_DM_PROJECT alias Project
  use etag
{
  use create;
  use update;
  use delete;

  use action Edit;
  use action Activate;
  use action Discard;
  use action Resume;
  use action Prepare;
  use action approve_project;

  use association _Tasks { create; }     " <- bare 'create;' inside, NOT 'use create;'
}

子投影(ZC_DM_TASK、ZC_DM_TIMEENTRY)需要在同一 DDLS 中或作为单独定义,拥有自己的投影 BDEF 块;每个块都使用 use association _Project / _Task 来公开向上的组合,以形成锁定主控链。在关联主体内使用裸 create;,规则相同。

步骤 7 — 生成行为实现(一次调用,完全自主)
SAPWrite(action="generate_behavior_implementation", type="CLAS", name="ZBP_DM_PROJECT")

这一次调用(PR-C,ARC-1 ≥ 2026-05-10 之后)会:

  1. 读取类元数据 → 提取 <class:rootEntityRef>,自动发现绑定的 BDEF(无需传入 bdefName)。
  2. 交叉验证 MAIN 的 FOR BEHAVIOR OF zr_dm_project 与 BDEF 的 managed implementation in class zbp_dm_project unique 是否一致。不一致时拒绝修改。
  3. 调用 scaffold_rap_handlers 使用的同一脚手架引擎,并设置 autoApply=true:
    • 自动创建缺失的 lhc_<alias> 骨架(CCDEF + CCIMP)。
    • 为 BDEF 要求的每个 action / determination / validation / authorization 注入 METHODS … 签名。
    • 在 CCIMP 中注入空的 METHOD … ENDMETHOD. 存根。
  4. 通过 PR #257 的 include= 路径,在同一把有状态锁下写入 CCDEF + CCIMP。
  5. 激活该类。

返回包含 discovery、validation、scaffoldChanged、计数信息和 activation.success 的结构化 JSON 报告。如果激活因已知的 "Local classes of CL_ABAP_BEHAVIOR_HANDLER…" 过时激活耦合问题而失败,响应会提供包含具体恢复选项的引导式 activation.hint,而不是抛出异常——刚写入的 CCDEF/CCIMP 源代码仍可用于这两种恢复方式。

如果激活因其他不明显的原因失败,请运行 SAPDiagnose(action="object_state", type="CLAS", name="ZBP_DM_PROJECT")(PR #254),查看每个 include 的激活版与非激活版差异,然后重试。它会指出不同步的具体 include,而不会转储原始源代码。

较低层级的替代方案:如果需要基于已有内容的类搭建脚手架,或想在不自动激活的情况下进行试运行式预览,请直接使用 scaffold_rap_handlers,明确设置 autoApply=true|false 和 bdefName。对于全新的行为池,优先使用 generate_behavior_implementation。

步骤 8 — 通过 edit_method 填写 action 函数体
SAPWrite(action="edit_method", type="CLAS", name="ZBP_DM_PROJECT",
         method="lhc_project~approve_project",
         source="
  METHOD approve_project.
    READ ENTITIES OF zr_dm_project IN LOCAL MODE
      ENTITY Project FIELDS ( ProjectId Status Aedat Aezet Aenam )
        WITH CORRESPONDING #( keys )
      RESULT DATA(projects).

    MODIFY ENTITIES OF zr_dm_project IN LOCAL MODE
      ENTITY Project
        UPDATE FIELDS ( Status Aedat Aezet Aenam )
        WITH VALUE #( FOR p IN projects (
          %tky    = p-%tky
          Status  = 'A'
          Aedat   = sy-datum
          Aezet   = sy-uzeit
          Aenam   = sy-uname
        ) ).

    READ ENTITIES OF zr_dm_project IN LOCAL MODE
      ENTITY Project ALL FIELDS WITH CORRESPONDING #( keys )
      RESULT DATA(updated).

    result = VALUE #( FOR u IN updated ( %tky = u-%tky %param = u ) ).
  ENDMETHOD.",
         transport="<transport>")

(不需要 COMMIT WORK——框架会在 action 提交时自动保存。)

步骤 9 — 服务定义 + 绑定
SAPWrite(action="batch_create", objects=[
  { type: "SRVD", name: "ZUI_DM_PROJECTS",
    source: "@EndUserText.label : 'Demo Project Manager (V4)'\ndefine service ZUI_DM_PROJECTS {\n  expose ZC_DM_PROJECT as Project;\n  expose ZC_DM_TASK as Task;\n  expose ZC_DM_TIMEENTRY as TimeEntry;\n}",
    package: "<target_package>", transport: "<transport>" },
  { type: "SRVB", name: "ZUI_DM_PROJECTS_O4",
    bindingType: "ODataV4-UI",
    serviceDefinition: "ZUI_DM_PROJECTS",
    package: "<target_package>", transport: "<transport>" }
])

注意: SRVD 暴露的是 ZC_DM_*(投影),而不是 ZR_DM_*(根)。如果暴露了根实体,则说明缺少投影层——返回步骤 3。

步骤 10 — 激活所有对象
SAPActivate(action="activate", objects=[
  { type: "DDLS", name: "ZR_DM_PROJECT" },
  { type: "DDLS", name: "ZR_DM_TASK" },
  { type: "DDLS", name: "ZR_DM_TIMEENTRY" },
  { type: "DDLS", name: "ZC_DM_PROJECT" },
  { type: "DDLS", name: "ZC_DM_TASK" },
  { type: "DDLS", name: "ZC_DM_TIMEENTRY" },
  { type: "BDEF", name: "ZR_DM_PROJECT" },
  { type: "BDEF", name: "ZC_DM_PROJECT" },
  { type: "CLAS", name: "ZBP_DM_PROJECT" },
  { type: "SRVD", name: "ZUI_DM_PROJECTS" },
  { type: "SRVB", name: "ZUI_DM_PROJECTS_O4" }
])
步骤 11 — 发布 V4 服务绑定
SAPActivate(action="publish_srvb", name="ZUI_DM_PROJECTS_O4")
步骤 12 — 视情况执行的手动步骤:V4 服务组注册

publish_srvb 会在 ADT 层级激活绑定。在大多数 7.5x 系统上,有两种 运行时 URL 形式会使用该绑定,但只有一种需要在 Gateway 中心注册:

路径执行 publish_srvb 后(未执行 IWFND 步骤)的行为
Gateway 中心 — http://<host>:50000/sap/opu/odata4/sap/<srvb>_o4/srvd_a2x/sap/<srvb_short>/0001/返回 403,并显示 IWBEP/CM_V4_COS/136 — Service 'ZUI_DM_PROJECTS' repository 'SRVD_A2X' is not assigned to group 'ZUI_DM_PROJECTS_O4'。需要注册。
SRVD 直连 — https://<host>:50001/sap/opu/odata4/sap/<srvb>_o4/srvd/sap/<srvb>/0001/无需 IWFND 注册即可返回 200——已由第 5 次运行 + 第 6 次运行验证。路径段为 srvd/sap/<srvb>(而非 srvd_a2x/sap/<srvb_short>)。

决策:

  1. 在第 7 阶段先尝试 SRVD 直连路径(无需手动步骤)。如果可用,请继续—— 步骤 12 无需执行。
  2. 仅当 SRVD 直连路径在目标系统上也失败,或使用者(FE 应用、合作伙伴)被固定配置为使用中心路径时,才返回此处执行手动步骤。

如果确实需要手动执行该步骤,请在 SAP GUI 中:

/n/IWFND/MAINT_SERVICE
  Add Service → System Alias = LOCAL → Filter "ZUI_DM_PROJECTS*" → Get Services
  Select ZUI_DM_PROJECTS_O4 → Add Selected Services → Package <target_package> → Transport <transport>

  (Or, on systems with the V4-specific transaction:)

/n/IWFND/V4_ADMIN
  → Service Group "ZUI_DM_PROJECTS_O4" → Assign SRVD_A2X service "ZUI_DM_PROJECTS"

注意:publish_srvb 执行后,中心路径可能会立即返回临时错误 503 CM_V4_RUNTIME/000(“Service alias cache outdated”),随后在下一次调用时返回 403 COS/136。这是两种不同的症状——503 表示别名缓存异常,403 表示缺少注册。请等待几秒后重新测试,再判断是否由缺少注册导致。

步骤 13 — Phase 5 → Phase 6 契约回显

遍历 Phase 5f 中的制品清单契约,并打印每个方框的状态:

=== Phase 6 contract echo ===

Roots (CDS):           [✓] ZR_DM_PROJECT  [✓] ZR_DM_TASK  [✓] ZR_DM_TIMEENTRY
Projections (CDS):     [✓] ZC_DM_PROJECT  [✓] ZC_DM_TASK  [✓] ZC_DM_TIMEENTRY
Draft tables (TABL):   [✓] ZDM_PROJECT_D  [✓] ZDM_TASK_D  [✓] ZDM_TIMEENTRY_D
BDEFs:                 [✓] ZR_DM_PROJECT (root)  [✓] ZC_DM_PROJECT (projection)
Behavior pool (CLAS):  [✓] ZBP_DM_PROJECT (with lhc_project, lhc_task, lhc_timeentry skeletons)
                       [✓] approve_project handler stub injected via scaffold_rap_handlers
                       [✓] approve_project body filled via edit_method
Service definition:    [✓] ZUI_DM_PROJECTS (exposes 3 projections)
Service binding:       [✓] ZUI_DM_PROJECTS_O4 (published at ADT level)

Manual steps remaining:
  [✗ — waiting on user] /IWFND/MAINT_SERVICE V4 group registration

如果任何方框为 ✗ 且未获豁免,请勿进入 Phase 7。请修复问题或上报给 用户。


Phase 7 — 并排冒烟测试

验证新的 V4 服务返回的数据与旧版 V2 相同。

7a. 旧版 V2 基线
curl -u "$USER:$PASS" "<base>/sap/opu/odata/sap/<legacy_service>/ProjectSet/\$count"
# expect: 5
curl -u "$USER:$PASS" "<base>/sap/opu/odata/sap/<legacy_service>/ProjectSet('PRJ-0001')/Tasks/\$count"
# expect: 3
7b. 新版 V4 — 选择可用的 URL 格式

并行尝试两种格式。SRVD 直连路径通常无需执行步骤 12 中的手动注册即可使用;Gateway hub 路径则需要。

# SRVD-direct (HTTPS, port 50001, /srvd/sap/<srvb>/ path) — preferred, no Step 12 needed
NEW_BASE_DIRECT="https://<host>:50001/sap/opu/odata4/sap/zui_dm_projects_o4/srvd/sap/zui_dm_projects_o4/0001"

# Gateway hub (HTTP, port 50000, /srvd_a2x/sap/<srvb_short>/ path) — needs Step 12
NEW_BASE_HUB="<base>/sap/opu/odata4/sap/zui_dm_projects_o4/srvd_a2x/sap/zui_dm_projects/0001"

# Smoke-test the SRVD-direct path first
curl -s -o /dev/null -w "%{http_code}\n" -u "$USER:$PASS" \
  "$NEW_BASE_DIRECT/Project?\$top=1&sap-client=001"
# 200 = good; 403 = check Gateway hub registration; 503 = transient alias cache, retry

选择返回 200 的路径,并在 Phase 7 的其余步骤中使用该路径。将可用的 URL 固定记录到 RUN-NOTES,供演示使用。

7c. 草稿 V4 键值 — 易忽略之处

对于从 managed-with-draft BDEF 生成的服务,$metadata 中公开的实体键是复合键:每个主键字段加上 IsActiveEntity(Edm.Boolean)。 单独使用 Project('PRJ-0001') 会返回 400;正确形式为:

NEW_BASE="$NEW_BASE_DIRECT"   # or $NEW_BASE_HUB after Step 12

# Counts — same as legacy V2 if the migration is byte-equivalent
curl -u "$USER:$PASS" "$NEW_BASE/Project/\$count?sap-client=001"
# expect: 5

# Composite key — note ProjectId AND IsActiveEntity, not just ProjectId
curl -u "$USER:$PASS" \
  "$NEW_BASE/Project(ProjectId='PRJ-0001',IsActiveEntity=true)/_Tasks/\$count?sap-client=001"
# expect: 3

如果要与旧版 V2 无草稿语义保持一致,请通过 ?$filter=IsActiveEntity eq true 显式筛选活动行集。

7d. 操作调用 — 必须提供 CSRF + If-Match

草稿实体上的 V4 绑定操作需要同时提供 X-CSRF-Token(以及会话 Cookie)和 If-Match: <etag>。缺少 If-Match 会返回 428 CX_OD_PRECOND_REQUIRED。

# 1. Fetch CSRF token + session cookies
curl -s -u "$USER:$PASS" -c /tmp/cookies.txt \
     -H "X-CSRF-Token: Fetch" \
     "$NEW_BASE/?sap-client=001" -D - -o /dev/null | grep -i 'x-csrf-token'

# 2. Get the current etag for the target row
ETAG=$(curl -s -u "$USER:$PASS" -b /tmp/cookies.txt \
       "$NEW_BASE/Project(ProjectId='PRJ-0003',IsActiveEntity=true)?sap-client=001" \
       -D - -o /dev/null | grep -i 'etag' | awk '{print $2}' | tr -d '\r')

# 3. POST the action with both headers
curl -u "$USER:$PASS" -b /tmp/cookies.txt -X POST \
     "$NEW_BASE/Project(ProjectId='PRJ-0003',IsActiveEntity=true)/com.sap.gateway.srvd.zui_dm_projects.v0001.approve_project?sap-client=001" \
     -H "Content-Type: application/json" \
     -H "X-CSRF-Token: <from step 1>" \
     -H "If-Match: $ETAG" \
     -d '{}'
# expect: 200 + entity with Status="A"

如果计数不匹配,CDS where 子句可能有误——请重新检查旧版 DPC_EXT 方法,找出遗漏的筛选条件。如果即使提供了这两个标头,操作仍以 412/428 失败,则该服务可能需要重新执行 publish_srvb,以应用较新的 BDEF 操作声明。


重置 / 重新运行

重新运行此技能——Phase 6a 会先删除 <target_package> 中的所有内容,因此可以安全地多次运行。<transport> 会一直保持 D(可修改)状态,直到你显式释放它。若要同时清除传输请求:

SAPTransport(action="release_recursive", number="<transport>")
SAPTransport(action="create", description="ARC-1 RAP migration outputs (resettable)", transportType="K")

……然后更新此技能的智能默认值中的传输请求 ID。


错误处理

症状可能原因修复方法
阶段 0 — rap.available=false系统版本 < 7.54,或已禁用 ABAP-Cloud 模式无法继续迁移;告知用户
阶段 0 — 预检 DDLS 创建返回 403目标包中的 DDLS 缺少 S_DEVELOP ACTVT=01,02用户在 SAP GUI 中运行 SU53;由 Basis 管理员授予角色
阶段 1 — 按约定找不到 MPC 类服务使用非标准命名,或位于子包中回退到 TADIR 查询(1a 替代路径)
阶段 2 — DEFINE_<entity> 正文使用了无法解析的辅助方法SEGW 使用 define_<entity>_property_<n>(...) 调用同样读取这些辅助方法 — SAPRead method="DEFINE_PROJECT_PROPERTY_1"
阶段 3 — EXECUTE_ACTION 正文包含多个 IF iv_action_name = 'X' 分支一个 DPC_EXT,多个函数导入将每个分支转换为单独的 BDEF 操作
阶段 6 — 激活失败,提示“released-API contract violation”旧版 DPC_EXT 使用了未发布的 ABAP API;CDS where 子句继承了同样的问题替换为已发布的等效 API(使用 mcp__sap-docs__search 查找现代 API)
阶段 6 — scaffold_rap_handlers 返回 0 个缺失方法激活顺序颠倒在搭建行为池之前先激活 BDEF — scaffold_rap_handlers 会读取已激活的 BDEF
阶段 7 — V4 Project/$count 与 V2 ProjectSet/$count 不一致CDS 视图包含隐式筛选条件(例如 WHERE delivered_status <> 'X'),但你遗漏了重新读取 DEFINE_PROJECT,查找 MPC 中的 set_filter_* 调用
阶段 7 — V4 _Tasks 返回空结果组合方向错误,或缺少参照约束检查 ZR_DM_PROJECT 源码;composition of 子句必须通过外键引用子项
阶段 7 — V4 端点返回 403,提示 IWBEP/CM_V4_COS/136 … not assigned to group跳过了阶段 6 第 12 步(V4 路由组注册)用户运行 /n/IWFND/MAINT_SERVICE,为 ZUI_DM_PROJECTS_O4 添加服务
阶段 6 第 5 步 — BDEF 激活拒绝 association _X { update; delete; }association 内联块中只允许 create从 association 块中移除 update/delete;在子实体自己的行为块中声明它们
阶段 6 第 5 步 — BDEF 拒绝 draft action Prepare必须写成 draft determine action Prepare(determine 关键字不可省略)修正为 draft determine action Prepare
阶段 6 第 5 步 — BDEF 拒绝子实体的 Edit/Activate/Discard/Resume 声明草稿生命周期操作只能放在根实体上(即带有 lock master 的实体)将它们移到根实体;子实体只需组合 association,以及 update;/delete;
阶段 6 第 5 步 — BDEF 拒绝不带 total etag 的 with draft启用草稿时,RAP 要求提供 total etag 字段在根行为中添加 total etag CreationTimeStamp(或你的时间戳字段)
阶段 6 第 5 步 — 如果 _Task 自身没有 lock master,BDEF 会拒绝子实体的 lock dependent by _Task锁定链必须以 lock master 实体结束在孙级根视图中添加跨 BO 的 association to ZR_DM_<root> as _Project,并在 BDEF 中使用 lock dependent by _Project
阶段 6 第 5 步 — BDEF 拒绝子实体在使用 lock dependent by _Project 时更新 ProjectId用于 lock-by 引用的字段必须为只读在子实体行为块中添加 field ( readonly : update ) <field>
阶段 6 第 5 步 — BDEF 拒绝每个实体都未声明 authorization 的 strict ( 2 )strict ( 2 ) 要求显式声明授权在根实体上添加 authorization master ( instance ),在子实体上添加 authorization dependent by _Project。第 7 步(generate_behavior_implementation)会自动创建匹配的 lhc_<alias> 框架,并注入 get_instance_authorizations 存根 — 无需手动预先创建。不要将删除 strict ( 2 ) 作为变通方法。
阶段 6 第 7 步 — generate_behavior_implementation 返回 activation: { success: false, hint: <stale-active recovery> }常见的 stale-active CCDEF/CCIMP 耦合问题:活动包含文件是 SAP 占位注释,而非活动包含文件现在包含实际处理程序,RAP 因而拒绝从非活动状态切换到活动状态。提示中提供两种恢复选项:(a) 通过 Eclipse“Generate Behavior Implementation”向导激活该类一次(可绕过耦合问题),或 (b) 执行 SAPManage(action="delete", type="CLAS") + SAPWrite(action="create"),然后对新创建的类重新运行 generate_behavior_implementation。无论采用哪种方式,刚写入的 CCDEF/CCIMP 源码都是正确的。
阶段 6 — CDS 拒绝 composition [0..*] of <child> as <name> on …托管组合不接受 on 子句;键关联是隐式的,或通过子实体中的 with foreign key 完成移除 on 子句;不要改用 association to
阶段 6 — CDS 拒绝根视图上的 provider contract transactional_interface该 contract 仅适用于投影视图(ZC_DM_*)这表示你缺少投影层 — 创建 ZC_DM_* 投影(第 3 步),并将 contract 放在那里
阶段 6 — CDS 拒绝 tstmp_from_dat_tim(...)函数名称错误使用 dats_tims_to_tstmp(...)(注意顺序:先 dats,后 tims)
阶段 6 — 批量激活发出 ED 064 — "no next/previous object found" 警告7.58 中无害的批量激活问题忽略该警告;如果所有对象都显示 active,则批量激活成功。如果同时出现实际错误,请改用单对象激活重试。
阶段 6a — SAPWrite create 返回“object exists”,但阶段 6a 重置后显示包为空对象存在于其他传输请求中,而非目标包在所有包中查询该名称的 TADIR:SAPQuery sql="SELECT * FROM tadir WHERE obj_name = '<name>'"。如果找到,先使用其实际传输请求删除,然后重试创建。
阶段 6 第 2/3 步 — CDS 确实以“unknown annotation”拒绝 @Semantics.systemDateTime.localInstanceLastChangedAt 或 @Semantics.businessDate.*系统版本早于 7.55(不支持草稿)。在 7.58 中这些注解有效 — 已在 S/4HANA 2023 上进行实时验证;SAP 自己的生成器会在 758 上生成 localInstanceLastChangedAt在 7.58 上保留它们。只有在确实较旧的版本(< 7.55)上,才回退到 createdAt/lastChangedAt,并省略 local-instance / businessDate 注解。
阶段 6 第 3 步 — CDS 拒绝 ZC_DM_<child> 上的“不适当 provider contract”provider contract transactional_interface 被放在了子投影上从 ZC_DM_TASK / ZC_DM_TIMEENTRY 中移除 contract。contract 只能放在根投影(ZC_DM_PROJECT)上。子实体声明不带 contract 的 as projection on …,并使用 redirected to parent 进行向上导航。
阶段 6 第 5 步 — BDEF 激活拒绝 "key field PROJECTID expected at position 2, found PROJECT_ID"(或草稿表中类似的字段名称不匹配)草稿表使用了蛇形命名字段(project_id、start_date),而不是经过 BO 别名规范化的名称(projectid、startdate)重写草稿 TABL,使字段名称与 BO 别名一致:去掉下划线。活动表可以保留蛇形命名(BDEF 映射会进行桥接);草稿表不能这样做。使用 SAPWrite 执行 update + activate — Run 2 已确认此方法可行。
阶段 6a — 对较长的 WHERE obj_name IN ('a','b','c',…) 筛选条件,SAPQuery 返回 HTTP 400ARC-1 ≥ 0.10.0 会自动将简单的长 IN (…) 字面量列表拆分处理(PR #254)。较旧的构建版本和复杂筛选条件(NOT IN、多个 SELECT、子查询)仍会触发 400。对跨包存在性检查,优先使用 SAPSearch(searchType="tadir_lookup", source="both", names=[...])(PR #256 + PR #270)— 无需 SQL;splitBrain 警告数组会在一次调用中显示 ADT/DB 差异。作为更大范围扫描的回退方案,使用带 devclass = '<package>' 的 SAPQuery。

说明

  • 此技能绝不会触及旧版源包、旧版 SEGW 项目或其传输。迁移会并行进行;旧版内容会一直保持运行状态,直到用户明确将其弃用。
  • 每当旧版 DPC_EXT 主体中包含不熟悉的 API 调用时,请使用 mcp__sap-docs__search——它会告知对应的云端正式发布版本。
  • 使用 mcp__sap-docs__abap_feature_matrix 检查用户的版本支持哪些 RAP 功能(例如 etag master、unmanaged save、lock master)。
  • generate-rap-service-researched.md 是规范的创建工具。此技能负责发现与转换;将实际的创建和激活工作委派给更深入的技能,可让此技能专注于自身职责。

评论

支持 Markdown