优化
This commit is contained in:
@@ -113,6 +113,99 @@ async function ensureReleasedOceanBooking(context, container) {
|
||||
return true
|
||||
}
|
||||
|
||||
async function uploadVersion(context, path, label, expectedVersion, operation, content) {
|
||||
const response = await upload(context, path, {
|
||||
expectedVersion, operationReason: `${context.marker}-${operation}`,
|
||||
}, content, `${context.marker}-${operation}.txt`, context.token, key(context, operation))
|
||||
const document = context.apiSuccess(MODULE, label, response, '', OPTIONS)
|
||||
context.assert(MODULE, `${label}返回当前唯一文件事实`, response,
|
||||
Boolean(document?.id) && Boolean(document?.fileId) && Number.isInteger(Number(document?.version))
|
||||
&& /^[0-9a-f]{64}$/i.test(String(document?.fileHash || '')),
|
||||
`documentId=${id(document?.id) || 'missing'}, fileId=${id(document?.fileId) || 'missing'}, version=${document?.version ?? 'missing'}`, OPTIONS)
|
||||
return { response, document }
|
||||
}
|
||||
|
||||
async function verifyFourContainerDocuments(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 overviewResponse = await context.request('GET', `/api/platform/v1/containers/${containerBookingId}/overview`)
|
||||
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))
|
||||
&& 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()) {
|
||||
const response = await download(context, photo.downloadPath, context.token)
|
||||
context.assert(MODULE, `下载整柜PDA照片证据${index + 1}`, response,
|
||||
response.http === 200 && response.bytes.length > 0 && !response.contentType.includes('application/json'),
|
||||
`fileId=${id(photo?.fileId) || 'missing'}, bytes=${response.bytes.length}`, OPTIONS)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export async function run(context) {
|
||||
const { fixture, container } = selectCustomsFlow(context.createdResources)
|
||||
const tenantId = id(fixture?.tenantId)
|
||||
@@ -120,7 +213,8 @@ export async function run(context) {
|
||||
const inboundId = id(fixture?.inboundId)
|
||||
const containerBookingId = id(container?.containerBookingId)
|
||||
const loadingPlanId = id(container?.loadingPlanId)
|
||||
if (!tenantId || !parentOrderId || !inboundId || !containerBookingId
|
||||
const warehouseId = id(container?.warehouseId || fixture?.warehouseId)
|
||||
if (!tenantId || !parentOrderId || !inboundId || !containerBookingId || !loadingPlanId || !warehouseId
|
||||
|| !['AWAITING_REVIEW', 'LOADED'].includes(container?.finalStatus)
|
||||
|| (container?.finalStatus === 'AWAITING_REVIEW'
|
||||
&& (container?.loadingPlanStatus !== 'LOADING' || !container?.canonicalPdaTaskId))) {
|
||||
@@ -380,6 +474,12 @@ 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,
|
||||
`containerBookingId=${containerBookingId}, loadingPlanId=${loadingPlanId}, warehouseId=${warehouseId}`, OPTIONS)
|
||||
return
|
||||
}
|
||||
|
||||
context.createdResources.phase3Customs = {
|
||||
marker: context.marker, tenantId, parentOrderId, orderId: actualOrderId, inboundId, containerBookingId, loadingPlanId,
|
||||
customsCaseId: caseId, submissionBatchId: batchId, releasedReceiptId: releasedId,
|
||||
|
||||
Reference in New Issue
Block a user