This commit is contained in:
2026-08-17 16:45:46 +08:00
parent 4a9438315c
commit d95c90bd1a
48 changed files with 1217 additions and 374 deletions
@@ -1,6 +1,6 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { http } from '@/shared/api/http'
import { createTruckAppointment, deleteTruckAppointmentAttachment, getTruckAppointment, listTruckAppointmentAttachments, listTruckAppointmentHistory, listTruckAppointments, performTruckAppointmentAction, referenceTruckAppointmentAttachment, uploadTruckAppointmentAttachment } from './platformTrucking'
import { createTruckAppointment, deleteTruckAppointmentAttachment, getTruckAppointment, listTruckAppointmentAttachments, listTruckAppointmentHistory, listTruckAppointments, performTruckAppointmentAction, referenceTruckAppointmentAttachment, rescheduleTruckAppointment, uploadTruckAppointmentAttachment } from './platformTrucking'
vi.mock('@/shared/api/http', () => ({ http: { get: vi.fn(), post: vi.fn(), put: vi.fn(), patch: vi.fn(), delete: vi.fn() }, unwrapApiResponse: (response: { data: unknown }) => response.data }))
@@ -20,7 +20,16 @@ describe('truck appointment API', () => {
await expect(getTruckAppointment('9001')).resolves.toMatchObject({ id: '9001' })
await expect(createTruckAppointment({ plannedLoadingAt: '2026-08-10T10:00:00', expectedVersion: 0 })).resolves.toMatchObject({ id: '9001' })
await expect(performTruckAppointmentAction('9001', 'DISPATCH', 2)).resolves.toMatchObject({ status: 'DISPATCHED' })
expect(http.post).toHaveBeenCalledWith('/platform/v1/truck-appointments/9001/dispatch', { expectedVersion: 2, reason: null }, { headers: {} })
expect(http.post).toHaveBeenCalledWith('/platform/v1/truck-appointments/9001/dispatch', { expectedVersion: 2, reason: null }, { headers: { 'Idempotency-Key': expect.stringContaining('truck-appointment-dispatch-9001-') } })
})
it('sends the required idempotency header for rescheduling', async () => {
vi.mocked(http.post).mockResolvedValue({ data: { id: '9001', status: 'RESCHEDULED' } })
await expect(rescheduleTruckAppointment('9001', '2026-08-20T10:00:00', 2, '调整时间')).resolves.toMatchObject({ status: 'RESCHEDULED' })
expect(http.post).toHaveBeenCalledWith('/platform/v1/truck-appointments/9001/reschedule', { expectedVersion: 2, reason: '调整时间' }, {
params: { plannedLoadingAt: '2026-08-20T10:00:00' },
headers: { 'Idempotency-Key': expect.stringContaining('truck-appointment-reschedule-9001-') },
})
})
it('uses scoped attachment and immutable history contracts', async () => {
+10 -2
View File
@@ -9,6 +9,10 @@ import type {
} from '../types'
const basePath = '/platform/v1/truck-appointments'
function operationId(action: string, id: string | number) {
return `truck-appointment-${action.toLowerCase()}-${id}-${Date.now()}-${Math.random().toString(36).slice(2)}`
}
function dtoPayload(payload: SaveTruckAppointmentRequest) {
return Object.fromEntries(Object.entries(payload).map(([key, value]) => [key, value === '' ? null : value])) as SaveTruckAppointmentRequest
}
@@ -103,13 +107,17 @@ export async function performTruckAppointmentAction(
idempotencyKey?: string,
requestHash?: string,
) {
const operationKey = idempotencyKey || operationId(action, id)
const response = await http.post<ApiEnvelope<TruckAppointmentResource>, ApiEnvelope<TruckAppointmentResource>>(`${basePath}/${id}/${action.toLowerCase()}`, { expectedVersion, reason: reason || null }, {
headers: { ...(idempotencyKey ? { 'Idempotency-Key': idempotencyKey } : {}), ...(requestHash ? { 'X-Request-Hash': requestHash } : {}) },
headers: { 'Idempotency-Key': operationKey, ...(requestHash ? { 'X-Request-Hash': requestHash } : {}) },
})
return unwrapApiResponse(response)
}
export async function rescheduleTruckAppointment(id: string | number, plannedLoadingAt: string, expectedVersion: number, reason?: string) {
const response = await http.post<ApiEnvelope<TruckAppointmentResource>, ApiEnvelope<TruckAppointmentResource>>(`${basePath}/${id}/reschedule`, { expectedVersion, reason: reason || null }, { params: { plannedLoadingAt } })
const response = await http.post<ApiEnvelope<TruckAppointmentResource>, ApiEnvelope<TruckAppointmentResource>>(`${basePath}/${id}/reschedule`, { expectedVersion, reason: reason || null }, {
params: { plannedLoadingAt },
headers: { 'Idempotency-Key': operationId('RESCHEDULE', id) },
})
return unwrapApiResponse(response)
}