test(fleet): exercise simplified intake HTTP workflow
This commit is contained in:
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
`fleet-capacity-pool-gray.mjs` 是明确 GRAY 的拼车专项入口,复用既有池全流程矩阵与两客户实货前置,不执行旧 FBA 下单、报关或装柜链。默认 `--plan` 零网络、零文件写入;执行必须传 `--execute --environment GRAY --run-id <新批次> --warehouse-id <实际海外仓ID> --destination-warehouse-id <FBA目的仓ID>`。命令前使用 `node --env-file=<独立jenkins.env> --env-file=<明确gray-acceptance.env>`,要求 `CHONGCHONG_ENV=prod` 或 `gray`、`CHONGCHONG_ADMIN_API_BASE_URL`(统一 HTTPS 源站加 `/api`)、`CHONGCHONG_ADMIN_USERNAME/PASSWORD` 和 `JENKINS_URL/USER/API_TOKEN`;不改私有配置、不冒充 TEST、不使用 TEST agent。库存证据由 `createGrayEvidenceReaders` 校验生产容器、profile及数据库身份后只读取得。报告独占 `reports/fleet-pool-gray-<新批次>/`,沿共享 `.fleet-acceptance.lock` 串行执行;每次写入先持久记录原键与意图,结果未知停写,失败保留样本,不自动续跑或覆盖。结束停用本轮临时销售/调度/PDA账号、退出会话;合成客户、司机/车辆、报价、文件和业务样本留存,费用仍由唯一真实派送来源产生,不发起支付。Admin 通用 GRAY CLI 的 F40 白名单不变,专用 transport factory 只能创建空模块且无故障注入的灰度上下文。直接合同 `fleet-capacity-pool-gray.test.mjs` 验证环境隔离、品牌门禁和复用范围;原 TEST CLI 仍拒绝 GRAY。本入口不部署、不写业务 SQL,不将专项通过冒充全部页面验收。
|
`fleet-capacity-pool-gray.mjs` 是明确 GRAY 的拼车专项入口,复用既有池全流程矩阵与两客户实货前置,不执行旧 FBA 下单、报关或装柜链。默认 `--plan` 零网络、零文件写入;执行必须传 `--execute --environment GRAY --run-id <新批次> --warehouse-id <实际海外仓ID> --destination-warehouse-id <FBA目的仓ID>`。命令前使用 `node --env-file=<独立jenkins.env> --env-file=<明确gray-acceptance.env>`,要求 `CHONGCHONG_ENV=prod` 或 `gray`、`CHONGCHONG_ADMIN_API_BASE_URL`(统一 HTTPS 源站加 `/api`)、`CHONGCHONG_ADMIN_USERNAME/PASSWORD` 和 `JENKINS_URL/USER/API_TOKEN`;不改私有配置、不冒充 TEST、不使用 TEST agent。库存证据由 `createGrayEvidenceReaders` 校验生产容器、profile及数据库身份后只读取得。报告独占 `reports/fleet-pool-gray-<新批次>/`,沿共享 `.fleet-acceptance.lock` 串行执行;每次写入先持久记录原键与意图,结果未知停写,失败保留样本,不自动续跑或覆盖。结束停用本轮临时销售/调度/PDA账号、退出会话;合成客户、司机/车辆、报价、文件和业务样本留存,费用仍由唯一真实派送来源产生,不发起支付。Admin 通用 GRAY CLI 的 F40 白名单不变,专用 transport factory 只能创建空模块且无故障注入的灰度上下文。直接合同 `fleet-capacity-pool-gray.test.mjs` 验证环境隔离、品牌门禁和复用范围;原 TEST CLI 仍拒绝 GRAY。本入口不部署、不写业务 SQL,不将专项通过冒充全部页面验收。
|
||||||
|
|
||||||
拼车主入口 `fleet-capacity-pool-test-env.mjs` 除两笔真实履约订单外,还各创建一笔本土散板/卡车的“已确认、未登记来货”取消样本,核对商业单与预订同步取消、三维容量恢复和原键重放;增加来货后取消/实收超量拒绝、绑定后占位转计划不双扣、事实失效须重新核对并显式恢复,以及跨销售写入/跨客户商业单拒绝。拒绝动作须回查原事实与库存不变,不能仅按错误码判定通过;不注入业务数据库故障。
|
拼车主入口 `fleet-capacity-pool-test-env.mjs` 除两笔真实履约订单外,还各创建一笔本土散板/卡车的“已确认、未登记来货”取消样本,核对商业单与预订同步取消、三维容量恢复和原键重放;增加来货后取消/实收超量拒绝、绑定后占位转计划不双扣、事实失效须重新核对并显式恢复,以及跨销售写入/跨客户商业单拒绝。正常链通过拼车专属 `save-and-confirm`、`fulfillment`、`incoming`、`receipt-confirmation` 和 `arrange-dispatch` 接口验证简化操作及原命令重放,不再以脚本手工建排车再绑定代替自动关联;另建精确履约权限账号,验证仅有拼车管理权限不能安排来货、确认实收或生成排车,销售仅能读取本人履约。拒绝动作须回查原事实与库存不变,不能仅按错误码判定通过;不注入业务数据库故障。
|
||||||
|
|
||||||
`fleet-capacity-pool-ui-fixture-test-env.mjs` 仅准备内部拼车池TEST页面验收数据,默认 `--plan` 零网络;实际命令沿 `--execute --environment TEST --run-id <新批次> --warehouse-id <实际海外仓ID> --destination-warehouse-id <目的仓ID>`,另必须显式传 `--private-credentials-file <用户.codex/env/chongchong-global目录直接子级的独占JSON绝对路径>`。通过明确TEST环境文件读取CHONGCHONG_ADMIN及Jenkins只读取证配置,创建两客户真实来货和计划车次、OPEN发布、两销售各12条不占位草稿及最小调度账号;停在可交互状态,不确认承运、不拣货发车、不声称全量验收。密码只在首次写入前独占创建的私有文件中保存,报告 `reports/fleet-capacity-pool-ui-<新批次>/` 不含密码/Token;失败保留现场和原请求,不自动重跑。退出脚本会话并停用自有fixture PDA,保留UI账号供浏览器验收;不部署、不写业务SQL、不删除业务样本。离线安全与作用域合同为 `fleet-capacity-pool-ui-fixture-test-env.test.mjs`。
|
`fleet-capacity-pool-ui-fixture-test-env.mjs` 仅准备内部拼车池TEST页面验收数据,默认 `--plan` 零网络;实际命令沿 `--execute --environment TEST --run-id <新批次> --warehouse-id <实际海外仓ID> --destination-warehouse-id <目的仓ID>`,另必须显式传 `--private-credentials-file <用户.codex/env/chongchong-global目录直接子级的独占JSON绝对路径>`。通过明确TEST环境文件读取CHONGCHONG_ADMIN及Jenkins只读取证配置,创建两客户真实来货和计划车次、OPEN发布、两销售各12条不占位草稿及最小调度账号;停在可交互状态,不确认承运、不拣货发车、不声称全量验收。密码只在首次写入前独占创建的私有文件中保存,报告 `reports/fleet-capacity-pool-ui-<新批次>/` 不含密码/Token;失败保留现场和原请求,不自动重跑。退出脚本会话并停用自有fixture PDA,保留UI账号供浏览器验收;不部署、不写业务SQL、不删除业务样本。离线安全与作用域合同为 `fleet-capacity-pool-ui-fixture-test-env.test.mjs`。
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ export const POOL_COVERAGE = Object.freeze([
|
|||||||
'bound-cargo-reservation-removed-without-double-count', 'other-sales-write-and-sensitive-trip-denied',
|
'bound-cargo-reservation-removed-without-double-count', 'other-sales-write-and-sensitive-trip-denied',
|
||||||
'other-customer-commercial-read-denied-with-source-unchanged',
|
'other-customer-commercial-read-denied-with-source-unchanged',
|
||||||
'pool-unspecified-container-and-palletizing-remain-unknown',
|
'pool-unspecified-container-and-palletizing-remain-unknown',
|
||||||
|
'save-and-confirm-original-command-replay', 'pool-fulfillment-owner-read-isolation',
|
||||||
|
'pool-fulfillment-explicit-write-permissions', 'pool-arrange-dispatch-without-manual-number',
|
||||||
])
|
])
|
||||||
export const POOL_REMAINING = Object.freeze([
|
export const POOL_REMAINING = Object.freeze([
|
||||||
'out-of-band-assigned-vehicle-drift-not-constructible-through-supported-api',
|
'out-of-band-assigned-vehicle-drift-not-constructible-through-supported-api',
|
||||||
@@ -311,6 +313,9 @@ async function poolScenario(context, fixture, { execute = false, readInventory,
|
|||||||
const sales = [], dispatchers = []
|
const sales = [], dispatchers = []
|
||||||
for (let n = 0; n < 2; n++) sales.push(await createActor('sales', n, ['list', 'inquiry-create', 'inquiry-edit', 'inquiry-cancel'].map(s => PREFIX + s)))
|
for (let n = 0; n < 2; n++) sales.push(await createActor('sales', n, ['list', 'inquiry-create', 'inquiry-edit', 'inquiry-cancel'].map(s => PREFIX + s)))
|
||||||
for (let n = 0; n < 2; n++) dispatchers.push(await createActor('dispatch', n, [PREFIX + 'manage']))
|
for (let n = 0; n < 2; n++) dispatchers.push(await createActor('dispatch', n, [PREFIX + 'manage']))
|
||||||
|
const fulfillmentToken = await createActor('fulfillment', 0, [PREFIX + 'manage',
|
||||||
|
'platform:fleet:trip:booking-edit', 'platform:fleet:trip:booking-confirm', 'platform:fleet:trip:edit',
|
||||||
|
'platform:overseas-warehouse:inbound-order:smart-dispatch'])
|
||||||
for (const member of fixture.members) {
|
for (const member of fixture.members) {
|
||||||
const credential = credentials?.get(member.customerId)
|
const credential = credentials?.get(member.customerId)
|
||||||
check('Original fixture customer credentials available only in memory', Boolean(credential))
|
check('Original fixture customer credentials available only in memory', Boolean(credential))
|
||||||
@@ -347,6 +352,11 @@ async function poolScenario(context, fixture, { execute = false, readInventory,
|
|||||||
proof.covered.push('unknown-load-stays-null')
|
proof.covered.push('unknown-load-stays-null')
|
||||||
const first = await createInquiry(0, 'LOCAL_LOOSEBOARD', 8, 'LOOSE')
|
const first = await createInquiry(0, 'LOCAL_LOOSEBOARD', 8, 'LOOSE')
|
||||||
await reject('Other sales cannot read this inquiry', 'GET', `${POOL}/inquiries/${first.bookingNo}`, undefined, sales[1])
|
await reject('Other sales cannot read this inquiry', 'GET', `${POOL}/inquiries/${first.bookingNo}`, undefined, sales[1])
|
||||||
|
const ownFulfillment = await request('GET', `${POOL}/inquiries/${first.bookingNo}/fulfillment`, undefined, sales[0])
|
||||||
|
check('Owner reads only the selected booking without trip-list permission', ownFulfillment.booking.bookingNo === first.bookingNo
|
||||||
|
&& ownFulfillment.booking.customerId === first.customerId && !ownFulfillment.dispatches && !ownFulfillment.bookings)
|
||||||
|
await reject('Other sales cannot read dedicated fulfillment', 'GET', `${POOL}/inquiries/${first.bookingNo}/fulfillment`, undefined, sales[1])
|
||||||
|
proof.covered.push('pool-fulfillment-owner-read-isolation')
|
||||||
const second = await createInquiry(1, 'US_TRUCK', 4, 'TRUCK-A')
|
const second = await createInquiry(1, 'US_TRUCK', 4, 'TRUCK-A')
|
||||||
check('Same display name does not collapse distinct sales identities', first.salesOwnerUserId && second.salesOwnerUserId && first.salesOwnerUserId !== second.salesOwnerUserId)
|
check('Same display name does not collapse distinct sales identities', first.salesOwnerUserId && second.salesOwnerUserId && first.salesOwnerUserId !== second.salesOwnerUserId)
|
||||||
proof.covered.push('two-sales-owner-isolation')
|
proof.covered.push('two-sales-owner-isolation')
|
||||||
@@ -393,9 +403,13 @@ async function poolScenario(context, fixture, { execute = false, readInventory,
|
|||||||
command, businessPath, dispatcherToken: dispatchers[index], label: `CANCEL-${type}` }))
|
command, businessPath, dispatcherToken: dispatchers[index], label: `CANCEL-${type}` }))
|
||||||
}
|
}
|
||||||
proof.covered.push('confirmed-cancel-before-incoming-releases-capacity-and-replays')
|
proof.covered.push('confirmed-cancel-before-incoming-releases-capacity-and-replays')
|
||||||
let current = await latest(first.bookingNo), firstCommand = command(current, 'CONFIRM-LOOSE')
|
let current = await latest(first.bookingNo), firstCommand = poolInquiryWrite(current, key('SAVE-CONFIRM-LOOSE'))
|
||||||
await reject('Sales cannot confirm capacity', 'POST', `${POOL}/inquiries/${first.bookingNo}/confirm`, firstCommand, sales[0])
|
await reject('Sales cannot save and confirm capacity', 'POST', `${POOL}/inquiries/${first.bookingNo}/save-and-confirm`, firstCommand, sales[0])
|
||||||
const firstConfirmed = await request('POST', `${POOL}/inquiries/${first.bookingNo}/confirm`, firstCommand, dispatchers[0])
|
const firstConfirmed = await request('POST', `${POOL}/inquiries/${first.bookingNo}/save-and-confirm`, firstCommand, dispatchers[0])
|
||||||
|
const saveReplay = await request('POST', `${POOL}/inquiries/${first.bookingNo}/save-and-confirm`, firstCommand, dispatchers[0])
|
||||||
|
assertSingleCommercial(firstConfirmed.inquiry, saveReplay.inquiry)
|
||||||
|
check('Save and confirm original request replays one commercial commitment', saveReplay.replayed === true)
|
||||||
|
proof.covered.push('save-and-confirm-original-command-replay')
|
||||||
assertPoolRemaining(firstConfirmed.offer, destinationKey, 4)
|
assertPoolRemaining(firstConfirmed.offer, destinationKey, 4)
|
||||||
proof.covered.push('same-segment-plan-18-of-30-reserve-8-leaves-4')
|
proof.covered.push('same-segment-plan-18-of-30-reserve-8-leaves-4')
|
||||||
const oversized = await createInquiry(1, 'US_TRUCK', 5, 'OVER-CAPACITY')
|
const oversized = await createInquiry(1, 'US_TRUCK', 5, 'OVER-CAPACITY')
|
||||||
@@ -426,7 +440,7 @@ async function poolScenario(context, fixture, { execute = false, readInventory,
|
|||||||
&& foreignRead.requestId && foreignRead.traceId, foreignRead)
|
&& foreignRead.requestId && foreignRead.traceId, foreignRead)
|
||||||
check('Denied old status and foreign read preserve commercial source', JSON.stringify(source) === JSON.stringify(await request('GET', businessPath(order))))
|
check('Denied old status and foreign read preserve commercial source', JSON.stringify(source) === JSON.stringify(await request('GET', businessPath(order))))
|
||||||
poolMembers.push(await receiveAndBindPoolOrder(context, fixture, order, index, { request, reject, check, key, readInventory,
|
poolMembers.push(await receiveAndBindPoolOrder(context, fixture, order, index, { request, reject, check, key, readInventory,
|
||||||
latest, command, businessPath, offerDetail: () => offerDetail(dispatchers[0]), dispatcherToken: dispatchers[0], destinationKey, proof }))
|
latest, command, businessPath, offerDetail: () => offerDetail(dispatchers[0]), dispatcherToken: dispatchers[0], fulfillmentToken, destinationKey, proof }))
|
||||||
}
|
}
|
||||||
proof.covered.push('old-manual-order-endpoints-rejected', 'one-commercial-order-and-one-incoming', 'both-business-types-real-pda-receipt',
|
proof.covered.push('old-manual-order-endpoints-rejected', 'one-commercial-order-and-one-incoming', 'both-business-types-real-pda-receipt',
|
||||||
'other-customer-commercial-read-denied-with-source-unchanged', 'incoming-cancel-rejected-with-source-unchanged',
|
'other-customer-commercial-read-denied-with-source-unchanged', 'incoming-cancel-rejected-with-source-unchanged',
|
||||||
@@ -436,7 +450,7 @@ async function poolScenario(context, fixture, { execute = false, readInventory,
|
|||||||
const closed = await request('POST', `${POOL}/${proof.offerNo}/close`, { tripRecordVersion: offer.tripRecordVersion,
|
const closed = await request('POST', `${POOL}/${proof.offerNo}/close`, { tripRecordVersion: offer.tripRecordVersion,
|
||||||
offerRecordVersion: offer.recordVersion, idempotencyKey: key('CLOSE'), reason: context.marker }, dispatchers[0])
|
offerRecordVersion: offer.recordVersion, idempotencyKey: key('CLOSE'), reason: context.marker }, dispatchers[0])
|
||||||
check('Offer closed without cancelling commitments', closed.offer.status === 'CLOSED')
|
check('Offer closed without cancelling commitments', closed.offer.status === 'CLOSED')
|
||||||
const replay = await request('POST', `${POOL}/inquiries/${first.bookingNo}/confirm`, firstCommand, dispatchers[0])
|
const replay = await request('POST', `${POOL}/inquiries/${first.bookingNo}/save-and-confirm`, firstCommand, dispatchers[0])
|
||||||
assertSingleCommercial(firstConfirmed.inquiry, replay.inquiry)
|
assertSingleCommercial(firstConfirmed.inquiry, replay.inquiry)
|
||||||
check('Successful confirmation still replays after close', replay.replayed === true)
|
check('Successful confirmation still replays after close', replay.replayed === true)
|
||||||
for (const row of beforeClose) check('Old confirmation replay cannot revert bound source', inquiryFact(row) === inquiryFact(await latest(row.bookingNo)))
|
for (const row of beforeClose) check('Old confirmation replay cannot revert bound source', inquiryFact(row) === inquiryFact(await latest(row.bookingNo)))
|
||||||
@@ -502,14 +516,16 @@ async function poolScenario(context, fixture, { execute = false, readInventory,
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function receiveAndBindPoolOrder(context, fixture, order, index, { request, reject, check, key, readInventory,
|
async function receiveAndBindPoolOrder(context, fixture, order, index, { request, reject, check, key, readInventory,
|
||||||
latest, command, businessPath, offerDetail, dispatcherToken, destinationKey, proof }) {
|
latest, command, businessPath, offerDetail, dispatcherToken, fulfillmentToken, destinationKey, proof }) {
|
||||||
const tripPath = `${API}/fleet/trips/${encode(fixture.tripNo)}`, bookingPath = `${tripPath}/bookings/${encode(order.bookingNo)}`
|
const tripPath = `${API}/fleet/trips/${encode(fixture.tripNo)}`, bookingPath = `${tripPath}/bookings/${encode(order.bookingNo)}`
|
||||||
|
const poolPath = `${POOL}/inquiries/${encode(order.bookingNo)}`
|
||||||
let bookingList = await request('GET', `${tripPath}/bookings`), booking = bookingList.records.find(row => row.bookingNo === order.bookingNo)
|
let bookingList = await request('GET', `${tripPath}/bookings`), booking = bookingList.records.find(row => row.bookingNo === order.bookingNo)
|
||||||
check('Pool commercial association survived into existing booking workflow', booking?.status === 'CONFIRMED')
|
check('Pool commercial association survived into existing booking workflow', booking?.status === 'CONFIRMED')
|
||||||
const body = { tripRecordVersion: bookingList.tripRecordVersion, recordVersion: booking.recordVersion, estimatedArrivalAt: new Date(Date.now() + 300000).toISOString(),
|
const body = { tripRecordVersion: bookingList.tripRecordVersion, recordVersion: booking.recordVersion, estimatedArrivalAt: new Date(Date.now() + 300000).toISOString(),
|
||||||
deliveryInstructions: `${context.marker} TEST客户自送`, idempotencyKey: key(`INCOMING-${index}`) }
|
deliveryInstructions: `${context.marker} TEST客户自送`, idempotencyKey: key(`INCOMING-${index}`) }
|
||||||
const created = await request('POST', `${bookingPath}/incoming`, body), incomingNo = created.booking.incomingNo
|
await reject('Pool management alone cannot arrange incoming', 'POST', `${poolPath}/incoming`, body, dispatcherToken)
|
||||||
const repeated = await request('POST', `${bookingPath}/incoming`, body)
|
const created = await request('POST', `${poolPath}/incoming`, body, fulfillmentToken), incomingNo = created.booking.incomingNo
|
||||||
|
const repeated = await request('POST', `${poolPath}/incoming`, body, fulfillmentToken)
|
||||||
check('One incoming document on exact replay', incomingNo && repeated.replayed === true && repeated.booking.incomingNo === incomingNo)
|
check('One incoming document on exact replay', incomingNo && repeated.replayed === true && repeated.booking.incomingNo === incomingNo)
|
||||||
const incomingBeforeCancel = await request('GET', `${bookingPath}/incoming`), sourceBeforeCancel = await request('GET', businessPath(order))
|
const incomingBeforeCancel = await request('GET', `${bookingPath}/incoming`), sourceBeforeCancel = await request('GET', businessPath(order))
|
||||||
const inquiryBeforeCancel = await latest(order.bookingNo), capacityBeforeCancel = (await offerDetail()).offer
|
const inquiryBeforeCancel = await latest(order.bookingNo), capacityBeforeCancel = (await offerDetail()).offer
|
||||||
@@ -532,14 +548,14 @@ async function receiveAndBindPoolOrder(context, fixture, order, index, { request
|
|||||||
check('Real TEST measurement file exists', success(uploaded) && uploaded.body.data?.fileId)
|
check('Real TEST measurement file exists', success(uploaded) && uploaded.body.data?.fileId)
|
||||||
const received = await request('POST', `${pdaPath}/palletize`, { idempotencyKey: key(`PALLETIZE-${index}`), photoFileIds: [id(uploaded.body.data.fileId)] }, fixture.pdaToken)
|
const received = await request('POST', `${pdaPath}/palletize`, { idempotencyKey: key(`PALLETIZE-${index}`), photoFileIds: [id(uploaded.body.data.fileId)] }, fixture.pdaToken)
|
||||||
check('Actual PDA receipt completed one original pallet', received.status === 'WAIT_CUSTOMER_CONFIRMATION' && received.pallets.length === 1 && received.pallets[0].status === 'COMPLETED')
|
check('Actual PDA receipt completed one original pallet', received.status === 'WAIT_CUSTOMER_CONFIRMATION' && received.pallets.length === 1 && received.pallets[0].status === 'COMPLETED')
|
||||||
const evidence = await request('GET', `${bookingPath}/incoming`)
|
const evidence = (await request('GET', `${poolPath}/fulfillment`, undefined, fulfillmentToken)).preview
|
||||||
const beforeOverReceipt = await latest(order.bookingNo), sourceBeforeReceipt = await request('GET', businessPath(order))
|
const beforeOverReceipt = await latest(order.bookingNo), sourceBeforeReceipt = await request('GET', businessPath(order))
|
||||||
const capacityBeforeReceipt = (await offerDetail()).offer, tripBeforeReceipt = await request('GET', tripPath)
|
const capacityBeforeReceipt = (await offerDetail()).offer, tripBeforeReceipt = await request('GET', tripPath)
|
||||||
const inventoryBeforeReceipt = await readInventory({ marker: context.marker, customerId: order.customerId, warehouseId: fixture.warehouseId })
|
const inventoryBeforeReceipt = await readInventory({ marker: context.marker, customerId: order.customerId, warehouseId: fixture.warehouseId })
|
||||||
const overReceipt = await reject('Actual received pallet spaces cannot exceed whole vehicle capacity', 'POST', `${bookingPath}/receipt-confirmation`,
|
const overReceipt = await reject('Actual received pallet spaces cannot exceed whole vehicle capacity', 'POST', `${poolPath}/receipt-confirmation`,
|
||||||
{ tripRecordVersion: evidence.tripRecordVersion, recordVersion: evidence.recordVersion, receiptHash: evidence.incoming.receiptHash,
|
{ tripRecordVersion: evidence.tripRecordVersion, recordVersion: evidence.recordVersion, receiptHash: evidence.incoming.receiptHash,
|
||||||
pallets: [{ warehousePalletId: id(pallet.warehousePalletId), palletSpaces: Number(tripBeforeReceipt.trip.maxPalletSpaces) + 1 }],
|
pallets: [{ warehousePalletId: id(pallet.warehousePalletId), palletSpaces: Number(tripBeforeReceipt.trip.maxPalletSpaces) + 1 }],
|
||||||
unitPrice: 30, idempotencyKey: key(`OVER-RECEIPT-${index}`) })
|
unitPrice: 30, idempotencyKey: key(`OVER-RECEIPT-${index}`) }, fulfillmentToken)
|
||||||
check('Receipt overrun is refused by actual capacity calculation', /超过.*限制|容量|载重|容积/.test(id(overReceipt.body?.message)))
|
check('Receipt overrun is refused by actual capacity calculation', /超过.*限制|容量|载重|容积/.test(id(overReceipt.body?.message)))
|
||||||
check('Rejected overreceipt leaves source receipt commercial booking and reservations unchanged',
|
check('Rejected overreceipt leaves source receipt commercial booking and reservations unchanged',
|
||||||
JSON.stringify(evidence) === JSON.stringify(await request('GET', `${bookingPath}/incoming`))
|
JSON.stringify(evidence) === JSON.stringify(await request('GET', `${bookingPath}/incoming`))
|
||||||
@@ -548,20 +564,28 @@ async function receiveAndBindPoolOrder(context, fixture, order, index, { request
|
|||||||
&& capacitySnapshot(capacityBeforeReceipt) === capacitySnapshot((await offerDetail()).offer))
|
&& capacitySnapshot(capacityBeforeReceipt) === capacitySnapshot((await offerDetail()).offer))
|
||||||
check('Rejected overreceipt leaves complete customer warehouse inventory unchanged', JSON.stringify(inventoryBeforeReceipt)
|
check('Rejected overreceipt leaves complete customer warehouse inventory unchanged', JSON.stringify(inventoryBeforeReceipt)
|
||||||
=== JSON.stringify(await readInventory({ marker: context.marker, customerId: order.customerId, warehouseId: fixture.warehouseId })))
|
=== JSON.stringify(await readInventory({ marker: context.marker, customerId: order.customerId, warehouseId: fixture.warehouseId })))
|
||||||
await request('POST', `${bookingPath}/receipt-confirmation`, { tripRecordVersion: evidence.tripRecordVersion, recordVersion: evidence.recordVersion,
|
const receiptBody = { tripRecordVersion: evidence.tripRecordVersion, recordVersion: evidence.recordVersion,
|
||||||
receiptHash: evidence.incoming.receiptHash, pallets: [{ warehousePalletId: id(pallet.warehousePalletId), palletSpaces: 1 }], unitPrice: 30, idempotencyKey: key(`RECEIPT-${index}`) })
|
receiptHash: evidence.incoming.receiptHash, pallets: [{ warehousePalletId: id(pallet.warehousePalletId), palletSpaces: 1 }], unitPrice: 30, idempotencyKey: key(`RECEIPT-${index}`) }
|
||||||
|
await reject('Pool management alone cannot confirm receipt', 'POST', `${poolPath}/receipt-confirmation`, receiptBody, dispatcherToken)
|
||||||
|
await request('POST', `${poolPath}/receipt-confirmation`, receiptBody, fulfillmentToken)
|
||||||
const inventory = await readInventory({ marker: context.marker, customerId: order.customerId, warehouseId: fixture.warehouseId })
|
const inventory = await readInventory({ marker: context.marker, customerId: order.customerId, warehouseId: fixture.warehouseId })
|
||||||
check('Immutable actual incoming STOCK_IN is unique', inventory.scope?.coverage === 'CUSTOMER_WAREHOUSE_COMPLETE'
|
check('Immutable actual incoming STOCK_IN is unique', inventory.scope?.coverage === 'CUSTOMER_WAREHOUSE_COMPLETE'
|
||||||
&& inventory.ledger.filter(row => row.businessOrderNo === incomingNo && row.businessType === 'STOCK_IN' && Number(row.deltaPackages) === 1).length === 1)
|
&& inventory.ledger.filter(row => row.businessOrderNo === incomingNo && row.businessType === 'STOCK_IN' && Number(row.deltaPackages) === 1).length === 1)
|
||||||
const dispatchBase = `${API}/dispatches`, candidates = await request('GET', `${dispatchBase}/candidates?${new URLSearchParams({ sourceStage: 'RECEIVED', orderNo: incomingNo })}`)
|
const dispatchBase = `${API}/dispatches`, candidates = await request('GET', `${dispatchBase}/candidates?${new URLSearchParams({ sourceStage: 'RECEIVED', orderNo: incomingNo })}`)
|
||||||
check('Received candidate exactly belongs to this commercial incoming', candidates.length === 1 && candidates[0].inboundCode === incomingNo
|
check('Received candidate exactly belongs to this commercial incoming', candidates.length === 1 && candidates[0].inboundCode === incomingNo
|
||||||
&& id(candidates[0].customerId) === order.customerId && id(candidates[0].warehousePalletId) === id(pallet.warehousePalletId))
|
&& id(candidates[0].customerId) === order.customerId && id(candidates[0].warehousePalletId) === id(pallet.warehousePalletId))
|
||||||
const draftToken = key(`DRAFT-${index}`), container = await request('POST', `${dispatchBase}/drafts/containers`, { draftToken, capacity: 30 })
|
|
||||||
await request('POST', `${dispatchBase}/draft-locks/lock`, { planPalletId: id(candidates[0].planPalletId), draftToken, draftContainerId: id(container.id) })
|
|
||||||
const dispatch = await request('POST', `${dispatchBase}/pre-arrange`, { planPalletIds: [id(candidates[0].planPalletId)], draftToken })
|
|
||||||
bookingList = await request('GET', `${tripPath}/bookings`); booking = bookingList.records.find(row => row.bookingNo === order.bookingNo)
|
bookingList = await request('GET', `${tripPath}/bookings`); booking = bookingList.records.find(row => row.bookingNo === order.bookingNo)
|
||||||
await request('POST', `${bookingPath}/dispatch`, { tripRecordVersion: bookingList.tripRecordVersion, recordVersion: booking.recordVersion,
|
const arrangeBody = { tripRecordVersion: bookingList.tripRecordVersion, recordVersion: booking.recordVersion,
|
||||||
dispatchNo: dispatch.dispatchNo, idempotencyKey: key(`BIND-DISPATCH-${index}`) })
|
idempotencyKey: key(`ARRANGE-DISPATCH-${index}`) }
|
||||||
|
await reject('Pool management alone cannot generate and bind dispatch', 'POST', `${poolPath}/arrange-dispatch`, arrangeBody, dispatcherToken)
|
||||||
|
const arranged = await request('POST', `${poolPath}/arrange-dispatch`, arrangeBody, fulfillmentToken)
|
||||||
|
const arrangeReplay = await request('POST', `${poolPath}/arrange-dispatch`, arrangeBody, fulfillmentToken)
|
||||||
|
check('One command generates and binds the actual receipt without a manually supplied number', arranged.booking.status === 'BOUND'
|
||||||
|
&& arranged.booking.dispatchNo && arranged.booking.incomingNo === incomingNo && arranged.booking.customerId === order.customerId)
|
||||||
|
check('Arrange replay returns the original dispatch and version', arrangeReplay.replayed === true
|
||||||
|
&& arrangeReplay.booking.dispatchNo === arranged.booking.dispatchNo && arrangeReplay.tripRecordVersion === arranged.tripRecordVersion)
|
||||||
|
const dispatch = { dispatchNo: arranged.booking.dispatchNo }
|
||||||
|
if (index === 0) proof.covered.push('pool-fulfillment-explicit-write-permissions', 'pool-arrange-dispatch-without-manual-number')
|
||||||
let plan = await request('GET', `${tripPath}/capacity-plan`), detail = await request('GET', tripPath)
|
let plan = await request('GET', `${tripPath}/capacity-plan`), detail = await request('GET', tripPath)
|
||||||
const actualMeasurements = controlledPalletMeasurements(plan.cargo, detail.trip.stops)
|
const actualMeasurements = controlledPalletMeasurements(plan.cargo, detail.trip.stops)
|
||||||
const restored = await verifyPoolBindingRevalidation({ offerDetail, check,
|
const restored = await verifyPoolBindingRevalidation({ offerDetail, check,
|
||||||
|
|||||||
Reference in New Issue
Block a user