feat(admin): add phase3 service center pages

This commit is contained in:
2026-08-11 11:26:31 +08:00
parent 9b994040b8
commit c8eb2d75cd
23 changed files with 260 additions and 3 deletions
@@ -0,0 +1,6 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { http } from '@/shared/api/http'
import { createPortSchedule, listPortSchedules, updatePortSchedule } from './platformPortSchedules'
vi.mock('@/shared/api/http', async () => { const actual = await vi.importActual<typeof import('@/shared/api/http')>('@/shared/api/http'); return { ...actual, http: { get: vi.fn(), post: vi.fn(), put: vi.fn() } } })
const data = { customerId: '1', oceanBookingId: '2', sourceType: 'MANUAL', sourceName: '美森', sourceRef: 'web', vesselName: 'MATSON', voyageNo: 'V1', departurePortCode: 'CNSHA', destinationPortCode: 'USLAX', observedAt: '2026-08-11T10:00:00', expiresAt: '2026-08-14T10:00:00' }
describe('platformPortSchedules', () => { beforeEach(() => vi.clearAllMocks()); it('normalizes long ids in pages', async () => { vi.mocked(http.get).mockResolvedValue({ code: '200', data: { records: [{ schedule: { ...data, id: 9, scheduleNo: 'PS1', customerId: 1, oceanBookingId: 2, version: '3' }, expired: false }], page: 1, size: 20, total: 1, pages: 1 } }); const result = await listPortSchedules({ page: 1, size: 20 }); expect(result.records[0]?.schedule).toMatchObject({ id: '9', customerId: '1', oceanBookingId: '2', version: 3 }) }); it('uses idempotency and expectedVersion for writes', async () => { vi.mocked(http.post).mockResolvedValue({ code: '200', data: { portSchedule: { schedule: { ...data, id: 9, scheduleNo: 'PS1', version: 0 }, expired: false }, histories: [] } }); vi.mocked(http.put).mockResolvedValue({ code: '200', data: { portSchedule: { schedule: { ...data, id: 9, scheduleNo: 'PS1', version: 2 }, expired: false }, histories: [] } }); await createPortSchedule(data); await updatePortSchedule('9', { expectedVersion: 1, data }); expect(vi.mocked(http.post).mock.calls[0]?.[2]).toMatchObject({ headers: { 'Idempotency-Key': expect.any(String) } }); expect(vi.mocked(http.put).mock.calls[0]?.[1]).toMatchObject({ expectedVersion: 1 }) }) })
@@ -0,0 +1,24 @@
import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
import type { PortScheduleData, PortScheduleDetail, PortSchedulePage, PortScheduleQuery, PortScheduleResource, PortScheduleSaveRequest, PortScheduleView } from '../types'
const basePath = '/platform/v1/port-schedules'
const ids = ['id', 'customerId', 'oceanBookingId', 'containerBookingId'] as const
function resource(raw: PortScheduleResource): PortScheduleResource {
const value = { ...raw } as PortScheduleResource & Record<string, unknown>
for (const key of ids) if (value[key] != null) value[key] = String(value[key])
value.version = Number(value.version ?? 0)
return value
}
function view(raw: PortScheduleView): PortScheduleView { return { ...raw, schedule: resource(raw.schedule) } }
function detail(raw: PortScheduleDetail): PortScheduleDetail {
return { ...raw, portSchedule: view(raw.portSchedule), histories: (raw.histories || []).map((item) => ({ ...item, id: String(item.id) })) }
}
function idem() { return `port-schedule-${Date.now()}-${Math.random().toString(36).slice(2)}` }
export async function listPortSchedules(query: PortScheduleQuery) {
const response = await http.get<ApiEnvelope<PortSchedulePage>, ApiEnvelope<PortSchedulePage>>(basePath, { params: query })
const data = unwrapApiResponse(response)
return { ...data, records: (data.records || []).map(view), page: Number(data.page), size: Number(data.size), total: Number(data.total), pages: Number(data.pages) }
}
export async function getPortSchedule(id: string) { const response = await http.get<ApiEnvelope<PortScheduleDetail>, ApiEnvelope<PortScheduleDetail>>(`${basePath}/${encodeURIComponent(id)}`); return detail(unwrapApiResponse(response)) }
export async function createPortSchedule(data: PortScheduleData) { const response = await http.post<ApiEnvelope<PortScheduleDetail>, ApiEnvelope<PortScheduleDetail>>(basePath, { expectedVersion: 0, data }, { headers: { 'Idempotency-Key': idem() } }); return detail(unwrapApiResponse(response)) }
export async function updatePortSchedule(id: string, request: PortScheduleSaveRequest) { const response = await http.put<ApiEnvelope<PortScheduleDetail>, ApiEnvelope<PortScheduleDetail>>(`${basePath}/${encodeURIComponent(id)}`, request, { headers: { 'Idempotency-Key': idem() } }); return detail(unwrapApiResponse(response)) }