优化
This commit is contained in:
@@ -6,6 +6,81 @@ const BASE = '/api/platform/v1/port-schedules'
|
||||
const OCEAN = '/api/platform/v1/ocean-bookings'
|
||||
const OUTBOUND = '/api/platform/v1/first-leg/outbound-dispatches'
|
||||
import { resolveOceanBookingMaster, oceanBookingDraft } from './ocean-booking-fixture.mjs'
|
||||
import { createHash } from 'node:crypto'
|
||||
|
||||
export function customsRetentionDigest(value) {
|
||||
const canonical = input => Array.isArray(input) ? input.map(canonical)
|
||||
: input && typeof input === 'object' ? Object.fromEntries(Object.keys(input).sort().map(key => [key, canonical(input[key])]))
|
||||
: input
|
||||
return createHash('sha256').update(JSON.stringify(canonical(value))).digest('hex')
|
||||
}
|
||||
|
||||
export async function verifyTaxCustomsAfterShipEdit(context, fixture) {
|
||||
if (fixture?.marker !== context.marker || !fixture?.loadingPlanId || !fixture?.queryId || !fixture?.scheduleId) {
|
||||
throw new Error('requires current marker owned tax customs, query and schedule fixture')
|
||||
}
|
||||
const customsPath = `/api/platform/v1/customs/loading-plans/${fixture.loadingPlanId}`
|
||||
const queryPath = `/api/platform/v1/carrier-schedule-queries/${fixture.queryId}`
|
||||
async function read(path, name) {
|
||||
const response = await request(context, 'GET', path)
|
||||
const value = context.apiSuccess(MODULE, name, response, '', OPTIONS)
|
||||
if (!value) throw new Error(`${name} failed`)
|
||||
return value
|
||||
}
|
||||
const beforeCustoms = await read(customsPath, '改船前冻结同一退税柜完整资料回执确认事实')
|
||||
const completedTax = beforeCustoms.status === 'COMPLETED' && Number(beforeCustoms.taxRefundCount) > 0
|
||||
&& id(beforeCustoms.loadingPlanId) === id(fixture.loadingPlanId)
|
||||
&& id(beforeCustoms.containerBookingId) === id(fixture.containerBookingId)
|
||||
&& beforeCustoms.documents?.length > 0
|
||||
&& beforeCustoms.documents.every(document => document.source?.fileId && document.receipt?.fileId
|
||||
&& document.receiptStatus === 'CONFIRMED' && document.finalDocument?.fileId)
|
||||
context.assert(MODULE, '改船样本为本轮已完成且含资料回执确认完成文件的退税柜', null,
|
||||
completedTax, `loadingPlanId=${fixture.loadingPlanId}, taxRefundCount=${beforeCustoms.taxRefundCount}`, OPTIONS)
|
||||
if (!completedTax) throw new Error('requires completed tax customs evidence')
|
||||
const beforeQuery = await read(queryPath, '改船前冻结原柜唯一船任务与正常节点')
|
||||
const beforeSchedule = await read(`${BASE}/${fixture.scheduleId}`, '改船前读取原柜船期')
|
||||
const schedule = beforeSchedule.portSchedule?.schedule
|
||||
const beforeTask = beforeQuery.query?.query
|
||||
if (!schedule || id(beforeTask?.id) !== id(fixture.queryId) || id(schedule.containerBookingId) !== id(fixture.containerBookingId)
|
||||
|| id(beforeTask?.containerBookingId) !== id(fixture.containerBookingId)) throw new Error('ship fixture identity mismatch')
|
||||
const editedData = { ...Object.fromEntries(['customerId', 'oceanBookingId', 'containerBookingId',
|
||||
'sourceType', 'sourceName', 'sourceRef', 'departurePortCode', 'departurePortName',
|
||||
'destinationPortCode', 'destinationPortName', 'etdAt', 'etaAt', 'bookingCutoffAt',
|
||||
'customsCutoffAt', 'portCutoffAt', 'observedAt', 'expiresAt', 'remark']
|
||||
.map(key => [key, schedule[key]])), vesselName: `人工改船-${context.marker}`.slice(0, 128),
|
||||
voyageNo: `R-${context.marker}`.slice(0, 64) }
|
||||
const response = await request(context, 'PUT', `${BASE}/${fixture.scheduleId}`,
|
||||
{ expectedVersion: Number(schedule.version), data: editedData, reason: `${context.marker} 原柜人工改船` },
|
||||
{ 'Idempotency-Key': `${context.marker}-tax-ship-edit`.slice(0, 128) })
|
||||
const edited = context.apiSuccess(MODULE, '退税报关完成后在原柜人工改船', response, '', OPTIONS)
|
||||
if (!edited) throw new Error('ship edit failed')
|
||||
const afterCustoms = await read(customsPath, '改船后读取同一退税柜完整资料回执确认事实')
|
||||
const afterQuery = await read(queryPath, '改船后回查原唯一船任务与正常节点')
|
||||
const afterSchedule = await read(`${BASE}/${fixture.scheduleId}`, '改船后读取修改历史')
|
||||
const selector = new URLSearchParams({ containerBookingId: fixture.containerBookingId, page: '1', size: '200' })
|
||||
const page = await read(`/api/platform/v1/carrier-schedule-queries?${selector}`, '改船后按原柜核对船任务唯一')
|
||||
const customsBeforeDigest = customsRetentionDigest(beforeCustoms)
|
||||
const customsAfterDigest = customsRetentionDigest(afterCustoms)
|
||||
context.assert(MODULE, '原柜改船前后报关资料回执确认完成文件完整摘要零漂移', null,
|
||||
customsBeforeDigest === customsAfterDigest, `before=${customsBeforeDigest}, after=${customsAfterDigest}`, OPTIONS)
|
||||
const sameTask = ['id', 'containerBookingId', 'oceanBookingId', 'trackingNo', 'latestActualNode', 'latestActualAt']
|
||||
.every(key => id(beforeTask[key]) === id(afterQuery.query?.query?.[key]))
|
||||
context.assert(MODULE, '原柜改船保留唯一查询任务和全部已记录正常节点', null,
|
||||
sameTask && customsRetentionDigest(beforeQuery.nodes) === customsRetentionDigest(afterQuery.nodes)
|
||||
&& Number(page.total) === 1 && id(page.records?.[0]?.query?.id) === id(fixture.queryId),
|
||||
`queryId=${fixture.queryId}, total=${page.total}`, OPTIONS)
|
||||
const latest = afterSchedule.portSchedule?.schedule
|
||||
const update = afterSchedule.histories?.at(-1)
|
||||
context.assert(MODULE, '人工改船保留原柜订舱及操作人原因版本历史', null,
|
||||
id(latest?.id) === id(schedule.id) && id(latest?.containerBookingId) === id(schedule.containerBookingId)
|
||||
&& id(latest?.oceanBookingId) === id(schedule.oceanBookingId)
|
||||
&& latest?.vesselName === editedData.vesselName && latest?.voyageNo === editedData.voyageNo
|
||||
&& update?.action === 'UPDATE' && Boolean(update.operatorId && update.createdAt)
|
||||
&& Number(update.toVersion) === Number(schedule.version) + 1,
|
||||
`scheduleId=${fixture.scheduleId}, historyId=${update?.id || 'missing'}`, OPTIONS)
|
||||
context.createdResources.taxCustomsShipRetention = { fixture, beforeCustoms, afterCustoms,
|
||||
customsBeforeDigest, customsAfterDigest, beforeQuery, afterQuery, beforeSchedule, afterSchedule }
|
||||
}
|
||||
|
||||
function id(value) { return value == null ? '' : String(value) }
|
||||
function codeOf(response) { return response?.body?.code == null ? 'missing' : String(response.body.code) }
|
||||
|
||||
Reference in New Issue
Block a user