feat(appointments): add FBA creation and destination filtering

This commit is contained in:
2026-10-19 17:02:27 +08:00
parent b92e5379bc
commit 5a46198bf0
14 changed files with 382 additions and 25 deletions
@@ -18,9 +18,10 @@ const row: ScheduleEvent = { key: 'warehouse-1', type: 'warehouse', date: '2026-
export function renderSchedule() {
return mount(AppointmentCalendarView, { global: { stubs: {
AdminPage: { template: '<main><slot /></main>' },
AppointmentCalendarPanel: { name: 'AppointmentCalendarPanel', props: ['events', 'loading', 'incomplete', 'initialDate', 'initialMode'], template: '<section data-calendar />' },
AppointmentCalendarPanel: { name: 'AppointmentCalendarPanel', props: ['events', 'loading', 'incomplete', 'initialDate', 'initialMode', 'canCreate'], template: '<section data-calendar />' },
AppDialog: { name: 'AppDialog', props: ['modelValue', 'beforeClose', 'confirmDisabled', 'confirmLoading'], emits: ['confirm'], template: '<section v-if="modelValue" data-dialog><slot /><button data-save :disabled="confirmDisabled" @click="$emit(\'confirm\')">保存</button><button data-close @click="beforeClose(() => {})">关闭</button></section>' },
WorkDetailPanel: true, SourceAppointmentPanel: true, ApiErrorMeta: true,
AppointmentCreatePanel: { name: 'AppointmentCreatePanel', props: ['canWrite', 'canLeave'], emits: ['created'], template: '<section data-create-panel />' },
LoadErrorState: { props: ['title', 'message'], emits: ['retry'], template: '<section role="alert">{{ title }} {{ message }}<slot name="details" /><button data-retry @click="$emit(\'retry\')">重试</button></section>' },
} } })
}
@@ -32,6 +33,30 @@ beforeEach(() => {
vi.mocked(saveWarehouseArrival).mockResolvedValue({})
})
describe('simplified appointment entry', () => {
it('opens creation lazily with the original read and write permissions and refreshes after creation', async () => {
const wrapper = renderSchedule(); await flushPromises()
expect(wrapper.find('[data-create-panel]').exists()).toBe(false)
const calendar = wrapper.getComponent({ name: 'AppointmentCalendarPanel' })
expect(calendar.props('canCreate')).toBe(true)
calendar.vm.$emit('create'); await flushPromises()
const panel = wrapper.getComponent({ name: 'AppointmentCreatePanel' })
expect(panel.props('canWrite')).toBe(true)
vi.mocked(getCalendarSources).mockClear()
panel.vm.$emit('created'); await flushPromises()
expect(getCalendarSources).toHaveBeenCalledTimes(1)
await wrapper.get('[data-close]').trigger('click'); await flushPromises()
expect(wrapper.find('[data-create-panel]').exists()).toBe(false)
wrapper.unmount()
})
it.each(['platform:overseas-warehouse:inbound-order:fba-appointment', 'platform:overseas-warehouse:last-mile-order:list'])('does not open creation without %s', async permission => {
routing.denied.add(permission)
const wrapper = renderSchedule(); await flushPromises()
const calendar = wrapper.getComponent({ name: 'AppointmentCalendarPanel' })
expect(calendar.props('canCreate')).toBe(false)
calendar.vm.$emit('create'); await flushPromises()
expect(wrapper.find('[data-create-panel]').exists()).toBe(false)
wrapper.unmount()
})
it('keeps unsaved input when the operator declines to leave', async () => {
vi.mocked(ElMessageBox.confirm).mockRejectedValueOnce('cancel')
const wrapper = renderSchedule(); await flushPromises()
@@ -45,7 +70,7 @@ describe('simplified appointment entry', () => {
})
it('loads original sources and real trips without loading work summaries or generated work items', async () => {
const wrapper = renderSchedule(); await flushPromises()
expect(getWarehouseArrivals).toHaveBeenCalledWith({ from: '2026-09-28', to: '2026-11-09' }, ['1'], expect.any(AbortSignal))
expect(getWarehouseArrivals).toHaveBeenCalledWith({ from: '2026-09-28', to: '2026-11-02' }, ['1'], expect.any(AbortSignal))
const panel = wrapper.getComponent({ name: 'AppointmentCalendarPanel' })
expect(panel.props('events').map((event: ScheduleEvent) => [event.type, event.date])).toEqual([['arrival', '2026-10-19'], ['warehouse', '2026-10-21']])
expect(wrapper.text()).not.toMatch(/全局待办|跟进|复核/)