文件
hyb-oa/docs/领域事件清单.md
T

144 行
14 KiB
Markdown
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
# 领域事件清单(P19 / P19+IM-1 / P19+ERP-3 / P19+AI-1 / P20+TRAVEL-1 / P20+TRAVEL-2 / P20+TRAVEL-3)
**文档状态**:技术参考和历史证据,不是当前 MVP 需求事实源。
**快照日期**:2026-03-10
**目标**:冻结 `hyb-oa` 当前采购、资产与差旅域的标准领域事件名,并补充 `P19+IM-1` 已实际消费的 IM 通知类型清单,以及 `P19+ERP-1 / ERP-3 / AI-1 / TRAVEL-1` 已实际落地的消费映射,作为后续 ERP / IM / AI / TRAVEL 适配的事实源。
**不得误读**:本文用于追溯 Outbox、IM、ERP、AI、TRAVEL 等历史集成边界;当前 10 人以下公司 MVP 范围以 [海音达OA-MVP需求规格说明书](./海音达OA-MVP需求规格说明书.md)、模块规约和最新 `issues/*.csv` 为准。
## 1. 使用原则
- 本文档同时维护两类事实:
- 领域事件名(长期稳定边界)
- IM 一期已消费的通知类型(当前运行时实现)
- 未来若继续扩展 Outbox / webhook / MQ,领域事件名与最小载荷必须保持向后兼容。
- 领域事件只表达“已经发生的业务事实”,不表达待办命令。
## 2. 标准领域事件清单
| 事件名 | 触发时机 | 领域对象 | 最小载荷字段 | 推荐消费者 | 备注 |
| --- | --- | --- | --- | --- | --- |
| `purchase_request.submitted` | 采购申请提交成功后 | `purchase_request` | `purchaseRequestId`, `requestNo`, `applicantId`, `departmentId`, `legalEntityId`, `costCenterId`, `supplierId`, `budgetPoolId`, `totalAmount`, `status` | IM、ERP、AI | 表示进入 `reviewing` |
| `purchase_request.approved` | 采购申请审批通过后 | `purchase_request` | `purchaseRequestId`, `requestNo`, `approvedBy`, `legalEntityId`, `costCenterId`, `supplierId`, `budgetPoolId`, `totalAmount`, `status` | ERP、IM、AI | 对应预算 `confirm` 已完成 |
| `purchase_request.rejected` | 采购申请审批驳回后 | `purchase_request` | `purchaseRequestId`, `requestNo`, `actorId`, `reason`, `status` | IM、AI | 若存在预算预留,应已释放 |
| `purchase_request.withdrawn` | 申请人撤回采购申请后 | `purchase_request` | `purchaseRequestId`, `requestNo`, `actorId`, `status` | IM、AI、ERP 补偿 | 表示由 `reviewing` 回到 `draft` |
| `travel_request.submitted` | 差旅申请提交成功后 | `travel_request` | `travelRequestId`, `requestNo`, `applicantEmployeeId`, `departmentId`, `legalEntityId`, `costCenterId`, `budgetPoolId`, `estimatedAmount`, `tripType`, `status` | IM、Budget、TRAVEL-2/3 | 表示进入 `reviewing` 且预算尝试 `reserve` |
| `travel_request.approved` | 差旅申请审批通过后 | `travel_request` | `travelRequestId`, `requestNo`, `approvedBy`, `legalEntityId`, `costCenterId`, `budgetPoolId`, `estimatedAmount`, `tripType`, `status` | IM、Budget、TRAVEL-2/3 | 对应预算 `confirm` 已完成;TRAVEL-1 不做 ERP 出站 |
| `travel_request.rejected` | 差旅申请审批驳回后 | `travel_request` | `travelRequestId`, `requestNo`, `actorId`, `reason`, `status` | IM、Budget、AI | 若存在预算预留,应已释放 |
| `travel_request.withdrawn` | 申请人撤回差旅申请后 | `travel_request` | `travelRequestId`, `requestNo`, `actorId`, `status` | Budget、AI、TRAVEL-2/3 | 动作结果固定回到 `draft`,不是持久状态 |
| `travel_order.paid` | 差旅订单被财务/行政标记为已支付后 | `travel_order` | `travelOrderId`, `travelRequestId`, `externalOrderNo`, `paymentVoucherNo`, `paymentDate`, `paymentChannel`, `amount`, `currency`, `status` | ERP、TRAVEL-3、AI | `TRAVEL-2` 起稳定承诺,用于支付 journal 出站 |
| `purchase_order.archived` | PO/合同归档保存成功后 | `purchase_order_archive` | `purchaseOrderArchiveId`, `purchaseRequestId`, `requestNo`, `poNo`, `contractNo`, `archivedAt`, `totalAmount` | ERP、AI | ERP-2 以此驱动 accrual journal 与 archive summary |
| `asset.card.created` | 资产卡片创建成功后 | `asset_card` | `assetId`, `assetNo`, `name`, `categoryId`, `legalEntityId`, `costCenterId`, `purchaseRequestId`, `status` | ERP、IM、AI | 资产域不接 workflow |
| `asset.assigned` | 资产领用成功后 | `asset_card` | `assetId`, `assetNo`, `employeeId`, `assignedAt`, `location`, `status` | IM、AI | 对应新增 open assignment |
| `asset.returned` | 资产归还成功后 | `asset_card` | `assetId`, `assetNo`, `employeeId`, `returnedAt`, `location`, `status` | IM、AI | 对应关闭 open assignment |
| `asset.transferred` | 资产调拨成功后 | `asset_card` | `assetId`, `assetNo`, `fromEmployeeId`, `toEmployeeId`, `fromLocation`, `toLocation`, `transferredAt`, `status` | IM、AI | 可只改位置,不一定改归属人;ERP-3 一期不消费 |
| `inventory_check.completed` | 盘点任务完成后 | `inventory_check` | `inventoryCheckId`, `checkNo`, `scopeType`, `scopeRefId`, `completedAt`, `itemCount`, `summary` | ERP、IM、AI | `summary` 建议包含 matched / missing / extra / damaged 计数 |
## 3. 事件名与运行时对象映射
| entityType | 推荐事件前缀 | 当前事实源 |
| --- | --- | --- |
| `purchase_request` | `purchase_request.*` | `P17A` / `P17B` |
| `travel_request` | `travel_request.*` | `P20+TRAVEL-1` |
| `purchase_order_archive` | `purchase_order.*` | `P17B` / `P19+ERP-2` |
| `asset_card` | `asset.*` | `P18A` / `P18B` |
| `inventory_check` | `inventory_check.*` | `P18B` |
## 4. 最小 Envelope 建议
未来若继续扩展事件投递,建议统一使用如下 envelope:
```json
{
"eventName": "purchase_request.submitted",
"eventId": "uuid",
"occurredAt": "2026-03-08T12:18:10+08:00",
"actorId": "user-id",
"entityType": "purchase_request",
"entityId": "entity-id",
"payloadVersion": 1,
"payload": {}
}
```
## 5. 兼容性规则
- 事件一旦对外承诺,名称不得随意修改。
- 新增字段必须保持向后兼容;删除字段需要单独版本升级。
- 载荷字段命名应与 HTTP camelCase 输出保持一致,不直接暴露数据库 snake_case 列名。
## 6. 明确不纳入当前事件主表的事项
- `purchase_order.cancelled`:待未来采购撤销 / 作废专项再冻结
- `inventory_check.started`:当前不是对外关键事实源
- `asset.repaired` / `asset.retired`:待未来维修 / 报废专项后再定义
## 7. 推荐消费场景
- IM:采购审批结果、报销结果、资产领用归还、盘点完成提醒
- ERP:采购审批 commitment、PO/合同归档 accrual / summary、资产卡片 ledger summary、盘点完成摘要
- AI:审批摘要、异常提示、主数据重复建议、盘点结果总结
## 8. P19+IM-1 已消费通知类型
> 说明:IM 一期运行时并未新增新的领域事件名,而是基于现有业务事实统一沉淀为通知类型,再通过 `notification.im` Outbox 异步派发。
| 通知类型 | 当前来源事实 | 默认 targetType | 默认 targetPath | 说明 |
| --- | --- | --- | --- | --- |
| `workflow.task_assigned` | workflow 新待办 / 转审待办 | `workflow_task` | `/workflow/tasks/:taskId` | 当前唯一待办类通知 |
| `reimbursement.approved` | 报销审批通过 | `reimbursement_form` | `/reimbursements/:id` | 结果通知 |
| `reimbursement.rejected` | 报销驳回 | `reimbursement_form` | `/reimbursements/:id` | 结果通知 |
| `reimbursement.returned` | 报销退回 | `reimbursement_form` | `/reimbursements/:id` | 结果通知 |
| `reimbursement.paid` | 报销付款完成 | `reimbursement_form` | `/reimbursements/:id` | 财务结果通知 |
| `leave.approved` | 请假审批通过 | `leave_request` | `/leave-requests` | 结果通知 |
| `leave.rejected` | 请假驳回 | `leave_request` | `/leave-requests` | 结果通知 |
| `purchase_request.approved` | 采购审批通过 | `purchase_request` | `/purchase-requests` | 结果通知 |
| `purchase_request.rejected` | 采购驳回 | `purchase_request` | `/purchase-requests` | 结果通知 |
| `travel_request.approved` | 差旅审批通过 | `travel_request` | `/travel-requests?id=<id>` | 结果通知 |
| `travel_request.rejected` | 差旅审批驳回 | `travel_request` | `/travel-requests?id=<id>` | 结果通知 |
| `asset.assigned` | 资产领用成功 | `asset_card` | `/assets` | 通知新领用人 |
| `asset.returned` | 资产归还成功 | `asset_card` | `/assets` | 通知原持有人 |
| `asset.transferred` | 资产调拨成功 | `asset_card` | `/assets` | 通知归属变更涉及人员 |
| `inventory_check.completed` | 盘点完成 | `inventory_check` | `/inventory-checks` | 通知创建人与 `finance_mgr` 角色 |
## 9. 当前仍未接入 IM 的冻结事件
- `purchase_request.submitted`
- `purchase_request.withdrawn`
- `travel_request.submitted`
- `travel_request.withdrawn`
- `inventory_check.started`
这些事件名称仍保留在长期蓝图中,但不属于 `P19+IM-1` 的首批协同覆盖范围。
## 10. P19+ERP-1 / ERP-3 已实现消费映射
- `P19+ERP-1` 没有新增新的领域事件名;ERP 一期真正落地消费的既有业务事实是 `reimbursement.paid`,运行时会先构建标准化 `ReimbursementJournalSlice`,再通过 `erp.journal.export` 写入 `IntegrationOutbox` 异步派发。
- `ReimbursementJournalSlice.sourceEventName` 固定为 `reimbursement.paid`,用于标记该凭证切片来源于“报销付款完成”这一既有业务事实;历史 `erp.reimbursement.paid` outbox 仍继续兼容。
- `P19+ERP-2` 正式冻结新增领域事件名 `purchase_order.archived`,并消费两类采购事实:
- `purchase_request.approved` → `PurchaseCommitmentJournalSlice` → `erp.journal.export`
- `purchase_order.archived` → `PurchaseAccrualJournalSlice` + `purchase_order_archive` summary → `erp.journal.export` / `erp.procurement.export`
- 采购 ERP 成功派发后,运行时统一把外部 ERP 编号回写到 `integration_bindings`,分别对应 `purchase_request_commitment_journal`、`purchase_order_accrual_journal`、`purchase_order_po`、`purchase_order_contract`。
- ERP 主数据同步(`LegalEntity` / `CostCenter` / `Supplier`)仍属于主动 Pull,不作为新的领域事件名纳入本表;`IntegrationSyncState` 也继续只承载主数据资源级同步状态,而不承载采购或资产单据轨道。
- `P19+ERP-3` 一期已落地资产消费映射:
- `asset.card.created` → `AssetLedgerSummaryPayload` → `erp.asset.export`
- `PATCH /api/assets/:id` 导致 `legalEntityId / costCenterId / status(retired|archived)` 变化 → 资产 ERP 内部集成事实 → `erp.asset.export`
- 资产 ERP 成功派发后,运行时统一把外部 ERP 资产编号回写到 `integration_bindings(provider=erp, entity_type=asset_erp_card)`;领用 / 归还 / 位置调拨仍不进入 ERP 消费映射。
## 11. P19+AI-1 已实现消费说明
- `P19+AI-1` 不新增新的公开领域事件名;AI 一期只消费既有报销、采购、资产事实,并保持同步只读建议形态。
- 报销详情 AI 建议消费的事实锚点为 `reimbursement_form` 当前详情、预算提示与待办节点;采购详情 AI 建议消费 `purchase_request` 当前详情、归档摘要与资产候选信息;资产详情 AI 建议消费 `asset_card` 当前卡片摘要与财务维度信息。
- AI 一期的三条建议接口均采用白名单快照,不提升为长期公共事件,也不改变 `asset.card.created`、`purchase_request.*`、`purchase_order.archived` 等既有事件名的稳定性承诺。
- AI 输出仅形成 `summary / riskHints / nextActions` 结构化建议,并通过审计动作 `AI_SUGGESTION_REQUESTED` 记录请求摘要;不写库、不派发 Outbox、不回写业务状态。
## 12. P20+TRAVEL-1 已实现消费说明
- `P20+TRAVEL-1` 新增稳定事件名 `travel_request.submitted`、`travel_request.approved`、`travel_request.rejected`、`travel_request.withdrawn`,用于冻结差旅申请一期的业务事实边界。
- 差旅一期运行时会在 `travel_request.submitted` 时创建 workflow 实例、分派 `workflow.task_assigned` 待办,并按预算池解析结果尝试 `reserve`;`travel_request.approved` / `travel_request.rejected` 则分别驱动预算 `confirm` / `release`。
- 当前 IM 已实际消费差旅审批结果通知 `travel_request.approved` 与 `travel_request.rejected`;`travel_request.submitted` 与 `travel_request.withdrawn` 仍作为稳定领域事实保留,不额外新增结果类通知。
- `travel_order.imported` 在 `TRAVEL-1` 当前仍只作为内部集成事实和入站表更新使用,不提升为稳定公共事件,也不在本表作为长期承诺事件名登记。
## 13. P20+TRAVEL-2 已实现消费说明
- `P20+TRAVEL-2` 新增稳定事件名 `travel_order.paid`,并明确 `card_authorization.imported`、`card_transaction.imported`、`card_transaction.reversed` 当前都只保留为内部支付轨道事实,不进入稳定公共事件表。
- `CardAuthorization` 导入成功后写支付预算 `authorize`;`CardTransaction` 的 `charge + settled` 写 `settle`,`refund / reversal` 写 `reverse`,且这些支付账轨不回改 `travel_request` 在 `TRAVEL-1` 已冻结的申请预算确认点。
- 差旅订单只有在已结算交易与订单金额/币种完全匹配时才会从 `imported` 推进到 `confirmed`;之后由 `mark-paid` 手工结算动作把订单推进到 `paid`,形成运营闭环。
- `travel_order.paid` 的固定消费路径为:`travel_order.paid` → `TravelOrderPaymentJournalSlice` → `erp.journal.export`;若 `erp_sync` feature flag 未开启,仅记录审计摘要,不进入 Outbox。
## 14. P20+TRAVEL-3 已实现消费说明
- `P20+TRAVEL-3` 不新增新的公开业务领域事件名;`TravelReconciliationBatch` / `TravelReconciliationItem` 当前只作为内部运营事实与批次快照存在,不进入稳定公共事件表。
- `travel_order.paid` 继续保持 `TRAVEL-2` 已冻结的稳定承诺,并作为 `TRAVEL-3` 对账批次生成时的只读事实来源之一;对账批次不会反向改写该事件的消费语义。
- `erp.travel.export` 在 `TRAVEL-3` 中新增,但它被固定归类为批次级集成事件,不计入公开业务领域事件名;其消费路径为 `TravelReconciliationBatch(reconciled)` → `TravelReconciliationSummaryPayload` → `erp.travel.export`。
- `card_authorization.imported`、`card_transaction.imported`、`card_transaction.reversed` 仍只保留为内部支付轨道事实;`TRAVEL-3` 只读取这些事实构建 snapshot,不提升为新的稳定公共事件名。