const PLATFORM = '/api/platform/v1/last-leg/warehouse-transfers' const MERCHANT = '/api/merchant/v1/last-leg/warehouse-transfers' const MODULE = 'phase4-overseas-client-services' const OPTIONS = { scenario: 'warehouse-transfer-review', deploymentBlocker: true } const ok = r => r?.http === 200 && String(r.body?.code) === '200' const rejected = (r, status) => r?.http === status || (r?.http === 200 && String(r.body?.code) === `A0${status}`) const evidence = r => `HTTP=${r?.http}, code=${r?.body?.code}, requestId=${r?.requestId}, traceId=${r?.traceId}` // Called by the registered client-services module, never a standalone fixture runner. export async function verifyWarehouseTransferReview(context, token, customerId, sourceBody, transferNo) { if (!customerId || String(customerId) === '0' || context.context?.merchantQualificationFixture?.marker !== context.marker) { context.addStep(MODULE, '转运审核缺少本轮客户作用域', null, false, 'No historical fallback', OPTIONS) return false } const scoped = no => `${PLATFORM}/${encodeURIComponent(no)}?customerId=${encodeURIComponent(customerId)}` const reviewPath = no => `${PLATFORM}/${encodeURIComponent(no)}/review?customerId=${encodeURIComponent(customerId)}` async function call(method, path, body, merchantToken) { return context.request(method, path, body, merchantToken, {}) } const initial = await call('GET', scoped(transferNo)) context.addStep(MODULE, '平台转运详情按客户读取待审核单', initial, ok(initial) && initial.body.data?.status === 'PENDING_REVIEW', evidence(initial), OPTIONS) if (!ok(initial)) return false const denied = await call('POST', reviewPath(transferNo), { decision: 'APPROVE' }, token) context.addStep(MODULE, '客户Token没有平台专用转运审核权限', denied, rejected(denied, 403), evidence(denied), OPTIONS) const missingScope = await call('POST', `${PLATFORM}/${encodeURIComponent(transferNo)}/review`, { decision: 'APPROVE' }) context.addStep(MODULE, '平台转运审核缺客户作用域拒绝', missingScope, rejected(missingScope, 400), evidence(missingScope), OPTIONS) const invalid = await call('POST', reviewPath(transferNo), { decision: 'INVALID' }) context.addStep(MODULE, '非法审核决定拒绝', invalid, rejected(invalid, 400), evidence(invalid), OPTIONS) const unchanged = await call('GET', scoped(transferNo)) context.addStep(MODULE, '审核负向后状态和分配零漂移', unchanged, ok(unchanged) && unchanged.body.data?.status === 'PENDING_REVIEW' && JSON.stringify(unchanged.body.data?.allocations) === JSON.stringify(initial.body.data?.allocations), evidence(unchanged), OPTIONS) // Existing-stock approval must not be confused with inbound-transit PDA receipt. const approved = await call('POST', reviewPath(transferNo), { decision: 'APPROVE', remark: context.marker }) context.addStep(MODULE, '已有库存转运通过为APPROVED且主单进入入库计量', approved, ok(approved) && approved.body.data?.status === 'APPROVED' && approved.body.data?.orderStatus === 'INBOUND_MEASURING', evidence(approved), OPTIONS) if (!ok(approved)) return false const replay = await call('POST', reviewPath(transferNo), { decision: 'APPROVE', remark: context.marker }) context.addStep(MODULE, '同决定重放不推进状态', replay, ok(replay) && replay.body.data?.status === 'APPROVED' && replay.body.data?.transferNo === transferNo, evidence(replay), OPTIONS) const reverse = await call('POST', reviewPath(transferNo), { decision: 'REJECT', remark: '反向审核应拒绝' }) context.addStep(MODULE, '已通过转运不能反向驳回', reverse, rejected(reverse, 409), evidence(reverse), OPTIONS) const readback = await call('GET', `${MERCHANT}/${encodeURIComponent(transferNo)}`, undefined, token) context.addStep(MODULE, '客户回查审核通过且未产生出库', readback, ok(readback) && readback.body.data?.status === 'APPROVED' && readback.body.data?.actualShipAt == null && Array.isArray(readback.body.data?.allocations) && readback.body.data.allocations.length === 0, evidence(readback), OPTIONS) for (const decision of ['APPROVE', 'REJECT']) { const body = { ...sourceBody, inboundCode: null, sourceLastlegOrderIds: [], idempotencyKey: `${context.marker}-TRANSIT-${decision}`.slice(0, 255) } const created = await call('POST', MERCHANT, body, token) const no = created.body?.data?.transferNo if (ok(created) && no && created.body.data?.status === 'DRAFT') { const submitted = await call('POST', `${MERCHANT}/${encodeURIComponent(no)}/submit`, undefined, token) if (ok(submitted) && submitted.body?.data) created.body.data = submitted.body.data } context.addStep(MODULE, `创建本轮新到仓转运${decision}对照`, created, ok(created) && Boolean(no) && created.body.data?.status === 'PENDING_REVIEW', evidence(created), OPTIONS) if (!ok(created) || !no) return false const result = await call('POST', reviewPath(no), { decision, remark: `${context.marker}-审核原因` }) const expected = decision === 'APPROVE' ? 'WAIT_PDA_RECEIVING' : 'REJECTED' const expectedOrder = decision === 'APPROVE' ? 'INBOUND_MEASURING' : 'CANCELLED' const detail = await call('GET', `${MERCHANT}/${encodeURIComponent(no)}`, undefined, token) context.addStep(MODULE, `新到仓转运${decision}回查${expected}`, detail, ok(result) && ok(detail) && detail.body.data?.status === expected && detail.body.data?.orderType === 'WAREHOUSE_TRANSFER' && detail.body.data?.orderStatus === expectedOrder && detail.body.data?.reviewDecision === `${decision}ED`.replace('APPROVEED', 'APPROVED') && detail.body.data?.reviewRemark === `${context.marker}-审核原因` && Boolean(detail.body.data?.reviewedBy) && Boolean(detail.body.data?.reviewedAt) && (detail.body.data?.pallets || []).length === 0, evidence(detail), OPTIONS) context.createdResources.phase4OverseasClientServices[`transit${decision}`] = { transferNo: no, status: detail.body?.data?.status } } const reasonFixture = await call('POST', MERCHANT, { ...sourceBody, inboundCode: null, sourceLastlegOrderIds: [], idempotencyKey: `${context.marker}-TRANSIT-REASON`.slice(0, 255) }, token) const reasonNo = reasonFixture.body?.data?.transferNo if (ok(reasonFixture) && reasonNo && reasonFixture.body.data?.status === 'DRAFT') { const submitted = await call('POST', `${MERCHANT}/${encodeURIComponent(reasonNo)}/submit`, undefined, token) if (ok(submitted) && submitted.body?.data) reasonFixture.body.data = submitted.body.data } context.addStep(MODULE, '创建空驳回原因独立反向票', reasonFixture, ok(reasonFixture) && Boolean(reasonNo), evidence(reasonFixture), OPTIONS) if (!ok(reasonFixture) || !reasonNo) return false const emptyReason = await call('POST', reviewPath(reasonNo), { decision: 'REJECT', remark: '' }) context.addStep(MODULE, '空驳回原因应拒绝', emptyReason, rejected(emptyReason, 400), evidence(emptyReason), OPTIONS) const reasonReadback = await call('GET', scoped(reasonNo)) context.addStep(MODULE, '空原因拒绝后仍待审核', reasonReadback, ok(reasonReadback) && reasonReadback.body.data?.status === 'PENDING_REVIEW', evidence(reasonReadback), OPTIONS) context.createdResources.phase4OverseasClientServices.reasonValidationTransferNo = reasonNo return true }