- 新增电工APP独立鉴权中间件与登录注册接口 - 新增定时任务管理模块,支持任务配置、执行日志与手动触发 - 新增运维费定价与电工派单成本价目管理 - 新增电工资质证照管理与到期提醒定时任务 - 完善数据库实体与DTO校验,优化权限控制
25 KiB
25 KiB
运维费定价与电工登记管理 Spec
change-id:
add-om-pricing-and-worker-registry范围:smart_grid_iot(后端 server + 管理端 client);电工 APP 仅交付后端接口,不改smart_grid_iot_app页面。
Why
系统当前缺少两块运营基础能力:
- 运维费定价缺失:既没有面向客户的运维收费价目表,也没有电工派单成本价目的维护界面。数据库设计 D10 已有
dispatch_price(共享电工派单价格)实体,但只有 entity、无接口、无页面,无法维护。 - 电工登记与资质管理缺失:
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_codeUNIQUE、(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/pricedispatch/price/indexdispatch:price35「电工派单成本价」/dispatch/costdispatch/cost/indexdispatch:cost36「电工登记管理」/dispatch/workerdispatch/worker/indexdispatch: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 - 页面使用现有
useTableScrollcomposable、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(需你复核)
price_mode三种模式全部开放:表结构、接口、页面下拉均支持「按次 / 按工时 / 材料」,而不是本期只开放「按次」。理由:这是满足「后续扩展」诉求的最低成本做法。若你希望界面暂只显示「按次」,请告知。- 材料归客户侧:材料费用只作为客户运维价目的
price_mode=3,电工派单成本价目暂不含材料项。 - 电工 APP 登录账号 =
worker表手机号 + 密码,与sys_user完全独立,不新建sys_user记录;电工 APP 权限不走 RBAC 菜单,接口即权限。 - 审核人 = ADMIN:
audit_by记录sys_user.id;本期不区分多级审核。 - 通知接收人仅 ADMIN 角色用户;提前 30 天标「临期」,
expire_date为 NULL 视为长期有效不提醒。 - 定时任务依赖新增
@midwayjs/cron;若你不希望引入新依赖,可改为@Configuration生命周期 +setInterval每日检查(功能等价,代码稍脏)。 - 电工下拉 / 推荐口径变更:
/api/workers/options、/api/workers/recommend增加「audit_status=1且status=1」过滤条件——即只有审核通过且在岗的电工才能被派单。这会改变现有勘察派单页可选电工范围(当前是只要有status=1)。这是有意的业务收紧,请确认。 dispatch_price无存量数据假设(或存量极少),迁移直接补默认值即可,不需要数据回填脚本。- 菜单 id 从 34 起分配,
sys_menu序列重置到最大值;db_seed.sql原有注释中的菜单计数同步更新。 - 管理端列表「资质状态」为实时计算,不落库;因此
certStatus筛选在 SQL 层用子查询 /EXISTS实现(Sequelizewhere内联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是否本期全部开放录入」