12 KiB
服务机构端 C4 质控闸门 实施计划
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: 在 C3 骨架上落地「质控闸门」:qc 表 + 补正表、全量质控(定时任务)、放行/退回(4001 自审自放行)、五类主动推送聚合、异常联动改计划、补正、admin 质控中心页。
Architecture: 纯新增。后端新增 qc 模块(依赖 record/abnormal/plan/order/elder),前端质控中心页。复用 C3 的 record(submitted)、abnormal、C2 的 plan.change、C1 的 elder。
Tech Stack: NestJS 12 + Prisma 6 + MySQL 8(utf8mb4_general_ci)、Vue 3 + Vite + Pinia + UnoCSS。
Spec: openspec/changes/svc-org-c4-qc/(proposal + specs + design + tasks);设计背景 docs/superpowers/specs/2026-10-15-服务机构端-C4质控闸门-design.md。
Global Constraints
- 机构=租户:业务表带
tenant_code、UUID v7、@mapsnake_case;新表utf8mb4_general_ci。 - 表命名:
qc(无前缀,质控记录)、ho_record_correction(补正属留痕域 ho_)。 - 不动脚手架文件;业务代码落
src/qc/、src/pages/ltc/qc/。 - 放行前自检按序:四要素(1004) → 必拍(3001) → 幂等 → 自审自放行(4001)。
- 五类推送 = pending-list 跨表聚合(非 qc 落库);每类带消除条件。
- 补正 = 新增说明不改原记录;不可补正签到时间/定位/照片;≥3 次进质控重点。
- 权限码 qc.*;错误码域 46;规则号 1004/3001/4001 写 message。
- ⚠️ 迁移链损坏:
migrate dev不可用,用migrate diff --from-schema-datasource+ 手工迁移目录 +migrate deploy。
Review Focus
- 自审自放行:
record.created_by === ctx.userId(放行人=录入人)→ 4001 拒绝(不因角色并集放宽)。 - 放行自检顺序:四要素 → 必拍 → 幂等 → 自审,任一不过不写 pass_time。
- 补正不改原值:correct 只写 correction + 生成 recheck qc,不更新 record 的签到时间/定位/照片/项目。
- 五类推送消除条件:每类处理完后从 pending-list 消失(否则列表只长不消)。
- 定时任务跨租户:@Cron 无请求上下文,用
prisma.raw(无租户注入)跨租户扫描,否则只扫到单租户。
Task 1: Prisma 模型 + 迁移
Files:
- Modify:
sources/ec-nest/prisma/schema.prisma - Modify:
sources/ec-nest/src/prisma/prisma.service.ts
Interfaces:
-
Produces:
Qc/RecordCorrection模型。 -
Step 1: 追加 2 个模型
/// 质控记录
model Qc {
id String @id @default(uuid(7)) @db.VarChar(36)
tenantCode String @map("tenant_code") @db.VarChar(64)
recordId String @map("record_id") @db.VarChar(36)
qcType String @default("normal") @map("qc_type") @db.VarChar(20)
qcResult String @default("pending") @map("qc_result") @db.VarChar(20)
qcNote String? @map("qc_note") @db.VarChar(200)
qcStaffId String? @map("qc_staff_id") @db.VarChar(36)
qcTime DateTime? @map("qc_time")
passTime DateTime? @map("pass_time")
firstQcId String? @map("first_qc_id") @db.VarChar(36)
createdAt DateTime @default(now()) @map("created_at")
createdBy String? @map("created_by") @db.VarChar(36)
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
@@index([tenantCode, recordId])
@@index([tenantCode, qcResult])
@@map("qc")
}
/// 补正说明(留痕域 ho_)
model RecordCorrection {
id String @id @default(uuid(7)) @db.VarChar(36)
tenantCode String @map("tenant_code") @db.VarChar(64)
recordId String @map("record_id") @db.VarChar(36)
qcId String? @map("qc_id") @db.VarChar(36)
correctionNote String @map("correction_note") @db.VarChar(200)
correctedBy String @map("corrected_by") @db.VarChar(36)
correctedAt DateTime @default(now()) @map("corrected_at")
@@index([recordId])
@@map("ho_record_correction")
}
-
Step 2: SOFT_DELETE 加
Qc(prisma.service.ts 第 8 行集合加'Qc';RecordCorrection子表不入) -
Step 3: 迁移(utf8mb4_general_ci)
Run: cd sources/ec-nest && pnpm exec prisma migrate diff --from-schema-datasource prisma/schema.prisma --to-schema-datamodel prisma/schema.prisma --script > /tmp/c4.sql;手工落盘迁移目录 20261015xxxxxx_c4_qc/migration.sql,utf8mb4_unicode_ci 全改 utf8mb4_general_ci;pnpm exec prisma migrate deploy && pnpm exec prisma generate。
- Step 4: Commit
git -C sources/ec-nest add prisma/schema.prisma prisma/migrations src/prisma/prisma.service.ts
git -C sources/ec-nest commit -m "feat(c4): qc + record_correction 数据模型"
Task 2: 权限码 + 错误码 + APIS
Files: permissions.ts、error-codes.ts、tenant-template.ts(APIS)
- Step 1: permissions.ts 追加:
qc: {
pendingList: 'qc:pending-list', detail: 'qc:detail', pass: 'qc:pass', return: 'qc:return',
exceptionLink: 'qc:exception-link', correct: 'qc:correct', batchPass: 'qc:batch-pass',
},
- Step 2: error-codes.ts 补域 46:
// 服务机构端·质控(46)
QC_NOT_FOUND: 46001,
QC_ALREADY_HANDLED: 46002,
QC_SELF_REVIEW: 46003, // 自审自放行(4001)
QC_ELEMENT_MISSING: 46004, // 缺要素(1004)
QC_NOTE_REQUIRED: 46005,
QC_BATCH_UNSAFE: 46006,
QC_RECORD_NOT_SUBMITTED: 46007,
- Step 3: APIS 登记(parent 'qc'):
{ code: Permissions.qc.pendingList, name: '待质控列表', method: 'GET', path: '/api/v1/qc/pending/list', parent: 'qc' },
{ code: Permissions.qc.detail, name: '质控详情', method: 'GET', path: '/api/v1/qc/:id', parent: 'qc' },
{ code: Permissions.qc.pass, name: '放行', method: 'POST', path: '/api/v1/qc/:id/pass', parent: 'qc' },
{ code: Permissions.qc.return, name: '退回', method: 'POST', path: '/api/v1/qc/:id/return', parent: 'qc' },
{ code: Permissions.qc.exceptionLink, name: '异常联动改计划', method: 'POST', path: '/api/v1/qc/exception/link', parent: 'qc' },
{ code: Permissions.qc.correct, name: '补正', method: 'POST', path: '/api/v1/qc/correct', parent: 'qc' },
{ code: Permissions.qc.batchPass, name: '批量放行', method: 'POST', path: '/api/v1/qc/batch-pass', parent: 'qc' },
- Step 4: tsc + Commit
git -C sources/ec-nest add src/common/permissions.ts src/common/error-codes.ts src/common/tenant-template.ts
git -C sources/ec-nest commit -m "feat(c4): qc 权限码/错误码/APIS"
Task 3: qc 模块
Files: 新建 src/qc/{qc.module,qc.controller,qc.service,dto,qc.service.spec}.ts;注册 module(本任务直接改 app.module.ts,本 change 单任务串行)。
Interfaces:
-
Consumes:
PrismaService(client + raw)、getRequestContext()、Permissions.qc.*、ErrorCodes.QC_*、record/abnormal/plan/order/elder 模型。 -
Produces:
QcService(generatePending/pendingList/detail/pass/return/exceptionLink/correct/batchPass + @Cron 定时任务)。 -
Step 1: 写失败测试(qc.service.spec.ts):定时任务生成 pending qc;放行自检四步(1004/3001/幂等/4001);退回 qc_note 必填;补正不改原值 + 生成 recheck;批量放行排除异常记录;五类推送消除条件。
-
Step 2: 跑测试确认失败
-
Step 3: 实现 qc.service
关键逻辑:
-
定时任务:
@Cron('0 * * * *')generatePendingQc()——用prisma.raw跨租户扫描record_status='submitted'且无 qc 的 record → 为每条 create qc(tenantCode 取 record.tenantCode、qcType='normal'、qcResult='pending')。 -
pendingList(q):聚合五类(跨表查询):
- 异常待处理:
recordAbnormal.count({ handleStatus:'open' })(消除条件=已处理)。 - 逾期未做:order
timeSlotEnd < now-24h且无 record 且 orderStatus 非 cancelled/not_met/done。 - 住院仍有派单:elder
treatStatus='hosp_pause'且有 orderStatus ∈ {to_assign, assigned, serving} 的 order。 - 计划未制定:elder
benefit_start ≤ now-5工作日且无 plan_status=active 的 plan。 - 留痕待放行:
qc.count({ qcResult:'pending' })+ 待放行队列(pending qc 列表,含 record 摘要 + 已等待时长)。
- 异常待处理:
-
pass(id):
requireQc→qcResult !== 'pending'→ QC_ALREADY_HANDLED(幂等);record.findUnique→ ① 四要素(checkin_time/checkout_time/duration_min/assignee_id 任一缺 → QC_ELEMENT_MISSING 1004)→ ② 必拍已拍(遍历 record_item photoRequired && done && !isRefusedPhoto 无 photo → 3001,复用 C3 口径)→ ④record.createdBy === ctx.userId→ QC_SELF_REVIEW 4001;通过 → qc.update({ qcResult:'passed', qcStaffId, qcTime:now, passTime:now })。 -
return(id, dto):
dto.qcNote空 → QC_NOTE_REQUIRED;qc.update({ qcResult:'returned', qcNote, qcStaffId, qcTime:now })。 -
detail(id):qc + 关联 record(全文/项目/照片/异常/定位)+ 补正历史(correction 列表)。
-
exceptionLink(dto):
abnormal.findUnique→ 复用planService.change(传 elderId + changeFromPlanId + items 含加项)生成新 plan →abnormal.update({ linkPlanChange:true })→ 返回新 plan。 -
correct(dto):
record.findUnique(record_status 须 submitted)→correction.create({ recordId, qcId, correctionNote, correctedBy: ctx.userId })→ 生成 recheck qc:qc.findFirst({ recordId, qcResult:'returned', orderBy 最新 })→qc.create({ recordId, qcType:'recheck', qcResult:'pending', firstQcId: 首次qcId })。(不改 record 任何证据字段。) -
batchPass(dto):对每条 record 校验「有 abnormal 关联 / 有 exec_result∈{undo_not_done,undo_refused} / 有 extra_flag / duration<5min / 有 correction」→ 命中任一则 QC_BATCH_UNSAFE;否则逐条 pass。
-
Step 4: 实现 controller + dto + module(
@Controller('qc');写操作@AuditLog;@Cron在 service;注册 module 到 app.module.ts)。路由序:静态路径(pending/list、correct、batch-pass、exception/link)必须声明在GET /:id之前(否则:id吞掉静态段)。 -
Step 5: 测试通过 + tsc + Commit
git -C sources/ec-nest add src/qc src/app.module.ts
git -C sources/ec-nest commit -m "feat(c4): 质控模块(全量质控/放行退回/五类推送/异常联动/补正)"
Task 4: 前端
Files: src/api/qc.ts;src/pages/ltc/qc/index.vue(替换占位)。
-
Step 1: api 封装(
http+ 类型对齐后端 DTO;pendingList/detail/pass/return/exceptionLink/correct/batchPass)。 -
Step 2: 质控中心页(
defineOptions({name:'qc'})):三区:- 待我处理(五类推送聚合:每类计数 + 点击跳对应队列)。
- 待放行队列(record 列表 → 逐条放行/退回,退回选原因下拉;批量放行按钮,后端拦不可批量项)。
- 异常处理台(abnormal 列表 → 处理/转派/关闭 + 处理意见 + 「联动改计划」入口)。
-
Step 3: type-check + Commit
git -C sources/ec-admin add src/api/qc.ts src/pages/ltc/qc
git -C sources/ec-admin commit -m "feat(c4): 质控中心页"
Task 5: 端到端验证
- Step 1: 起后端 + 登录,冒烟:submitted 留痕 → 定时任务(或手动触发)生成 qc → 放行(4001 自审拒/通过)→ 退回 → 补正 → recheck → 放行。
- Step 2: 五类推送:异常/逾期/住院/计划/待放行各出现与消除。
- Step 3: 异常联动改计划生成新 plan + link_plan_change;审计落库。停后端清理。
Self-Review Notes
- Spec 覆盖:data-model 2 实体→T1;api-contract→T3;backend/qc→T3;frontend/ad/qc→T4。
- Review Focus 5 条均有测试(T3)。
- 类型一致:
Qc/RecordCorrection模型名、Permissions.qc.*、ErrorCodes.QC_*在 T1/T2/T3 一致。 - 定时任务用
prisma.raw跨租户(Review Focus #5)。