# Generate layered Excel testcases: atomic cases from e2e/tests, then composed E2E flows.
$ErrorActionPreference = 'Stop'

$Root = (Resolve-Path '.').Path
$Output = Join-Path $Root 'SaaS_E2E测试用例_脚本小用例与组合流程版.xlsx'

function Steps {
    param([string[]]$Items)
    $result = @()
    for ($i = 0; $i -lt $Items.Count; $i++) {
        $result += ('{0}. {1}' -f ($i + 1), $Items[$i])
    }
    return ($result -join "`n")
}

function Add-Sheet {
    param($Workbook, [string]$Name, [object[]]$Headers, [object[]]$Rows)
    $ws = $Workbook.Worksheets.Add([Type]::Missing, $Workbook.Worksheets.Item($Workbook.Worksheets.Count))
    $ws.Name = $Name
    for ($c = 0; $c -lt $Headers.Count; $c++) {
        $ws.Cells.Item(1, $c + 1).Value2 = [string]$Headers[$c]
    }
    for ($r = 0; $r -lt $Rows.Count; $r++) {
        $row = $Rows[$r]
        for ($c = 0; $c -lt $row.Count; $c++) {
            $ws.Cells.Item($r + 2, $c + 1).Value2 = [string]$row[$c]
        }
    }
    $ws.Rows.Item(1).Font.Bold = $true
    $ws.Rows.Item(1).Interior.ColorIndex = 37
    $ws.UsedRange.WrapText = $true
    $ws.UsedRange.VerticalAlignment = -4160
    $ws.Columns.AutoFit() | Out-Null
    for ($c = 1; $c -le $ws.UsedRange.Columns.Count; $c++) {
        if ($ws.Columns.Item($c).ColumnWidth -gt 60) {
            $ws.Columns.Item($c).ColumnWidth = 60
        }
    }
    return $ws
}

$atomicHeaders = @(
    '小用例ID','来源脚本','自动化函数/方法','小用例名称','归属阶段','前置条件','测试数据/入参','操作步骤','预期结果','产出数据/状态','可被哪些大流程复用','自动化状态'
)

$atomicRows = @(
    @('A-SAAS-001','test_e2e_saas_001_enterprise_certification.py','test_租户注册企业并由主站完成商户认证','租户企业注册并由主站认证','企业准备',
        '测试手机号可用；租户注册入口可访问；SaaS主站管理员账号可登录。',
        '手机号、企业名称、账号密码；主站账号=settings.admin_username/settings.admin_password。',
        (Steps @(
            '打开租户平台注册/企业入驻入口。',
            '填写手机号并获取验证码。',
            '填写企业名称、联系人、账号密码等注册信息。',
            '提交注册和认证资料。',
            'SaaS主站管理员登录。',
            '进入企业认证审核列表。',
            '搜索新注册企业。',
            '查看认证详情并审核通过。',
            '租户平台使用注册账号重新登录。',
            '查看企业认证状态。'
        )),
        '租户企业注册成功；主站审核通过；租户端展示认证通过状态。',
        '台账新增企业账号；certification_status=approved。',
        'F-REG-001、F-MAIN-001','已自动化'
    ),
    @('A-SAAS-003','test_e2e_saas_003_wanyouji_app_opening.py','test_已认证商户申请开通丸友集并由主站审批','已认证企业开通丸友集应用','应用开通',
        '企业已认证；租户账号可登录；主站管理员可审批应用。',
        '应用=丸友集；企业账号来自台账。',
        (Steps @(
            '租户平台登录已认证企业。',
            '进入订阅购买页/应用中心。',
            '选择丸友集应用。',
            '提交开通申请。',
            'SaaS主站管理员登录。',
            '进入应用开通审核列表。',
            '搜索该企业的丸友集申请。',
            '审批通过。',
            '租户平台刷新应用页。',
            '确认丸友集展示已开通或可进入。'
        )),
        '丸友集申请可审批；审批后租户端入口可用。',
        'wanyouji_status=opened。',
        'F-REG-001、F-MAIN-001','已自动化'
    ),
    @('A-SAAS-004','test_e2e_saas_004_wanzhanggui_app_opening.py','test_已认证商户申请开通丸掌柜并由主站审批','已认证企业开通丸掌柜应用','应用开通',
        '企业已认证；租户账号可登录；主站管理员可审批应用。',
        '应用=丸掌柜；企业账号来自台账。',
        (Steps @(
            '租户平台登录已认证企业。',
            '进入订阅购买页/应用中心。',
            '选择丸掌柜应用。',
            '提交开通申请。',
            'SaaS主站管理员登录。',
            '进入应用开通审核列表。',
            '搜索该企业的丸掌柜申请。',
            '审批通过。',
            '租户平台刷新应用页。',
            '确认丸掌柜展示已开通或可进入。'
        )),
        '丸掌柜申请可审批；审批后租户端入口可用。',
        'wanzhanggui_status=opened。',
        'F-REG-001、F-MAIN-001','已自动化'
    ),
    @('A-AUTH-001','test_e2e_auth_001_authorization_closed_loop.py','test_丸友集生成密钥并绑定丸掌柜后完成权限配置闭环','丸友集授权密钥生成、丸掌柜激活并保存权限','授权绑定',
        '丸友集企业和丸掌柜企业均已开通；两个主账号可登录。',
        '丸友集账号；丸掌柜账号；auth_key；权限至少包含创建订单。',
        (Steps @(
            '丸友集主账号登录。',
            '进入企业管理-授权管理-授权密钥。',
            '生成丸友集授权密钥。',
            '记录密钥值和授权方企业。',
            '丸掌柜主账号登录。',
            '进入企业管理-授权管理-我的授权。',
            '激活丸友集授权密钥。',
            '丸友集主账号重新登录。',
            '进入管理授权/权限配置。',
            '保存授权权限。',
            '丸掌柜侧校验已获得创建订单等授权能力。'
        )),
        '授权密钥可生成并激活；丸友集侧权限保存成功；丸掌柜侧具备创建订单权限。',
        'latest_auth_key；授权关系；授权权限已保存。',
        'F-CONFIG-001、F-MAIN-001','已自动化'
    ),
    @('A-PUSH-001','test_e2e_push_001_wanzhanggui_workshop_authorization.py','test_丸掌柜新增丸友集工作室授权','丸掌柜新增丸友集工作室授权','推单配置前置',
        '丸掌柜已激活丸友集授权；丸友集账号未被该丸掌柜添加工作室授权。',
        'wanzhanggui_phone；wanyouji enterprise_name。',
        (Steps @(
            '丸掌柜主账号登录。',
            '进入工作室管理。',
            '点击新增/添加工作室授权。',
            '选择已绑定的丸友集企业或授权关系。',
            '选择丸友集工作室。',
            '按页面要求补充授权字段。',
            '保存授权。',
            '返回工作室管理列表。',
            '确认新增工作室授权记录展示可用。'
        )),
        '丸友集工作室授权添加成功；后续推单配置可选择该工作室。',
        'workshop_authorized=true。',
        'F-CONFIG-001、F-MAIN-001','已自动化'
    ),
    @('A-PUSH-003','test_e2e_push_003_dispatch_strategy.py','test_丸掌柜新增推单配置策略','丸掌柜新增推单配置策略','推单配置',
        '丸掌柜已添加足够数量的丸友集工作室授权；策略类型符合脚本参数。',
        'strategy_type=单平台指定/多工作室同时分发/多平台分发；strategy_name；workshop_enterprise_names。',
        (Steps @(
            '丸掌柜主账号登录。',
            '进入推单配置。',
            '点击新增推单配置。',
            '填写策略名称。',
            '选择策略类型。',
            '按策略类型选择1个或多个已授权丸友集工作室。',
            '填写页面必填配置项。',
            '保存策略。',
            '返回推单配置列表。',
            '确认策略名称、策略类型和工作室展示正确。'
        )),
        '策略新增成功；策略状态可用；后续手工录单可选择该策略。',
        'dispatch_strategy_created；strategy_name；strategy_type。',
        'F-CONFIG-001、F-MAIN-001、F-BRANCH-DISPATCH-001','已自动化'
    ),
    @('A-ORDER-001','test_e2e_order_001_manual_order_dispatch_closed_loop.py','test_丸掌柜手工录单发单并在丸友集抢单大厅验证闭环','丸掌柜手工录单创建外来订单并发布到丸友集','订单创建/发单',
        '丸掌柜已开通；已存在可用推单策略；相关丸友集账号可登录。',
        'source_order_no=E2EORDER时间戳；order_title；strategy_name；strategy_type。',
        (Steps @(
            '丸掌柜主账号登录。',
            '进入商家管理-订单管理-手工录单。',
            '点击新增订单。',
            '填写来源单号source_order_no。',
            '填写订单标题order_title。',
            '添加子单。',
            '选择推单策略strategy_name。',
            '填写子单金额、数量、服务项目等页面必填项。',
            '提交/保存订单。',
            '丸友集相关账号登录。',
            '进入工作室履约应用-抢单大厅。',
            '按source_order_no或order_title搜索订单。',
            '确认订单在预期范围展示：单平台指定为私单，多工作室/多平台为公池单或私单。'
        )),
        '丸掌柜订单提交成功；丸友集抢单大厅可见该外来订单。',
        'dispatched_orders新增；status=dispatched。',
        'F-MAIN-001、F-DISPATCH-001、F-CANCEL-001','已自动化'
    ),
    @('A-ORDER-002','test_e2e_order_002_revoke_dispatch_closed_loop.py','test_丸掌柜撤销派单并在丸友集验证订单不可见','丸掌柜撤销已派单订单并验证丸友集不可见','撤销',
        '台账存在status=dispatched的订单；订单尚未被丸友集管理员接单。',
        'source_order_no；order_title；strategy_type；expected_scope。',
        (Steps @(
            '从台账查找最新已派单订单。',
            '丸掌柜主账号登录。',
            '进入商家管理-订单管理。',
            '按source_order_no或order_title搜索订单。',
            '执行撤销派单。',
            '按页面要求确认撤销。',
            '丸友集相关账号逐个登录。',
            '进入工作室履约应用-抢单大厅。',
            '按source_order_no或order_title搜索。',
            '确认订单不在预期范围内展示。',
            '台账标记订单已撤销。'
        )),
        '撤销后丸友集抢单大厅不可见；不会继续进入打手履约链路。',
        'status=revoked。',
        'F-CANCEL-001','已自动化'
    ),
    @('A-TASK-001','test_e2e_task_001_external_order_dispatch_to_player.py','test_丸掌柜外部派单由丸友集接单并发到打手抢单大厅','丸友集接外部订单并发给打手，打手抢单','接单/分发/打手抢单',
        '台账存在status=dispatched或task_accepted/task_dispatched_to_player_hall的订单；丸友集主账号和打手账号可登录。',
        'source_order_no；order_title；assignment_method=抢单大厅/指派打手/智能派单；player_account。',
        (Steps @(
            '丸友集主账号登录。',
            '进入工作室履约应用-抢单大厅。',
            '搜索外部订单。',
            '如订单尚未接入，点击立即接单。',
            '确认订单进入工作室订单列表。',
            '如尚未发给打手，进入订单列表并点击立即发单。',
            '选择或确认assignment_method。',
            '填写发单价、服务时限、订单标题等必填项。',
            '提交发单。',
            '打手子账号短信登录。',
            '进入打手应用-抢单大厅。',
            '搜索订单并立即抢单。',
            '进入打手应用-我的订单。',
            '确认订单出现在我的订单。'
        )),
        '丸友集管理员接单成功；订单发到打手侧；打手抢单成功并进入我的订单。',
        'status依次为task_accepted、task_dispatched_to_player_hall、task_player_accepted。',
        'F-MAIN-001、F-DISPATCH-001、F-BRANCH-DISPATCH-001','已自动化'
    ),
    @('A-DELIVERY-001','test_e2e_delivery_001_player_submit_acceptance.py','test_打手确认上号上传首图并提交验收后主账号查看','打手提交验收，主账号查看待验收','打手履约',
        '台账存在status=task_player_accepted的订单；打手账号和丸友集主账号可登录。',
        'source_order_no；order_title；player_account；wanyouji_account。',
        (Steps @(
            '打手子账号短信登录。',
            '进入打手应用-我的订单。',
            '搜索已接订单。',
            '打开订单详情。',
            '执行确认上号。',
            '上传首图或页面要求的履约凭证。',
            '提交验收。',
            '丸友集主账号登录。',
            '进入工作室履约应用-订单列表。',
            '搜索同一订单。',
            '确认订单状态为待验收或可申请上游验收。'
        )),
        '打手提交验收成功；丸友集主账号能在订单列表看到待验收/申请上游验收状态。',
        'status=delivery_submitted。',
        'F-MAIN-001、F-SETTLE-001','已自动化'
    ),
    @('A-DELIVERY-002','test_e2e_delivery_002_upstream_acceptance_settlement.py','test_丸友集申请上游验收丸掌柜完结主单并校验收入','申请上游验收、丸掌柜完结主单、打手收入验证','验收/结算/收入',
        '台账存在status=delivery_submitted或upstream_acceptance_applied的订单。',
        'source_order_no；order_title；wanyouji_account；wanzhanggui_account；player_account；expected_amount。',
        (Steps @(
            '丸友集主账号登录。',
            '进入工作室履约应用-订单列表。',
            '搜索已提交验收的订单。',
            '点击申请上游验收。',
            '丸掌柜主账号登录。',
            '进入订单管理或验收入口。',
            '搜索同一订单。',
            '执行完结主单/验收通过。',
            '确认没有未完成子单提示。',
            '丸友集主账号重新登录。',
            '进入工作室订单列表。',
            '确认订单状态为已完成或结算中。',
            '打手子账号短信登录。',
            '进入打手应用-我的收入。',
            '按订单号或标题校验收入记录。'
        )),
        '上游验收申请成功；丸掌柜主单完结；丸友集订单完成或结算中；打手收入可见。',
        'status依次为upstream_acceptance_applied、upstream_completed、income_verified。',
        'F-MAIN-001、F-SETTLE-001','已自动化'
    ),
    @('A-MEMBER-001','test_e2e_member_001_enterprise_member_invitation.py','test_丸友集企业新增客服或打手子成员并写入台账','丸友集邀请客服/打手子成员并验证登录权限','成员/账号',
        '丸友集企业主账号可登录；企业管理-成员管理可访问；测试手机号可用。',
        'role_mode=全部/客服/打手；member phone；member role。',
        (Steps @(
            '丸友集主账号登录。',
            '进入企业管理-成员管理。',
            '创建邀请链接或新增成员。',
            '选择成员角色：客服或打手。',
            '填写成员手机号、姓名等字段。',
            '生成邀请并完成成员注册/加入企业。',
            '主账号确认成员加入。',
            '子成员使用短信验证码登录。',
            '选择子账号身份。',
            '进入丸友集应用。',
            '按角色校验菜单：打手应看到打手应用，客服应看到收单/客服相关菜单。',
            '校验越权菜单不可见或不可操作。'
        )),
        '客服/打手子成员可加入企业并登录；菜单权限符合角色。',
        '台账新增member account；role=客服/打手。',
        'F-ACCOUNT-001、F-MAIN-001','已自动化'
    )
)

$flowHeaders = @(
    '流程用例ID','优先级','流程名称','组合小用例顺序','流程前置条件','流程测试数据','组合执行步骤','整体预期结果','跨小用例衔接断言','覆盖分支/说明'
)

$flowRows = @(
    @('F-REG-001','P0','企业注册认证并开通双应用流程','A-SAAS-001 -> A-SAAS-003 -> A-SAAS-004',
        '测试手机号可用；主站管理员账号可用。',
        '企业账号、丸友集应用、丸掌柜应用。',
        (Steps @(
            '执行小用例A-SAAS-001，完成租户企业注册和主站认证。',
            '确认台账中该企业certification_status=approved。',
            '执行小用例A-SAAS-003，申请并审批丸友集应用。',
            '确认台账中wanyouji_status=opened。',
            '执行小用例A-SAAS-004，申请并审批丸掌柜应用。',
            '确认台账中wanzhanggui_status=opened。',
            '租户平台重新登录企业账号。',
            '确认两个应用入口均可进入。'
        )),
        '企业已认证；丸友集和丸掌柜均开通；后续授权和派单流程具备账号基础。',
        '同一企业账号贯穿三个小用例；企业ID、手机号、企业名称一致。',
        '基础注册/开通main流程'
    ),
    @('F-CONFIG-001','P0','授权绑定、工作室授权、推单策略配置流程','A-AUTH-001 -> A-PUSH-001 -> A-PUSH-003',
        '企业已开通丸友集和丸掌柜；丸友集主账号、丸掌柜主账号可登录。',
        'auth_key、workshop_enterprise_name、strategy_name、strategy_type。',
        (Steps @(
            '执行小用例A-AUTH-001，完成丸友集授权密钥生成、丸掌柜激活、权限保存。',
            '确认丸掌柜侧具备创建订单权限。',
            '执行小用例A-PUSH-001，丸掌柜添加丸友集工作室授权。',
            '确认工作室授权记录可用。',
            '执行小用例A-PUSH-003，创建推单配置策略。',
            '按策略类型记录策略名称和绑定工作室。',
            '回到丸掌柜手工录单页面。',
            '确认添加子单时可以选择该策略。'
        )),
        '授权关系、工作室授权、推单策略三项配置均可用。',
        'A-AUTH-001产出的授权关系被A-PUSH-001使用；A-PUSH-001产出的工作室授权被A-PUSH-003使用；A-PUSH-003产出的strategy_name被订单小用例使用。',
        '派单前置配置main流程'
    ),
    @('F-MAIN-001','P0','手工录单外来订单派单履约结算完整流程','A-ORDER-001 -> A-TASK-001 -> A-DELIVERY-001 -> A-DELIVERY-002',
        '已完成F-CONFIG-001；打手子账号存在；订单策略可用。',
        'source_order_no、order_title、strategy_name、assignment_method、player_account。',
        (Steps @(
            '执行小用例A-ORDER-001，丸掌柜手工录单并发布外来订单。',
            '从台账读取source_order_no和order_title。',
            '执行小用例A-TASK-001，丸友集主账号接外部订单并发给打手。',
            '确认台账状态到task_player_accepted。',
            '执行小用例A-DELIVERY-001，打手提交验收。',
            '确认台账状态到delivery_submitted。',
            '执行小用例A-DELIVERY-002，丸友集申请上游验收、丸掌柜完结、打手验证收入。',
            '确认台账最终状态到income_verified。',
            '回查丸掌柜订单、丸友集订单、打手收入三端信息。'
        )),
        '外来订单从丸掌柜发布到丸友集、再到打手履约、上游验收、收入验证完整闭环。',
        'source_order_no/order_title贯穿所有小用例；台账状态按dispatched -> task_player_accepted -> delivery_submitted -> income_verified推进。',
        '当前最重要main流程'
    ),
    @('F-CANCEL-001','P1','已派单未接单撤销流程','A-ORDER-001 -> A-ORDER-002',
        '订单已由丸掌柜发布，但丸友集尚未接外部订单。',
        'source_order_no、order_title、strategy_type。',
        (Steps @(
            '执行小用例A-ORDER-001生成一笔已派单订单。',
            '不要执行A-TASK-001。',
            '执行小用例A-ORDER-002，从丸掌柜撤销派单。',
            '逐个相关丸友集账号验证抢单大厅不可见。',
            '检查台账状态为revoked。',
            '确认没有打手订单和收入。'
        )),
        '已派单未接单状态下撤销成功，丸友集抢单大厅不可见。',
        '撤销用例复用A-ORDER-001产出的订单，不独立造数据。',
        '撤销一级分支之一'
    ),
    @('F-DISPATCH-001','P1','外来订单接入后不同打手分配方式流程','A-ORDER-001 -> A-TASK-001(参数化assignment_method)',
        '订单已派到丸友集；存在可用打手；assignment_method分别取抢单大厅/指派打手/智能派单。',
        'source_order_no、order_title、assignment_method、player_account。',
        (Steps @(
            '执行A-ORDER-001生成订单。',
            '执行A-TASK-001，第一次assignment_method=抢单大厅。',
            '记录抢单大厅可见范围和抢单结果。',
            '重新生成订单或回滚数据。',
            '执行A-TASK-001，第二次assignment_method=指派打手。',
            '记录被指派打手和非指派打手可见性。',
            '重新生成订单或回滚数据。',
            '执行A-TASK-001，第三次assignment_method=智能派单。',
            '记录命中打手、未命中打手可见性和失败提示。'
        )),
        '三种分配方式均能从同一个订单接入节点分支出去，并产生明确打手可见/可操作结果。',
        '每次分配都使用A-ORDER-001产出的订单结构；差异只在A-TASK-001的assignment_method。',
        '打手分配方式组合流程'
    ),
    @('F-SETTLE-001','P0','已接单订单履约验收收入流程','A-DELIVERY-001 -> A-DELIVERY-002',
        '台账存在task_player_accepted订单。',
        'source_order_no、order_title、player_account、wanyouji_account、wanzhanggui_account。',
        (Steps @(
            '执行A-DELIVERY-001，打手提交验收。',
            '确认丸友集主账号订单列表出现待验收/申请上游验收状态。',
            '执行A-DELIVERY-002，丸友集申请上游验收。',
            '丸掌柜完结主单。',
            '丸友集回查完成或结算中。',
            '打手我的收入校验订单收入。'
        )),
        '从打手已接单状态推进到收入验证完成。',
        'delivery_submitted是A-DELIVERY-001和A-DELIVERY-002之间的衔接状态。',
        '履约结算可单独回归'
    )
)

$branchAtomicHeaders = @('分支小用例ID','归属一级分支','可插入位置/依赖小用例','分支小用例名称','前置状态','操作步骤','预期结果','组合建议')
$branchAtomicRows = @(
    @('B-SOURCE-DOUYIN-001','订单来源','替代A-ORDER-001','抖音录单创建外来订单并发布到丸友集','已完成F-CONFIG-001；抖音店铺/抖音录单入口可用',
        (Steps @('丸掌柜抖音录单账号登录。','进入抖音录单或抖音订单入口。','创建或同步抖音订单。','记录抖音订单号和丸掌柜source_order_no。','添加1个子单并选择已有推单策略。','保存并发布订单。','丸友集抢单大厅搜索抖音订单号或source_order_no。','确认订单可见并来源标识为抖音。')),
        '抖音来源订单正向可替代手工录单进入后续A-TASK-001/A-DELIVERY流程。',
        '组合成F-SOURCE-001：B-SOURCE-DOUYIN-001 -> A-TASK-001 -> A-DELIVERY-001 -> A-DELIVERY-002'
    ),
    @('B-CANCEL-002','撤销','A-TASK-001中accept_external_order_from_hall之后、dispatch_workshop_order_to_player_hall之前','丸友集已接外部订单但未发给打手时撤销','台账状态task_accepted',
        (Steps @('丸友集主账号进入工作室订单列表。','搜索已接入的外部订单。','确认尚未发给打手。','在丸友集侧或丸掌柜侧发起撤销/关闭。','填写撤销原因。','打手抢单大厅搜索该订单。','确认不可见。','丸掌柜侧回查主单/子单状态。')),
        '工作室已接但未发给打手的订单撤销后，不得继续流入打手侧。',
        '组合成F-CANCEL-002：A-ORDER-001 -> A-TASK-001前半段 -> B-CANCEL-002'
    ),
    @('B-CANCEL-003','撤销','A-TASK-001之后、A-DELIVERY-001之前','打手已抢单未提交验收时撤销','台账状态task_player_accepted',
        (Steps @('打手进入我的订单确认待履约。','丸友集主账号进入订单列表。','发起撤销/终止履约。','填写原因。','打手刷新我的订单。','确认不能继续提交验收。','丸掌柜回查子单状态。','打手收入确认无收入。')),
        '打手已接单未履约时撤销，不生成收入，不允许继续验收。',
        '组合成F-CANCEL-003：A-ORDER-001 -> A-TASK-001 -> B-CANCEL-003'
    ),
    @('B-CANCEL-004','撤销','A-DELIVERY-001之后、A-DELIVERY-002之前','打手已提交验收但未申请上游验收时驳回/撤销','台账状态delivery_submitted',
        (Steps @('丸友集主账号打开订单列表。','查看打手提交成果。','不申请上游验收。','执行驳回成果或撤销订单。','填写原因。','打手查看驳回/撤销原因。','如驳回则重新提交成果。','如撤销则确认不能继续履约。')),
        '提交验收后异常处理清晰，驳回不结算，撤销不继续流转。',
        '组合成F-CANCEL-004：A-ORDER-001 -> A-TASK-001 -> A-DELIVERY-001 -> B-CANCEL-004'
    ),
    @('B-CANCEL-005','撤销','A-DELIVERY-002中apply_upstream_acceptance之后、complete_upstream_order之前','已申请上游验收但丸掌柜未完结时驳回','台账状态upstream_acceptance_applied',
        (Steps @('丸掌柜进入验收/完结入口。','搜索已申请上游验收订单。','打开详情查看成果。','点击驳回/不通过。','填写原因。','丸友集订单列表查看状态回写。','打手我的订单查看原因。','收入页面确认未生成收入。')),
        '上游验收被驳回时状态回写丸友集和打手，不产生收入。',
        '组合成F-CANCEL-005：A-ORDER-001 -> A-TASK-001 -> A-DELIVERY-001 -> A-DELIVERY-002前半段 -> B-CANCEL-005'
    ),
    @('B-MULTI-001','主子单','替代A-ORDER-001','一主两子一次发出','手工录单页面支持多子单',
        (Steps @('丸掌柜创建主单。','添加子单1并选择策略。','填写子单1金额和服务项目。','添加子单2并选择策略。','填写子单2金额和服务项目。','保存并发布主单。','丸友集按主单号搜索。','确认两个子单均可识别或可展开。')),
        '一主两子均可进入丸友集，子单状态独立。',
        '组合成F-MULTI-001：B-MULTI-001 -> A-TASK-001按子单执行 -> A-DELIVERY-001/002按子单执行'
    ),
    @('B-MULTI-002','主子单','A-DELIVERY-002之后','第1子单完成后续发第2子单','系统支持主单完成部分子单后追加子单',
        (Steps @('完成主单下子单1到收入。','丸掌柜回到主单详情。','点击追加/续发子单。','填写子单2并选择策略。','发布子单2。','丸友集搜索子单2。','对子单2执行接单、履约、验收、收入。','检查主单最终状态。')),
        '第1子单完成不影响续发第2子单；主单最终按全部子单汇总。',
        '组合成F-MULTI-002：A-ORDER-001 -> A-TASK-001 -> A-DELIVERY-001/002 -> B-MULTI-002'
    ),
    @('B-DISPATCH-ASSIGN-001','分配方式','A-TASK-001中dispatch_workshop_order_to_player_hall','指派打手','订单已进入工作室订单列表；目标打手可用',
        (Steps @('管理员点击立即发单。','选择指派打手。','选择目标打手。','填写发单价和时限。','提交。','目标打手查看我的订单。','非目标打手搜索订单。','确认非目标打手不可见或不可操作。')),
        '只有被指派打手能处理订单。',
        '组合成F-DISPATCH-001的指派分支'
    ),
    @('B-DISPATCH-AUTO-001','分配方式','A-TASK-001中dispatch_workshop_order_to_player_hall','智能派单','智能派单规则已配置',
        (Steps @('管理员点击立即发单。','选择智能派单。','提交派单。','查看命中打手或失败提示。','命中打手登录查看订单。','未命中打手确认不可处理。')),
        '智能派单命中正确打手或给出明确失败提示。',
        '组合成F-DISPATCH-001的智能派单分支'
    ),
    @('B-PRICE-PERCENT-001','价格模式','替代A-PUSH-003/A-ORDER-001中的价格配置','百分比发单价','发单设置支持百分比',
        (Steps @('创建百分比发单价策略。','设置百分比、保底、封顶、小数规则。','创建不同金额主单。','选择百分比策略添加子单。','保存后核对计算价。','完成到收入。','核对收入金额等于计算价。')),
        '百分比计算正确，并贯穿订单展示、结算、收入。',
        '组合成F-PRICE-001：B-PRICE-PERCENT-001 -> A-TASK-001 -> A-DELIVERY-001/002'
    ),
    @('B-PRICE-SKU-001','价格模式','替代A-PUSH-003/A-ORDER-001中的价格配置','SKU报价','SKU报价表可配置',
        (Steps @('配置SKU-A/SKU-B报价。','创建SKU-A订单。','保存子单核对命中SKU-A报价。','创建多数量SKU订单。','核对单价乘数量或固定价规则。','创建未命中SKU订单。','观察拦截或默认价。','可发布订单完成收入校验。')),
        'SKU报价命中、数量、未命中处理明确。',
        '组合成F-PRICE-002：B-PRICE-SKU-001 -> A-TASK-001 -> A-DELIVERY-001/002'
    ),
    @('B-PENALTY-001','处罚追缴','A-DELIVERY-002之后','已完成子单处罚追缴','订单已income_verified',
        (Steps @('选择已完成子单。','记录收入金额。','管理员发起处罚追缴。','选择责任方。','填写金额和原因。','提交审核。','审核通过。','责任方查看扣减/追缴流水。','主站/财务查看处罚记录。')),
        '已完成子单可按规则追缴，原订单终态保留，追加流水。',
        '组合成F-PENALTY-001：F-MAIN-001 -> B-PENALTY-001'
    ),
    @('B-PENALTY-002','处罚追缴','B-CANCEL-*之后','撤销完成子单处罚追缴','子单撤销完成',
        (Steps @('选择撤销完成子单。','查看是否存在处罚入口。','如可处罚，填写责任方、金额、原因。','提交并审核。','责任方查看流水。','重复发起同原因处罚。','确认重复限制或独立审批。')),
        '撤销完成子单允许追缴或明确拦截；不得重复扣款。',
        '组合成F-PENALTY-002：任一F-CANCEL流程 -> B-PENALTY-002'
    )
)

$matrixRows = @(
    @('F-MAIN-001','完整正向','A-ORDER-001','A-TASK-001','A-DELIVERY-001','A-DELIVERY-002','手工录单+单子单+默认分配+默认价格'),
    @('F-SOURCE-001','抖音来源正向','B-SOURCE-DOUYIN-001','A-TASK-001','A-DELIVERY-001','A-DELIVERY-002','将订单创建小用例替换为抖音录单'),
    @('F-CANCEL-001','发布后未接撤销','A-ORDER-001','A-ORDER-002','','','已自动化撤销分支'),
    @('F-CANCEL-002','已接未发撤销','A-ORDER-001','A-TASK-001前半段','B-CANCEL-002','','补充task_accepted节点'),
    @('F-CANCEL-003','打手已接未履约撤销','A-ORDER-001','A-TASK-001','B-CANCEL-003','','补充task_player_accepted节点'),
    @('F-CANCEL-004','提交验收未上游撤销','A-ORDER-001','A-TASK-001','A-DELIVERY-001','B-CANCEL-004','补充delivery_submitted节点'),
    @('F-CANCEL-005','已申请上游验收驳回','A-ORDER-001','A-TASK-001','A-DELIVERY-001','A-DELIVERY-002前半段 -> B-CANCEL-005','补充upstream_acceptance_applied节点'),
    @('F-MULTI-001','一主两子一次发','B-MULTI-001','A-TASK-001按子单执行','A-DELIVERY-001/002按子单执行','','多子单正向'),
    @('F-MULTI-002','完成后续发','A-ORDER-001 -> A-TASK-001 -> A-DELIVERY-001/002','B-MULTI-002','','','续发分支'),
    @('F-DISPATCH-001','分配方式矩阵','A-ORDER-001','A-TASK-001抢单大厅','B-DISPATCH-ASSIGN-001','B-DISPATCH-AUTO-001','同一接入节点替换分配小用例'),
    @('F-PRICE-001','百分比价格','B-PRICE-PERCENT-001','A-TASK-001','A-DELIVERY-001','A-DELIVERY-002','替换价格配置/订单价格输入'),
    @('F-PRICE-002','SKU价格','B-PRICE-SKU-001','A-TASK-001','A-DELIVERY-001','A-DELIVERY-002','替换价格配置/订单价格输入'),
    @('F-PENALTY-001','已完成追缴','F-MAIN-001','B-PENALTY-001','','','履约结束后治理'),
    @('F-PENALTY-002','撤销完成追缴','任一F-CANCEL流程','B-PENALTY-002','','','撤销结束后治理')
)

$excel = New-Object -ComObject Excel.Application
$excel.Visible = $false
$excel.DisplayAlerts = $false
try {
    if (Test-Path $Output) { Remove-Item $Output -Force }
    $wb = $excel.Workbooks.Add()
    while ($wb.Worksheets.Count -gt 1) { $wb.Worksheets.Item($wb.Worksheets.Count).Delete() }
    $intro = $wb.Worksheets.Item(1)
    $intro.Name = '说明'
    $intro.Cells.Item(1,1).Value2 = '项目'
    $intro.Cells.Item(1,2).Value2 = '内容'
    $intro.Cells.Item(2,1).Value2 = '结构'
    $intro.Cells.Item(2,2).Value2 = '先生成小用例库：每条来自e2e/tests下已有自动化脚本；再生成组合流程用例：用小用例ID串联成大流程；分支也先沉淀为分支小用例，再放入组合矩阵。'
    $intro.Cells.Item(3,1).Value2 = '使用方式'
    $intro.Cells.Item(3,2).Value2 = '先评审小用例是否贴合脚本，再评审组合流程是否符合业务；后续新增自动化脚本时先加小用例，再加组合流程。'
    $intro.Rows.Item(1).Font.Bold = $true
    $intro.Rows.Item(1).Interior.ColorIndex = 37
    $intro.UsedRange.WrapText = $true
    $intro.Columns.AutoFit() | Out-Null

    Add-Sheet $wb '小用例库_脚本原子用例' $atomicHeaders $atomicRows | Out-Null
    Add-Sheet $wb '大流程用例_组合编排' $flowHeaders $flowRows | Out-Null
    Add-Sheet $wb '分支小用例库' $branchAtomicHeaders $branchAtomicRows | Out-Null
    Add-Sheet $wb '组合矩阵' @('组合流程ID','流程类型','节点1','节点2','节点3','节点4','说明') $matrixRows | Out-Null
    Add-Sheet $wb '状态衔接' @('状态','来源小用例','可进入的小用例/分支','状态说明','关键断言') @(
        @('approved','A-SAAS-001','A-SAAS-003/A-SAAS-004','企业已认证','租户端和主站状态一致'),
        @('opened','A-SAAS-003/A-SAAS-004','A-AUTH-001','应用已开通','应用入口可进入'),
        @('authorized','A-AUTH-001','A-PUSH-001','授权已绑定并保存权限','丸掌柜具备创建订单权限'),
        @('strategy_created','A-PUSH-003','A-ORDER-001/B-SOURCE-DOUYIN-001','推单策略已创建','手工录单添加子单可选择策略'),
        @('dispatched','A-ORDER-001','A-ORDER-002/A-TASK-001/B-CANCEL-002','订单已派到丸友集大厅','大厅可见，未被工作室接入'),
        @('task_accepted','A-TASK-001前半段','B-CANCEL-002/A-TASK-001继续发单','工作室已接外部订单','工作室订单列表可见，打手不可见'),
        @('task_dispatched_to_player_hall','A-TASK-001发单后','A-TASK-001打手抢单','订单已发到打手侧','打手大厅可见'),
        @('task_player_accepted','A-TASK-001','A-DELIVERY-001/B-CANCEL-003','打手已抢单','打手我的订单可见'),
        @('delivery_submitted','A-DELIVERY-001','A-DELIVERY-002/B-CANCEL-004','打手已提交验收','丸友集主账号可申请上游验收'),
        @('upstream_acceptance_applied','A-DELIVERY-002前半段','A-DELIVERY-002继续完结/B-CANCEL-005','已申请上游验收','丸掌柜出现待完结/验收记录'),
        @('income_verified','A-DELIVERY-002','B-PENALTY-001','收入已验证','打手我的收入出现记录')
    ) | Out-Null

    $wb.Worksheets.Item('说明').Activate()
    $wb.SaveAs($Output, 51)
    $wb.Close($true)
}
finally {
    $excel.Quit()
}

$excel2 = New-Object -ComObject Excel.Application
$excel2.Visible = $false
$excel2.DisplayAlerts = $false
try {
    $wb2 = $excel2.Workbooks.Open($Output)
    Write-Output 'excel_open_ok=True'
    Write-Output ('sheets=' + $wb2.Worksheets.Count)
    Write-Output ('atomic_rows=' + $wb2.Worksheets.Item('小用例库_脚本原子用例').UsedRange.Rows.Count)
    Write-Output ('flow_rows=' + $wb2.Worksheets.Item('大流程用例_组合编排').UsedRange.Rows.Count)
    Write-Output ('branch_atomic_rows=' + $wb2.Worksheets.Item('分支小用例库').UsedRange.Rows.Count)
    Write-Output ('matrix_rows=' + $wb2.Worksheets.Item('组合矩阵').UsedRange.Rows.Count)
    $wb2.Close($false)
}
finally {
    $excel2.Quit()
}

Write-Output $Output
