feat(firstleg): show actual and estimated transport nodes by loaded fragment

This commit is contained in:
2026-10-05 01:06:13 +08:00
parent f0777ba2db
commit e139c5c87b
7 changed files with 131 additions and 1 deletions
@@ -0,0 +1,51 @@
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import { runInNewContext } from 'node:vm'
import test from 'node:test'
import ts from 'typescript'
const source = readFileSync(new URL('./transportFacts.ts', import.meta.url), 'utf8')
const javascript = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 } }).outputText
const exports = {}
runInNewContext(javascript, { exports })
const rows = exports.customerTransportRows
test('split inbound fragments retain separate cabinet facts and actual/estimated times', () => {
const result = rows([
{ inboundCode: 'NY-1', cabinetNo: 'D1', containerNo: 'TSTU0000001', nodes: [
{ standardNode: 'AT_ORIGIN_PORT', actualAt: '2026-10-05T09:00:00', estimatedAt: null, providerCode: 'MANUAL' },
{ standardNode: 'DEPARTED_ORIGIN_PORT', actualAt: null, estimatedAt: '2026-10-07T08:00:00', providerCode: 'MATSON' },
] },
{ inboundCode: 'NY-2', cabinetNo: 'D2', containerNo: 'TSTU0000002', nodes: [
{ standardNode: 'AT_ORIGIN_PORT', actualAt: '2026-10-06T10:00:00', estimatedAt: null, providerCode: 'MATSON' },
] },
])
assert.equal(result.length, 3)
assert.equal(result[0].nodeLabel, '已进港')
assert.equal(result[0].source, '人工记录')
assert.equal(result[1].nodeLabel, '预计开航')
assert.equal(result[1].actualAt, null)
assert.equal(result[1].estimatedAt, '2026-10-07T08:00:00')
assert.equal(result[2].cabinetNo, 'D2')
assert.equal(result[2].inboundCode, 'NY-2')
assert.equal(result[2].actualAt, '2026-10-06T10:00:00')
})
test('empty actual scope creates no invented nodes or database identifier fallback', () => {
assert.equal(rows([]).length, 0)
assert.equal(rows([{ inboundCode: 'NY-1', nodes: [] }]).length, 0)
const [row] = rows([{ id: 'INTERNAL', nodes: [{ standardNode: 'AT_ORIGIN_PORT', actualAt: null, estimatedAt: '2026-10-05T09:00:00' }] }])
assert.equal(row.cabinetNo, '—')
assert.equal(row.containerNo, '—')
assert.equal(row.inboundCode, '—')
assert.equal(row.actualAt, null)
})
test('detail consumes the existing merchant detail API and displays times separately', () => {
const store = readFileSync(new URL('../store/firstLegOrders.ts', import.meta.url), 'utf8')
const table = readFileSync(new URL('../components/TransportFactsTable.vue', import.meta.url), 'utf8')
assert.match(store, /transportFragments: detail\.transportFragments/)
assert.match(table, /label="实际时间".*time\(row\.actualAt\)/)
assert.match(table, /label="预计时间".*time\(row\.estimatedAt\)/)
assert.doesNotMatch(table, /platform\/|fetch\(|actualAt\s*\|\|\s*.*estimatedAt/)
})
@@ -0,0 +1,24 @@
import type { MerchantTransportFragment } from '../api/unifiedOrders'
const labels: Readonly<Record<string, string>> = {
DEPARTED_WAREHOUSE: '已离仓', AT_ORIGIN_PORT: '已进港', LOADED_ON_VESSEL: '已装船',
DEPARTED_ORIGIN_PORT: '已开航', IN_TRANSIT: '海运途中', ARRIVED_DESTINATION_PORT: '船舶已到目的港',
DISCHARGED_FROM_VESSEL: '已卸船', PICKUP_READY: '已清关、可提柜', PICKED_UP: '已提柜', EMPTY_RETURNED: '已还柜',
}
const estimatedLabels: Readonly<Record<string, string>> = {
DEPARTED_WAREHOUSE: '预计离仓', AT_ORIGIN_PORT: '预计进港', LOADED_ON_VESSEL: '预计装船',
DEPARTED_ORIGIN_PORT: '预计开航', IN_TRANSIT: '预计海运途中', ARRIVED_DESTINATION_PORT: '预计船舶到目的港',
DISCHARGED_FROM_VESSEL: '预计卸船', PICKUP_READY: '预计可提柜', PICKED_UP: '预计提柜', EMPTY_RETURNED: '预计还柜',
}
export function customerTransportRows(fragments: MerchantTransportFragment[]) {
return fragments.flatMap((fragment, index) => fragment.nodes.map((node, nodeIndex) => ({
key: `${index}-${nodeIndex}`,
inboundCode: fragment.inboundCode || '—', cabinetNo: fragment.cabinetNo || '—',
containerNo: fragment.containerNo || '—',
nodeLabel: (node.actualAt ? labels : estimatedLabels)[node.standardNode] || '运输节点',
actualAt: node.actualAt, estimatedAt: node.estimatedAt,
location: [node.locationCode, node.locationName].filter(Boolean).join(' · ') || '—',
source: node.providerCode === 'MANUAL' ? '人工记录' : node.providerCode === 'FIRSTLEG' ? '平台记录' : node.providerCode ? '承运轨迹' : '—',
})))
}