调整预订事项字段展示与基础信息编辑
This commit is contained in:
@@ -7,9 +7,8 @@
|
||||
:class="{ 'v4-field--readonly': !isEditable(field) }"
|
||||
>
|
||||
<span class="v4-field__label">
|
||||
{{ field.display_name }}
|
||||
{{ fieldDisplayName(field) }}
|
||||
<sup v-if="field.required">*</sup>
|
||||
<em v-if="!isEditable(field)">{{ t('taskV4.field.readonly') }}</em>
|
||||
</span>
|
||||
|
||||
<select
|
||||
@@ -99,11 +98,7 @@
|
||||
</span>
|
||||
|
||||
<small
|
||||
v-if="field.control_hint"
|
||||
class="v4-field__hint"
|
||||
>{{ field.control_hint }}</small>
|
||||
<small
|
||||
v-for="hint in lookupHints(field)"
|
||||
v-for="hint in visibleLookupHints(field)"
|
||||
:key="hint.key"
|
||||
class="v4-field__hint"
|
||||
:class="`v4-field__hint--${hint.tone}`"
|
||||
@@ -227,6 +222,11 @@ function fieldKey(field: ReservationV4TaskCardFieldResult): string {
|
||||
return reservationV4FieldKey(field)
|
||||
}
|
||||
|
||||
function fieldDisplayName(field: ReservationV4TaskCardFieldResult): string {
|
||||
const labelKey = fieldLabelKey(field)
|
||||
return labelKey ? t(labelKey) : field.display_name
|
||||
}
|
||||
|
||||
function dateFieldInputId(field: ReservationV4TaskCardFieldResult): string {
|
||||
return `v4-date-${fieldKey(field).replace(/[^a-zA-Z0-9_-]/g, '-')}`
|
||||
}
|
||||
@@ -402,6 +402,10 @@ function lookupHints(field: ReservationV4TaskCardFieldResult): LookupHint[] {
|
||||
return hints
|
||||
}
|
||||
|
||||
function visibleLookupHints(field: ReservationV4TaskCardFieldResult): LookupHint[] {
|
||||
return lookupHints(field).filter((hint) => hint.tone === 'error')
|
||||
}
|
||||
|
||||
function visibleFieldErrors(field: ReservationV4TaskCardFieldResult): string[] {
|
||||
return [
|
||||
...(field.validation_errors ?? []),
|
||||
@@ -645,6 +649,62 @@ function isExactLookupLoading(field: ReservationV4TaskCardFieldResult): boolean
|
||||
function exactLookupKey(kind: ReservationV4LookupKind, code: string): string {
|
||||
return `${props.hotelId ?? ''}:${kind}:${code}`
|
||||
}
|
||||
|
||||
function fieldLabelKey(field: ReservationV4TaskCardFieldResult): string | null {
|
||||
const key = normalizeFieldKey(field)
|
||||
if (key === '/basic_information/account_code') {
|
||||
return 'taskV4.fieldLabels.basicInformation.accountCode'
|
||||
}
|
||||
if (key === '/basic_information/market_code') {
|
||||
return 'taskV4.fieldLabels.basicInformation.marketCode'
|
||||
}
|
||||
if (key === '/basic_information/source_code') {
|
||||
return 'taskV4.fieldLabels.basicInformation.sourceCode'
|
||||
}
|
||||
if (key === '/room_information/final_values/group_block_name') {
|
||||
return 'taskV4.roomInformation.groupBlockName'
|
||||
}
|
||||
if (key === '/room_information/final_values/fit_name') {
|
||||
return 'taskV4.roomInformation.fitName'
|
||||
}
|
||||
if (key === '/room_information/final_values/arrival_date') {
|
||||
return 'taskV4.roomInformation.arrivalDate'
|
||||
}
|
||||
if (key === '/room_information/final_values/departure_date') {
|
||||
return 'taskV4.roomInformation.departureDate'
|
||||
}
|
||||
if (key === '/room_information/final_values/nights') {
|
||||
return 'taskV4.roomInformation.nights'
|
||||
}
|
||||
if (key === '/room_information/final_values/rate_code') {
|
||||
return 'taskV4.roomInformation.rateCode'
|
||||
}
|
||||
if (key === '/room_information/final_values/breakfast_included') {
|
||||
return 'taskV4.roomInformation.breakfastIncluded'
|
||||
}
|
||||
if (key === '/room_information/final_values/group_booking_status') {
|
||||
return 'taskV4.roomInformation.groupBookingStatus'
|
||||
}
|
||||
if (key === '/room_information/final_values/block_id') {
|
||||
return 'taskV4.roomInformation.blockId'
|
||||
}
|
||||
if (key === '/room_information/final_values/confirmation_number') {
|
||||
return 'taskV4.roomInformation.confirmationNumber'
|
||||
}
|
||||
if (/^\/room_information\/final_values\/room_items\/\d+\/room_type_code$/.test(key)) {
|
||||
return 'taskV4.roomInformation.roomTypeCode'
|
||||
}
|
||||
if (/^\/room_information\/final_values\/room_items\/\d+\/room_count$/.test(key)) {
|
||||
return 'taskV4.roomInformation.roomCount'
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function normalizeFieldKey(field: ReservationV4TaskCardFieldResult): string {
|
||||
return reservationV4FieldKey(field)
|
||||
.replace(/\./g, '/')
|
||||
.replace(/^([^/])/, '/$1')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -672,15 +732,6 @@ function exactLookupKey(kind: ReservationV4LookupKind, code: string): string {
|
||||
color: var(--th-color-danger);
|
||||
}
|
||||
|
||||
.v4-field__label em {
|
||||
border-radius: 999px;
|
||||
background: var(--th-color-slate-100);
|
||||
color: var(--th-color-slate-500);
|
||||
font-size: 11px;
|
||||
font-style: normal;
|
||||
padding: 2px 6px;
|
||||
}
|
||||
|
||||
.v4-field__control,
|
||||
.v4-field__static {
|
||||
min-height: 38px;
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
<span class="trace-field__label">
|
||||
{{ field.display_name }}
|
||||
<sup v-if="field.required">*</sup>
|
||||
<em v-if="!isEditable(field)">{{ t('taskV4.field.readonly') }}</em>
|
||||
</span>
|
||||
|
||||
<select
|
||||
@@ -94,14 +93,9 @@
|
||||
</span>
|
||||
|
||||
<small
|
||||
v-if="field.control_hint"
|
||||
class="trace-field__hint"
|
||||
>{{ field.control_hint }}</small>
|
||||
<small
|
||||
v-for="hint in lookupHints(field)"
|
||||
:key="hint"
|
||||
class="trace-field__hint"
|
||||
>{{ hint }}</small>
|
||||
v-if="lookupError(field)"
|
||||
class="trace-field__hint trace-field__hint--error"
|
||||
>{{ lookupError(field) }}</small>
|
||||
<small
|
||||
v-for="error in visibleFieldErrors(field)"
|
||||
:key="error"
|
||||
@@ -298,17 +292,14 @@ function selectDisabled(field: ReservationV4TaskCardFieldResult): boolean {
|
||||
(roomTypeLookup.loading || Boolean(roomTypeLookup.error))
|
||||
}
|
||||
|
||||
function lookupHints(field: ReservationV4TaskCardFieldResult): string[] {
|
||||
function lookupError(field: ReservationV4TaskCardFieldResult): string {
|
||||
if (reservationV4LookupKindForOptionsSource(field.options_source) !== 'ROOM_TYPE') {
|
||||
return []
|
||||
}
|
||||
if (roomTypeLookup.loading) {
|
||||
return [t('taskV4.lookup.loading')]
|
||||
return ''
|
||||
}
|
||||
if (roomTypeLookup.error) {
|
||||
return [roomTypeLookup.error]
|
||||
return roomTypeLookup.error
|
||||
}
|
||||
return []
|
||||
return ''
|
||||
}
|
||||
|
||||
function visibleFieldErrors(field: ReservationV4TaskCardFieldResult): string[] {
|
||||
@@ -488,15 +479,6 @@ function isRecord(value: unknown): value is ReservationRecord {
|
||||
color: var(--th-color-danger);
|
||||
}
|
||||
|
||||
.trace-field__label em {
|
||||
border-radius: 999px;
|
||||
background: var(--th-color-slate-100);
|
||||
color: var(--th-color-slate-500);
|
||||
font-size: 11px;
|
||||
font-style: normal;
|
||||
padding: 2px 6px;
|
||||
}
|
||||
|
||||
.trace-field__control,
|
||||
.trace-field__static {
|
||||
min-height: 38px;
|
||||
@@ -529,6 +511,11 @@ function isRecord(value: unknown): value is ReservationRecord {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.trace-field__hint--error {
|
||||
color: var(--th-color-danger);
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.trace-field__error {
|
||||
color: var(--th-color-danger);
|
||||
font-size: 12px;
|
||||
|
||||
@@ -608,6 +608,13 @@ export default {
|
||||
cardConfirmed: 'Card confirmed and detail refreshed.',
|
||||
reviewResolved: 'Review submitted and detail refreshed.',
|
||||
validationFailed: 'Fix current-card field errors first.',
|
||||
fieldLabels: {
|
||||
basicInformation: {
|
||||
accountCode: 'Account Code',
|
||||
marketCode: 'Market Code',
|
||||
sourceCode: 'Source Code',
|
||||
},
|
||||
},
|
||||
roomInformation: {
|
||||
noDisplayModel: 'Room and date information is temporarily unavailable. Refresh later or contact an administrator.',
|
||||
eventType: {
|
||||
|
||||
@@ -608,6 +608,13 @@ export default {
|
||||
cardConfirmed: 'ยืนยันการ์ดแล้วและรีเฟรชรายละเอียดแล้ว',
|
||||
reviewResolved: 'ส่งผลตรวจสอบแล้วและรีเฟรชรายละเอียดแล้ว',
|
||||
validationFailed: 'โปรดแก้ไขข้อมูลในการ์ดปัจจุบันก่อน',
|
||||
fieldLabels: {
|
||||
basicInformation: {
|
||||
accountCode: 'รหัสบัญชี',
|
||||
marketCode: 'รหัสตลาด',
|
||||
sourceCode: 'รหัสแหล่งที่มา',
|
||||
},
|
||||
},
|
||||
roomInformation: {
|
||||
noDisplayModel: 'ข้อมูลห้องและวันที่ยังไม่พร้อมใช้งาน โปรดลองรีเฟรชภายหลังหรือติดต่อผู้ดูแลระบบ',
|
||||
eventType: {
|
||||
@@ -626,10 +633,10 @@ export default {
|
||||
arrivalDate: 'วันเข้าพัก',
|
||||
departureDate: 'วันออก',
|
||||
nights: 'จำนวนคืน',
|
||||
rateCode: 'Rate Code',
|
||||
rateCode: 'รหัสราคา',
|
||||
breakfastIncluded: 'รวมอาหารเช้า',
|
||||
groupBookingStatus: 'สถานะกรุ๊ป',
|
||||
blockId: 'Block ID',
|
||||
blockId: 'รหัสบล็อก',
|
||||
confirmationNumber: 'เลขยืนยัน',
|
||||
roomItems: 'รายละเอียดห้อง',
|
||||
roomTypeCode: 'รหัสประเภทห้อง',
|
||||
|
||||
@@ -608,6 +608,13 @@ export default {
|
||||
cardConfirmed: '卡片已确认,详情已刷新。',
|
||||
reviewResolved: '复核已提交,详情已刷新。',
|
||||
validationFailed: '请先修正当前卡片字段。',
|
||||
fieldLabels: {
|
||||
basicInformation: {
|
||||
accountCode: '客户代码',
|
||||
marketCode: '市场代码',
|
||||
sourceCode: '来源代码',
|
||||
},
|
||||
},
|
||||
roomInformation: {
|
||||
noDisplayModel: '房型与日期信息暂不可用,请稍后刷新或联系管理员。',
|
||||
eventType: {
|
||||
@@ -626,10 +633,10 @@ export default {
|
||||
arrivalDate: '入住日期',
|
||||
departureDate: '离店日期',
|
||||
nights: '晚数',
|
||||
rateCode: 'Rate Code',
|
||||
rateCode: '价格代码',
|
||||
breakfastIncluded: '含早',
|
||||
groupBookingStatus: '团队预订状态',
|
||||
blockId: 'Block ID',
|
||||
blockId: '团队预留编号',
|
||||
confirmationNumber: '确认号',
|
||||
roomItems: '房型明细',
|
||||
roomTypeCode: '房型代码',
|
||||
|
||||
@@ -115,8 +115,21 @@ describe('reservation V4 pages', () => {
|
||||
|
||||
await wrapper.find('select').setValue('ACC-LIVE')
|
||||
await flushPromises()
|
||||
expect(wrapper.text()).toContain('LEISURE')
|
||||
expect(wrapper.text()).toContain('TRAVEL_AGENT')
|
||||
const basicCard = wrapper.findAll('.task-card-section')[0]!
|
||||
expect(basicCard.find('.v4-field__label em').exists()).toBe(false)
|
||||
expect(basicCard.text()).not.toContain('Market 由 Account Code 派生')
|
||||
expect(basicCard.text()).not.toContain('Source 由 Account Code 派生')
|
||||
expect(basicCard.text()).not.toContain('选择后会带出市场')
|
||||
const marketField = findFieldByLabel(basicCard, '市场代码')
|
||||
const sourceField = findFieldByLabel(basicCard, '来源代码')
|
||||
expect(marketField?.find('input[name="/basic_information/market_code"]').exists()).toBe(true)
|
||||
expect(sourceField?.find('input[name="/basic_information/source_code"]').exists()).toBe(true)
|
||||
expect((marketField?.find('input[name="/basic_information/market_code"]').element as HTMLInputElement).value)
|
||||
.toBe('LEISURE')
|
||||
expect((sourceField?.find('input[name="/basic_information/source_code"]').element as HTMLInputElement).value)
|
||||
.toBe('TRAVEL_AGENT')
|
||||
await marketField?.find('input[name="/basic_information/market_code"]').setValue('MICE')
|
||||
await sourceField?.find('input[name="/basic_information/source_code"]').setValue('DIRECT')
|
||||
await wrapper.findAll('button').find((button) => button.text().includes('确认卡片'))?.trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
@@ -125,6 +138,8 @@ describe('reservation V4 pages', () => {
|
||||
confirmed_payload: {
|
||||
basic_information: {
|
||||
account_code: 'ACC-LIVE',
|
||||
market_code: 'MICE',
|
||||
source_code: 'DIRECT',
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -830,6 +845,9 @@ describe('reservation V4 pages', () => {
|
||||
expect(roomCard.text()).not.toContain('target_order')
|
||||
expect(roomCard.text()).not.toContain('Adult')
|
||||
expect(roomCard.text()).not.toContain('Legacy Room Type')
|
||||
expect(roomCard.text()).not.toContain('Group Block Name')
|
||||
expect(roomCard.text()).not.toContain('Rate Code')
|
||||
expect(roomCard.text()).not.toContain('Group Booking Status')
|
||||
expect(roomCard.find('input[name="/room_information/final_values/adult"]').exists()).toBe(false)
|
||||
expect(roomCard.find('input[name="/room_information/final_values/nights"]').exists()).toBe(false)
|
||||
expect(roomCard.find('input[name="/room_information/final_values/target_order/locator_value"]').exists()).toBe(false)
|
||||
@@ -1194,7 +1212,7 @@ describe('reservation V4 pages', () => {
|
||||
)
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.text()).toContain(zhCN.taskV4.lookup.empty)
|
||||
expect(wrapper.text()).not.toContain(zhCN.taskV4.lookup.empty)
|
||||
expect(wrapper.text()).not.toContain(zhCN.taskV4.lookup.stale)
|
||||
expect(wrapper.text()).not.toContain('PMS sync is stale')
|
||||
expect(wrapper.text()).not.toContain('PMS_SYNC')
|
||||
@@ -1307,9 +1325,6 @@ describe('reservation V4 pages', () => {
|
||||
page_num: 1,
|
||||
page_size: 20,
|
||||
})
|
||||
expect(wrapper.text()).toContain('MICE')
|
||||
expect(wrapper.text()).toContain('DIRECT')
|
||||
|
||||
await wrapper.findAll('button').find((button) => button.text().includes('确认卡片'))?.trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
@@ -1318,6 +1333,8 @@ describe('reservation V4 pages', () => {
|
||||
confirmed_payload: {
|
||||
basic_information: {
|
||||
account_code: 'ACC-101',
|
||||
market_code: 'LEISURE',
|
||||
source_code: 'TRAVEL_AGENT',
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -1347,7 +1364,7 @@ describe('reservation V4 pages', () => {
|
||||
)
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.text()).toContain(zhCN.taskV4.lookup.empty)
|
||||
expect(wrapper.text()).not.toContain(zhCN.taskV4.lookup.empty)
|
||||
expect(wrapper.text()).not.toContain('固定种子初始化')
|
||||
expect(wrapper.text()).not.toContain('真实 PMS')
|
||||
expect(wrapper.text()).not.toContain('PMS_SYNC')
|
||||
@@ -1582,6 +1599,8 @@ function createOrderTaskDetail(options: {
|
||||
businessFields?: ReservationV4TaskCardResult['fields']
|
||||
sourceDisplayPayload?: ReservationV4TaskCardResult['display_payload']
|
||||
basicAccountValue?: string
|
||||
basicMarketCodeValue?: string
|
||||
basicSourceCodeValue?: string
|
||||
basicAccountRequired?: boolean
|
||||
} = {}): ReservationV4OrderTaskDetailResult {
|
||||
const sourceCard = createCard('card-source', 'SOURCE_MESSAGE_DISPLAY', 'READONLY', {
|
||||
@@ -1609,6 +1628,26 @@ function createOrderTaskDetail(options: {
|
||||
options_source: 'RESERVATION_V4_ACCOUNT_CATALOG',
|
||||
control_type: 'SELECT',
|
||||
}),
|
||||
createField('/basic_information/market_code', {
|
||||
display_name: 'Market Code',
|
||||
value: options.basicMarketCodeValue ?? 'LEISURE',
|
||||
editable: false,
|
||||
raw_readonly: true,
|
||||
control_type: 'READONLY',
|
||||
edit_scope: 'NEVER',
|
||||
write_target: 'NONE',
|
||||
control_hint: 'Market 由 Account Code 派生,前端只读展示。',
|
||||
}),
|
||||
createField('/basic_information/source_code', {
|
||||
display_name: 'Source Code',
|
||||
value: options.basicSourceCodeValue ?? 'TRAVEL_AGENT',
|
||||
editable: false,
|
||||
raw_readonly: true,
|
||||
control_type: 'READONLY',
|
||||
edit_scope: 'NEVER',
|
||||
write_target: 'NONE',
|
||||
control_hint: 'Source 由 Account Code 派生,前端只读展示。',
|
||||
}),
|
||||
createField('/basic_information/read_only_marker', {
|
||||
display_name: 'Read only marker',
|
||||
value: 'VISIBLE',
|
||||
|
||||
@@ -283,11 +283,12 @@ async function loadDetail(): Promise<void> {
|
||||
}
|
||||
|
||||
function applyDetail(result: ReservationV4OrderTaskDetailResult): void {
|
||||
detail.value = result
|
||||
const normalizedResult = normalizeReservationV4Detail(result)
|
||||
detail.value = normalizedResult
|
||||
const cards = [
|
||||
result.source_message_card,
|
||||
result.basic_information_card,
|
||||
...result.business_cards,
|
||||
normalizedResult.source_message_card,
|
||||
normalizedResult.basic_information_card,
|
||||
...normalizedResult.business_cards,
|
||||
].filter((card): card is ReservationV4TaskCardResult => Boolean(card))
|
||||
cardValues.value = cards.reduce<Record<string, ReservationRecord>>((values, card) => {
|
||||
values[card.card_id] = buildReservationV4InitialFieldValues(card.fields)
|
||||
@@ -298,13 +299,60 @@ function applyDetail(result: ReservationV4OrderTaskDetailResult): void {
|
||||
cardSuccessMessages.value = {}
|
||||
reviewForms.value = cards.reduce<Record<string, { confirmed_order_id: string; reason: string }>>((forms, card) => {
|
||||
forms[card.card_id] = {
|
||||
confirmed_order_id: result.order_task.order_id ?? '',
|
||||
confirmed_order_id: normalizedResult.order_task.order_id ?? '',
|
||||
reason: '',
|
||||
}
|
||||
return forms
|
||||
}, {})
|
||||
}
|
||||
|
||||
function normalizeReservationV4Detail(result: ReservationV4OrderTaskDetailResult): ReservationV4OrderTaskDetailResult {
|
||||
return {
|
||||
...result,
|
||||
basic_information_card: result.basic_information_card
|
||||
? normalizeBasicInformationCard(result.basic_information_card)
|
||||
: result.basic_information_card,
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeBasicInformationCard(card: ReservationV4TaskCardResult): ReservationV4TaskCardResult {
|
||||
return {
|
||||
...card,
|
||||
fields: card.fields.map((field) =>
|
||||
isEditableBasicMarketSourceField(field)
|
||||
? normalizeEditableBasicMarketSourceField(card, field)
|
||||
: field,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeEditableBasicMarketSourceField(
|
||||
card: ReservationV4TaskCardResult,
|
||||
field: ReservationV4TaskCardResult['fields'][number],
|
||||
): ReservationV4TaskCardResult['fields'][number] {
|
||||
const reviewMode = card.card_status === 'REVIEW_REQUIRED'
|
||||
return {
|
||||
...field,
|
||||
editable: true,
|
||||
raw_readonly: false,
|
||||
control_type: 'TEXT',
|
||||
edit_scope: reviewMode ? 'REVIEW' : 'CONFIRM',
|
||||
write_target: reviewMode ? 'REVIEW_RESOLUTION_FIELD_OVERRIDES' : 'CONFIRMED_PAYLOAD_JSON',
|
||||
control_hint: null,
|
||||
}
|
||||
}
|
||||
|
||||
function isEditableBasicMarketSourceField(field: ReservationV4TaskCardResult['fields'][number]): boolean {
|
||||
const key = normalizeV4FieldKey(field)
|
||||
return key === '/basic_information/market_code' || key === '/basic_information/source_code'
|
||||
}
|
||||
|
||||
function normalizeV4FieldKey(field: ReservationV4TaskCardResult['fields'][number]): string {
|
||||
return reservationV4FieldKey(field)
|
||||
.replace(/\./g, '/')
|
||||
.replace(/^([^/])/, '/$1')
|
||||
}
|
||||
|
||||
function setCardValues(card: ReservationV4TaskCardResult, values: ReservationRecord): void {
|
||||
cardValues.value = {
|
||||
...cardValues.value,
|
||||
|
||||
Reference in New Issue
Block a user