feat: add appointment calendar and cross-customer booking
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { http } from '@/shared/api/http'
|
||||
import { createFbaAppointment, getFbaAppointment, listInboundOrders, updateFbaAppointment } from './inboundOrders'
|
||||
import { createFbaAppointment, getFbaAppointment, listFbaAppointmentCalendar, listInboundOrders, updateFbaAppointment } from './inboundOrders'
|
||||
|
||||
vi.mock('@/shared/api/http', async (importOriginal) => ({ ...(await importOriginal<typeof import('@/shared/api/http')>()), http: { get: vi.fn(), post: vi.fn(), put: vi.fn() } }))
|
||||
|
||||
@@ -11,16 +11,32 @@ describe('inbound order overseas API', () => {
|
||||
vi.mocked(http.put).mockReset()
|
||||
})
|
||||
|
||||
it('loads a calendar range in one request and supports cancelling obsolete requests', async () => {
|
||||
vi.mocked(http.get).mockResolvedValue({ code: '200', message: 'success', data: [] })
|
||||
const controller = new AbortController()
|
||||
const query = { from: '2026-08-31', to: '2026-10-12' }
|
||||
expect(await listFbaAppointmentCalendar(query, controller.signal)).toEqual([])
|
||||
expect(http.get).toHaveBeenCalledTimes(1)
|
||||
expect(http.get).toHaveBeenCalledWith('/platform/v1/last-leg/inbound-plans/fba-appointments/calendar', {
|
||||
params: query, signal: controller.signal,
|
||||
})
|
||||
})
|
||||
|
||||
it('does not turn calendar business errors into an empty successful calendar', async () => {
|
||||
vi.mocked(http.get).mockResolvedValue({ code: '500', message: '日历查询失败', data: null })
|
||||
await expect(listFbaAppointmentCalendar({ from: '2026-09-01', to: '2026-10-01' })).rejects.toThrow()
|
||||
})
|
||||
|
||||
it('loads appointment details and updates the existing appointment without replacing its lines', async () => {
|
||||
const detail = { id: '1', customerId: '190000000000000001', appointmentNo: 'APT/1', appointmentAt: '2026-08-20T10:30:00', expectedLoadingAt: '2026-08-20T09:00:00', appointmentStatus: 'SUCCESS', selectedCount: 2, fbaWarehouseCode: 'ONT8', items: [] }
|
||||
const detail = { version: 0, id: '1', customerId: '190000000000000001', appointmentNo: 'APT/1', appointmentAt: '2026-08-20T10:30:00', expectedLoadingAt: '2026-08-20T09:00:00', appointmentStatus: 'SUCCESS', selectedCount: 2, fbaWarehouseCode: 'ONT8', items: [] }
|
||||
vi.mocked(http.get).mockResolvedValue({ code: '200', message: 'success', data: detail })
|
||||
vi.mocked(http.put).mockResolvedValue({ code: '200', message: 'success', data: detail })
|
||||
|
||||
await getFbaAppointment('190000000000000001', 'APT/1')
|
||||
await updateFbaAppointment('APT/1', { customerId: '190000000000000001', appointmentAt: detail.appointmentAt, expectedLoadingAt: detail.expectedLoadingAt, appointmentStatus: 'SUCCESS' })
|
||||
await getFbaAppointment('APT/1')
|
||||
await updateFbaAppointment('APT/1', { version: detail.version, appointmentAt: detail.appointmentAt, expectedLoadingAt: detail.expectedLoadingAt, appointmentStatus: 'SUCCESS' })
|
||||
|
||||
expect(http.get).toHaveBeenCalledWith('/platform/v1/last-leg/inbound-plans/fba-appointments/APT%2F1', { params: { customerId: '190000000000000001' } })
|
||||
expect(http.put).toHaveBeenCalledWith('/platform/v1/last-leg/inbound-plans/fba-appointments/APT%2F1', expect.objectContaining({ customerId: '190000000000000001' }))
|
||||
expect(http.get).toHaveBeenCalledWith('/platform/v1/last-leg/inbound-plans/fba-appointments/APT%2F1')
|
||||
expect(http.put).toHaveBeenCalledWith('/platform/v1/last-leg/inbound-plans/fba-appointments/APT%2F1', expect.not.objectContaining({ customerId: expect.anything() }))
|
||||
})
|
||||
|
||||
it('loads the paginated list across all customers without a customer scope', async () => {
|
||||
@@ -34,7 +50,6 @@ describe('inbound order overseas API', () => {
|
||||
it('preserves selected line ids as strings when creating an appointment', async () => {
|
||||
vi.mocked(http.post).mockResolvedValue({ code: '200', message: 'success', data: { id: '1', appointmentNo: 'APT-1', appointmentStatus: 'SUCCESS', selectedCount: 1, fbaWarehouseCode: 'ONT8' } })
|
||||
const payload = {
|
||||
customerId: '190000000000000001',
|
||||
lineIds: ['190000000000000099'],
|
||||
appointmentNo: 'APT-1',
|
||||
appointmentAt: '2026-08-20T10:30:00',
|
||||
|
||||
@@ -54,29 +54,37 @@ export interface InboundOrderSummaryDto {
|
||||
}
|
||||
|
||||
export interface BatchFbaAppointmentPayload {
|
||||
customerId: string
|
||||
lineIds: string[]
|
||||
appointmentNo: string
|
||||
appointmentAt: string
|
||||
appointmentEndAt?: string | null
|
||||
expectedLoadingAt: string
|
||||
appointmentStatus: 'PENDING' | 'SUCCESS' | 'FAILED' | 'CANCELLED'
|
||||
appointmentRemark?: string
|
||||
}
|
||||
|
||||
export interface FbaAppointmentDto {
|
||||
version: number
|
||||
id: string
|
||||
customerId: string
|
||||
appointmentNo: string
|
||||
appointmentAt: string
|
||||
appointmentEndAt?: string | null
|
||||
expectedLoadingAt: string
|
||||
appointmentStatus: string
|
||||
appointmentRemark?: string | null
|
||||
selectedCount: number
|
||||
fbaWarehouseCode: string
|
||||
items: Array<{ id: string; planId: string; lineId: string; orderId: string; inboundCode: string; fbaWarehouseCode: string }>
|
||||
items: Array<{ customerId: string; id: string; planId: string; lineId: string; orderId: string; inboundCode: string; fbaWarehouseCode: string }>
|
||||
}
|
||||
|
||||
export type UpdateFbaAppointmentPayload = Omit<BatchFbaAppointmentPayload, 'lineIds' | 'appointmentNo'>
|
||||
export type UpdateFbaAppointmentPayload = Omit<BatchFbaAppointmentPayload, 'lineIds' | 'appointmentNo'> & { version: number }
|
||||
|
||||
export async function listFbaAppointmentCalendar(query: { from: string; to: string }, signal?: AbortSignal) {
|
||||
const response = await http.get<ApiEnvelope<FbaAppointmentDto[]>, ApiEnvelope<FbaAppointmentDto[]>>(
|
||||
'/platform/v1/last-leg/inbound-plans/fba-appointments/calendar', { params: query, signal },
|
||||
)
|
||||
return unwrapApiResponse(response)
|
||||
}
|
||||
|
||||
export async function listInboundOrders(query: InboundOrderQuery) {
|
||||
const response = await http.get<ApiEnvelope<OverseasPagePayload<InboundOrderSummaryDto>>, ApiEnvelope<OverseasPagePayload<InboundOrderSummaryDto>>>(
|
||||
@@ -92,10 +100,9 @@ export async function createFbaAppointment(payload: BatchFbaAppointmentPayload)
|
||||
return unwrapApiResponse(response)
|
||||
}
|
||||
|
||||
export async function getFbaAppointment(customerId: string, appointmentNo: string) {
|
||||
export async function getFbaAppointment(appointmentNo: string) {
|
||||
const response = await http.get<ApiEnvelope<FbaAppointmentDto>, ApiEnvelope<FbaAppointmentDto>>(
|
||||
`/platform/v1/last-leg/inbound-plans/fba-appointments/${encodeURIComponent(appointmentNo)}`,
|
||||
{ params: { customerId } },
|
||||
)
|
||||
return unwrapApiResponse(response)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user