# 商户管理 — 测试用例

> **依据：** 《商户管理功能需求.md》v1.7、《商户管理技术方案.md》v1.7  
> **范围：** 平台管理端 **商户管理** 模块（列表、入驻含 **绑定经营账号**、详情、编辑、认证、删除、协作接口）；**省市区依赖** `GET /agri/region/tree`（§6.8 / §9.9）  
> **排除：** C 端商城、会员模块独立 CRUD、商品/店铺/分类/订单模块的独立功能用例（订单 Facade 未建时按桩 `false` 测删商户正向）  
> **环境：** RuoYi v3.9.2 平台端；接口基路径 `/agri/merchant`；UI 默认 `http://{host}/` + 农资管理菜单（以实际路由为准）

---

## 用例编号说明

| 前缀 | 类型 | 工具 |
|------|------|------|
| MER-UT-xxx | 单元测试 | JUnit 5 + Mockito（Service/Validator/状态机） |
| MER-API-xxx | 接口测试 | Postman / Apifox / REST Assured + 平台 Token |
| MER-UI-xxx | 界面测试 | Playwright（Chromium/Chrome） |

---

## 一、单元测试

### MER-UT-001 个人主体 `biz_complete` 未补经营信息为 0

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 经营完整度计算 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证仅主体入驻时 `biz_complete=0` |
| **前置条件** | 构造个人商户实体，经营字段均为空 |
| **测试步骤** | 调用 `BizCompleteEvaluator.evaluate(merchant)` |
| **预期结果** | 返回 `0`；`pendingBizFields` 含商户名称、客服电话等个人必填项 |

### MER-UT-002 个人主体经营信息齐全 `biz_complete` 为 1

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 经营完整度计算 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证 §9.2 个人必填项齐全时完整 |
| **前置条件** | 个人商户，§9.2 个人字段均有合法值（无营业执照/开户许可证） |
| **测试步骤** | 调用完整度评估 |
| **预期结果** | `biz_complete=1`；`pendingBizFields` 为空 |

### MER-UT-003 企业主体缺营业执照 `biz_complete` 为 0

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 经营完整度计算 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证企业专属字段纳入完整判定 |
| **前置条件** | 企业商户，除 `businessLicense`、`accountPermit` 外均已填 |
| **测试步骤** | 调用完整度评估 |
| **预期结果** | `biz_complete=0`；待完善含「营业执照电子版」「开户许可证」 |

### MER-UT-004 经营地址需 code+name 同时有值

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | bizRegion 判定 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证 `biz_region_code` 与 `biz_region_name` 缺一不可 |
| **前置条件** | 仅填 `biz_region_code`，`biz_region_name` 为空 |
| **测试步骤** | 评估完整度 |
| **预期结果** | `biz_complete=0`；待完善含「经营地址」 |

### MER-UT-005 新增入驻写入认证正常与认证时间

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 新增 Service |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit + Mockito |
| **测试目的** | 验证 R3：入驻即正常并记 `cert_time` |
| **前置条件** | Mock Mapper；合法个人主体 DTO |
| **测试步骤** | `insertMerchant(dto)`（`MerchantCreateDTO` 含 `bindType`+`bindUserId`） |
| **预期结果** | `cert_status=0`；`cert_time` 非空；`shop_count=0`；`biz_complete=0`；调用 `accountBindService.bindAccountOnMerchantCreate` |

### MER-UT-006 新增拒绝经营字段入库

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 新增白名单 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证 R10：POST 仅主体字段 |
| **前置条件** | 新增 DTO 含 `merchantName`、`contactPhone` |
| **测试步骤** | 调用新增；检查持久化实体 |
| **预期结果** | 经营字段不入库或为 null；或 Service 抛业务异常拒绝 |

### MER-UT-007 编辑忽略 `merchantType` 变更

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 编辑 Service |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证 R4 |
| **前置条件** | DB 为个人商户 `merchant_type=1` |
| **测试步骤** | `updateMerchant` 传入 `merchantType=2` |
| **预期结果** | 库中仍为 `1`；UPDATE SQL 不含 `merchant_type` |

### MER-UT-008 编辑 SQL 不更新证件号与信用代码

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 编辑 Service |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证 R11 |
| **前置条件** | 已有 `id_card_no` / `credit_code` |
| **测试步骤** | 编辑传入新证件号、新信用代码 |
| **预期结果** | 库中原值不变 |

### MER-UT-009 已注销商户编辑抛错

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 编辑前置 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证注销不可编辑 |
| **前置条件** | `cert_status=2` |
| **测试步骤** | 调用 `updateMerchant` |
| **预期结果** | 抛 `ServiceException`，msg 含「商户已注销，不可编辑」 |

### MER-UT-010 编辑不更新 `cert_time`

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 编辑 Service |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证 §7.3 编辑不改认证时间 |
| **前置条件** | 记录原 `cert_time` |
| **测试步骤** | 保存编辑成功 |
| **预期结果** | `cert_time` 与原值相同 |

### MER-UT-011 认证状态变更更新 `cert_time`

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 改认证 Service |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证改认证刷新认证时间 |
| **前置条件** | 正常商户；`confirm=true` |
| **测试步骤** | `updateCertStatus(merchantId, 1, true)` |
| **预期结果** | `cert_status=1`；`cert_time` 更新为当前时间 |

### MER-UT-012 认证状态机：正常→冻结允许

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 状态机 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证 §9.1 合法流转 |
| **前置条件** | `cert_status=0` |
| **测试步骤** | `canTransition(0,1)` |
| **预期结果** | `true`；`needConfirm=true` |

### MER-UT-013 认证状态机：注销→冻结不允许

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 状态机 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证非法流转 |
| **前置条件** | `cert_status=2` |
| **测试步骤** | `canTransition(2,1)` |
| **预期结果** | `false` |

### MER-UT-014 认证状态机：注销→正常允许

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 状态机 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证已注销仅可恢复正常 |
| **前置条件** | `cert_status=2` |
| **测试步骤** | `canTransition(2,0)` |
| **预期结果** | `true` |

### MER-UT-015 `confirm=false` 不改库

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 两步确认 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证 §3.6 预确认不落库 |
| **前置条件** | 正常商户 |
| **测试步骤** | `updateCertStatus(id, 1, false)` |
| **预期结果** | 返回 `needConfirm=true`；DB `cert_status` 仍为 0 |

### MER-UT-016 统一社会信用代码唯一校验

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 唯一性 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit + Mockito |
| **测试目的** | 验证 R7 企业入驻信用代码唯一 |
| **前置条件** | Mapper 返回已存在同 `credit_code` |
| **测试步骤** | 企业新增校验 |
| **预期结果** | 校验失败，提示「该统一社会信用代码已入驻」 |

### MER-UT-017 商户名称编辑唯一（排除自身）

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 唯一性 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证 R7 商户名称唯一 |
| **前置条件** | 商户 A、B 均已填不同 `merchant_name` |
| **测试步骤** | A 编辑改为 B 的名称 |
| **预期结果** | 失败，「商户名称已存在」 |

### MER-UT-018 身份证 18 位格式校验失败

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 字段校验 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证 §9.4 身份证规则 |
| **前置条件** | `idCardNo=123` |
| **测试步骤** | JSR303/Validator 校验 |
| **预期结果** | 校验不通过 |

### MER-UT-019 证件区间有效 end≥start

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 字段校验 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证有效期区间合法 |
| **前置条件** | `idValidType=1`，end&lt;start |
| **测试步骤** | 校验 |
| **预期结果** | 校验不通过 |

### MER-UT-020 长期有效证件 end 可空

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 字段校验 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证 `idValidType=2` 规则 |
| **前置条件** | `idValidType=2`，`idValidEnd=null`，start 有值 |
| **测试步骤** | 校验 |
| **预期结果** | 通过 |

### MER-UT-021 删除预检：有店铺不可删

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 删除预检 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证 shop_count&gt;0 阻断 |
| **前置条件** | `shop_count=2`，`cert_status=0` |
| **测试步骤** | `deleteCheck(merchantId)` |
| **预期结果** | `canDelete=false`；reasons 含店铺数量相关文案 |

### MER-UT-022 删除预检：有未完成订单不可删

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 删除预检 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit + Mockito |
| **测试目的** | 验证 OrderFacade 联动 |
| **前置条件** | `shop_count=0`；`hasUnfinishedOrdersByMerchant=true` |
| **测试步骤** | `deleteCheck` |
| **预期结果** | `canDelete=false`；reasons 含「存在未完成订单」 |

### MER-UT-023 批量删除任一条失败整批不删

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 批量删除 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit + Mockito |
| **测试目的** | 验证 §9.6 整批失败 |
| **前置条件** | ID 列表含可删与不可删各一 |
| **测试步骤** | `deleteMerchants(ids)` |
| **预期结果** | 抛错或返回失败；两条 `del_flag` 均未变 |

### MER-UT-024 `openShopCheck` 经营未完善

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 开店校验 Facade |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证 R6 经营完整前置 |
| **前置条件** | `cert_status=0`，`biz_complete=0` |
| **测试步骤** | `checkOpenShop(merchantId)` |
| **预期结果** | `allowed=false`；reason=经营信息未完善 |

### MER-UT-025 `openShopCheck` 不设店铺数量上限

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 开店校验 Facade |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证 R1 不因 shop_count 拒绝 |
| **前置条件** | `biz_complete=1`，`cert_status=0`，`shop_count=10` |
| **测试步骤** | `checkOpenShop` |
| **预期结果** | `allowed=true`；reason 为空 |

### MER-UT-026 编辑接口不可修改 `shop_count`

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 编辑 Service |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证 shop_count 仅店铺 Facade 维护 |
| **前置条件** | `shop_count=1` |
| **测试步骤** | 编辑 DTO 传 `shopCount=99` 保存 |
| **预期结果** | 库中 `shop_count` 仍为 1 |

### MER-UT-027 证件过期标记 `warnExpired` 仍成功

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 新增 Service |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证 §6.4 过期可提交 |
| **前置条件** | `idValidEnd` 为昨日 |
| **测试步骤** | 新增个人商户 |
| **预期结果** | 入库成功；返回 `warnExpired=true` 且 `expiredItems` 非空 |

### MER-UT-028 认证状态机：同态变更不允许

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 状态机 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证 §9.1 同态不可流转 |
| **前置条件** | `cert_status=0` |
| **测试步骤** | `canTransition(0,0)` |
| **预期结果** | `false` |

### MER-UT-029 个人证件号入驻唯一

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 唯一性 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit + Mockito |
| **测试目的** | 验证个人 `id_card_no` 未删除范围内唯一 |
| **前置条件** | 已存在个人商户占用证件号 X |
| **测试步骤** | 个人新增校验证件号 X |
| **预期结果** | 失败；msg「该证件号码已入驻」 |

### MER-UT-030 银行卡号格式 8～30 位数字

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 字段校验 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证 §9.4 `bankAccount` |
| **前置条件** | `bankAccount=123`（不足 8 位） |
| **测试步骤** | 编辑保存校验 |
| **预期结果** | 校验不通过 |

### MER-UT-031 `allowedCertStatuses` 按当前状态过滤

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 详情 VO |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证 §9.1 可选目标与 `needConfirm` |
| **前置条件** | 正常、已注销商户各一 |
| **测试步骤** | 构建详情 `allowedCertStatuses` |
| **预期结果** | 正常含冻结/注销且 `needConfirm=true`；注销 **仅** 含恢复为正常 |

### MER-UT-032 绑定管理员写入经营账号关联

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | MerchantAccountBindService |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit + Mockito |
| **测试目的** | 验证 R12：insert `(account_id=user_id, merchant_id, shop_id=NULL)` |
| **前置条件** | Mock `sys_user` userId=10，具 **role_key=merchant**；`countByAccountId(10)=0` |
| **测试步骤** | `bindAccountOnMerchantCreate(1L, dto{SYS_USER,10}, op)` |
| **预期结果** | `merchantAccountMapper.insert` 的 `accountId=10`，`merchantId=1`，`shopId=null` |

### MER-UT-033 未选 bindType 抛错

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 绑定校验 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证强制绑定 |
| **前置条件** | — |
| **测试步骤** | `bindAccountOnMerchantCreate` 且 dto 无 bindType |
| **预期结果** | `ServiceException`；msg 含「请选择并绑定」 |

### MER-UT-034 绑定会员要求昵称非空

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 绑定会员 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit + Mockito |
| **测试目的** | 验证 R13：会员名称/昵称非空校验 |
| **前置条件** | 会员 memberCode（=user_name）非空，nickName 为空 |
| **测试步骤** | `BIND_MEMBER` + bindMemberId |
| **预期结果** | 抛错；msg 含「会员昵称不能为空」 |

### MER-UT-035 绑定会员写入经营账号关联

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 绑定会员 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit + Mockito |
| **测试目的** | 验证 R13：insert `account_id=memberId`；复用会员 sys_user 并追加 merchant 角色 |
| **前置条件** | memberCode=张三，memberId=5，nickName=小张；会员具 role_key=member |
| **测试步骤** | 完整 `BIND_MEMBER` 流程 |
| **预期结果** | `grantRoleIfAbsent(5, merchant)`；`biz_merchant_account.account_id=5`，`shop_id=NULL`；**不**新建 sys_user |

---

## 二、接口测试

### MER-API-001 列表分页与默认排序

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | GET /list |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证列表基础能力 |
| **前置条件** | 已登录；`agri:merchant:list`；存在≥2 条未删除商户 |
| **测试步骤** | `GET /agri/merchant/list?pageNum=1&pageSize=10` |
| **预期结果** | `code=200`；`rows` 按 `create_time` 倒序；不含 `del_flag=2` 数据 |

### MER-API-002 列表按商户名称模糊检索

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | GET /list |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §5 名称检索（含 unitName） |
| **前置条件** | 存在 `merchant_name` 含「农资」的商户 |
| **测试步骤** | `GET /list?merchantName=农资` |
| **预期结果** | 返回记录均匹配 `merchant_name`/`person_name`/`company_name` 之一 |

### MER-API-003 列表按认证状态筛选

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | GET /list |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证认证状态筛选 |
| **前置条件** | 存在正常、已冻结商户各一 |
| **测试步骤** | `GET /list?certStatus=1` |
| **预期结果** | 全部 `certStatus=1`（已冻结） |

### MER-API-004 列表 `canEdit`/`canDelete` 标志

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | GET /list |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证列表操作可用性 |
| **前置条件** | 正常可删商户；已注销商户各一 |
| **测试步骤** | 对比两条记录的 flags |
| **预期结果** | 正常且满足删条件的 `canDelete=true`；注销的 `canEdit=false` |

### MER-API-005 个人商户新增成功

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | POST / |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证个人入驻主流程 |
| **前置条件** | `agri:merchant:add`；证件号未占用 |
| **测试步骤** | `POST /` 提交 §9.3 个人样例 + `bindType=SYS_USER`+`bindUserId` |
| **预期结果** | `code=200`；msg 含「请尽快在编辑中完善商户经营信息」；详情 `certStatus=0`，`bizComplete=0`；`biz_merchant_account` 已 1 条 |

### MER-API-006 企业商户新增成功

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | POST / |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证企业入驻主流程 |
| **前置条件** | 信用代码未占用 |
| **测试步骤** | `POST /` 提交 §9.3 企业样例 JSON |
| **预期结果** | `code=200`；`companyName` 入库；列表 `unitName` 为企业名称 |

### MER-API-007 新增缺少必填字段失败

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | POST / |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证必填校验 |
| **前置条件** | — |
| **测试步骤** | `POST /` 仅传 `merchantType=1`，无姓名证件 |
| **预期结果** | `code≠200` 或字段级错误；未新增记录 |

### MER-API-008 企业信用代码重复

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | POST / |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 R7 入驻唯一 |
| **前置条件** | 已存在企业 `creditCode=A` |
| **测试步骤** | 再次 `POST` 相同 `creditCode` |
| **预期结果** | 失败；msg「该统一社会信用代码已入驻」 |

### MER-API-009 新增携带经营字段被拒绝或忽略

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | POST / |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 R10 |
| **前置条件** | — |
| **测试步骤** | 1. `POST /` 主体合法且带 `merchantName`、`bankAccount` 2. `GET /{id}` 查详情 |
| **预期结果** | 成功时经营字段仍为空/`bizComplete=0`；或整单拒绝 |

### MER-API-010 证件过期仍 200 且带 warn

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | POST / |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证过期可提交 |
| **前置条件** | — |
| **测试步骤** | `idValidEnd` 设为过去日期提交 |
| **预期结果** | `code=200`；`data.warnExpired=true` |

### MER-API-011 详情含待完善字段列表

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | GET /{id} |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证详情契约 |
| **前置条件** | 仅主体、未补经营的新商户 |
| **测试步骤** | `GET /agri/merchant/{id}` |
| **预期结果** | `bizComplete=0`；`pendingBizFields` 为非空中文字符串数组 |

### MER-API-012 详情 `allowedCertStatuses` 与当前状态一致

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | GET /{id} |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §9.1 可选目标状态 |
| **前置条件** | 正常商户 |
| **测试步骤** | 获取详情 |
| **预期结果** | 含冻结、注销选项；不含「注销→冻结」类非法项 |

### MER-API-013 编辑补全经营信息成功

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | PUT / |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证补全经营主流程 |
| **前置条件** | 正常个人商户；`agri:merchant:edit` |
| **测试步骤** | `PUT /` 提交 §9.3 编辑样例（含经营字段） |
| **预期结果** | `code=200`；`bizComplete=1`；msg 可含「已可开设店铺」；`certTime` 不变 |

### MER-API-014 编辑商户名称重复失败

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | PUT / |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 R7 |
| **前置条件** | 商户 B 已占用名称「XX农资」 |
| **测试步骤** | 商户 A `PUT` 改 `merchantName=XX农资` |
| **预期结果** | 失败；「商户名称已存在」 |

### MER-API-015 已注销商户编辑失败

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | PUT / |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证注销只读 |
| **前置条件** | `cert_status=2` |
| **测试步骤** | `PUT /` 修改联系人 |
| **预期结果** | 失败；「商户已注销，不可编辑」 |

### MER-API-016 编辑接口传 `certStatus` 无效

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | PUT / |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 R5 |
| **前置条件** | 正常商户 |
| **测试步骤** | `PUT /` body 含 `certStatus=2` |
| **预期结果** | 认证状态不变；或拒绝并提示在详情改认证 |

### MER-API-017 改认证两步确认-预检

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | PUT /{id}/certStatus |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 confirm 协议 |
| **前置条件** | 正常商户；`agri:merchant:cert` |
| **测试步骤** | `PUT .../certStatus` body `{certStatus:1, confirm:false}` |
| **预期结果** | `code=200`；`needConfirm=true`；`confirmMessage` 含「冻结后不可新开店铺」；DB 仍为正常 |

### MER-API-018 改认证确认后持久化

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | PUT /{id}/certStatus |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证正常→冻结 |
| **前置条件** | MER-API-017 同一商户 |
| **测试步骤** | `confirm:true` 提交冻结 |
| **预期结果** | `certStatus=1`；`certTime` 更新 |

### MER-API-019 非法认证变更拒绝

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | PUT /{id}/certStatus |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证注销→冻结不允许 |
| **前置条件** | `cert_status=2` |
| **测试步骤** | `PUT` 目标 `certStatus=1`，`confirm:true` |
| **预期结果** | 失败；「不允许的认证状态变更」 |

### MER-API-020 已注销恢复为正常

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | PUT /{id}/certStatus |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 2→0 唯一恢复路径 |
| **前置条件** | 已注销商户 |
| **测试步骤** | `confirm:true` 改为正常 |
| **预期结果** | `certStatus=0`；之后 `PUT /` 编辑可成功 |

### MER-API-021 删除预检-无店铺无订单可删

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | GET /{id}/deleteCheck |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证删除前置正向 |
| **前置条件** | 正常；`shop_count=0`；订单桩 false |
| **测试步骤** | `GET .../deleteCheck` |
| **预期结果** | `canDelete=true`；`reasons` 为空 |

### MER-API-022 删除预检-有店铺

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | GET /{id}/deleteCheck |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 R8 有店不可删 |
| **前置条件** | `shop_count≥1` |
| **测试步骤** | `GET .../deleteCheck` |
| **预期结果** | `canDelete=false`；reasons 含店铺相关说明 |

### MER-API-023 逻辑删除成功

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | DELETE /{ids} |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证删除主流程 |
| **前置条件** | MER-API-021 通过；`agri:merchant:remove` |
| **测试步骤** | `DELETE /agri/merchant/{id}` |
| **预期结果** | `code=200`；列表不可见；DB `del_flag=2` |

### MER-API-024 已注销商户不可删除

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | DELETE /{ids} |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证注销不可删 |
| **前置条件** | `cert_status=2`，`shop_count=0` |
| **测试步骤** | `DELETE` |
| **预期结果** | 失败；msg 含「已注销商户不可删除」或等价 |

### MER-API-025 批量删除部分失败整批取消

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | DELETE /{ids} |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证批量整批失败 |
| **前置条件** | ID1 可删；ID2 有店铺 |
| **测试步骤** | `DELETE /agri/merchant/ID1,ID2` |
| **预期结果** | 失败；「部分商户不满足删除条件」；两条均未逻辑删除 |

### MER-API-026 selectList 仅返回可开店商户

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | GET /selectList |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 R6 selectList 条件 |
| **前置条件** | 准备：A 正常+经营完整；B 正常+经营未完整；C 冻结+完整 |
| **测试步骤** | `GET /agri/merchant/selectList` |
| **预期结果** | 仅含 A；不含 B、C、已删除 |

### MER-API-027 openShopCheck 经营完整允许

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | GET /{id}/openShopCheck |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证开店校验正向 |
| **前置条件** | 正常且 `biz_complete=1` |
| **测试步骤** | `GET .../openShopCheck` |
| **预期结果** | `allowed=true`；`reason` 为空 |

### MER-API-028 openShopCheck 经营未完善

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | GET /{id}/openShopCheck |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证经营前置 |
| **前置条件** | 正常且 `biz_complete=0` |
| **测试步骤** | `GET .../openShopCheck` |
| **预期结果** | `allowed=false`；reason=经营信息未完善 |

### MER-API-029 openShopCheck 已冻结/已注销

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | GET /{id}/openShopCheck |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证认证状态阻断开店 |
| **前置条件** | 冻结、注销商户各一且经营完整 |
| **测试步骤** | 分别调用 openShopCheck |
| **预期结果** | 冻结→「商户已冻结」；注销→「商户已注销」 |

### MER-API-030 openShopCheck 高 shop_count 仍允许

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | GET /{id}/openShopCheck |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 R1 无店数上限 |
| **前置条件** | 正常完整商户 `shop_count≥5`（测试数据） |
| **测试步骤** | `GET .../openShopCheck` |
| **预期结果** | `allowed=true` |

### MER-API-031 商户下店铺简要列表

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | GET /{id}/shops |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证详情协作数据 |
| **前置条件** | 商户下有 2 家未删除店铺 |
| **测试步骤** | `GET .../shops` |
| **预期结果** | 返回 2 条 `shopId, shopName, shopStatus`；与 `shop_count` 一致 |

### MER-API-032 无 list 权限 403

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 权限 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 `@PreAuthorize` |
| **前置条件** | 使用无 `agri:merchant:list` 的 Token |
| **测试步骤** | `GET /list` |
| **预期结果** | HTTP 403 或 RuoYi 无权限响应 |

### MER-API-033 无 add 权限新增失败

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 权限 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证新增权限 |
| **前置条件** | 无 `agri:merchant:add` |
| **测试步骤** | `POST /` 合法 body |
| **预期结果** | 403；无新记录 |

### MER-API-034 详情敏感字段脱敏展示

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | GET /{id} |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §9.7 脱敏（若已实现） |
| **前置条件** | 商户含完整证件号、银行卡 |
| **测试步骤** | 获取详情 VO |
| **预期结果** | 证件号前3后4掩码；银行卡仅后4位可见（或按实现约定） |

### MER-API-035 正常→注销（两步确认）

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | PUT /{id}/certStatus |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §9.1 正常→注销 |
| **前置条件** | 正常商户 |
| **测试步骤** | 1. `confirm:false` 2. `confirm:true` 目标 `certStatus=2` |
| **预期结果** | 预检 `confirmMessage` 含「注销后资料不可编辑」；确认后 `certStatus=2` |

### MER-API-036 已冻结→正常恢复

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | PUT /{id}/certStatus |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证冻结可恢复 |
| **前置条件** | `cert_status=1` |
| **测试步骤** | `confirm:true` 改为 `certStatus=0` |
| **预期结果** | `certStatus=0`；`openShopCheck` 在经营完整时为 `allowed=true` |

### MER-API-037 个人证件号重复入驻

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | POST / |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证证件号唯一 |
| **前置条件** | 已存在个人证件号 X |
| **测试步骤** | 再次 `POST` 个人主体且 `idCardNo=X` |
| **预期结果** | 失败；「该证件号码已入驻」 |

### MER-API-038 已冻结商户仍可编辑

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | PUT / |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §7.2 冻结可编辑 |
| **前置条件** | `cert_status=1` |
| **测试步骤** | `PUT` 修改 `servicePhone` |
| **预期结果** | `code=200`；`certStatus` 仍为 1 |

### MER-API-039 无 edit/cert/remove 权限

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 权限 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §6 菜单权限 |
| **前置条件** | Token 仅含 `list`、`query` |
| **测试步骤** | 分别 `PUT /`、`PUT .../certStatus`、`DELETE /{id}` |
| **预期结果** | 均为 403；数据无变更 |

### MER-API-040 商户不存在返回错误

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | GET /{id} |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证异常 ID |
| **前置条件** | — |
| **测试步骤** | `GET /agri/merchant/999999999` |
| **预期结果** | `code≠200` 或 404；无敏感数据泄露 |

### MER-API-041 openShopCheck 已逻辑删除商户

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | GET /{id}/openShopCheck |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证已删除不可开店 |
| **前置条件** | `del_flag=2` 商户 |
| **测试步骤** | `GET .../openShopCheck` |
| **预期结果** | `allowed=false`；reason=商户已删除 |

### MER-API-042 删除预检-存在未完成订单

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | GET /{id}/deleteCheck |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 OrderFacade 接口层 |
| **前置条件** | `shop_count=0`；`hasUnfinishedOrdersByMerchant=true`（订单模块或 Mock） |
| **测试步骤** | `GET .../deleteCheck` |
| **预期结果** | `canDelete=false`；reasons 含「存在未完成订单」 |

### MER-API-043 详情 `shopListUrl` 与店铺数一致

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | GET /{id} |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §3.3 店铺跳转参数 |
| **前置条件** | 经营完整商户 `merchantName=张三农资店`，`shopCount=1` |
| **测试步骤** | `GET /{id}` |
| **预期结果** | `shopListUrl` 含 `merchantName=` 编码值；`shops` 长度=1 |

### MER-API-044 管理员选项 keyword 检索

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | GET /adminUserOptions |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox / MockMvc |
| **测试目的** | 验证 §3.4.1 |
| **前置条件** | 存在 **role_key=merchant** 用户 userName 含 admin |
| **测试步骤** | `GET /agri/merchant/adminUserOptions?keyword=admin` |
| **预期结果** | `code=200`；`data[]` 含 userId、userName、nickName、phonenumber；均为 merchant 角色用户 |

### MER-API-045 会员选项 keyword 检索

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | GET /memberOptions |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox / MockMvc |
| **测试目的** | 验证 §3.4.2 |
| **前置条件** | 存在 **user_name**（接口字段 memberCode）含「张三」的会员（role_key=member） |
| **测试步骤** | `GET /agri/merchant/memberOptions?keyword=张三` |
| **预期结果** | `code=200`；`data[]` 含 memberId、memberCode（=user_name）、nickName、mobile |

### MER-API-046 新增缺少 bindType 失败

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | POST / |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证强制绑定 |
| **前置条件** | 合法主体 JSON，无 bindType |
| **测试步骤** | `POST /` |
| **预期结果** | 失败；msg「请选择并绑定管理员账号或会员账号」；无商户/经营账号记录 |

### MER-API-047 绑定管理员-用户无 merchant 角色

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | POST / |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 R12 角色校验 |
| **前置条件** | sys_user 无 **role_key=merchant** |
| **测试步骤** | `bindType=SYS_USER`+该 userId |
| **预期结果** | 失败；「所选用户未分配商户经营角色」 |

### MER-API-048 绑定会员成功并追加 merchant 角色

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | POST / |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 R13 全链路 |
| **前置条件** | 会员有 user_name（memberCode）、nickName；该 memberId 未绑定经营账号 |
| **测试步骤** | `POST /` §9.8 会员样例 |
| **预期结果** | 商户成功；`sys_user_role` 追加 **merchant**；`biz_merchant_account.account_id=member_id`，`shop_id=NULL` |

### MER-API-049 经营账号重复绑定失败

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | POST / |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证同一 account_id 不可重复绑定 |
| **前置条件** | 会员 user_id 已存在于 `biz_merchant_account.account_id` |
| **测试步骤** | 再次以该会员绑定新商户 |
| **预期结果** | 失败；「该账号已绑定商户经营账号」 |

### MER-API-050 无 add 权限不可查绑定选项

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 权限 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证选项接口权限 |
| **前置条件** | Token 无 `agri:merchant:add` |
| **测试步骤** | `GET /adminUserOptions` |
| **预期结果** | 403 |

### MER-API-051 省市区树接口（经营/注册地址数据源）

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理（依赖） |
| **测试项** | GET /agri/region/tree |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §6.8 三级树供地址选择 |
| **前置条件** | 已登录平台；`biz_region` 表有数据 |
| **测试步骤** | `GET /agri/region/tree` |
| **预期结果** | `code=200`；`data[]` 为省级节点；含 `type=1` 及 `children`；末级区县 `type=3` 且带 `code` |

### MER-API-052 编辑写入经营地址省市区

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | PUT / |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §9.9 落库与 `biz_complete` |
| **前置条件** | 正常商户仅主体；从 MER-API-051 选取区县 |
| **测试步骤** | `PUT /` 提交 `bizRegionCode=110101`、`bizRegionName=北京市/市辖区/东城区` 及其余经营必填项 |
| **预期结果** | 成功；库表 `biz_region_code/name` 一致；`biz_complete=1` 时 msg 可含「已可开设店铺」 |

---

## 三、界面测试（Playwright）

> **通用前置：** 平台管理员账号已登录；浏览器 Chromium；baseURL 配置为测试环境；菜单路径以「农资管理 → 商户管理」为准（路由实现后替换选择器）。

### MER-UI-001 进入商户列表页

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 列表页加载 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright (Chrome) |
| **测试目的** | 验证菜单与列表渲染 |
| **前置条件** | 有 `agri:merchant:list` 权限 |
| **测试步骤** | 1. 登录平台 2. 点击「商户管理」菜单 |
| **预期结果** | 展示表格列：所属单位、类型、商户名称、联系人、店铺数、认证状态、认证时间、操作；默认分页 |

### MER-UI-002 列表按商户名称检索

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 高级检索 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 §5 检索 |
| **前置条件** | 存在名称含「测试农资」商户 |
| **测试步骤** | 1. 输入商户名称「测试农资」 2. 点击查询 |
| **预期结果** | 列表仅显示匹配行；分页重置 |

### MER-UI-003 列表按认证状态筛选

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 高级检索 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证状态筛选 UI |
| **前置条件** | 存在已冻结商户 |
| **测试步骤** | 认证状态选「已冻结」→ 查询 |
| **预期结果** | 列表均为已冻结；标签样式正确 |

### MER-UI-004 添加个人商户-仅主体表单

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 添加商户 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证入驻表单范围 R10 |
| **前置条件** | 有 add 权限 |
| **测试步骤** | 1. 点击「新增」 2. 选个人 3. 查看表单字段 |
| **预期结果** | **无** 经营结算字段；有个人证件、证照；有 **绑定类型**（管理员/会员）及远程搜索框 |

### MER-UI-005 添加个人商户成功提示

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 添加商户 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证入驻成功交互 |
| **前置条件** | 合法个人数据；证照已上传 |
| **测试步骤** | 填写必填项→提交 |
| **预期结果** | 成功提示含「完善商户经营信息」「开设店铺」；列表新增行认证=正常；商户名称显示待完善或所属单位 |

### MER-UI-006 添加企业商户成功

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 添加商户 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证企业入驻 UI |
| **前置条件** | 未占用信用代码 |
| **测试步骤** | 选企业→填法人+企业信息→提交 |
| **预期结果** | 成功；列表所属单位=企业名称 |

### MER-UI-007 添加必填项为空提交校验

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 添加商户 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证前端/后端必填提示 |
| **前置条件** | 打开新增页 |
| **测试步骤** | 不填姓名直接提交 |
| **预期结果** | 表单项标红或 Toast 提示；不关闭弹窗/不跳转 |

### MER-UI-008 查看详情-经营待完善展示

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 查看详情 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 §8.1 待完善 |
| **前置条件** | 仅主体入驻的商户 |
| **测试步骤** | 列表点「查看详情」 |
| **预期结果** | 经营信息区显示「待完善」及缺失字段清单；主体资质只读展示 |

### MER-UI-009 详情改认证-正常转冻结二次确认

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 认证状态 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 §7.4 冻结确认文案 |
| **前置条件** | 正常商户；有 cert 权限 |
| **测试步骤** | 详情选「已冻结」→确认弹窗点确定 |
| **预期结果** | 弹窗含「冻结后不可新开店铺」；保存后状态=已冻结；认证时间更新 |

### MER-UI-010 详情改认证-取消确认不落库

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 认证状态 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证两步确认取消 |
| **前置条件** | 正常商户 |
| **测试步骤** | 改冻结→弹窗点取消 |
| **预期结果** | 仍为正常；认证时间不变 |

### MER-UI-011 详情改认证-注销后编辑入口不可用

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 认证状态 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证注销权限矩阵 |
| **前置条件** | 已注销商户 |
| **测试步骤** | 打开详情/返回列表 |
| **预期结果** | 列表无「编辑」或编辑禁用；详情可改认证仅「恢复为正常」 |

### MER-UI-012 编辑页含主体+经营且无认证修改

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 编辑商户 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 §8.3、§9 |
| **前置条件** | 正常商户 |
| **测试步骤** | 点「编辑」 |
| **预期结果** | 有经营信息区块；**无** 认证状态下拉（或只读）；商户类型不可改 |

### MER-UI-013 编辑补全经营信息成功 Toast

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 编辑商户 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 §9.5 可开店提示 |
| **前置条件** | 经营未完整商户 |
| **测试步骤** | 填写 §6.6 全部经营字段→保存 |
| **预期结果** | Toast「已可开设店铺」或等价；详情 `bizComplete` 展示完整 |

### MER-UI-014 编辑证件号/信用代码只读

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 编辑商户 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 R11 UI |
| **前置条件** | 个人/企业商户各一 |
| **测试步骤** | 打开编辑页查看证件号、信用代码控件 |
| **预期结果** | 控件 disabled 或只读文本，不可输入 |

### MER-UI-015 已冻结商户仍可编辑

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 编辑商户 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 §7.2 冻结可编辑 |
| **前置条件** | 已冻结商户 |
| **测试步骤** | 修改客服电话→保存 |
| **预期结果** | 保存成功；仍为已冻结 |

### MER-UI-016 删除商户-二次确认与成功

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 删除商户 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证删除 UI 流程 |
| **前置条件** | 正常；0 店铺；0 未完成订单 |
| **测试步骤** | 点删除→确认框「删除后不可恢复」→确定 |
| **预期结果** | 列表消失；提示成功 |

### MER-UI-017 删除商户-有店铺按钮禁用或失败提示

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 删除商户 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证删商户约束 |
| **前置条件** | `shop_count>0` |
| **测试步骤** | 尝试删除 |
| **预期结果** | 删除按钮不可用，或点击后提示「请先删除该商户下全部店铺」 |

### MER-UI-018 列表联系人未完善显示破折号

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 列表展示 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 §4.1 联系人列 |
| **前置条件** | 未补经营商户 |
| **测试步骤** | 查看列表该行 |
| **预期结果** | 联系人、联系人手机显示「—」 |

### MER-UI-019 详情跳转店铺列表（若有）

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 详情协作 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 §8.1 店铺跳转 |
| **前置条件** | 商户有店铺；详情有「查看店铺」链接 |
| **测试步骤** | 点击链接 |
| **预期结果** | 打开店铺列表且自动带 `merchantName` 筛选（**不断言** 店铺模块 CRUD，仅验证跳转与参数） |

### MER-UI-020 无 add 权限不显示新增按钮

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 权限 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证菜单按钮权限 |
| **前置条件** | 角色仅 list+query |
| **测试步骤** | 进入商户列表 |
| **预期结果** | 无「新增」按钮 |

### MER-UI-021 证件过期提交成功带风险提示（若 UI 展示）

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 添加商户 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 §6.4 过期提示 |
| **前置条件** | — |
| **测试步骤** | 填过期证件有效期→提交 |
| **预期结果** | 仍成功；页面展示过期风险提示（或成功弹窗含 warn 文案） |

### MER-UI-022 列表无会员相关列

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 列表展示 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证列表不展示会员运营列 |
| **前置条件** | 打开列表 |
| **测试步骤** | 检查表头 |
| **预期结果** | **无**「会员等级」「注册来源」等会员运营列（绑定仅在新增页） |

### MER-UI-029 新增-选管理员远程搜索

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 绑定账号 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 §6.7.1 UI |
| **前置条件** | 存在 **role_key=merchant** 用户 |
| **测试步骤** | 新增→绑定类型选「平台管理员」→输入手机号/用户名搜索→选中 |
| **预期结果** | 下拉展示昵称/用户名；选中后提交可用 |

### MER-UI-030 新增-选会员远程搜索

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 绑定账号 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 §6.7.2 UI |
| **前置条件** | 存在会员 |
| **测试步骤** | 绑定类型选「会员」→按会员名称/手机号搜索→选中 |
| **预期结果** | 展示会员名称、昵称；选中后可提交 |

### MER-UI-031 新增-未选绑定不可提交

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 绑定账号 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证强制绑定 |
| **前置条件** | 主体已填 |
| **测试步骤** | 不选绑定直接提交 |
| **预期结果** | 前端校验或后端 Toast「请选择并绑定…」 |

### MER-UI-023 添加企业-信用代码重复提示

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 添加商户 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 R7 企业唯一性 UI |
| **前置条件** | 已存在企业信用代码 A |
| **测试步骤** | 企业入驻填相同信用代码→提交 |
| **预期结果** | Toast/表单错误「该统一社会信用代码已入驻」；未新增行 |

### MER-UI-024 编辑商户名称重复

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 编辑商户 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 R7 名称唯一 UI |
| **前置条件** | 商户 B 已占用名称「YY农资」 |
| **测试步骤** | 编辑商户 A，商户名称改为「YY农资」→保存 |
| **预期结果** | 保存失败并提示「商户名称已存在」 |

### MER-UI-025 详情改认证-正常转注销

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 认证状态 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证注销流转与 §7.4 文案 |
| **前置条件** | 正常商户 |
| **测试步骤** | 详情选「已注销」→确认弹窗确定 |
| **预期结果** | 弹窗含「注销后资料不可编辑」；保存后列表认证=已注销；编辑按钮不可用 |

### MER-UI-026 已注销恢复为正常后可编辑

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 认证状态 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 2→0 恢复路径 |
| **前置条件** | 已注销商户 |
| **测试步骤** | 详情改认证为「正常」并确认→返回列表点编辑 |
| **预期结果** | 确认文案含「恢复后可编辑、可开店」；编辑页可打开并保存 |

### MER-UI-027 添加企业表单无经营结算字段

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 添加商户 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 R10 企业入驻范围 |
| **前置条件** | 有 add 权限 |
| **测试步骤** | 新增→选企业→检查表单项 |
| **预期结果** | 含法人、企业信息；**无** 商户名称、开户银行、营业执照（经营项在编辑页） |

### MER-UI-028 详情展示店铺数与下属店铺

| 要素 | 内容 |
|------|------|
| **测试模块** | 商户管理 |
| **测试项** | 查看详情 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 §8.1 店铺关联展示 |
| **前置条件** | `shop_count≥1` |
| **测试步骤** | 打开详情 |
| **预期结果** | 「已绑定店铺数量」与列表一致；展示下属店铺名称列表（或表格） |

---

## 四、用例统计

| 类型 | 数量 | 编号段 |
|------|------|--------|
| 单元测试 | 35 | MER-UT-001～035 |
| 接口测试 | 50 | MER-API-001～050 |
| 界面测试 | 31 | MER-UI-001～031 |
| **合计** | **116** | — |

---

## 五、业务规则覆盖索引

| 规则 | 含义 | 对应用例（示例） |
|------|------|------------------|
| R1 | 不设开店数量上限 | MER-UT-025, MER-API-030 |
| R2 | 入驻绑定账号 | MER-UT-032～035, MER-API-044～049, MER-UI-029～031 |
| R12 | 绑定管理员字段 | MER-UT-032, MER-API-047 |
| R13 | 绑定会员 | MER-UT-034/035, MER-API-048/049 |
| R3 | 入驻即正常+认证时间 | MER-UT-005, MER-API-005/006 |
| R4 | 类型不可互转 | MER-UT-007, MER-API-009 |
| R5 | 认证仅详情改 | MER-API-016, MER-UI-009/012 |
| R6 | 开店选商户三条件 | MER-API-026～030, MER-UT-024 |
| R7 | 名称/信用代码唯一 | MER-UT-016/017/029, MER-API-008/014/037, MER-UI-023/024 |
| R8 | 逻辑删除前置 | MER-UT-021～023, MER-API-021～025/042, MER-UI-016/017 |
| R9 | 认证变更不级联店/商品 | （店铺/商品模块联调；本文档不重复） |
| R10 | 入驻仅主体 | MER-UT-006, MER-API-009, MER-UI-004/027 |
| R11 | 证件号/信用代码不可改 | MER-UT-008, MER-UI-014 |

| 功能域 | 正常流程 | 异常/约束 |
|--------|----------|-----------|
| 列表 | MER-API-001～004, MER-UI-001～003 | MER-API-032, MER-UI-020 |
| 入驻 | MER-API-005/006/048, MER-UI-005/006 | MER-API-007/008/037/046～049, MER-UI-007/023/031 |
| 绑定选项 | MER-API-044/045 | MER-API-050 |
| 详情 | MER-API-011/012, MER-UI-008 | MER-API-040 |
| 编辑 | MER-API-013, MER-UI-012/013 | MER-API-014/015, MER-UI-024 |
| 认证 | MER-API-017～020/035/036, MER-UI-009/010 | MER-API-019, MER-UI-025/026 |
| 删除 | MER-API-023, MER-UI-016 | MER-API-022/024/025/042, MER-UI-017 |
| 协作接口 | MER-API-026～031/041/043 | MER-API-028/029/041 |

---

## 六、测试数据建议

| 数据 | 说明 |
|------|------|
| 个人商户-仅主体 | 用于入驻、待完善详情 |
| 个人商户-经营完整 | 用于 openShopCheck/selectList 正向 |
| 企业商户-信用代码唯一 | 用于重复校验 |
| 冻结/注销商户 | 用于状态机与权限 |
| 高 shop_count 商户 | 用于 R1 无上限（≥5，由店铺测试数据准备，本文仅断言 openShopCheck） |
| 有店铺商户 | 用于删除阻断 |
| 证件/营业已过期主体 | 用于 warnExpired（MER-API-010, MER-UI-021） |
| **role_key=merchant** 的平台用户 | 用于 adminUserOptions、SYS_USER 绑定（MER-API-044/047） |
| 有 user_name（memberCode）+ nickName 的会员 | 用于 MEMBER 绑定（MER-API-048） |
| 会员 user_id 已绑定经营账号的会员 | 用于 MER-API-049 |
| `biz_region` 省市区表数据 | MER-API-051 树接口；MER-API-052 编辑落库 |

---

## 七、Playwright 执行说明（UI 用例）

**依赖：** Node.js 18+；`@playwright/test`；Chromium（`npx playwright install chromium`）。

**建议 `playwright.config.ts` 片段：**

```typescript
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
  testDir: './tests/merchant',
  use: {
    baseURL: process.env.BASE_URL || 'http://localhost:80',
    ...devices['Desktop Chrome'],
    locale: 'zh-CN',
  },
});
```

**登录与导航（示例）：**

```typescript
// tests/merchant/merchant-list.spec.ts
import { test, expect } from '@playwright/test';

test.beforeEach(async ({ page }) => {
  await page.goto('/login');
  await page.fill('input[name="username"]', process.env.ADMIN_USER!);
  await page.fill('input[name="password"]', process.env.ADMIN_PASS!);
  await page.click('button:has-text("登")');
  await page.getByRole('menuitem', { name: '商户管理' }).click(); // 按实际菜单文案调整
});

test('MER-UI-001 列表加载', async ({ page }) => {
  await expect(page.getByRole('columnheader', { name: '商户所属单位' })).toBeVisible();
});
```

> 选择器以 RuoYi-Vue 实际 DOM 为准；菜单路由、弹窗 class 实现后替换 `getByRole` / `data-testid`。UI 用例 **MER-UI-001～028** 与上表一一对应编写 spec 即可。

---

*文档版本：v1.4 · 关联《商户管理功能需求.md》v1.7、《商户管理技术方案.md》v1.7*
