提交代码
This commit is contained in:
@@ -29,7 +29,7 @@
|
||||
:class="{ 'trace-field--readonly': !isEditable(field) }"
|
||||
>
|
||||
<span class="trace-field__label">
|
||||
{{ field.display_name }}
|
||||
{{ traceFieldLabel(field) }}
|
||||
<sup v-if="field.required">*</sup>
|
||||
</span>
|
||||
|
||||
@@ -387,6 +387,23 @@ function fieldPointer(field: ReservationV4TaskCardFieldResult): string {
|
||||
return field.field_pointer || fieldPathToPointer(field.field_path)
|
||||
}
|
||||
|
||||
function traceFieldLabel(field: ReservationV4TaskCardFieldResult): string {
|
||||
const pointer = fieldPointer(field)
|
||||
if (pointer.endsWith('/text')) {
|
||||
return t('taskV4.trace.textLabel')
|
||||
}
|
||||
if (pointer.endsWith('/department_code')) {
|
||||
return t('taskV4.trace.department')
|
||||
}
|
||||
if (pointer.endsWith('/target_room_type_code')) {
|
||||
return t('taskV4.trace.targetRoomType')
|
||||
}
|
||||
if (pointer.endsWith('/extra_bed_room_count')) {
|
||||
return t('taskV4.trace.extraBedRoomCount')
|
||||
}
|
||||
return field.display_name || fieldKey(field)
|
||||
}
|
||||
|
||||
function lookupItemLabel(item: ReservationV4CatalogLookupItem): string {
|
||||
if (item.display_name && item.display_name !== item.code) {
|
||||
return `${item.code} - ${item.display_name}`
|
||||
|
||||
@@ -628,6 +628,10 @@ export default {
|
||||
cardConfirmed: 'Card confirmed and detail refreshed.',
|
||||
reviewResolved: 'Review submitted and detail refreshed.',
|
||||
validationFailed: 'Fix current-card field errors first.',
|
||||
fieldValidation: {
|
||||
required: '{field} is required',
|
||||
positiveInteger: '{field} must be an integer greater than 0',
|
||||
},
|
||||
fieldLabels: {
|
||||
basicInformation: {
|
||||
accountCode: 'Account Code',
|
||||
@@ -692,6 +696,10 @@ export default {
|
||||
generalTitle: 'General note',
|
||||
extraBedTitle: 'Extra bed note',
|
||||
action: 'Fixed action',
|
||||
textLabel: 'Follow-up note',
|
||||
department: 'Department',
|
||||
targetRoomType: 'Target room type',
|
||||
extraBedRoomCount: 'Extra bed room count',
|
||||
},
|
||||
lookup: {
|
||||
loading: 'Loading options',
|
||||
|
||||
@@ -628,6 +628,10 @@ export default {
|
||||
cardConfirmed: 'ยืนยันการ์ดแล้วและรีเฟรชรายละเอียดแล้ว',
|
||||
reviewResolved: 'ส่งผลตรวจสอบแล้วและรีเฟรชรายละเอียดแล้ว',
|
||||
validationFailed: 'โปรดแก้ไขข้อมูลในการ์ดปัจจุบันก่อน',
|
||||
fieldValidation: {
|
||||
required: 'จำเป็นต้องกรอก {field}',
|
||||
positiveInteger: '{field} ต้องเป็นจำนวนเต็มมากกว่า 0',
|
||||
},
|
||||
fieldLabels: {
|
||||
basicInformation: {
|
||||
accountCode: 'รหัสบัญชี',
|
||||
@@ -692,6 +696,10 @@ export default {
|
||||
generalTitle: 'หมายเหตุทั่วไป',
|
||||
extraBedTitle: 'รายการเตียงเสริม',
|
||||
action: 'การดำเนินการคงที่',
|
||||
textLabel: 'รายละเอียดติดตาม',
|
||||
department: 'แผนกที่รับผิดชอบ',
|
||||
targetRoomType: 'ประเภทห้องเป้าหมาย',
|
||||
extraBedRoomCount: 'จำนวนห้องที่เพิ่มเตียง',
|
||||
},
|
||||
lookup: {
|
||||
loading: 'กำลังโหลดตัวเลือก',
|
||||
|
||||
@@ -627,6 +627,10 @@ export default {
|
||||
cardConfirmed: '卡片已确认,详情已刷新。',
|
||||
reviewResolved: '复核已提交,详情已刷新。',
|
||||
validationFailed: '请先修正当前卡片字段。',
|
||||
fieldValidation: {
|
||||
required: '{field}为必填项',
|
||||
positiveInteger: '{field}必须是大于 0 的整数',
|
||||
},
|
||||
fieldLabels: {
|
||||
basicInformation: {
|
||||
accountCode: '客户代码',
|
||||
@@ -691,6 +695,10 @@ export default {
|
||||
generalTitle: '普通事项',
|
||||
extraBedTitle: '加床事项',
|
||||
action: '固定动作',
|
||||
textLabel: '跟进内容',
|
||||
department: '处理部门',
|
||||
targetRoomType: '目标房型',
|
||||
extraBedRoomCount: '加床房间数',
|
||||
},
|
||||
lookup: {
|
||||
loading: '选项加载中',
|
||||
|
||||
@@ -662,6 +662,10 @@ describe('reservation V4 pages', () => {
|
||||
.findAll('.task-card-section')
|
||||
.find((section) => section.find('[data-testid="trace-card"]').exists())
|
||||
expect(traceSection).toBeTruthy()
|
||||
expect(traceSection!.text()).toContain(zhCN.taskV4.trace.textLabel)
|
||||
expect(traceSection!.text()).toContain(zhCN.taskV4.trace.department)
|
||||
expect(traceSection!.text()).not.toContain('Trace 内容')
|
||||
expect(traceSection!.text()).not.toContain('Department Code')
|
||||
expect((traceSection!.find('textarea[name="/trace_items/0/text"]').element as HTMLTextAreaElement).value)
|
||||
.toBe('Late arrival note')
|
||||
expect(traceSection!.text()).not.toContain('CONTENT_SHOULD_NOT_RENDER')
|
||||
@@ -716,6 +720,12 @@ describe('reservation V4 pages', () => {
|
||||
.find((section) => section.find('[data-testid="trace-card"]').exists())
|
||||
expect(traceSection).toBeTruthy()
|
||||
expect(traceSection!.text()).toContain('SET EXTRA BED')
|
||||
expect(traceSection!.text()).toContain(zhCN.taskV4.trace.targetRoomType)
|
||||
expect(traceSection!.text()).toContain(zhCN.taskV4.trace.extraBedRoomCount)
|
||||
expect(traceSection!.text()).toContain(zhCN.taskV4.trace.department)
|
||||
expect(traceSection!.text()).not.toContain('Target Room Type Code')
|
||||
expect(traceSection!.text()).not.toContain('Extra Bed Room Count')
|
||||
expect(traceSection!.text()).not.toContain('Department Code')
|
||||
expect(traceSection!.find('select[name="/trace_items/0/target_room_type_code"]').text()).toContain('RM2')
|
||||
const roomCountInput = traceSection!.find('input[name="/trace_items/0/extra_bed_room_count"]')
|
||||
expect(roomCountInput.attributes('type')).toBe('number')
|
||||
@@ -729,7 +739,10 @@ describe('reservation V4 pages', () => {
|
||||
await flushPromises()
|
||||
|
||||
expect(service.confirmReservationV4OrderTaskCard).not.toHaveBeenCalled()
|
||||
expect(traceSection!.text()).toContain('Extra Bed Room Count must be a positive integer')
|
||||
expect(traceSection!.text()).toContain(
|
||||
zhCN.taskV4.fieldValidation.positiveInteger.replace('{field}', zhCN.taskV4.trace.extraBedRoomCount),
|
||||
)
|
||||
expect(traceSection!.text()).not.toContain('Extra Bed Room Count must be a positive integer')
|
||||
|
||||
await roomCountInput.setValue('2')
|
||||
await traceSection!.find('button.primary-button').trigger('click')
|
||||
@@ -2045,7 +2058,7 @@ function useTraceBusinessCard(
|
||||
fields: itemType === 'EXTRA_BED'
|
||||
? [
|
||||
createField('/trace_items/0/target_room_type_code', {
|
||||
display_name: 'Target Room Type',
|
||||
display_name: 'Target Room Type Code',
|
||||
value: 'RM2',
|
||||
options_source: 'RESERVATION_V4_ROOM_TYPE_CATALOG',
|
||||
control_type: 'SELECT',
|
||||
@@ -2070,7 +2083,7 @@ function useTraceBusinessCard(
|
||||
]
|
||||
: [
|
||||
createField('/trace_items/0/text', {
|
||||
display_name: 'Trace Text',
|
||||
display_name: 'Trace 内容',
|
||||
value: 'Late arrival note',
|
||||
control_type: 'TEXTAREA',
|
||||
edit_scope: isReviewRequired ? 'REVIEW' : 'CONFIRM',
|
||||
@@ -2099,7 +2112,7 @@ function createTraceDepartmentField(
|
||||
validationErrors: string[] = [],
|
||||
): ReservationV4TaskCardResult['fields'][number] {
|
||||
return createField('/trace_items/0/department_code', {
|
||||
display_name: 'Department',
|
||||
display_name: 'Department Code',
|
||||
value: 'FO',
|
||||
control_type: 'SELECT',
|
||||
options_source: 'reservation_v4_trace_department_fixed',
|
||||
|
||||
@@ -30,6 +30,15 @@ const hiddenDisplayValue = Symbol('reservation-v4-hidden-display-value')
|
||||
|
||||
export type ReservationV4FieldErrorMap = Record<string, string>
|
||||
export type ReservationV4LookupKind = 'ACCOUNT' | 'ROOM_TYPE' | 'RATE_CODE'
|
||||
export type ReservationV4FieldValidationErrorType = 'required' | 'positiveInteger'
|
||||
export type ReservationV4FieldValidationMessageContext = {
|
||||
type: ReservationV4FieldValidationErrorType
|
||||
field: ReservationV4TaskCardFieldResult
|
||||
fieldKey: string
|
||||
}
|
||||
export type ReservationV4FieldValidationOptions = {
|
||||
formatMessage?: (context: ReservationV4FieldValidationMessageContext) => string
|
||||
}
|
||||
|
||||
export function reservationV4FieldKey(field: ReservationV4TaskCardFieldResult): string {
|
||||
return field.field_pointer || field.field_path
|
||||
@@ -81,21 +90,42 @@ export function buildReservationV4ReviewOverrides(
|
||||
export function validateReservationV4Fields(
|
||||
fields: ReservationV4TaskCardFieldResult[],
|
||||
values: ReservationRecord,
|
||||
options: ReservationV4FieldValidationOptions = {},
|
||||
): ReservationV4FieldErrorMap {
|
||||
return fields.filter(isReservationV4EditableField).reduce<ReservationV4FieldErrorMap>((errors, field) => {
|
||||
const key = reservationV4FieldKey(field)
|
||||
const value = readV4FieldValue(field, values)
|
||||
if (field.required && isEmptyV4Value(value)) {
|
||||
errors[key] = `${field.display_name} is required`
|
||||
errors[key] = formatValidationMessage('required', field, key, options)
|
||||
return errors
|
||||
}
|
||||
if (isTracePositiveIntegerField(field) && !isEmptyV4Value(value) && !isPositiveIntegerValue(value)) {
|
||||
errors[key] = `${field.display_name} must be a positive integer`
|
||||
errors[key] = formatValidationMessage('positiveInteger', field, key, options)
|
||||
}
|
||||
return errors
|
||||
}, {})
|
||||
}
|
||||
|
||||
function formatValidationMessage(
|
||||
type: ReservationV4FieldValidationErrorType,
|
||||
field: ReservationV4TaskCardFieldResult,
|
||||
fieldKey: string,
|
||||
options: ReservationV4FieldValidationOptions,
|
||||
): string {
|
||||
const formattedMessage = options.formatMessage?.({
|
||||
type,
|
||||
field,
|
||||
fieldKey,
|
||||
})
|
||||
if (formattedMessage?.trim()) {
|
||||
return formattedMessage
|
||||
}
|
||||
if (type === 'positiveInteger') {
|
||||
return `${field.display_name} must be a positive integer`
|
||||
}
|
||||
return `${field.display_name} is required`
|
||||
}
|
||||
|
||||
export function isReservationV4ConfirmWritableField(field: ReservationV4TaskCardFieldResult): boolean {
|
||||
if (!isReservationV4EditableField(field)) {
|
||||
return false
|
||||
|
||||
@@ -195,6 +195,7 @@ import type {
|
||||
ReservationRecord,
|
||||
ReservationV4OrderTaskDetailResult,
|
||||
ReservationV4TaskCardResult,
|
||||
ReservationV4TaskCardFieldResult,
|
||||
} from '@/types/reservation'
|
||||
import {
|
||||
formatReservationTaskCard,
|
||||
@@ -209,7 +210,9 @@ import {
|
||||
isReservationV4ReviewWritableField,
|
||||
isReservationV4TraceSafeField,
|
||||
mapReservationV4BackendDetailsToFields,
|
||||
fieldPathToPointer,
|
||||
reservationV4FieldKey,
|
||||
type ReservationV4FieldValidationMessageContext,
|
||||
validateReservationV4Fields,
|
||||
} from '@/utils/reservationV4FieldRules'
|
||||
|
||||
@@ -405,7 +408,11 @@ function canReviewCard(card: ReservationV4TaskCardResult): boolean {
|
||||
async function submitConfirm(card: ReservationV4TaskCardResult): Promise<void> {
|
||||
const submissionFields = submissionFieldsForCard(card)
|
||||
const writableFields = submissionFields.filter(isReservationV4ConfirmWritableField)
|
||||
const localErrors = validateReservationV4Fields(writableFields, cardValues.value[card.card_id] ?? {})
|
||||
const localErrors = validateReservationV4Fields(
|
||||
writableFields,
|
||||
cardValues.value[card.card_id] ?? {},
|
||||
{ formatMessage: (context) => formatLocalFieldValidationMessage(card, context) },
|
||||
)
|
||||
cardFieldErrors.value = {
|
||||
...cardFieldErrors.value,
|
||||
[card.card_id]: localErrors,
|
||||
@@ -458,7 +465,11 @@ async function submitReview(card: ReservationV4TaskCardResult): Promise<void> {
|
||||
|
||||
const submissionFields = submissionFieldsForCard(card)
|
||||
const writableFields = submissionFields.filter(isReservationV4ReviewWritableField)
|
||||
const localErrors = validateReservationV4Fields(writableFields, cardValues.value[card.card_id] ?? {})
|
||||
const localErrors = validateReservationV4Fields(
|
||||
writableFields,
|
||||
cardValues.value[card.card_id] ?? {},
|
||||
{ formatMessage: (context) => formatLocalFieldValidationMessage(card, context) },
|
||||
)
|
||||
cardFieldErrors.value = {
|
||||
...cardFieldErrors.value,
|
||||
[card.card_id]: localErrors,
|
||||
@@ -610,6 +621,39 @@ function cardTitle(card: ReservationV4TaskCardResult): string {
|
||||
return formatReservationTaskCard(t, card.card_type)
|
||||
}
|
||||
|
||||
function formatLocalFieldValidationMessage(
|
||||
card: ReservationV4TaskCardResult,
|
||||
context: ReservationV4FieldValidationMessageContext,
|
||||
): string {
|
||||
const fieldLabel = localFieldLabel(card, context.field)
|
||||
if (context.type === 'positiveInteger') {
|
||||
return t('taskV4.fieldValidation.positiveInteger', { field: fieldLabel })
|
||||
}
|
||||
return t('taskV4.fieldValidation.required', { field: fieldLabel })
|
||||
}
|
||||
|
||||
function localFieldLabel(
|
||||
card: ReservationV4TaskCardResult,
|
||||
field: ReservationV4TaskCardFieldResult,
|
||||
): string {
|
||||
const pointer = field.field_pointer || fieldPathToPointer(field.field_path)
|
||||
if (isTraceCard(card)) {
|
||||
if (pointer.endsWith('/text')) {
|
||||
return t('taskV4.trace.textLabel')
|
||||
}
|
||||
if (pointer.endsWith('/department_code')) {
|
||||
return t('taskV4.trace.department')
|
||||
}
|
||||
if (pointer.endsWith('/target_room_type_code')) {
|
||||
return t('taskV4.trace.targetRoomType')
|
||||
}
|
||||
if (pointer.endsWith('/extra_bed_room_count')) {
|
||||
return t('taskV4.trace.extraBedRoomCount')
|
||||
}
|
||||
}
|
||||
return field.display_name || reservationV4FieldKey(field)
|
||||
}
|
||||
|
||||
function formatApiError(error: unknown, fallback: string): {
|
||||
message: string
|
||||
details: unknown
|
||||
|
||||
Reference in New Issue
Block a user