This commit is contained in:
2026-08-26 13:58:39 +08:00
parent a3d04eaad8
commit 5daaf1a5fa
6 changed files with 103 additions and 59 deletions
@@ -51,10 +51,14 @@ describe('CarrierScheduleQueryPanel loading states', () => {
it('shows an explicit non-fabricated empty state and performs no detail reads', async () => {
vi.mocked(listCarrierScheduleQueries).mockResolvedValue({ records: [], page: 1, size: 200, total: 0, pages: 0 })
const wrapper = mount(CarrierScheduleQueryPanel, { props: { oceanBookingId: '2' }, global: { plugins: [ElementPlus, pinia] } })
const wrapper = mount(CarrierScheduleQueryPanel, { props: { oceanBookingId: '2', warehouseTimeline: [{ eventType: 'PDA_COMPLETE_LOADING', detail: 'complete-key-technical', operatorName: '仓库员', occurredAt: '2026-08-20T08:00:00' }] }, global: { plugins: [ElementPlus, pinia] } })
await flushPromises()
expect(wrapper.text()).toContain('本页面不会创建或模拟任务')
expect(wrapper.text()).toContain('货柜履约轨迹')
expect(wrapper.text()).toContain('完成装柜')
expect(wrapper.text()).toContain('仓内操作')
expect(wrapper.text()).toContain('确认离仓后自动开始船节点跟踪')
expect(wrapper.findAll('.tracking-prompt')).toHaveLength(1)
expect(getCarrierScheduleQuery).not.toHaveBeenCalled()
expect(getPortSchedule).not.toHaveBeenCalled()
})
@@ -103,9 +107,39 @@ describe('CarrierScheduleQueryPanel loading states', () => {
expect(wrapper.text()).toContain('最新实际节点已离开国内仓库')
expect(wrapper.text()).toContain('预计 2026-08-28 12:00')
expect(wrapper.text()).not.toContain('最新实际节点船舶已到目的港')
expect(wrapper.text()).toContain('异常事实')
expect(wrapper.text()).toContain('异常')
expect(wrapper.text()).toContain('海关扣留 · 发生')
expect(wrapper.text()).toContain('Customs hold')
expect(wrapper.text()).toContain('船运跟踪 · HEDE · hede-hold')
})
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' }
const estimated = { id: '62', providerCode: 'COSCO', providerEventId: 'estimate-62', providerNodeCode: 'ETA', standardNode: 'ARRIVED_DESTINATION_PORT' as const, nodeSequence: 60, observedAt: '2026-08-20T10:00:00', estimatedAt: '2026-08-21T10:00:00', actualAt: null, locationCode: 'USLAX', locationName: 'Los Angeles', sourceReference: 'estimate-source', rawPayloadHash: 'f'.repeat(64), createdAt: '2026-08-20T10:01:00' }
const manual = { id: '63', providerCode: 'MANUAL', providerEventId: 'manual-63', providerNodeCode: 'TRANSIT', standardNode: 'IN_TRANSIT' as const, nodeSequence: 50, observedAt: '2026-08-21T09:00:00', estimatedAt: null, actualAt: '2026-08-21T09:00:00', locationCode: null, locationName: '太平洋', sourceReference: '人工核验', rawPayloadHash: 'g'.repeat(64), createdAt: '2026-08-21T09:01:00' }
vi.mocked(getCarrierScheduleQuery).mockResolvedValue({ query: { ...queryView, query: { ...queryView.query, resultPortScheduleId: null }, expired: false }, nodes: [estimated, actual, { ...actual, id: '64' }, manual], exceptions: [{ id: '65', providerCode: 'COSCO', providerEventId: 'hold-65', providerStatus: 'Terminal hold', exceptionCode: 'TERMINAL_HOLD', active: true, observedAt: '2026-08-21T08:30:00', locationCode: 'USLAX', locationName: 'Los Angeles', sourceReference: 'hold-source', rawPayloadHash: 'h'.repeat(64), createdAt: '2026-08-21T08:31:00' }], histories: [{ id: '66', action: 'MANUAL_NODE', fromVersion: 1, toVersion: 2, reason: '人工核验', operatorId: '1', operatorName: '管理员', createdAt: '2026-08-21T09:01:00' }] })
const wrapper = mount(CarrierScheduleQueryPanel, { props: { oceanBookingId: '2', warehouseTimeline: [{ eventType: 'PDA_COMPLETE_LOADING', detail: '装柜完成', operatorName: '仓库员', occurredAt: '2026-08-20T08:00:00' }] }, global: { plugins: [ElementPlus, pinia] } })
await flushPromises()
const rows = wrapper.findAll('.fulfillment-timeline li')
expect(rows.filter((row) => row.text().includes('已装船'))).toHaveLength(1)
expect(rows[0]?.text()).toContain('已装船')
expect(rows[1]?.text()).toContain('预计 2026-08-21 10:00')
expect(rows[1]?.text()).toContain('船运节点')
expect(rows.map((row) => row.text()).join('|')).toContain('人工补录')
expect(rows.map((row) => row.text()).join('|')).toContain('码头扣留 · 发生')
expect(wrapper.text()).toContain('查询任务摘要、异常与变更历史')
expect(wrapper.text()).toContain('人工补录标准节点')
})
it('hides the manual action without the frozen permission while retaining fulfillment facts', async () => {
useAuthStore().permissions = []
vi.mocked(listCarrierScheduleQueries).mockResolvedValue({ records: [{ ...queryView, query: { ...queryView.query, resultPortScheduleId: null }, expired: false }], page: 1, size: 200, total: 1, pages: 1 })
vi.mocked(getCarrierScheduleQuery).mockResolvedValue({ query: { ...queryView, query: { ...queryView.query, resultPortScheduleId: null }, expired: false }, nodes: [], exceptions: [], histories: [] })
const wrapper = mount(CarrierScheduleQueryPanel, { props: { oceanBookingId: '2', warehouseTimeline: [{ eventType: 'PDA_BATCH', detail: '拣货结果已提交', operatorName: '仓库员', occurredAt: '2026-08-20T07:00:00' }] }, global: { plugins: [ElementPlus, pinia] } })
await flushPromises()
expect(wrapper.text()).toContain('提交拣货结果')
expect(wrapper.findAll('button').some((button) => button.text().includes('人工补录节点'))).toBe(false)
})
it('keeps the form context and dialog-scoped idempotency key when a conflict is retried', async () => {