Files

113 lines
5.9 KiB
Markdown
Raw Permalink 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.
# T5 · 诊单待办事项 + 企微定时推送
> 来源:~/.claude/plans/6-7-fluffy-sunset.mdT5 节)
> 范围:单个 PR、全栈,独立于 T1–T4,可单独并行上线。
## 用户故事
二中心医助希望在诊单上挂一些「将来要做的事」(例:3 天后回访、2 小时后提醒患者复测血糖),到点由企业微信推送给**创建人本人**(不是患者、不是当前指派医助)。失败一次即终态,由人工取消重建,不做自动重试。
## 范围
- 「待办事项」tab 出现在诊单编辑页 (`tcm/diagnosis/edit.vue`) — 在「运动打卡记录」之后。
- 待办列表(历史 + 当前一锅端,按 `remind_time desc`,分页)。
- 新建:选时间 + 输入内容;只允许 `remind_time > now`
- 取消:仅 `status=0` 可取消;只允许创建人本人或超管,按已有 role 边界判定。
- 不实现编辑(要改 = 取消重建)。
- 不实现提前 N 分钟提醒、不实现自动重试。
- 列表本身不做轮询,进 tab 拉一次。
## 数据模型
新建表 `zyt_tcm_diagnosis_todo`
| 字段 | 类型 | 备注 |
|---|---|---|
| `id` | int unsigned PK auto_increment | |
| `diagnosis_id` | int unsigned | 所属诊单 |
| `patient_id` | int unsigned | 冗余便于反查 |
| `content` | varchar(500) | 通知内容 |
| `remind_time` | int(10) unsigned | 提醒时间戳(秒) |
| `status` | tinyint | 0=待执行 / 1=已发送 / 2=已取消 / 3=发送失败 |
| `creator_id` | int unsigned | admin.id |
| `creator_name` | varchar(64) | 冗余,免 join |
| `notified_at` | int(10) unsigned NULL | 实际推送时间(成功时写) |
| `error` | varchar(500) NULL | 失败原因 |
| `cancelled_at` | int(10) unsigned NULL | |
| `cancelled_by` | int unsigned NULL | |
| `create_time` / `update_time` / `delete_time` | int(10) unsigned | 软删 + 整型时间戳 |
索引:
- `idx_diagnosis_id (diagnosis_id)`
- `idx_creator_id (creator_id)`
- `idx_status_remind_time (status, remind_time)` —— cron 扫表核心索引
迁移文件:`server/sql/1.9.20260420/add_tcm_diagnosis_todo.sql`
## API 路径
```
GET /tcm.diagnosisTodo/lists?diagnosis_id=:id 分页列表
POST /tcm.diagnosisTodo/add { diagnosis_id, content, remind_time }
POST /tcm.diagnosisTodo/cancel { id }
GET /tcm.diagnosisTodo/detail?id=:id
```
权限节点(`add_diagnosis_todo_menu.sql`,按钮型菜单挂在「诊单管理」下):
- `tcm.diagnosisTodo/lists`
- `tcm.diagnosisTodo/add`
- `tcm.diagnosisTodo/cancel`
## 后端文件
- `server/app/common/model/tcm/DiagnosisTodo.php`
- `server/app/adminapi/controller/tcm/DiagnosisTodoController.php`
- `server/app/adminapi/logic/tcm/DiagnosisTodoLogic.php`
- `server/app/adminapi/lists/tcm/DiagnosisTodoLists.php`
- `server/app/adminapi/validate/tcm/DiagnosisTodoValidate.php`
- `server/app/command/DiagnosisTodoNotify.php`
## Command 行为(`tcm:diagnosis-todo-notify`
每次执行:
1. `select * from zyt_tcm_diagnosis_todo where status=0 AND remind_time<=now AND delete_time IS NULL order by remind_time asc limit 200`
2. 对每条:
- 乐观锁更新:`update set status=1, notified_at=now where id=? and status=0` —— 如果影响行 0,说明被其它进程抢走,跳过。
-`creator_id``Admin::find($creator_id)->work_wechat_userid`,没绑定时记录 `error='创建人未绑定企业微信'` 并置 `status=3`
- 推文本:`【患者跟踪提醒】\n患者:{patient_name}\n时间:{remind_time_text}\n内容:{content}\n— 二中心跟踪系统`
-`WechatWorkAppMessageService::sendTextToUser($wxId, $text)`;失败 → `status=3, error=<msg>`
3. 每条 try-catch,整批不中断;输出 `处理 N / 成功 X / 失败 Y / 跳过未绑定 Z`
4. 注册到 `server/config/console.php``'tcm:diagnosis-todo-notify' => 'app\\command\\DiagnosisTodoNotify'`
5. 用户在服务器 crontab 自挂:`* * * * * cd /path && php think tcm:diagnosis-todo-notify >> /var/log/zyt-todo.log 2>&1`
## 前端文件
- 新建:`admin/src/views/tcm/diagnosis/components/DiagnosisTodoList.vue`
- 修改:`admin/src/views/tcm/diagnosis/edit.vue`(在 `name="exercise"` 之后追加 tab
- 修改:`admin/src/api/tcm.ts`(加 4 个函数)
UI
- 顶部「+ 新增待办」按钮 → `el-dialog` (`el-date-picker type=datetime` + `el-input type=textarea`)。
- 表格列:提醒时间 / 内容 / 状态 chip / 创建人 / 推送时间 / 失败原因 / 操作。
- 状态 chip`info` (待执行) / `success` (已发送) / `warning` (已取消) / `danger` (失败)。
- 操作列:`status===0` 显「取消」二次确认;其它隐藏。
- 排序:`remind_time desc`;分页。
## 验收清单(对应计划文件 T5 验收)
1. [x] 在某诊单编辑页 → 待办事项 tab → 新建一条「2 分钟后,内容=测试」→ 表格出现「待执行」。
2. [x] 等 2 分钟,手动跑 `php think tcm:diagnosis-todo-notify`,企微收到文本;状态变「已发送」、推送时间已写。
3. [x] 把 admin `work_wechat_userid` 清空,再创建一条,跑命令 → 状态「失败」+ `error` 列显示原因;不会再次重试。
4. [x] 创建一条 1 小时后的 → 立即点「取消」→ 状态变「已取消」、操作列消失;再跑命令也不会推。
5. [x] 切换到该诊单的另一名 admin(非创建人)→ 看见这条但**没有取消按钮**。
6. [x] 历史 / 当前同表,按时间倒序,分页正常。
7. [x] `tcm:diagnosis-todo-notify` 二次执行不重复推送同一条(乐观锁验证)。
## 不做(边界)
- 不实现「提前 N 分钟提醒」逻辑("时间到了"做精准触发即可)。
- 不实现重试退避 —— 失败即终态。
- 不引入 SSE / WebSocket(待办列表不实时刷新,分钟级 cron 已够用)。
- 不修改已有的 `BloodRecordList.vue / DietRecordList.vue / ExerciseRecordList.vue`
- 不与 T1–T4 共用任何文件之外的逻辑(这些子任务是独立 PR)。