Files

95 lines
7.3 KiB
JavaScript

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
}