接入M002V4订单任务多卡前端
This commit is contained in:
1 parent
13364e7edf
commit
cae169cefd
19 files changed
+4084
-343
No files matched your search
@@ -32,82 +32,43 @@
|
||||
>
|
||||
</label>
|
||||
<label>
|
||||
<span>{{ t('taskList.taskType') }}</span>
|
||||
<span>{{ t('taskList.itemType') }}</span>
|
||||
<select
|
||||
v-model="filters.task_type"
|
||||
name="task_type"
|
||||
@change="resetToFirstPage"
|
||||
v-model="filters.item_type"
|
||||
name="item_type"
|
||||
@change="changeItemType"
|
||||
>
|
||||
<option value="">{{ t('common.all') }}</option>
|
||||
<option value="NEW_BOOKING">{{ t('cardType.NEW_BOOKING') }}</option>
|
||||
<option value="UPDATE_BOOKING">{{ t('cardType.UPDATE_BOOKING') }}</option>
|
||||
<option value="CANCEL_BOOKING">{{ t('cardType.CANCEL_BOOKING') }}</option>
|
||||
<option value="MANUAL_REVIEW">{{ t('cardType.MANUAL_REVIEW') }}</option>
|
||||
<option value="SOURCE_MESSAGE_ONLY">{{ t('cardType.SOURCE_MESSAGE_ONLY') }}</option>
|
||||
<option value="ORDER_TASK">{{ t('taskList.itemTypeOrderTask') }}</option>
|
||||
<option value="SOURCE_NOTIFICATION">{{ t('taskList.itemTypeSourceNotification') }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span>{{ t('taskList.taskSubtype') }}</span>
|
||||
<span>{{ t('taskList.orderTaskStatus') }}</span>
|
||||
<select
|
||||
v-model="filters.task_subtype"
|
||||
name="task_subtype"
|
||||
v-model="filters.order_task_status"
|
||||
name="order_task_status"
|
||||
:disabled="!isOrderTaskMode"
|
||||
@change="resetToFirstPage"
|
||||
>
|
||||
<option value="">{{ t('common.all') }}</option>
|
||||
<option value="NEW_BOOKING">{{ t('taskSubtype.NEW_BOOKING') }}</option>
|
||||
<option value="UPDATE_BOOKING">{{ t('taskSubtype.UPDATE_BOOKING') }}</option>
|
||||
<option value="CANCEL_BOOKING">{{ t('taskSubtype.CANCEL_BOOKING') }}</option>
|
||||
<option value="RATE_CHANGE">{{ t('taskSubtype.RATE_CHANGE') }}</option>
|
||||
<option value="UPDATE_STAY_DATES">{{ t('taskSubtype.UPDATE_STAY_DATES') }}</option>
|
||||
<option value="UPDATE_ROOM_TYPE">{{ t('taskSubtype.UPDATE_ROOM_TYPE') }}</option>
|
||||
<option value="UPDATE_FIX_CHARGE">{{ t('taskSubtype.UPDATE_FIX_CHARGE') }}</option>
|
||||
<option value="NEW_GROUP_BLOCK">{{ t('taskSubtype.NEW_GROUP_BLOCK') }}</option>
|
||||
<option value="NEW_FIT_RESERVATION">{{ t('taskSubtype.NEW_FIT_RESERVATION') }}</option>
|
||||
<option value="MANUAL_REVIEW">{{ t('taskSubtype.MANUAL_REVIEW') }}</option>
|
||||
<option value="S10">{{ t('taskSubtype.S10') }}</option>
|
||||
<option value="S99">{{ t('taskSubtype.S99') }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span>{{ t('workbench.taskStatus') }}</span>
|
||||
<select
|
||||
v-model="filters.task_status"
|
||||
name="task_status"
|
||||
@change="resetToFirstPage"
|
||||
>
|
||||
<option value="">{{ t('common.all') }}</option>
|
||||
<option value="PENDING_CONFIRM">{{ t('status.PENDING_CONFIRM') }}</option>
|
||||
<option value="READY">{{ t('status.READY') }}</option>
|
||||
<option value="EXECUTING">{{ t('status.EXECUTING') }}</option>
|
||||
<option value="FAILED">{{ t('status.FAILED') }}</option>
|
||||
<option value="BLOCKED">{{ t('status.BLOCKED') }}</option>
|
||||
<option value="OPEN">{{ t('status.OPEN') }}</option>
|
||||
<option value="COMPLETED">{{ t('status.COMPLETED') }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span>{{ t('workbench.orderStatus') }}</span>
|
||||
<span>{{ t('taskList.cardStatus') }}</span>
|
||||
<select
|
||||
v-model="filters.order_status"
|
||||
name="order_status"
|
||||
v-model="filters.card_status"
|
||||
name="card_status"
|
||||
:disabled="!isOrderTaskMode"
|
||||
@change="resetToFirstPage"
|
||||
>
|
||||
<option value="">{{ t('common.all') }}</option>
|
||||
<option value="TEMPORARY">{{ t('status.TEMPORARY') }}</option>
|
||||
<option value="ACTIVE">{{ t('status.ACTIVE') }}</option>
|
||||
<option value="ENDED">{{ t('status.ENDED') }}</option>
|
||||
<option value="LOGIC_DELETED">{{ t('status.LOGIC_DELETED') }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span>{{ t('taskList.queueParticipation') }}</span>
|
||||
<select
|
||||
v-model="filters.queue_participation"
|
||||
name="queue_participation"
|
||||
@change="resetToFirstPage"
|
||||
>
|
||||
<option :value="undefined">{{ t('taskList.queueAll') }}</option>
|
||||
<option :value="true">{{ t('taskList.queueIncluded') }}</option>
|
||||
<option :value="false">{{ t('taskList.queueExcluded') }}</option>
|
||||
<option value="PENDING_CONFIRM">{{ t('status.PENDING_CONFIRM') }}</option>
|
||||
<option value="REVIEW_REQUIRED">{{ t('status.REVIEW_REQUIRED') }}</option>
|
||||
<option value="CONFIRMED">{{ t('status.CONFIRMED') }}</option>
|
||||
<option value="READONLY">{{ t('status.READONLY') }}</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
@@ -138,7 +99,7 @@
|
||||
{{ errorMessage }}
|
||||
</div>
|
||||
<div
|
||||
v-else-if="!tasks.length"
|
||||
v-else-if="!items.length"
|
||||
class="empty-state"
|
||||
>
|
||||
{{ t('common.noData') }}
|
||||
@@ -151,65 +112,79 @@
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ t('taskList.columns.task') }}</th>
|
||||
<th>{{ t('taskList.columns.item') }}</th>
|
||||
<th>{{ t('taskList.columns.order') }}</th>
|
||||
<th>{{ t('taskList.columns.card') }}</th>
|
||||
<th>{{ t('taskList.columns.status') }}</th>
|
||||
<th>{{ t('taskList.columns.source') }}</th>
|
||||
<th>{{ t('taskList.columns.queue') }}</th>
|
||||
<th>{{ t('taskList.columns.cards') }}</th>
|
||||
<th>{{ t('workbench.columns.operation') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="task in tasks"
|
||||
:key="task.task_id"
|
||||
v-for="item in items"
|
||||
:key="`${item.item_type}-${item.target_id}`"
|
||||
>
|
||||
<td>
|
||||
<strong class="th-code">#{{ task.task_id }}</strong>
|
||||
<small>{{ formatReservationTaskRouteSummary(t, task) }}</small>
|
||||
<strong>{{ itemKindLabel(item) }}</strong>
|
||||
<small class="th-code">#{{ item.target_id }}</small>
|
||||
</td>
|
||||
<td>
|
||||
<template v-if="isReadonlyDiagnosticTask(task)">
|
||||
<strong>{{ t('taskList.sourceOnlyOrderLabel') }}</strong>
|
||||
<small>{{ t('taskList.hiddenTechnicalOrder') }}</small>
|
||||
<template v-if="item.item_type === 'ORDER_TASK'">
|
||||
<strong>{{ item.display_order_key ?? t('taskList.orderUnresolved') }}</strong>
|
||||
<small
|
||||
v-if="item.order_id"
|
||||
class="th-code"
|
||||
>{{ item.order_id }}</small>
|
||||
</template>
|
||||
<template v-else>
|
||||
<strong>{{ task.display_order_key ?? task.temporary_order_no ?? task.order_id }}</strong>
|
||||
<small class="th-code">{{ task.order_id }}</small>
|
||||
<strong>{{ t('taskList.sourceNotificationNoOrder') }}</strong>
|
||||
<small>{{ t('taskList.sourceNotificationNoOrderHint') }}</small>
|
||||
</template>
|
||||
</td>
|
||||
<td>
|
||||
<span>{{ formatReservationTaskCardLabel(t, task) }}</span>
|
||||
<small v-if="task.result_type">{{ formatReservationResultType(t, task.result_type) }}</small>
|
||||
</td>
|
||||
<td><ReservationStatusBadge :status="task.task_status" /></td>
|
||||
<td>
|
||||
<span>{{ task.source_subject ?? t('taskList.noSourceSubject') }}</span>
|
||||
<small>{{ task.source_sender_summary ?? task.source_message_id ?? '-' }}</small>
|
||||
<small>{{ t('taskList.sourceReceivedAt') }}: {{ formatReservationDateTime(task.source_received_at) }}</small>
|
||||
<ReservationStatusBadge :status="displayStatus(item)" />
|
||||
<small v-if="item.readonly_reason_code">
|
||||
{{ formatReservationReadonlyReason(t, item.readonly_reason_code) }}
|
||||
</small>
|
||||
</td>
|
||||
<td>
|
||||
<span>{{ task.queue_sequence ?? '-' }}</span>
|
||||
<small>{{ task.can_process ? t('taskList.canProcess') : formatReservationReadonlyReason(t, task.readonly_reason_code) }}</small>
|
||||
<span>{{ sourceSubject(item) }}</span>
|
||||
<small>{{ sourceSender(item) }}</small>
|
||||
<small>{{ t('taskList.sourceReceivedAt') }}: {{ formatReservationDateTime(sourceReceivedAt(item)) }}</small>
|
||||
</td>
|
||||
<td>
|
||||
<template v-if="item.card_counts">
|
||||
<span>{{ t('taskList.cardCountsTotal', { total: item.card_counts.total_count }) }}</span>
|
||||
<small>
|
||||
{{ t('taskList.cardCountsPending', { count: item.card_counts.pending_confirm_count }) }}
|
||||
·
|
||||
{{ t('taskList.cardCountsReview', { count: item.card_counts.review_required_count }) }}
|
||||
·
|
||||
{{ t('taskList.cardCountsConfirmed', { count: item.card_counts.confirmed_count }) }}
|
||||
</small>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span>{{ item.notification_status ? formatStatusLabel(item.notification_status) : '-' }}</span>
|
||||
</template>
|
||||
</td>
|
||||
<td>
|
||||
<div class="row-actions">
|
||||
<RouterLink
|
||||
class="row-action"
|
||||
:to="`/reservation/tasks/${task.task_id}`"
|
||||
:to="detailPath(item)"
|
||||
>
|
||||
{{ t('taskList.viewTask') }}
|
||||
{{ item.item_type === 'SOURCE_NOTIFICATION' ? t('taskList.viewNotification') : t('taskList.viewTask') }}
|
||||
</RouterLink>
|
||||
<RouterLink
|
||||
v-if="!isReadonlyDiagnosticTask(task)"
|
||||
v-if="item.order_id"
|
||||
class="row-action"
|
||||
:to="`/reservation/orders/${task.order_id}`"
|
||||
:to="`/reservation/orders/${item.order_id}`"
|
||||
>
|
||||
{{ t('taskList.viewOrder') }}
|
||||
</RouterLink>
|
||||
<span
|
||||
v-if="!task.source_message_id"
|
||||
v-if="!item.source_message_id"
|
||||
class="pending-link"
|
||||
>
|
||||
{{ t('common.endpointMissing') }}
|
||||
@@ -217,7 +192,7 @@
|
||||
<RouterLink
|
||||
v-else
|
||||
class="row-action"
|
||||
:to="`/reservation/source-messages/${task.source_message_id}/conversation`"
|
||||
:to="`/reservation/source-messages/${item.source_message_id}/conversation`"
|
||||
>
|
||||
{{ t('taskList.viewConversation') }}
|
||||
</RouterLink>
|
||||
@@ -270,35 +245,53 @@ import { RouterLink } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import ReservationStatusBadge from '@/components/reservation/ReservationStatusBadge.vue'
|
||||
import { fetchReservationTaskList } from '@/services/reservationService'
|
||||
import {
|
||||
fetchReservationV4OrderTasks,
|
||||
fetchReservationV4WorkbenchItems,
|
||||
} from '@/services/reservationService'
|
||||
import { useAuthStore } from '@/stores/authStore'
|
||||
import type { ReservationPageResult, ReservationTaskListFilters, ReservationTaskListItem } from '@/types/reservation'
|
||||
import type {
|
||||
ReservationPageResult,
|
||||
ReservationV4OrderTaskListFilters,
|
||||
ReservationV4OrderTaskListItem,
|
||||
ReservationV4WorkbenchFilters,
|
||||
ReservationV4WorkbenchItem,
|
||||
ReservationV4WorkbenchItemType,
|
||||
} from '@/types/reservation'
|
||||
import {
|
||||
formatReservationReadonlyReason,
|
||||
formatReservationResultType,
|
||||
formatReservationTaskCardLabel,
|
||||
formatReservationTaskRouteSummary,
|
||||
isReservationReadonlyDiagnosticTask,
|
||||
} from '@/utils/reservationDisplay'
|
||||
import { formatReservationDateTime } from '@/utils/reservationFormat'
|
||||
|
||||
type WorkbenchItemFilter = ReservationV4WorkbenchItemType | ''
|
||||
type WorkbenchRow = ReservationV4WorkbenchItem & {
|
||||
order_id?: string | null
|
||||
}
|
||||
|
||||
const { t } = useI18n()
|
||||
const authStore = useAuthStore()
|
||||
const loading = ref(false)
|
||||
const errorMessage = ref('')
|
||||
const tasks = ref<ReservationTaskListItem[]>([])
|
||||
const items = ref<WorkbenchRow[]>([])
|
||||
const page = ref(createPage(1, 20, 0))
|
||||
const total = computed(() => page.value.total)
|
||||
const currentPage = computed(() => page.value.page_num)
|
||||
const totalPages = computed(() => Math.max(1, Math.ceil(page.value.total / Math.max(page.value.page_size, 1))))
|
||||
const isOrderTaskMode = computed(() => filters.item_type === 'ORDER_TASK')
|
||||
let taskListRequestSequence = 0
|
||||
const filters = reactive<ReservationTaskListFilters>({
|
||||
|
||||
const filters = reactive<{
|
||||
keyword: string
|
||||
item_type: WorkbenchItemFilter
|
||||
order_task_status: '' | 'OPEN' | 'COMPLETED'
|
||||
card_status: '' | 'READONLY' | 'PENDING_CONFIRM' | 'REVIEW_REQUIRED' | 'CONFIRMED'
|
||||
page_num: number
|
||||
page_size: number
|
||||
}>({
|
||||
keyword: '',
|
||||
task_type: '',
|
||||
task_subtype: '',
|
||||
task_status: '',
|
||||
order_status: '',
|
||||
queue_participation: undefined,
|
||||
item_type: '',
|
||||
order_task_status: '',
|
||||
card_status: '',
|
||||
page_num: 1,
|
||||
page_size: 20,
|
||||
})
|
||||
@@ -326,18 +319,20 @@ async function loadTasks(): Promise<void> {
|
||||
loading.value = true
|
||||
errorMessage.value = ''
|
||||
try {
|
||||
const result = await fetchReservationTaskList({ ...filters })
|
||||
const result = isOrderTaskMode.value
|
||||
? await fetchReservationV4OrderTasks(buildOrderTaskFilters())
|
||||
: await fetchReservationV4WorkbenchItems(buildWorkbenchFilters())
|
||||
if (requestSequence !== taskListRequestSequence) {
|
||||
return
|
||||
}
|
||||
tasks.value = result.items
|
||||
items.value = result.items.map((item) => 'order_task' in item ? toWorkbenchRowFromOrderTask(item) : item)
|
||||
page.value = result.page
|
||||
} catch (error) {
|
||||
if (requestSequence !== taskListRequestSequence) {
|
||||
return
|
||||
}
|
||||
tasks.value = []
|
||||
page.value = createPage(filters.page_num ?? 1, filters.page_size ?? 20, 0)
|
||||
items.value = []
|
||||
page.value = createPage(filters.page_num, filters.page_size, 0)
|
||||
errorMessage.value = error instanceof Error ? error.message : String(error)
|
||||
} finally {
|
||||
if (requestSequence === taskListRequestSequence) {
|
||||
@@ -346,17 +341,63 @@ async function loadTasks(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
function buildWorkbenchFilters(): ReservationV4WorkbenchFilters {
|
||||
return {
|
||||
keyword: filters.keyword,
|
||||
item_type: filters.item_type,
|
||||
page_num: filters.page_num,
|
||||
page_size: filters.page_size,
|
||||
}
|
||||
}
|
||||
|
||||
function buildOrderTaskFilters(): ReservationV4OrderTaskListFilters {
|
||||
return {
|
||||
keyword: filters.keyword,
|
||||
order_task_status: filters.order_task_status,
|
||||
card_status: filters.card_status,
|
||||
page_num: filters.page_num,
|
||||
page_size: filters.page_size,
|
||||
}
|
||||
}
|
||||
|
||||
function toWorkbenchRowFromOrderTask(item: ReservationV4OrderTaskListItem): WorkbenchRow {
|
||||
return {
|
||||
item_type: 'ORDER_TASK',
|
||||
target_id: item.order_task.order_task_id,
|
||||
hotel_id: item.order_task.hotel_id,
|
||||
source_message_id: item.order_task.source_message_id,
|
||||
source_message_summary: item.source_message_summary,
|
||||
source_received_at: item.order_task.source_received_at,
|
||||
display_order_key: item.order_task.display_order_key,
|
||||
card_counts: item.card_counts,
|
||||
next_action_card_id: item.next_action_card_id,
|
||||
notification_status: null,
|
||||
order_task_status: item.order_task.order_task_status,
|
||||
display_status: item.display_status,
|
||||
readonly_reason_code: item.availability.readonly_reason_code,
|
||||
created_at: item.order_task.created_at,
|
||||
updated_at: item.order_task.updated_at,
|
||||
order_id: item.order_task.order_id,
|
||||
}
|
||||
}
|
||||
|
||||
function resetFilters(): void {
|
||||
filters.keyword = ''
|
||||
filters.task_type = ''
|
||||
filters.task_subtype = ''
|
||||
filters.task_status = ''
|
||||
filters.order_status = ''
|
||||
filters.queue_participation = undefined
|
||||
filters.item_type = ''
|
||||
filters.order_task_status = ''
|
||||
filters.card_status = ''
|
||||
filters.page_num = 1
|
||||
filters.page_size = 20
|
||||
}
|
||||
|
||||
function changeItemType(): void {
|
||||
if (!isOrderTaskMode.value) {
|
||||
filters.order_task_status = ''
|
||||
filters.card_status = ''
|
||||
}
|
||||
resetToFirstPage()
|
||||
}
|
||||
|
||||
function resetToFirstPage(): void {
|
||||
filters.page_num = 1
|
||||
}
|
||||
@@ -373,8 +414,37 @@ function goToPage(page: number): void {
|
||||
filters.page_num = Math.min(Math.max(page, 1), totalPages.value)
|
||||
}
|
||||
|
||||
function isReadonlyDiagnosticTask(task: ReservationTaskListItem): boolean {
|
||||
return isReservationReadonlyDiagnosticTask(task)
|
||||
function itemKindLabel(item: WorkbenchRow): string {
|
||||
return item.item_type === 'SOURCE_NOTIFICATION'
|
||||
? t('taskList.itemTypeSourceNotification')
|
||||
: t('taskList.itemTypeOrderTask')
|
||||
}
|
||||
|
||||
function displayStatus(item: WorkbenchRow): string {
|
||||
return item.display_status ?? item.notification_status ?? item.order_task_status ?? 'OPEN'
|
||||
}
|
||||
|
||||
function formatStatusLabel(status: string): string {
|
||||
return t(`status.${status}`, status)
|
||||
}
|
||||
|
||||
function detailPath(item: WorkbenchRow): string {
|
||||
if (item.item_type === 'SOURCE_NOTIFICATION') {
|
||||
return `/reservation/source-notifications/${item.target_id}`
|
||||
}
|
||||
return `/reservation/order-tasks/${item.target_id}`
|
||||
}
|
||||
|
||||
function sourceSubject(item: WorkbenchRow): string {
|
||||
return item.source_message_summary?.subject ?? t('taskList.noSourceSubject')
|
||||
}
|
||||
|
||||
function sourceSender(item: WorkbenchRow): string {
|
||||
return item.source_message_summary?.sender_summary ?? item.source_message_id ?? '-'
|
||||
}
|
||||
|
||||
function sourceReceivedAt(item: WorkbenchRow): string | null {
|
||||
return item.source_received_at ?? item.source_message_summary?.received_at ?? null
|
||||
}
|
||||
|
||||
function createPage(pageNum: number, pageSize: number, total: number): ReservationPageResult {
|
||||
@@ -413,7 +483,7 @@ function createPage(pageNum: number, pageSize: number, total: number): Reservati
|
||||
|
||||
.filter-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 14px;
|
||||
padding: 18px 20px 0;
|
||||
}
|
||||
@@ -446,13 +516,6 @@ function createPage(pageNum: number, pageSize: number, total: number): Reservati
|
||||
opacity: 0.72;
|
||||
}
|
||||
|
||||
.filter-hint {
|
||||
color: var(--th-color-warning);
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.text-button,
|
||||
.row-action {
|
||||
border: 0;
|
||||
|
||||
Reference in new issue
Block a user