test(appointments): verify stable batches and current HTTP contracts
This commit is contained in:
@@ -2,11 +2,16 @@ const MODULE = 'phase4-overseas-inbound-operations'
|
||||
const OPTIONS = { scenario: 'phase4-inbound-admin-and-inventory-commands', deploymentBlocker: true }
|
||||
const PLAN_BASE = '/api/platform/v1/last-leg/inbound-plans'
|
||||
const INVENTORY_BASE = '/api/platform/v1/last-leg/inventory'
|
||||
const APPOINTMENT_BASE = '/api/platform/v1/last-leg/appointment-planning'
|
||||
|
||||
function id(value) { return value == null ? '' : String(value) }
|
||||
function count(value) {
|
||||
const parsed = value == null || value === '' ? NaN : Number(value)
|
||||
return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : NaN
|
||||
}
|
||||
function ok(response) { return response?.http === 200 && String(response?.body?.code) === '200' }
|
||||
function rows(data) { return Array.isArray(data?.records) ? data.records : Array.isArray(data) ? data : [] }
|
||||
function conflict(response) { return response?.http === 200 && ['A0409', '409'].includes(String(response?.body?.code)) }
|
||||
function conflict(response) { return [200, 409].includes(response?.http) && String(response?.body?.code) === 'A0409' && response.body?.data == null }
|
||||
function trace(response) { return `HTTP=${response?.http ?? 'missing'}, code=${response?.body?.code ?? 'missing'}, requestId=${response?.requestId || 'missing'}, traceId=${response?.traceId || 'missing'}` }
|
||||
function query(path, values) {
|
||||
const params = new URLSearchParams()
|
||||
@@ -18,7 +23,154 @@ function key(context, operation) { return `${context.marker}:P4-INBOUND:${operat
|
||||
async function request(context, method, name, path, body, headers = {}) {
|
||||
const response = await context.request(method, path, body, undefined, headers)
|
||||
context.addStep(MODULE, name, response, ok(response), trace(response), OPTIONS)
|
||||
return ok(response) ? response.body?.data : null
|
||||
if (!ok(response)) throw Error(`${name}: ${trace(response)}; stop, retain original request and sample`)
|
||||
return response.body?.data
|
||||
}
|
||||
|
||||
function requireProof(context, name, condition, evidence) {
|
||||
context.assert(MODULE, name, null, condition, evidence, OPTIONS)
|
||||
if (!condition) throw Error(`${name}: ${evidence}`)
|
||||
}
|
||||
|
||||
/** Current-run original-domain producer: verifies actual sources, proves cancellation, retains a fresh active batch. */
|
||||
export async function prepareAppointmentFixture(context, { customerId, plan, fbaLines }) {
|
||||
const sample = fbaLines[0]
|
||||
requireProof(context, '本轮预约只选择同接仓点同目的仓真实来源', Boolean(customerId && plan?.planNo && plan?.overseasWarehouseId && sample?.fbaWarehouseCode)
|
||||
&& fbaLines.length > 0 && fbaLines.length <= 100 && fbaLines.every(l => l.orderId && l.id && l.fbaWarehouseCode === sample.fbaWarehouseCode
|
||||
&& id(l.destinationWarehouseId) === id(sample.destinationWarehouseId)), `planNo=${plan?.planNo || 'missing'}`)
|
||||
const suffix = String(context.marker).replace(/[^A-Za-z0-9]/g, '').slice(-18).toUpperCase()
|
||||
const verified = []
|
||||
for (const orderId of [...new Set(fbaLines.map(l => id(l.orderId)))]) {
|
||||
const sourceKey = `ORDER:${orderId}`, destinationKey = `FBA:${sample.fbaWarehouseCode}`
|
||||
const sourcePath = `${APPOINTMENT_BASE}/sources/${encodeURIComponent(sourceKey)}/cargo-verification`
|
||||
const orderPath = query(`/api/platform/v1/last-leg/orders/${encodeURIComponent(orderId)}`, { customerId })
|
||||
const orderBefore = await request(context, 'GET', '核实前读取原订单货量', orderPath)
|
||||
const orderFacts = value => ({ id: id(value?.order?.id), customerId: id(value?.order?.customerId), packageCount: value?.order?.packageCount,
|
||||
receivedPackageCount: value?.order?.receivedPackageCount, sourceInboundId: value?.order?.sourceInboundId, version: value?.order?.version, cargo: value?.cargo })
|
||||
requireProof(context, '核实前原订单身份和箱数真实可读', id(orderBefore?.order?.id) === orderId && id(orderBefore.order.customerId) === customerId
|
||||
&& Number.isSafeInteger(orderBefore.order.packageCount) && orderBefore.order.packageCount > 0, sourceKey)
|
||||
const before = await request(context, 'GET', '读取本轮逐票核实上下文', query(sourcePath, { customerId, destinationKey }))
|
||||
requireProof(context, '核实数量和版本必须来自当前货源', before?.source?.sourceKey === sourceKey && id(before.source.customerId) === customerId
|
||||
&& before.source.destinationKey === destinationKey && id(before.source.originWarehouseId) === id(plan.overseasWarehouseId)
|
||||
&& before.source.canFollowup === true && typeof before.source.sourceVersion === 'string' && before.source.sourceVersion.length > 0
|
||||
&& Number.isSafeInteger(before.source.packages) && before.source.packages > 0 && count(before.allocatedPackages) === 0 && count(before.unknownAllocationCount) === 0
|
||||
&& before.sourceChanged === false,
|
||||
`sourceKey=${sourceKey}, allocated=${before?.allocatedPackages ?? 'missing'}`)
|
||||
const body = { destinationKey, expectedSourceVersion: before.source.sourceVersion, expectedVersion: before.verification?.version ?? 0,
|
||||
packageCount: before.source.packages > 1 ? before.source.packages - 1 : before.source.packages, shipmentReferences: before.verification?.shipmentReferences ?? [],
|
||||
remark: `${context.marker} PENDING预约仅核实真实箱数`, idempotencyKey: `verify-${suffix}-${verified.length}` }
|
||||
const stale = await context.request('POST', query(sourcePath, { customerId }), { ...body,
|
||||
expectedSourceVersion: `stale-${before.source.sourceVersion}`.slice(0, 160), idempotencyKey: `${body.idempotencyKey}-stale` })
|
||||
context.addStep(MODULE, '陈旧货源版本拒绝保存核实资料', stale, conflict(stale), trace(stale), OPTIONS)
|
||||
requireProof(context, '陈旧货源拒绝必须明确返回冲突', conflict(stale), trace(stale))
|
||||
const unchanged = await request(context, 'GET', '拒绝后回查核实记录未变', query(sourcePath, { customerId, destinationKey }))
|
||||
requireProof(context, '陈旧货源写入未生成核实记录', JSON.stringify(unchanged.verification) === JSON.stringify(before.verification)
|
||||
&& unchanged.allocatedPackages === before.allocatedPackages, sourceKey)
|
||||
let saved = await request(context, 'POST', '保存独立预约核实箱数和当前来源版本', query(sourcePath, { customerId }), body)
|
||||
const replay = await request(context, 'POST', '逐票核实原键原载荷重放', query(sourcePath, { customerId }), body)
|
||||
requireProof(context, '核实重放版本和事实零重复', JSON.stringify(saved) === JSON.stringify(replay)
|
||||
&& saved.version === body.expectedVersion + 1 && saved.sourceVersion === body.expectedSourceVersion && saved.packageCount === body.packageCount, sourceKey)
|
||||
const orderAfter = await request(context, 'GET', '核实箱数减少后原订单仍保持原事实', orderPath)
|
||||
requireProof(context, '预约核实不覆盖原订单箱数版本及货物明细', JSON.stringify(orderFacts(orderAfter)) === JSON.stringify(orderFacts(orderBefore)), sourceKey)
|
||||
if (body.packageCount !== before.source.packages) saved = await request(context, 'POST', '将预约核实恢复为本轮完整货量', query(sourcePath, { customerId }), {
|
||||
...body, expectedVersion: saved.version, packageCount: before.source.packages, idempotencyKey: `${body.idempotencyKey}-full` })
|
||||
const current = await request(context, 'GET', '回查当前核实及可分配箱数', query(sourcePath, { customerId, destinationKey }))
|
||||
requireProof(context, '核实来源未漂移且箱数完整可分配', current.sourceChanged === false && current.verification?.version === saved.version
|
||||
&& count(current.availablePackages) === before.source.packages && count(current.allocatedPackages) === 0, sourceKey)
|
||||
verified.push({ customerId, sourceKey, destinationKey, packageCount: before.source.packages, sourceVersion: saved.sourceVersion, cargoVerificationVersion: saved.version })
|
||||
}
|
||||
const createBody = { originWarehouseId: id(plan.overseasWarehouseId), fbaWarehouseCode: sample.fbaWarehouseCode,
|
||||
lineIds: fbaLines.map(l => id(l.id)), idempotencyKey: `fba-${suffix}`,
|
||||
details: { plannedDate: new Date(Date.now() + 86400000).toISOString().slice(0, 10), appointmentStatus: 'PENDING', appointmentRemark: context.marker } }
|
||||
const appointment = await request(context, 'POST', '批量创建FBA预约', `${PLAN_BASE}/fba-appointments/batch`, createBody)
|
||||
const appointmentNo = appointment?.appointmentNo
|
||||
requireProof(context, '原预约必须返回不可变批次及业务号', Boolean(appointmentNo && appointment.id && appointment.batchKey === `FBA_APPOINTMENT:${appointment.id}`), 'stable batch identity')
|
||||
const replay = await request(context, 'POST', 'FBA预约同单号同载荷重放', `${PLAN_BASE}/fba-appointments/batch`, createBody)
|
||||
requireProof(context, 'FBA创建重放保留不可变批次和全部逐票分配', JSON.stringify(replay) === JSON.stringify(appointment), appointmentNo)
|
||||
const changed = await context.request('POST', `${PLAN_BASE}/fba-appointments/batch`, { ...createBody, details: { ...createBody.details, appointmentRemark: `${context.marker}-different` } })
|
||||
context.addStep(MODULE, 'FBA预约同单号异载荷返回A0409', changed, conflict(changed), trace(changed), OPTIONS)
|
||||
requireProof(context, 'FBA异载荷必须明确拒绝', conflict(changed), trace(changed))
|
||||
const detailPath = `${PLAN_BASE}/fba-appointments/${encodeURIComponent(appointmentNo)}`
|
||||
const detail = await request(context, 'GET', '查询FBA预约详情', detailPath)
|
||||
const exactItems = value => (value?.items || []).map(i => JSON.stringify({ customerId: id(i.customerId), sourceKey: i.sourceKey,
|
||||
destinationKey: i.destinationKey, packageCount: i.packageCount, sourceVersion: i.sourceVersion, cargoVerificationVersion: i.cargoVerificationVersion })).sort()
|
||||
requireProof(context, 'FBA预约逐票归属数量及核实版本冻结', detail.batchKey === appointment.batchKey && detail.appointmentStatus === 'PENDING'
|
||||
&& JSON.stringify(exactItems(detail)) === JSON.stringify(verified.map(v => JSON.stringify(v)).sort()), appointmentNo)
|
||||
const batch = await request(context, 'GET', '按不可变批次查询原预约上下文', `${APPOINTMENT_BASE}/batches/${encodeURIComponent(detail.batchKey)}`)
|
||||
requireProof(context, '稳定批次上下文返回同一原预约', batch?.appointment?.batchKey === detail.batchKey && batch.appointment.appointmentNo === appointmentNo
|
||||
&& JSON.stringify(exactItems(batch.appointment)) === JSON.stringify(exactItems(detail)), detail.batchKey)
|
||||
const cancelBody = { version: detail.version, cancelReason: `${context.marker}-cancelled` }
|
||||
await request(context, 'DELETE', '按正式取消操作取消本轮预约', detailPath, cancelBody)
|
||||
await request(context, 'DELETE', '原取消版本和原因重放', detailPath, cancelBody)
|
||||
const cancelled = await request(context, 'GET', '回查已取消预约的原批次与历史事实', detailPath)
|
||||
requireProof(context, '取消保留原批次且仅推进一次版本', cancelled.appointmentStatus === 'CANCELLED' && cancelled.batchKey === detail.batchKey
|
||||
&& cancelled.version === detail.version + 1 && JSON.stringify(exactItems(cancelled)) === JSON.stringify(exactItems(detail)), appointmentNo)
|
||||
const history = await request(context, 'GET', '核对唯一取消历史和保留的原计划', `${detailPath}/cancellations?page=1&size=20`)
|
||||
requireProof(context, '取消重放不重复历史且保留原计划日期', count(history?.total) === 1 && history.records?.length === 1
|
||||
&& history.records[0].cancelReason === cancelBody.cancelReason && history.records[0].version === cancelled.version
|
||||
&& history.records[0].snapshot?.appointmentStatus === 'CANCELLED'
|
||||
&& history.records[0].snapshot?.plannedDate === detail.plannedDate, appointmentNo)
|
||||
for (const v of verified) {
|
||||
const restored = await request(context, 'GET', '取消后逐票可分配量恢复', query(`${APPOINTMENT_BASE}/sources/${encodeURIComponent(v.sourceKey)}/cargo-verification`, { customerId, destinationKey: v.destinationKey }))
|
||||
requireProof(context, '取消未删除核实事实且恢复剩余需求', count(restored.allocatedPackages) === 0 && count(restored.availablePackages) === v.packageCount
|
||||
&& restored.verification?.version === v.cargoVerificationVersion && restored.sourceChanged === false, v.sourceKey)
|
||||
}
|
||||
const active = await request(context, 'POST', '为本轮后续验收重新建立有效批次', `${PLAN_BASE}/fba-appointments/batch`, { ...createBody, idempotencyKey: `${createBody.idempotencyKey}-active` })
|
||||
requireProof(context, '重新安排生成新批次且逐票数量不变', active.batchKey === `FBA_APPOINTMENT:${active.id}` && active.batchKey !== cancelled.batchKey
|
||||
&& active.appointmentNo !== appointmentNo && active.appointmentStatus === 'PENDING' && JSON.stringify(exactItems(active)) === JSON.stringify(exactItems(detail)), active?.appointmentNo || 'missing')
|
||||
const activeContext = await request(context, 'GET', '回查保留的有效批次', `${APPOINTMENT_BASE}/batches/${encodeURIComponent(active.batchKey)}`)
|
||||
requireProof(context, '保留批次上下文完整可供后续HTTP核对', activeContext?.appointment?.batchKey === active.batchKey
|
||||
&& activeContext.appointment.appointmentStatus === 'PENDING', active.batchKey)
|
||||
const repartitioned = await verifyRepartition(context, active, verified, suffix, createBody.details.plannedDate)
|
||||
return { appointmentNo: repartitioned.active.appointmentNo, batchKey: repartitioned.active.batchKey, terminalStatus: repartitioned.active.appointmentStatus,
|
||||
cancelledAppointmentNo: appointmentNo, cancelledBatchKey: cancelled.batchKey, cancelledVersion: cancelled.version,
|
||||
replacedBatchKeys: repartitioned.replacedBatchKeys, splitBatchKeys: repartitioned.splitBatchKeys,
|
||||
verifiedSources: verified, plannedDate: createBody.details.plannedDate, timezone: repartitioned.active.timezone }
|
||||
}
|
||||
|
||||
async function verifyRepartition(context, initial, verified, suffix, plannedDate) {
|
||||
const total = verified.reduce((n,v)=>n+v.packageCount,0)
|
||||
requireProof(context, '分批合并使用本轮至少两箱真实来源', total >= 2, `packages=${total}; use at least two actual packages`)
|
||||
const detailPath = no => `${PLAN_BASE}/fba-appointments/${encodeURIComponent(no)}`
|
||||
const active = await request(context,'PUT','推进原预约版本以验证陈旧版本拒绝',detailPath(initial.appointmentNo),{
|
||||
version:initial.version,idempotencyKey:`version-${suffix}`,details:{plannedDate,appointmentStatus:'PENDING',appointmentRemark:`${context.marker}-version-proof`} })
|
||||
const allocation = (v,count=v.packageCount) => ({customerId:v.customerId,sourceKey:v.sourceKey,packageCount:count,
|
||||
expectedSourceVersion:v.sourceVersion,expectedVerificationVersion:v.cargoVerificationVersion})
|
||||
let left=Math.floor(total/2);const first=[],second=[]
|
||||
for(const v of verified){const count=Math.min(v.packageCount,left);if(count)first.push(allocation(v,count));if(count<v.packageCount)second.push(allocation(v,v.packageCount-count));left-=count}
|
||||
const body={originals:[{appointmentNo:active.appointmentNo,version:active.version}],batches:[first,second].map(allocations=>({plannedDate,remark:context.marker,allocations})),reason:`${context.marker}-split`,idempotencyKey:`split-${suffix}`}
|
||||
const endpoint=`${APPOINTMENT_BASE}/appointments/repartition`
|
||||
for(const [name,negative,expected]of [
|
||||
['旧预约版本分批拒绝',{...body,originals:[{appointmentNo:active.appointmentNo,version:initial.version}],idempotencyKey:`split-stale-${suffix}`},'A0409'],
|
||||
['分批逐票总量不守恒拒绝',{...body,batches:[{...body.batches[0],allocations:body.batches[0].allocations.map((a,i)=>i? a:{...a,packageCount:a.packageCount+1})},body.batches[1]],idempotencyKey:`split-count-${suffix}`},'A0400']]){
|
||||
const denied=await context.request('POST',endpoint,negative)
|
||||
const rejected=[200,expected==='A0409'?409:400].includes(denied?.http)&&denied?.body?.code===expected&&denied.body.data==null
|
||||
context.addStep(MODULE,name,denied,rejected,trace(denied),OPTIONS);requireProof(context,name,rejected,trace(denied))
|
||||
const unchanged=await request(context,'GET','拒绝分批后原批次保持不变',detailPath(active.appointmentNo))
|
||||
requireProof(context,'拒绝未改写原批次版本分配或替代链',JSON.stringify(unchanged)===JSON.stringify(active),active.appointmentNo)
|
||||
}
|
||||
const sumSources = batches => {
|
||||
const result=new Map()
|
||||
for(const b of batches)for(const i of b.items){const k=`${i.customerId}/${i.sourceKey}/${i.destinationKey}`;result.set(k,(result.get(k)||0)+i.packageCount)}
|
||||
return JSON.stringify([...result].sort((a,b)=>a[0].localeCompare(b[0])))
|
||||
}
|
||||
const split=await request(context,'POST','原批次按真实货量分成两批',endpoint,body)
|
||||
const splitReplay=await request(context,'POST','分批原键原载荷重放',endpoint,body)
|
||||
requireProof(context,'分批重放只返回原两批且逐票总量守恒',JSON.stringify(split)===JSON.stringify(splitReplay)&&split.appointments?.length===2
|
||||
&&new Set(split.appointments.map(a=>a.batchKey)).size===2&&split.appointments.every(a=>a.batchKey===`FBA_APPOINTMENT:${a.id}`&&a.batchKey!==active.batchKey)
|
||||
&&sumSources(split.appointments)===sumSources([active]),active.batchKey)
|
||||
const old=await request(context,'GET','旧批次保存明确分批替代链',detailPath(active.appointmentNo))
|
||||
requireProof(context,'旧批次不可变键保留并指向真实新批次',old.batchKey===active.batchKey&&JSON.stringify([...old.replacementBatchKeys].sort())===JSON.stringify(split.appointments.map(a=>a.batchKey).sort()),active.batchKey)
|
||||
const changed=await context.request('POST',endpoint,{...body,reason:`${body.reason}-different`})
|
||||
requireProof(context,'分批原键异载荷拒绝',conflict(changed),trace(changed))
|
||||
const mergeBody={originals:split.appointments.map(a=>({appointmentNo:a.appointmentNo,version:a.version})),batches:[{plannedDate,remark:context.marker,allocations:verified.map(v=>allocation(v))}],reason:`${context.marker}-merge`,idempotencyKey:`merge-${suffix}`}
|
||||
const merged=await request(context,'POST','两批按原来源数量合并',endpoint,mergeBody)
|
||||
const mergeReplay=await request(context,'POST','合并原键原载荷重放',endpoint,mergeBody)
|
||||
requireProof(context,'合并新键唯一且逐票总量不变',JSON.stringify(merged)===JSON.stringify(mergeReplay)&&merged.appointments?.length===1
|
||||
&&merged.appointments[0].batchKey===`FBA_APPOINTMENT:${merged.appointments[0].id}`&&!split.appointments.some(a=>a.batchKey===merged.appointments[0].batchKey)
|
||||
&&sumSources(merged.appointments)===sumSources([active]),active.appointmentNo)
|
||||
for(const child of split.appointments){const replaced=await request(context,'GET','分批历史指向合并后的稳定批次',detailPath(child.appointmentNo));requireProof(context,'合并保留旧批次及唯一替代指向',replaced.batchKey===child.batchKey&&JSON.stringify(replaced.replacementBatchKeys)===JSON.stringify([merged.appointments[0].batchKey]),child.appointmentNo)}
|
||||
return {active:merged.appointments[0],replacedBatchKeys:[active.batchKey,...split.appointments.map(a=>a.batchKey)],splitBatchKeys:split.appointments.map(a=>a.batchKey)}
|
||||
}
|
||||
|
||||
async function inboundAdminLifecycle(context) {
|
||||
@@ -68,8 +220,10 @@ async function inboundAdminLifecycle(context) {
|
||||
Boolean(plan?.planNo) && replay?.planNo === plan.planNo,
|
||||
`first=${plan?.planNo || 'missing'}, replay=${replay?.planNo || 'missing'}`, OPTIONS)
|
||||
const planNo = plan?.planNo
|
||||
const fbaLines = (plan?.lines || []).filter((line) => line?.fbaWarehouseCode
|
||||
const eligibleLines = (plan?.lines || []).filter((line) => line?.fbaWarehouseCode
|
||||
&& line?.destinationWarehouseId)
|
||||
const fbaLines = eligibleLines.filter(line => line.fbaWarehouseCode === eligibleLines[0]?.fbaWarehouseCode
|
||||
&& id(line.destinationWarehouseId) === id(eligibleLines[0]?.destinationWarehouseId))
|
||||
context.assert(MODULE, 'FBA入仓计划包含可预约明细', null,
|
||||
Boolean(planNo) && fbaLines.length > 0,
|
||||
`planNo=${planNo || 'missing'}, fbaLines=${fbaLines.length}`, OPTIONS)
|
||||
@@ -88,44 +242,10 @@ async function inboundAdminLifecycle(context) {
|
||||
&& id(supplement?.destinationWarehouseId) === id(sample.destinationWarehouseId),
|
||||
`palletId=${id(supplement?.id) || 'missing'}, status=${supplement?.assignmentStatus || 'missing'}`, OPTIONS)
|
||||
|
||||
const suffix = String(context.marker).replace(/[^A-Za-z0-9]/g, '').slice(-18).toUpperCase()
|
||||
const now = Date.now()
|
||||
const appointmentBody = {
|
||||
originWarehouseId: id(plan.overseasWarehouseId), fbaWarehouseCode: sample.fbaWarehouseCode,
|
||||
lineIds: fbaLines.map((line) => id(line.id)), idempotencyKey: `fba-${suffix}`,
|
||||
details: { plannedDate: new Date(now + 24 * 60 * 60_000).toISOString().slice(0, 10),
|
||||
appointmentStatus: 'PENDING', appointmentRemark: context.marker },
|
||||
}
|
||||
const appointment = await request(context, 'POST', '批量创建FBA预约', `${PLAN_BASE}/fba-appointments/batch`, appointmentBody)
|
||||
const appointmentNo = appointment.appointmentNo
|
||||
const expectedSources = new Set(fbaLines.map(line => id(line.orderId))).size
|
||||
const appointmentReplay = await request(context, 'POST', 'FBA预约同单号同载荷重放', `${PLAN_BASE}/fba-appointments/batch`, appointmentBody)
|
||||
context.assert(MODULE, 'FBA预约重放返回同一聚合且明细一致', null,
|
||||
id(appointmentReplay?.id) === id(appointment?.id)
|
||||
&& appointmentReplay?.items?.length === expectedSources,
|
||||
`appointmentId=${id(appointment?.id) || 'missing'}, items=${appointmentReplay?.items?.length ?? 'missing'}`, OPTIONS)
|
||||
const appointmentConflict = await context.request('POST', `${PLAN_BASE}/fba-appointments/batch`, {
|
||||
...appointmentBody, details: { ...appointmentBody.details, appointmentRemark: `${context.marker}-different` },
|
||||
})
|
||||
context.addStep(MODULE, 'FBA预约同单号异载荷返回A0409', appointmentConflict,
|
||||
conflict(appointmentConflict), trace(appointmentConflict), OPTIONS)
|
||||
const detail = await request(context, 'GET', '查询FBA预约详情',
|
||||
`${PLAN_BASE}/fba-appointments/${encodeURIComponent(appointmentNo)}`)
|
||||
context.assert(MODULE, 'FBA预约详情冻结仓码和所选明细', null,
|
||||
detail?.appointmentNo === appointmentNo && detail?.fbaWarehouseCode === sample.fbaWarehouseCode
|
||||
&& detail?.items?.length === expectedSources,
|
||||
`appointmentNo=${detail?.appointmentNo || 'missing'}, warehouse=${detail?.fbaWarehouseCode || 'missing'}`, OPTIONS)
|
||||
const cancelled = await request(context, 'PUT', '取消本轮FBA预约收口',
|
||||
`${PLAN_BASE}/fba-appointments/${encodeURIComponent(appointmentNo)}`, {
|
||||
idempotencyKey: `fba-cancel-${suffix}`, version: detail.version,
|
||||
details: { appointmentStatus: 'CANCELLED', appointmentRemark: `${context.marker}-cancelled` },
|
||||
})
|
||||
context.assert(MODULE, 'FBA预约最终为CANCELLED', null,
|
||||
cancelled?.appointmentStatus === 'CANCELLED', `status=${cancelled?.appointmentStatus || 'missing'}`, OPTIONS)
|
||||
const appointmentProof = await prepareAppointmentFixture(context, { customerId, plan, fbaLines })
|
||||
context.createdResources.phase4OverseasInboundAdmin = {
|
||||
marker: context.marker, customerId, orderId: id(order.id), planNo,
|
||||
supplementedPalletId: id(supplement?.id) || null, appointmentNo,
|
||||
terminalStatus: cancelled?.appointmentStatus || null,
|
||||
supplementedPalletId: id(supplement?.id) || null, ...appointmentProof,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user