85 lines
8.1 KiB
JavaScript
85 lines
8.1 KiB
JavaScript
import fs from 'node:fs';
|
|
const base=process.env.ADMIN_TEST_GATEWAY;
|
|
if(!base||!['127.0.0.1','localhost'].includes(new URL(base).hostname))throw Error('仅允许本机');
|
|
const marker='LOCAL-TAIL-20261020';
|
|
const file=new URL('tail-flow-state.json',import.meta.url);
|
|
const state=fs.existsSync(file)?JSON.parse(fs.readFileSync(file,'utf8')):{marker,environment:'LOCAL',photoEvidence:'SYNTHETIC_TEST_PNG',checks:[],taskId:'2102273311474982914',containerId:'2092814929307344898',inboundId:'2092814880129130499',orderId:'2092814878690398210',warehouseId:'193000000000200001'};
|
|
const save=()=>fs.writeFileSync(file,JSON.stringify(state,null,2));
|
|
async function api(token,method,path,body,key){
|
|
const form=body instanceof FormData;
|
|
const r=await fetch(base+path,{method,headers:{...(token?{Authorization:'Bearer '+token}:{}),...(!form&&body!==undefined?{'Content-Type':'application/json'}:{}),...(key?{'Idempotency-Key':key}:{})},body:body===undefined?undefined:form?body:JSON.stringify(body),signal:AbortSignal.timeout(60000)});
|
|
const v=await r.json();
|
|
if(!path.includes('/auth/')){state.checks.push({method,path,http:r.status,code:v.code,message:String(v.code)==='200'?undefined:v.message});save();console.log(method,path,v.code,String(v.code)==='200'?'':v.message);}
|
|
if(!r.ok||String(v.code)!=='200')throw Error(`${method} ${path}: ${v.code} ${v.message}`);
|
|
return v.data;
|
|
}
|
|
const admin=(await api('','POST','/api/auth/login',{loginId:process.env.ADMIN_TEST_USERNAME,password:process.env.ADMIN_TEST_PASSWORD})).accessToken;
|
|
const root='/api/platform/v1/first-leg/pda-loading/tasks/'+state.taskId;
|
|
const task=()=>api(admin,'GET',root+'?warehouseId='+state.warehouseId);
|
|
const action=process.argv[2]||'inspect';
|
|
try {
|
|
if(action==='gate'){
|
|
for(const suffix of ['?containerBookingId='+state.containerId,'/'+state.containerId+'/gate']){
|
|
console.log(JSON.stringify(await api(admin,'GET','/api/platform/v1/first-leg/outbound-dispatches'+suffix)));
|
|
}
|
|
}
|
|
if(action==='verify'){
|
|
const doc=await api(admin,'GET','/api/platform/v1/containers/'+state.containerId+'/clearance');
|
|
const rows={};
|
|
for(const status of ['SAILING_CLEARANCE','WAIT_UNLOAD','WAIT_DISPATCH']){
|
|
const page=await api(admin,'GET','/api/platform/v1/orders?page=1&size=200&mainStatus='+status);
|
|
rows[status]={total:page.total,matching:page.records.filter(r=>r.inboundId===state.inboundId)};
|
|
}
|
|
if(rows.SAILING_CLEARANCE.matching.length||rows.WAIT_UNLOAD.matching.length!==1||rows.WAIT_DISPATCH.matching.length!==1)throw Error('测试来源页签流转不符合预期');
|
|
const r=await fetch(base+'/api/platform/v1/containers/'+state.containerId+'/clearance/document/'+doc.fileId+'/download',{headers:{Authorization:'Bearer '+admin},signal:AbortSignal.timeout(30000)});
|
|
const downloaded=await r.text();
|
|
if(!r.ok||!downloaded.includes(marker))throw Error('测试清关附件下载不一致');
|
|
state.verification={at:new Date().toISOString(),document:doc,rows,download:{http:r.status,containsLocalTestMarker:true}};save();
|
|
console.log(JSON.stringify({document:doc,rows:Object.fromEntries(Object.entries(rows).map(([s,p])=>[s,{total:p.total,matching:p.matching.map(r=>r.inboundId)}])),downloadHttp:r.status}));
|
|
}
|
|
if(action==='prepare'){
|
|
if(!state.pdaUserId && state.checks.some(c=>c.method==='POST'&&c.path==='/api/platform/v1/iam/users'&&String(c.code)==='200')){ const a=await api('','POST','/api/auth/login',{loginId:state.pdaUsername,password:process.env.LOCAL_TRANSFER_PDA_PASSWORD}); state.pdaUserId=JSON.parse(Buffer.from(a.accessToken.split('.')[1],'base64url')).sub;save(); }
|
|
if(!state.pdaUserId){
|
|
const roles=await api(admin,'GET','/api/platform/v1/iam/roles?page=1&size=200');
|
|
const role=(roles.records||roles).find(r=>r.roleCode==='CN_FIRSTLEG_PDA_OPERATOR');
|
|
if(!role)throw Error('缺少国内PDA角色');
|
|
state.pdaUsername='local_tail_pda_20261020';save();
|
|
await api(admin,'POST','/api/platform/v1/iam/users',{username:state.pdaUsername,displayName:'本地尾程流转验证PDA',phone:'18'+String(Date.now()).slice(-9),tenantId:'0',roleIds:[role.id],domesticWarehouseId:state.warehouseId,password:process.env.LOCAL_TRANSFER_PDA_PASSWORD,status:'ACTIVE'});
|
|
const users=await api(admin,'GET','/api/platform/v1/iam/users?page=1&size=20&keyword='+state.pdaUsername);
|
|
const user=users.records.find(r=>r.username===state.pdaUsername);
|
|
if(!user)throw Error('PDA创建后未查到');state.pdaUserId=user.userId;save();
|
|
}
|
|
const pda=(await api('','POST','/api/auth/login',{loginId:state.pdaUsername,password:process.env.LOCAL_TRANSFER_PDA_PASSWORD})).accessToken;
|
|
let current=await task();
|
|
if(current.assignment?.assigneeId!==state.pdaUserId)current=await api(admin,'POST',root+'/assign',{assigneeId:state.pdaUserId,assigneeName:'本地尾程流转验证PDA',expectedAssignmentVersion:current.assignment?.version||0,operationId:marker+'-assign'});
|
|
if(!state.before){state.before=await api(admin,'GET','/api/platform/v1/orders/status-counts');save();}
|
|
for(const item of current.items){if(item.itemStatus!=='PICKED')await api(pda,'POST',root+'/items/'+item.id+'/pick',{warehouseId:state.warehouseId,pickedPackageCount:item.plannedPackageCount,version:item.version,operationId:marker+'-pick-'+item.id});}
|
|
const evidence=await api(pda,'GET',root+'/evidence?warehouseId='+state.warehouseId);
|
|
for(const type of ['PHOTO_STEP_1','PHOTO_STEP_2','PHOTO_STEP_3','PHOTO_STEP_4','SEAL']){
|
|
if(evidence.evidences?.some(e=>e.evidenceType===type&&e.status==='ACTIVE'))continue;
|
|
if(state.evidence?.includes(type))continue;
|
|
const q=new URLSearchParams({warehouseId:state.warehouseId,expectedVersion:'0',evidenceType:type,remark:marker+' 合成测试图片;仅本地流转验收'});
|
|
if(type==='SEAL')q.set('sealNo','LOCALTEST20261020');
|
|
const bytes=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]);
|
|
const body=new FormData();body.append('file',new Blob([bytes],{type:'image/png'}),marker+'-'+type+'.png');
|
|
await api(pda,'POST',root+'/evidence?'+q,body,marker+'-'+type);(state.evidence??=[]).push(type);save();
|
|
}
|
|
current=await task();
|
|
if(current.taskStatus==='LOADING'||current.taskStatus==='COMPLETION_FAILED')state.completion=await api(pda,'POST',root+'/complete',{expectedTaskVersion:state.completion?.completionCommand?.expectedTaskVersion??current.version,operationId:marker+'-complete',remark:marker+' 本地测试装柜交接验证'});
|
|
save();
|
|
}
|
|
if(action==='inspect'||action==='prepare') {state.task=await task();state.counts=await api(admin,'GET','/api/platform/v1/orders/status-counts');save();console.log(JSON.stringify({taskStatus:state.task.taskStatus,completion:state.task.completionCommand,counts:state.counts}));}
|
|
if(action==='depart'){
|
|
if((await task()).taskStatus!=='COMPLETED')throw Error('必须先装柜完成');
|
|
state.nodeTime??=new Date(Date.now()+8*3600000).toISOString().slice(0,19);save();
|
|
state.departure=await api(admin,'POST','/api/platform/v1/container-shipping-nodes/'+state.containerId,{standardNode:'DEPARTED_ORIGIN_PORT',observedAt:state.nodeTime,actualAt:state.nodeTime,locationName:'本地手动开船流转验收',reason:marker+' 用户授权本地手动节点验证'},marker+'-depart');
|
|
state.afterDeparture=await api(admin,'GET','/api/platform/v1/orders/status-counts');save();console.log(JSON.stringify(state.afterDeparture));
|
|
}
|
|
if(action==='clearance'){
|
|
state.clearanceBefore=await api(admin,'GET','/api/platform/v1/containers/'+state.containerId+'/clearance');save();
|
|
const form=new FormData();form.append('file',new Blob([marker+' LOCAL TEST ONLY 清关资料上传流转测试\n'],{type:'text/plain'}),marker+'-clearance.txt');
|
|
state.clearance=await api(admin,'POST','/api/platform/v1/containers/'+state.containerId+'/clearance/document/upload?expectedVersion='+(state.clearanceBefore?.version||0)+'&operationReason='+encodeURIComponent('本地流转测试资料'),form,marker+'-clearance');
|
|
state.afterClearance=await api(admin,'GET','/api/platform/v1/orders/status-counts');save();console.log(JSON.stringify(state.afterClearance));
|
|
}
|
|
}catch(e){state.lastError=e.message;save();console.error(e.message);process.exitCode=1;}
|