关于 migrate-segw-to-rap
用于逆向分析经典 SEGW OData V2 服务并在同一 SAP 系统中重建为 RAP 服务的工作流技能。迁移后的对象放入独立包,旧服务继续运行。
主要功能
- 01检查 SEGW 模型和提供程序类
- 02将实体与关系转换为 RAP
- 03将旧操作映射到 RAP 行为
- 04创建 CDS 视图和服务对象
适用场景
- 01将旧版 OData V2 后端迁移到 RAP
- 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_DEVELOPACTVT=03 OBJTYPE=CLAS DEVCLASS=<source-pkg>。"authProbe.transportAccess == true— 失败时提示:"用户无法读取传输请求;需要S_TRANSPRTACTVT=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>):
-
Filter for classes endingSAPRead(type="DEVC", name="<package>")_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_ENTITYSET | CDS 视图的读取访问权限——在托管场景中通常无需额外实现 |
<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"])
按以下顺序读取响应:
results— 通过 ADT 发现的名称(用于判断“对象是否实际存在”)。为空 = 从 ADT 的角度看,该包是干净的。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 添加子实体、组合、操作和字段映射(生成器无法完成这些工作,因为它是一次性的)。
如果使用此生成器:
- **探测。**没有
abap_generators-list_generators工具(来自abap-mcp服务)→ 跳到 6b,手动构建。 - 解析 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。 - 获取架构 → 生成。调用
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>,遵循与任何写入操作相同的防护措施。 - 交回 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 总结出的三条规则:
- 字段名使用 BO 别名大小写格式,不带下划线(
projectid、startdate, 而不是project_id、start_date)。ABAP 会将 BDEF 别名规范化为大写且不带下划线; 草稿绑定会按该名称查找,而不是按活动表的蛇形命名法查找。 - 必须包含
creationtimestamp和lastchangedstamp列,以便 BDEF 中的total etag+etag master子句能够绑定。 - 必须设置
@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 sourceZR_DM_TASKdoes not exist"(运行 6 的复现案例)。有三种安全方案,可根据实际运行情况选择:
- 使用
activateAtEnd: true的batch_create(ARC-1 ≥ 0.9.5,PR #270)。先为每个对象写入未激活的草稿,然后调用一次末尾的activateBatch,让 SAP 激活器一次性解析交叉引用。这是 ARC-1 支持该功能时的推荐做法 — 只需调用一次工具,无需执行恢复步骤:SAPWrite(action="batch_create", activateAtEnd: true, objects=[...])- 对每个根视图分别调用
SAPWrite(action="create"),然后在最后(所有 DDL 源都已写入之后)执行一次SAPActivate(action="activate", objects=[...])批量激活。适用于所有 ARC-1 版本。- 手动自底向上:先
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 forzr_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 中两条不明显的语法规则——将它们内置:
- 当根 BDEF 声明了
with draft时,use draft;必须位于顶部(而非主体内部)。否则,use action Edit/Activate/Discard/Resume/Prepare会被拒绝。 - 在
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 之后)会:
- 读取类元数据 → 提取
<class:rootEntityRef>,自动发现绑定的 BDEF(无需传入bdefName)。 - 交叉验证 MAIN 的
FOR BEHAVIOR OF zr_dm_project与 BDEF 的managed implementation in class zbp_dm_project unique是否一致。不一致时拒绝修改。 - 调用
scaffold_rap_handlers使用的同一脚手架引擎,并设置autoApply=true:- 自动创建缺失的
lhc_<alias>骨架(CCDEF + CCIMP)。 - 为 BDEF 要求的每个 action / determination / validation /
authorization 注入
METHODS …签名。 - 在 CCIMP 中注入空的
METHOD … ENDMETHOD.存根。
- 自动创建缺失的
- 通过 PR #257 的 include= 路径,在同一把有状态锁下写入 CCDEF + CCIMP。
- 激活该类。
返回包含 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>)。 |
决策:
- 在第 7 阶段先尝试 SRVD 直连路径(无需手动步骤)。如果可用,请继续—— 步骤 12 无需执行。
- 仅当 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 400 | ARC-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是规范的创建工具。此技能负责发现与转换;将实际的创建和激活工作委派给更深入的技能,可让此技能专注于自身职责。