2026-09-14 12:00:48 +08:00
|
|
|
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.requestOverseas(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 === 'PENDING_ASSIGNMENT', 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
|
|
|
|
|
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' ? 'WAIT_DELIVERY' : '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
|
2026-09-15 15:15:46 +08:00
|
|
|
&& 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)
|
2026-09-14 12:00:48 +08:00
|
|
|
&& (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
|
|
|
|
|
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: '' })
|
2026-09-14 13:46:32 +08:00
|
|
|
context.addStep(MODULE, '空驳回原因应拒绝', emptyReason, rejected(emptyReason, 400), evidence(emptyReason), OPTIONS)
|
2026-09-14 12:00:48 +08:00
|
|
|
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
|
|
|
|
|
}
|