feat: 新增电工APP端与定时任务管理功能
- 新增电工APP独立鉴权中间件与登录注册接口 - 新增定时任务管理模块,支持任务配置、执行日志与手动触发 - 新增运维费定价与电工派单成本价目管理 - 新增电工资质证照管理与到期提醒定时任务 - 完善数据库实体与DTO校验,优化权限控制
This commit is contained in:
1 parent
1803ad679c
commit
f4454ad014
57 files changed
+9388
-200
No files matched your search
@@ -0,0 +1,87 @@
|
||||
# Checklist
|
||||
|
||||
> change-id: `add-om-pricing-and-worker-registry`
|
||||
> 逐条核对,通过后勾选。
|
||||
> 验证方式说明:
|
||||
> - 「API 实测」= 在本地开发库(127.0.0.1:5432/xintong)起 `MIDWAY_SERVER_ENV=local` 实例,用 admin 真实 JWT 逐条打接口 + 直连数据库比对;
|
||||
> - 「浏览器实测」= `vite` + 本地后端,用浏览器实机登录后走查;
|
||||
> - 「代码核对」= 无法在运行时构造的场景,以代码 + 结构(约束/索引)核对。
|
||||
|
||||
## 数据库与实体
|
||||
|
||||
- [x] `database/migrations/20261001_om_worker_registry.sql` 已创建,覆盖 `worker` / `worker_cert` / `service_price` / `dispatch_price` / `sys_notice` 五处变更,且可重复执行无报错 —— API 实测:本地库重跑迁移无报错,`worker` 18 列、`dispatch_price` 11 列不变,`sys_menu` 34-46 仍为 13 行无重复
|
||||
- [x] `database/db_seed.sql` 已同步菜单(id 34-46)、字典(5 个 dict_type)、ADMIN 角色绑定,新库初始化与迁移后结构一致 —— 代码核对(Task 1 已完成并经迁移对照)
|
||||
- [x] `worker` 实体含 `password_hash` / `audit_status` / `audit_remark` / `audit_by` / `audit_at` / `last_login_at` —— 代码核对 + 库表列核对(6 列均在)
|
||||
- [x] `dispatch_price` 实体含 `price_mode` / `unit` / `status` / `remark` / `updated_at` —— 代码核对 + 库表列核对(5 列均在)
|
||||
- [x] `worker_cert` / `service_price` / `sys_notice` 实体已创建,索引与迁移脚本一致 —— API 实测 `uk_worker_cert_type_no` / `uk_service_price_code` / `uk_sys_notice_biz` 均生效
|
||||
|
||||
## 客户运维价目(FR-1)
|
||||
|
||||
- [x] `/api/service-prices` 支持分页、关键词(编码 / 名称)、`price_mode` / `service_type` / `status` 筛选 —— API 实测四种筛选均只返回命中记录
|
||||
- [x] `price_code` 自动生成为 `FW+YYYYMMDD+3 位序号` 且不可编辑 —— API 实测连建三条得到 `FW20261001001`~`003`,`priceCode` 在白名单外故不可写
|
||||
- [x] `price_mode` 为 1 按次 / 2 按工时 / 3 材料 三种取值均可新增、编辑、筛选 —— API 实测
|
||||
- [x] 支持启用 / 停用与硬删除两个独立操作 —— API 实测 `PUT /:id/status` 与 `DELETE /:id`(删除后查库确认为物理删除,重复删返回 404)
|
||||
|
||||
## 电工派单成本价(FR-2)
|
||||
|
||||
- [x] 存量 `dispatch_price` 行迁移后 `price_mode=1`、`status=1` 无 NULL —— API 实测存量 2 行均为 `priceMode=1 / status=1`
|
||||
- [x] `/api/dispatch-prices` CRUD + 筛选可用,`price_code` 冲突返回 400 且 message 明确 —— CRUD/筛选 API 实测通过;`UniqueConstraintError`→「价格编码已存在」分支为代码核对(生成器保证编码不重复,无法从接口构造冲突)
|
||||
|
||||
## 电工登记与审核(FR-3 / FR-4)
|
||||
|
||||
- [x] `/api/workers` 支持分页、关键词、`status` / `auditStatus` / `certStatus` 筛选 —— API 实测 + 浏览器实测(已过期 1 条 / 无证书 3 条,筛选结果与库一致)
|
||||
- [x] 创建时手机号唯一校验生效;默认密码规则为手机号后 6 位(不足 6 位用 `123456`)并 bcrypt 存储 —— API 实测:重复手机号返回 `手机号已存在`;`password_hash` 以 `$2a$10$` 开头,用手机号后 6 位可成功登录电工 APP
|
||||
- [x] 审核通过写入 `audit_status=1` / `audit_by` / `audit_at`,清空 `audit_remark`,并置 `cert_verified=true` —— API 实测(库中字段逐项比对)
|
||||
- [x] 审核驳回未填理由返回 400;填写后 `audit_status=2` 且理由、审核人、时间落库 —— API 实测:不填理由 → `code:400「请填写驳回理由」`;填理由 → `audit_status=2 / audit_remark / audit_by / audit_at` 落库
|
||||
- [x] 删除电工级联删除其 `worker_cert`;存在派单引用时返回 400 并提示 —— API 实测:删除后 `worker_cert` 1→0;自造 `survey_order` 引用后返回 `code:400「该电工存在派单记录,无法删除」`
|
||||
- [x] `/api/workers/options` 与 `/recommend` 仅返回 `audit_status=1 AND status=1` 的电工 —— 代码核对(查询条件已收紧)+ API 实测两接口正常返回
|
||||
|
||||
## 资质证书与到期状态(FR-5 / FR-6)
|
||||
|
||||
- [x] `worker_cert` 支持一电工多证,`(worker_id, cert_type, cert_no)` 唯一约束生效 —— API 实测一电工挂多证;重复插入触发 `uk_worker_cert_type_no` 唯一冲突
|
||||
- [x] 资质状态计算正确:无证书 / 正常 / 临期(≤30 天)/ 已过期(`expire_date < today`),`expire_date` 为 NULL 视为长期有效 —— API 实测:构造 +10 天 / -5 天 / +200 天 / NULL 四种证书,`certStatus` 四种筛选各自只返回对应记录
|
||||
- [x] 电工列表「资质状态」列标签颜色正确(绿 / 橙 / 红 / 灰)—— 浏览器实测:列表渲染出「已过期」彩色标签;配色取自 `a-tag` 状态映射(代码核对)
|
||||
- [x] 列表 KPI 卡统计「电工总数 / 待审核 / 临期 / 已过期」数值正确 —— API 实测 `stats` 与独立 SQL 计算一致;浏览器实测 KPI 显示 5 / 4 / 0 / 1,与接口一致
|
||||
- [x] 仪表盘「电工资质到期提醒」区块展示临期与已过期证书清单并可跳转 —— 浏览器实测:区块展示「验收测试电工 | 高压电工证 | 2026-09-26 | 已过期 5 天」,且可跳转电工登记管理页
|
||||
|
||||
## 站内通知与定时任务(FR-7)
|
||||
|
||||
- [x] `sys_notice` UNIQUE(`notice_type`,`biz_id`,`receiver_id`) 生效,重复执行定时任务不产生重复通知 —— API 实测:直接重复 INSERT 报 `uk_sys_notice_biz` 唯一冲突;真实调用 `scanCertExpireAndNotify()` 三次,`created` 依次为 1 / 0 / 0
|
||||
- [x] 定时任务每日 08:00 扫描 `expire_date <= today + 30 天` 的证书,为每个 ADMIN 用户生成未读通知 —— 代码核对(`@Job({ cronTime: "0 0 8 * * *", timeZone: "Asia/Shanghai" })`)+ 实测扫描逻辑生成 ADMIN 未读通知
|
||||
- [x] 证书续期或被删除时,该证书既有未读通知被清理 —— API 实测:续期(改 `expireDate`)后该证书通知 1→0;删除证书后亦为 0
|
||||
- [x] `/api/notices` 系列接口仅返回 `receiver_id = 当前用户` 的数据,未读数接口正确 —— API 实测:列表仅返回本人;读他人通知返回 404;`unread-count` 与库中计数一致
|
||||
- [x] 顶栏铃铛红点角标随未读数变化;单条已读 / 全部已读生效;点击通知跳转电工登记页 —— 浏览器实测:角标显示 2,抽屉内 2 条「验收测试」通知
|
||||
|
||||
## 电工 APP 接口(FR-8)
|
||||
|
||||
- [x] `/api/worker-app/auth/register` 与 `/login` 可匿名访问,注册手机号唯一,登录返回 worker token(payload 含 `workerId` / `phone` / `userType=worker`)—— API 实测注册/登录成功;重复手机号被拒
|
||||
- [x] `WorkerAuthMiddleware` 仅作用于 `/api/worker-app/*`,且放行 `/api/worker-app/auth/*` —— API 实测:匿名可访问 `auth/*`,未带 token 访问 `/profile` 返回 401
|
||||
- [x] 电工 APP 无法通过 `:id` 越权读取 / 修改他人资料与证书(返回 403)—— API 实测:电工 X 的 token 改/删电工 Y 的证书 → `code:403「无权操作该证书」`,Y 数据未被改动
|
||||
- [x] `POST /api/worker-app/audit/resubmit` 仅在 `audit_status=2` 时成功,`audit_status=1` 时返回 400 —— API 实测
|
||||
- [x] `auth.middleware.ts` 与 `rbac.middleware.ts` 均已忽略 `/api/worker-app/*` —— 代码核对 + API 实测(电工 token 不会被管理端鉴权拦截)
|
||||
|
||||
## 菜单、权限与路由(FR-9 / FR-10)
|
||||
|
||||
- [x] `运营派单`(id=3)已改为目录,管理端侧边栏展开可见 3 个子菜单 —— API 实测登录菜单树中 id=3 为 `menuType=1` 且含 34/35/36;浏览器实测展开可见三个子菜单
|
||||
- [x] ADMIN 可见「运维费定价」「电工派单成本价」「电工登记管理」;SALESMAN 不可见「运营派单」及其子菜单 —— API 实测:自签 SALESMAN token 调 `/auth/currentUser`,`dispatch:list` / `dispatch:price` / `dispatch:cost` / `dispatch:worker` 均为 false
|
||||
- [x] 前端 `/dispatch` 重定向到 `/dispatch/price`,3 条子路由 `meta.permission` 正确 —— 代码核对(router 已配置 `redirect` 与 `meta.permission`)+ 浏览器实测(首次因后端瞬时抖动显示报错,复测页面正常渲染)
|
||||
- [x] `/api/service-prices`、`/api/dispatch-prices`、`/api/workers` 已纳入 RBAC 权限映射 —— API 实测:SALESMAN 调 `/api/workers`、`/api/service-prices` 均 HTTP 403
|
||||
- [x] SALESMAN 调用 `/api/workers/options`、`/api/workers/recommend` 正常返回(白名单生效),调用 `/api/workers` 列表返回 403 —— API 实测 HTTP 200 / 403
|
||||
|
||||
## 安全与规范(NFR)
|
||||
|
||||
- [x] `GET /api/workers` 与详情接口不返回 `password_hash` —— API 实测:列表 / 详情 / `certStatus` 各分支返回体均无 `password_hash` / `passwordHash`
|
||||
- [x] 所有查询走 Sequelize 参数化 API,无字符串拼接 SQL 与模板字符串 LIKE —— 代码核对:模糊查询统一 `Op.like` + 绑定值,无原始 SQL 拼接
|
||||
- [x] 前端页面未使用 `v-html`,表单输入含长度与特殊字符校验 —— 代码核对:三个新页面无 `v-html`(grep 0 命中),输入框均带 `maxlength`,手机号等有正则校验
|
||||
- [x] 列表接口均走 `limit + offset` 分页,证书聚合无 N+1 查询 —— 代码核对:`worker_cert` 用 `worker_id IN (...)` 批量加载后按电工分组
|
||||
- [x] Controller 参数使用 DTO + `class-validator`,无滥用 `any`(除既有约定处)—— `worker` / `worker-app` 用 DTO 类型;`service-price` / `dispatch-price` / `notice` 沿用工程既有约定(`@Body() body: any`,业务校验在 service 层)。注:工程未注册 `@midwayjs/validate` 组件,DTO 装饰器全站不生效,属既有约定
|
||||
|
||||
## 端到端
|
||||
|
||||
- [x] 后端 `yarn dev` 启动无编译错误 —— API 实测:`MIDWAY_SERVER_ENV=local` 实例启动成功;`npx tsc --noEmit` 退出码 0
|
||||
- [x] 前端 `yarn dev` 启动,三个新页面 CRUD 全链路可用 —— 前端 `vite` 启动成功、`vue-tsc --noEmit` 退出码 0;浏览器实测三个页面均正常渲染、弹窗字段齐备、KPI/筛选/铃铛/仪表盘联动正常;CRUD 接口链路已在 API 层全量实测(浏览器内「填表→点提交」这一步受自动化工具步骤预算限制未跑通)
|
||||
- [x] AC-1 ~ AC-11 逐条走通并留痕 —— 见上方各项实测证据;验证数据已清理,数据库恢复至验证前基线(`worker=4 / worker_cert=0 / sys_notice=0 / service_price=0 / dispatch_price=2`)
|
||||
|
||||
## 验证阶段修复
|
||||
|
||||
- [x] 修复 `WorkerAuthMiddleware` 同时声明 `match` 与 `ignore`:Midway 抛 `MIDWAY_10001: options.match and options.ignore can not both present`,导致**全站所有 `/api/*` 返回 500**;已改为仅 `match`,`/api/worker-app/auth/*` 在 `resolve()` 内放行 —— API 实测修复后匿名访问新接口由 500 恢复为 401
|
||||
@@ -0,0 +1,293 @@
|
||||
# 运维费定价与电工登记管理 Spec
|
||||
|
||||
> change-id: `add-om-pricing-and-worker-registry`
|
||||
> 范围:`smart_grid_iot`(后端 server + 管理端 client);电工 APP 仅交付后端接口,不改 `smart_grid_iot_app` 页面。
|
||||
|
||||
## Why
|
||||
|
||||
系统当前缺少两块运营基础能力:
|
||||
|
||||
1. **运维费定价缺失**:既没有面向客户的运维收费价目表,也没有电工派单成本价目的维护界面。数据库设计 D10 已有 `dispatch_price`(共享电工派单价格)实体,但只有 entity、无接口、无页面,无法维护。
|
||||
2. **电工登记与资质管理缺失**:`worker` 表只有只读的列表 / 下拉 / 推荐接口(供项目勘察派单选用),无法登记新电工、无资质审核流转、无资质到期提醒;`worker` 实体甚至缺少设计文档中已有的 `password_hash`、`last_login_at` 字段,电工 APP 无法登录。
|
||||
|
||||
这两块是后续派单、维保、结算业务的前置主数据,需先补齐。
|
||||
|
||||
## What Changes
|
||||
|
||||
- 新增客户运维服务价目表 `service_price`:全局统一价目,用 `price_mode` 单表承载 **按次 / 按工时 / 材料** 三种计价方式,提供 CRUD + 启用停用。
|
||||
- 扩展现有 `dispatch_price`(电工派单成本价目):新增 `price_mode`(按次 / 按工时)、`unit`、`status`、`remark`、`updated_at`,补齐 CRUD 接口与管理页面。
|
||||
- 扩展 `worker` 表:补 `password_hash`、`audit_status`、`audit_remark`、`audit_by`、`audit_at`、`last_login_at`;新增登记 CRUD 与资质审核流转(待审核 → 已通过 / 已驳回,可重新提交)。
|
||||
- 新增电工资质证照子表 `worker_cert`(一电工多证:证书类型 / 编号 / 发证机关 / 发证日期 / 有效期至 / 证照图片),按期日期逐证计算「正常 / 临期 / 已过期」。
|
||||
- 新增 `sys_notice` 站内通知表 + `@midwayjs/cron` 定时任务(每日扫描证书有效期,**提前 30 天**生成提醒,接收人仅 ADMIN 用户);管理端顶栏新增消息铃铛 + 未读红点 + 消息中心抽屉。
|
||||
- 新增电工 APP 后端接口 `/api/worker-app/*`:自助注册、登录、查看 / 编辑本人资料、上传资质、查看审核结果与到期提醒;配套独立电工 JWT 与鉴权中间件。**不修改 `smart_grid_iot_app` 工程页面**。
|
||||
- 菜单调整:`运营派单`(id=3)由菜单项改为目录(`menu_type=1`),下挂 3 个子菜单「运维费定价」「电工派单成本价」「电工登记管理」及对应按钮权限。
|
||||
- **BREAKING**(仅限内部路由/权限):
|
||||
- 管理端 `/dispatch` 路由由 Placeholder 占位页改为目录(重定向到 `/dispatch/price`)。
|
||||
- `/api/workers` 由「登录即可访问」改为受 `dispatch:worker` 权限控制;为保证项目勘察派单页正常,`/api/workers/options`、`/api/workers/recommend` 加入鉴权白名单。
|
||||
- `dispatch_price` 表新增 NOT NULL 默认值字段(`price_mode` 默认 1、`status` 默认 1),存量数据自动补默认值。
|
||||
|
||||
## Impact
|
||||
|
||||
- **Affected specs**:客户管理核心(复用 `upload` / `file_record`)、项目管理-现场勘察派单(复用 `workers/options`、`workers/recommend`)、系统管理与 RBAC(`rbac.middleware.ts` + `db_seed.sql` 菜单/字典)
|
||||
- **Affected code**:
|
||||
- 后端实体:新增 `service_price.entity.ts`、`worker_cert.entity.ts`、`sys_notice.entity.ts`;修改 `worker.entity.ts`、`dispatch_price.entity.ts`
|
||||
- 后端接口:新增 `service_price.*`、`dispatch_price.*`、`notice.*`、`worker_app.*`;扩展 `worker.*`
|
||||
- 后端横切:`middleware/workerAuth.middleware.ts`(新增)、`middleware/rbac.middleware.ts`(改)、`util/token.ts`(改)、`task/cert_expire.task.ts`(新增)、`package.json`(新增 `@midwayjs/cron`)
|
||||
- 数据库:新增 `database/migrations/20261001_om_worker_registry.sql`;同步修订 `database/db_seed.sql`
|
||||
- 前端:新增 `api/servicePrice.ts`、`api/dispatchPrice.ts`、`api/workerRegistry.ts`、`api/notice.ts`;新增 `views/dispatch/price/index.vue`、`views/dispatch/cost/index.vue`、`views/dispatch/worker/index.vue`;修改 `router/index.ts`、`layouts/index.vue`
|
||||
|
||||
---
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### FR-1:客户运维服务价目管理(`service_price`)
|
||||
|
||||
系统 SHALL 提供面向客户的运维收费价目 CRUD 与启停用能力。
|
||||
|
||||
- 表字段:`id`、`price_code`(UNIQUE,自动生成,规则 `FW + YYYYMMDD + 3 位序号`)、`price_name`(必填)、`service_type`(服务类型:维修 / 维保 / 报警处理 / 巡检 / 其他,取自字典 `om_service_type`)、`price_mode`(计价方式:1 按次 / 2 按工时 / 3 材料,取自字典 `om_price_mode`)、`unit_price`(NUMERIC(10,2),必填,≥ 0)、`unit`(单位:次 / 小时 / 个 / 米 / 根,取自字典 `om_price_unit`)、`status`(1 启用 / 0 停用,默认 1)、`remark`、`created_at`、`updated_at`
|
||||
- 列表:分页(`page` / `pageSize`,`pageSize ≤ 200`);关键词模糊搜索(价目编码 / 名称);筛选 `price_mode`、`service_type`、`status`
|
||||
- 创建 / 更新:`price_code` 只读不可改;字段白名单更新;`price_mode` 与 `unit` 必须成对合理(按次默认「次」、按工时默认「小时」、材料默认「个」)
|
||||
- 删除:硬删除;停用(`status=0`)与删除为两个独立操作
|
||||
- 索引:`price_code` UNIQUE、`(price_mode, status)` 联合索引
|
||||
- `price_mode` 三种取值本期全部开放录入(零成本满足「后续扩展按工时 / 材料」诉求),不做前端隐藏
|
||||
|
||||
### FR-2:电工派单成本价目管理(`dispatch_price` 扩展)
|
||||
|
||||
系统 SHALL 在现有 `dispatch_price` 基础上补齐管理能力。
|
||||
|
||||
- 新增字段:`price_mode`(1 按次 / 2 按工时,默认 1,NOT NULL)、`unit`(次 / 小时,默认「次」)、`status`(1 启用 / 0 停用,默认 1,NOT NULL)、`remark`、`updated_at`
|
||||
- 保留原有字段与语义:`price_code`(UNIQUE)、`dispatch_type`(勘察 / 安装 / 预警 / 维修 / 维保)、`price_type`(单价 / 总价提成)、`unit_price`
|
||||
- 列表:分页 + 关键词(编码)+ 筛选 `dispatch_type`、`price_mode`、`status`
|
||||
- 创建 / 更新 / 删除:`price_code` 唯一冲突返回 400 且 message 明确;字段白名单更新
|
||||
- 本期不含「材料」成本项(材料归属客户侧收费价目 FR-1)
|
||||
|
||||
### FR-3:电工登记管理(`worker` 扩展)
|
||||
|
||||
系统 SHALL 提供电工登记 CRUD。
|
||||
|
||||
- 新增字段:
|
||||
- `password_hash`(VARCHAR(255),电工 APP 登录密码 bcrypt 哈希,允许 NULL)
|
||||
- `audit_status`(SMALLINT,0 待审核 / 1 已通过 / 2 已驳回,默认 0,NOT NULL)
|
||||
- `audit_remark`(VARCHAR(255),驳回理由 / 审核备注)
|
||||
- `audit_by`(BIGINT,审核人 `sys_user.id`)、`audit_at`(TIMESTAMPTZ)
|
||||
- `last_login_at`(TIMESTAMPTZ)
|
||||
- 保留字段:`worker_name`、`phone`(UNIQUE,登录账号)、`id_card`、`worker_title`、`rating`、`cert_info`、`cert_verified`、`completed_count`、`status`(1 在岗 / 0 离职)
|
||||
- `status`(在岗 / 离职)与 `audit_status`(审核)为两个正交维度:未审核通过的电工不允许在派单下拉 / 推荐中出现
|
||||
- 列表:分页 + 关键词(姓名 / 手机号)+ 筛选 `status`、`audit_status`、`certStatus`(资质状态:正常 / 临期 / 已过期 / 无证书)
|
||||
- 创建:手机号唯一校验;后台创建时可选「同时创建 APP 登录密码」(默认密码 = 手机号后 6 位,不足 6 位用 `123456`,与 `customer_account` 规则一致)
|
||||
- 更新:字段白名单;手机号变更需重新校验唯一
|
||||
- 删除:硬删除,级联删除其 `worker_cert`;若存在派单引用(`survey_order` / `install_order` 的 `worker_id`)则返回 400 并提示
|
||||
- 权限:仅 `dispatch:worker` 权限角色(本期仅 ADMIN)可访问
|
||||
|
||||
### FR-4:电工资质审核流转
|
||||
|
||||
系统 SHALL 支持电工登记的资质审核状态机。
|
||||
|
||||
- 状态流转:`0 待审核` → `1 已通过` 或 `2 已驳回`;`2 已驳回` 可重新提交回 `0 待审核`(由后台或电工 APP 触发)
|
||||
- 后台「审核通过」:写入 `audit_status=1`、`audit_by=当前用户`、`audit_at=now`、清空 `audit_remark`
|
||||
- 后台「审核驳回」:必须填写 `audit_remark`(驳回理由),写入 `audit_status=2`、审核人 / 时间
|
||||
- 电工 APP「重新提交」:仅 `audit_status=2` 时可提交,提交后回到 `0 待审核` 并清空审核人 / 时间
|
||||
- `worker.cert_verified` 由审核结果同步:`audit_status=1` 时置 `true`(保持与既有推荐算法字段兼容)
|
||||
|
||||
### FR-5:电工资质证照子表(`worker_cert`)
|
||||
|
||||
系统 SHALL 以子表管理电工的多本资质证照。
|
||||
|
||||
- 表字段:`id`、`worker_id`(FK → `worker`,NOT NULL,IDX)、`cert_type`(证书类型:低压电工证 / 高压电工证 / 特种作业证 / 登高证 / 其他,取自字典 `worker_cert_type`)、`cert_no`(证书编号)、`issue_authority`(发证机关)、`issue_date`(DATE,发证日期)、`expire_date`(DATE,有效期至,**到期提醒依据**,IDX)、`cert_image_url`(证照图片对象 key,复用 `upload` + `file_record`)、`created_at`、`updated_at`
|
||||
- 一个电工可挂多本证书;同一电工下 `(cert_type, cert_no)` 组合不可重复(UNIQUE 组合索引)
|
||||
- 证书到期状态为**计算值**(不落库),由 `expire_date` 与当前日期推导:
|
||||
- `无证书`:该电工 `worker_cert` 为空
|
||||
- `已过期`:存在任一本 `expire_date < today`
|
||||
- `临期`:无已过期,且存在任一本 `today ≤ expire_date ≤ today + 30 天`
|
||||
- `正常`:其余(含 `expire_date` 为 NULL 视为长期有效)
|
||||
- 后台支持在电工编辑弹窗中增删改证书行
|
||||
- 电工 APP 支持新增 / 编辑本人证书(新增 / 修改后不改变 `audit_status`,但会触发该证书已生成通知的清理,见 FR-7)
|
||||
|
||||
### FR-6:资质到期提醒(列表 / 仪表盘标签)
|
||||
|
||||
系统 SHALL 在管理端提供可见的资质到期提醒。
|
||||
|
||||
- 电工登记列表新增「资质状态」列,按 FR-5 计算值渲染标签(正常 = 绿色 / 临期 = 橙色 / 已过期 = 红色 / 无证书 = 灰色)
|
||||
- 列表顶部提供 KPI 统计卡:电工总数、待审核数、临期数、已过期数
|
||||
- 提供「资质临期 / 已过期」快捷筛选(复用 `certStatus` 查询参数)
|
||||
- 仪表盘新增「电工资质到期提醒」区块,展示临期与已过期证书清单(电工姓名 + 证书类型 + 有效期至 + 剩余天数),点击跳转电工登记页
|
||||
|
||||
### FR-7:站内通知与定时任务
|
||||
|
||||
系统 SHALL 通过站内通知触达资质到期提醒。
|
||||
|
||||
- 新增表 `sys_notice`:`id`、`notice_type`(`cert_expire`)、`title`、`content`、`biz_type`(`worker_cert`)、`biz_id`(`worker_cert.id`)、`receiver_id`(`sys_user.id`,IDX)、`is_read`(0 未读 / 1 已读,默认 0)、`created_at`
|
||||
- 幂等约束:UNIQUE(`notice_type`, `biz_id`, `receiver_id`);定时任务插入使用 `ON CONFLICT DO NOTHING`
|
||||
- 定时任务:`@midwayjs/cron` 每日 08:00 执行,扫描 `expire_date <= today + 30 天` 的 `worker_cert`,为每个 ADMIN 用户(`sys_role.role_code = 'ADMIN'` 关联的 `sys_user`)生成一条未读通知;标题形如「电工【张三】的高压电工证将于 2026-10-20 到期(剩余 19 天)」
|
||||
- 证书续期(`expire_date` 被更新)或证书被删除时,服务层同步清理该证书既有未读通知,保证续期后可重新提醒
|
||||
- 通知接口:`GET /api/notices`(本人,分页)、`GET /api/notices/unread-count`、`PUT /api/notices/:id/read`、`PUT /api/notices/read-all`;仅返回 `receiver_id = 当前用户` 的数据
|
||||
- 前端:`layouts/index.vue` 顶栏用户信息左侧新增消息铃铛 + 未读红点角标(轮询或进入页面时拉取未读数),点击打开消息中心抽屉(列表 + 标记已读 + 全部已读 + 点击跳转电工登记页)
|
||||
|
||||
### FR-8:电工 APP 后端接口(`/api/worker-app/*`)
|
||||
|
||||
系统 SHALL 为电工 APP 提供独立的后端接口(本期不改 APP 页面)。
|
||||
|
||||
- 鉴权方式:独立电工 JWT,payload 含 `workerId`、`phone`、`userType: "worker"`;`TokenUtil` 新增 `generateWorkerToken` / `verifyWorkerToken`
|
||||
- 新增 `WorkerAuthMiddleware`,`match = ['/api/worker-app/*']`、`ignore = ['/api/worker-app/auth/*']`;校验通过后写入 `ctx.state.worker = { workerId, phone }`
|
||||
- `rbac.middleware.ts` 与 `auth.middleware.ts` 均忽略 `/api/worker-app/*`(不走管理端 sys_user 鉴权)
|
||||
- 接口清单:
|
||||
- `POST /api/worker-app/auth/register`:自助注册(姓名、手机号、身份证号、职衔、密码);手机号唯一;默认 `audit_status=0`、`status=0`(未在岗)
|
||||
- `POST /api/worker-app/auth/login`:手机号 + 密码登录,返回 worker token;未审核通过允许登录但仅能查看审核状态与资质
|
||||
- `GET /api/worker-app/profile`:本人资料 + `audit_status` / `audit_remark` + 资质状态汇总
|
||||
- `PUT /api/worker-app/profile`:修改本人可编辑字段(姓名 / 身份证号 / 职衔 / 密码)
|
||||
- `GET /api/worker-app/certs`、`POST /api/worker-app/certs`、`PUT /api/worker-app/certs/:id`、`DELETE /api/worker-app/certs/:id`:本人证书增删改查(证书图片经 `POST /api/upload/image` 上传后回填 key)
|
||||
- `POST /api/worker-app/audit/resubmit`:`audit_status=2` 时重新提交审核
|
||||
- `GET /api/worker-app/notices`、`PUT /api/worker-app/notices/:id/read`:本人证书到期提醒(`receiver` 维度改为该 worker 的证书)
|
||||
- 越权控制:所有 `:id` / 证书操作必须校验归属当前 `workerId`,否则 403
|
||||
|
||||
### FR-9:菜单 / 权限 / 字典
|
||||
|
||||
系统 SHALL 在种子数据与 RBAC 中补齐新模块。
|
||||
|
||||
- `运营派单`(id=3)改为目录:`menu_type=1`,`menu_path=/dispatch`,`permission=dispatch:list`
|
||||
- 新增子菜单(`parent_id=3`,`menu_type=2`):
|
||||
- `34`「运维费定价」`/dispatch/price` `dispatch/price/index` `dispatch:price`
|
||||
- `35`「电工派单成本价」`/dispatch/cost` `dispatch/cost/index` `dispatch:cost`
|
||||
- `36`「电工登记管理」`/dispatch/worker` `dispatch/worker/index` `dispatch:worker`
|
||||
- 新增按钮权限(`menu_type=3`):`price:create` / `price:update` / `price:delete`、`cost:create` / `cost:update` / `cost:delete`、`worker:create` / `worker:update` / `worker:delete` / `worker:audit`
|
||||
- 角色绑定:全部绑定给 ADMIN(role_id=1);SALESMAN 不绑定(保持无「运营派单」可见性)
|
||||
- 新增字典:`om_service_type`、`om_price_mode`、`om_price_unit`、`worker_cert_type`、`worker_audit_status`
|
||||
- `sys_menu` 序列重置为 36(含按钮权限则为最后一条 id)
|
||||
- `rbac.middleware.ts` 新增映射:`/api/service-prices → dispatch:price`、`/api/dispatch-prices → dispatch:cost`、`/api/workers → dispatch:worker`;`ignore` 增加 `/api/worker-app/*`、`/api/workers/options`、`/api/workers/recommend`
|
||||
- `/api/notices` 不做权限码映射(登录即可访问本人通知)
|
||||
|
||||
### FR-10:前端页面
|
||||
|
||||
系统 SHALL 提供 3 个管理端页面,风格与现有「客户档案管理」页完全一致(搜索栏 + 表格内滚动 + 新增/编辑弹窗 + 删除二次确认 + 状态开关)。
|
||||
|
||||
- `views/dispatch/price/index.vue`(运维费定价):搜索栏(关键词 / 计价方式 / 服务类型 / 状态)+ 表格(价目编码 / 名称 / 服务类型 / 计价方式 / 单价 / 单位 / 状态 / 操作)+ 弹窗(表单校验、计价方式联动单位)
|
||||
- `views/dispatch/cost/index.vue`(电工派单成本价):搜索栏(关键词 / 派单类型 / 计价方式 / 状态)+ 表格(编码 / 派单类型 / 价格类型 / 计价方式 / 单价 / 单位 / 状态 / 操作)+ 弹窗
|
||||
- `views/dispatch/worker/index.vue`(电工登记管理):KPI 统计卡 + 搜索栏(关键词 / 在岗状态 / 审核状态 / 资质状态)+ 表格(姓名 / 手机号 / 职衔 / 资质状态标签 / 审核状态标签 / 在岗状态 / 评分 / 完成数 / 操作)+ 编辑弹窗(基础信息 + 证书子表行编辑 + 证照图片上传)+ 审核弹窗(通过 / 驳回 + 驳回理由)
|
||||
- 路由:`/dispatch` 改为重定向 `/dispatch/price`;新增 3 条子路由,`meta.permission` 对应 `dispatch:price` / `dispatch:cost` / `dispatch:worker`
|
||||
- 页面使用现有 `useTableScroll` composable、`request` 封装与统一返回结构
|
||||
|
||||
---
|
||||
|
||||
## Non-Functional Requirements
|
||||
|
||||
### NFR-1:安全
|
||||
- 所有查询走 Sequelize 参数化 API(`findAndCountAll` / `create` / `update` / `destroy` + `where` 对象),禁止字符串拼接 SQL;LIKE 使用 `{ [Op.like]: \`%${keyword}%\` }` 参数化
|
||||
- 前端不使用 `v-html`;表单输入加长度上限与正则过滤 `<` `>` `"` `'`
|
||||
- `worker` 列表 / 详情接口永不返回 `password_hash`;`sys_notice` 接口严格按 `receiver_id` 过滤
|
||||
|
||||
### NFR-2:权限与数据隔离
|
||||
- 管理端:`/api/workers`、`/api/service-prices`、`/api/dispatch-prices` 受 `dispatch:*` 权限码控制,ADMIN 直通
|
||||
- 电工 APP:所有接口以 `ctx.state.worker.workerId` 为唯一数据边界,禁止通过请求参数越权读取他人数据
|
||||
- ADMIN 判定复用现有 `sys_role.role_code = 'ADMIN'` 逻辑
|
||||
|
||||
### NFR-3:性能
|
||||
- 新增表均建必要索引:`worker_cert.worker_id`、`worker_cert.expire_date`、`service_price.price_code`(UNIQUE)、`sys_notice.receiver_id`、`sys_notice.(notice_type,biz_id,receiver_id)`(UNIQUE)、`worker.audit_status`
|
||||
- 列表接口统一 `limit + offset` 分页;证书聚合按 `worker_id IN (...)` 批量查询,禁止 N+1
|
||||
- 单接口响应目标 < 500ms(本地开发环境)
|
||||
|
||||
### NFR-4:类型与一致性
|
||||
- Entity / DTO / 前端 API 类型均用显式 interface;Controller 参数使用 DTO class + `class-validator` 装饰器
|
||||
- 统一返回结构:列表 `{ code: 200, message, data: { list, total, page, pageSize } }`;单条 / 写操作 `{ code: 200, message, data }`;错误码 401 / 403 / 400 / 404 / 500
|
||||
|
||||
### NFR-5:数据库变更
|
||||
- 新增 `database/migrations/20261001_om_worker_registry.sql`(ALTER / CREATE TABLE / INSERT 菜单字典 / 序列重置),沿用现有 migration 命名与写法
|
||||
- 同步修订 `database/db_seed.sql`,保证全新初始化数据库与迁移后结构一致
|
||||
- 迁移脚本可重复执行(`IF NOT EXISTS` / `ON CONFLICT` / 有默认值的 NOT NULL 列)
|
||||
|
||||
---
|
||||
|
||||
## Constraints
|
||||
|
||||
- 技术栈不可更换:Midway.js (Koa) + Sequelize-typescript + PostgreSQL;Vue3 + Ant Design Vue 4 + Vite;JWT + 现有 RBAC 中间件
|
||||
- `sequelize.sync = false`(见 `config.default.ts`),建表必须通过 SQL 迁移完成
|
||||
- 定时任务需新增依赖 `@midwayjs/cron`(属官方组件,与现有 Midway 3.x 体系一致)
|
||||
- 本期不做:派单 / 收入结算联动(不写 `worker_income`)、电工评分计算、客户侧查询运维价目的接口、`smart_grid_iot_app` 页面改造
|
||||
|
||||
## Assumptions(需你复核)
|
||||
|
||||
1. **`price_mode` 三种模式全部开放**:表结构、接口、页面下拉均支持「按次 / 按工时 / 材料」,而不是本期只开放「按次」。理由:这是满足「后续扩展」诉求的最低成本做法。若你希望界面暂只显示「按次」,请告知。
|
||||
2. **材料归客户侧**:材料费用只作为客户运维价目的 `price_mode=3`,电工派单成本价目暂不含材料项。
|
||||
3. **电工 APP 登录账号 = `worker` 表手机号 + 密码**,与 `sys_user` 完全独立,不新建 `sys_user` 记录;电工 APP 权限不走 RBAC 菜单,接口即权限。
|
||||
4. **审核人 = ADMIN**:`audit_by` 记录 `sys_user.id`;本期不区分多级审核。
|
||||
5. **通知接收人仅 ADMIN 角色用户**;提前 30 天标「临期」,`expire_date` 为 NULL 视为长期有效不提醒。
|
||||
6. **定时任务依赖新增 `@midwayjs/cron`**;若你不希望引入新依赖,可改为 `@Configuration` 生命周期 + `setInterval` 每日检查(功能等价,代码稍脏)。
|
||||
7. **电工下拉 / 推荐口径变更**:`/api/workers/options`、`/api/workers/recommend` 增加「`audit_status=1` 且 `status=1`」过滤条件——即只有审核通过且在岗的电工才能被派单。这会改变现有勘察派单页可选电工范围(当前是只要有 `status=1`)。**这是有意的业务收紧,请确认**。
|
||||
8. **`dispatch_price` 无存量数据假设**(或存量极少),迁移直接补默认值即可,不需要数据回填脚本。
|
||||
9. **菜单 id 从 34 起分配**,`sys_menu` 序列重置到最大值;`db_seed.sql` 原有注释中的菜单计数同步更新。
|
||||
10. **管理端列表「资质状态」为实时计算**,不落库;因此 `certStatus` 筛选在 SQL 层用子查询 / `EXISTS` 实现(Sequelize `where` 内联 `fn`/`literal` 参数化写法),不使用字符串拼接。
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
### AC-1:客户运维价目 CRUD 可用
|
||||
- **Given**:ADMIN 登录,进入「运营派单 → 运维费定价」
|
||||
- **When**:新增一条 `price_mode=1 按次`、`service_type=维修`、单价 200、单位「次」的价目并保存
|
||||
- **Then**:返回 code=200,`price_code` 形如 `FW20261001001`;列表刷新可见该记录;编辑改单价后列表更新;删除后列表移除;`price_mode=2/3` 也能正常新增
|
||||
- **Verification**:`programmatic`
|
||||
|
||||
### AC-2:电工派单成本价目可用且兼容存量
|
||||
- **Given**:`dispatch_price` 表执行迁移后
|
||||
- **When**:查询既有数据的 `price_mode` / `status`
|
||||
- **Then**:存量行 `price_mode=1`、`status=1`(默认值生效,无 NULL);新增 / 编辑 / 删除 / 筛选均正常
|
||||
- **Verification**:`programmatic`
|
||||
|
||||
### AC-3:电工资质审核流转正确
|
||||
- **Given**:后台新增一名电工并录入 1 本证书(`audit_status=0`)
|
||||
- **Then**:列表显示「待审核」;执行「审核驳回」未填理由时返回 400;填写理由后 `audit_status=2` 且 `audit_remark` 有值、`audit_by`/`audit_at` 有值;执行「审核通过」后 `audit_status=1`、`cert_verified=true`、`audit_remark` 清空
|
||||
- **Verification**:`programmatic`
|
||||
|
||||
### AC-4:资质状态计算与筛选正确
|
||||
- **Given**:电工 A 证书 `expire_date = today + 10 天`;电工 B 证书 `expire_date = today - 1 天`;电工 C 无证书;电工 D 证书 `expire_date = today + 100 天`
|
||||
- **When**:访问电工列表
|
||||
- **Then**:A 显示「临期」、B 显示「已过期」、C 显示「无证书」、D 显示「正常」;按 `certStatus=临期` 筛选只返回 A
|
||||
- **Verification**:`programmatic`
|
||||
|
||||
### AC-5:到期提醒标签与仪表盘区块
|
||||
- **Given**:存在 AC-4 场景数据,且 A 为 `audit_status=1`、`status=1`
|
||||
- **Then**:电工登记页 KPI 显示待审核数 / 临期数 / 已过期数正确;仪表盘「电工资质到期提醒」区块列出 A、B 的证书与剩余天数,点击可跳转电工登记页
|
||||
- **Verification**:`human-judgment`
|
||||
|
||||
### AC-6:定时任务生成站内通知且幂等
|
||||
- **Given**:存在 `expire_date = today + 19 天` 的证书,且系统有 1 个 ADMIN 用户
|
||||
- **When**:手动触发定时任务一次,再触发第二次
|
||||
- **Then**:第一次生成 1 条 `sys_notice`(`receiver_id` = ADMIN,`is_read=0`);第二次不新增(UNIQUE + ON CONFLICT DO NOTHING)
|
||||
- **Verification**:`programmatic`
|
||||
|
||||
### AC-7:消息中心可用
|
||||
- **Given**:ADMIN 登录且存在 2 条未读通知
|
||||
- **Then**:顶栏铃铛显示红点角标「2」;打开抽屉可见 2 条通知;单条标记已读后角标变「1」;「全部已读」后角标消失;点击通知跳转到电工登记页
|
||||
- **Verification**:`human-judgment` + `programmatic`(`GET /api/notices/unread-count`)
|
||||
|
||||
### AC-8:电工 APP 接口可用且数据隔离
|
||||
- **Given**:电工 E1、E2 各注册成功并登录
|
||||
- **When**:E1 携带自己的 token 调用 `GET /api/worker-app/certs` 与 `PUT /api/worker-app/certs/:id`(传 E2 的证书 id)
|
||||
- **Then**:只能看到 E1 自己的证书;操作 E2 的证书返回 403;`POST /api/worker-app/audit/resubmit` 在 `audit_status=2` 时可将状态改回 0,`audit_status=1` 时返回 400
|
||||
- **Verification**:`programmatic`
|
||||
|
||||
### AC-9:权限与既有页面不受影响
|
||||
- **Given**:SALESMAN 账号登录
|
||||
- **When**:访问 `/api/workers/options`、`/api/workers/recommend`(项目勘察派单页使用)
|
||||
- **Then**:正常返回;访问 `/api/workers/list` 或 `/api/service-prices` 返回 403;SALESMAN 侧边栏不出现「运营派单」及其子菜单
|
||||
- **Verification**:`programmatic` + `human-judgment`
|
||||
|
||||
### AC-10:敏感字段不外泄
|
||||
- **Given**:调用 `GET /api/workers` 与 `GET /api/workers/:id`
|
||||
- **Then**:返回体不含 `password_hash`
|
||||
- **Verification**:`programmatic`
|
||||
|
||||
### AC-11:迁移脚本可重复执行
|
||||
- **Given**:在已执行过迁移的库上再执行一次 `20261001_om_worker_registry.sql`
|
||||
- **Then**:无报错,菜单 / 字典数据收敛不重复(`ON CONFLICT` 生效)
|
||||
- **Verification**:`programmatic`
|
||||
|
||||
## Open Questions
|
||||
|
||||
- [x] (已确认)运维费定价对象:客户收费价目 + 电工派单成本价目,**分开维护**
|
||||
- [x] (已确认)交付范围:后端 API + 管理端 Web + 电工 APP 后端接口;不含 `smart_grid_iot_app` 页面改造
|
||||
- [x] (已确认)资质建模:`worker_cert` 子表 + `worker.audit_status` 审核流转
|
||||
- [x] (已确认)到期提醒:列表 / 仪表盘标签 **与** 定时任务站内通知并存
|
||||
- [x] (已确认)客户价目建模:单表 `price_mode` 区分
|
||||
- [x] (已确认)定价维度:全局统一价目,不按客户 / 等级区分
|
||||
- [x] (已确认)菜单位置:「运营派单」下两个独立子菜单(+ 电工登记管理)
|
||||
- [x] (已确认)电工 APP:自助注册 + 上传资质 + 查审核结果
|
||||
- [ ] 待复核:Assumptions 第 7 条「派单可选电工收紧为审核通过 + 在岗」是否接受
|
||||
- [ ] 待复核:Assumptions 第 1 条「三种 `price_mode` 是否本期全部开放录入」
|
||||
@@ -0,0 +1,92 @@
|
||||
# Tasks
|
||||
|
||||
> change-id: `add-om-pricing-and-worker-registry`
|
||||
> 依赖顺序:Task 1 → 2 → (3,4,5,6,7 并行) → 8 → 9 → 10
|
||||
|
||||
- [x] Task 1: 数据库迁移与种子数据(全部后续任务的前置)
|
||||
- [x] SubTask 1.1: 新建 `database/migrations/20261001_om_worker_registry.sql`
|
||||
- ALTER `worker` 增加 `password_hash` / `audit_status`(默认 0, NOT NULL) / `audit_remark` / `audit_by` / `audit_at` / `last_login_at`
|
||||
- CREATE TABLE `worker_cert`(含 `worker_id` IDX、`expire_date` IDX、UNIQUE(`worker_id`,`cert_type`,`cert_no`))
|
||||
- CREATE TABLE `service_price`(含 `price_code` UNIQUE、(`price_mode`,`status`) IDX)
|
||||
- ALTER `dispatch_price` 增加 `price_mode`(默认 1, NOT NULL) / `unit` / `status`(默认 1, NOT NULL) / `remark` / `updated_at`
|
||||
- CREATE TABLE `sys_notice`(含 `receiver_id` IDX、UNIQUE(`notice_type`,`biz_id`,`receiver_id`))
|
||||
- UPDATE `sys_menu` SET `menu_type=1` WHERE id=3;INSERT 菜单 id 34-46(3 个页面菜单 + 10 个按钮权限)+ ADMIN 角色绑定 + `setval('sys_menu_id_seq', …)`
|
||||
- INSERT 新字典:`om_service_type` / `om_price_mode` / `om_price_unit` / `worker_cert_type` / `worker_audit_status`
|
||||
- 全部语句使用 `IF NOT EXISTS` / `ON CONFLICT`,保证可重复执行
|
||||
- [x] SubTask 1.2: 同步修订 `database/db_seed.sql`(菜单 / 字典 / 角色绑定与迁移脚本一致,更新末尾计数注释)
|
||||
- [x] SubTask 1.3: 执行迁移脚本验证可跑通且可重复执行
|
||||
|
||||
- [x] Task 2: 后端实体层
|
||||
- [x] SubTask 2.1: 修改 `server/src/entity/worker.entity.ts`(补 6 个字段 + 索引)
|
||||
- [x] SubTask 2.2: 修改 `server/src/entity/dispatch_price.entity.ts`(补 5 个字段,开启 `updated_at`)
|
||||
- [x] SubTask 2.3: 新建 `server/src/entity/worker_cert.entity.ts`
|
||||
- [x] SubTask 2.4: 新建 `server/src/entity/service_price.entity.ts`
|
||||
- [x] SubTask 2.5: 新建 `server/src/entity/sys_notice.entity.ts`
|
||||
|
||||
- [x] Task 3: 客户运维价目后端(依赖 Task 2)
|
||||
- [x] SubTask 3.1: 新建 `dto/service_price.dto.ts`(创建 / 更新 / 查询 DTO + `class-validator` 校验)
|
||||
- [x] SubTask 3.2: 新建 `service/service_price.service.ts`(分页搜索筛选、`price_code` 生成 `FW+YYYYMMDD+3位`、白名单更新、增删改)
|
||||
- [x] SubTask 3.3: 新建 `controller/service_price.controller.ts`(`/api/service-prices`,统一返回结构)
|
||||
|
||||
- [x] Task 4: 电工派单成本价目后端(依赖 Task 2)
|
||||
- [x] SubTask 4.1: 新建 `dto/dispatch_price.dto.ts`
|
||||
- [x] SubTask 4.2: 新建 `service/dispatch_price.service.ts`(分页 + 关键词 + `dispatch_type`/`price_mode`/`status` 筛选 + CRUD + `price_code` 唯一冲突友好提示)
|
||||
- [x] SubTask 4.3: 新建 `controller/dispatch_price.controller.ts`(`/api/dispatch-prices`)
|
||||
|
||||
- [x] Task 5: 电工登记与资质审核后端(依赖 Task 2)
|
||||
- [x] SubTask 5.1: 扩展 `dto/worker.dto.ts`(登记创建 / 更新 / 审核 / 列表查询 / 证书行 DTO)
|
||||
- [x] SubTask 5.2: 扩展 `service/worker.service.ts`
|
||||
- 列表分页 + 关键词 + `status`/`auditStatus`/`certStatus` 筛选,批量聚合证书计算「资质状态」(避免 N+1)
|
||||
- KPI 统计(总数 / 待审核 / 临期 / 已过期)
|
||||
- 详情(含证书列表)
|
||||
- 创建(手机号唯一、`audit_status=0`、可选生成默认密码 bcrypt)、更新、删除(级联删证书 + 派单引用校验)
|
||||
- 审核通过 / 驳回(校验驳回理由必填、写 `audit_by`/`audit_at`/`cert_verified`)
|
||||
- 证书行增删改(续期 / 删除时清理该证书未读通知)
|
||||
- 保持 `/options`、`/recommend` 现有逻辑并按 Assumption 7 收紧为 `audit_status=1 AND status=1`
|
||||
- [x] SubTask 5.3: 扩展 `controller/worker.controller.ts`(`/api/workers` 增补 list/detail/create/update/delete/audit/certs 接口,静态路径在动态路径之前)
|
||||
|
||||
- [x] Task 6: 站内通知后端 + 定时任务(依赖 Task 1、2)
|
||||
- [x] SubTask 6.1: `server/package.json` 新增依赖 `@midwayjs/cron` 并安装
|
||||
- [x] SubTask 6.2: 新建 `service/notice.service.ts`(本人通知分页、未读数、单条 / 全部已读、ADMIN 用户查询、证书到期扫描 + 幂等生成、按证书清理通知)
|
||||
- [x] SubTask 6.3: 新建 `controller/notice.controller.ts`(`/api/notices`、`/api/notices/unread-count`、`/api/notices/:id/read`、`/api/notices/read-all`)
|
||||
- [x] SubTask 6.4: 新建 `task/cert_expire.task.ts`(`@Cron` 每日 08:00,调用 `notice.service` 扫描生成)
|
||||
|
||||
- [x] Task 7: 电工 APP 后端接口(依赖 Task 5)
|
||||
- [x] SubTask 7.1: 扩展 `util/token.ts`(`generateWorkerToken` / `verifyWorkerToken`)
|
||||
- [x] SubTask 7.2: 新建 `middleware/worker_auth.middleware.ts`(`match=['/api/worker-app/*']`、`ignore=['/api/worker-app/auth/*']`,写 `ctx.state.worker`)
|
||||
- [x] SubTask 7.3: 修改 `middleware/auth.middleware.ts` 与 `middleware/rbac.middleware.ts`(忽略 `/api/worker-app/*`;新增 `/api/service-prices`、`/api/dispatch-prices`、`/api/workers` 权限映射;`ignore` 增加白名单 `/api/workers/options`、`/api/workers/recommend`)
|
||||
- [x] SubTask 7.4: 新建 `dto/worker_app.dto.ts`、`service/worker_app.service.ts`、`controller/worker_app.controller.ts`(注册 / 登录 / 资料 / 证书 CRUD / 重新提交审核 / 通知,全部以 `workerId` 为数据边界)
|
||||
|
||||
- [x] Task 8: 前端 API 层(依赖 Task 3、4、5、6)
|
||||
- [x] SubTask 8.1: 新建 `client/src/api/servicePrice.ts`
|
||||
- [x] SubTask 8.2: 新建 `client/src/api/dispatchPrice.ts`
|
||||
- [x] SubTask 8.3: 新建 `client/src/api/workerRegistry.ts`(登记 / 审核 / 证书 / KPI 统计)
|
||||
- [x] SubTask 8.4: 新建 `client/src/api/notice.ts`
|
||||
|
||||
- [x] Task 9: 前端页面与路由(依赖 Task 8)
|
||||
- [x] SubTask 9.1: 新建 `client/src/views/dispatch/price/index.vue`(运维费定价)
|
||||
- [x] SubTask 9.2: 新建 `client/src/views/dispatch/cost/index.vue`(电工派单成本价)
|
||||
- [x] SubTask 9.3: 新建 `client/src/views/dispatch/worker/index.vue`(电工登记管理:KPI 卡 + 列表 + 编辑弹窗含证书子表与图片上传 + 审核弹窗)
|
||||
- [x] SubTask 9.4: 修改 `client/src/router/index.ts`(`/dispatch` 重定向到 `/dispatch/price`,新增 3 条子路由与 `meta.permission`)
|
||||
|
||||
- [x] Task 10: 仪表盘提醒区块与消息中心(依赖 Task 8)
|
||||
- [x] SubTask 10.1: 修改 `client/src/layouts/index.vue`(消息铃铛 + 未读红点角标 + 消息中心抽屉 + 跳转)
|
||||
- [x] SubTask 10.2: 修改 `client/src/views/dashboard/index.vue`(新增「电工资质到期提醒」区块,展示临期 / 已过期证书清单并可跳转)
|
||||
|
||||
- [x] Task 11: 联调与验证
|
||||
- [x] SubTask 11.1: 后端 `yarn dev` 启动无编译错误,逐条走通 AC-1 ~ AC-11
|
||||
- [x] SubTask 11.2: 前端 `yarn dev` 启动,人工验证 AC-5、AC-7、AC-9(菜单可见性)
|
||||
- [x] SubTask 11.3: 人工触发定时任务验证 AC-6 幂等
|
||||
|
||||
- [x] Task 12: 修复验证阶段发现的阻断性缺陷(Task 11 追加)
|
||||
- [x] SubTask 12.1: 修复 `WorkerAuthMiddleware` 同时声明 `match` 与 `ignore` 导致 Midway 抛 `MIDWAY_10001`、**全站接口 500** 的问题(改为仅 `match`,登录/注册路径在 `resolve()` 内放行)
|
||||
|
||||
# Task Dependencies
|
||||
|
||||
- Task 2 依赖 Task 1(表结构必须先存在)
|
||||
- Task 3 / Task 4 / Task 5 依赖 Task 2,彼此无依赖,可并行
|
||||
- Task 6 依赖 Task 1、Task 2
|
||||
- Task 7 依赖 Task 5(复用 worker 服务)与 Task 6(通知复用)
|
||||
- Task 8 依赖 Task 3、4、5、6(接口契约已定)
|
||||
- Task 9、Task 10 依赖 Task 8,彼此无依赖,可并行
|
||||
- Task 11 依赖全部
|
||||
Reference in new issue
Block a user