touyun-elderly-care/docs/superpowers/specs/2026-10-15-服务机构端-C3履约留痕-design.md
liuyun 45aaeb9d43 docs: C3 留痕四表前缀改为 ho(用户指定)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-15 00:11:18 +08:00

79 lines
8.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 服务机构端 C3 履约留痕 · 设计文档
> 本文是 C3 履约留痕的设计背景,规范权威见 `openspec/changes/svc-org-c3-record/`(proposal + specs + design + tasks)与 PRD《20261011-投云长护险管理系统-P0 PRD(交付开发·V1)》。
## Context
C0–C2 已落地:elder(十态)、plan/plan_item、order(八态,含 not_met_reason/not_met_at)、elder_svc、staff、svc_org(L4)、排班派单。C3 新增**履约留痕**:护理员签到/签退/项目执行/拍照/异常上报 + 垫付台账与结转 + admin「工单留痕/异常列表」两页。录入主战场是护理员 APP(尚未开发),本期交付**后端 record 模块 + admin Web**,录入流程靠 API 冒烟验收(同 C2)。
## Goals / Non-Goals
**Goals**:record/record_item/record_photo/record_abnormal 四表 + 录入 API(签到/项目/照片/签退/离线补传/详情)+ 异常上报/列表 + 垫付台账与结转(is_prepaid)+ admin 工单留痕/异常列表两页 + settlement/report 二期预留建空表。
**Non-Goals**:质控放行/退回(qc 表 + 4001 自审自放行,C4)、补正(质控退回联动,C4)、离线队列+自动补传(APP 期,本期只做 client_md5 去重)、家属端、结算对账(二期)。
## Decisions
1. **表命名**:`ho_record`/`ho_record_item`/`ho_record_photo`/`ho_record_abnormal`(前缀 `ho`,禁用 checkin/clockin/punch)。`settlement`/`report` 为二期预留、本期建空表不写业务数据。`qc` 表 C4 建。
2. **模块划分**:`src/record/`(record + item + photo + 签到/签退/离线)+ `src/abnormal/`(异常有独立生命周期 notify/handle,供 C4 质控联动)。垫付结转并入 record 模块。
3. **record 生命周期**:`serving`(签到后)→ `submitted`(签退后);`returned`/`passed` 留 C4 质控。
4. **服务器取时**:`checkin_time`/`checkout_time`/`duration_min` 一律系统取,接口不接受客户端传入(防补录时间)。`submit_time` 系统取。
5. **is_prepaid**:record 创建时若 `elder.treat_status = waiting_period` → `is_prepaid=1`、`billable=0`(不计费、不进上报池,L1 例外不被误拦)。
6. **结转**:待遇转「正常享受」后,档案页「结转垫付期服务」按钮 → 勾选若干 `is_prepaid=1` record → 生成对应正式 record(复制,`is_prepaid=0` + 来源标记),**原垫付记录永久保留只读、不改原始时间戳**(签到时间/定位/照片仍是原始值)。
7. **扑空(未遇)**:异常上报专门子类 → 工单转 `not_met`(not_met_reason/not_met_at)+ 写 record_abnormal(abnormal_type=扑空分类,含到访时间/定位/原因)→ **不产生 record、不计费**。与「逾期未做」严格区分。
8. **exec_result 枚举**:`done`/`undo_not_done`(本次未执行)/`undo_refused`(老人拒绝)——措辞纪律。
9. **照片**:复用 C1 MinIO StorageService,压缩后 ≤500KB,`visible_to_family` 恒 0,`keep_until` 补传后 ≥7 天。
10. **必拍未拍**:签退硬拒 `3001` + 拒拍出路(`is_refused_photo=1`+原因,放行)。
11. **client_md5 去重**:离线补传按 client_md5 幂等,静默不弹窗。
12. **主键口径**:全站已用 UUID v7 `@db.VarChar(36)`(PRD 写 varchar(32)),C3 循代码库口径 VarChar(36)(字段一致唯一锚点)。
## 数据模型(DDL 概览)
通用:UUID v7、`tenant_code`、`@map` snake_case、utf8mb4_general_ci。record 入 SOFT_DELETE;record_item/record_photo/record_abnormal 为子表(循 ElderStatusLog/PlanItem 先例:带 tenant_code、无 deleted_at、不入 SOFT_DELETE)。
- **record**(`ho_record`):`id`、`tenant_code`、`order_id`、`elder_id`、`assignee_id`、`checkin_time`、`checkout_time`、`duration_min`、`checkin_lat`、`checkin_lng`、`locate_status`(ok/failed/denied)、`record_status`(serving/submitted)、`voice_file`、`offline_flag`、`client_md5`、`submit_time`、`actual_duration_tier`、`biz_line`(恒 ltci)、`is_prepaid`、`billable`、`source_prepaid_of`(结转来源标记)、时间戳 + `deleted_at`。索引 (tenant_code, order_id)、(tenant_code, elder_id)、(tenant_code, assignee_id, checkin_time)。
- **record_item**:`id`、`tenant_code`、`record_id`、`svc_id`、`svc_name_snapshot`、`exec_result`(done/undo_not_done/undo_refused)、`undo_reason`、`extra_flag`、`photo_required`、`is_refused_photo`、`refuse_reason`。索引 (record_id)。
- **record_photo**:`id`、`tenant_code`、`record_id`、`svc_id`、`file_path`、`file_size`、`visible_to_family`(恒 0)、`upload_batch`、`keep_until`。索引 (record_id)。
- **record_abnormal**:`id`、`tenant_code`、`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`。索引 (tenant_code, elder_id)、(tenant_code, notify_status)。
- **settlement**(预留空表):`id`、`tenant_code`、`settle_period`、`amount_total`、`amount_passed`、`amount_rejected`、`amount_fund`、`amount_self_pay`、`submit_date`、`pay_date`、`exception_reason`、`handle_deadline`。
- **report**(预留空表):`id`、`tenant_code`、`record_id`、`order_id`、`report_status`、`report_time`、`official_no`、`loc_code`、`nat_code_snapshot`、`adapter_result`。
> 表前缀(定):留痕四表前缀 `ho`(用户指定)——`ho_record`/`ho_record_item`/`ho_record_photo`/`ho_record_abnormal`;`settlement`/`report` 为二期预留,无前缀(与 PRD §7.3 一致)。
## 接口(`@Controller('record')` / `@Controller('abnormal')`)
**record**
- `POST /record/checkin` — 签到:校验 L3(order 存在、order_status ∈ {assigned, serving}、elder/assignee 匹配)→ 系统取 checkin_time → 定位可选(失败不阻塞 3002)→ 建 record(serving) → 返回 record_id。
- `POST /record/item/save` — 保存项目执行:record_id + items[](svc_id 落本机构 L4、exec_result 枚举)。
- `POST /record/photo/upload` — 上传照片:record_id + svc_id + file(≤500KB)→ MinIO → 返回 photo_id。
- `POST /record/checkout` — 签退提交:record_id → 必拍未拍硬拒 3001(+拒拍出路)→ 系统取 checkout_time + duration_min → record_status=submitted。
- `POST /record/offline/sync` — 离线补传:client_md5 去重幂等。
- `GET /record/detail` — 详情。
- `GET /record/list` — 工单留痕列表(admin B3,质控员/院长;筛选 elder/assignee/日期/状态)。
- `GET /record/prepaid-list` — 垫付台账列表(档案页,按 elderId 查 is_prepaid=1)。
- `POST /record/prepaid/carryover` — 结转:勾选 record_ids → 生成正式 record(复制、is_prepaid=0、source_prepaid_of=原 id)→ 原记录保留只读。
**abnormal**
- `POST /abnormal/report` — 异常上报:abnormal_type 必选,其余可选;可关联 record 或 order(扑空)。
- `GET /abnormal/list` — 异常列表(admin B4)。
权限码:record.checkin/itemSave/photoUpload/checkout/offlineSync/detail/list/prepaidList/prepaidCarryover、abnormal.report/list。错误码域 45(RECORD_* / ABNORMAL_*);规则号 3001/3002/3003/3004 写 message 文本(循 C2 R3 口径)。
## 硬锁与校验
- **L3**:留痕必须关联有效工单(order 存在且非 cancelled/not_met/done)。
- **L1 例外**:waiting_period → is_prepaid=1、billable=0(不误拦)。
- **必拍未拍** → 3001 硬拒;定位失败 → 3002 标记不阻塞;垫付 → 3004 标记不计费;扑空 → 3003 状态变更。
- **服务器取时**:接口不接受 checkin_time/checkout_time/duration_min。
## Risks / Trade-offs
- [离线补传幂等] → client_md5 唯一约束 + upsert;APP 期补本地队列。
- [扑空 vs 逾期未做] → 扑空有到访时间/定位/原因佐证;逾期未做=到点无记录(C4 质控发现)。
- [结转=复制而非改标记] → 两套并存,合规(不自动算钱);代价=存储翻倍但可追溯。
- [必拍档项目清单的 source of truth] → photo_tier 来自 plan_item/order.item_list 快照;C3 落 record_item.photo_required 快照。
## Migration Plan
新增 6 表(record/record_item/record_photo/record_abnormal + settlement/report 预留)。utf8mb4_general_ci 手改。⚠️ 迁移链已损坏(tech-debt):`migrate dev` 不可用,用 `migrate diff --from-schema-datasource` + 手工迁移目录 + `migrate deploy`。