优化
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
const MODULE = 'phase3-container-documents'
|
||||
const OPTIONS = { scenario: 'phase3-container-four-documents', deploymentBlocker: true }
|
||||
const CONTAINERS = '/api/platform/v1/containers'
|
||||
const FIRST_LEG = '/api/platform/v1/first-leg'
|
||||
|
||||
export const DOCUMENT_TYPES = Object.freeze([
|
||||
'DESTINATION_CLEARANCE', 'CUSTOMS_DECLARATION', 'DELIVERY_ORDER', 'PICKUP_ORDER',
|
||||
])
|
||||
|
||||
function id(value) { return value == null ? '' : String(value) }
|
||||
function codeOf(response) { return response?.body?.code == null ? 'missing' : String(response.body.code) }
|
||||
function success(response) { return response?.http === 200 && codeOf(response) === '200' }
|
||||
function conflict(response) { return response?.http === 200 && codeOf(response) === 'A0409' }
|
||||
function traced(response) { return Boolean(response?.requestId) && Boolean(response?.traceId) }
|
||||
function key(context, label) { return `${context.marker}-four-documents-${label}`.slice(0, 128) }
|
||||
function validDocument(document, type, completed) {
|
||||
return Boolean(document?.id) && Boolean(document?.fileId) && document?.documentType === type
|
||||
&& /^[0-9a-f]{64}$/i.test(String(document?.fileHash || ''))
|
||||
&& Number.isInteger(Number(document?.version))
|
||||
&& (completed ? Boolean(document?.completedAt) : document?.completedAt == null)
|
||||
}
|
||||
|
||||
function documentForm(context, type, expectedVersion, operationReason, altered = false) {
|
||||
const form = new FormData()
|
||||
const suffix = altered ? '-altered' : ''
|
||||
form.append('file', new Blob([`${context.marker}-${type}${suffix}`], { type: 'text/plain' }),
|
||||
`${type.toLowerCase()}${suffix}.txt`)
|
||||
form.append('expectedVersion', String(expectedVersion))
|
||||
form.append('operationReason', operationReason)
|
||||
return form
|
||||
}
|
||||
|
||||
async function upload(context, path, type, expectedVersion, operationReason, idempotencyKey, altered = false) {
|
||||
return context.requestMultipart('POST', path,
|
||||
documentForm(context, type, expectedVersion, operationReason, altered), context.token,
|
||||
{ 'Idempotency-Key': idempotencyKey })
|
||||
}
|
||||
|
||||
async function uploadAndVerify(context, { type, uploadPath, readPath, completePath = null }) {
|
||||
const operationReason = `${context.marker} 四类货柜资料全链验收`
|
||||
const uploadKey = key(context, `${type}-upload`)
|
||||
const response = await upload(context, uploadPath, type, 0, operationReason, uploadKey)
|
||||
let document = context.apiSuccess(MODULE, `上传${type}`, response, `documentType=${type}`, OPTIONS)
|
||||
context.assert(MODULE, `${type}上传返回归属、文件哈希和草稿版本`, response,
|
||||
validDocument(document, type, false) && traced(response),
|
||||
`documentId=${id(document?.id) || 'missing'}, fileId=${id(document?.fileId) || 'missing'}, version=${document?.version ?? 'missing'}`, OPTIONS)
|
||||
if (!validDocument(document, type, false)) return null
|
||||
|
||||
const replay = await upload(context, uploadPath, type, 0, operationReason, uploadKey)
|
||||
const replayed = context.apiSuccess(MODULE, `${type}同键同载荷重放`, replay, '', OPTIONS)
|
||||
context.assert(MODULE, `${type}上传重放不新增资料或递增版本`, replay,
|
||||
id(replayed?.id) === id(document.id) && id(replayed?.fileId) === id(document.fileId)
|
||||
&& Number(replayed?.version) === Number(document.version),
|
||||
`documentId=${id(replayed?.id) || 'missing'}, version=${replayed?.version ?? 'missing'}`, OPTIONS)
|
||||
|
||||
const hashConflict = await upload(context, uploadPath, type, 0, operationReason, uploadKey, true)
|
||||
context.addStep(MODULE, `${type}同键异文件返回A0409`, hashConflict,
|
||||
conflict(hashConflict) && traced(hashConflict),
|
||||
`HTTP=${hashConflict.http}, code=${codeOf(hashConflict)}, requestId=${hashConflict.requestId || 'missing'}, traceId=${hashConflict.traceId || 'missing'}`, OPTIONS)
|
||||
|
||||
if (completePath) {
|
||||
const completeKey = key(context, `${type}-complete`)
|
||||
const completeBody = { expectedVersion: Number(document.version), operationReason }
|
||||
const completeResponse = await context.request('POST', completePath, completeBody, context.token,
|
||||
{ 'Idempotency-Key': completeKey })
|
||||
document = context.apiSuccess(MODULE, `完成${type}`, completeResponse, '', OPTIONS)
|
||||
context.assert(MODULE, `${type}完成后锁定且版本递增`, completeResponse,
|
||||
validDocument(document, type, true) && Number(document.version) === Number(completeBody.expectedVersion) + 1,
|
||||
`completedAt=${document?.completedAt || 'missing'}, version=${document?.version ?? 'missing'}`, OPTIONS)
|
||||
|
||||
const completeReplay = await context.request('POST', completePath, completeBody, context.token,
|
||||
{ 'Idempotency-Key': completeKey })
|
||||
const replayedComplete = context.apiSuccess(MODULE, `${type}完成同键重放`, completeReplay, '', OPTIONS)
|
||||
context.assert(MODULE, `${type}完成重放保持同一终态`, completeReplay,
|
||||
id(replayedComplete?.id) === id(document.id)
|
||||
&& Number(replayedComplete?.version) === Number(document.version)
|
||||
&& replayedComplete?.completedAt === document.completedAt,
|
||||
`documentId=${id(replayedComplete?.id) || 'missing'}, version=${replayedComplete?.version ?? 'missing'}`, OPTIONS)
|
||||
|
||||
const completeConflict = await context.request('POST', completePath,
|
||||
{ ...completeBody, operationReason: `${operationReason}-altered` }, context.token,
|
||||
{ 'Idempotency-Key': completeKey })
|
||||
context.addStep(MODULE, `${type}完成同键异载荷返回A0409`, completeConflict,
|
||||
conflict(completeConflict) && traced(completeConflict),
|
||||
`HTTP=${completeConflict.http}, code=${codeOf(completeConflict)}`, OPTIONS)
|
||||
}
|
||||
|
||||
const readResponse = await context.request('GET', readPath)
|
||||
const stored = context.apiSuccess(MODULE, `回读${type}`, readResponse, '', OPTIONS)
|
||||
const expectedCompleted = Boolean(completePath)
|
||||
context.assert(MODULE, `${type}负向请求后持久事实零漂移`, readResponse,
|
||||
validDocument(stored, type, expectedCompleted) && id(stored.id) === id(document.id)
|
||||
&& id(stored.fileId) === id(document.fileId) && Number(stored.version) === Number(document.version),
|
||||
`documentId=${id(stored?.id) || 'missing'}, version=${stored?.version ?? 'missing'}`, OPTIONS)
|
||||
return stored
|
||||
}
|
||||
|
||||
export async function run(context) {
|
||||
const fixture = context.createdResources?.phase3Loading
|
||||
const containerId = id(fixture?.containerBookingId)
|
||||
const planId = id(fixture?.loadingPlanId)
|
||||
const warehouseId = id(fixture?.warehouseId)
|
||||
if (fixture?.marker !== context.marker || fixture?.finalizeSucceeded !== true
|
||||
|| fixture?.finalStatus !== 'LOADED' || !containerId || !planId || !warehouseId) {
|
||||
context.addStep(MODULE, '取得本轮LOADED柜次四类资料前置', null, false,
|
||||
`marker=${fixture?.marker || 'missing'}, status=${fixture?.finalStatus || 'missing'}, containerBookingId=${containerId || 'missing'}, loadingPlanId=${planId || 'missing'}, warehouseId=${warehouseId || 'missing'}`,
|
||||
OPTIONS)
|
||||
return
|
||||
}
|
||||
|
||||
const specs = [
|
||||
{
|
||||
type: 'DESTINATION_CLEARANCE',
|
||||
uploadPath: `${CONTAINERS}/${containerId}/clearance/document/upload`,
|
||||
readPath: `${CONTAINERS}/${containerId}/clearance`,
|
||||
completePath: `${CONTAINERS}/${containerId}/clearance/complete`,
|
||||
},
|
||||
{
|
||||
type: 'CUSTOMS_DECLARATION',
|
||||
uploadPath: `${FIRST_LEG}/loading-lists/${planId}/customs/document/upload?warehouseId=${encodeURIComponent(warehouseId)}`,
|
||||
readPath: `${FIRST_LEG}/loading-lists/${planId}/customs?warehouseId=${encodeURIComponent(warehouseId)}`,
|
||||
completePath: `${FIRST_LEG}/loading-lists/${planId}/customs/complete?warehouseId=${encodeURIComponent(warehouseId)}`,
|
||||
},
|
||||
...['DELIVERY_ORDER', 'PICKUP_ORDER'].map((type) => ({
|
||||
type,
|
||||
uploadPath: `${CONTAINERS}/${containerId}/documents/${type}/upload`,
|
||||
readPath: `${CONTAINERS}/${containerId}/documents/${type}`,
|
||||
})),
|
||||
]
|
||||
|
||||
const documents = []
|
||||
for (const spec of specs) documents.push(await uploadAndVerify(context, spec))
|
||||
const overviewResponse = await context.request('GET', `${CONTAINERS}/${containerId}/overview`)
|
||||
const overview = context.apiSuccess(MODULE, '回查货柜四类资料聚合', overviewResponse, '', OPTIONS)
|
||||
const storedTypes = new Set((overview?.documents || []).map((item) => item?.documentType))
|
||||
context.assert(MODULE, '同一LOADED柜次聚合返回四类资料且身份未漂移', overviewResponse,
|
||||
id(overview?.container?.id) === containerId && documents.every(Boolean)
|
||||
&& DOCUMENT_TYPES.every((type) => storedTypes.has(type)),
|
||||
`containerBookingId=${containerId}, documentTypes=${[...storedTypes].join('|') || 'none'}`, OPTIONS)
|
||||
if (documents.every(Boolean) && DOCUMENT_TYPES.every((type) => storedTypes.has(type))) {
|
||||
context.createdResources.phase3ContainerDocuments = {
|
||||
marker: context.marker, containerBookingId: containerId, loadingPlanId: planId,
|
||||
warehouseId, finalStatus: 'COMPLETED', documentTypes: [...DOCUMENT_TYPES],
|
||||
documentIds: Object.fromEntries(documents.map((item) => [item.documentType, id(item.id)])),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user