This commit is contained in:
2026-08-18 16:39:39 +08:00
parent 7fe5b4e31f
commit a9061eca54
43 changed files with 821 additions and 1217 deletions
@@ -26,7 +26,7 @@ async function request(context, method, path, body, headers = {}) {
}
async function uploadAttachment(context, appointmentId) {
const path = `${BASE}/${appointmentId}/attachments/upload?attachmentType=ACCEPTANCE_EVIDENCE`
const path = `${BASE}/${appointmentId}/attachments/upload?attachmentType=BILL_OF_LADING`
const form = new FormData()
const png = Uint8Array.from([137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,1,0,0,0,1,8,6,0,0,0,31,21,196,137,0,0,0,13,73,68,65,84,8,215,99,248,207,192,240,31,0,5,0,1,255,137,153,61,29,0,0,0,0,73,69,78,68,174,66,96,130])
form.append('file', new Blob([png], { type: 'image/png' }), `${context.marker}-truck.png`)
@@ -89,6 +89,8 @@ export function truckAppointmentPayload(context, suffix, identity, version = 0,
loadingPlanId: associations.loadingPlanId || null, warehouseId: associations.warehouseId || null,
customerId: identity.customerId, customerName: identity.customerName || null,
carrierId: supplierId || null, carrierName: supplierName || null,
billOfLadingNo: Object.hasOwn(associations, 'billOfLadingNo')
? associations.billOfLadingNo : `BL-TRUCK-${suffix}`.slice(0, 128),
fleetContact: '车队联系人', fleetPhone: '13800000004',
driverName: '司机验收', driverPhone: '13800000005', driverIdCard: null, vehicleNo: `粤B-${suffix.slice(-6)}`,
storeName: `门店-${suffix}`, storeContact: '门店联系人', storePhone: '13800000006', storeAddress: '深圳市南山区验收地址',
@@ -155,6 +157,21 @@ export async function run(context) {
`HTTP=${rejected.http}, code=${codeOf(rejected)}, requestId=${rejected.requestId || 'missing'}, traceId=${rejected.traceId || 'missing'}`, OPTIONS)
}
const missingBillCreate = await request(context, 'POST', BASE,
truckAppointmentPayload(context, `${suffix}B`, customerIdentity, 0, { billOfLadingNo: null }))
const missingBill = data(missingBillCreate)
if (missingBill?.id) {
const missingBillSubmit = await request(context, 'POST', `${BASE}/${id(missingBill.id)}/submit`,
{ expectedVersion: Number(missingBill.version), reason: context.marker },
{ 'Idempotency-Key': `${context.marker}-truck-no-bill-submit`.slice(0, 128) })
const submitted = data(missingBillSubmit)
const rejectedDispatch = await request(context, 'POST', `${BASE}/${id(missingBill.id)}/dispatch`,
{ expectedVersion: Number(submitted?.version), reason: context.marker },
{ 'Idempotency-Key': `${context.marker}-truck-no-bill-dispatch`.slice(0, 128) })
context.addStep(MODULE, '拖车 DRAFT 可无提单但 DISPATCH 缺提单号与文件明确拒绝', rejectedDispatch,
failure(rejectedDispatch), `HTTP=${rejectedDispatch.http}, code=${codeOf(rejectedDispatch)}`, OPTIONS)
}
const linkedFlow = [context.createdResources?.phase3Loading, context.createdResources?.phase3ContainerLoading]
.find((item) => item?.marker === context.marker && id(item.containerBookingId) && id(item.loadingPlanId))
if (linkedFlow) {
@@ -204,20 +221,14 @@ export async function run(context) {
const upload = await uploadAttachment(context, appointmentId); const attachment = context.apiSuccess(MODULE, '上传本轮拖车预约图片附件', upload, `fileName=${context.marker}-truck.png`, OPTIONS)
const attachmentId = id(attachment?.id)
context.assert(MODULE, '附件引用归属、类型和哈希元数据完整', upload, Boolean(attachmentId) && id(attachment?.appointmentId) === appointmentId && attachment?.attachmentType === 'ACCEPTANCE_EVIDENCE' && Boolean(attachment?.fileId) && Boolean(attachment?.fileHash), `attachmentId=${attachmentId || 'missing'}, fileId=${attachment?.fileId || 'missing'}`, OPTIONS)
context.assert(MODULE, '提单附件归属、类型和哈希元数据完整', upload, Boolean(attachmentId) && id(attachment?.appointmentId) === appointmentId && attachment?.attachmentType === 'BILL_OF_LADING' && Boolean(attachment?.fileId) && Boolean(attachment?.fileHash), `attachmentId=${attachmentId || 'missing'}, fileId=${attachment?.fileId || 'missing'}`, OPTIONS)
const attachments = await request(context, 'GET', `${path}/attachments`); const attachmentList = context.apiSuccess(MODULE, '查询拖车预约附件列表', attachments, '', OPTIONS)
context.assert(MODULE, '附件列表包含本轮上传引用', attachments, Array.isArray(attachmentList) && attachmentList.some((item) => id(item.id) === attachmentId), `attachments=${attachmentList?.length ?? 'n/a'}`, OPTIONS)
const forged = await request(context, 'POST', `${path}/attachments`, { fileId: '999999999999999999', attachmentType: 'ACCEPTANCE_EVIDENCE', fileName: `${context.marker}-forged.txt`, fileHash: 'forged' })
const forged = await request(context, 'POST', `${path}/attachments`, { fileId: '999999999999999999', attachmentType: 'BILL_OF_LADING', fileName: `${context.marker}-forged.txt`, fileHash: 'forged' })
context.addStep(MODULE, '伪造附件 fileId 安全拒绝', forged, failure(forged), `HTTP=${forged.http}, code=${codeOf(forged)}`, OPTIONS)
if (attachmentId) {
const removed = await request(context, 'DELETE', `${path}/attachments/${attachmentId}`)
context.apiSuccess(MODULE, '删除本轮拖车预约附件引用', removed, `attachmentId=${attachmentId}`, OPTIONS)
const afterRemoval = await request(context, 'GET', `${path}/attachments`)
const remainingAttachments = context.apiSuccess(MODULE, '删除后回查拖车预约附件列表', afterRemoval, '', OPTIONS)
context.assert(MODULE, '删除后附件引用不再返回', afterRemoval,
Array.isArray(remainingAttachments) && !remainingAttachments.some((item) => id(item.id) === attachmentId),
`attachmentId=${attachmentId}, remaining=${remainingAttachments?.length ?? 'n/a'}`, OPTIONS)
}
context.assert(MODULE, '派车前保留有效 BILL_OF_LADING 提单附件', attachments,
Boolean(attachmentId) && attachmentList.some((item) => id(item.id) === attachmentId && item.attachmentType === 'BILL_OF_LADING'),
`attachmentId=${attachmentId || 'missing'}`, OPTIONS)
let version = Number(appointment.version)
const edit = await request(context, 'PUT', path, { ...truckAppointmentPayload(context, suffix, customerIdentity, version), vehicleNo: `粤C-${suffix.slice(-6)}` }); const edited = context.apiSuccess(MODULE, '编辑独立拖车预约且关联身份保持为空', edit, `version=${version}`, OPTIONS); version = Number(edited?.version)