邀请发送 (菜单暂隐藏, 待确认参数见 发送邀请-待确认参数.md): - 后端: BizInvite / BizInviteRecipient + Controller/Service/Mapper/XML - 邮件: InviteMailSender (spring-boot-starter-mail SMTP) + application*.yml 邮件配置 - 上传进度: UploadProgressRegistry + UploadProgressController - 前端: InviteList / InviteNew / InviteDetail / InviteView + api/business/invite.js - 原型: proto/html/components/invite-detail / new-invitation / send-invitation 登录/账号: - 首次设密: /getInfo 返回 isPasswordEmpty, SysProfileController 密码为空时跳过旧密码校验, ForcePasswordDialog 强制弹窗 - 姓名单一可信源 resolveDisplayName: doctor→biz_expert.name, sponsor/executor→biz_person.name, 其余回退 nick_name - OA compliance 门禁改为按手机号查 ecology 视图 (不再限定 manager/leader) 其它: - OSS zip 在线查看 (列清单+取单文件, 公开只读) + SecurityConfig permitAll - doctor 项目详情 ProjectDetail.vue - 数据库/测试/设计文档 (md) 入库
34 KiB
测试设计方案(内部)
版本:V2.0 读者:研发 / 测试 / QA(内部文档,含风险、覆盖缺口、遗留问题) 被测系统:合规管理平台(六角色全流程协同,前后端分离 + H5 移动端) 配套文档:《详细设计方案 V2.0》《数据库设计文档 V2.0》《待优化遗留项》
0. 阅读约定
- 所有接口路径均以
/business为前缀(完整示例:POST /business/auth/smsLogin)。 - 所有「预期结果」中标注的错误文案均取自后端
Controller源码中的AjaxResult.error(...)字面量,测试时以msg字段为准,逐字比对。 - HTTP 语义:本系统所有业务接口统一返回
HTTP 200+AjaxResult结构体{code, msg, data},成功 code=200、失败 code=500(部分全局异常/无权限由框架返回401/403)。本文「预期结果」中提到的 code 均指AjaxResult.code,除非特别注明 HTTP 状态码。 - 「返回体结构」统一约定:列表接口返回
{code, msg, total, rows};详情/操作返回{code, msg, data};错误返回{code:500, msg:"<错误文案>"}。
1. 测试目的与范围
1.1 测试目的
- 验证六类角色(后台管理员 / 合规人员 / 医生 / 执行方 / 支持方 / 项目负责人)的功能正确性与数据隔离正确性。
- 验证会议「事实 + 推导」状态机、材料两级审核、费用汇总与结算等核心业务规则符合设计。
- 验证短信登录、注册、电子签、OCR、供应商同步、OA 对接等跨服务 / 跨系统链路的可用性与容错。
- 在验收前暴露高风险缺陷,明确覆盖缺口与遗留问题,为回归与验收提供依据。
1.2 测试范围(范围内)
| 功能域 | 覆盖内容 |
|---|---|
| 账号与认证 | 短信验证码登录、三类注册、登录状态与角色跳转 |
| 角色权限与数据隔离 | 六角色权限矩阵、主 / 子账号隔离、越权拦截、数据脱敏 |
| 专家管理 | 建档、启用/停用、查询回填、导入导出 |
| 机构 / 人员管理 | 机构、人员、供应商账号同步 |
| 策划方案与专项计划 | 投稿、审核、立项、专项计划维护 |
| 项目管理 | 立项、分配(执行方 / 监察员 / 执行人)、公示、意向、报名、评分、项目开通 |
| 会议管理 | 建会校验、参会人、材料、发票、海报、会议级分配 |
| 材料两级审核 | 提交、一级审核、二级审核、批量审核、退回重算、审核日志 |
| 费用与结算 | 费用口径、费用汇总、结算 / 完结 / 解冻 |
| 劳务电子签 | 推送、填写、手写签名、PDF 归档 |
| 消息中心 | 列表、未读、已读、SSE 推送 |
1.3 测试范围(范围外 / 不覆盖)
| 项 | 原因 |
|---|---|
| 单元测试 / 接口自动化脚本 | 本方案为手工测试设计,不含自动化用例代码 |
| 生产环境数据迁移校验 | 由 DBA 单独负责(见《数据库设计文档 V2.0》) |
| 性能压测(并发量级、TPS 阈值) | 仅做功能级性能冒烟(见 §5) |
| 安全渗透 / 代码审计 | 由安全团队单独进行,本方案只覆盖权限与越权功能面 |
2. 测试环境与测试数据
2.1 测试账号矩阵(基础账号,后续用例引用)
统一短信验证码:固定测试码
123456(测试环境短信网关 mock,发码后固定回123456,验证码有效期 5 分钟)。 账号状态列audit_status:'1'=待审核(拦截登录)、'2'=通过(可登录)、'3'=拒绝;biz_expert.status:'Y'=正常、'N'=禁用。smsLogin中"您的信息正在审核中"的判定值为audit_status='1'。
| 编号 | 角色 | 用户名 | 手机号 | role_type | account_type | 关键字段 / 备注 |
|---|---|---|---|---|---|---|
| U01 | 后台管理员 | admin | 13800000000 | admin | MAIN | 框架管理员,可访问 admin 全部菜单 |
| U02 | 合规经理 | manager01 | 13800000002 | manager | MAIN | create_user_id=U02 |
| U03 | 医生(已通过) | doctor01 | 13800000001 | doctor | MAIN | audit_status='2' |
| U04 | 医生(待审核) | doctor02 | 13800000003 | doctor | MAIN | audit_status='1' |
| U05 | 执行方主账号 | execmain | 13800000010 | executor | MAIN | exec_user_id=U05, execution_unit_id=EU1 |
| U06 | 执行方子账号 | execsub | 13800000011 | executor | SUB | parent_user_id=U05, staff_user_id=U06 |
| U07 | 支持方主账号 | sponsormain | 13800000020 | sponsor | MAIN | sponsor_admin_user_id=U07, sponsor_org_id=SO1 |
| U08 | 支持方子账号 | sponsorsub | 13800000021 | sponsor | SUB | parent_user_id=U07, monitor_user_id=U08 |
| U09 | 项目负责人 | leader01 | 13800000030 | leader | MAIN | lead_user_id=U09 |
| U10 | 执行方子账号2 | execsub2 | 13800000012 | executor | SUB | parent_user_id=U05, staff_user_id=U10 |
| U11 | 支持方子账号2 | sponsorsub2 | 13800000022 | sponsor | SUB | parent_user_id=U07, monitor_user_id=U11 |
2.2 基础业务数据(测试前置,一次性准备)
| 数据 | 字段值 | 说明 |
|---|---|---|
| 机构(支持方)SO1 | org_id=SO1, org_name="XX医药支持机构", status='0'(启用) | sponsor 主/子账号挂靠 |
| 执行单位 EU1 | execution_unit_id=EU1, name="XX执行单位" | executor 主/子账号挂靠 |
| 项目 P1 | project_no="P202609001", create_user_id=U02, 总场次 totalSessions=10, is_published='Y' | 由 U02 立项 |
| 项目 P2 | project_no="P202609002", create_user_id=U02, is_published='N' | 未发布,用于公示过滤用例 |
| 项目 P3 | project_no="P202609003", create_user_id=U02, is_finished='Y' | 已结题,用于「已结题不能分配」用例 |
| 会议 M1 | meeting 属于 P1, 第 1 期, is_executed=0 | 未执行,用于建会/参会人/材料用例 |
| 会议 M2 | 属于 P1, 第 2 期, is_executed=1, laborAuditStage=4, serviceAuditStage=4, fee_calc_status=1, 凭证已传 | 已执行且可结算,用于结算用例 |
| 会议 M3 | 属于 P1, 第 3 期, is_frozen=1 | 已冻结,用于「冻结不能提交材料」用例 |
| 专家 E1 | expert_id=E1, name="王医生", phone=13800000001, status='0'(启用), title="主任医师", dept="心内科" | 供参会人回填、评分用例 |
2.3 数据准备 / 清理规则
- 准备:每轮回归前,按 §2.2 用 admin 或 manager 账号在界面/接口建好基础数据;短信验证码固定
123456。 - 清理:费用结算、评分、意向等有状态流转的用例,执行前先重置目标对象状态(见各用例「前置条件」的还原说明),避免用例间互相污染。
- 原则:涉及唯一性约束的用例(如「本机构已创建第 X 期会议」),先删掉同名对象再执行,保证可重复。
3. 测试策略
3.1 分层策略
| 层级 | 内容 | 手段 |
|---|---|---|
| L1 功能测试 | 每个用例逐条验证接口/界面行为 | 手工 + 接口联调(Postman/Apifox) |
| L2 业务规则测试 | 状态机、审核链、费用口径、结算前置 | 场景组合(§4 核心用例) |
| L3 权限/隔离测试 | 六角色越权、主/子账号隔离、脱敏 | 交叉矩阵(§4.3、§4.5) |
| L4 链路测试 | 短信、OCR、OSS、电子签、SSE、供应商同步 | 端到端(§4.9、§4.11、§5) |
3.2 优先级定义
| 优先级 | 定义 | 举例 |
|---|---|---|
| P0 | 核心主流程,阻塞验收 | 登录、建会、结算、材料审核 |
| P1 | 重要业务规则/权限隔离 | 越权、状态机、费用口径、脱敏 |
| P2 | 一般功能/边界 | 分页、排序、字段回显、提示文案 |
| P3 | 次要/低频 | 导出、日志、操作留痕 |
3.3 判定标准
- 每条用例结果分 Pass / Fail / Block(阻塞)/ NA(不适用)。
- 一条用例 Pass 的标准:实际返回/表现与「预期结果」完全一致,包括错误文案逐字一致、code 一致、字段值与脱敏规则一致。
- 出现任何一条 P0 用例 Fail → 该模块不可验收;P1 用例存在未关闭的 Fail → 整体验收不通过。
4. 分模块测试设计
每个模块给出「测试点清单」(全部要点)+「代表用例」(展开到可逐行执行的步骤)。代表用例是本文的核心,覆盖最高风险路径。
4.1 登录与账号体系(M1)
测试点清单
| 编号 | 测试点 |
|---|---|
| M1-01 | 已注册手机号发码成功,未注册手机号发码报错 |
| M1-02 | 验证码登录成功返回 JWT,role_type 驱动跳转 |
| M1-03 | 医生 audit_status='0' 登录拦截 |
| M1-04 | 停用/删除账号登录拦截 |
| M1-05 | 执行方/支持方子账号在主账号停用/机构禁用时的拦截 |
| M1-06 | 执行方注册(校验链 + 密码/用户名规则) |
| M1-07 | 支持方注册(企业/角色校验) |
| M1-08 | 验证码错误、过期、手机号格式错误 |
代表用例 M1-02-01:验证码登录成功(医生)
- 前置:U03(doctor01 / 13800000001,audit_status='1')存在且启用。
- 步骤:
POST /business/auth/smsSendCode,请求体{"phone":"13800000001"}。- 预期:
{code:200, msg:"发送成功", data:...},data 含uuid。
- 预期:
- 取出
uuid,POST /business/auth/smsLogin,请求体{"phone":"13800000001", "smsCode":"123456", "uuid":"<第1步uuid>"}。- 预期:
{code:200, msg:"登录成功", data:{token:"<JWT>"}};data.token非空;后续请求携带Authorization: Bearer <token>可正常访问医生首页。
- 预期:
GET /business/auth/getInfo(携带 token)。- 预期:
data.user.roleType == "doctor",前端据此跳转医生首页。
- 预期:
代表用例 M1-03-01:待审核医生登录拦截
- 前置:U04(doctor02 / 13800000003,audit_status='0')。
- 步骤:发码后
POST /business/auth/smsLogin{"phone":"13800000003","smsCode":"123456","uuid":...}。 - 预期:
{code:500, msg:"您的信息正在审核中"};不返回 token;登录失败。
代表用例 M1-04-01:停用/删除账号登录拦截(错误文案覆盖)
- 前置:分别准备「已删除」账号(del_flag='2')与「已停用」账号(status='1')。
- 步骤:对两个账号分别发码登录。
- 预期:删除账号 →
msg:"账号已被删除";停用账号 →msg:"账号已被停用"。
代表用例 M1-06-01:执行方注册——用户名/密码规则校验链
- 步骤:
POST /business/auth/registerExecutor,逐条发送以下 body 并断言 msg:{"username":"ab","unitName":"XX","businessNature":"服务商","phone":"13900000001","smsCode":"123456","password":"123456","confirmPassword":"123456","uuid":...}→msg:"用户名长度 4-20 位"。{"username":"a b!","...同1}→msg:"用户名只能包含字母/数字/下划线"。{"username":"valid01","unitName":"","...}→msg:"企业名称不能为空"。{"username":"valid01","unitName":"XX","businessNature":"","...}→msg:"企业性质不能为空"。{"username":"valid01","unitName":"XX","businessNature":"服务商","password":"123","confirmPassword":"123",...}→msg:"密码长度 6-20 位"。- 密码
123456/ 确认654321→msg:"两次密码输入不一致"。 - phone 填已注册手机号 →
msg:"该手机号已注册, 请直接登录"。 - username 填已占用用户名 →
msg:"用户名已被占用: <用户名>"。 - 全部合法 →
{code:200},且自动创建 sys_user。
代表用例 M1-07-01:支持方注册——企业/角色校验链
- 步骤:
POST /business/auth/registerSponsor,逐条断言:- 缺少
orgId→msg:"请选择企业"。 - 缺少
realName→msg:"请输入联系人姓名"。 orgId不存在 →msg:"所选企业不存在"。orgId指向非支持方机构 →msg:"所选企业不是支持方"。- 全部合法 →
{code:200}。
- 缺少
4.2 专家管理(M2)
测试点清单
| 编号 | 测试点 |
|---|---|
| M2-01 | admin 建档(同建 sys_user,用户名/密码=手机号) |
| M2-02 | 启用/停用专家(同步 biz_expert.status + sys_user.status) |
| M2-03 | 按手机号查询回填(命中返回档案,未命中返回 null) |
| M2-04 | profile 按当前登录用户路由 |
| M2-05 | 编辑(注意:不改动 expertId,否则后端 WHERE 失效) |
| M2-06 | 导入(dry-run 预检 / force 覆盖) |
代表用例 M2-01-01:admin 建档专家
- 步骤:
POST /business/expert,body{"name":"李医生","phone":"13800000004","dept":"神经内科","title":"副主任医师","status":"0"}。 - 预期:
{code:200, data:{...sys_user 含明文 password}};库中biz_expert新增一条,且sys_user.user_name=13800000004、sys_user.password=13800000004对应的 BCrypt 密文。
代表用例 M2-03-01:手机号放大镜回填
- 步骤:
GET /business/expert/byPhone/13800000001→ 预期data为 E1 档案。GET /business/expert/byPhone/19999999999→ 预期data == null,前端不报错、显示「未找到」。
代表用例 M2-05-01:编辑专家(回归已知缺陷)
- 前置:E1 存在。
- 步骤:
PUT /business/expert,body 带expertId=E1、修改dept="心内科二区"(必须携带原 expertId)。 - 预期:
{code:200},库中 E1.dept 已更新;expertId字段不被清空。
4.3 机构 / 人员管理(M3)
测试点清单
| 编号 | 测试点 |
|---|---|
| M3-01 | 机构启用/停用对子账号登录的影响 |
| M3-02 | 人员(biz_person)显示登录账号(join sys_user) |
| M3-03 | 供应商账号同步(开放接口账号密码校验链) |
代表用例 M3-01-01:机构停用后子账号登录拦截
- 前置:U07/U08 挂靠 SO1;先验证 U08 可正常登录。
- 步骤:admin 停用 SO1(status='1')→ U08 再次发码登录。
- 预期:
msg:"您所属的机构已禁用",登录失败。恢复 SO1 后可再次登录。
代表用例 M3-02-01:人员列表显示登录账号
- 步骤:
GET /business/person/list(manager)。 - 预期:行数据含
account字段(来自sys_user.user_name),与loginUsername字段命名不冲突;未绑 sys_user 的人员account为空。
4.4 项目管理(M4)
测试点清单
| 编号 | 测试点 |
|---|---|
| M4-01 | manager 只见自己 create_user_id 的项目 |
| M4-02 | sponsor 主/子账号项目可见性(sponsorAdminUserId / monitorUserId 反查) |
| M4-03 | executor 主/子账号项目可见性(execUserId / staffUserId 反查) |
| M4-04 | 项目删除(仅 admin;软删除级联) |
| M4-05 | 分配执行方/监察员/执行人(参数必填 + 已结题拦截) |
| M4-06 | 公示过滤(按 is_published) |
| M4-07 | 评分(角色限制 + 聚合口径 sum/(count×4)) |
| M4-08 | 项目开通(openStatus / openDeadline) |
代表用例 M4-04-01:项目删除权限与软删级联
- 步骤:
- U09(leader)
DELETE /business/project/{P1}→ 预期{code:500, msg:"只有管理员可删除项目"}。 - U01(admin)
DELETE /business/project/{P1}→ 预期{code:200}。 - 断言级联:
biz_project、项目关联的 5 张分配表、会议链记录is_deleted均为软删标记(非物理删除)。
- U09(leader)
代表用例 M4-05-01:已结题项目不能分配
- 前置:P3(is_finished='Y')。
- 步骤:
POST /business/project/saveAssigns,body 含projectId=P3。 - 预期:
msg:"已结题项目不能分配";未写入分配表。
代表用例 M4-05-02:分配参数必填校验
- 步骤:
- sponsorAssign 缺
monitorUserIds→msg:"monitorUserIds 必填"(或"monitorUserId 必填")。 - executorAssign 缺
staffUserIds→msg:"staffUserIds 必填"(或"staffUserId 必填")。 - 批量分配传空数组 →
msg:"请提供至少一个分配记录"。
- sponsorAssign 缺
代表用例 M4-07-01:评分聚合口径
- 前置:P1 已有 2 条评分(分值为 4、4)。
- 步骤:U02(manager)
POST /business/project/rate,body{"projectId":"P1","score":4}。 - 预期:
data.score == 4.0((4+4+4)/(3×4)=1.0 → ×4 = 4.0,四舍五入保留 1 位)。 - 反向:U03(doctor)评分 →
msg:"当前角色不可评分";缺 projectId →msg:"projectId 必填"。
4.5 会议管理(M5)—— 状态机核心
测试点清单
| 编号 | 测试点 |
|---|---|
| M5-01 | 建会校验链(projectId 必填、期数上限、重复期数、时间窗) |
| M5-02 | 删除会议(仅 admin / 合规经理) |
| M5-03 | 阶段推导(劳务/会务两轨 min 汇总;FROZEN 仍算已执行) |
| M5-04 | 提交材料前置(非执行方 / 未执行 / 已冻结拦截) |
| M5-05 | 一级审核 / 二级审核(合规人 / 监察员权限 + 拒绝意见必填) |
| M5-06 | 批量审核(返回 successCount/failCount) |
| M5-07 | 会议级分配与批量上传回填 |
| M5-08 | sponsor 会议详情脱敏 |
代表用例 M5-01-01:建会校验链(逐条断言)
- 前置:P1 总场次=10,已建 0 场;P1 开始时间 2026-09-01、结束时间 2026-09-30。
- 步骤:
POST /business/meeting,逐条断言:- 缺
projectId→msg:"建会必须指定 projectId"。 projectId存在但meetingNo(期数)填 11 →msg:"期数不能超过分配给本公司的场次 (共 10 场)"。- 首次建第 1 期成功 → 再次建第 1 期 →
msg:"本机构已创建第 1 期会议, 请勿重复"。 - 会议开始时间早于项目开始 →
msg:"会议开始时间不能早于项目开始时间";结束晚于项目结束 →msg:"会议结束时间不能晚于项目结束时间"。
- 缺
- 上限分支:把「分配给本公司场次」调成 1,建满后 →
msg:"本项目分配给本公司的场次为 1 场, 已建 1 场, 已达上限"。
代表用例 M5-03-01:阶段推导(两轨 min)
- 前置:M1 处于
laborAuditStage=4(A/已审核) +serviceAuditStage=1(N/待审核)。 - 步骤:
GET /business/meeting/{M1}(manager)。 - 预期:会议级 stage == 服务轨 min(
1),而非 4;is_executed保持推导值。
代表用例 M5-04-01:提交材料前置拦截
- 步骤:
POST /business/meeting/submit-material,body{"meetingId":"M1","types":["LABOR"]},逐条断言:- 以 U02(非执行方)提交 →
msg:"您不是该项目的执行方, 无法提交材料"。 - 以 U05(执行方)对「未执行会议」提交 →
msg:"会议尚未执行, 不能提交材料"。 - 以 U05 对 M3(已冻结)提交 →
msg:"会议已冻结, 不能提交材料"。 - types 为空 →
msg:"请选择要提交的材料类型"。 - types 含
"XXX"→msg:"未知材料类型: XXX"。 - types 只含
"SERVICE"且无会务材料 →msg:"会务材料至少上传一条"。 - 提交
"LABOR"但会议无参会人 →msg:"请先添加参会人员 (劳务材料)"。
- 以 U02(非执行方)提交 →
代表用例 M5-05-01:二级审核权限与拒绝意见
- 步骤:
- 非合规人
POST /business/meeting/audit-compliance→msg:"仅合规人员可操作"。 - 合规人提交
items=[{type:"LABOR",approved:false}]且opinion为空 →msg:"拒绝时意见不能为空"。 - 非监察员
POST /business/meeting/audit-supervision→msg:"您不是该会议监察员, 无权监察"。
- 非合规人
代表用例 M5-06-01:批量审核返回统计
- 前置:M1、M2 均处于待一级审核。
- 步骤:
POST /business/meeting/batch-audit-compliance,body{"meetingIds":["M1","M2"],"approved":true,"opinion":"通过"}。 - 预期:
data == {total:2, successCount:2, failCount:0, failures:[]};两会议审核状态推进。
代表用例 M5-08-01:sponsor 会议详情脱敏
- 前置:M1 有参会人(姓名李小明、手机号 13800000005、银行卡 6222000000000000、身份证 110101199001011234)。
- 步骤:
GETsponsor 视角会议详情 / 参会人列表(走 maskForSponsor)。 - 预期:
maskName:李小明→李*明;张三→张*。maskPhone:138****0005(前3+****+后4)。maskBankCard:6222********0000。maskIdCard:110101********1234。- 操作区只读、身份证附件列隐藏。
4.6 参会人管理(M6)
测试点清单
| 编号 | 测试点 |
|---|---|
| M6-01 | 邀请函打开即报名(权限校验) |
| M6-02 | 新增参会人(劳务费字段) |
| M6-03 | 编辑锁定(劳务已提交后不可改) |
| M6-04 | 导入(dry-run 预检 vs force 覆盖,返回 ngList) |
| M6-05 | 劳务费= fee_pre_tax 合计口径 |
代表用例 M6-01-01:邀请函打开即报名
- 前置:M1 下存在参会人 A1(attendee_id=A1)。
- 步骤:
GET /business/meetingAttendee/invitation/{A1}(无权限用户)→msg:"无权限查看该邀请函"。GET /business/meetingAttendee/invitation/999999→msg:"参会记录不存在"。- 合法用户
GET .../invitation/{A1}→{code:200},且该参会人报名状态变为已报名。
代表用例 M6-03-01:劳务已提交后不可修改参会人
- 前置:M1 已提交劳务材料。
- 步骤:
PUT /business/meetingAttendee,body 修改 A1 的feePreTax。 - 预期:
msg:"劳务已提交, 不能修改参会人";或checkAttendeeEditable返回不可编辑标记,前端禁用编辑。
代表用例 M6-04-01:导入预检与覆盖
- 前置:准备参会人 Excel(含手机号/姓名/工作单位等,其中 1 条手机号与现有参会人冲突)。
- 步骤:
- dry-run 模式导入 → 预期返回
{total, mismatches},mismatches列出冲突行,不落库。 - force 模式导入 → 预期返回
ImportResult {okNum, ngNum, ngList},okNum为成功数、ngList为失败明细。
- dry-run 模式导入 → 预期返回
4.7 材料与凭证(M7)
测试点清单
| 编号 | 测试点 |
|---|---|
| M7-01 | 凭证上传权限(仅合规/管理员) |
| M7-02 | 拍照上传白名单(L_PANORAMA 拆 FRONT/BACK) |
| M7-03 | 打包下载权限(会务/劳务) |
| M7-04 | 会务材料上传异步 jobId |
代表用例 M7-01-01:凭证上传权限
- 步骤:U03(医生)
POST /business/meetingMaterial/saveVouchers→msg:"只有合规或管理员可上传凭证"。 - 预期:U01/U02 可正常上传并返回 code 200。
代表用例 M7-03-01:打包下载权限
- 步骤:U03
GET /business/meetingMaterial/downloadZip?meetingId=M1→ 拦截;U02 同请求 → 返回 zip 流。 - 预期:劳务材料同理(
downloadLaborZip),仅管理员/合规经理可下载。
4.8 费用与结算(M8)
测试点清单
| 编号 | 测试点 |
|---|---|
| M8-01 | 劳务费=参会人 fee_pre_tax 合计 |
| M8-02 | 会务费=总发票(M_INVOICE)合计(覆盖 SUB) |
| M8-03 | fee_calc_status 门控(未汇总完成不能结算) |
| M8-04 | 结算前置(两轨 APPROVED + 凭证上传) |
| M8-05 | 结算即终态(settle 自动 finish) |
| M8-06 | 完结/解冻权限与前置 |
代表用例 M8-03-01:费用未汇总完成不能结算
- 前置:M2 两轨已审核通过 + 凭证已传,但
fee_calc_status=0。 - 步骤:U01
POST /business/meeting/settlebody{"meetingId":"M2"}。 - 预期:
msg:"会议费用尚未汇总完成,无法结算"。等 FeeCalcScheduler 汇总至fee_calc_status=1后再结算 → 成功。
代表用例 M8-04-01:结算前置校验(逐条)
- 步骤:对 M2 依次制造缺失条件,逐个断言:
- 非合规/管理员 →
msg:"只有合规或管理员可结算"。 - 仅劳务轨通过 →
msg:"劳务与会务材料都审核通过后才能结算"。 - 已结算后再次结算 →
msg:"会议已结算"。 - 缺劳务凭证 →
msg:"请先上传劳务凭证";缺会务凭证 →msg:"请先上传会务凭证";都缺 →msg:"请先上传劳务凭证与会务凭证"。
- 非合规/管理员 →
代表用例 M8-05-01:结算即终态
- 步骤:M2 满足全部前置后
POST .../settle。 - 预期:
{code:200};库中is_settled=1、is_finished=1,会议进入已完结终态,不可再结算/完结。
代表用例 M8-06-01:完结/解冻
- 步骤:
- 未结算会议
POST .../finish→msg:"会议尚未结算, 不能完结"。 - 已完结再次 finish →
msg:"会议已完结";非合规人 →msg:"只有合规或管理员可完结"。 - 未冻结会议
POST .../unfreeze→msg:"会议未冻结, 无需解冻";非合规人 →msg:"只有合规或管理员可解冻"。
- 未结算会议
4.9 电子签章(M9)
测试点清单
| 编号 | 测试点 |
|---|---|
| M9-01 | sign info / resolve 查询 |
| M9-02 | saveProfile 保存参保信息 |
| M9-03 | contract 返回 HTML |
| M9-04 | submit 手写签名 + 内容 + X-Real-IP |
代表用例 M9-04-01:提交签名
- 前置:A1 已报名且处于可签署状态。
- 步骤:
POST /business/sign/submit?attendeeId=A1,header 加X-Real-IP: 1.2.3.4,body{"handsign":"data:image/png;base64,...","contentHtml":"<html>...</html>"}。 - 预期:
{code:200};库中签名记录落库,labor_signed相关状态推进(若已接线);IP 字段记录 1.2.3.4。
4.10 策划方案 / 专项计划(M10)
测试点清单
| 编号 | 测试点 |
|---|---|
| M10-01 | 投稿角色只见自己 submitter_id 的稿 |
| M10-02 | 投稿角色新建/编辑强制 submitter_id 写自己 |
| M10-03 | 管理角色排除 status='0' 草稿 |
| M10-04 | 专项计划 status 0/1 语义 |
代表用例 M10-01-01:投稿角色数据隔离
- 步骤:U03(doctor)
GET /business/projectPlan/list。 - 预期:返回行全部
submitterId == U03;不包含其他医生投稿。
代表用例 M10-02-01:越权篡改 submitter 被后端强制覆盖
- 步骤:U03
POST /business/projectPlan,body 里submitterId故意写 U04。 - 预期:落库
submitter_id被后端强制改为 U03(controller 覆盖),且status缺省为'0'。
代表用例 M10-03-01:管理角色排除草稿
- 步骤:U02(manager)
GET /business/projectPlan/list。 - 预期:不返回任何
status='0'的未提交草稿,仅返回已提交稿。
4.11 消息中心与 SSE(M11)
测试点清单
| 编号 | 测试点 |
|---|---|
| M11-01 | my 分页/快捷模式返回 {rows, unread, total} |
| M11-02 | 单条已读(归属校验) |
| M11-03 | 详情归属校验(非管理员只能看自己的) |
| M11-04 | 管理员发送(接收人/标题必填) |
| M11-05 | SSE 推送不弹窗、静默刷新 + 角标 |
代表用例 M11-02-01:已读归属校验
- 前置:U03 有一条消息 msgId=MSG1(receiver=U03)。
- 步骤:U02
PUT /business/message/read/MSG1。 - 预期:
msg:"消息不存在或无权操作",MSG1 未读状态不变。U03 自己操作 → code 200。
代表用例 M11-04-01:管理员发送校验
- 步骤:U01
POST /business/message:- 缺
receiverUserId→msg:"接收人不能为空"。 - 缺
title→msg:"标题不能为空"。 - 合法 → code 200。
- 缺
代表用例 M11-05-01:SSE 推送不打扰用户
- 步骤:U03 登录并订阅 SSE;触发一条发送给 U03 的消息。
- 预期:前端不弹 ElNotification,仅列表静默刷新 + 未读角标自增;重新进入后角标随已读动作递减。
4.12 公示意向(M12)
测试点清单
| 编号 | 测试点 |
|---|---|
| M12-01 | 公示页匿名意向按钮落库到 publicity_support_intent / publicity_execution_intent |
| M12-02 | SUB sponsor 可见性 UNION publicity_support_intent + sponsor_assign |
| M12-03 | 意向数据不混入登录视角强绑表 |
代表用例 M12-01-01:匿名意向落库隔离
- 步骤:公示页点「支持意向」→ 落库
biz_publicity_support_intent;点「执行意向」→ 落库biz_publicity_execution_intent。 - 预期:两张表独立、互不写入
biz_support_intent(已删表)或登录强绑表;同一用户可分别产生两类意向记录。
5. 非功能测试
| 类别 | 测试点 | 方法 | 通过标准 |
|---|---|---|---|
| 性能冒烟 | 项目/会议列表查询耗时 | 200 条数据下 GET .../list |
响应 < 2s,无明显 N+1 慢查询 |
| 性能冒烟 | OCR 识别单张发票 | 上传标准发票图片 | 无异常、结果可读(已知瓶颈 8–10s,记录即可) |
| 兼容 | 六角色主要页面在 Chrome/Edge 正常 | 手工走查 | 无 JS 报错、布局正常 |
| 移动端 | H5 相机拍照上传、无 margin 二次叠加 | 真机/模拟器 | 拍照回传、页面无 2x 间距 |
| 容错 | 短信/OCR/OSS 服务不可用 | 断点注入 | 后端返回友好错误、前端有 toast、不白屏 |
| 安全-功能面 | 越权访问他人数据、主/子账号越界 | §4.3/§4.5 交叉矩阵 | 全部拦截 |
6. 已知风险与覆盖缺口(GAP)
来源于《待优化遗留项》与代码走查,测试时必须把以下项作为「已知风险」显式记录到缺陷/风险表,不能按「通过」处理。
| 编号 | 风险 / 缺口 | 影响 | 测试时的处理 |
|---|---|---|---|
| GAP-1 | biz_special_plan.status / biz_doctor_title.status / biz_org.status / sys_user.status / biz_project_assign.status 仍为 0/1,与新 Y/N 约定不一致 |
0/1 语义各表不同,易误判 | 逐表查 COLUMN_COMMENT 确认语义后再断言,不要照搬 Y/N 方向 |
| GAP-2 | biz_project_plan.is_finished 是死字段(无列、无映射) |
接口/前端若读它恒为空 | 断言该字段应被移除;出现读取即 Bug |
| GAP-3 | biz_meeting.is_finished 是 tinyint 0/1(与项目级同名不同型) |
会议/项目状态判断不一致 | 会议侧按 0/1 断言,项目侧按 Y/N 断言 |
| GAP-4 | biz_meeting.labor_signed 签署链路未接线(全库恒 N,无 setLaborSigned 调用) |
「签署劳务」动作未实现 | M9 用例标注为 NA/Block,待实现后补测;落库值应为 'Y' |
| GAP-5 | 执行方子账号项目可见性走 staff_user_id 反查,需严格隔离 | 子账号可能看到非本人项目 | M4-03 需覆盖子账号 A/B 互不可见 |
| GAP-6 | 缴费口径两态无锁 + FeeCalcScheduler 每分钟 | 并发下可能重复/遗漏汇总 | M8-03 覆盖「汇总中」「汇总完成」两态门控 |
| GAP-7 | sponsor 会议详情脱敏复用 labor_protocol_masked(未 alter table) | 若列不存在则脱敏失效 | M5-08 断言列存在且打码正确 |
7. 验收准出标准
- 所有 P0 用例 100% Pass(无 Block/NA 说明未实现且已登记)。
- P1 用例 Pass 率 ≥ 98%,未关闭 Fail 需有明确责任人与修复计划。
- 每个 GAP 项均已登记,并明确「已处理 / 遗留 + 计划」。
- 缺陷单完整:每条 Fail 有可复现步骤、实际结果、预期结果、截图/接口响应。
- 回归通过:P0 全量 + 受影响模块 P1 重跑通过后,方可验收。
8. 附录:接口/文案速查(供测试断言)
| 模块 | 接口 | 关键错误文案 |
|---|---|---|
| 登录 | POST /business/auth/smsLogin |
手机号格式错误 / 请输入短信验证码 / 验证码错误或已过期 / 用户不存在 / 账号已被删除 / 账号已被停用 / 所属主账号不存在, 请联系管理员 / 您所属的机构已禁用 / 您的信息正在审核中 |
| 注册 | POST /business/auth/registerExecutor |
用户名长度 4-20 位 / 用户名只能包含字母/数字/下划线 / 企业名称不能为空 / 企业性质不能为空 / 密码长度 6-20 位 / 两次密码输入不一致 / 该手机号已注册, 请直接登录 / 用户名已被占用: X |
| 注册 | POST /business/auth/registerSponsor |
请选择企业 / 请输入联系人姓名 / 所选企业不存在 / 所选企业不是支持方 |
| 项目 | POST /business/project/rate |
projectId 必填 / 当前角色不可评分 |
| 项目 | DELETE /business/project/{id} |
只有管理员可删除项目 |
| 项目 | 分配 | monitorUserIds 必填 / staffUserIds 必填 / 项目不存在 / 已结题项目不能分配 / 请提供至少一个分配记录 |
| 会议 | POST /business/meeting |
建会必须指定 projectId / 期数不能超过分配给本公司的场次 (共 X 场) / 本机构已创建第 X 期会议, 请勿重复 / 已达上限 / 会议开始时间不能早于项目开始时间 / 会议结束时间不能晚于项目结束时间 |
| 会议 | POST /business/meeting/submit-material |
您不是该项目的执行方, 无法提交材料 / 会议尚未执行, 不能提交材料 / 会议已冻结, 不能提交材料 / 请选择要提交的材料类型 / 未知材料类型: X / 会务材料至少上传一条 / 请先添加参会人员 (劳务材料) |
| 会议 | audit-compliance |
仅合规人员可操作 / 拒绝时意见不能为空 |
| 会议 | audit-supervision |
您不是该会议监察员, 无权监察 |
| 会议 | settle |
只有合规或管理员可结算 / 劳务与会务材料都审核通过后才能结算 / 会议已结算 / 会议费用尚未汇总完成,无法结算 / 请先上传劳务凭证与会务凭证 / 请先上传劳务凭证 / 请先上传会务凭证 |
| 会议 | finish / unfreeze |
只有合规或管理员可完结 / 会议尚未结算, 不能完结 / 会议已完结 / 只有合规或管理员可解冻 / 会议未冻结, 无需解冻 |
| 参会人 | invitation |
缺少参会记录 / 参会记录不存在 / 无权限查看该邀请函 |
| 参会人 | 编辑 | 劳务已提交, 不能修改参会人 |
| 材料 | saveVouchers |
只有合规或管理员可上传凭证 |
| 材料 | downloadZip / downloadLaborZip |
只有管理员或合规经理可下载会务材料 / …劳务材料 |
| 消息 | POST /business/message |
接收人不能为空 / 标题不能为空 |
| 消息 | PUT /business/message/read/{id} |
消息不存在或无权操作 |
| 消息 | GET /business/message/{id} |
消息不存在 / 无权查看 |