From 9f789fccb5e2f74193abc316cc5b2a8491d99dd5 Mon Sep 17 00:00:00 2001 From: liuyun Date: Thu, 15 Oct 2026 00:13:48 +0800 Subject: [PATCH] =?UTF-8?q?docs(openspec):=20C3=20=E5=B1=A5=E7=BA=A6?= =?UTF-8?q?=E7=95=99=E7=97=95=20change=20=E6=8F=90=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- .../changes/svc-org-c3-record/.openspec.yaml | 2 + openspec/changes/svc-org-c3-record/design.md | 42 +++++++++++++ .../changes/svc-org-c3-record/proposal.md | 32 ++++++++++ .../specs/backend/abnormal/spec.md | 26 ++++++++ .../specs/backend/record/spec.md | 62 +++++++++++++++++++ .../specs/frontend/ad/record/spec.md | 26 ++++++++ .../specs/shared/api-contract/spec.md | 26 ++++++++ .../specs/shared/data-model/spec.md | 36 +++++++++++ openspec/changes/svc-org-c3-record/tasks.md | 39 ++++++++++++ 9 files changed, 291 insertions(+) create mode 100644 openspec/changes/svc-org-c3-record/.openspec.yaml create mode 100644 openspec/changes/svc-org-c3-record/design.md create mode 100644 openspec/changes/svc-org-c3-record/proposal.md create mode 100644 openspec/changes/svc-org-c3-record/specs/backend/abnormal/spec.md create mode 100644 openspec/changes/svc-org-c3-record/specs/backend/record/spec.md create mode 100644 openspec/changes/svc-org-c3-record/specs/frontend/ad/record/spec.md create mode 100644 openspec/changes/svc-org-c3-record/specs/shared/api-contract/spec.md create mode 100644 openspec/changes/svc-org-c3-record/specs/shared/data-model/spec.md create mode 100644 openspec/changes/svc-org-c3-record/tasks.md diff --git a/openspec/changes/svc-org-c3-record/.openspec.yaml b/openspec/changes/svc-org-c3-record/.openspec.yaml new file mode 100644 index 0000000..a216797 --- /dev/null +++ b/openspec/changes/svc-org-c3-record/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-10-15 diff --git a/openspec/changes/svc-org-c3-record/design.md b/openspec/changes/svc-org-c3-record/design.md new file mode 100644 index 0000000..fee7ac4 --- /dev/null +++ b/openspec/changes/svc-org-c3-record/design.md @@ -0,0 +1,42 @@ +## Context + +C2 已有 order 八态、plan/plan_item、staff、MinIO StorageService。C3 新增履约留痕域,纯新增、不侵入脚手架。设计背景见 `docs/superpowers/specs/2026-10-15-服务机构端-C3履约留痕-design.md`。 + +## Goals / Non-Goals + +**Goals:** ho_record/ho_record_item/ho_record_photo/ho_record_abnormal 四表 + 录入 API + 垫付台账与结转 + admin 工单留痕/异常列表 + settlement/report 预留空表。 + +**Non-Goals:** 质控放行/退回(qc 表 + 4001,C4)、补正(C4)、离线队列+自动补传(APP 期)、家属端、结算对账(二期)。 + +## Decisions + +1. **表命名**:`ho_record`/`ho_record_item`/`ho_record_photo`/`ho_record_abnormal`(前缀 ho,禁用 checkin/clockin/punch);`settlement`/`report` 无前缀预留。 +2. **模块划分**:`src/record/`(record + item + photo + 签到签退离线 + 垫付结转)+ `src/abnormal/`。 +3. **record 生命周期**:serving(签到)→ submitted(签退);returned/passed 留 C4。 +4. **服务器取时**:checkin_time/checkout_time/duration_min 系统取,接口不接受。 +5. **is_prepaid**:record 创建时 elder.treat_status = waiting_period → is_prepaid=1、billable=0。 +6. **结转**:复制垫付 record → 正式 record(is_prepaid=0 + source_prepaid_of),原记录保留只读、不改原始时间戳。 +7. **扑空**:工单转 not_met + 写 record_abnormal(到访/定位/原因),不产生 record。 +8. **exec_result**:done/undo_not_done/undo_refused。 +9. **照片**:MinIO 复用,≤500KB,visible_to_family 恒 0,keep_until ≥7 天。 +10. **client_md5 去重**:离线补传幂等。 +11. **主键**:UUID v7 VarChar(36)(循代码库,PRD 的 varchar(32) 归一)。 + +## 数据模型 + +通用字段 + UUID v7 + tenantCode + 软删(仅 record;子表循 ElderStatusLog/PlanItem 先例:带 tenant_code、无 deleted_at、不入 SOFT_DELETE)。6 表字段见 docs/superpowers 设计文档;record_abnormal 增加 order_id(扑空指向工单,PRD 最小字段未含)。 + +## 接口 + +record:checkin / item/save / photo/upload / checkout / offline/sync / detail / list / prepaid-list / prepaid/carryover。abnormal:report / list。权限码 record.*/abnormal.*;错误码域 45;规则号 3001/3002/3003/3004 写 message。 + +## Risks / Trade-offs + +- 离线幂等 → client_md5 唯一约束 + upsert。 +- 扑空 vs 逾期未做 → 有到访证据 vs 无记录。 +- 结转复制 vs 改标记 → 复制(两套并存合规)。 +- 必拍清单 source of truth → order.item_list 快照落 record_item.photo_required。 + +## Migration Plan + +新增 6 表(utf8mb4_general_ci 手改)。迁移链已损坏:用 `migrate diff --from-schema-datasource` + 手工迁移目录 + `migrate deploy`。 diff --git a/openspec/changes/svc-org-c3-record/proposal.md b/openspec/changes/svc-org-c3-record/proposal.md new file mode 100644 index 0000000..0ac0c5d --- /dev/null +++ b/openspec/changes/svc-org-c3-record/proposal.md @@ -0,0 +1,32 @@ +## Why + +C2 已落地派单工单(八态 order)+ 老人个人项目库。C3 交付 PRD 第三块「履约留痕」:护理员签到/签退/项目执行/拍照/异常上报 + 垫付台账与结转(is_prepaid)+ admin 工单留痕/异常列表。为 C4 质控闸门提供「已提交留痕」这一 L4/L5 前提。 + +## What Changes + +- 新增 `ho_record`/`ho_record_item`/`ho_record_photo`/`ho_record_abnormal` 四张表 + `settlement`/`report` 二期预留空表。 +- 履约留痕:签到(服务器取时)、项目执行(exec_result 枚举)、拍照(≤500KB)、签退(必拍 3001 硬拒 + 拒拍出路)、离线补传(client_md5 去重)。 +- 异常上报:`abnormal_type` 必选、其余可选;扑空(未遇)专门形态(工单转 not_met + 到访证据,不产生 record)。 +- 垫付台账与结转:`waiting_period` 留痕 `is_prepaid=1`、`billable=0`;档案页「结转」按钮勾选垫付记录生成正式记录(原记录保留只读、不改原始时间戳)。 +- L3 硬锁落地:留痕必须关联有效工单;L1 例外:`waiting_period` 走垫付不误拦。 +- 前端「工单留痕」页 + 「异常列表」页 + 服务对象页结转入口。 + +## Capabilities + +### New Capabilities + +- `backend/record`: 履约留痕——签到/签退/项目/照片/离线/列表/垫付台账/结转。 +- `backend/abnormal`: 异常上报/列表 + 扑空专门形态。 +- `frontend/ad/record`: admin 工单留痕页 + 异常列表页 + 服务对象页结转入口。 + +### Modified Capabilities + +- `shared/data-model`: 新增 `ho_record`/`ho_record_item`/`ho_record_photo`/`ho_record_abnormal` + `settlement`/`report` 六实体。 +- `shared/api-contract`: 新增 record/abnormal/prepaid 接口。 + +## Impact + +- 后端 ec-nest:新增 record/abnormal 模块(纯新增)。 +- 前端 ec-admin:工单留痕页 + 异常列表页 + 服务对象页扩展。 +- 数据:新增 6 张表(迁移,utf8mb4_general_ci)。 +- 依赖:C2 的 order 八态、plan/plan_item、staff(护理员)、MinIO StorageService。 diff --git a/openspec/changes/svc-org-c3-record/specs/backend/abnormal/spec.md b/openspec/changes/svc-org-c3-record/specs/backend/abnormal/spec.md new file mode 100644 index 0000000..a69433f --- /dev/null +++ b/openspec/changes/svc-org-c3-record/specs/backend/abnormal/spec.md @@ -0,0 +1,26 @@ +## Purpose + +定义服务机构端异常上报域的后端行为——异常上报(9 宫格)与扑空(未遇)专门形态。 + +## ADDED Requirements + +### Requirement: 异常上报 +`POST /abnormal/report` MUST 仅 abnormal_type 必选,其余(desc/voice/photo/关联)可选;关联 record 或 order(扑空)。 + +#### Scenario: 分类必选其余可选 +- **WHEN** 上报异常只选分类、其余留空 +- **THEN** 允许提交(宁要一条没人名的异常也不要漏报) + +### Requirement: 扑空专门形态 +扑空(未遇)MUST:工单转 not_met(not_met_reason/not_met_at)+ 写 record_abnormal(到访时间/定位/原因必留);不产生 record、不计费。与「逾期未做」(到点无记录)严格区分。 + +#### Scenario: 扑空留证据 +- **WHEN** 护理员登记未遇(老人不在/拒绝/改期) +- **THEN** 到访时间+定位+原因必留,工单转 not_met,不产生服务记录 + +### Requirement: 异常列表 +`GET /abnormal/list` MUST 返回异常列表(后台 B4),按 abnormal_type/notify_status/handle_status/老人 筛选。 + +#### Scenario: 质控查看异常 +- **WHEN** 质控员打开异常列表 +- **THEN** 看到异常分类、状态、关联老人 diff --git a/openspec/changes/svc-org-c3-record/specs/backend/record/spec.md b/openspec/changes/svc-org-c3-record/specs/backend/record/spec.md new file mode 100644 index 0000000..17dc130 --- /dev/null +++ b/openspec/changes/svc-org-c3-record/specs/backend/record/spec.md @@ -0,0 +1,62 @@ +## Purpose + +定义服务机构端履约留痕域的后端行为——签到/签退/项目执行/照片/离线补传/垫付台账与结转。 + +## ADDED Requirements + +### Requirement: 签到 +`POST /record/checkin` MUST 校验 L3(order 存在、order_status ∈ {assigned, serving}、elder/assignee 匹配);checkin_time 服务器取时(不接受客户端传入);定位失败 3002 不阻塞(locate_status=failed/denied);建 record(serving)并返回 record_id。 + +#### Scenario: 服务器取时 +- **WHEN** 签到 +- **THEN** checkin_time 由服务端生成,客户端传入被忽略 + +#### Scenario: 定位失败不阻塞 +- **WHEN** 签到但定位失败 +- **THEN** 标记 locate_status=failed,照常提交(3002) + +### Requirement: 签退提交 +`POST /record/checkout` MUST 校验必拍未拍(3001 硬拒 + 拒拍出路 is_refused_photo=1);checkout_time/duration_min 服务器取时;record_status 转 submitted;waiting_period 老人 is_prepaid=1、billable=0(3004 仅标记)。 + +#### Scenario: 必拍未拍硬拒 +- **WHEN** 必拍项未拍照且未走拒拍出路 +- **THEN** 返回 3001 拒绝签退 + +#### Scenario: 垫付标记 +- **WHEN** 等待期老人签退 +- **THEN** record.is_prepaid=1、billable=0,不计费 + +### Requirement: 项目执行 +`POST /record/item/save` MUST exec_result ∈ {done, undo_not_done, undo_refused};svc_id 落本机构 L4;photo_required 快照自 order.item_list。 + +#### Scenario: 措辞纪律 +- **WHEN** 项目未执行 +- **THEN** exec_result 用 undo_not_done(非 not_done) + +### Requirement: 照片上传 +`POST /record/photo/upload` MUST 校验文件 ≤500KB;visible_to_family=0;keep_until 补传后 ≥7 天。 + +#### Scenario: 超限拒绝 +- **WHEN** 上传超过 500KB 的照片 +- **THEN** 拒绝并提示压缩 + +### Requirement: 离线补传 +`POST /record/offline/sync` MUST 按 client_md5 幂等去重(重复提交返回已存在,静默不弹窗)。 + +#### Scenario: 去重 +- **WHEN** 同 client_md5 重复补传 +- **THEN** 幂等返回,不重复落库 + +### Requirement: 垫付台账与结转 +`waiting_period` 老人留痕 MUST `is_prepaid=1`、`billable=0`;`POST /record/prepaid/carryover` MUST 勾选若干 is_prepaid=1 记录 → 生成对应正式记录(复制、is_prepaid=0、source_prepaid_of=原 id),原记录保留只读、不改原始时间戳(签到时间/定位/照片仍是原始值)。 + +#### Scenario: 结转不改原始时间戳 +- **WHEN** 结转垫付记录 +- **THEN** 新记录签到时间/定位/照片仍为原始值,仅标记来源 + +### Requirement: 留痕列表与详情 +`GET /record/list`(admin B3,按 elder/assignee/日期/状态筛选)与 `GET /record/detail` MUST 返回留痕列表/详情(含项目、照片、异常、定位、垫付标记)。 + +#### Scenario: 质控查看留痕 +- **WHEN** 质控员查询留痕列表与详情 +- **THEN** 返回时间/项目/照片/异常/定位/垫付标记 diff --git a/openspec/changes/svc-org-c3-record/specs/frontend/ad/record/spec.md b/openspec/changes/svc-org-c3-record/specs/frontend/ad/record/spec.md new file mode 100644 index 0000000..402fc50 --- /dev/null +++ b/openspec/changes/svc-org-c3-record/specs/frontend/ad/record/spec.md @@ -0,0 +1,26 @@ +## Purpose + +定义服务机构端「工单留痕」「异常列表」页面与服务对象页结转入口的交互。 + +## ADDED Requirements + +### Requirement: 工单留痕页 +admin「工单留痕」页 MUST 提供记录列表 + 详情(签到/签退时间、时长、项目 exec_result、照片、异常、定位、垫付标记);质控员/院长可见,护理员看自己。 + +#### Scenario: 查看留痕 +- **WHEN** 质控员打开工单留痕 +- **THEN** 看到记录列表与详情(时间/项目/照片/异常/定位) + +### Requirement: 异常列表页 +admin「异常列表」页 MUST 展示异常(分类、状态、关联老人、语音/照片入口)。 + +#### Scenario: 查看异常 +- **WHEN** 质控员打开异常列表 +- **THEN** 看到异常分类与状态 + +### Requirement: 结转入口 +服务对象页 MUST 在待遇转「正常享受」后提供「结转垫付期服务」按钮:勾选 is_prepaid=1 记录 → 生成正式记录,原记录保留只读。 + +#### Scenario: 结转垫付 +- **WHEN** 待遇已生效,点「结转垫付期服务」 +- **THEN** 勾选垫付记录生成正式记录,原记录保留只读 diff --git a/openspec/changes/svc-org-c3-record/specs/shared/api-contract/spec.md b/openspec/changes/svc-org-c3-record/specs/shared/api-contract/spec.md new file mode 100644 index 0000000..b29d2b8 --- /dev/null +++ b/openspec/changes/svc-org-c3-record/specs/shared/api-contract/spec.md @@ -0,0 +1,26 @@ +## ADDED Requirements + +### Requirement: 履约留痕接口 +`POST /record/checkin`、`POST /record/item/save`、`POST /record/photo/upload`、`POST /record/checkout`、`POST /record/offline/sync`、`GET /record/detail`、`GET /record/list`、`GET /record/prepaid-list`、`POST /record/prepaid/carryover` MUST 存在,遵循统一响应。 + +#### Scenario: 签退必拍未拍硬拒 +- **WHEN** 必拍项未拍照且未走拒拍出路时签退 +- **THEN** 返回 3001,拒绝签退 + +#### Scenario: 签到服务器取时 +- **WHEN** 签到 +- **THEN** checkin_time 由服务端生成,客户端传入被忽略 + +### Requirement: 异常接口 +`POST /abnormal/report`、`GET /abnormal/list` MUST 存在,遵循统一响应。 + +#### Scenario: 扑空 +- **WHEN** 护理员登记未遇(扑空) +- **THEN** 工单转 not_met、到访时间/定位/原因必留、不产生 record + +### Requirement: 垫付结转接口 +`GET /record/prepaid-list` MUST 返回老人垫付台账(is_prepaid=1);`POST /record/prepaid/carryover` MUST 勾选垫付记录生成正式记录,原记录保留只读。 + +#### Scenario: 结转不改原始时间戳 +- **WHEN** 结转垫付记录 +- **THEN** 新记录签到时间/定位/照片仍为原始值,仅标记来源=垫付台账 diff --git a/openspec/changes/svc-org-c3-record/specs/shared/data-model/spec.md b/openspec/changes/svc-org-c3-record/specs/shared/data-model/spec.md new file mode 100644 index 0000000..f4081f4 --- /dev/null +++ b/openspec/changes/svc-org-c3-record/specs/shared/data-model/spec.md @@ -0,0 +1,36 @@ +## ADDED Requirements + +### Requirement: 履约留痕实体 ho_record +`ho_record` 表 MUST 存在(order_id、elder_id、assignee_id、checkin_time、checkout_time、duration_min、checkin_lat/lng、locate_status、record_status、voice_file、offline_flag、client_md5、submit_time、actual_duration_tier、biz_line、is_prepaid、billable、source_prepaid_of)。`record_status` MUST ∈ {serving, submitted}(returned/passed 留 C4)。签到签退时间与时长由系统生成,不接受客户端传入。 + +#### Scenario: 留痕关联有效工单 +- **WHEN** 创建一条留痕 +- **THEN** 必须带 order_id 且工单有效(L3 硬锁) + +### Requirement: 留痕项目明细 ho_record_item +`ho_record_item` 表 MUST 存在(record_id、svc_id 引 L4、svc_name_snapshot、exec_result、undo_reason、extra_flag、photo_required、is_refused_photo、refuse_reason)。`exec_result` MUST ∈ {done, undo_not_done, undo_refused}。 + +#### Scenario: 项目只能从 L4 选 +- **WHEN** 保存留痕项目 +- **THEN** svc_id 必须在本机构 svc_org 内,否则拒绝 + +### Requirement: 留痕照片 ho_record_photo +`ho_record_photo` 表 MUST 存在(record_id、svc_id、file_path、file_size、visible_to_family、upload_batch、keep_until)。`visible_to_family` 本期恒 0;文件压缩后 ≤500KB。 + +#### Scenario: 家属端不可见 +- **WHEN** 上传留痕照片 +- **THEN** visible_to_family = 0 + +### Requirement: 异常上报 ho_record_abnormal +`ho_record_abnormal` 表 MUST 存在(record_id 可空、order_id 可空、elder_id、reporter_id、abnormal_type、abnormal_desc、voice_file、has_photo、notify_status、handle_status、handle_result、link_plan_change)。`abnormal_type` MUST 必填,其余可选。 + +#### Scenario: 扑空无留痕 +- **WHEN** 登记未遇(扑空) +- **THEN** record_id 为空、order_id 指向工单、abnormal_type 为扑空分类 + +### Requirement: 二期预留表 settlement / report +`settlement`/`report` 表 MUST 建空表不写业务数据(settlement 含 amount_fund/amount_self_pay 个人自付拆分字段)。 + +#### Scenario: 预留表空建 +- **WHEN** 迁移完成后 +- **THEN** settlement/report 表存在且无业务数据 diff --git a/openspec/changes/svc-org-c3-record/tasks.md b/openspec/changes/svc-org-c3-record/tasks.md new file mode 100644 index 0000000..7aab51e --- /dev/null +++ b/openspec/changes/svc-org-c3-record/tasks.md @@ -0,0 +1,39 @@ +# Tasks — svc-org-c3-record + +## 1. 数据层 + +- [ ] 1.1 `schema.prisma` 新增 `Record`/`RecordItem`/`RecordPhoto`/`RecordAbnormal`/`Settlement`/`Report` 模型(表名 ho_record/ho_record_item/ho_record_photo/ho_record_abnormal/settlement/report)。 +- [ ] 1.2 `prisma.service.ts`:`Record` 加入 SOFT_DELETE;子表(item/photo/abnormal)不入。 +- [ ] 1.3 迁移 `c3_record`(utf8mb4_general_ci,用 migrate diff + deploy)+ `prisma generate`。 + +## 2. 权限码 + 错误码 + APIS + +- [ ] 2.1 `permissions.ts` 新增 `record.*`/`abnormal.*`(checkin/itemSave/photoUpload/checkout/offlineSync/detail/list/prepaidList/prepaidCarryover/report/list)。 +- [ ] 2.2 `error-codes.ts` 补域 45:RECORD_NOT_FOUND、RECORD_ORDER_INVALID、RECORD_PHOTO_REQUIRED(3001)、RECORD_STATUS_INVALID、ABNORMAL_NOT_FOUND 等。 +- [ ] 2.3 `tenant-template.ts` APIS 登记 record/abnormal 接口资源。 + +## 3. record 模块(履约留痕 + 垫付结转) + +- [ ] 3.1 `src/record/` 模块:checkin(L3 + 服务器取时 + 3002)、item/save(exec_result + L4)、photo/upload(≤500KB)、checkout(3001 必拍 + 服务器时长 + is_prepaid)、offline/sync(client_md5 去重)、detail、list。 +- [ ] 3.2 垫付台账与结转:prepaid-list、prepaid/carryover(复制 + source_prepaid_of + 不改原始时间戳)。 +- [ ] 3.3 TDD:签到 L3/取时/定位、签退 3001/垫付、exec_result 枚举、结转不改时间戳、离线去重;`pnpm test` + `tsc --noEmit`。 + +## 4. abnormal 模块(异常 + 扑空) + +- [ ] 4.1 `src/abnormal/` 模块:report(abnormal_type 必选)、list、扑空(工单转 not_met + record_abnormal,不产生 record)。 +- [ ] 4.2 TDD:分类必选其余可选、扑空转 not_met + 留证据、列表筛选;`pnpm test` + `tsc --noEmit`。 + +## 5. 前端 + +- [ ] 5.1 `src/api/record.ts`、`src/api/abnormal.ts`。 +- [ ] 5.2 工单留痕页(`ltc/record`):列表 + 详情(时间/项目/照片/异常/定位/垫付标记)。 +- [ ] 5.3 异常列表页(`ltc/qc` 或独立):分类/状态/关联老人。 +- [ ] 5.4 服务对象页:垫付台账 + 结转入口。 +- [ ] 5.5 `pnpm type-check` 通过。 + +## 6. 验证 + +- [ ] 6.1 冒烟:签到 → 项目 → 照片 → 签退(3001 必拍)→ 详情;waiting_period 老人垫付 is_prepaid=1。 +- [ ] 6.2 扑空:工单转 not_met + 到访证据,不产生 record。 +- [ ] 6.3 结转:勾选垫付记录生成正式记录,原始时间戳不变。 +- [ ] 6.4 离线补传 client_md5 去重幂等;审计落库。