import { createHash } from 'node:crypto' const MODULE='phase5-root-fulfillment',OPTIONS={scenario:'f40-tax-customs-after-completion',deploymentBlocker:true} const PLATFORM='/api/platform/v1/customs/loading-plans',MERCHANT='/api/merchant/v1/customs' const id=value=>String(value??'') const sha=bytes=>createHash('sha256').update(bytes).digest('hex') function check(context,name,passed,response=null){ context.assert(MODULE,name,response,Boolean(passed),'exact-current-tax-source',OPTIONS) if(!passed)throw Error(name) } async function api(context,method,name,path,body,token=context.token,key){ const response=await context.request(method,path,body,token,key?{'Idempotency-Key':key}:{}) const data=context.apiSuccess(MODULE,name,response,'',OPTIONS) check(context,name,response?.http===200&&String(response.body?.code)==='200',response) return data } export function taxDocumentMatches(view,proof){ const documents=view?.documents||[] const document=documents.find(row=>id(row.documentId)===proof.documentId) return view?.status==='COMPLETED'&&id(view.loadingPlanId)===proof.loadingPlanId &&id(view.containerBookingId)===proof.containerBookingId&&document?.frozen===true &&id(document.tenantId)===proof.customerId&&document.inboundIds?.map(id).includes(proof.inboundId) &&document.source?.fileHash===proof.files.source.sha256&&document.receiptStatus==='CONFIRMED' &&document.receipt?.fileHash===proof.files.receipt.sha256&&document.finalDocument?.fileHash===proof.files.final.sha256 } export async function prepareTaxCustoms(context,loading){ const proof={customerId:id(loading.tenantId),orderId:id(loading.orderId),inboundId:id(loading.inboundId), inboundCode:loading.inboundCode,containerBookingId:id(loading.containerBookingId),loadingPlanId:id(loading.loadingPlanId), containerNo:loading.containerNo,files:{},stages:[],status:'PREPARING'} context.context.f40TaxEvidence.push(proof) const token=context.context.f40TaxMerchantToken check(context,'F40 退税报关使用本轮客户会话',Boolean(token)) const base=`${PLATFORM}/${proof.loadingPlanId}` const key=action=>`${context.marker}-F40-TAX-${action}`.slice(0,128) const detail=()=>api(context,'GET','F40 回查真实退税报关聚合',base) const upload=async(kind,path,token)=>{ const content=new TextEncoder().encode(`${context.marker} TAX_REFUND ${proof.inboundCode} ${kind}`) proof.files[kind]={sha256:sha(content),byteCount:content.length} const response=await context.requestMultipart('POST',path,{name:`${context.marker}-${kind}.txt`,type:'text/plain',content},token,{'Idempotency-Key':key(kind)}) const data=context.apiSuccess(MODULE,`F40 真实上传退税${kind}文件`,response,'',OPTIONS) check(context,`F40 退税${kind}上传成功`,response.http===200&&String(response.body?.code)==='200',response) return data } let current=await detail() check(context,'F40 实装来源确为本客户退税入仓码',current.inbounds?.length===1&¤t.inbounds.every(row=> row.taxMode==='TAX_REFUND'&&id(row.inboundId)===proof.inboundId)) const source=await upload('source',`${MERCHANT}/loading-plans/${proof.loadingPlanId}/documents/upload?inboundIds=${proof.inboundId}`,token) proof.documentId=id(source.documentId) check(context,'F40 客户原始报关文件精确绑定本票',Boolean(proof.documentId)&&source.source?.fileHash===proof.files.source.sha256 &&id(source.tenantId)===proof.customerId&&source.inboundIds?.map(id).includes(proof.inboundId)) for(const action of ['start','send-to-broker']){ current=await detail() const next=await api(context,'POST',`F40 退税真实${action}`,`${base}/${action}`, {expectedVersion:Number(current.version),confirm:true,remark:context.marker},context.token,key(action)) proof.stages.push({action,status:next.status,version:next.version}) } const receipt=await upload('receipt',`${PLATFORM}/documents/${proof.documentId}/receipt/upload?expectedVersion=0`,context.token) await api(context,'POST','F40 客户确认本票报关回执',`${MERCHANT}/documents/${proof.documentId}/receipt/confirm`, {expectedVersion:Number(receipt.receipt.version),confirm:true,remark:context.marker},token,key('confirm')) current=await detail() await api(context,'POST','F40 平台发送已确认回执执行报关',`${base}/submit-to-broker`, {expectedVersion:Number(current.version),confirm:true,remark:context.marker},context.token,key('execute')) current=await detail() const batch=await api(context,'POST','F40 人工创建真实正式申报批次',`${base}/declaration-batches`, {expectedVersion:Number(current.version),remark:context.marker},context.token,key('declaration')) proof.declarationBatchId=id(batch.id) const submitted=await api(context,'POST','F40 人工提交正式申报',`${PLATFORM}/declaration-batches/${batch.id}/submit`, {expectedVersion:Number(batch.version),customsEntryNo:`F40TAX-${proof.loadingPlanId}`,remark:context.marker},context.token,key('submit-declaration')) const now=new Date(),local=new Date(now.getTime()-now.getTimezoneOffset()*60000).toISOString().slice(0,19) const release=await api(context,'POST','F40 人工记录海关放行',`${PLATFORM}/declaration-batches/${batch.id}/results`, {expectedVersion:Number(submitted.version),resultType:'RELEASED',occurredAt:local,resolution:'本轮退税验收人工放行',resolved:true},context.token,key('release')) check(context,'F40 真实申报已提交且放行已处理',submitted.status==='SUBMITTED'&&release.resultType==='RELEASED'&&release.resultStatus==='RESOLVED') await upload('final',`${PLATFORM}/documents/${proof.documentId}/final/upload?expectedVersion=0`,context.token) proof.completedCustoms=await detail() check(context,'F40 退税完成文件齐全才完成报关',taxDocumentMatches(proof.completedCustoms,proof)) proof.status='CUSTOMS_COMPLETED' context.createdResources.phase3Customs={...loading,parentOrderId:loading.orderId,finalStatus:'CLEARANCE_COMPLETED'} return proof } export async function verifyTaxDocumentsAfterCompletion(context,proofs,merchantToken){ check(context,'F40 完成后验收具有真实退税报关证据',proofs.length>0) for(const proof of proofs){ const order=await api(context,'GET','F40 客户回查退税原订单最终完成',`/api/merchant/v1/orders/${proof.orderId}`,undefined,merchantToken) check(context,'F40 退税订单最终COMPLETED且税制未变',id(order.summary?.orderId)===proof.orderId&&order.summary.mainStatus==='COMPLETED'&&order.summary.taxMode==='TAX_REFUND') proof.afterCompletedView=await api(context,'GET','F40 COMPLETED后客户仍可读取本票报关资料',`${MERCHANT}/orders/${proof.orderId}`,undefined,merchantToken) check(context,'F40 完成后报关资料客户范围及文件哈希保持一致',taxDocumentMatches(proof.afterCompletedView,proof)) proof.downloads=[] const customer=Object.create(context);customer.token=merchantToken for(const [kind,suffix] of [['source','download'],['receipt','receipt/download'],['final','final/download']]){ const response=await customer.requestBinary('GET',`${MERCHANT}/documents/${proof.documentId}/${suffix}`) const fact={kind,http:response.http,byteCount:response.bytes?.length||0,sha256:sha(response.bytes||new Uint8Array())} proof.downloads.push(fact) check(context,`F40 COMPLETED后客户真实下载${kind}字节保持一致`,fact.http===200&&fact.byteCount===proof.files[kind].byteCount&&fact.sha256===proof.files[kind].sha256, {...response,bytes:undefined}) } proof.status='ORDER_COMPLETED_CUSTOMS_READ_DOWNLOAD_VERIFIED' } }