# FSD m08 · 诊疗过程（care）

LLMS 索引： [llms.txt](/llms.txt)

---

> 本册覆盖代码域 `internal/domain/care`（无新表——care_stage 落 ticket 列 `TransitionStageInTx` 唯一入口，阶段历史=lifecycle 行集，不建阶段记录表——DIF-M7 ①）。全局规则见 [00-总则](/prd/fsd-medilink/00-%E6%80%BB%E5%88%99/) §2（C 轴状态机 §2.2）。

## m08 功能节目录

| ID | 名称 | 路由/入口 |
|---|---|---|
| F8.01 | C 轴 8 阶段推进 | `POST /api/care/:ticketId/stage` |
| F8.02 | 阶段资料版本组 | `POST /api/care/:ticketId/documents/presign` + `/confirm` |
| F8.03 | 患者进度同步 | （F4.04 派生 + SSE ticket 事件） |

---

#### F8.01 C 轴 8 阶段推进

> 对应 SRS：F-CARE-001 ｜ 实现落点：`internal/domain/care/service.go:50`（AdvanceStage）/`ticket/service.go`（TransitionStageInTx）/`ticket/pure.go:115`（stageTransitions）/`main.go:311`（路由） ｜ 操作入口：web 工单详情「阶段推进」操作（顾问）

##### 1 功能定义

顾问推进患者医疗旅程 8 阶段（出发准备→到达接待→初诊→检查→确诊→治疗→康复→返程）：C 轴白名单单向链推进，**前置 status=IN_TREATMENT 严格校验**；推进=care_stage 谓词 CAS + lifecycle(from/to 填 stage 值——A/C 两轴事件同表靠值区分) + op_log 同事务 → Commit 后三 key SSE。

##### 2 触发条件与前置状态

`care:write`（CONSULTANT）；工单 `status='IN_TREATMENT'`（否则 409 ErrNotInTreatment）；COMPLETED 后查询可见但不推进。

##### 3 输入与校验

| 字段 | 类型 | 校验 |
|---|---|---|
| to | string | stageTransitions 白名单（回退/跳段 409 ErrInvalidStageTransition） |
| reason | string | 必填（关键操作审计） |

##### 4 处理流程

```mermaid
sequenceDiagram
    participant W as web 工单详情
    participant H as care.Handler
    participant S as care.Service
    participant DB as MySQL
    participant T as ticket.Service
    W->>H: POST /api/care/:ticketId/stage {to, reason}
    H->>S: AdvanceStage
    S->>DB: SELECT 工单（status≠IN_TREATMENT → 409）
    S->>S: CanTransitionStage 白名单前置
    S->>DB: WithTx: TransitionStageInTx（care_stage 谓词 CAS + lifecycle + op_log）
    S->>T: Commit 后 NotifyTransition（Status 不变 CareStage=to——两轴正交）
    H-->>W: 204
```

##### 5 输出与结果状态

204；`ticket.care_stage` 已推进；患者进度页（F4.04 派生）对应步点亮；SSE ticket 事件双端刷新。

##### 6 状态流转

C 轴单向链（NONE→PREP_DEPARTURE→…→DEPARTURE，无回退边——SRS 无回退语义）；推进前置 status=IN_TREATMENT 由 service 层校验（白名单外-1：C 轴仅在就医中推进，tech-design §6.2 正交性）。

##### 7 边界与异常

| 场景 | 行为 |
|---|---|
| 工单不在 IN_TREATMENT | 409 ErrNotInTreatment（前置不满足） |
| 回退/跳段 | 409 ErrInvalidStageTransition |
| DEPARTURE 再推进 | 409（终态无出边） |
| 阶段历史查询 | lifecycle 行集（`GET /api/lifecycle/ticket/:id` 统一端点，from/to=stage 值） |

##### 8 权限与数据规则

`care:write`（CONSULTANT 专属——患者只读 F8.03）；无阶段记录表（历史=lifecycle 行集，双日志轨兑现「状态表必配日志表」六原则——DIF-M7 ①）。

---

#### F8.02 阶段资料版本组

> 对应 SRS：F-CARE-002（文件上传、文字说明、自动归类、版本管理） ｜ 实现落点：`internal/domain/care/service.go:71`（PresignStageDocument）/`:81`（ConfirmStageDocument）/`ticket/service.go:164`（InsertStageDocumentInTx）/`ReplaceDocumentVersionInTx` ｜ 操作入口：web 工单详情「上传阶段资料」（选阶段+文件+说明，可替换既有组）

##### 1 功能定义

顾问上传阶段资料（检查报告/处方等）：直传两道（B 端内部操作不走 pub 面 gsid 配额——20MB/mime 由 Store 执行）；stage 列落上传阶段（自动归类）；**版本组语义**：缺省=新组首传（doc_group 事务内回填=id）；显式 doc_group=替换（旧行 is_current=0 CAS + INSERT version_no+1 同事务）。

##### 2 触发条件与前置状态

`care:write`；工单归属存在（替换时组须属于本单）。

##### 3 输入与校验

| 端点/字段 | 校验 |
|---|---|
| presign {mime} | pdf/jpg/png（Store 白名单）20MB |
| confirm {key, file_name, label, stage, doc_group?} | Confirm 复核实际 size/mime；doc_group=nil=新组；显式 0=历史散件不可替换（400 ErrDocGroupInvalid）；组不存在/不属于本单 400；替换并发 409 ErrDocGroupConflict |

##### 4 处理流程

```mermaid
sequenceDiagram
    participant W as web 工单详情
    participant H as care.Handler
    participant S as care.Service
    participant OS as MinIO
    participant DB as MySQL
    W->>H: POST /api/care/:ticketId/documents/presign {mime}
    S->>OS: PresignPut(key=care/*, 20MB)
    H-->>W: 201 {key, upload_url}
    W->>OS: PUT 文件
    W->>H: POST .../documents/confirm {key, label, stage, doc_group?}
    S->>OS: Confirm（HeadObject 复核）
    alt doc_group 缺省（新组首传）
        S->>DB: WithTx: INSERT(doc_group=0) → 回填 doc_group=id → 双日志(DOC_UPLOAD)
    else doc_group 显式（替换）
        S->>DB: SELECT 现行行（组归属+is_current=1；不过→400）
        S->>DB: WithTx: 旧行 is_current=0 CAS → INSERT version_no+1 → 双日志
    end
    H-->>W: 200 文档 JSON
```

##### 5 输出与结果状态

文档 JSON（doc_group/version_no/is_current/stage/label…）；同组下 current 唯一由事务保证；历史版本只软置 is_current=0 不物理删（审计要求）。

##### 6 状态流转

medical_document 版本翻转（is_current 1→0 / 新行 1）；doc_group=0=历史散件哨兵（M4 注册直传行恒 0，语义不可替换）；lifecycle event=DOC_REPLACE（替换时）。

##### 7 边界与异常

| 场景 | 行为 |
|---|---|
| doc_group=0 显式传入 | 400（历史散件组不可作为替换目标——语义裁断） |
| 组不存在/不属于本单 | 400 ErrDocGroupInvalid |
| 并发替换（is_current CAS 命中 0 行） | 409 ErrDocGroupConflict（回滚零副作用） |
| 对象不存在/超限/mime 不符 | 404/400（Confirm 第二道） |
| 存量 doc_group=0 行 | 不回填迁移（DIF-M7 ③ 申报——语义=不可替换历史散件） |

##### 8 权限与数据规则

`care:write`；uploaded_by=actor_id；版本读端点=共享 F5.04（`GET /api/documents/:id/versions`，归属谓词同款）。

---

#### F8.03 患者进度同步

> 对应 SRS：F-CARE-003（可视化进度条、阶段详情、待办事项、推送通知、报告下载） ｜ 实现落点：派生视图 `ticket/pure.go:68`（DerivePatientSteps，消费在 F4.04）+ SSE ticket 事件（F8.01 Commit 后三 key） ｜ 操作入口：app H5 `#/pages/progress` 进度页（实时刷新）

##### 1 功能定义

患者端进度的同步机制=三层组合：①SSE 实时推送（ticket 事件携 status/care_stage）；②REST 兜底（/pt/progress 派生重算）；③派生八步投影（纯函数跨 A+C 两轴）。**注意与 SRS 的差异**：待办事项不做（MVP 无该实体——登记 DIF），推送通知=EMAIL outbox 仅注册/方案/支付三节点（无阶段级邮件——DIF-M7 ⑤ app 只读拉取口径）。

##### 2 触发条件与前置状态

pt JWT；F8.01 推进后 SSE 自动到达；重连/打开页面走 /pt/progress 全量重算。

##### 3 输入与校验

无业务入参（F4.04 同源）。

##### 4 处理流程

```mermaid
flowchart LR
    A["F8.01 Commit 后"] --> B["SSE ticket 事件 → s:会话 key（患者流）"]
    B --> C[app 进度页就地刷新]
    D["断线/打开页"] --> E["GET /pt/progress<br/>DerivePatientSteps 全量重算"]
    E --> F[进度条/阶段详情渲染]
    G["报告下载"] --> H["GET /pt/documents/:id/download<br/>F5.04 患者双轨归属谓词"]
```

##### 5 输出与结果状态

进度步数组（done 标记）+ 阶段详情（ticket 列+版本组文档）+ 出行记录（F9.03）同页可见。

##### 6 状态流转

只读消费；不反灌状态机（口径 B 投影纪律）。

##### 7 边界与异常

| 场景 | 行为 |
|---|---|
| SSE 丢失事件 | 重连补拉 + REST 全量重算兜底 |
| 待办事项 | **未实现**（SRS F-CARE-003 列出，MVP 裁剪——登记 99-附录差异台账 DIF-F8-1） |
| 阶段级推送通知 | 未实现（EMAIL 仅注册/方案/支付三节点；trip 无 SSE——DIF-M7 ⑦ 明确不做清单） |

##### 8 权限与数据规则

pt JWT + patient_id 谓词；SSE 事件经会话 key 定向（不广播他人数据）。

---

反链：

- [功能规格说明书(FSD)](/prd/fsd-medilink/)
- [MediLink Global 功能规格说明书（FSD）v1.0 · 总则](/prd/fsd-medilink/00-%E6%80%BB%E5%88%99/)
