param( [string]$GatewayBaseUrl = 'http://8.163.30.128:9000', [string]$PlatformUsername = 'platform_admin', [string]$MerchantUsername = 'merchant_admin', [string]$PlatformPassword = 'ChangeMe123!', [string]$MerchantPassword = 'ChangeMe123!', [long]$TenantId = 10001, [string]$OutputPath = '' ) $ErrorActionPreference = 'Stop' $repoRoot = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path) $stamp = Get-Date -Format 'yyyyMMddHHmmss' $marker = "API-E2E-$stamp" if ([string]::IsNullOrWhiteSpace($OutputPath)) { $OutputPath = Join-Path $repoRoot "reports\二期测试环境API验收-$stamp.json" } $steps = [System.Collections.Generic.List[object]]::new() $ids = [ordered]@{ tenantId = [string]$TenantId; qualificationReviewId = $null; warehouseId = $null; carrierId = $null; customerRuleId = $null; carrierRateId = $null; orderId = $null; inboundId = $null; measurementId = $null; weightResultId = $null; fileId = $null; feeId = $null } $versions = [ordered]@{ inbound = $null; measurement = $null; weightResult = $null } $fatal = $null $billingDefect = $null function New-Key([string]$Action) { return "$marker-$Action-$([guid]::NewGuid().ToString('N'))" } function Invoke-Api( [string]$Method, [string]$Path, [string]$Token = '', [object]$Body = $null, [hashtable]$ExtraHeaders = @{} ) { $headers = @{} if (-not [string]::IsNullOrWhiteSpace($Token)) { $headers.Authorization = "Bearer $Token" } foreach ($entry in $ExtraHeaders.GetEnumerator()) { $headers[$entry.Key] = $entry.Value } $args = @{ Method = $Method; Uri = "$GatewayBaseUrl$Path"; Headers = $headers; TimeoutSec = 30; SkipHttpErrorCheck = $true } if ($null -ne $Body) { $args.ContentType = 'application/json; charset=utf-8' $jsonText = $Body | ConvertTo-Json -Depth 20 -Compress $args.Body = [Text.Encoding]::UTF8.GetBytes($jsonText) } $response = Invoke-WebRequest @args $json = $null if (-not [string]::IsNullOrWhiteSpace($response.Content)) { try { $json = $response.Content | ConvertFrom-Json } catch { } } return [pscustomobject]@{ http = [int]$response.StatusCode body = $json raw = if ($null -eq $json) { $response.Content } else { $null } requestId = if ($null -ne $json) { $json.requestId } else { $response.Headers['X-Request-Id'] } traceId = if ($null -ne $json) { $json.traceId } else { $response.Headers['X-Trace-Id'] } } } function Invoke-Upload([string]$Path, [string]$Token, [string]$FilePath) { $file = Get-Item -LiteralPath $FilePath $content = & curl.exe -sS -X POST "$GatewayBaseUrl$Path" ` -H "Authorization: Bearer $Token" -F "file=@$($file.FullName);type=image/png" if ($LASTEXITCODE -ne 0) { throw '称重图片上传发生传输错误' } $json = $null if (-not [string]::IsNullOrWhiteSpace($content)) { try { $json = $content | ConvertFrom-Json } catch { } } return [pscustomobject]@{ http = 200 body = $json raw = if ($null -eq $json) { [string]$content } else { $null } requestId = if ($null -ne $json) { $json.requestId } else { $null } traceId = if ($null -ne $json) { $json.traceId } else { $null } } } function Invoke-RequiredQualificationUpload([string]$Token, [string]$FilePath, [string]$CreditCode) { $query = "unifiedSocialCreditCode=$CreditCode&replaceBusinessLicense=true&replaceLegalPersonId=false" $businessFile = Get-Item -LiteralPath $FilePath $legalPersonFile = Get-Item -LiteralPath (Join-Path $businessFile.DirectoryName 'c8.png') $content = & curl.exe -sS -X POST "$GatewayBaseUrl/api/merchant/v1/files/qualifications/required-package?$query" ` -H "Authorization: Bearer $Token" ` -F "businessLicense=@$($businessFile.FullName);type=image/png" ` -F "legalPersonId=@$($legalPersonFile.FullName);type=image/png" if ($LASTEXITCODE -ne 0) { throw '必备资质包上传发生传输错误' } $json = $null if (-not [string]::IsNullOrWhiteSpace($content)) { try { $json = $content | ConvertFrom-Json } catch { } } return [pscustomobject]@{ http = 200 body = $json raw = if ($null -eq $json) { [string]$content } else { $null } requestId = if ($null -ne $json) { $json.requestId } else { $null } traceId = if ($null -ne $json) { $json.traceId } else { $null } } } function Add-Step([string]$Name, $Response, [bool]$Passed, [string]$Evidence = '') { $code = if ($null -ne $Response.body) { [string]$Response.body.code } else { $null } $message = if ($null -ne $Response.body) { [string]$Response.body.message } else { [string]$Response.raw } $steps.Add([pscustomobject]@{ name = $Name passed = $Passed http = $Response.http code = $code message = $message requestId = $Response.requestId traceId = $Response.traceId evidence = $Evidence details = if ($Passed -or $null -eq $Response.body) { $null } else { $Response.body.data } }) Write-Host "[$(if ($Passed) { '通过' } else { '失败' })] $Name HTTP=$($Response.http) code=$code $Evidence" } function Require-Success([string]$Name, $Response, [string]$Evidence = '') { $passed = $Response.http -eq 200 -and [string]$Response.body.code -eq '200' Add-Step $Name $Response $passed $Evidence if (-not $passed) { throw "$Name 失败:HTTP=$($Response.http) code=$($Response.body.code) message=$($Response.body.message)" } return $Response.body.data } function Require-Condition([string]$Name, $Response, [bool]$Condition, [string]$Evidence) { $passed = $Response.http -eq 200 -and [string]$Response.body.code -eq '200' -and $Condition Add-Step $Name $Response $passed $Evidence if (-not $passed) { throw "$Name 断言失败:$Evidence" } } try { $platformLoginResponse = Invoke-Api POST '/api/auth/login' '' @{ username = $PlatformUsername; password = $PlatformPassword } $platformLogin = Require-Success '平台登录' $platformLoginResponse $platformToken = $platformLogin.accessToken $merchantLoginResponse = Invoke-Api POST '/api/auth/login' '' @{ username = $MerchantUsername; password = $MerchantPassword } $merchantLogin = Require-Success '商户登录' $merchantLoginResponse $merchantToken = $merchantLogin.accessToken $healthResponse = Invoke-Api GET '/api/platform/v1/first-leg/status' $platformToken $null = Require-Success 'Firstleg 服务健康检查' $healthResponse $qualificationsResponse = Invoke-Api GET '/api/merchant/v1/iam/tenant/qualifications' $merchantToken $qualifications = @(Require-Success '查询商户必备资质' $qualificationsResponse) $approvedTypes = @($qualifications | Where-Object { $_.status -eq 'APPROVED' } | ForEach-Object { $_.qualificationType }) if ('BUSINESS_LICENSE' -notin $approvedTypes -or 'LEGAL_PERSON_ID' -notin $approvedTypes) { $e2eUsername = "api_e2e_$stamp" $e2ePassword = 'ApiE2e123!' $registerResponse = Invoke-Api POST '/api/auth/register' '' @{ username = $e2eUsername; password = $e2ePassword; displayName = 'API E2E Tester' companyName = "API E2E 测试企业 $stamp"; legalPerson = 'API Tester'; legalPhone = '13800138000' contactEmail = "api-e2e-$stamp@example.com"; countryCode = 'CN'; address = 'API E2E 测试地址' } $registration = Require-Success '注册隔离的 API E2E 测试企业' $registerResponse $TenantId = [long]$registration.tenantId $ids.tenantId = [string]$TenantId $e2eLoginResponse = Invoke-Api POST '/api/auth/login' '' @{ username = $e2eUsername; password = $e2ePassword } $e2eLogin = Require-Success '登录隔离的 API E2E 测试企业' $e2eLoginResponse $merchantToken = $e2eLogin.accessToken $qualificationImage = Join-Path (Split-Path -Parent $repoRoot) 'apps\pda-uniapp\static\c7.png' $creditCode = "91${stamp}A1" $qualificationUploadResponse = Invoke-RequiredQualificationUpload $merchantToken $qualificationImage $creditCode $null = Require-Success '补齐商户营业执照与法人身份证测试包' $qualificationUploadResponse $reviewSubmitResponse = Invoke-Api POST '/api/merchant/v1/iam/tenant/qualifications/submit-review' $merchantToken $null = Require-Success '提交商户资质审核' $reviewSubmitResponse $reviewsResponse = Invoke-Api GET '/api/platform/v1/iam/reviews' $platformToken $reviews = @(Require-Success '查询平台待审核企业' $reviewsResponse) $review = @($reviews | Where-Object { [string]$_.tenantId -eq [string]$TenantId } | Select-Object -First 1) if ($review.Count -eq 0) { throw "未找到 tenantId=$TenantId 的资质审核单" } $ids.qualificationReviewId = [string]$review[0].id $reviewApproveResponse = Invoke-Api POST "/api/platform/v1/iam/reviews/$($ids.qualificationReviewId)/approve" $platformToken @{ comment = "$marker 测试环境验收补齐" } $null = Require-Success '平台审核通过商户资质' $reviewApproveResponse } $carrierPayload = [ordered]@{ partnerCode = "API-E2E-CARRIER-$stamp" partnerName = "API E2E 测试承运商 $stamp" partnerType = 'CARRIER' types = @('CARRIER') contactName = 'API Tester' contactPhone = '13800138000' contactEmail = 'api-e2e@example.com' countryCode = 'CN' province = '广东省' city = '深圳市' district = '南山区' addressLine = 'API E2E 测试地址' postalCode = '518000' status = 'ACTIVE' remark = $marker } $carrierResponse = Invoke-Api POST '/api/platform/v1/partners?tenantId=0' $platformToken $carrierPayload $ids.carrierId = [string](Require-Success '创建公共测试承运商' $carrierResponse) $warehousePayload = [ordered]@{ tenantId = 0 warehouseCode = "API-E2E-WH-$stamp" warehouseName = "API E2E 收货仓 $stamp" warehouseType = 'DOMESTIC' countryCode = 'CN' timezone = 'UTC+8' contactName = 'API Tester' contactPhone = '13800138000' contactEmail = 'api-e2e@example.com' status = 'ACTIVE' remark = $marker address = [ordered]@{ addressType = 'WAREHOUSE'; countryCode = 'CN'; stateProvince = '广东省'; city = '深圳市' district = '南山区'; postalCode = '518000'; addressLine1 = 'API E2E 测试收货仓地址' addressLine2 = $null; latitude = $null; longitude = $null } capabilities = @([ordered]@{ capabilityCode = 'RECEIVING'; capabilityName = '头程收货'; enabled = $true cutoffTime = '16:00:00'; description = $marker }) } $warehouseResponse = Invoke-Api POST '/api/platform/v1/warehouse-admin' $platformToken $warehousePayload $ids.warehouseId = [string](Require-Success '创建启用收货能力的公共测试仓' $warehouseResponse) $effectiveFrom = (Get-Date).AddDays(-1).ToString('yyyy-MM-ddTHH:mm:ss') $customerRuleResponse = Invoke-Api POST "/api/platform/v1/fba-pricing/customer-rules/versions?tenantId=$TenantId" $platformToken @{ ruleCode = "API-E2E-RULE-$stamp"; customerId = '191000000000000001' customerName = '深圳示例贸易有限公司'; businessType = 'FBA_SEA'; chargeMode = 'KG' taxMode = 'TAX_INCLUDED'; conversionFactor = 167; roundingScale = 3 roundingMode = 'HALF_UP'; currency = 'CNY'; effectiveFrom = $effectiveFrom effectiveTo = $null; remark = $marker } $customerRule = Require-Success '创建客户计费规则版本' $customerRuleResponse $ids.customerRuleId = [string]$customerRule.id $carrierRateResponse = Invoke-Api POST '/api/platform/v1/fba-pricing/carrier-rates/versions' $platformToken @{ rateCode = "API-E2E-RATE-$stamp"; originWarehouseId = $ids.warehouseId originWarehouseName = $warehousePayload.warehouseName; businessType = 'FBA_SEA' channelCode = 'API-E2E-SEA'; carrierId = $ids.carrierId carrierName = $carrierPayload.partnerName; destinationCode = 'ABE2'; chargeUnit = 'KG' unitPrice = 2; currency = 'CNY'; additionalFee = 10; effectiveFrom = $effectiveFrom effectiveTo = $null; remark = $marker } $carrierRate = Require-Success '创建承运商 FBA 费率版本' $carrierRateResponse $ids.carrierRateId = [string]$carrierRate.id $sender = [ordered]@{ companyName = '深圳示例贸易有限公司'; contactName = 'API Tester'; phone = '13800138000' email = 'api-e2e@example.com'; countryCode = 'CN'; province = '广东省'; city = '深圳市' district = '南山区'; addressLine = 'API E2E 测试地址'; postalCode = '518000' } $orderPayload = [ordered]@{ customerId = '191000000000000001' customerName = '深圳示例贸易有限公司' transportMode = 'SEA' tradeTerm = 'FOB' destinationCountryCode = 'US' destinationWarehouseId = '193000000000000001' destinationWarehouseName = 'ABE2' expectedShipDate = (Get-Date).AddDays(10).ToString('yyyy-MM-dd') customerRemark = "$marker 初始草稿" sender = $sender receiver = $null cargoes = @([ordered]@{ cargoName = 'API E2E 测试货物'; cargoNameEn = 'API E2E Test Cargo'; hsCode = '8518210000' customsHsCode = '8518210000'; packageType = 'CARTON'; packageCount = 1 grossWeightKg = 10; netWeightKg = 9; volumeCbm = 0.06 lengthCm = 50; widthCm = 40; heightCm = 30; declaredValue = 100; unitDeclaredValue = 100 currency = 'USD'; dangerousGoods = $false; cargoRemark = $marker skuNo = "SKU-$stamp"; upcNo = "UPC-$stamp"; unitsPerPackage = 1; totalQuantity = 1 material = '塑料'; usage = '测试'; model = 'E2E'; brand = 'API-E2E' }) pickupMethod = 'PICKUP' receivingWarehouseId = $ids.warehouseId receivingWarehouseName = $warehousePayload.warehouseName channelCode = 'API-E2E-SEA' channelName = 'API E2E 海运渠道' carrierId = $null carrierName = $null taxMode = 'BUY_DECLARATION' containerMode = 'LCL' customsInspection = $false destinationType = 'FBA' amazonWarehouseCode = 'ABE2' shipmentId = "SHIP-$stamp" referenceId = $marker } $createKey = New-Key 'order-create' $createResponse = Invoke-Api POST '/api/merchant/v1/orders/first-leg' $merchantToken $orderPayload @{ 'Idempotency-Key' = $createKey } $ids.orderId = [string](Require-Success '创建头程订单草稿' $createResponse) $replayResponse = Invoke-Api POST '/api/merchant/v1/orders/first-leg' $merchantToken $orderPayload @{ 'Idempotency-Key' = $createKey } $replayedOrderId = [string](Require-Success '重放创建订单幂等请求' $replayResponse) Require-Condition '创建订单幂等结果一致' $replayResponse ($replayedOrderId -eq $ids.orderId) "orderId=$replayedOrderId" $draftDetailResponse = Invoke-Api GET "/api/merchant/v1/orders/first-leg/$($ids.orderId)" $merchantToken $draftDetail = Require-Success '查询订单草稿' $draftDetailResponse Require-Condition '订单初始状态为 DRAFT' $draftDetailResponse ($draftDetail.order.status -eq 'DRAFT') "status=$($draftDetail.order.status)" $orderPayload.customerRemark = "$marker 编辑后草稿" $updateResponse = Invoke-Api PUT "/api/merchant/v1/orders/first-leg/$($ids.orderId)" $merchantToken $orderPayload $null = Require-Success '编辑头程订单草稿' $updateResponse $editedDetailResponse = Invoke-Api GET "/api/merchant/v1/orders/first-leg/$($ids.orderId)" $merchantToken $editedDetail = Require-Success '回查编辑后的订单草稿' $editedDetailResponse Require-Condition '订单编辑内容已保存' $editedDetailResponse ($editedDetail.order.customerRemark -eq $orderPayload.customerRemark) "customerRemark=$($editedDetail.order.customerRemark)" $submitResponse = Invoke-Api POST "/api/merchant/v1/orders/first-leg/$($ids.orderId)/submit" $merchantToken $null @{ 'Idempotency-Key' = (New-Key 'order-submit') } $null = Require-Success '商户提交订单' $submitResponse $unifiedPendingResponse = Invoke-Api GET "/api/platform/v1/orders/$($ids.orderId)?tenantId=$TenantId" $platformToken $unifiedPending = Require-Success '统一订单详情查询待审核状态' $unifiedPendingResponse Require-Condition '统一订单主状态为 PENDING_REVIEW' $unifiedPendingResponse ($unifiedPending.summary.mainStatus -eq 'PENDING_REVIEW') "mainStatus=$($unifiedPending.summary.mainStatus)" $approveOrderResponse = Invoke-Api POST "/api/platform/v1/orders/first-leg/$($ids.orderId)/approve?tenantId=$TenantId" $platformToken $null = Require-Success '平台审核通过订单' $approveOrderResponse $unifiedApprovedResponse = $null $unifiedApproved = $null for ($attempt = 1; $attempt -le 20; $attempt++) { $unifiedApprovedResponse = Invoke-Api GET "/api/platform/v1/orders/$($ids.orderId)?tenantId=$TenantId" $platformToken if ($unifiedApprovedResponse.http -eq 200 -and [string]$unifiedApprovedResponse.body.code -eq '200' -and $null -ne $unifiedApprovedResponse.body.data.summary.inboundId) { $unifiedApproved = $unifiedApprovedResponse.body.data break } Start-Sleep -Seconds 3 } $inboundReady = $null -ne $unifiedApproved -and $null -ne $unifiedApproved.summary.inboundId Add-Step '异步生成入库单' $unifiedApprovedResponse $inboundReady $(if ($inboundReady) { "inboundId=$($unifiedApproved.summary.inboundId)" } else { '60 秒内未取得 inboundId' }) if (-not $inboundReady) { throw '订单审核后 60 秒内未异步生成入库单' } $ids.inboundId = [string]$unifiedApproved.summary.inboundId $versions.inbound = [int]$unifiedApproved.summary.inboundVersion $inboundDetailResponse = Invoke-Api GET "/api/platform/inbounds/$($ids.inboundId)?tenantId=$TenantId" $platformToken $inboundDetail = Require-Success '查询入库单详情' $inboundDetailResponse $versions.inbound = [int]$inboundDetail.summary.version Require-Condition '入库单进入待测量状态' $inboundDetailResponse ($inboundDetail.summary.processStatus -eq 'WAIT_MEASURE') "processStatus=$($inboundDetail.summary.processStatus), version=$($versions.inbound)" $measurementDetailResponse = Invoke-Api GET "/api/platform/v1/first-leg/inbounds/$($ids.inboundId)/measurement?tenantId=$TenantId" $platformToken $null = Require-Success '查询初始测量详情' $measurementDetailResponse $draftPayload = [ordered]@{ measurementId = $null; version = 0; originalDocumentNo = $marker originalGrossWeightKg = 10; originalVolumeCbm = 0.06; actualGoodsName = 'API E2E 测试货物' items = @([ordered]@{ itemNo = 'ITEM-1'; boxSpecification = '50x40x30cm'; packageCount = 1 lengthCm = 50; widthCm = 40; heightCm = 30; unitWeightKg = 10 intelligentImageFileId = $null; overLimitWarning = $null outerLengthCm = 50; outerWidthCm = 40; outerHeightCm = 30; remark = $marker }) } $saveDraftResponse = Invoke-Api PUT "/api/platform/v1/first-leg/inbounds/$($ids.inboundId)/measurement/draft?tenantId=$TenantId" $platformToken $draftPayload $savedDraft = Require-Success '保存测量草稿' $saveDraftResponse $ids.measurementId = [string]$savedDraft.measurementId $versions.measurement = [int]$savedDraft.version $imagePath = Join-Path (Split-Path -Parent $repoRoot) 'apps\pda-uniapp\static\c7.png' if (-not (Test-Path -LiteralPath $imagePath)) { throw "找不到测试称重图片:$imagePath" } $uploadResponse = Invoke-Upload "/api/platform/v1/files/measurements/$TenantId/$($ids.inboundId)" $platformToken $imagePath $uploaded = Require-Success '上传称重图片并触发 OCR' $uploadResponse $ids.fileId = [string]$uploaded.fileId $weightPayload = [ordered]@{ fileId = $ids.fileId ocrProvider = 'API_E2E_MANUAL' ocrRawText = if ($uploaded.weightValue) { [string]$uploaded.weightValue } else { 'OCR unavailable; manual verification required' } ocrWeightValue = $uploaded.weightValue ocrWeightUnit = if ($uploaded.weightValue) { 'KG' } else { $null } confidence = $uploaded.confidence } $weightCreateResponse = Invoke-Api POST "/api/platform/v1/first-leg/inbounds/$($ids.inboundId)/weight-results?tenantId=$TenantId" $platformToken $weightPayload $weightResult = Require-Success '登记称重结果' $weightCreateResponse $ids.weightResultId = [string]$weightResult.id $versions.weightResult = [int]$weightResult.version $correctResponse = Invoke-Api PUT "/api/platform/v1/first-leg/inbounds/$($ids.inboundId)/weight-results/$($ids.weightResultId)?tenantId=$TenantId" $platformToken @{ version = $versions.weightResult; adjustedWeightValue = 10; adjustedWeightUnit = 'KG'; adjustmentReason = "$marker 人工确认" } $corrected = Require-Success '人工修正称重结果' $correctResponse $versions.weightResult = [int]$corrected.version $confirmResponse = Invoke-Api POST "/api/platform/v1/first-leg/inbounds/$($ids.inboundId)/weight-results/$($ids.weightResultId)/confirm?tenantId=$TenantId" $platformToken @{ version = $versions.weightResult } $confirmed = Require-Success '确认称重结果' $confirmResponse $versions.weightResult = [int]$confirmed.version $measurementBeforeSubmitResponse = Invoke-Api GET "/api/platform/v1/first-leg/inbounds/$($ids.inboundId)/measurement?tenantId=$TenantId" $platformToken $measurementBeforeSubmit = Require-Success '提交前回查测量详情' $measurementBeforeSubmitResponse $versions.measurement = [int]$measurementBeforeSubmit.version Require-Condition '确认重量汇总为 10KG' $measurementBeforeSubmitResponse ([decimal]$measurementBeforeSubmit.actualGrossWeightKg -eq 10) "actualGrossWeightKg=$($measurementBeforeSubmit.actualGrossWeightKg)" $measurementSubmitResponse = Invoke-Api POST "/api/platform/v1/first-leg/inbounds/$($ids.inboundId)/measurement/submit?tenantId=$TenantId" $platformToken @{ measurementId = $ids.measurementId; version = $versions.measurement } $measurementSubmitted = Require-Success '提交测量审核' $measurementSubmitResponse $versions.measurement = [int]$measurementSubmitted.version Require-Condition '测量进入待审核状态' $measurementSubmitResponse ($measurementSubmitted.processStatus -eq 'MEASURE_PENDING' -and $measurementSubmitted.measureReviewStatus -eq 'PENDING') "processStatus=$($measurementSubmitted.processStatus), reviewStatus=$($measurementSubmitted.measureReviewStatus)" $pendingInboundResponse = Invoke-Api GET "/api/platform/inbounds/$($ids.inboundId)?tenantId=$TenantId" $platformToken $pendingInbound = Require-Success '审核前回查入库单版本' $pendingInboundResponse $versions.inbound = [int]$pendingInbound.summary.version $measurementApproveResponse = Invoke-Api POST "/api/platform/v1/inbounds/$($ids.inboundId)/measurements/approve?tenantId=$TenantId" $platformToken @{ measurementId = $ids.measurementId; expectedVersion = $versions.inbound; reason = $null } @{ 'Idempotency-Key' = (New-Key 'measurement-approve') } $null = Require-Success '平台审核通过测量' $measurementApproveResponse $approvedInboundResponse = Invoke-Api GET "/api/platform/inbounds/$($ids.inboundId)?tenantId=$TenantId" $platformToken $approvedInbound = Require-Success '审核后回查入库单' $approvedInboundResponse $versions.inbound = [int]$approvedInbound.summary.version Require-Condition '二期边界停在 WAIT_SPLIT' $approvedInboundResponse ($approvedInbound.summary.processStatus -eq 'WAIT_SPLIT' -and $approvedInbound.summary.measureReviewStatus -eq 'APPROVED') "processStatus=$($approvedInbound.summary.processStatus), reviewStatus=$($approvedInbound.summary.measureReviewStatus)" $feeResponse = Invoke-Api POST "/api/platform/v1/inbounds/$($ids.inboundId)/fees/calculate?tenantId=$TenantId" $platformToken @{} @{ 'Idempotency-Key' = (New-Key 'fee-calculate') } $calculatedFee = Require-Success '二期计费接口试算' $feeResponse $ids.feeId = [string]$calculatedFee.feeId Require-Condition '计费试算保持 WAIT_SPLIT 主流程边界' $feeResponse ($calculatedFee.nextProcessStatus -eq 'WAIT_SPLIT') "nextProcessStatus=$($calculatedFee.nextProcessStatus)" $feeConfirmResponse = Invoke-Api POST "/api/platform/v1/inbounds/$($ids.inboundId)/fees/confirm?tenantId=$TenantId" $platformToken $null @{ 'Idempotency-Key' = (New-Key 'fee-confirm') } $confirmedFee = Require-Success '确认二期费用快照' $feeConfirmResponse Require-Condition '费用确认后仍保持 WAIT_SPLIT 主流程边界' $feeConfirmResponse ($confirmedFee.feeStatus -eq 'CONFIRMED' -and $confirmedFee.nextProcessStatus -eq 'WAIT_SPLIT') "feeStatus=$($confirmedFee.feeStatus), nextProcessStatus=$($confirmedFee.nextProcessStatus)" } catch { $fatal = $_.Exception.Message Write-Host "[中止] $fatal" } finally { $passedCount = @($steps | Where-Object passed).Count $failedCount = @($steps | Where-Object { -not $_.passed }).Count $report = [ordered]@{ reportName = '二期测试环境 API 验收' scope = @('头程下单', '商户提交', '平台审核', '异步生成入库单', '测量草稿', '称重图片与结果', '人工修正与确认', '测量提交与审核', '计费试算') excluded = @('分柜', '预装', 'PDA 拣货', '装柜证据', '装柜完成', '派送') gateway = $GatewayBaseUrl marker = $marker startedAt = [datetime]::ParseExact($stamp, 'yyyyMMddHHmmss', $null).ToString('o') finishedAt = (Get-Date).ToString('o') success = $null -eq $fatal -and $null -eq $billingDefect -and $failedCount -eq 0 passedCount = $passedCount failedCount = $failedCount ids = $ids versions = $versions fatal = $fatal billingDefect = $billingDefect steps = $steps } $report | ConvertTo-Json -Depth 20 | Set-Content -LiteralPath $OutputPath -Encoding utf8 Write-Host "报告:$OutputPath" $report | ConvertTo-Json -Depth 20 }