const MODULE = 'manual-state-matrix' const OPTIONS = { scenario: 'manual-clickable-list-state-matrix', deploymentBlocker: true } const GROUPS = [ 'order-inbound-status-fixtures', 'manual-state-loading-pda', 'manual-state-ocean-trucking', ] const FIXTURE_UNAVAILABLE = { inbounds: { MEASURE_REJECTED: '当前稳定 Admin 契约未开放测量审核驳回接口,无法稳定构造该状态', WAIT_PRICING: '自动计费会继续推进,无法稳定保留 WAIT_PRICING', FEE_CONFIRMING: '当前无可稳定保留的费用确认前公开生成链路', INBOUNDING: '自动入库在同一调用内继续推进 WAIT_SPLIT,无可暂停 API', VOIDED: '当前 Controller/Service 没有作废入库单的公开 API', }, } function rows(value) { return Array.isArray(value) ? value : [] } function id(value) { return value == null ? '' : String(value) } export function normalizeStateRequirements(matrix) { for (const [page, value] of Object.entries(matrix?.pages || {})) { const supportedStatuses = [...new Set([ ...rows(value?.supportedStatuses), ...rows(value?.requiredStatuses), ])] const unavailable = FIXTURE_UNAVAILABLE[page] || {} value.supportedStatuses = supportedStatuses value.requiredStatuses = supportedStatuses.filter((status) => !unavailable[status]) if (Object.keys(unavailable).length > 0) { value.coverageGaps = Object.fromEntries(Object.entries(unavailable).map(([status, reason]) => [status, { category: 'fixture-unavailable', reason, }])) } } return matrix } export function missingRequiredStateFixtures(matrix) { return Object.entries(matrix?.pages || {}).flatMap(([page, value]) => rows(value?.requiredStatuses) .filter((status) => !value?.fixtures?.[status]?.id) .map((status) => `${page}:${status}`)) } export async function attachDeliveredOrderFixture(context, matrix) { const fulfilled = context.createdResources?.phase5OverseasFulfillment const proof = fulfilled?.orderCompletionProof const customerId = id(proof?.customerId) const orderId = id(proof?.rootOrderId) if (fulfilled?.marker !== context.marker || !customerId || !orderId || !proof?.rootDelivered || proof.expectedCompleted !== false) { throw new Error('DELIVERED 样本须复用本轮已签收且仍有有效余量的履约根') } const response = await context.request('GET', `/api/platform/v1/orders/${encodeURIComponent(orderId)}?tenantId=${encodeURIComponent(customerId)}`) const detail = context.apiSuccess(MODULE, '回读真实签收产生的 DELIVERED 统一订单', response, '', OPTIONS) const valid = id(detail?.summary?.orderId) === orderId && detail?.summary?.mainStatus === 'DELIVERED' context.assert(MODULE, 'DELIVERED 样本客户与真实根签收状态正确', response, valid, `orderId=${orderId}, status=${detail?.summary?.mainStatus || 'missing'}`, OPTIONS) if (!valid) throw new Error('DELIVERED 履约根详情未收敛到真实签收状态') matrix.pages.unifiedOrders.fixtures.DELIVERED = { id: orderId, status: 'DELIVERED', customerId, detailPath: `/orders/${orderId}` } } function registerFixtureUnavailableGaps(context, matrix) { for (const [page, value] of Object.entries(matrix?.pages || {})) { for (const [status, gap] of Object.entries(value?.coverageGaps || {})) { if (value?.fixtures?.[status]?.id) continue if (rows(context.coverageGaps).some((item) => item?.category === gap.category && String(item?.name || '').includes(status))) continue context.addCoverageGap?.(MODULE, OPTIONS.scenario, `${page}:${status}`, gap.reason, gap.category, { marker: context.marker, page, status, blocking: false }) } } } async function attachUnifiedOrderFixture(context, matrix, resourceName, expectedStatus) { const resource = context.createdResources?.[resourceName] const inboundId = id(resource?.inboundId) const tenantId = id(resource?.tenantId) context.assert(MODULE, `${expectedStatus} 专项资源已生成`, null, Boolean(inboundId && tenantId), `resource=${resourceName}, inboundId=${inboundId || 'missing'}, tenantId=${tenantId || 'missing'}`, OPTIONS) if (!inboundId || !tenantId) return const inboundResponse = await context.request('GET', `/api/platform/inbounds/${encodeURIComponent(inboundId)}?tenantId=${encodeURIComponent(tenantId)}`) const inbound = context.apiSuccess(MODULE, `读取 ${expectedStatus} 专项入库详情`, inboundResponse, '', OPTIONS) const orderId = id(inbound?.summary?.orderId) context.assert(MODULE, `${expectedStatus} 入库详情身份与状态正确`, inboundResponse, id(inbound?.summary?.id) === inboundId && id(inbound?.summary?.tenantId) === tenantId && Boolean(orderId) && inbound?.summary?.processStatus === expectedStatus, `inboundId=${id(inbound?.summary?.id) || 'missing'}, orderId=${orderId || 'missing'}, status=${inbound?.summary?.processStatus || 'missing'}`, OPTIONS) if (!orderId || inbound?.summary?.processStatus !== expectedStatus) return const unifiedResponse = await context.request('GET', `/api/platform/v1/orders/${encodeURIComponent(orderId)}?tenantId=${encodeURIComponent(tenantId)}`) const unified = context.apiSuccess(MODULE, `读取 ${expectedStatus} 统一订单详情`, unifiedResponse, '', OPTIONS) const valid = id(unified?.summary?.orderId) === orderId && unified?.summary?.mainStatus === expectedStatus context.assert(MODULE, `${expectedStatus} 统一订单详情可打开`, unifiedResponse, valid, `orderId=${id(unified?.summary?.orderId) || 'missing'}, mainStatus=${unified?.summary?.mainStatus || 'missing'}`, OPTIONS) if (!valid) return matrix.pages.unifiedOrders.fixtures[expectedStatus] = { id: orderId, status: expectedStatus, inboundId, detailPath: `/orders/${orderId}`, } } export async function run(context) { const matrix = { marker: context.marker, generatedAt: new Date().toISOString(), pages: {} } context.createdResources.manualStateMatrix = matrix for (const name of GROUPS) { const extension = await import(new URL(`./${name}.mjs`, import.meta.url)) if (typeof extension.run !== 'function') throw new Error(`${name} 必须导出 async function run(context)`) await extension.run(context, matrix) } await attachUnifiedOrderFixture(context, matrix, 'phase3Loading', 'LOADED') await attachDeliveredOrderFixture(context, matrix) normalizeStateRequirements(matrix) registerFixtureUnavailableGaps(context, matrix) const pages = Object.entries(matrix.pages) const missing = missingRequiredStateFixtures(matrix) const missingActions = pages.flatMap(([page, value]) => rows(value?.requiredActions) .filter((action) => !value?.actions?.[action]?.id || !value?.actions?.[action]?.documentId || !value?.actions?.[action]?.completedAt) .map((action) => `${page}:action:${action}`)) context.assert(MODULE, '人工验收列表状态数据全部生成并保留', null, pages.length > 0 && missing.length === 0 && missingActions.length === 0, `pages=${pages.length}, missing=${[...missing, ...missingActions].join(',') || 'none'}`, OPTIONS) }