Status Transitions — CAP Best Practices 是一份 SAP CAP 状态机实现指南,主要参考资料为 CAP 官方发布说明与状态流转指南。需要说明的是,状态流转(Status-Transition Flows)在 CAP 2025 年 12 月版本中处于 Gamma 状态,在 cds 9.5+ 中达到生产可用。
方案 A:@flow 注解(Gamma,cds 9.5+,零样板)
在 CDS 中直接定义状态机。示例中先定义 TravelStatus 类型枚举(Open='O'、Accepted='A'、Canceled='C'),在 Travels 实体上声明 status 字段(默认 'O'),再用 annotate Travels with @flow.status: status actions 声明动作:acceptTravel(@from #Open @to #Accepted)、rejectTravel(@from #Open @to #Canceled)、reopenTravel(@from #Canceled @to #Open)。
CAP 会自动生成:OData 绑定动作 acceptTravel、rejectTravel、reopenTravel;入口条件校验(@from 状态检查);状态转换执行(@to 赋值);以及 Fiori Elements 对象页按钮的 UI 注解(当 @from 条件不满足时自动禁用)。
覆盖生成逻辑以自定义校验
可在服务中通过 this.before('acceptTravel', Travels, ...) 在生成处理器之前插入自定义校验,例如读取 Travel 记录,若 totalCost <= 0 则 req.reject(422, ...)。
跟踪转换历史(恢复上一状态)
在 @flow 声明中可使用 $flow.previous,CAP 会自动跟踪上一状态并提供所需数据结构,例如定义 undoTransition @to: $flow.previous。
方案 B:手动状态机(显式 handler,完全控制)
适用于复杂工作流或 @flow 不够灵活时。示例定义 OrderStatus 枚举(Draft='D'、Submitted='S'、Approved='A'、Rejected='R'、Shipped='X'),在 service 中声明 submitOrder、approveOrder、rejectOrder(reason)、shipOrder(trackingNr) 等 action,并定义 TRANSITIONS 映射表(每个动作的 from 数组与 to 值)。在 init() 中为各动作注册统一 transition(action, req, extra) 方法:查询订单、找不到则 reject 404、当前状态不在 from 列表则 reject 409(INVALID_TRANSITION),否则 UPDATE 状态并返回 true。
状态字段枚举最佳实践
始终使用类型化枚举而非普通 String,以便获得值帮助与关键性。示例:type InvoiceStatus : String enum { Draft='D', Pending='P', Approved='A', Rejected='R', Paid='X' },并用 annotate Invoices:status with @(Common.ValueList: {...}, UI.Hidden: false) 配置 Fiori 关键性配色与值帮助。
Fiori Elements 对象页自动按钮
使用 @flow 注解后,Fiori Elements 会自动在对象页显示动作按钮,并在 @from 条件不满足时禁用,无需额外的 @UI.DataFieldForAction 注解;也可通过 @UI.LineItem 中的 UI.DataFieldForAction 自定义按钮位置与标签。
常见错误
不要用普通 String 代替类型枚举(会丢失值帮助与关键性);不要让客户端直接 PATCH 修改状态字段,应始终通过 action 变更;不要省略 @from 守卫,否则任意状态都能调用动作;不要在同一实体上混用 @flow 与手写 handler,二选一;不要在 cds < 9.5 上使用 @flow;建议通过 @cap-js/change-tracking 对 status 字段做变更审计。
CAP Java 额外配置
CAP Node.js 内置 @flow 支持;CAP Java 需在 srv/pom.xml 添加 groupId com.sap.cds、artifactId cds-feature-flow、scope runtime 的 Maven 依赖。
当前限制
草稿启用(draft-enabled)的实体:处于草稿状态时所有动作被禁用,状态流转只能在活动实体上执行。CRUD/DRAFT 操作无法被状态流转限制,只有绑定动作(bound actions)可以受 flow 控制。