实现V4 Rooming List轻量事项卡

This commit is contained in:
andy
2026-07-21 17:21:01 +07:00
parent 61380a9ed8
commit 0c4f5e298d
7 changed files with 380 additions and 6 deletions

View File

@@ -0,0 +1,124 @@
<template>
<section
class="rooming-list-card"
data-testid="rooming-list-card"
>
<div class="rooming-list-card__body">
<div>
<span>{{ t('taskV4.roomingList.targetOrder') }}</span>
<strong>{{ orderLabel }}</strong>
</div>
<RouterLink
v-if="boundOrderId"
class="rooming-list-card__link"
:to="`/reservation/orders/${boundOrderId}`"
>
{{ t('task.viewOrder') }}
</RouterLink>
</div>
<p>{{ t('taskV4.roomingList.description') }}</p>
<small>{{ t('taskV4.roomingList.confirmHint') }}</small>
</section>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { RouterLink } from 'vue-router'
import { useI18n } from 'vue-i18n'
const props = withDefaults(defineProps<{
orderReference?: string | null
targetLocator?: string | null
boundOrderId?: string | null
boundOrderDisplayName?: string | null
}>(), {
orderReference: null,
targetLocator: null,
boundOrderId: null,
boundOrderDisplayName: null,
})
const { t } = useI18n()
const orderLabel = computed(() =>
readableValue(props.boundOrderDisplayName) ||
readableValue(props.orderReference) ||
readableValue(props.targetLocator) ||
t('taskV4.roomingList.unboundOrder'),
)
function readableValue(value?: string | null): string {
const trimmedValue = value?.trim() ?? ''
return trimmedValue === '-' ? '' : trimmedValue
}
</script>
<style scoped>
.rooming-list-card {
display: grid;
gap: 12px;
margin: 0 20px 18px;
border: 1px solid var(--th-color-slate-200);
border-radius: var(--th-radius-sm);
background: var(--th-color-slate-50);
padding: 14px;
}
.rooming-list-card__body {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.rooming-list-card__body div {
display: grid;
gap: 4px;
min-width: 0;
}
.rooming-list-card__body span {
color: var(--th-color-slate-500);
font-size: 12px;
font-weight: 800;
}
.rooming-list-card__body strong {
color: var(--th-color-slate-900);
font-size: 14px;
overflow-wrap: anywhere;
}
.rooming-list-card p,
.rooming-list-card small {
margin: 0;
color: var(--th-color-slate-600);
font-size: 13px;
line-height: 1.55;
}
.rooming-list-card small {
color: var(--th-color-slate-500);
font-weight: 700;
}
.rooming-list-card__link {
color: var(--th-color-blue-600);
flex: 0 0 auto;
font-size: 13px;
font-weight: 900;
text-decoration: none;
}
.rooming-list-card__link:hover {
text-decoration: underline;
}
@media (max-width: 760px) {
.rooming-list-card__body {
align-items: flex-start;
flex-direction: column;
}
}
</style>

View File

@@ -46,8 +46,15 @@
@update:model-value="emit('update:modelValue', $event)" @update:model-value="emit('update:modelValue', $event)"
/> />
<template v-else> <template v-else>
<ReservationV4RoomingListCard
v-if="isRoomingListCard"
:order-reference="orderReference"
:target-locator="targetLocator"
:bound-order-id="boundOrderId"
:bound-order-display-name="boundOrderDisplayName"
/>
<ReservationV4PaymentAttachmentPreview <ReservationV4PaymentAttachmentPreview
v-if="isPaymentCard" v-else-if="isPaymentCard"
:card="card" :card="card"
:source-message-id="sourceMessageId" :source-message-id="sourceMessageId"
/> />
@@ -65,7 +72,7 @@
</template> </template>
<details <details
v-if="!isRoomInformationCard && safePayloadRows.length" v-if="!isRoomInformationCard && !isRoomingListCard && safePayloadRows.length"
class="safe-payload" class="safe-payload"
> >
<summary> <summary>
@@ -133,7 +140,7 @@
{{ submitting ? t('taskV4.confirmingCard') : t('taskV4.confirmCard') }} {{ submitting ? t('taskV4.confirmingCard') : t('taskV4.confirmCard') }}
</button> </button>
<RouterLink <RouterLink
v-if="boundOrderId" v-if="boundOrderId && !isRoomingListCard"
class="card-order-link" class="card-order-link"
:to="`/reservation/orders/${boundOrderId}`" :to="`/reservation/orders/${boundOrderId}`"
> >
@@ -155,7 +162,7 @@
{{ submitting ? t('taskV4.confirmingCard') : t('taskV4.confirmCard') }} {{ submitting ? t('taskV4.confirmingCard') : t('taskV4.confirmCard') }}
</button> </button>
<RouterLink <RouterLink
v-if="boundOrderId" v-if="boundOrderId && !isRoomingListCard"
class="card-order-link" class="card-order-link"
:to="`/reservation/orders/${boundOrderId}`" :to="`/reservation/orders/${boundOrderId}`"
> >
@@ -173,6 +180,7 @@ import { useI18n } from 'vue-i18n'
import ReservationStatusBadge from '@/components/reservation/ReservationStatusBadge.vue' import ReservationStatusBadge from '@/components/reservation/ReservationStatusBadge.vue'
import ReservationV4PaymentAttachmentPreview from '@/components/reservation/ReservationV4PaymentAttachmentPreview.vue' import ReservationV4PaymentAttachmentPreview from '@/components/reservation/ReservationV4PaymentAttachmentPreview.vue'
import ReservationV4RoomInformationCard from '@/components/reservation/ReservationV4RoomInformationCard.vue' import ReservationV4RoomInformationCard from '@/components/reservation/ReservationV4RoomInformationCard.vue'
import ReservationV4RoomingListCard from '@/components/reservation/ReservationV4RoomingListCard.vue'
import ReservationV4TaskCardFieldRenderer from '@/components/reservation/ReservationV4TaskCardFieldRenderer.vue' import ReservationV4TaskCardFieldRenderer from '@/components/reservation/ReservationV4TaskCardFieldRenderer.vue'
import type { ReservationRecord, ReservationV4TaskCardResult } from '@/types/reservation' import type { ReservationRecord, ReservationV4TaskCardResult } from '@/types/reservation'
import { formatReservationReadonlyReason } from '@/utils/reservationDisplay' import { formatReservationReadonlyReason } from '@/utils/reservationDisplay'
@@ -196,6 +204,8 @@ const props = withDefaults(defineProps<{
hotelId?: string hotelId?: string
orderTaskId?: string | null orderTaskId?: string | null
sourceMessageId?: string | null sourceMessageId?: string | null
orderReference?: string | null
targetLocator?: string | null
actionErrors?: string[] actionErrors?: string[]
successMessage?: string successMessage?: string
reviewForm?: ReviewForm reviewForm?: ReviewForm
@@ -203,11 +213,14 @@ const props = withDefaults(defineProps<{
canConfirm: boolean canConfirm: boolean
canReview: boolean canReview: boolean
boundOrderId?: string | null boundOrderId?: string | null
boundOrderDisplayName?: string | null
}>(), { }>(), {
validationErrors: () => ({}), validationErrors: () => ({}),
hotelId: undefined, hotelId: undefined,
orderTaskId: null, orderTaskId: null,
sourceMessageId: null, sourceMessageId: null,
orderReference: null,
targetLocator: null,
actionErrors: () => [], actionErrors: () => [],
successMessage: '', successMessage: '',
reviewForm: () => ({ reviewForm: () => ({
@@ -215,6 +228,7 @@ const props = withDefaults(defineProps<{
reason: '', reason: '',
}), }),
boundOrderId: null, boundOrderId: null,
boundOrderDisplayName: null,
}) })
const emit = defineEmits<{ const emit = defineEmits<{
@@ -227,6 +241,7 @@ const emit = defineEmits<{
const { t } = useI18n() const { t } = useI18n()
const isRoomInformationCard = computed(() => props.card.card_type === 'ROOM_INFORMATION') const isRoomInformationCard = computed(() => props.card.card_type === 'ROOM_INFORMATION')
const isPaymentCard = computed(() => props.card.card_type === 'PAYMENT') const isPaymentCard = computed(() => props.card.card_type === 'PAYMENT')
const isRoomingListCard = computed(() => props.card.card_type === 'ROOMING_LIST')
const visibleFields = computed(() => const visibleFields = computed(() =>
isPaymentCard.value isPaymentCard.value
? props.card.fields.filter((field) => !isPaymentAttachmentIdField(field)) ? props.card.fields.filter((field) => !isPaymentAttachmentIdField(field))

View File

@@ -623,6 +623,13 @@ export default {
unavailableReason: 'Unavailable reason: {reason}', unavailableReason: 'Unavailable reason: {reason}',
fileMeta: '{type} · {size}', fileMeta: '{type} · {size}',
}, },
roomingList: {
title: 'Rooming List action',
targetOrder: 'Target order',
unboundOrder: 'No visible order is bound yet',
description: 'The current source message contains a Rooming List item that needs manual handling.',
confirmHint: 'This first version only confirms that the item has been handled manually. Guest details and attachment content are not displayed or processed here.',
},
lookup: { lookup: {
loading: 'Loading catalog', loading: 'Loading catalog',
empty: 'No options in the current catalog. A no-match search does not mean the catalog is uninitialized.', empty: 'No options in the current catalog. A no-match search does not mean the catalog is uninitialized.',

View File

@@ -623,6 +623,13 @@ export default {
unavailableReason: 'เหตุผลที่ใช้ไม่ได้: {reason}', unavailableReason: 'เหตุผลที่ใช้ไม่ได้: {reason}',
fileMeta: '{type} · {size}', fileMeta: '{type} · {size}',
}, },
roomingList: {
title: 'งาน Rooming List',
targetOrder: 'ออเดอร์เป้าหมาย',
unboundOrder: 'ยังไม่มีออเดอร์ที่มองเห็นได้ผูกอยู่',
description: 'ข้อความต้นทางปัจจุบันมีรายการ Rooming List ที่ต้องให้เจ้าหน้าที่จัดการ',
confirmHint: 'เวอร์ชันแรกนี้ใช้ยืนยันว่ารายการได้รับการจัดการด้วยคนแล้วเท่านั้น ไม่แสดงหรือประมวลผลรายชื่อแขกและเนื้อหาไฟล์แนบในหน้านี้',
},
lookup: { lookup: {
loading: 'กำลังโหลดแค็ตตาล็อก', loading: 'กำลังโหลดแค็ตตาล็อก',
empty: 'ไม่มีตัวเลือกในแค็ตตาล็อกปัจจุบัน หากค้นหาไม่พบไม่ได้หมายความว่าแค็ตตาล็อกยังไม่เริ่มต้น', empty: 'ไม่มีตัวเลือกในแค็ตตาล็อกปัจจุบัน หากค้นหาไม่พบไม่ได้หมายความว่าแค็ตตาล็อกยังไม่เริ่มต้น',

View File

@@ -623,6 +623,13 @@ export default {
unavailableReason: '不可用原因:{reason}', unavailableReason: '不可用原因:{reason}',
fileMeta: '{type} · {size}', fileMeta: '{type} · {size}',
}, },
roomingList: {
title: 'Rooming List / 房表事项',
targetOrder: '目标订单',
unboundOrder: '暂未绑定可见订单',
description: '当前来源消息包含需要人工处理的 Rooming List 事项,请在酒店内部流程处理后确认该卡片。',
confirmHint: '第一版仅确认该事项已由人工处理,不展示或处理名单明细和附件内容。',
},
lookup: { lookup: {
loading: '目录加载中', loading: '目录加载中',
empty: '当前目录没有可选项;如果是搜索无结果,不代表目录未初始化。', empty: '当前目录没有可选项;如果是搜索无结果,不代表目录未初始化。',

View File

@@ -386,6 +386,134 @@ describe('reservation V4 pages', () => {
expect(JSON.stringify(submittedRequest)).not.toContain('https://oss.example') expect(JSON.stringify(submittedRequest)).not.toContain('https://oss.example')
}) })
it('renders Rooming List cards as lightweight action items without rows, attachments or raw payloads', async () => {
const detail = createOrderTaskDetail()
useRoomingListBusinessCard(detail)
useBoundOrder(detail)
vi.mocked(service.fetchReservationV4OrderTaskDetail).mockResolvedValue(detail)
const wrapper = await mountWithPlugins(
ReservationV4OrderTaskDetailView,
'/reservation/order-tasks/9001',
)
await flushPromises()
const roomingListSection = wrapper
.findAll('.task-card-section')
.find((section) => section.find('[data-testid="rooming-list-card"]').exists())
expect(roomingListSection).toBeTruthy()
const roomingListCard = roomingListSection!.find('[data-testid="rooming-list-card"]')
expect(roomingListCard.exists()).toBe(true)
expect(roomingListSection!.text()).toContain(zhCN.taskV4.roomingList.title)
expect(roomingListCard.text()).toContain(zhCN.taskV4.roomingList.description)
expect(roomingListCard.text()).toContain('GRP-001')
expect(roomingListSection!.findAll('a').filter((link) => link.text() === zhCN.task.viewOrder)).toHaveLength(1)
expect(wrapper.text()).toContain(zhCN.taskV4.sourceMessageCard)
expect(wrapper.text().indexOf(zhCN.taskV4.sourceMessageCard)).toBeGreaterThan(
wrapper.text().indexOf(zhCN.taskV4.roomingList.title),
)
expect(roomingListSection!.find('details.safe-payload').exists()).toBe(false)
expect(roomingListSection!.text()).not.toContain('ROW_SHOULD_NOT_RENDER')
expect(roomingListSection!.text()).not.toContain('attachment_ids')
expect(roomingListSection!.text()).not.toContain('https://oss.example/private/rooming-list.xlsx')
expect(roomingListSection!.text()).not.toContain('AI_PAYLOAD_SHOULD_NOT_RENDER')
expect(roomingListSection!.text()).not.toContain('PMS_IMPORT_SHOULD_NOT_RENDER')
expect(roomingListSection!.text()).not.toContain('OHIP_SHOULD_NOT_RENDER')
expect(roomingListSection!.text()).not.toContain('Excel')
expect(wrapper.find('[data-testid="payment-attachment-preview"]').exists()).toBe(false)
})
it('shows an explanatory unbound order label for Rooming List cards without target order clues', async () => {
const detail = createOrderTaskDetail()
useRoomingListBusinessCard(detail)
detail.order_task.display_order_key = null
detail.order_task.order_ref = null
detail.order_task.target_locator_type = null
detail.order_task.target_locator_value = null
vi.mocked(service.fetchReservationV4OrderTaskDetail).mockResolvedValue(detail)
const wrapper = await mountWithPlugins(
ReservationV4OrderTaskDetailView,
'/reservation/order-tasks/9001',
)
await flushPromises()
const roomingListCard = wrapper.find('[data-testid="rooming-list-card"]')
expect(roomingListCard.text()).toContain(zhCN.taskV4.roomingList.unboundOrder)
})
it('confirms Rooming List cards with version only and applies the refreshed backend detail', async () => {
const detail = createOrderTaskDetail()
useRoomingListBusinessCard(detail)
const confirmedDetail = createOrderTaskDetail()
useRoomingListBusinessCard(confirmedDetail, 'CONFIRMED')
vi.mocked(service.fetchReservationV4OrderTaskDetail).mockResolvedValue(detail)
vi.mocked(service.confirmReservationV4OrderTaskCard).mockResolvedValue(confirmedDetail)
const wrapper = await mountWithPlugins(
ReservationV4OrderTaskDetailView,
'/reservation/order-tasks/9001',
)
await flushPromises()
const roomingListSection = wrapper
.findAll('.task-card-section')
.find((section) => section.find('[data-testid="rooming-list-card"]').exists())
expect(roomingListSection).toBeTruthy()
await roomingListSection!.find('button.primary-button').trigger('click')
await flushPromises()
expect(service.confirmReservationV4OrderTaskCard).toHaveBeenCalledWith('9001', 'card-rooming-list', {
version: 12,
})
const submittedRequest = vi.mocked(service.confirmReservationV4OrderTaskCard).mock.calls[0]?.[2]
expect(JSON.stringify(submittedRequest)).not.toContain('confirmed_payload')
expect(JSON.stringify(submittedRequest)).not.toContain('rows')
expect(JSON.stringify(submittedRequest)).not.toContain('attachment_ids')
expect(JSON.stringify(submittedRequest)).not.toContain('https://oss.example')
expect(wrapper.text()).toContain(zhCN.status.CONFIRMED)
})
it('resolves Rooming List review cards without submitting row or attachment overrides', async () => {
const detail = createOrderTaskDetail()
useRoomingListBusinessCard(detail, 'REVIEW_REQUIRED')
detail.order_task.order_id = 'order-2001'
detail.order_task.target_resolution_status = 'RESOLVED'
const resolvedDetail = createOrderTaskDetail()
useRoomingListBusinessCard(resolvedDetail, 'PENDING_CONFIRM')
vi.mocked(service.fetchReservationV4OrderTaskDetail).mockResolvedValue(detail)
vi.mocked(service.resolveReservationV4OrderTaskCardReview).mockResolvedValue(resolvedDetail)
const wrapper = await mountWithPlugins(
ReservationV4OrderTaskDetailView,
'/reservation/order-tasks/9001',
)
await flushPromises()
const roomingListSection = wrapper
.findAll('.task-card-section')
.find((section) => section.find('[data-testid="rooming-list-card"]').exists())
expect(roomingListSection).toBeTruthy()
expect(roomingListSection!.find('.review-box').exists()).toBe(true)
expect(roomingListSection!.find('.task-card-section__fields').exists()).toBe(false)
await roomingListSection!.find('textarea').setValue('handled rooming list')
await roomingListSection!.find('button.primary-button').trigger('click')
await flushPromises()
expect(service.confirmReservationV4OrderTaskCard).not.toHaveBeenCalled()
expect(service.resolveReservationV4OrderTaskCardReview).toHaveBeenCalledWith('9001', 'card-rooming-list', {
version: 12,
confirmed_order_id: 'order-2001',
reason: 'handled rooming list',
field_overrides: [],
})
const submittedRequest = vi.mocked(service.resolveReservationV4OrderTaskCardReview).mock.calls[0]?.[2]
expect(JSON.stringify(submittedRequest)).not.toContain('rows')
expect(JSON.stringify(submittedRequest)).not.toContain('attachment_ids')
expect(JSON.stringify(submittedRequest)).not.toContain('https://oss.example')
})
it('renders New Booking Room Information as a business form and confirms stable final values', async () => { it('renders New Booking Room Information as a business form and confirms stable final values', async () => {
const detail = createOrderTaskDetail({ const detail = createOrderTaskDetail({
businessEventType: 'NEW_BOOKING', businessEventType: 'NEW_BOOKING',
@@ -1360,6 +1488,75 @@ function usePaymentBusinessCard(detail: ReservationV4OrderTaskDetailResult): voi
] ]
} }
function useRoomingListBusinessCard(
detail: ReservationV4OrderTaskDetailResult,
cardStatus = 'PENDING_CONFIRM',
): void {
const isPendingConfirm = cardStatus === 'PENDING_CONFIRM'
const isReviewRequired = cardStatus === 'REVIEW_REQUIRED'
detail.business_cards = [
createCard('card-rooming-list', 'ROOMING_LIST', cardStatus, {
version: 12,
availability: createAvailability({
confirmable: isPendingConfirm,
reviewable: isReviewRequired,
editable: false,
read_only: !isPendingConfirm,
}),
display_payload: {
rows: [
{
guest_name: 'ROW_SHOULD_NOT_RENDER',
room_no: '1001',
},
],
attachment_ids: ['rooming-list-att-1'],
attachment_url: 'https://oss.example/private/rooming-list.xlsx',
ai_payload_json: 'AI_PAYLOAD_SHOULD_NOT_RENDER',
pms_import_plan: 'PMS_IMPORT_SHOULD_NOT_RENDER',
ohip_payload: 'OHIP_SHOULD_NOT_RENDER',
},
fields: [
createField('/rooming_list/rows/0/guest_name', {
display_name: 'Rooming list row',
value: 'ROW_SHOULD_NOT_RENDER',
editable: true,
edit_scope: 'CONFIRM',
write_target: 'CONFIRMED_PAYLOAD_JSON',
}),
createField('/rooming_list/attachment_ids', {
display_name: 'attachment_ids',
value: ['rooming-list-att-1'],
editable: true,
edit_scope: 'CONFIRM',
write_target: 'CONFIRMED_PAYLOAD_JSON',
}),
],
}),
]
detail.card_counts = {
...detail.card_counts,
pending_confirm_count: cardStatus === 'PENDING_CONFIRM' ? 2 : 1,
confirmed_count: cardStatus === 'CONFIRMED' ? 1 : 0,
}
}
function useBoundOrder(detail: ReservationV4OrderTaskDetailResult): void {
detail.bound_order = {
order_id: 'order-2001',
hotel_id: 'HOTEL-TEST',
order_status: 'ACTIVE',
temporary_order_no: null,
confirmation_number: null,
group_code: 'GRP-001',
block_code: null,
allotment_code: null,
display_name: 'GRP-001',
created_at: '2026-07-08T03:00:00Z',
updated_at: '2026-07-08T03:00:00Z',
}
}
function createPaymentConversationMedia(): SourceMessageOriginalMedia[] { function createPaymentConversationMedia(): SourceMessageOriginalMedia[] {
return [ return [
{ {

View File

@@ -89,6 +89,8 @@
:hotel-id="detail.order_task.hotel_id" :hotel-id="detail.order_task.hotel_id"
:order-task-id="detail.order_task.order_task_id" :order-task-id="detail.order_task.order_task_id"
:source-message-id="detail.order_task.source_message_id" :source-message-id="detail.order_task.source_message_id"
:order-reference="displayOrderReference"
:target-locator="targetLocator"
:action-errors="cardActionErrors[detail.basic_information_card.card_id] ?? []" :action-errors="cardActionErrors[detail.basic_information_card.card_id] ?? []"
:success-message="cardSuccessMessages[detail.basic_information_card.card_id] ?? ''" :success-message="cardSuccessMessages[detail.basic_information_card.card_id] ?? ''"
:review-form="reviewForms[detail.basic_information_card.card_id]" :review-form="reviewForms[detail.basic_information_card.card_id]"
@@ -96,6 +98,7 @@
:can-confirm="canConfirmCard(detail.basic_information_card)" :can-confirm="canConfirmCard(detail.basic_information_card)"
:can-review="canReviewCard(detail.basic_information_card)" :can-review="canReviewCard(detail.basic_information_card)"
:bound-order-id="detail.bound_order?.order_id" :bound-order-id="detail.bound_order?.order_id"
:bound-order-display-name="detail.bound_order?.display_name"
@update:model-value="setCardValues(detail.basic_information_card, $event)" @update:model-value="setCardValues(detail.basic_information_card, $event)"
@update:review-form="setReviewForm(detail.basic_information_card.card_id, $event)" @update:review-form="setReviewForm(detail.basic_information_card.card_id, $event)"
@confirm="submitConfirm(detail.basic_information_card)" @confirm="submitConfirm(detail.basic_information_card)"
@@ -132,6 +135,8 @@
:hotel-id="detail.order_task.hotel_id" :hotel-id="detail.order_task.hotel_id"
:order-task-id="detail.order_task.order_task_id" :order-task-id="detail.order_task.order_task_id"
:source-message-id="detail.order_task.source_message_id" :source-message-id="detail.order_task.source_message_id"
:order-reference="displayOrderReference"
:target-locator="targetLocator"
:action-errors="cardActionErrors[businessCard.card_id] ?? []" :action-errors="cardActionErrors[businessCard.card_id] ?? []"
:success-message="cardSuccessMessages[businessCard.card_id] ?? ''" :success-message="cardSuccessMessages[businessCard.card_id] ?? ''"
:review-form="reviewForms[businessCard.card_id]" :review-form="reviewForms[businessCard.card_id]"
@@ -139,6 +144,7 @@
:can-confirm="canConfirmCard(businessCard)" :can-confirm="canConfirmCard(businessCard)"
:can-review="canReviewCard(businessCard)" :can-review="canReviewCard(businessCard)"
:bound-order-id="detail.bound_order?.order_id" :bound-order-id="detail.bound_order?.order_id"
:bound-order-display-name="detail.bound_order?.display_name"
@update:model-value="setCardValues(businessCard, $event)" @update:model-value="setCardValues(businessCard, $event)"
@update:review-form="setReviewForm(businessCard.card_id, $event)" @update:review-form="setReviewForm(businessCard.card_id, $event)"
@confirm="submitConfirm(businessCard)" @confirm="submitConfirm(businessCard)"
@@ -459,7 +465,7 @@ function editableFieldKeys(card: ReservationV4TaskCardResult): string[] {
} }
function submissionFieldsForCard(card: ReservationV4TaskCardResult): ReservationV4TaskCardResult['fields'] { function submissionFieldsForCard(card: ReservationV4TaskCardResult): ReservationV4TaskCardResult['fields'] {
if (isPaymentCard(card)) { if (isVersionOnlyConfirmCard(card)) {
return [] return []
} }
return card.card_type === 'ROOM_INFORMATION' return card.card_type === 'ROOM_INFORMATION'
@@ -473,7 +479,7 @@ function buildConfirmRequest(
card: ReservationV4TaskCardResult, card: ReservationV4TaskCardResult,
submissionFields: ReservationV4TaskCardResult['fields'], submissionFields: ReservationV4TaskCardResult['fields'],
): ReservationV4CardConfirmRequest { ): ReservationV4CardConfirmRequest {
if (isPaymentCard(card)) { if (isVersionOnlyConfirmCard(card)) {
return { return {
version: card.version, version: card.version,
} }
@@ -488,6 +494,14 @@ function isPaymentCard(card: ReservationV4TaskCardResult): boolean {
return card.card_type === 'PAYMENT' return card.card_type === 'PAYMENT'
} }
function isRoomingListCard(card: ReservationV4TaskCardResult): boolean {
return card.card_type === 'ROOMING_LIST'
}
function isVersionOnlyConfirmCard(card: ReservationV4TaskCardResult): boolean {
return isPaymentCard(card) || isRoomingListCard(card)
}
function isGroupRoomInformationBreakfastField( function isGroupRoomInformationBreakfastField(
card: ReservationV4TaskCardResult, card: ReservationV4TaskCardResult,
field: ReservationV4TaskCardResult['fields'][number], field: ReservationV4TaskCardResult['fields'][number],
@@ -521,6 +535,9 @@ function applyCardActionError(card: ReservationV4TaskCardResult, error: unknown,
} }
function cardTitle(card: ReservationV4TaskCardResult): string { function cardTitle(card: ReservationV4TaskCardResult): string {
if (isRoomingListCard(card)) {
return t('taskV4.roomingList.title')
}
return formatReservationTaskCard(t, card.card_type) return formatReservationTaskCard(t, card.card_type)
} }