统一履约状态和节点日志展示并整理装柜与尾程界面

This commit is contained in:
2026-10-28 16:56:32 +08:00
parent a52a6620fc
commit d990637b38
20 changed files with 270 additions and 43 deletions
@@ -10,6 +10,7 @@ import { useAuthStore } from '@/core/auth/auth'
import { ApiResponseError } from '@/shared/api/http'
import { h } from 'vue'
import AppDialog from '@/shared/components/AppDialog.vue'
import type { CarrierScheduleNodeFact } from '../types'
enableAutoUnmount(afterEach)
@@ -251,6 +252,39 @@ describe('CarrierScheduleQueryPanel loading states', () => {
expect(wrapper.text()).toContain('船运跟踪 · HEDE · hede-hold')
})
it.each([false, true])('shows recorded usernames for human nodes and retains automatic sources (dense=%s)', async dense => {
const manual: CarrierScheduleNodeFact = {
id: 'manual-1', providerCode: 'MANUAL', providerEventId: 'manual-event-1', providerNodeCode: 'IN_TRANSIT',
standardNode: 'IN_TRANSIT', nodeSequence: 50, observedAt: '2026-10-28T10:00:00',
actualAt: '2026-10-28T10:00:00', sourceReference: 'MANUAL:1000001',
rawPayloadHash: 'a'.repeat(64), operatorName: 'platform_admin',
}
vi.mocked(listCarrierScheduleQueries).mockResolvedValue({ records: [], page: 1, size: 200, total: 0, pages: 0 })
vi.mocked(getContainerShippingNodes).mockResolvedValue({ containerBookingId: '3', nodes: [
manual,
{ ...manual, id: 'outbound-1', providerCode: 'FIRSTLEG', providerEventId: 'OUTBOUND_CONFIRM', providerNodeCode: 'OUTBOUND_CONFIRM', standardNode: 'DEPARTED_WAREHOUSE', nodeSequence: 10, actualAt: '2026-10-28T09:00:00', sourceReference: 'OUTBOUND_CONFIRM', operatorName: 'outbound_operator' },
{ ...manual, id: 'automatic-1', providerCode: 'COSCO', providerEventId: 'carrier-event-1', providerNodeCode: 'ARRIVED', standardNode: 'ARRIVED_DESTINATION_PORT', nodeSequence: 60, actualAt: '2026-10-28T11:00:00', sourceReference: 'carrier-event-1', operatorName: null },
{ ...manual, id: 'missing-1', providerEventId: 'manual-event-2', providerNodeCode: 'PICKED_UP', standardNode: 'PICKED_UP', nodeSequence: 90, actualAt: '2026-10-28T12:00:00', sourceReference: 'MANUAL:2000002', operatorName: null },
] })
const wrapper = mount(CarrierScheduleQueryPanel, {
props: { dense, hideTechnicalDetails: true, oceanBookingId: '2', containerBookingId: '3', warehouseTimeline: [{ eventType: 'PDA_COMPLETE_LOADING', operatorName: 'pda_operator', occurredAt: '2026-10-28T08:00:00' }] },
global: { plugins: [ElementPlus, pinia] },
})
await flushPromises()
const rows = wrapper.findAll(dense ? '.fulfillment-table tbody tr' : '.fulfillment-timeline li')
const source = (title: string) => {
const row = rows.find(item => item.get('strong').text() === title)!
return dense ? row.findAll('td').at(-1)!.text() : row.get('small').text()
}
expect(source('海运途中')).toBe('platform_admin')
expect(source('已离开国内仓库')).toBe('outbound_operator')
expect(source('完成装柜')).toBe('pda_operator')
expect(source('船舶已到目的港')).toBe('COSCO · carrier-event-1')
expect(source('已提柜')).toBe('操作人未记录')
expect(rows.map(row => row.text()).join('|')).not.toContain('MANUAL:')
expect(rows.map(row => row.text()).join('|')).not.toContain('OUTBOUND_CONFIRM')
})
it('merges warehouse, actual, estimated, manual and exception facts with stable ordering and deduplication', async () => {
vi.mocked(listCarrierScheduleQueries).mockResolvedValue({ records: [{ ...queryView, query: { ...queryView.query, resultPortScheduleId: null }, expired: false }], page: 1, size: 200, total: 1, pages: 1 })
const actual = { id: '61', providerCode: 'COSCO', providerEventId: 'actual-61', providerNodeCode: 'LOAD', standardNode: 'LOADED_ON_VESSEL' as const, nodeSequence: 30, observedAt: '2026-08-21T10:05:00', estimatedAt: null, actualAt: '2026-08-21T10:00:00', locationCode: 'CNNGB', locationName: '宁波港', sourceReference: 'actual-source', rawPayloadHash: 'e'.repeat(64), createdAt: '2026-08-21T10:06:00' }