from __future__ import annotations

from collections import Counter
from pathlib import Path

from openpyxl import load_workbook
from openpyxl.styles import Alignment, Font, PatternFill
from openpyxl.utils import get_column_letter


ROOT = Path(__file__).resolve().parents[1]
EXCEL_PATH = ROOT / "测试用例" / "SaaS_订阅邀请登录_测试功能点.xlsx"


COLUMNS = [
    "用例编号",
    "一级模块",
    "功能点",
    "用例标题",
    "优先级",
    "用例类型",
    "角色/入口",
    "前置条件",
    "测试数据",
    "操作步骤",
    "预期结果",
    "自动化建议",
    "需求依据",
]


def lines(items: list[str]) -> str:
    return "\n".join(f"{index}. {item}" for index, item in enumerate(items, 1))


CASES = [
    {
        "id": "SUB-TC-001",
        "module": "SaaS-admin 产品管理",
        "feature": "产品列表搜索",
        "title": "按产品编码精确搜索产品",
        "priority": "P0",
        "type": "功能/查询",
        "entry": "SaaS-admin / 产品管理-产品列表",
        "precondition": lines(["管理员账号可登录 SaaS-admin", "系统存在至少 2 个产品，且产品编码不同"]),
        "data": "产品编码：取已存在产品的完整产品编号；错误编码：不存在的产品编号",
        "steps": lines(["登录 SaaS-admin", "进入产品管理-产品列表", "在产品编码输入框输入已存在的完整产品编号", "点击查询", "清空产品编码，输入不存在的产品编号", "再次点击查询"]),
        "expected": lines(["首次查询仅返回编码完全匹配的产品", "列表字段展示完整且产品编号与查询值一致", "第二次查询结果为空或展示无数据态", "未输入产品编码时默认查询全部产品"]),
        "automation": "建议用 Page Object 封装产品列表查询；断言列表行数和目标产品编码。",
        "basis": "5.2 产品列表-产品编码精确搜索",
    },
    {
        "id": "SUB-TC-002",
        "module": "SaaS-admin 产品管理",
        "feature": "产品列表搜索",
        "title": "按产品名称模糊搜索产品",
        "priority": "P0",
        "type": "功能/查询",
        "entry": "SaaS-admin / 产品管理-产品列表",
        "precondition": lines(["管理员账号可登录 SaaS-admin", "系统存在名称包含相同关键字的产品"]),
        "data": "产品名称关键字：例如 123；预期匹配：123**、**123、**123**",
        "steps": lines(["进入产品管理-产品列表", "在产品名称输入框输入关键字", "点击查询", "查看列表所有返回行", "清空名称条件后再次查询"]),
        "expected": lines(["列表返回名称包含关键字的产品", "不包含关键字的产品不展示", "清空条件后恢复默认全部数据", "搜索过程中无前端报错或异常空白页"]),
        "automation": "可使用测试数据前缀创建产品后查询，避免环境数据波动。",
        "basis": "5.2 产品列表-产品名称模糊搜索",
    },
    {
        "id": "SUB-TC-003",
        "module": "SaaS-admin 产品管理",
        "feature": "产品列表搜索",
        "title": "按业务类型、状态、创建时间组合搜索",
        "priority": "P0",
        "type": "功能/查询",
        "entry": "SaaS-admin / 产品管理-产品列表",
        "precondition": lines(["存在订阅开通、指定开通、默认开通三类产品", "存在待发布、已上架、已下架产品", "存在不同创建时间的产品"]),
        "data": "业务类型：订阅开通/指定开通/默认开通；状态：待发布/已上架/已下架；创建时间范围：包含目标产品",
        "steps": lines(["进入产品列表", "选择一个或多个业务类型", "选择一个或多个状态", "选择创建时间范围", "点击查询", "逐行核对返回数据", "清空所有筛选条件并查询"]),
        "expected": lines(["返回数据同时满足业务类型、状态和创建时间条件", "多选条件按或关系匹配，同类条件外与其他筛选条件按与关系匹配", "未选择筛选条件时默认查询全部", "创建时间边界值包含起止日期内的数据"]),
        "automation": "适合做组合查询自动化；可拆成业务类型、状态、时间三个子用例。",
        "basis": "5.2 产品列表-多选与时间范围搜索",
    },
    {
        "id": "SUB-TC-004",
        "module": "SaaS-admin 产品管理",
        "feature": "产品列表字段展示",
        "title": "产品列表字段与操作入口展示正确",
        "priority": "P1",
        "type": "UI/展示",
        "entry": "SaaS-admin / 产品管理-产品列表",
        "precondition": lines(["管理员账号可登录", "列表中至少存在一个产品"]),
        "data": "任意产品数据",
        "steps": lines(["进入产品管理-产品列表", "查看列表表头", "查看任意产品行字段", "根据产品状态查看操作列按钮"]),
        "expected": lines(["表头包含产品编号、产品名称、开通方式、提供方类型、状态、产品描述、版本数量、订阅租户数、创建时间、更新时间", "字段值与产品详情一致", "待发布产品展示查看、编辑、发布、版本管理、删除", "已上架产品展示查看、下架", "已下架产品展示查看、编辑、版本管理、上架、删除"]),
        "automation": "适合作为低成本 UI 回归用例。",
        "basis": "5.1 产品状态机；5.2 产品列表字段",
    },
    {
        "id": "SUB-TC-005",
        "module": "SaaS-admin 产品管理",
        "feature": "新增产品基础信息",
        "title": "新增产品基础信息必填和格式校验",
        "priority": "P0",
        "type": "功能/表单校验",
        "entry": "SaaS-admin / 产品管理-添加产品",
        "precondition": lines(["管理员账号可登录", "已准备 JPG 或 PNG Logo 文件"]),
        "data": "产品名称：E2E订阅产品；描述：200字以内；支付方式：支付宝、微信支付；允许购买商户类型：企业商户",
        "steps": lines(["进入添加产品页", "不填写任何字段直接点击下一步", "逐项填写产品名称、提供方类型、Logo、产品描述、支付方式、允许购买商户类型、自动续费", "选择自动续费为到期提醒", "勾选续费提醒时间", "点击下一步"]),
        "expected": lines(["未填写时所有必填项出现校验提示", "产品名称必填且不超过20字", "产品描述必填且不超过200字", "Logo 支持 JPG/PNG 上传并回显", "支付方式和允许购买商户类型支持多选", "选择到期提醒后展示续费提醒多选字段", "填写合法数据后可进入订阅版本配置"]),
        "automation": "需要封装图片上传；建议准备固定测试 Logo。",
        "basis": "5.2.1.1 产品基础信息字段",
    },
    {
        "id": "SUB-TC-006",
        "module": "SaaS-admin 产品管理",
        "feature": "新增产品基础信息",
        "title": "产品名称唯一性和长度限制校验",
        "priority": "P0",
        "type": "功能/边界",
        "entry": "SaaS-admin / 产品管理-添加产品",
        "precondition": lines(["系统已存在名称为 E2E重复产品 的产品"]),
        "data": "重复名称：E2E重复产品；超长名称：超过20个字符的产品名称",
        "steps": lines(["进入添加产品页", "输入已存在产品名称并填写其他合法字段", "点击下一步或保存", "将产品名称改为超过20个字符", "再次提交"]),
        "expected": lines(["重复名称不允许提交，并提示名称已存在或类似提示", "超过20字的产品名称不允许提交或被输入限制", "提交失败后产品仍未创建", "页面保留用户已填写内容，便于修正"]),
        "automation": "可通过接口或后台先创建重复产品数据。",
        "basis": "5.2.1.1 产品名唯一、20字限制",
    },
    {
        "id": "SUB-TC-007",
        "module": "SaaS-admin 产品管理",
        "feature": "产品草稿与取消",
        "title": "添加产品时保存草稿和取消确认",
        "priority": "P1",
        "type": "功能/流程",
        "entry": "SaaS-admin / 产品管理-添加产品",
        "precondition": "管理员账号可登录",
        "data": "产品基础信息：合法但未配置完整版本",
        "steps": lines(["进入添加产品页", "填写部分或全部基础信息", "点击保存草稿", "返回产品列表搜索该产品", "重新进入编辑页", "修改字段后点击取消", "在二次确认弹窗点击取消关闭或确认离开"]),
        "expected": lines(["保存草稿成功，产品状态为待发布或草稿态", "草稿产品不在官网展示和售卖", "重新编辑时已保存字段正确回显", "点击取消出现二次确认", "取消关闭弹窗时仍停留当前页，确认离开时不保存未提交修改"]),
        "automation": "适合覆盖草稿状态和二次确认弹窗。",
        "basis": "5.2.1.1 操作按钮；3.1 产品发布流程",
    },
    {
        "id": "SUB-TC-008",
        "module": "订阅版本配置",
        "feature": "新增订阅版本",
        "title": "新增订阅版本必填字段校验",
        "priority": "P0",
        "type": "功能/表单校验",
        "entry": "SaaS-admin / 产品编辑-订阅版本配置",
        "precondition": lines(["产品基础信息已填写", "应用管理中存在至少一个可选应用"]),
        "data": "版本名称：基础版；基准月价：99；成员限制：15；试用天数：7；订阅时长：1/3/12个月；应用：任意一个应用",
        "steps": lines(["进入订阅版本配置页", "点击新增版本", "不填写字段直接保存", "填写版本名称、开通方式、基准月价、成员限制、免费试用天数", "添加订阅时长和应用", "保存版本"]),
        "expected": lines(["缺少必填字段时无法保存并展示提示", "版本名称在产品内唯一", "基准月价为数字且单位为元", "成员限制为数字且不超过999999", "试用天数为0表示不支持试用，大于0表示支持试用", "至少一条订阅时长且至少一个应用后保存成功"]),
        "automation": "版本弹窗建议封装为独立组件操作。",
        "basis": "5.2.1.2 订阅版本配置字段",
    },
    {
        "id": "SUB-TC-009",
        "module": "订阅版本配置",
        "feature": "订阅时长与折扣",
        "title": "订阅时长折扣价格自动计算",
        "priority": "P0",
        "type": "功能/计算",
        "entry": "SaaS-admin / 版本配置-订阅时长列表",
        "precondition": "已进入版本编辑页并填写基准月价",
        "data": "基准月价：100；时长：1、3、12；折扣：100、90、80",
        "steps": lines(["在订阅时长列表新增三条时长", "分别填写时长1个月折扣100、3个月折扣90、12个月折扣80", "查看实际价格字段", "保存版本后重新打开"]),
        "expected": lines(["1个月实际价格为100.00", "3个月实际价格为270.00", "12个月实际价格为960.00", "金额保留两位小数", "保存后重新打开价格仍正确"]),
        "automation": "适合做纯 UI 断言，也可补充单元/API 计算校验。",
        "basis": "5.2.1.2 订阅期限与折扣",
    },
    {
        "id": "SUB-TC-010",
        "module": "订阅版本配置",
        "feature": "订阅时长与折扣",
        "title": "订阅时长删除至少保留一条",
        "priority": "P0",
        "type": "功能/边界",
        "entry": "SaaS-admin / 版本配置-订阅时长列表",
        "precondition": "版本订阅时长列表中仅剩一条记录",
        "data": "订阅时长：1个月",
        "steps": lines(["打开版本编辑页", "定位订阅时长列表", "点击唯一一条订阅时长的删除按钮", "确认删除或观察提示"]),
        "expected": lines(["系统不允许删除唯一订阅时长", "Toast 提示“请最少保留一个订阅时长”", "订阅时长列表仍保留原记录", "版本不可在无订阅时长状态下保存发布"]),
        "automation": "直接断言 Toast 和列表行数。",
        "basis": "5.2.1.2 删除订阅时长限制",
    },
    {
        "id": "SUB-TC-011",
        "module": "订阅版本配置",
        "feature": "应用配置",
        "title": "按应用名称或编码搜索并添加应用",
        "priority": "P0",
        "type": "功能/配置",
        "entry": "SaaS-admin / 版本配置-应用配置",
        "precondition": "应用管理中存在可选应用",
        "data": "应用名称关键字；应用编码关键字",
        "steps": lines(["进入版本编辑页", "在可选应用搜索框输入应用名称关键字", "选择目标应用并添加", "再按应用编码关键字搜索另一个应用", "添加到已选应用", "删除其中一个已选应用"]),
        "expected": lines(["搜索结果按应用名称或编码模糊匹配", "添加后应用出现在已选应用列表", "已添加应用不应重复添加", "删除后应用从已选列表移除", "保存版本前至少保留一个已选应用"]),
        "automation": "需要准备稳定应用数据；可断言已选应用列表文本。",
        "basis": "5.2.1.2 应用配置",
    },
    {
        "id": "SUB-TC-012",
        "module": "订阅版本配置",
        "feature": "版本卡片操作",
        "title": "复制版本并校验副本内容",
        "priority": "P1",
        "type": "功能/配置",
        "entry": "SaaS-admin / 版本卡片列表",
        "precondition": "产品下存在一个配置完整的版本",
        "data": "原版本：基础版",
        "steps": lines(["进入产品版本配置页", "点击基础版卡片的复制按钮", "查看生成的版本副本", "打开副本编辑页", "修改版本名称后保存"]),
        "expected": lines(["系统基于原版本生成副本，可命名为“基础版副本”或类似名称", "副本继承原版本价格、成员限制、订阅时长、应用配置", "副本名称修改后可保存", "原版本内容不受副本修改影响"]),
        "automation": "可作为减少版本配置成本的回归用例。",
        "basis": "5.2.1.2 复制版本",
    },
    {
        "id": "SUB-TC-013",
        "module": "产品发布状态机",
        "feature": "新建产品发布/上架",
        "title": "配置完整的新产品发布为已上架",
        "priority": "P0",
        "type": "端到端/后台配置",
        "entry": "SaaS-admin / 产品创建与发布",
        "precondition": lines(["管理员账号可登录", "已准备产品基础信息、Logo、至少一个应用"]),
        "data": "开通方式：订阅开通；版本：基础版；订阅时长：1/3/12个月",
        "steps": lines(["创建产品并填写完整基础信息", "新增并保存一个启用版本", "配置价格、成员限制、存储空间、试用天数、订阅期限、应用", "点击发布", "在发布确认弹窗点击确认", "返回产品列表查询该产品"]),
        "expected": lines(["发布前完整性校验通过", "确认后产品状态变为已上架", "订阅开通类型产品可在官网订阅页展示", "产品列表版本数量、状态、更新时间正确更新", "操作日志记录新建或发布行为"]),
        "automation": "这是后续官网购买用例的数据前置核心链路。",
        "basis": "3.1 产品发布流程；5.2.2 发布产品",
    },
    {
        "id": "SUB-TC-014",
        "module": "产品发布状态机",
        "feature": "发布完整性校验",
        "title": "配置不完整产品发布失败",
        "priority": "P0",
        "type": "功能/异常",
        "entry": "SaaS-admin / 产品发布",
        "precondition": "存在基础信息不完整或无完整启用版本的产品",
        "data": "缺失项：无启用版本、无价格、无订阅期限、无应用、无支付方式",
        "steps": lines(["创建或编辑一个配置不完整产品", "点击发布或上架", "观察系统提示", "进入编辑页补齐缺失项", "再次发布"]),
        "expected": lines(["配置不完整时发布失败", "系统明确提示缺失配置并引导进入编辑页补全", "失败后产品保持原状态", "补齐配置后可发布成功"]),
        "automation": "建议参数化缺失项，形成多条自动化断言。",
        "basis": "5.2.2 发布/上架前置校验",
    },
    {
        "id": "SUB-TC-015",
        "module": "产品发布状态机",
        "feature": "下架产品",
        "title": "已上架产品下架后不再对官网展示",
        "priority": "P0",
        "type": "端到端/状态",
        "entry": "SaaS-admin / 产品列表或详情",
        "precondition": "存在已上架且官网可见的订阅开通产品",
        "data": "目标产品：已上架订阅产品",
        "steps": lines(["在官网价格页确认目标产品可见", "登录 SaaS-admin 进入产品列表", "点击目标产品下架", "在风险提示中确认", "刷新官网价格页", "返回后台产品列表查询"]),
        "expected": lines(["后台产品状态变为已下架", "官网价格页不再展示该产品", "后台仍可查询该产品详情", "已购买租户的存量订阅权益不受影响"]),
        "automation": "可覆盖后台到官网展示联动。",
        "basis": "5.2.2 下架；5.1 产品状态机",
    },
    {
        "id": "SUB-TC-016",
        "module": "产品发布状态机",
        "feature": "重新上架产品",
        "title": "已下架产品补齐配置后重新上架",
        "priority": "P0",
        "type": "功能/状态",
        "entry": "SaaS-admin / 产品列表或详情",
        "precondition": "存在已下架产品",
        "data": "目标产品：已下架产品",
        "steps": lines(["进入产品列表查询已下架产品", "点击上架", "如提示配置缺失则进入编辑页补齐", "再次点击上架并确认", "查看产品状态和官网展示"]),
        "expected": lines(["上架前执行与发布一致的完整性校验", "配置缺失时不允许上架", "配置完整后状态变为已上架", "订阅开通产品重新出现在官网订阅页"]),
        "automation": "适合与下架用例串联执行。",
        "basis": "5.2.2 上架",
    },
    {
        "id": "SUB-TC-017",
        "module": "产品发布状态机",
        "feature": "购买中下架校验",
        "title": "用户未支付前产品下架阻止继续购买",
        "priority": "P0",
        "type": "端到端/异常",
        "entry": "官网购买链路 + SaaS-admin 产品下架",
        "precondition": "产品已上架，用户已进入购买确认页但未支付",
        "data": "目标产品：订阅开通产品",
        "steps": lines(["用户在官网选择目标产品和版本，进入确认或支付前页面", "管理员在 SaaS-admin 下架该产品", "用户继续提交订单或点击支付", "观察购买页面提示"]),
        "expected": lines(["提交订单或支付前再次校验产品状态", "产品已下架时阻止购买", "页面提示“该产品已下架，暂不支持购买，请返回重新选择”", "不产生有效订阅"]),
        "automation": "需要两个会话或接口辅助下架；可作为高级 E2E。",
        "basis": "5.1 正在购买时下架",
    },
    {
        "id": "SUB-TC-018",
        "module": "产品发布状态机",
        "feature": "支付回调快照开通",
        "title": "已支付成功后产品下架仍按订单快照开通",
        "priority": "P0",
        "type": "端到端/支付回调",
        "entry": "官网支付回调",
        "precondition": "用户已完成支付，支付回调前产品被下架",
        "data": "目标产品订单快照",
        "steps": lines(["用户完成目标产品支付", "在支付回调处理前将产品下架", "触发或模拟支付成功回调", "查询订阅订单和租户权益"]),
        "expected": lines(["系统不因产品下架导致已支付订单开通失败", "按订单快照开通订阅", "订阅权益、人数上限、应用版本和到期时间与快照一致", "订单支付状态为已支付，订阅状态为生效中"]),
        "automation": "优先做接口自动化或支付沙箱回调测试。",
        "basis": "5.1 支付回调时产品下架规则",
    },
    {
        "id": "SUB-TC-019",
        "module": "产品编辑/删除",
        "feature": "编辑产品",
        "title": "编辑产品基础信息并发布变更",
        "priority": "P1",
        "type": "功能/配置",
        "entry": "SaaS-admin / 产品编辑页",
        "precondition": "存在允许编辑的产品",
        "data": "新的产品描述、支付方式、允许购买商户类型",
        "steps": lines(["进入产品详情", "点击编辑", "修改产品描述、支付方式等字段", "点击保存", "返回产品详情查看回显", "如产品已上架，点击发布变更"]),
        "expected": lines(["字段校验规则与新增一致", "保存成功后详情页回显新内容", "已上架产品的新配置对新订单生效", "历史订单和历史订阅快照不自动变更", "修改产品名称或支付方式时需要通知用户"]),
        "automation": "可断言详情回显和列表更新时间。",
        "basis": "5.2.3 编辑产品",
    },
    {
        "id": "SUB-TC-020",
        "module": "产品编辑/删除",
        "feature": "删除产品",
        "title": "有订阅记录的产品不允许删除",
        "priority": "P0",
        "type": "功能/异常",
        "entry": "SaaS-admin / 产品列表或详情",
        "precondition": "目标产品已有订阅记录",
        "data": "目标产品：已有订阅订单或订阅记录",
        "steps": lines(["进入产品列表", "定位已有订阅记录的产品", "点击删除", "确认删除操作"]),
        "expected": lines(["系统阻止删除", "Toast 提示“产品已有订阅记录，不允许删除，请下架产品”", "产品仍保留在列表中", "用户可改为执行下架操作"]),
        "automation": "需要准备已订阅产品数据。",
        "basis": "5.2.4 删除产品",
    },
    {
        "id": "SUB-TC-021",
        "module": "产品编辑/删除",
        "feature": "删除产品",
        "title": "无订阅记录的产品支持逻辑删除",
        "priority": "P1",
        "type": "功能/删除",
        "entry": "SaaS-admin / 产品列表或详情",
        "precondition": "目标产品无订阅记录",
        "data": "目标产品：新建未被购买的产品",
        "steps": lines(["进入产品列表", "定位无订阅记录产品", "点击删除", "在确认弹窗点击确认", "重新搜索该产品"]),
        "expected": lines(["删除操作成功", "产品执行逻辑删除", "默认产品列表不再展示该产品", "删除不影响其他产品和版本数据"]),
        "automation": "删除类用例使用专用 E2E 测试产品，避免误删共享数据。",
        "basis": "5.2.4 删除产品",
    },
    {
        "id": "SUB-TC-022",
        "module": "功能对比配置",
        "feature": "版本功能对比",
        "title": "配置功能对比后官网正确展示",
        "priority": "P1",
        "type": "后台配置+前台展示",
        "entry": "SaaS-admin / 功能对比配置；官网订阅页",
        "precondition": "产品存在至少两个版本且已上架",
        "data": "功能项：基础订单流、结算中心、对账中心等",
        "steps": lines(["进入产品功能对比配置页", "为不同版本配置功能项差异", "保存并发布配置", "打开官网订阅与定价页", "查看该产品的版本功能对比表"]),
        "expected": lines(["后台配置保存成功", "官网展示版本对比表", "每个版本的功能有无、文案与后台配置一致", "刷新页面后配置仍生效"]),
        "automation": "可先覆盖展示一致性，复杂表格可用文本断言。",
        "basis": "5.2.6 功能对比配置",
    },
    {
        "id": "SUB-TC-023",
        "module": "版本管理",
        "feature": "已上架版本权益变更",
        "title": "已上架版本增加应用后老客户自动获得权益",
        "priority": "P0",
        "type": "业务规则",
        "entry": "SaaS-admin / 版本管理",
        "precondition": lines(["产品已上架", "存在订阅该版本且未到期的老客户", "存在一个该版本未包含的应用"]),
        "data": "版本：专业版；新增应用：智能派单",
        "steps": lines(["进入版本管理", "编辑已上架版本", "为版本增加新应用", "点击保存", "在确认提示中点击确认", "使用老客户账号登录并查看应用权限", "使用新客户购买该版本后查看应用权限"]),
        "expected": lines(["保存前展示提示：新增应用将立即对新老客户生效", "确认后版本保存成功", "老客户无需升级或付费即可获得新增应用", "新客户购买该版本后自动包含新增应用"]),
        "automation": "需要账号和权限断言；可拆后台配置和前台权限校验两段。",
        "basis": "5.2.7 版本管理-增加应用",
    },
    {
        "id": "SUB-TC-024",
        "module": "版本管理",
        "feature": "已上架版本权益变更",
        "title": "已上架版本删除应用不影响老客户存量权益",
        "priority": "P0",
        "type": "业务规则",
        "entry": "SaaS-admin / 版本管理",
        "precondition": "存在已订阅该版本且未到期的老客户",
        "data": "版本：专业版；删除应用：任意已包含应用",
        "steps": lines(["进入版本管理编辑目标版本", "删除一个已选应用", "保存并确认", "老客户登录系统查看原应用入口", "新客户购买该版本后查看应用入口"]),
        "expected": lines(["版本保存成功", "老客户保留原有应用直到订阅到期", "新客户购买后不再包含被删除应用", "历史订单权益快照不被修改"]),
        "automation": "重点断言新老客户权益差异。",
        "basis": "5.2.7 删除应用影响规则",
    },
    {
        "id": "SUB-TC-025",
        "module": "版本管理",
        "feature": "价格变更",
        "title": "已上架版本涨价或降价不影响老客户历史价格",
        "priority": "P0",
        "type": "业务规则",
        "entry": "SaaS-admin / 版本管理；官网购买页",
        "precondition": "存在已订阅目标版本的老客户",
        "data": "原价：100；新价：120或80",
        "steps": lines(["进入版本管理编辑价格", "保存并发布变更", "老客户进入订阅管理查看当前订阅", "新客户打开官网价格页查看该版本价格", "老客户到期后或续费时查看价格规则"]),
        "expected": lines(["新价格对新客户展示生效", "老客户当前订阅不受影响", "历史订单仍保留原价快照", "老客户续费按需求规则展示原价续费或到期后按新价"]),
        "automation": "价格快照建议优先接口断言。",
        "basis": "5.2.7 涨价/降价规则",
    },
    {
        "id": "SUB-TC-026",
        "module": "版本管理",
        "feature": "删除订阅版本",
        "title": "删除版本不影响历史订购客户",
        "priority": "P0",
        "type": "业务规则/删除",
        "entry": "SaaS-admin / 版本管理",
        "precondition": "产品下存在多个版本，且其中一个版本已有历史订购客户",
        "data": "目标版本：已有历史订购记录的版本",
        "steps": lines(["进入版本管理", "点击目标版本删除", "在删除确认弹窗确认", "查看产品版本列表", "使用历史客户账号登录查看服务权益"]),
        "expected": lines(["删除弹窗提示将删除方案以及方案与产品关联关系", "删除成功后版本不再面向新购买展示", "历史订购客户仍可使用原方案权益", "历史订单快照不受影响"]),
        "automation": "删除动作需使用隔离测试版本。",
        "basis": "5.2.8 删除订阅版本",
    },
    {
        "id": "SUB-TC-027",
        "module": "版本管理",
        "feature": "删除订阅版本",
        "title": "产品仅剩一个版本时不允许删除",
        "priority": "P0",
        "type": "功能/边界",
        "entry": "SaaS-admin / 版本管理",
        "precondition": "目标产品仅有一个版本",
        "data": "目标版本：唯一版本",
        "steps": lines(["进入目标产品版本管理", "点击唯一版本的删除按钮", "确认删除或观察提示"]),
        "expected": lines(["系统不允许删除唯一版本", "提示发布时至少需要一个版本或类似提示", "版本列表仍保留该版本", "产品状态不受影响"]),
        "automation": "直接断言 Toast 和版本数量。",
        "basis": "5.2.1.3 版本删除限制",
    },
    {
        "id": "SUB-TC-028",
        "module": "版本管理",
        "feature": "启用/禁用订阅版本",
        "title": "禁用版本后新客户不可购买但老客户可继续使用",
        "priority": "P0",
        "type": "后台配置+前台展示",
        "entry": "SaaS-admin / 版本管理；官网价格页",
        "precondition": "产品已上架且目标版本启用，存在老客户订阅",
        "data": "目标版本：基础版",
        "steps": lines(["在官网价格页确认目标版本可选", "进入 SaaS-admin 版本管理", "点击目标版本禁用并确认", "刷新官网价格页", "老客户登录系统查看权益"]),
        "expected": lines(["版本状态变为禁用", "官网不再展示或不可选择禁用版本", "老客户服务完全不受影响直到订阅到期", "后台仍可查询该版本信息"]),
        "automation": "适合做状态配置和前台展示联动。",
        "basis": "5.2.9 启用、禁用订阅版本",
    },
    {
        "id": "SUB-TC-029",
        "module": "到期提醒",
        "feature": "到期提醒短信",
        "title": "按配置发送到期前提醒",
        "priority": "P1",
        "type": "通知/定时任务",
        "entry": "系统定时任务/短信服务",
        "precondition": "产品配置到期提醒，订阅距离到期时间满足提醒阈值",
        "data": "提醒时间：到期前1天、3天、7天、30天；手机号：SaaS主账号手机号",
        "steps": lines(["配置产品为到期提醒并勾选提醒时间", "准备一条即将到期的订阅", "触发定时任务或等待任务执行", "查询短信发送记录或通知记录"]),
        "expected": lines(["系统向 SaaS 用户主账号手机号发送提醒", "短信包含产品名称、用户姓名、套餐名称、到期月日、续费短链接", "未勾选的提醒时间不发送", "同一提醒节点不重复发送"]),
        "automation": "建议接口或任务级自动化，UI 只校验配置。",
        "basis": "5.2.10 到期提醒",
    },
    {
        "id": "SUB-TC-030",
        "module": "到期提醒",
        "feature": "续费成功通知",
        "title": "续费成功后发送确认通知并延长有效期",
        "priority": "P1",
        "type": "通知/续费",
        "entry": "支付回调/续费成功后",
        "precondition": "存在可续费订阅",
        "data": "套餐名称、产品名、新到期日",
        "steps": lines(["租户发起续费并完成支付", "等待或触发支付成功回调", "查询订阅有效期", "查询短信或通知发送记录"]),
        "expected": lines(["订阅有效期延长到新到期日", "发送续费成功确认通知", "通知包含产品名、套餐名称、新到期日", "订阅订单生成续费记录"]),
        "automation": "支付链路可用沙箱或模拟回调。",
        "basis": "5.2.10 续费成功确认",
    },
    {
        "id": "SUB-TC-031",
        "module": "订阅订单",
        "feature": "订阅订单列表",
        "title": "订阅订单默认展示与详情查看",
        "priority": "P0",
        "type": "功能/查询",
        "entry": "SaaS-admin / 产品管理-订阅订单",
        "precondition": "存在近3个月内和3个月前的订阅订单",
        "data": "订单类型：免费、试用、付费、指定开通、默认开通",
        "steps": lines(["登录 SaaS-admin", "进入产品管理-订阅订单", "查看默认列表排序和时间范围", "点击任意订单详情", "查看支付流水和权益快照"]),
        "expected": lines(["默认展示订单创建时间近3个月数据", "列表按订单创建时间降序排列", "详情展示订单完整信息、支付流水、服务开通信息、权益快照", "后台管理员不能在订单列表中代替用户发起续费"]),
        "automation": "可断言列表时间排序和详情字段。",
        "basis": "5.3 订阅订单列表",
    },
    {
        "id": "SUB-TC-032",
        "module": "订阅订单",
        "feature": "订阅订单导出",
        "title": "导出订阅订单字段正确",
        "priority": "P1",
        "type": "导出",
        "entry": "SaaS-admin / 产品管理-订阅订单",
        "precondition": "订阅订单列表存在数据",
        "data": "订单筛选条件：按需选择",
        "steps": lines(["进入订阅订单列表", "设置筛选条件", "点击导出", "下载并打开导出文件", "核对导出字段和数据范围"]),
        "expected": lines(["导出成功生成文件", "导出字段包含支付单号、支付方式、支付状态、开通用户等", "导出数据与当前筛选条件一致", "无权限或无数据时有明确提示"]),
        "automation": "Playwright 可监听下载并用 openpyxl 校验导出内容。",
        "basis": "5.3 产品管理-订阅订单导出",
    },
    {
        "id": "SUB-TC-033",
        "module": "订阅订单",
        "feature": "支付状态流转",
        "title": "待支付订单1小时超时关闭",
        "priority": "P0",
        "type": "状态/时间",
        "entry": "官网购买 + 订阅订单列表",
        "precondition": "用户创建待支付订单但不支付",
        "data": "待支付订单",
        "steps": lines(["用户在官网提交付费订单并进入支付页", "不完成支付", "等待或模拟订单超过1小时", "进入 SaaS-admin 订阅订单列表查询该订单", "尝试继续支付原订单"]),
        "expected": lines(["订单初始支付状态为待支付，订阅状态为未生效", "超过1小时后订单状态变为已取消或已关闭", "关闭后不可继续支付", "用户需要重新下单"]),
        "automation": "建议通过接口修改创建时间或触发关单任务，避免真实等待。",
        "basis": "5.3 待支付状态1小时关单",
    },
    {
        "id": "SUB-TC-034",
        "module": "订阅订单",
        "feature": "订阅状态判断",
        "title": "试用中、生效中、即将到期、已过期状态展示",
        "priority": "P0",
        "type": "状态判断",
        "entry": "SaaS-admin / 订阅订单列表",
        "precondition": "存在不同到期时间和试用状态的订阅",
        "data": "试用订阅、生效订阅、距离到期小于提醒阈值订阅、已过期订阅、待支付订单",
        "steps": lines(["进入订阅订单列表", "分别查询不同订阅数据", "查看订阅状态字段", "查看即将到期状态的剩余天数展示"]),
        "expected": lines(["试用期订单展示免费试用或试用中", "已开通且未进入提醒期展示生效中", "到期前小于提醒阈值展示即将到期并显示剩余天数", "超过到期时间展示已过期", "待支付订单展示未生效"]),
        "automation": "可通过构造不同到期时间的数据做参数化校验。",
        "basis": "5.3 订阅状态",
    },
    {
        "id": "SUB-TC-035",
        "module": "产品日志",
        "feature": "产品变更日志",
        "title": "产品新增、更新、新增版本产生日志并支持搜索",
        "priority": "P1",
        "type": "审计/查询",
        "entry": "SaaS-admin / 产品日志",
        "precondition": "管理员已执行新建产品、更新产品、新增版本操作",
        "data": "产品名称、操作人用户名或手机号、提交类型、提交时间",
        "steps": lines(["进入产品日志页面", "按产品名称模糊搜索", "按操作人用户名或手机号搜索", "选择提交类型", "选择提交时间范围", "查看列表字段"]),
        "expected": lines(["每次产品变更生成日志", "搜索条件过滤正确", "列表展示产品名称、版本号、包含应用、产品ID、操作人、提交类型、提交时间", "未输入条件时默认查询全部"]),
        "automation": "建议在创建产品用例后顺带校验日志。",
        "basis": "5.4 产品日志",
    },
    {
        "id": "SUB-TC-036",
        "module": "官网订阅购买",
        "feature": "官网价格页展示",
        "title": "官网仅展示已上架且订阅开通产品",
        "priority": "P0",
        "type": "前台展示",
        "entry": "官网 / SaaS价格页",
        "precondition": "存在已上架订阅开通产品、已上架指定开通产品、已下架订阅产品",
        "data": "不同状态和开通方式产品",
        "steps": lines(["打开官网 SaaS 价格页", "查看产品 Tab 列表", "搜索或浏览目标产品", "切换后台产品状态和开通方式后刷新官网"]),
        "expected": lines(["仅展示已上架且业务类型为订阅开通的产品", "指定开通、默认开通产品不对外展示", "已下架产品不展示", "后台状态变化后官网展示同步更新"]),
        "automation": "前后台联动核心展示用例。",
        "basis": "6 官网订阅购买流程",
    },
    {
        "id": "SUB-TC-037",
        "module": "官网订阅购买",
        "feature": "官网价格页展示",
        "title": "订阅版本、时长、价格、折扣和到期时间展示",
        "priority": "P0",
        "type": "前台展示",
        "entry": "官网 / SaaS价格页",
        "precondition": "产品已上架并配置多个版本和订阅时长",
        "data": "版本：基础版/商家版；时长：1/3/12个月；折扣：限时特惠",
        "steps": lines(["打开官网价格页", "选择目标产品 Tab", "查看订阅服务版本卡片", "切换不同订阅时长", "查看价格、折扣标识、到期时间、支付方式和订阅提示"]),
        "expected": lines(["版本卡片展示版本图标、名称、描述", "所有订阅时长展示时间和价格", "有折扣时展示标准价格划线和限时特惠标识", "默认按12个月展示价格；若无12个月则展示最小月份价格", "到期时间按选择时长计算并展示", "支付方式来自产品配置"]),
        "automation": "需封装官网价格页组件断言。",
        "basis": "6 官网订阅购买流程",
    },
    {
        "id": "SUB-TC-038",
        "module": "官网订阅购买",
        "feature": "立即购买校验",
        "title": "未登录购买跳转登录后回到原购买上下文",
        "priority": "P0",
        "type": "端到端/登录回跳",
        "entry": "官网 / 立即购买",
        "precondition": "用户未登录，官网存在可购买产品",
        "data": "产品、版本、订阅时长、支付方式",
        "steps": lines(["清空登录态打开官网价格页", "选择产品、版本、订阅时长和支付方式", "勾选服务协议", "点击立即购买", "在登录页完成登录", "观察登录成功后的页面和选择项"]),
        "expected": lines(["未登录时跳转登录页", "登录成功后回到原购买上下文", "产品、版本、订阅时长、支付方式等选择不丢失", "用户可继续进入支付流程"]),
        "automation": "需要复用现有登录 Page Object；登录章节虽不做，但购买链路必须校验回跳。",
        "basis": "3.2 客户购买与续费流程；6 立即购买按钮",
    },
    {
        "id": "SUB-TC-039",
        "module": "官网订阅购买",
        "feature": "立即购买校验",
        "title": "购买必选项和服务协议校验",
        "priority": "P0",
        "type": "功能/表单校验",
        "entry": "官网 / 立即购买",
        "precondition": "用户已登录且具备购买身份",
        "data": "不选择订阅服务、不选择时长、不选择支付方式、不勾选协议",
        "steps": lines(["打开官网价格页", "保持订阅服务未选择点击立即购买", "选择服务但不选订阅时长点击立即购买", "选择时长但不选支付方式点击立即购买", "不勾选服务协议点击立即购买"]),
        "expected": lines(["缺少订阅服务时提示请选择对应内容", "缺少订阅时长时提示请选择对应内容", "缺少支付方式时提示请选择对应内容", "未勾选服务协议时阻止提交并提示", "所有必选项完整后可进入支付页"]),
        "automation": "适合参数化校验 Toast 文案。",
        "basis": "6 立即购买按钮",
    },
    {
        "id": "SUB-TC-040",
        "module": "官网订阅购买",
        "feature": "配置变更校验",
        "title": "购买期间后台变更产品内容提示刷新重试",
        "priority": "P0",
        "type": "端到端/并发变更",
        "entry": "官网购买页 + SaaS-admin 产品编辑",
        "precondition": "用户已打开官网价格页并选择产品内容",
        "data": "目标产品版本或订阅时长",
        "steps": lines(["用户在官网选择产品、版本、订阅时长、支付方式", "管理员在后台修改该产品版本、时长或支付方式并发布变更", "用户不刷新页面直接点击立即购买"]),
        "expected": lines(["系统在提交时校验订阅服务、订阅时长、支付方式是否被变更", "发现变更后阻止继续支付", "Toast 提示“产品内容已更新，请刷新重试”", "刷新后展示最新配置"]),
        "automation": "需要两个会话；也可接口修改配置后触发前台提交。",
        "basis": "6 立即购买按钮",
    },
    {
        "id": "SUB-TC-041",
        "module": "官网订阅购买",
        "feature": "付费购买",
        "title": "付费订阅支付成功后开通服务",
        "priority": "P0",
        "type": "端到端/支付",
        "entry": "官网 / 支付页",
        "precondition": "用户已登录、为主账号、已完成商户认证，产品价格大于0",
        "data": "支付方式：微信扫码或支付宝扫码",
        "steps": lines(["打开官网价格页选择付费产品", "选择订阅服务、订阅时长、支付方式并勾选协议", "点击立即购买进入支付页", "完成或模拟支付成功", "在支付结果弹窗点击支付成功", "进入开通成功页", "登录后台查看订阅订单和租户权益"]),
        "expected": lines(["校验成功后跳转支付页", "支付成功后订单支付状态为已支付", "订阅状态为生效中", "系统自动开通订阅服务", "人数上限、应用版本权益、订阅到期时间按订单快照更新", "用户收到服务开通通知"]),
        "automation": "若无支付沙箱，可先做模拟回调或半自动用例。",
        "basis": "3.2 客户购买与续费流程；6 支付流程",
    },
    {
        "id": "SUB-TC-042",
        "module": "官网订阅购买",
        "feature": "支付结果弹窗",
        "title": "支付结果弹窗重新支付和支付成功按钮行为",
        "priority": "P1",
        "type": "功能/支付",
        "entry": "官网 / 支付页",
        "precondition": "用户已进入支付页且支付结果确认弹窗展示",
        "data": "待支付订单",
        "steps": lines(["在支付结果确认弹窗点击重新支付", "观察弹窗和支付二维码", "再次打开或等待支付结果弹窗", "点击支付成功", "查询支付结果"]),
        "expected": lines(["点击重新支付后弹窗隐藏，用户可继续扫码支付", "点击支付成功后页面刷新并查询支付结果", "若后端确认支付成功则跳转开通成功状态", "若未支付成功则保持待支付或提示未查询到支付成功"]),
        "automation": "可通过订单状态模拟支付成功/未成功两种结果。",
        "basis": "6 支付校验",
    },
    {
        "id": "SUB-TC-043",
        "module": "官网订阅购买",
        "feature": "0元开通",
        "title": "0元产品不拉起支付直接开通",
        "priority": "P0",
        "type": "端到端/免费开通",
        "entry": "官网 / 立即购买",
        "precondition": "存在实际价格为0的订阅产品或版本时长",
        "data": "价格：0元",
        "steps": lines(["用户登录官网价格页", "选择0元产品或0元订阅时长", "勾选服务协议", "点击立即购买", "查看跳转结果", "查询订阅订单"]),
        "expected": lines(["系统不拉起微信或支付宝支付", "直接进入开通成功页", "生成无需支付订单", "订阅状态生效，权益按0元订单快照开通"]),
        "automation": "非常适合自动化，避免支付依赖。",
        "basis": "6 立即购买按钮-价格为0",
    },
    {
        "id": "SUB-TC-044",
        "module": "免费试用",
        "feature": "申请免费试用",
        "title": "符合条件用户申请免费试用成功",
        "priority": "P0",
        "type": "端到端/试用",
        "entry": "官网 / 免费试用",
        "precondition": "产品配置免费试用天数大于等于1，用户符合试用规则",
        "data": "试用天数：7；试用版本：基础版",
        "steps": lines(["用户打开官网价格页", "点击免费试用", "如未登录则完成登录", "系统校验账号和租户试用资格", "确认开通试用", "查询订阅订单和租户权益"]),
        "expected": lines(["未登录时先登录，登录后回到试用上下文", "符合规则时开通试用订阅", "订单类型为试用或免费试用，支付状态为无需支付", "订阅状态为试用中", "租户权益按试用版本限制生效"]),
        "automation": "建议使用新租户账号，避免试用资格被占用。",
        "basis": "3.3 免费试用流程；6 免费试用流程",
    },
    {
        "id": "SUB-TC-045",
        "module": "免费试用",
        "feature": "试用资格校验",
        "title": "不符合试用规则时阻止重复试用",
        "priority": "P0",
        "type": "功能/异常",
        "entry": "官网 / 免费试用",
        "precondition": "当前账号或租户已使用过该产品试用资格",
        "data": "已试用租户账号",
        "steps": lines(["使用已试用账号登录官网", "进入价格页点击同一产品免费试用", "观察资格校验结果"]),
        "expected": lines(["系统识别账号或租户不符合试用规则", "阻止再次开通免费试用", "页面给出明确提示", "不生成新的试用订阅"]),
        "automation": "需要可复用的已试用账号或通过数据准备创建。",
        "basis": "3.3 免费试用流程",
    },
    {
        "id": "SUB-TC-046",
        "module": "免费试用",
        "feature": "试用转付费",
        "title": "试用期内转为付费套餐",
        "priority": "P0",
        "type": "端到端/升级",
        "entry": "官网或企业管理 / 订阅管理",
        "precondition": "租户处于试用期内",
        "data": "付费套餐：企业版；订阅时长：12个月",
        "steps": lines(["试用租户进入订阅管理或官网价格页", "选择付费套餐和订阅时长", "完成支付或模拟支付成功", "查询当前订阅版本和到期时间"]),
        "expected": lines(["试用期内允许转付费套餐", "支付成功后当前生效版本切换为付费套餐", "试用限制被付费权益替换", "订单和订阅记录可追溯试用转付费过程"]),
        "automation": "可与0元试用和支付回调组合。",
        "basis": "3.3 免费试用流程",
    },
    {
        "id": "SUB-TC-047",
        "module": "免费试用",
        "feature": "试用到期限制",
        "title": "试用到期未付费按到期规则限制使用",
        "priority": "P0",
        "type": "状态/权限",
        "entry": "租户工作台/应用入口",
        "precondition": "租户试用订阅已超过到期时间且未付费",
        "data": "已过期试用订阅",
        "steps": lines(["使用试用过期租户账号登录系统", "访问试用版本内应用菜单", "访问超出限制的功能或资源", "查看订阅管理页状态"]),
        "expected": lines(["订阅状态展示已过期或试用已过期", "系统按到期规则限制使用", "引导用户续费或购买付费套餐", "不会继续享受付费或试用权益"]),
        "automation": "需要时间模拟或直接准备过期数据。",
        "basis": "3.3 免费试用流程",
    },
    {
        "id": "SUB-TC-048",
        "module": "购买身份校验",
        "feature": "主账号与商户认证",
        "title": "非主账号或未认证商户购买被阻止",
        "priority": "P0",
        "type": "权限/身份",
        "entry": "官网 / 点击购买",
        "precondition": "存在主账号、子账号、已认证和未认证商户账号",
        "data": "账号A：子账号；账号B：主账号未认证；账号C：主账号已认证",
        "steps": lines(["使用子账号登录官网并点击购买", "使用未认证主账号登录官网并点击购买", "使用已认证主账号登录官网并点击购买"]),
        "expected": lines(["子账号购买被阻止并提示需主账号操作", "未认证主账号购买被阻止并提示完成商户认证", "已认证主账号可通过身份校验进入购买后续流程"]),
        "automation": "可复用现有企业认证和成员账号数据。",
        "basis": "6 身份校验",
    },
    {
        "id": "SUB-TC-049",
        "module": "订阅周期",
        "feature": "自然月到期时间计算",
        "title": "月付、季付、年付按自然月计算到期时间",
        "priority": "P0",
        "type": "规则/计算",
        "entry": "订阅开通/续费",
        "precondition": "系统支持指定或模拟开通时间",
        "data": "开通时间：2026-06-12 15:30；月付、季付、年付",
        "steps": lines(["以2026-06-12 15:30开通月付订阅", "以同一时间开通季付订阅", "以同一时间开通年付订阅", "查询订阅到期时间"]),
        "expected": lines(["月付到期时间为2026-07-12 15:30", "季付到期时间为2026-09-12 15:30", "年付到期时间为2027-06-12 15:30", "系统不按固定30天或31天简单累加"]),
        "automation": "优先接口或单元测试，UI 只验证展示。",
        "basis": "3.4 订阅周期",
    },
    {
        "id": "SUB-TC-050",
        "module": "订阅周期",
        "feature": "月末到期时间计算",
        "title": "目标月份无对应日期时取月末",
        "priority": "P0",
        "type": "规则/边界",
        "entry": "订阅开通/续费",
        "precondition": "系统支持指定或模拟开通时间",
        "data": "开通时间：2026-01-31；闰年开通时间：2028-01-31",
        "steps": lines(["以2026-01-31开通月付订阅", "查询到期时间", "以2028-01-31开通月付订阅", "查询到期时间"]),
        "expected": lines(["2026-01-31月付到期时间为2026-02-28", "2028-01-31月付到期时间为2028-02-29", "闰年和非闰年月末规则正确"]),
        "automation": "作为边界规则必测，建议接口级自动化。",
        "basis": "3.4 特殊日期按月末处理",
    },
    {
        "id": "SUB-TC-051",
        "module": "订阅升级/调整",
        "feature": "同一产品仅一个生效版本",
        "title": "同一商户同一产品升级后立即切换生效版本",
        "priority": "P0",
        "type": "业务规则/升级",
        "entry": "官网购买或 SaaS-admin 手动调整订阅",
        "precondition": "商户已开通某产品基础版且未到期",
        "data": "原版本：基础版；新版本：企业版；新订阅时长：3个月",
        "steps": lines(["查询商户当前生效订阅版本为基础版", "用户购买企业版或管理员手动调整为企业版", "支付或调整成功后查询订阅记录", "登录商户账号查看应用权益"]),
        "expected": lines(["同一商户同一产品同一时间只存在一个生效版本", "升级后当前生效版本立即切到企业版", "不会同时生效基础版和企业版", "企业版权益立即可用"]),
        "automation": "可通过后台调整订阅先实现，支付升级作为后续增强。",
        "basis": "3.5 订阅更换产品版本",
    },
    {
        "id": "SUB-TC-052",
        "module": "开通方式",
        "feature": "指定开通",
        "title": "SaaS-admin 对指定商户开通指定开通版本",
        "priority": "P0",
        "type": "后台业务",
        "entry": "SaaS-admin / 版本管理-指定开通",
        "precondition": "产品已上架，版本启用且配置完整，开通方式为指定开通",
        "data": "指定商户：已认证商户；版本：指定开通版",
        "steps": lines(["进入产品版本管理", "确认指定开通按钮仅在指定开通版本展示", "点击指定开通", "选择目标商户", "提交开通", "使用目标商户账号登录查看权益", "打开官网价格页查看该版本"]),
        "expected": lines(["只有 SaaS-admin 可见指定开通按钮", "开通前校验产品已上架、版本启用且配置完整", "提交后目标商户获得该产品订阅权益", "指定开通版本不在官网对外展示"]),
        "automation": "适合后台业务自动化，需商户选择控件封装。",
        "basis": "5.2 开通方式区分",
    },
    {
        "id": "SUB-TC-053",
        "module": "开通方式",
        "feature": "默认开通",
        "title": "购买其他产品成功后默认开通关联产品",
        "priority": "P1",
        "type": "业务联动",
        "entry": "官网购买/默认开通配置",
        "precondition": "存在默认开通产品，且另一个订阅产品可购买",
        "data": "购买产品A；默认开通产品B",
        "steps": lines(["确认产品B开通方式为默认开通且不在官网展示", "用户购买产品A并完成支付", "查询用户订阅列表", "查看产品B订阅时间和权益"]),
        "expected": lines(["默认开通产品不对外展示", "购买产品A成功后产品B自动开通", "产品B开通时间跟随产品A的订阅时间", "后续开通新产品时默认开通产品按规则更新"]),
        "automation": "规则略依赖实现细节，建议先做接口级校验。",
        "basis": "5.2 开通方式区分-默认开通",
    },
    {
        "id": "SUB-TC-054",
        "module": "企业管理",
        "feature": "续费/增购",
        "title": "丸友集和丸掌柜授权时间与版本订阅时间关联",
        "priority": "P1",
        "type": "业务联动",
        "entry": "企业管理 / 续费、增购",
        "precondition": "租户已开通丸友集或丸掌柜应用授权和版本订阅",
        "data": "续费时长：1个月或12个月；增购内容：按实际页面配置",
        "steps": lines(["进入企业管理续费或增购页面", "查看当前应用授权时间和版本订阅时间", "执行续费或增购操作", "完成支付或后台确认", "再次查看授权时间和订阅时间"]),
        "expected": lines(["续费或增购成功", "丸友集和丸掌柜的授权时间与版本订阅时间保持关联一致", "不会出现应用授权有效但版本订阅过期或相反的不一致状态", "订阅订单记录本次续费或增购"]),
        "automation": "需求描述较简，需要实测页面后补充具体字段。",
        "basis": "7 企业管理（续费、增购）",
    },
    {
        "id": "SUB-TC-055",
        "module": "商户认证优化",
        "feature": "认证详情字段展示",
        "title": "SaaS-admin 查看企业商户认证完整信息",
        "priority": "P0",
        "type": "功能/展示",
        "entry": "SaaS-admin / 商户管理-商户认证-查看",
        "precondition": "存在企业商户认证记录，且提交了认证资质图片和补充说明",
        "data": "企业商户认证资料",
        "steps": lines(["登录 SaaS-admin", "进入商户管理-商户认证", "查询企业商户认证记录", "点击查看", "核对详情字段"]),
        "expected": lines(["详情页展示企业商户审核信息", "认证资质图片可查看", "补充说明正确展示", "已有审核字段不缺漏", "页面无图片加载失败或字段空白异常"]),
        "automation": "可断言字段标签和关键值；图片可断言 img src 或预览入口。",
        "basis": "8 商户认证优化",
    },
    {
        "id": "SUB-TC-056",
        "module": "商户认证优化",
        "feature": "认证详情字段展示",
        "title": "SaaS-admin 查看个体工商户认证完整信息",
        "priority": "P0",
        "type": "功能/展示",
        "entry": "SaaS-admin / 商户管理-商户认证-查看",
        "precondition": "存在个体工商户认证记录，且提交经营规模、认证资质图片、补充说明",
        "data": "个体工商户认证资料",
        "steps": lines(["登录 SaaS-admin", "进入商户管理-商户认证", "查询个体工商户认证记录", "点击查看", "核对详情字段"]),
        "expected": lines(["详情页展示个体工商户审核信息", "经营规模字段展示正确", "认证资质图片可查看", "补充说明正确展示", "字段完整无缺漏"]),
        "automation": "与企业商户详情可共用页面对象。",
        "basis": "8 商户认证优化",
    },
    {
        "id": "SUB-TC-057",
        "module": "商户认证优化",
        "feature": "拒绝后重新认证",
        "title": "商户认证被拒绝后支持重新提交并新增认证记录",
        "priority": "P0",
        "type": "端到端/认证",
        "entry": "SaaS 商户认证页 + SaaS-admin 审核",
        "precondition": "商户已有一条被拒绝的认证记录",
        "data": "新的认证资料",
        "steps": lines(["使用商户账号进入认证页面", "查看被拒绝状态和重新认证入口", "重新填写并提交认证资料", "登录 SaaS-admin 查询该商户认证记录", "查看认证记录数量和最新记录状态"]),
        "expected": lines(["拒绝后商户可重新认证", "提交成功后新增一条认证数据", "原拒绝记录保留不被覆盖", "最新记录进入待审核或对应状态", "管理员可查看新旧认证记录"]),
        "automation": "可复用现有企业认证 E2E 能力扩展。",
        "basis": "8 商户认证优化",
    },
    {
        "id": "SUB-TC-058",
        "module": "权限管理",
        "feature": "角色权限控制菜单",
        "title": "角色权限控制 SaaS-admin 页面菜单可见性",
        "priority": "P1",
        "type": "权限",
        "entry": "SaaS-admin / 系统管理-角色管理",
        "precondition": "存在可配置菜单权限的角色和对应账号",
        "data": "角色A：有产品管理权限；角色B：无产品管理权限",
        "steps": lines(["管理员进入角色管理", "为角色A授予产品管理菜单权限", "为角色B取消产品管理菜单权限", "分别使用角色A和角色B账号登录", "查看侧边菜单和页面访问权限"]),
        "expected": lines(["角色A可见并可访问产品管理菜单", "角色B不可见产品管理菜单", "角色B直接访问产品管理URL时被拒绝或跳转无权限页", "权限变更后重新登录或刷新生效"]),
        "automation": "已有权限探测脚本可复用。",
        "basis": "8 角色权限说明",
    },
    {
        "id": "SUB-TC-059",
        "module": "登录样式",
        "feature": "动态主题与默认样式",
        "title": "登录页根据 code 展示方案主题，异常时使用默认样式",
        "priority": "P2",
        "type": "展示/观察项",
        "entry": "登录页 / URL code",
        "precondition": "存在至少一个方案 code；登录章节当前标注不做",
        "data": "有效 code；无效 code；模拟配置为空或网络错误",
        "steps": lines(["使用有效 code 访问登录页", "查看方案名称、Logo、主题色", "使用无效 code 访问登录页", "模拟配置为空或网络错误后访问登录页"]),
        "expected": lines(["有效 code 时展示对应方案名称、Logo 和主题色", "无效 code 或异常情况不阻断页面展示", "系统展示默认登录样式", "不影响用户继续登录"]),
        "automation": "需求标注不做，当前仅保留为低优先级观察项。",
        "basis": "9 登录（不做）；9.1 登录页配置项",
    },
]


def style_cases(ws) -> None:
    header_fill = PatternFill("solid", fgColor="1F4E78")
    header_font = Font(color="FFFFFF", bold=True)
    for cell in ws[1]:
        cell.fill = header_fill
        cell.font = header_font
        cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
    widths = [14, 20, 22, 34, 10, 16, 30, 42, 34, 54, 54, 34, 26]
    for index, width in enumerate(widths, 1):
        ws.column_dimensions[get_column_letter(index)].width = width
    for row in ws.iter_rows(min_row=2):
        for cell in row:
            cell.alignment = Alignment(vertical="top", wrap_text=True)
    ws.freeze_panes = "A2"
    ws.auto_filter.ref = ws.dimensions


def style_summary(ws) -> None:
    for cell in ws[1]:
        cell.fill = PatternFill("solid", fgColor="548235")
        cell.font = Font(color="FFFFFF", bold=True)
        cell.alignment = Alignment(horizontal="center", vertical="center")
    for index, width in enumerate([24, 12, 12, 12, 12], 1):
        ws.column_dimensions[get_column_letter(index)].width = width
    for row in ws.iter_rows(min_row=2):
        for cell in row:
            cell.alignment = Alignment(vertical="top", wrap_text=True)


def main() -> None:
    wb = load_workbook(EXCEL_PATH)
    if "测试功能点" in wb.sheetnames:
        ws = wb["测试功能点"]
        ws.title = "原功能点备份"
    for sheet_name in ["测试用例", "用例汇总"]:
        if sheet_name in wb.sheetnames:
            del wb[sheet_name]

    cases_ws = wb.create_sheet("测试用例", 0)
    cases_ws.append(COLUMNS)
    for case in CASES:
        cases_ws.append(
            [
                case["id"],
                case["module"],
                case["feature"],
                case["title"],
                case["priority"],
                case["type"],
                case["entry"],
                case["precondition"],
                case["data"],
                case["steps"],
                case["expected"],
                case["automation"],
                case["basis"],
            ]
        )
    style_cases(cases_ws)

    summary_ws = wb.create_sheet("用例汇总", 1)
    summary_ws.append(["一级模块", "用例数", "P0", "P1", "P2"])
    modules = Counter(case["module"] for case in CASES)
    priorities_by_module: dict[str, Counter[str]] = {}
    for case in CASES:
        priorities_by_module.setdefault(case["module"], Counter())[case["priority"]] += 1
    for module, count in modules.items():
        priorities = priorities_by_module[module]
        summary_ws.append([module, count, priorities["P0"], priorities["P1"], priorities["P2"]])
    style_summary(summary_ws)

    wb.save(EXCEL_PATH)
    print(f"updated={EXCEL_PATH}")
    print(f"cases={len(CASES)}")


if __name__ == "__main__":
    main()
