From c936732aa26eac05162ce18159bf675fb298f856 Mon Sep 17 00:00:00 2001 From: liuyun Date: Thu, 15 Oct 2026 00:09:30 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=9C=8D=E5=8A=A1=E6=9C=BA=E6=9E=84?= =?UTF-8?q?=E7=AB=AF=20C3=20=E5=B1=A5=E7=BA=A6=E7=95=99=E7=97=95=E8=AE=BE?= =?UTF-8?q?=E8=AE=A1=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- ...2026-10-15-服务机构端-C3履约留痕-design.md | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 docs/superpowers/specs/2026-10-15-服务机构端-C3履约留痕-design.md diff --git a/docs/superpowers/specs/2026-10-15-服务机构端-C3履约留痕-design.md b/docs/superpowers/specs/2026-10-15-服务机构端-C3履约留痕-design.md new file mode 100644 index 0000000..6e539f8 --- /dev/null +++ b/docs/superpowers/specs/2026-10-15-服务机构端-C3履约留痕-design.md @@ -0,0 +1,78 @@ +# 服务机构端 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. **表命名**:`record`/`record_item`/`record_photo`/`record_abnormal`(禁用 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**(`ltc_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`。 + +> 表前缀(定):留痕属老人服务域,循 `ltc_` 先例(`ltc_elder`/`ltc_elder_status_log`/`ltc_elder_svc`)——`ltc_record`/`ltc_record_item`/`ltc_record_photo`/`ltc_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`。