接入订单列表V4继续处理入口

This commit is contained in:
andy
2026-07-20 11:15:19 +07:00
parent dfafd2c095
commit d7b24cbef7
8 changed files with 245 additions and 13 deletions

View File

@@ -391,6 +391,11 @@ describe('reservationService real API mode', () => {
display_name: 'GRP-001',
open_task_count: 2,
next_processable_task_id: '10002',
next_v4_order_task_id: '9001',
next_v4_action_card_id: '9101',
next_v4_action_type: 'REVIEW',
next_v4_action_status: 'REVIEW_REQUIRED',
v4_open_order_task_count: 1,
created_at: '2026-07-08T03:00:00Z',
updated_at: '2026-07-08T03:10:00Z',
},
@@ -415,6 +420,10 @@ describe('reservationService real API mode', () => {
)
expect(result.items[0]?.display_order_key).toBe('GRP-001')
expect(result.items[0]?.next_processable_task_id).toBe('10002')
expect(result.items[0]?.next_v4_order_task_id).toBe('9001')
expect(result.items[0]?.next_v4_action_type).toBe('REVIEW')
expect(result.items[0]?.next_v4_action_status).toBe('REVIEW_REQUIRED')
expect(result.items[0]?.v4_open_order_task_count).toBe(1)
})
it('passes order status and business-key filters to the backend', async () => {

View File

@@ -11,6 +11,7 @@ import ReservationSourceMessageConversationView from '@/views/reservation/Reserv
import ReservationTaskListView from '@/views/reservation/ReservationTaskListView.vue'
import { useAuthStore } from '@/stores/authStore'
import type {
ReservationOrderListResult,
ReservationV4OrderTaskListResult,
ReservationV4WorkbenchListResult,
SourceMessageConversationResult,
@@ -38,7 +39,8 @@ function createOrderListResult(
page_size: 20,
total: 1,
},
) {
orderOverrides: Partial<ReservationOrderListResult['items'][number]> = {},
): ReservationOrderListResult {
return {
items: [
{
@@ -54,6 +56,7 @@ function createOrderListResult(
next_processable_task_id: '10002',
created_at: '2026-07-08T03:00:00Z',
updated_at: '2026-07-08T03:10:00Z',
...orderOverrides,
},
],
page,
@@ -455,7 +458,8 @@ async function mountWithPlugins(component: object, initialPath = '/', stubs: Rec
plugins: [pinia, i18n, router],
stubs: {
RouterLink: {
template: '<a><slot /></a>',
props: ['to'],
template: '<a :href="to"><slot /></a>',
},
...stubs,
},
@@ -512,6 +516,83 @@ describe('reservation P0 views', () => {
expect(wrapper.text()).not.toContain('接口待接入')
})
it('routes continue action to the V4 order task when the backend returns next_v4_order_task_id', async () => {
vi.mocked(service.fetchReservationOrders).mockResolvedValue(createOrderListResult('GRP-001', {
page_num: 1,
page_size: 20,
total: 1,
}, {
next_v4_order_task_id: '9001',
next_v4_action_card_id: '9101',
next_v4_action_type: 'CONFIRM',
next_v4_action_status: 'PENDING_CONFIRM',
v4_open_order_task_count: 1,
}))
const wrapper = await mountWithPlugins(ReservationOrderListView)
await vi.dynamicImportSettled()
expect(wrapper.find('a[href="/reservation/orders/20001"]').text()).toContain('查看详情')
expect(wrapper.find('a[href="/reservation/order-tasks/9001"]').text()).toContain('继续处理')
expect(wrapper.find('a[href="/reservation/tasks/10002"]').exists()).toBe(false)
expect(wrapper.text()).toContain('待确认')
})
it('shows review state for V4 review entrypoints in the order list', async () => {
vi.mocked(service.fetchReservationOrders).mockResolvedValue(createOrderListResult('GRP-001', {
page_num: 1,
page_size: 20,
total: 1,
}, {
next_v4_order_task_id: '9002',
next_v4_action_card_id: '9102',
next_v4_action_type: 'REVIEW',
next_v4_action_status: 'REVIEW_REQUIRED',
v4_open_order_task_count: 1,
}))
const wrapper = await mountWithPlugins(ReservationOrderListView)
await vi.dynamicImportSettled()
expect(wrapper.find('a[href="/reservation/order-tasks/9002"]').text()).toContain('继续处理')
expect(wrapper.text()).toContain('待复核')
})
it('falls back to the legacy task detail route when there is no V4 order task entrypoint', async () => {
vi.mocked(service.fetchReservationOrders).mockResolvedValue(createOrderListResult())
const wrapper = await mountWithPlugins(ReservationOrderListView)
await vi.dynamicImportSettled()
expect(wrapper.find('a[href="/reservation/orders/20001"]').text()).toContain('查看详情')
expect(wrapper.find('a[href="/reservation/tasks/10002"]').text()).toContain('继续处理')
expect(wrapper.find('a[href="/reservation/order-tasks/9001"]').exists()).toBe(false)
})
it('shows no open task state when neither V4 nor legacy entrypoint exists', async () => {
vi.mocked(service.fetchReservationOrders).mockResolvedValue(createOrderListResult('GRP-001', {
page_num: 1,
page_size: 20,
total: 1,
}, {
open_task_count: 0,
next_processable_task_id: null,
next_v4_order_task_id: null,
next_v4_action_card_id: null,
next_v4_action_type: 'NONE',
next_v4_action_status: null,
v4_open_order_task_count: 0,
}))
const wrapper = await mountWithPlugins(ReservationOrderListView)
await vi.dynamicImportSettled()
expect(wrapper.find('a[href="/reservation/orders/20001"]').text()).toContain('查看详情')
expect(wrapper.find('a[href^="/reservation/tasks/"]').exists()).toBe(false)
expect(wrapper.find('a[href^="/reservation/order-tasks/"]').exists()).toBe(false)
expect(wrapper.text()).toContain('无待处理任务')
})
it('filters order list with order status and business-key fields', async () => {
vi.mocked(service.fetchReservationOrders).mockResolvedValue(createOrderListResult())

View File

@@ -58,6 +58,9 @@ export interface ReservationOrderListFilters {
page_size?: number
}
export type ReservationV4OrderListActionType = 'CONFIRM' | 'REVIEW' | 'NONE'
export type ReservationV4OrderListActionStatus = 'PENDING_CONFIRM' | 'REVIEW_REQUIRED'
export interface ReservationOrderListItem {
order_id: string
hotel_id: string
@@ -69,6 +72,11 @@ export interface ReservationOrderListItem {
display_name: string
open_task_count: number | null
next_processable_task_id: string | null
next_v4_order_task_id?: string | null
next_v4_action_card_id?: string | null
next_v4_action_type?: ReservationV4OrderListActionType | null
next_v4_action_status?: ReservationV4OrderListActionStatus | null
v4_open_order_task_count?: number | null
created_at: string | null
updated_at: string | null
}

View File

@@ -132,14 +132,20 @@
{{ t('orderList.viewDetail') }}
</RouterLink>
<RouterLink
v-if="order.next_processable_task_id"
v-if="resolveContinueTarget(order)"
class="row-action"
:to="`/reservation/tasks/${order.next_processable_task_id}`"
:to="resolveContinueTarget(order) ?? '/'"
>
{{ t('orderList.continueTask') }}
</RouterLink>
<span
v-else
v-if="formatContinueActionLabel(order)"
class="row-action-tag"
>
{{ formatContinueActionLabel(order) }}
</span>
<span
v-if="!resolveContinueTarget(order)"
class="row-action row-action--muted"
>
{{ t('orderList.noOpenTask') }}
@@ -302,6 +308,29 @@ function formatOrderKey(order: ReservationOrderListItem): string {
order.order_id
)
}
function resolveContinueTarget(order: ReservationOrderListItem): string | null {
if (order.next_v4_order_task_id) {
return `/reservation/order-tasks/${order.next_v4_order_task_id}`
}
if (order.next_processable_task_id) {
return `/reservation/tasks/${order.next_processable_task_id}`
}
return null
}
function formatContinueActionLabel(order: ReservationOrderListItem): string {
if (!order.next_v4_order_task_id) {
return ''
}
if (order.next_v4_action_type === 'REVIEW' || order.next_v4_action_status === 'REVIEW_REQUIRED') {
return t('status.REVIEW_REQUIRED')
}
if (order.next_v4_action_type === 'CONFIRM' || order.next_v4_action_status === 'PENDING_CONFIRM') {
return t('status.PENDING_CONFIRM')
}
return ''
}
</script>
<style scoped>
@@ -374,6 +403,15 @@ function formatOrderKey(order: ReservationOrderListItem): string {
cursor: default;
}
.row-action-tag {
border-radius: 999px;
background: var(--th-color-blue-100);
color: var(--th-color-blue-600);
font-size: 12px;
font-weight: 800;
padding: 2px 8px;
}
.empty-state {
min-height: 220px;
display: grid;