const MODULE = 'suppliers' const OPTIONS = { scenario: 'platform-public-suppliers', deploymentBlocker: true } const BASE = '/api/platform/v1/suppliers' const MERCHANT_USERNAME = process.env.MERCHANT_TEST_USERNAME || '' const MERCHANT_PASSWORD = process.env.MERCHANT_TEST_PASSWORD || '' function ok(response) { return response?.http === 200 && String(response?.body?.code) === '200' } function code(response, expected) { return response?.http === 200 && String(response?.body?.code) === expected } function records(page) { return Array.isArray(page?.records) ? page.records : [] } function id(value) { return value == null ? '' : String(value) } function traced(response) { return Boolean(response?.requestId && response?.traceId) } function sameCarrierCodes(actual, expected) { return Array.isArray(actual) && [...actual].sort().join('|') === [...expected].sort().join('|') } function query(values = {}) { const p = new URLSearchParams(); for (const [k,v] of Object.entries(values)) if (v !== '' && v != null) p.set(k,String(v)); return `${BASE}${p.size ? `?${p}` : ''}` } function payload(marker, overrides = {}) { return { name:`供应商验收-${marker}`,category:'BOOKING',carrierCodes:['MATSON','COSCO'],region:'宁波',legalRepresentative:'验收法人',contactName:'验收联系人',phone:'13800000000',email:'supplier-acceptance@example.com',address:'验收地址',businessLicenseNo:`ACPT${marker}`.slice(0,64),licenseFileId:null,status:'ACTIVE',settlementPeriod:'MONTHLY_30',remark:marker,...overrides } } export async function run(context) { const marker=context.marker.replace(/[^0-9A-Za-z]/g,'').slice(-20) const listResponse=await context.request('GET',query({tenantId:0,category:'BOOKING',page:1,size:20})) const list=context.apiSuccess(MODULE,'列表分页与订舱筛选',listResponse,'category=BOOKING',OPTIONS) context.assert(MODULE,'列表分页结构完整',listResponse,Array.isArray(list?.records)&&Number(list?.page)===1&&Number(list?.size)===20&&Number(list?.total)>=0&&Number(list?.pages)>=0,`records=${records(list).length}, total=${list?.total??'missing'}`,OPTIONS) const statsResponse=await context.request('GET',`${BASE}/stats?tenantId=0`) const stats=context.apiSuccess(MODULE,'查询四类供应商统计',statsResponse,'',OPTIONS) context.assert(MODULE,'统计字段完整',statsResponse,['booking','customs','trucking','certificate'].every(k=>Number(stats?.[k])>=0),JSON.stringify(stats),OPTIONS) const createBody=payload(marker) const createResponse=await context.request('POST',`${BASE}?tenantId=0`,createBody) const supplierId=id(context.apiSuccess(MODULE,'新增供应商',createResponse,`name=${createBody.name}`,OPTIONS)) context.assert(MODULE,'新增返回供应商ID',createResponse,Boolean(supplierId),`supplierId=${supplierId||'missing'}`,OPTIONS) if(!supplierId)return let inactive=false try { const detailResponse=await context.request('GET',`${BASE}/${encodeURIComponent(supplierId)}?tenantId=0`) const detail=context.apiSuccess(MODULE,'查询供应商详情',detailResponse,'',OPTIONS) context.assert(MODULE,'详情字段与平台作用域及多船司关联正确',detailResponse,id(detail?.id)===supplierId&&id(detail?.tenantId)==='0'&&detail?.name===createBody.name&&!Object.hasOwn(detail,'scope')&&sameCarrierCodes(detail?.carrierCodes,['MATSON','COSCO']),`id=${id(detail?.id)}, tenantId=${id(detail?.tenantId)}, carrierCodes=${JSON.stringify(detail?.carrierCodes)}, scopeExposed=${Object.hasOwn(detail||{},'scope')}`,OPTIONS) const duplicateResponse=await context.request('POST',`${BASE}?tenantId=0`,payload(`${marker}D`,{name:createBody.name})) context.assert(MODULE,'重复名称返回冲突并保留链路ID',duplicateResponse,code(duplicateResponse,'A0409')&&traced(duplicateResponse),`code=${duplicateResponse.body?.code}, requestId=${duplicateResponse.requestId||'missing'}, traceId=${duplicateResponse.traceId||'missing'}`,OPTIONS) const tenantResponse=await context.request('GET',query({tenantId:1,page:1,size:20})) context.assert(MODULE,'tenantId非0被拒绝',tenantResponse,code(tenantResponse,'A0400')&&traced(tenantResponse),`code=${tenantResponse.body?.code}, requestId=${tenantResponse.requestId||'missing'}, traceId=${tenantResponse.traceId||'missing'}`,OPTIONS) const nonBookingCarrierResponse=await context.request('POST',`${BASE}?tenantId=0`,payload(`${marker}NB`,{name:`非订舱供应商非法船司-${marker}`,category:'CUSTOMS',carrierCodes:['MATSON']})) context.assert(MODULE,'非BOOKING携带carrierCodes返回A0400并保留链路ID',nonBookingCarrierResponse,code(nonBookingCarrierResponse,'A0400')&&traced(nonBookingCarrierResponse),`code=${nonBookingCarrierResponse.body?.code}, requestId=${nonBookingCarrierResponse.requestId||'missing'}, traceId=${nonBookingCarrierResponse.traceId||'missing'}`,OPTIONS) const merchantLogin=await context.request('POST','/api/auth/login',{loginId:MERCHANT_USERNAME,password:MERCHANT_PASSWORD},'') const merchantToken=ok(merchantLogin)?merchantLogin.body?.data?.accessToken:'' const forbidden=merchantToken?await context.request('GET',query({tenantId:0,page:1,size:20}),undefined,merchantToken):merchantLogin context.assert(MODULE,'客户账号调用平台供应商接口被拒绝',forbidden,Boolean(merchantToken)&&(forbidden.http===403||code(forbidden,'A0403'))&&traced(forbidden),`HTTP=${forbidden.http}, code=${forbidden.body?.code??'missing'}, requestId=${forbidden.requestId||'missing'}, traceId=${forbidden.traceId||'missing'}`,OPTIONS) const updateBody=payload(marker,{name:`供应商验收已编辑-${marker}`,contactName:'编辑联系人',settlementPeriod:'MONTHLY_45'}) const updateResponse=await context.request('PUT',`${BASE}/${encodeURIComponent(supplierId)}?tenantId=0`,updateBody) context.apiSuccess(MODULE,'编辑供应商',updateResponse,'',OPTIONS) const editedResponse=await context.request('GET',`${BASE}/${encodeURIComponent(supplierId)}?tenantId=0`) const edited=context.apiSuccess(MODULE,'回查编辑结果',editedResponse,'',OPTIONS) context.assert(MODULE,'编辑字段及多船司关联已持久化',editedResponse,edited?.name===updateBody.name&&!Object.hasOwn(edited,'scope')&&edited?.contactName==='编辑联系人'&&edited?.settlementPeriod==='MONTHLY_45'&&sameCarrierCodes(edited?.carrierCodes,['MATSON','COSCO']),`name=${edited?.name||'missing'}, carrierCodes=${JSON.stringify(edited?.carrierCodes)}, scopeExposed=${Object.hasOwn(edited||{},'scope')}`,OPTIONS) const disableResponse=await context.request('PATCH',`${BASE}/${encodeURIComponent(supplierId)}/status?tenantId=0`,{status:'INACTIVE'}) context.apiSuccess(MODULE,'停用供应商',disableResponse,'',OPTIONS); inactive=ok(disableResponse) const inactiveResponse=await context.request('GET',`${BASE}/${encodeURIComponent(supplierId)}?tenantId=0`) const inactiveDetail=context.apiSuccess(MODULE,'回查停用状态',inactiveResponse,'',OPTIONS) context.assert(MODULE,'停用状态已持久化',inactiveResponse,inactiveDetail?.status==='INACTIVE',`status=${inactiveDetail?.status||'missing'}`,OPTIONS) } finally { if(!inactive){const cleanup=await context.request('PATCH',`${BASE}/${encodeURIComponent(supplierId)}/status?tenantId=0`,{status:'INACTIVE'}); inactive=ok(cleanup); context.assert(MODULE,'异常路径停用验收供应商',cleanup,inactive,`supplierId=${supplierId}`,OPTIONS)} context.createdResources.suppliers={marker:context.marker,supplierId,finalStatus:inactive?'INACTIVE':'UNKNOWN',retained:true} } }