fix(lastleg): complete generated pallet receiving flow

This commit is contained in:
2026-09-09 02:54:09 +08:00
parent 86646ccd69
commit c5862db98d
20 changed files with 367 additions and 225 deletions
@@ -125,76 +125,46 @@ async function uploadVersion(context, path, label, expectedVersion, operation, c
return { response, document }
}
async function verifyFourContainerDocuments(context, { containerBookingId, loadingPlanId, warehouseId }) {
async function verifyDomesticCustomsEvidence(context, { containerBookingId, loadingPlanId, warehouseId }) {
const reason = `${context.marker}-人工验收节点资料`
const simpleDocuments = [
{ type: 'DESTINATION_CLEARANCE', base: `/api/platform/v1/containers/${containerBookingId}/clearance`,
uploadPath: `/api/platform/v1/containers/${containerBookingId}/clearance/document/upload` },
{ type: 'CUSTOMS_DECLARATION', base: `/api/platform/v1/first-leg/loading-lists/${loadingPlanId}/customs`,
query: `warehouseId=${encodeURIComponent(warehouseId)}`,
uploadPath: `/api/platform/v1/first-leg/loading-lists/${loadingPlanId}/customs/document/upload?warehouseId=${encodeURIComponent(warehouseId)}` },
]
const completed = []
for (const document of simpleDocuments) {
const first = await uploadVersion(context, document.uploadPath, `首次上传${document.type}`, 0,
`${document.type.toLowerCase()}-v1`, `${context.marker} ${document.type} v1`)
if (!first.document?.fileId) return false
const readPath = `${document.base}${document.query ? `?${document.query}` : ''}`
const firstRead = await context.request('GET', readPath)
const firstEcho = context.apiSuccess(MODULE, `回显${document.type}首版`, firstRead, '', OPTIONS)
context.assert(MODULE, `${document.type}首版回显身份一致`, firstRead,
id(firstEcho?.id) === id(first.document.id) && id(firstEcho?.fileId) === id(first.document.fileId),
`documentId=${id(firstEcho?.id) || 'missing'}, version=${firstEcho?.version ?? 'missing'}`, OPTIONS)
const firstDownloadPath = document.type === 'DESTINATION_CLEARANCE'
? `${document.base}/document/${id(first.document.fileId)}/download`
: `${document.base}/document/${id(first.document.fileId)}/download?${document.query}`
const firstDownload = await download(context, firstDownloadPath, context.token)
context.assert(MODULE, `下载${document.type}首版`, firstDownload,
firstDownload.http === 200 && firstDownload.bytes.length > 0 && firstDownload.contentType.includes('text/plain'),
`HTTP=${firstDownload.http}, bytes=${firstDownload.bytes.length}`, OPTIONS)
const second = await uploadVersion(context, document.uploadPath, `更新${document.type}为第二版`,
Number(firstEcho?.version), `${document.type.toLowerCase()}-v2`, `${context.marker} ${document.type} v2`)
context.assert(MODULE, `${document.type}更新保持同一聚合并递增版本`, second.response,
id(second.document?.id) === id(first.document.id) && Number(second.document?.version) === Number(firstEcho?.version) + 1,
`documentId=${id(second.document?.id) || 'missing'}, version=${second.document?.version ?? 'missing'}`, OPTIONS)
const completeResponse = await request(context, 'POST', `${document.base}/complete${document.query ? `?${document.query}` : ''}`, {
expectedVersion: Number(second.document?.version), operationReason: reason,
}, context.token, { 'Idempotency-Key': key(context, `${document.type.toLowerCase()}-complete`) })
const complete = context.apiSuccess(MODULE, `完成${document.type}`, completeResponse, '', OPTIONS)
context.assert(MODULE, `${document.type}完成态、时间和版本可回读`, completeResponse,
id(complete?.id) === id(first.document.id) && Boolean(complete?.completedAt)
&& Number(complete?.version) === Number(second.document?.version) + 1,
`completedAt=${complete?.completedAt || 'missing'}, version=${complete?.version ?? 'missing'}`, OPTIONS)
completed.push(complete)
}
for (const type of ['DELIVERY_ORDER', 'PICKUP_ORDER']) {
const base = `/api/platform/v1/containers/${containerBookingId}/documents/${type}`
const first = await uploadVersion(context, `${base}/upload`, `首次上传${type}`, 0,
`${type.toLowerCase()}-v1`, `${context.marker} ${type} v1`)
if (!first.document?.fileId) return false
const firstDownload = await download(context, `${base}/${id(first.document.fileId)}/download`, context.token)
context.assert(MODULE, `下载${type}首版`, firstDownload,
firstDownload.http === 200 && firstDownload.bytes.length > 0 && firstDownload.contentType.includes('text/plain'),
`HTTP=${firstDownload.http}, bytes=${firstDownload.bytes.length}`, OPTIONS)
const second = await uploadVersion(context, `${base}/upload`, `更新${type}为第二版`,
Number(first.document.version), `${type.toLowerCase()}-v2`, `${context.marker} ${type} v2`)
const echoResponse = await context.request('GET', base)
const echo = context.apiSuccess(MODULE, `回显${type}当前版本`, echoResponse, '', OPTIONS)
context.assert(MODULE, `${type}首次与更新保持同一聚合且当前版本唯一`, echoResponse,
id(echo?.id) === id(first.document.id) && id(echo?.fileId) === id(second.document?.fileId)
&& Number(echo?.version) === Number(first.document.version) + 1,
`documentId=${id(echo?.id) || 'missing'}, version=${echo?.version ?? 'missing'}`, OPTIONS)
}
const type = 'CUSTOMS_DECLARATION'
const query = `warehouseId=${encodeURIComponent(warehouseId)}`
const base = `/api/platform/v1/first-leg/loading-lists/${loadingPlanId}/customs`
const uploadPath = `${base}/document/upload?${query}`
const first = await uploadVersion(context, uploadPath, `首次上传${type}`, 0,
`${type.toLowerCase()}-v1`, `${context.marker} ${type} v1`)
if (!first.document?.fileId) return false
const firstRead = await context.request('GET', `${base}?${query}`)
const firstEcho = context.apiSuccess(MODULE, `回显${type}首版`, firstRead, '', OPTIONS)
context.assert(MODULE, `${type}首版回显身份一致`, firstRead,
id(firstEcho?.id) === id(first.document.id) && id(firstEcho?.fileId) === id(first.document.fileId),
`documentId=${id(firstEcho?.id) || 'missing'}, version=${firstEcho?.version ?? 'missing'}`, OPTIONS)
const firstDownload = await download(context,
`${base}/document/${id(first.document.fileId)}/download?${query}`, context.token)
context.assert(MODULE, `下载${type}首版`, firstDownload,
firstDownload.http === 200 && firstDownload.bytes.length > 0 && firstDownload.contentType.includes('text/plain'),
`HTTP=${firstDownload.http}, bytes=${firstDownload.bytes.length}`, OPTIONS)
const second = await uploadVersion(context, uploadPath, `更新${type}为第二版`,
Number(firstEcho?.version), `${type.toLowerCase()}-v2`, `${context.marker} ${type} v2`)
context.assert(MODULE, `${type}更新保持同一聚合并递增版本`, second.response,
id(second.document?.id) === id(first.document.id) && Number(second.document?.version) === Number(firstEcho?.version) + 1,
`documentId=${id(second.document?.id) || 'missing'}, version=${second.document?.version ?? 'missing'}`, OPTIONS)
const completeResponse = await request(context, 'POST', `${base}/complete?${query}`, {
expectedVersion: Number(second.document?.version), operationReason: reason,
}, context.token, { 'Idempotency-Key': key(context, `${type.toLowerCase()}-complete`) })
const complete = context.apiSuccess(MODULE, `完成${type}`, completeResponse, '', OPTIONS)
context.assert(MODULE, `${type}完成态、时间和版本可回读`, completeResponse,
id(complete?.id) === id(first.document.id) && Boolean(complete?.completedAt)
&& Number(complete?.version) === Number(second.document?.version) + 1,
`completedAt=${complete?.completedAt || 'missing'}, version=${complete?.version ?? 'missing'}`, OPTIONS)
const overviewResponse = await context.request('GET', `/api/platform/v1/containers/${containerBookingId}/overview`)
const overview = context.apiSuccess(MODULE, '四类资料完成后回查整柜聚合', overviewResponse, '', OPTIONS)
const overview = context.apiSuccess(MODULE, '国内报关完成后回查整柜聚合', overviewResponse, '', OPTIONS)
const documents = Array.isArray(overview?.documents) ? overview.documents : []
const photos = Array.isArray(overview?.pda?.photos) ? overview.pda.photos.filter((item) => item?.status === 'ACTIVE') : []
context.assert(MODULE, '整柜聚合回显四类当前资料与真实PDA照片证据', overviewResponse,
['DESTINATION_CLEARANCE', 'CUSTOMS_DECLARATION', 'DELIVERY_ORDER', 'PICKUP_ORDER']
.every((type) => documents.some((item) => item?.documentType === type))
&& completed.every((item) => documents.some((value) => id(value?.id) === id(item?.id) && value?.completed === true))
context.assert(MODULE, '整柜聚合回显国内报关资料与真实PDA照片证据', overviewResponse,
documents.some((item) => item?.documentType === type
&& id(item?.id) === id(complete?.id) && item?.completed === true)
&& photos.length >= 5 && photos.every((item) => /^\/api\/platform\/v1\/containers\/\d+\/pda\/photos\/\d+\/download$/.test(String(item?.downloadPath || ''))),
`documents=${documents.map((item) => item?.documentType).join('|') || 'none'}, activePhotos=${photos.length}`, OPTIONS)
for (const [index, photo] of photos.entries()) {
@@ -505,8 +475,8 @@ export async function run(context) {
context.addStep(MODULE, '回执 fileId 不能伪造为客户报关资料引用', forgedReference, failure(forgedReference),
`HTTP=${forgedReference.http}, code=${codeOf(forgedReference)}`, OPTIONS)
if (!(await verifyFourContainerDocuments(context, { containerBookingId, loadingPlanId, warehouseId }))) {
context.addStep(MODULE, '四类整柜资料必须完成后才允许进入出仓确认', null, false,
if (!(await verifyDomesticCustomsEvidence(context, { containerBookingId, loadingPlanId, warehouseId }))) {
context.addStep(MODULE, '国内报关资料与PDA证据必须完成后才允许进入出仓确认', null, false,
`containerBookingId=${containerBookingId}, loadingPlanId=${loadingPlanId}, warehouseId=${warehouseId}`, OPTIONS)
return
}