Files
smart_grid_iot/.trae/specs/add-om-pricing-and-worker-registry/spec.md
T
andyesfly f4454ad014 feat: 新增电工APP端与定时任务管理功能
- 新增电工APP独立鉴权中间件与登录注册接口
- 新增定时任务管理模块,支持任务配置、执行日志与手动触发
- 新增运维费定价与电工派单成本价目管理
- 新增电工资质证照管理与到期提醒定时任务
- 完善数据库实体与DTO校验,优化权限控制
2026-10-02 16:08:00 +08:00

25 KiB
Raw Blame History

运维费定价与电工登记管理 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

  • (已确认)运维费定价对象:客户收费价目 + 电工派单成本价目,分开维护
  • (已确认)交付范围:后端 API + 管理端 Web + 电工 APP 后端接口;不含 smart_grid_iot_app 页面改造
  • (已确认)资质建模:worker_cert 子表 + worker.audit_status 审核流转
  • (已确认)到期提醒:列表 / 仪表盘标签 与 定时任务站内通知并存
  • (已确认)客户价目建模:单表 price_mode 区分
  • (已确认)定价维度:全局统一价目,不按客户 / 等级区分
  • (已确认)菜单位置:「运营派单」下两个独立子菜单(+ 电工登记管理)
  • (已确认)电工 APP:自助注册 + 上传资质 + 查审核结果
  • 待复核:Assumptions 第 7 条「派单可选电工收紧为审核通过 + 在岗」是否接受
  • 待复核:Assumptions 第 1 条「三种 price_mode 是否本期全部开放录入」