const MODULE = 'phase4-overseas-business' const SCENARIO = 'phase4-overseas-business-write-lifecycle' const OPTIONS = { scenario: SCENARIO, deploymentBlocker: true } const BASE = '/api/platform/v1/last-leg' function id(value) { return value == null ? '' : String(value) } function codeOf(response) { return response?.body?.code == null ? 'missing' : String(response.body.code) } function trace(response) { return `HTTP=${response?.http ?? 'missing'}, code=${codeOf(response)}, requestId=${response?.requestId || 'missing'}, traceId=${response?.traceId || 'missing'}` } function records(page) { return Array.isArray(page?.records) ? page.records : [] } function query(path, params) { const search = new URLSearchParams(); for (const [key, value] of Object.entries(params)) if (value !== '' && value != null) search.set(key, String(value)); return `${path}?${search}` } function summary(data) { return data?.summary && typeof data.summary === 'object' ? data.summary : data } function detailValue(data, field) { return data && Object.hasOwn(data, field) ? data[field] : summary(data)?.[field] } function conflict(response) { return response?.http === 409 || (response?.http === 200 && codeOf(response).endsWith('409')) } function versionConflict(response) { return response?.http === 200 && codeOf(response) === 'A0409' } function longId(value) { return /^[1-9]\d*$/.test(id(value)) } function nonEmpty(value) { return typeof value === 'string' && value.trim().length > 0 } function pageValid(page) { return Array.isArray(page?.records) && Number(page?.page) >= 1 && Number(page?.size) >= 1 && Number(page?.total) >= page.records.length && Number(page?.pages) >= 0 } function ownedRegistry(context) { context.createdResources.phase4OverseasBusiness ??= { marker: context.marker }; return context.createdResources.phase4OverseasBusiness } function blocker(context, name, response, reason) { context.addStep(MODULE, name, response || null, false, `${reason}; ${trace(response)}`, OPTIONS) return null } function expectedConflict(context, name, response, exactVersion = false) { const passed = exactVersion ? versionConflict(response) : conflict(response) context.addStep(MODULE, name, response, passed, trace(response), OPTIONS) return passed } async function domesticGet(context, name, path) { const response = await context.request('GET', path) const data = context.apiSuccess(MODULE, name, response, trace(response), OPTIONS) return { response, data } } async function domestic(context, method, name, path, body) { const response = await context.request(method, path, body) const data = context.apiSuccess(MODULE, name, response, trace(response), OPTIONS) return { response, data } } async function overseas(context, method, name, path, body) { const response = await context.request(method, path, body) const data = context.apiSuccess(MODULE, name, response, trace(response), OPTIONS) return { response, data } } function assertPage(context, name, result) { context.assert(MODULE, `${name}分页结构正确`, result.response, pageValid(result.data), `records=${result.data?.records?.length ?? 'missing'}, page=${result.data?.page ?? 'missing'}, size=${result.data?.size ?? 'missing'}, total=${result.data?.total ?? 'missing'}; ${trace(result.response)}`, OPTIONS) } function partnerWarehousePayload(context) { const suffix = String(context.marker || '').replace(/[^A-Za-z0-9]/g, '').slice(-20).toUpperCase() return { tenantId: 0, warehouseCode: `P4B-${suffix}`.slice(0, 50), warehouseName: `${context.marker} 采购合作仓`, warehouseType: 'OVERSEAS_PARTNER', countryCode: 'US', timezone: 'UTC-8', contactName: 'Phase4 QA', contactPhone: '12135550177', contactEmail: null, status: 'ACTIVE', remark: `${context.marker}-procurement-fixture`, address: { addressType: 'WAREHOUSE', countryCode: 'US', stateProvince: 'California', city: 'Los Angeles', district: 'Commerce', postalCode: '90040', addressLine1: '601 Procurement Partner Avenue', addressLine2: null, latitude: null, longitude: null }, capabilities: [{ capabilityCode: 'STORAGE', capabilityName: 'Storage', enabled: true, cutoffTime: null, description: `${context.marker} procurement acceptance` }], } } async function createPartnerWarehouse(context) { const created = await domestic(context, 'POST', '创建本轮采购合作仓', '/api/platform/v1/warehouse-admin', partnerWarehousePayload(context)) const warehouseId = id(created.data) const valid = longId(warehouseId) context.assert(MODULE, '采购合作仓创建返回长ID', created.response, valid, `warehouseId=${warehouseId || 'missing'}; ${trace(created.response)}`, OPTIONS) if (!valid) return null const resource = { marker: context.marker, id: warehouseId, finalStatus: 'ACTIVE', retained: true } ownedRegistry(context).partnerWarehouse = resource return resource } function procurementPayload(context, activeCustomer) { return { customerName: activeCustomer?.tenantName || activeCustomer?.name || `Customer-${id(activeCustomer?.id)}`, purchaseCategory: 'OFFICE_SUPPLIES', productName: `${context.marker} 采购商品`, productSpecification: 'Phase4 acceptance', productUrl: null, purchaseQuantity: 10, budgetUnitPrice: 1.25, currency: 'USD', receivingAddress: 'Los Angeles', contactName: '采购验收联系人', contactPhone: '13800000002', remark: context.marker, idempotencyKey: `${context.marker}-phase4-procurement`.slice(0, 255), } } async function loginMerchant(context, activeCustomer) { const fixture = context.context?.merchantQualificationFixture if (!nonEmpty(fixture?.username) || !nonEmpty(fixture?.password)) { return blocker(context, '采购客户登录前置完整', null, `缺少 marker=${context.marker} 的客户账号凭据,禁止使用历史客户或降级为 coverageGap`) } const login = await context.request('POST', '/api/auth/login', { loginId: fixture.username, password: fixture.password }, '') const auth = context.apiSuccess(MODULE, '采购客户账号登录', login, trace(login), OPTIONS) const token = auth?.accessToken context.assert(MODULE, '采购客户登录返回统一Token', login, nonEmpty(token), `customerId=${id(activeCustomer?.id) || 'missing'}, tokenPresent=${Boolean(token)}; ${trace(login)}`, OPTIONS) return nonEmpty(token) ? token : null } async function createMerchantProcurement(context, activeCustomer, token) { const path = '/api/merchant/v1/last-leg/overseas-procurements' const payload = procurementPayload(context, activeCustomer) let activeToken = token let response = await context.request('POST', path, payload, activeToken, {}) // A newly approved customer is projected to the overseas region asynchronously. The // domestic login can therefore succeed a few seconds before the overseas gateway has // received the matching USER/TENANT/AUTHORIZATION versions. Retry only that exact // security-projection conflict and always issue a fresh short-lived overseas token. for (let attempt = 1; attempt <= 12 && response?.http === 403 && codeOf(response) === 'A0403'; attempt += 1) { await new Promise((resolve) => setTimeout(resolve, 1_000)) const refreshedToken = await loginMerchant(context, activeCustomer) if (!refreshedToken) break activeToken = refreshedToken response = await context.request('POST', path, payload, activeToken, {}) } const detail = context.apiSuccess(MODULE, '创建本轮客户采购订单', response, trace(response), OPTIONS) const value = summary(detail) const resource = nonEmpty(value?.orderNo) && longId(value?.customerId) ? { marker: context.marker, orderNo: id(value.orderNo), customerId: id(value.customerId), version: Number(value.version), finalStatus: value.status, retained: true } : null if (resource) ownedRegistry(context).purchase = resource const valid = nonEmpty(value?.orderNo) && id(value?.customerId) === id(activeCustomer?.id) && value?.status === 'PENDING_REVIEW' && Number(value?.version) === 0 context.assert(MODULE, '客户采购订单创建为待审核且归属正确', response, valid, `orderNo=${id(value?.orderNo) || 'missing'}, customerId=${id(value?.customerId) || 'missing'}, status=${value?.status || 'missing'}, version=${value?.version ?? 'missing'}; ${trace(response)}`, OPTIONS) if (!valid) return null const replayResponse = await context.request('POST', path, payload, activeToken, {}) const replayDetail = context.apiSuccess(MODULE, '采购创建同幂等键同载荷精确重放', replayResponse, trace(replayResponse), OPTIONS) const replay = summary(replayDetail) context.assert(MODULE, '采购幂等重放复用订单且版本不漂移', replayResponse, replay?.orderNo === value.orderNo && Number(replay?.version) === Number(value.version), `orderNo=${id(replay?.orderNo) || 'missing'}, version=${replay?.version ?? 'missing'}; ${trace(replayResponse)}`, OPTIONS) const mismatch = await context.request('POST', path, { ...payload, productName: `${payload.productName}-DIFFERENT` }, activeToken, {}) expectedConflict(context, '采购创建同幂等键异载荷返回冲突', mismatch) return { resource, payload } } async function prerequisites(context) { const tenants = await domesticGet(context, '查询 ACTIVE 客户候选', '/api/platform/v1/iam/tenants?page=1&size=200&status=ACTIVE') assertPage(context, 'ACTIVE 客户候选', tenants) const activeCustomers = records(tenants.data).filter((item) => longId(item?.id) && item?.status === 'ACTIVE') if (!activeCustomers.length) return blocker(context, '海外业务写验收 ACTIVE 客户前置', tenants.response, 'IAM 未返回真实 ACTIVE 客户,禁止跳过完整生命周期') const fixtureCustomerId = id(context.createdResources?.merchantQualificationFixture?.tenantId) const activeCustomer = activeCustomers.find((item) => id(item?.id) === fixtureCustomerId) if (!longId(fixtureCustomerId) || !activeCustomer) return blocker(context, '本轮资格客户属于 ACTIVE 候选', tenants.response, `marker fixture 客户 ${fixtureCustomerId || 'missing'} 不在 ACTIVE 候选中,禁止借用其他客户身份`) const token = await loginMerchant(context, activeCustomer) if (!token) return null const partnerWarehouse = await createPartnerWarehouse(context) if (!partnerWarehouse) return null const purchase = await createMerchantProcurement(context, activeCustomer, token) if (!purchase) return null const customerOptions = await overseas(context, 'GET', '查询采购客户候选', `${BASE}/overseas-procurements/customer-options?limit=200`) const warehouseOptions = await overseas(context, 'GET', '查询 ACTIVE 采购仓候选', `${BASE}/overseas-procurements/warehouse-options`) context.assert(MODULE, '采购客户候选结构正确', customerOptions.response, Array.isArray(customerOptions.data) && customerOptions.data.every((item) => longId(item?.customerId) && nonEmpty(item?.customerName)), `options=${customerOptions.data?.length ?? 'missing'}; ${trace(customerOptions.response)}`, OPTIONS) context.assert(MODULE, '采购仓候选结构正确', warehouseOptions.response, Array.isArray(warehouseOptions.data) && warehouseOptions.data.every((item) => longId(item?.warehouseId) && nonEmpty(item?.warehouseName)), `options=${warehouseOptions.data?.length ?? 'missing'}; ${trace(warehouseOptions.response)}`, OPTIONS) const procurementCustomer = Array.isArray(customerOptions.data) ? customerOptions.data.find((item) => id(item?.customerId) === id(activeCustomer.id)) : null const warehouse = Array.isArray(warehouseOptions.data) ? warehouseOptions.data.find((item) => id(item?.warehouseId) === partnerWarehouse.id) || warehouseOptions.data.find((item) => longId(item?.warehouseId)) : null if (!procurementCustomer) return blocker(context, '采购客户候选包含本轮客户', customerOptions.response, `本轮客户 ${id(activeCustomer.id)} 未进入采购客户候选,禁止继续写验收`) if (!warehouse) return blocker(context, '采购仓候选存在 ACTIVE 仓', warehouseOptions.response, '采购仓候选为空,禁止跳过采购三态清空') return { activeCustomer, procurementCustomer, warehouse, customerOptions, warehouseOptions, partnerWarehouse, purchase, token } } function assertProcurementHistory(context, result) { const histories = Array.isArray(result.data?.histories) ? result.data.histories : [] const operations = new Set(histories.map((item) => item?.operationType)) context.assert(MODULE, '采购流水覆盖创建编辑与审核', result.response, ['CREATE', 'UPDATE', 'REVIEW'].every((type) => operations.has(type)) && histories.some((item) => String(item?.remark || item?.changeDetail || '').includes(context.marker)), `historyCount=${histories.length}, operations=${[...operations].join(',') || 'missing'}; ${trace(result.response)}`, OPTIONS) } async function procurementFlow(context, prerequisite) { const customerId = id(prerequisite.procurementCustomer.customerId) const warehouseId = id(prerequisite.warehouse.warehouseId) const owned = prerequisite.purchase.resource const list = await overseas(context, 'GET', '按本轮客户查询采购订单', query(`${BASE}/overseas-procurements`, { page: 1, size: 20, customerId, orderNo: owned.orderNo })) assertPage(context, '海外采购订单', list) const row = records(list.data).find((item) => item?.orderNo === owned.orderNo && id(item?.customerId) === customerId) context.assert(MODULE, '采购列表包含本轮 marker 订单', list.response, Boolean(row), `orderNo=${owned.orderNo}, customerId=${customerId}; ${trace(list.response)}`, OPTIONS) if (!row) return blocker(context, '采购列表详情前置完整', list.response, `列表缺少 marker=${context.marker} 的本轮采购订单`) const path = query(`${BASE}/overseas-procurements/${encodeURIComponent(owned.orderNo)}`, { customerId }) const detail = await overseas(context, 'GET', '查询本轮采购订单详情', path) const before = summary(detail.data) context.assert(MODULE, '采购详情归属和初始版本正确', detail.response, before?.orderNo === owned.orderNo && id(before?.customerId) === customerId && Number(before?.version) === 0, `orderNo=${id(before?.orderNo) || 'missing'}, version=${before?.version ?? 'missing'}; ${trace(detail.response)}`, OPTIONS) const setResult = await overseas(context, 'PATCH', '采购可空字段设置非空值', path, { version: Number(before?.version), productSpecification: `${context.marker}-SPEC`, productUrl: `phase4-product-${context.marker}`, purchaseWarehouseId: warehouseId, purchaseWarehouseName: prerequisite.warehouse.warehouseName, domesticCarrier: `${context.marker}-CARRIER`, domesticTrackingNo: `${context.marker}-TRACK`, estimatedArrivalAt: '2026-12-31T12:00:00', remark: context.marker }) const setSummary = summary(setResult.data) context.assert(MODULE, '采购三态第一态设置值并递增版本', setResult.response, setSummary?.productSpecification === `${context.marker}-SPEC` && detailValue(setResult.data, 'productUrl')?.includes(context.marker) && id(setSummary?.purchaseWarehouseId) === warehouseId && Number(setSummary?.version) === Number(before?.version) + 1, `version=${setSummary?.version ?? 'missing'}, warehouseId=${id(setSummary?.purchaseWarehouseId) || 'missing'}; ${trace(setResult.response)}`, OPTIONS) const omittedResult = await overseas(context, 'PATCH', '采购可空字段缺失保持原值', path, { version: Number(setSummary?.version), domesticCarrier: `${context.marker}-CARRIER-2` }) const omittedSummary = summary(omittedResult.data) context.assert(MODULE, '采购三态第二态字段缺失不修改', omittedResult.response, omittedSummary?.productSpecification === setSummary?.productSpecification && detailValue(omittedResult.data, 'productUrl') === detailValue(setResult.data, 'productUrl') && id(omittedSummary?.purchaseWarehouseId) === warehouseId && Number(omittedSummary?.version) === Number(setSummary?.version) + 1, `version=${omittedSummary?.version ?? 'missing'}, specification=${omittedSummary?.productSpecification || 'missing'}; ${trace(omittedResult.response)}`, OPTIONS) const clearedResult = await overseas(context, 'PATCH', '采购可空字段显式 null 与空串清空', path, { version: Number(omittedSummary?.version), productSpecification: null, productUrl: '', purchaseWarehouseId: null, purchaseWarehouseName: null, domesticCarrier: null, domesticTrackingNo: '', estimatedArrivalAt: null, remark: null }) const clearedSummary = summary(clearedResult.data) context.assert(MODULE, '采购三态第三态显式清空全部生效', clearedResult.response, clearedSummary?.productSpecification == null && detailValue(clearedResult.data, 'productUrl') == null && clearedSummary?.purchaseWarehouseId == null && clearedSummary?.purchaseWarehouseName == null && detailValue(clearedResult.data, 'domesticCarrier') == null && detailValue(clearedResult.data, 'domesticTrackingNo') == null && detailValue(clearedResult.data, 'estimatedArrivalAt') == null && Number(clearedSummary?.version) === Number(omittedSummary?.version) + 1, `version=${clearedSummary?.version ?? 'missing'}, specification=${clearedSummary?.productSpecification ?? 'null'}, warehouseId=${clearedSummary?.purchaseWarehouseId ?? 'null'}; ${trace(clearedResult.response)}`, OPTIONS) const stale = await context.request('PATCH', path, { version: Number(setSummary?.version), productSpecification: 'STALE-DO-NOT-APPLY' }) expectedConflict(context, '采购旧 version 返回 A0409', stale, true) const afterConflict = await overseas(context, 'GET', '采购冲突后零漂移回查', path) const afterConflictSummary = summary(afterConflict.data) context.assert(MODULE, '采购旧 version 冲突未造成部分更新', afterConflict.response, afterConflictSummary?.productSpecification == null && Number(afterConflictSummary?.version) === Number(clearedSummary?.version), `version=${afterConflictSummary?.version ?? 'missing'}, specification=${afterConflictSummary?.productSpecification ?? 'null'}; ${trace(afterConflict.response)}`, OPTIONS) const rejected = await overseas(context, 'POST', '驳回本轮采购订单并进入合法终态', query(`${BASE}/overseas-procurements/${encodeURIComponent(owned.orderNo)}/review`, { customerId }), { version: Number(afterConflictSummary?.version), decision: 'REJECT', idempotencyKey: `${context.marker}-procurement-review-reject`.slice(0, 255), remark: `${context.marker}-REJECTED` }) const rejectedSummary = summary(rejected.data) context.assert(MODULE, '采购审核正向驳回成功', rejected.response, rejectedSummary?.status === 'REJECTED' && Number(rejectedSummary?.version) === Number(clearedSummary?.version) + 1, `status=${rejectedSummary?.status || 'missing'}, version=${rejectedSummary?.version ?? 'missing'}; ${trace(rejected.response)}`, OPTIONS) const rejectedReplayBody = { version: Number(afterConflictSummary?.version), decision: 'REJECT', idempotencyKey: `${context.marker}-procurement-review-reject`.slice(0, 255), remark: `${context.marker}-REJECTED` } const rejectedReplay = await overseas(context, 'POST', '采购审核同键同载荷精确重放', query(`${BASE}/overseas-procurements/${encodeURIComponent(owned.orderNo)}/review`, { customerId }), rejectedReplayBody) context.assert(MODULE, '采购审核重放复用终态且版本不漂移', rejectedReplay.response, summary(rejectedReplay.data)?.status === 'REJECTED' && Number(summary(rejectedReplay.data)?.version) === Number(rejectedSummary?.version), trace(rejectedReplay.response), OPTIONS) const reviewHashConflict = await context.request('POST', query(`${BASE}/overseas-procurements/${encodeURIComponent(owned.orderNo)}/review`, { customerId }), { ...rejectedReplayBody, remark: 'DIFFERENT-DO-NOT-APPLY' }) expectedConflict(context, '采购审核同键异载荷返回冲突', reviewHashConflict) const reverseReview = await context.request('POST', query(`${BASE}/overseas-procurements/${encodeURIComponent(owned.orderNo)}/review`, { customerId }), { version: Number(rejectedSummary?.version), decision: 'APPROVE', idempotencyKey: `${context.marker}-procurement-review-terminal-reverse`.slice(0, 255), remark: 'REVERSE-DO-NOT-APPLY' }) expectedConflict(context, '采购审核终态逆向返回冲突', reverseReview) const final = await overseas(context, 'GET', '采购最终状态与流水回查', path) const finalSummary = summary(final.data) context.assert(MODULE, '采购 marker 资源最终保持合法终态', final.response, finalSummary?.status === 'REJECTED' && Number(finalSummary?.version) === Number(rejectedSummary?.version), `status=${finalSummary?.status || 'missing'}, version=${finalSummary?.version ?? 'missing'}; ${trace(final.response)}`, OPTIONS) assertProcurementHistory(context, final) Object.assign(owned, { version: Number(finalSummary?.version), finalStatus: finalSummary?.status, retained: finalSummary?.status !== 'REJECTED' }) } function truckPayload(context) { const suffix = context.marker.replace(/[^A-Za-z0-9]/g, '').slice(-16) return { transportType: 'PORT_PICKUP', origin: `验收起运-${suffix}`, originAddress: 'Long Beach', destination: `验收目的-${suffix}`, destinationAddress: 'Ontario', containerType: '40HQ', palletCount: 1, packageCount: 2, totalWeightKg: 100, totalVolumeCbm: 2.5, budgetAmount: 88, currency: 'USD', contactName: '四期验收联系人', contactInfo: '13800000000', requestedDate: new Date(Date.now() + 7 * 86400000).toISOString().slice(0, 10), remark: context.marker, idempotencyKey: `${context.marker}-phase4-truck`.slice(0, 255), } } function looseboardPayload(context) { const suffix = context.marker.replace(/[^A-Za-z0-9]/g, '').slice(-16) return { transportMode: 'SHARED_TRUCK', palletRequired: 'YES', transportType: 'WAREHOUSE_TO_WAREHOUSE', origin: `散板起运-${suffix}`, originAddress: 'Los Angeles', destination: `散板目的-${suffix}`, destinationAddress: 'Ontario', containerType: '40GP', packageCount: 3, totalWeightKg: 80, totalVolumeCbm: 1.8, budgetAmount: 66, currency: 'USD', contactName: '散板验收联系人', contactInfo: '13800000001', requestedDate: new Date(Date.now() + 8 * 86400000).toISOString().slice(0, 10), remark: context.marker, idempotencyKey: `${context.marker}-phase4-looseboard`.slice(0, 255), } } async function assertCreateIdempotency(context, base, payload, customerId, label) { const path = query(base, { customerId }) const created = await overseas(context, 'POST', `创建 marker ${label}订单`, path, payload) const value = summary(created.data) if (!nonEmpty(value?.orderNo)) return { created, value, path } const replay = await overseas(context, 'POST', `${label}创建同幂等键同载荷精确重放`, path, payload) const replayValue = summary(replay.data) context.assert(MODULE, `${label}幂等重放复用订单且版本不漂移`, replay.response, replayValue?.orderNo === value.orderNo && Number(replayValue?.version) === Number(value.version), `orderNo=${id(replayValue?.orderNo) || 'missing'}, version=${replayValue?.version ?? 'missing'}; ${trace(replay.response)}`, OPTIONS) const mismatch = await context.request('POST', path, { ...payload, destination: `${payload.destination}-DIFFERENT` }) expectedConflict(context, `${label}创建同幂等键异载荷返回冲突`, mismatch) return { created, value, path } } async function cancelMarkerOrder(context, resource, base, cancellableStatuses, label) { if (!resource || resource.marker !== context.marker || !resource.orderNo || !longId(resource.customerId)) { blocker(context, `${label}最终处置资源归属完整`, null, `缺少 marker=${context.marker} 的 ${label} 资源登记,无法证明合法终态`) return } const detailPath = query(`${base}/${encodeURIComponent(resource.orderNo)}`, { customerId: resource.customerId }) const current = await overseas(context, 'GET', `清理前回查 marker ${label}订单`, detailPath) const currentSummary = summary(current.data) if (currentSummary?.status === 'CANCELLED') { resource.finalStatus = 'CANCELLED'; resource.version = Number(currentSummary.version); resource.retained = false return } if (!cancellableStatuses.includes(currentSummary?.status)) { resource.finalStatus = currentSummary?.status || resource.finalStatus; resource.version = Number(currentSummary?.version); resource.retained = true blocker(context, `${label}测试数据最终处置`, current.response, `本轮 marker 资源 ${resource.orderNo} 当前状态 ${resource.finalStatus || 'missing'} 不允许安全取消`) return } const statusPath = query(`${base}/${encodeURIComponent(resource.orderNo)}/status`, { customerId: resource.customerId }) const keyScope = label === '卡车' ? 'truck' : 'looseboard' const cancelled = await overseas(context, 'PATCH', `取消 marker ${label}订单`, statusPath, { version: Number(currentSummary.version), status: 'CANCELLED', idempotencyKey: `${context.marker}-${keyScope}-${resource.orderNo}-cancel`.slice(0, 255), remark: `${context.marker}-acceptance-cleanup` }) const cancelledSummary = summary(cancelled.data) const verified = await overseas(context, 'GET', `回查 marker ${label}订单最终状态`, detailPath) const finalSummary = summary(verified.data) const passed = cancelledSummary?.status === 'CANCELLED' && finalSummary?.status === 'CANCELLED' && Number(finalSummary?.version) === Number(cancelledSummary?.version) context.assert(MODULE, `${label}本轮测试数据最终合法取消`, verified.response, passed, `orderNo=${resource.orderNo}, status=${finalSummary?.status || 'missing'}, version=${finalSummary?.version ?? 'missing'}; statusCall=${trace(cancelled.response)}; verify=${trace(verified.response)}`, OPTIONS) resource.finalStatus = finalSummary?.status || cancelledSummary?.status || resource.finalStatus resource.version = Number(finalSummary?.version ?? cancelledSummary?.version) resource.retained = !passed } async function truckFlow(context, customerId) { const base = `${BASE}/us-truck-orders` const payload = truckPayload(context) const creation = await assertCreateIdempotency(context, base, payload, customerId, '卡车') const created = creation.created; const createdSummary = creation.value const orderNo = id(createdSummary?.orderNo) context.assert(MODULE, '卡车创建返回本轮业务键和待审核状态', created.response, Boolean(orderNo) && id(createdSummary?.customerId) === customerId && createdSummary?.status === 'PENDING_REVIEW' && Number(createdSummary?.version) === 0, `orderNo=${orderNo || 'missing'}, customerId=${id(createdSummary?.customerId) || 'missing'}, status=${createdSummary?.status || 'missing'}; ${trace(created.response)}`, OPTIONS) if (!orderNo) return blocker(context, '卡车完整生命周期创建前置', created.response, '卡车创建未返回订单号') const registry = ownedRegistry(context) registry.truck = { marker: context.marker, orderNo, customerId, id: id(createdSummary?.id), finalStatus: createdSummary.status, retained: true } const path = query(`${base}/${encodeURIComponent(orderNo)}`, { customerId }) try { const list = await overseas(context, 'GET', '按本轮客户与单号查询卡车列表', query(base, { page: 1, size: 20, customerId, orderNo })) assertPage(context, '本土卡车订单', list) context.assert(MODULE, '卡车列表包含本轮 marker 订单', list.response, records(list.data).some((item) => item?.orderNo === orderNo && id(item?.customerId) === customerId), `orderNo=${orderNo}; ${trace(list.response)}`, OPTIONS) const detail = await overseas(context, 'GET', '查询 marker 本土卡车详情', path) const before = summary(detail.data) const edited = await overseas(context, 'PATCH', '编辑 marker 本土卡车订单', path, { version: Number(before?.version), destination: `${payload.destination}-EDIT`, remark: `${context.marker}-truck-edited` }) const after = summary(edited.data) context.assert(MODULE, '卡车编辑版本递增且普通字段回显', edited.response, after?.destination === `${payload.destination}-EDIT` && Number(after?.version) === Number(before?.version) + 1 && after?.status === 'PENDING_REVIEW', `version=${after?.version ?? 'missing'}, destination=${after?.destination || 'missing'}; ${trace(edited.response)}`, OPTIONS) const stale = await context.request('PATCH', path, { version: Number(before?.version), destination: 'STALE-DO-NOT-APPLY' }) expectedConflict(context, '卡车旧 version 编辑返回 A0409', stale, true) const afterStale = await overseas(context, 'GET', '卡车旧 version 冲突后零漂移回查', path) const afterStaleSummary = summary(afterStale.data) context.assert(MODULE, '卡车冲突未造成部分更新', afterStale.response, afterStaleSummary?.destination === after?.destination && Number(afterStaleSummary?.version) === Number(after?.version), `destination=${afterStaleSummary?.destination || 'missing'}, version=${afterStaleSummary?.version ?? 'missing'}; ${trace(afterStale.response)}`, OPTIONS) const reviewPath = query(`${base}/${encodeURIComponent(orderNo)}/review`, { customerId }) const reviewBody = { version: Number(afterStaleSummary?.version), decision: 'APPROVE', idempotencyKey: `${context.marker}-truck-review-approve`.slice(0, 255), remark: `${context.marker}-FORWARD` } const forwarded = await overseas(context, 'POST', '卡车审核正向推进到待入仓', reviewPath, reviewBody) const forwardedSummary = summary(forwarded.data) context.assert(MODULE, '卡车状态正向推进成功', forwarded.response, forwardedSummary?.status === 'WAITING_INBOUND' && Number(forwardedSummary?.version) === Number(afterStaleSummary?.version) + 1, `status=${forwardedSummary?.status || 'missing'}, version=${forwardedSummary?.version ?? 'missing'}; ${trace(forwarded.response)}`, OPTIONS) const reviewReplay = await overseas(context, 'POST', '卡车审核同键同载荷精确重放', reviewPath, reviewBody) context.assert(MODULE, '卡车审核重放复用待入仓终态且版本不漂移', reviewReplay.response, summary(reviewReplay.data)?.status === 'WAITING_INBOUND' && Number(summary(reviewReplay.data)?.version) === Number(forwardedSummary?.version), trace(reviewReplay.response), OPTIONS) const reviewMismatch = await context.request('POST', reviewPath, { ...reviewBody, remark: 'DIFFERENT-DO-NOT-APPLY' }) expectedConflict(context, '卡车审核同键异载荷返回冲突', reviewMismatch) const reversed = await context.request('POST', reviewPath, { version: Number(forwardedSummary?.version), decision: 'REJECT', idempotencyKey: `${context.marker}-truck-review-terminal-reverse`.slice(0, 255), remark: 'REVERSE-DO-NOT-APPLY' }) expectedConflict(context, '卡车审核终态逆向返回冲突', reversed) const history = await overseas(context, 'GET', '卡车状态冲突后流水回查', path) const historySummary = summary(history.data) const histories = Array.isArray(history.data?.histories) ? history.data.histories : [] context.assert(MODULE, '卡车流水覆盖创建编辑与正向状态', history.response, historySummary?.status === 'WAITING_INBOUND' && histories.length >= 3 && histories.some((item) => String(item?.remark || '').includes(context.marker)), `status=${historySummary?.status || 'missing'}, historyCount=${histories.length}; ${trace(history.response)}`, OPTIONS) Object.assign(registry.truck, { version: Number(historySummary?.version), finalStatus: historySummary?.status }) } finally { await cancelMarkerOrder(context, registry.truck, base, ['PENDING_REVIEW', 'WAITING_INBOUND'], '卡车') } } async function looseboardFlow(context, customerId) { const base = `${BASE}/local-looseboard-orders` const payload = looseboardPayload(context) const creation = await assertCreateIdempotency(context, base, payload, customerId, '散板') const created = creation.created; const createdSummary = creation.value const orderNo = id(createdSummary?.orderNo) context.assert(MODULE, '散板创建返回本轮业务键和待接单状态', created.response, Boolean(orderNo) && id(createdSummary?.customerId) === customerId && createdSummary?.status === 'PENDING_ACCEPTANCE' && Number(createdSummary?.version) === 0, `orderNo=${orderNo || 'missing'}, customerId=${id(createdSummary?.customerId) || 'missing'}, status=${createdSummary?.status || 'missing'}; ${trace(created.response)}`, OPTIONS) if (!orderNo) return blocker(context, '散板完整生命周期创建前置', created.response, '散板创建未返回订单号') const registry = ownedRegistry(context) registry.looseboard = { marker: context.marker, orderNo, customerId, id: id(createdSummary?.id), finalStatus: createdSummary.status, retained: true } const path = query(`${base}/${encodeURIComponent(orderNo)}`, { customerId }) try { const list = await overseas(context, 'GET', '按本轮客户与单号查询散板列表', query(base, { page: 1, size: 20, customerId, orderNo })) assertPage(context, '本土散板订单', list) context.assert(MODULE, '散板列表包含本轮 marker 订单', list.response, records(list.data).some((item) => item?.orderNo === orderNo && id(item?.customerId) === customerId), `orderNo=${orderNo}; ${trace(list.response)}`, OPTIONS) const detail = await overseas(context, 'GET', '查询 marker 本土散板详情', path) const before = summary(detail.data) const { idempotencyKey: _key, ...editable } = payload const edited = await overseas(context, 'PUT', '编辑 marker 本土散板订单', path, { ...editable, version: Number(before?.version), destination: `${payload.destination}-EDIT`, remark: `${context.marker}-looseboard-edited` }) const after = summary(edited.data) context.assert(MODULE, '散板编辑版本递增且普通字段回显', edited.response, after?.destination === `${payload.destination}-EDIT` && Number(after?.version) === Number(before?.version) + 1 && after?.status === 'PENDING_ACCEPTANCE', `version=${after?.version ?? 'missing'}, destination=${after?.destination || 'missing'}; ${trace(edited.response)}`, OPTIONS) const stale = await context.request('PUT', path, { ...editable, version: Number(before?.version), destination: 'STALE-DO-NOT-APPLY' }) expectedConflict(context, '散板旧 version 编辑返回 A0409', stale, true) const afterStale = await overseas(context, 'GET', '散板旧 version 冲突后零漂移回查', path) const afterStaleSummary = summary(afterStale.data) context.assert(MODULE, '散板冲突未造成部分更新', afterStale.response, afterStaleSummary?.destination === after?.destination && Number(afterStaleSummary?.version) === Number(after?.version), `destination=${afterStaleSummary?.destination || 'missing'}, version=${afterStaleSummary?.version ?? 'missing'}; ${trace(afterStale.response)}`, OPTIONS) const statusPath = query(`${base}/${encodeURIComponent(orderNo)}/status`, { customerId }) const forwarded = await overseas(context, 'PATCH', '散板状态正向推进到已派车', statusPath, { version: Number(afterStaleSummary?.version), status: 'DISPATCHED', idempotencyKey: `${context.marker}-looseboard-status-dispatched`.slice(0, 255), remark: `${context.marker}-FORWARD` }) const forwardedSummary = summary(forwarded.data) context.assert(MODULE, '散板状态正向推进成功', forwarded.response, forwardedSummary?.status === 'DISPATCHED' && Number(forwardedSummary?.version) === Number(afterStaleSummary?.version) + 1, `status=${forwardedSummary?.status || 'missing'}, version=${forwardedSummary?.version ?? 'missing'}; ${trace(forwarded.response)}`, OPTIONS) const forwardBody = { version: Number(afterStaleSummary?.version), status: 'DISPATCHED', idempotencyKey: `${context.marker}-looseboard-status-dispatched`.slice(0, 255), remark: `${context.marker}-FORWARD` } const forwardReplay = await overseas(context, 'PATCH', '散板状态同键同载荷精确重放', statusPath, forwardBody) context.assert(MODULE, '散板状态重放复用已派车终态且版本不漂移', forwardReplay.response, summary(forwardReplay.data)?.status === 'DISPATCHED' && Number(summary(forwardReplay.data)?.version) === Number(forwardedSummary?.version), trace(forwardReplay.response), OPTIONS) const forwardMismatch = await context.request('PATCH', statusPath, { ...forwardBody, remark: 'DIFFERENT-DO-NOT-APPLY' }) expectedConflict(context, '散板状态同键异载荷返回冲突', forwardMismatch) const reversed = await context.request('PATCH', statusPath, { version: Number(forwardedSummary?.version), status: 'PENDING_ACCEPTANCE', idempotencyKey: `${context.marker}-looseboard-status-terminal-reverse`.slice(0, 255), remark: 'REVERSE-DO-NOT-APPLY' }) expectedConflict(context, '散板状态逆向回退返回冲突', reversed) const history = await overseas(context, 'GET', '散板状态冲突后流水回查', path) const historySummary = summary(history.data) const histories = Array.isArray(history.data?.histories) ? history.data.histories : [] const operations = new Set(histories.map((item) => item?.operationType)) context.assert(MODULE, '散板流水覆盖创建编辑与正向状态', history.response, historySummary?.status === 'DISPATCHED' && ['CREATE', 'UPDATE', 'STATUS'].every((type) => operations.has(type)) && histories.some((item) => String(item?.remark || item?.changeDetail || '').includes(context.marker)), `status=${historySummary?.status || 'missing'}, historyCount=${histories.length}, operations=${[...operations].join(',') || 'missing'}; ${trace(history.response)}`, OPTIONS) Object.assign(registry.looseboard, { version: Number(historySummary?.version), finalStatus: historySummary?.status }) } finally { await cancelMarkerOrder(context, registry.looseboard, base, ['PENDING_ACCEPTANCE', 'DISPATCHED'], '散板') } } async function retirePartnerWarehouse(context, resource) { if (!resource?.id) return const retired = await context.request('PATCH', `/api/platform/v1/warehouse-admin/${encodeURIComponent(resource.id)}/status?tenantId=0&status=INACTIVE`) const passed = retired?.http === 200 && codeOf(retired) === '200' context.addStep(MODULE, '本轮采购合作仓已停用收口', retired, passed, trace(retired), OPTIONS) resource.finalStatus = passed ? 'INACTIVE' : resource.finalStatus resource.retained = !passed } async function finalizePendingPurchase(context, resource) { if (!resource?.orderNo || !longId(resource?.customerId) || ['REJECTED', 'APPROVED'].includes(resource.finalStatus)) return const path = query(`${BASE}/overseas-procurements/${encodeURIComponent(resource.orderNo)}`, { customerId: resource.customerId }) const current = await overseas(context, 'GET', '采购最终处置前回查本轮订单', path) const currentSummary = summary(current.data) if (['REJECTED', 'APPROVED'].includes(currentSummary?.status)) { resource.finalStatus = currentSummary.status; resource.version = Number(currentSummary.version); resource.retained = false return } if (currentSummary?.status !== 'PENDING_REVIEW') { blocker(context, '采购测试数据最终处置', current.response, `本轮 marker 资源 ${resource.orderNo} 当前状态 ${currentSummary?.status || 'missing'} 无法安全驳回`) resource.finalStatus = currentSummary?.status || resource.finalStatus; resource.retained = true return } const rejected = await overseas(context, 'POST', '驳回未完成的本轮采购订单', query(`${BASE}/overseas-procurements/${encodeURIComponent(resource.orderNo)}/review`, { customerId: resource.customerId }), { version: Number(currentSummary?.version), decision: 'REJECT', idempotencyKey: `${context.marker}-procurement-review-cleanup`.slice(0, 255), remark: `${context.marker}-prerequisite-cleanup` }) const rejectedSummary = summary(rejected.data) const verified = await overseas(context, 'GET', '回查未完成采购订单最终状态', path) const finalSummary = summary(verified.data) const passed = rejectedSummary?.status === 'REJECTED' && finalSummary?.status === 'REJECTED' && Number(finalSummary?.version) === Number(rejectedSummary?.version) context.assert(MODULE, '采购本轮测试数据最终合法驳回', verified.response, passed, `orderNo=${resource.orderNo}, status=${finalSummary?.status || 'missing'}, version=${finalSummary?.version ?? 'missing'}; review=${trace(rejected.response)}; verify=${trace(verified.response)}`, OPTIONS) resource.finalStatus = finalSummary?.status || rejectedSummary?.status || resource.finalStatus resource.version = Number(finalSummary?.version ?? rejectedSummary?.version) resource.retained = !passed } export async function run(context) { let prerequisite = null try { prerequisite = await prerequisites(context) if (!prerequisite) return await procurementFlow(context, prerequisite) const customerId = id(prerequisite.activeCustomer.id) await truckFlow(context, customerId) await looseboardFlow(context, customerId) } finally { const registry = context.createdResources.phase4OverseasBusiness await finalizePendingPurchase(context, registry?.purchase) await retirePartnerWarehouse(context, registry?.partnerWarehouse) } }