This commit is contained in:
2026-08-14 18:50:11 +08:00
parent 93e0294ec0
commit 44ee2be246
9 changed files with 196 additions and 29 deletions
@@ -1,5 +1,5 @@
import { describe, expect, it, vi } from 'vitest'
import { createOceanBooking, listOceanBookings, performOceanBookingAction, releaseOceanBooking, requestOceanBookingChange } from './platformOceanBookings'
import { createOceanBooking, listOceanBookings, listWarehouseLogisticsChannels, performOceanBookingAction, releaseOceanBooking, requestOceanBookingChange } from './platformOceanBookings'
import { http } from '@/shared/api/http'
const detailResponse = (version = 0) => ({
@@ -22,7 +22,7 @@ describe('ocean bookings api', () => {
it('uses version zero for create and extracts the id from the returned Detail', async () => {
const post = vi.spyOn(http, 'post').mockResolvedValue({ code: '200', message: 'ok', data: detailResponse() })
const created = await createOceanBooking({ expectedVersion: 0, data: { customerId: '456', customerName: '客户', channelCode: 'SEA', channelName: '海运', warehouseId: '8', warehouseName: '义乌仓', parties: [], containerRequirements: [] } })
const created = await createOceanBooking({ expectedVersion: 0, data: { customerId: '456', customerName: '客户', channelCode: 'SEA', warehouseId: '8', warehouseName: '义乌仓', parties: [], containerRequirements: [] } })
expect(post.mock.calls[0]?.[1]).toMatchObject({ expectedVersion: 0 })
expect(post.mock.calls[0]?.[2]).toMatchObject({ headers: { 'Idempotency-Key': expect.any(String) } })
expect(created.id).toBe('123')
@@ -43,4 +43,10 @@ describe('ocean bookings api', () => {
expect(post.mock.calls[2]?.[0]).toContain('/123/release')
expect(post.mock.calls[2]?.[1]).toMatchObject({ billOfLadingNo: 'BL-1', vesselName: 'VESSEL', voyageNo: 'V001', estimatedSailingAt: '2026-08-12T10:00:00', estimatedArrivalAt: '2026-09-01T10:00:00' })
})
it('loads warehouse-scoped logistics channels from the booking contract', async () => {
vi.spyOn(http, 'get').mockResolvedValue({ code: '200', message: 'ok', data: [{ channelCode: 'SEA_FCL', channelName: '海运整箱' }] })
await expect(listWarehouseLogisticsChannels('8')).resolves.toEqual([{ channelCode: 'SEA_FCL', channelName: '海运整箱' }])
expect(http.get).toHaveBeenCalledWith('/platform/v1/ocean-bookings/channels', { params: { originWarehouseId: '8' } })
})
})
@@ -2,6 +2,11 @@ import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
import type { OceanBookingActionRequest, OceanBookingChangeRequest, OceanBookingDetail, OceanBookingPage, OceanBookingQuery, OceanBookingResource, OceanBookingSaveRequest, OceanBookingStatus, OceanBookingReleaseRequest } from '../types'
const basePath = '/platform/v1/ocean-bookings'
export interface OceanLogisticsChannel {
channelCode: string
channelName: string
}
function pageNumber(value: number | string, fallback: number) { const n = Number(value); return Number.isFinite(n) ? Math.trunc(n) : fallback }
function booking(raw: OceanBookingResource): OceanBookingResource {
const item = raw as OceanBookingResource & { version?: number | string | null }
@@ -49,4 +54,8 @@ export async function performOceanBookingAction(id: string, action: Exclude<Ocea
export async function releaseOceanBooking(id: string, request: OceanBookingReleaseRequest) { const response = await http.post<ApiEnvelope<OceanBookingDetail>, ApiEnvelope<OceanBookingDetail>>(`${basePath}/${encodeURIComponent(id)}/release`, request, { headers: { 'Idempotency-Key': idempotencyKey() } }); return detail(unwrapApiResponse(response)) }
export async function requestOceanBookingChange(id: string, request: OceanBookingChangeRequest) { const response = await http.post<ApiEnvelope<OceanBookingDetail>, ApiEnvelope<OceanBookingDetail>>(`${basePath}/${encodeURIComponent(id)}/change-request`, request, { headers: { 'Idempotency-Key': idempotencyKey() } }); return detail(unwrapApiResponse(response)) }
export async function printOceanBooking(id: string, type: 'SHIPPING_INSTRUCTION' | 'BILL_CONFIRMATION') { return http.get<Blob, Blob>(`${basePath}/${encodeURIComponent(id)}/print`, { params: { type }, responseType: 'blob' }) }
export async function listWarehouseLogisticsChannels(warehouseId: string): Promise<OceanLogisticsChannel[]> {
const response = await http.get<ApiEnvelope<OceanLogisticsChannel[]>, ApiEnvelope<OceanLogisticsChannel[]>>(`${basePath}/channels`, { params: { originWarehouseId: warehouseId } })
return (unwrapApiResponse(response) || []).map((item) => ({ channelCode: String(item.channelCode), channelName: String(item.channelName) }))
}
export type { OceanBookingResource, OceanBookingStatus }