清理手工开票页面冗余文案
This commit is contained in:
@@ -295,6 +295,7 @@ export default {
|
|||||||
total: 'Total',
|
total: 'Total',
|
||||||
addCharge: 'Add row',
|
addCharge: 'Add row',
|
||||||
removeCharge: 'Remove',
|
removeCharge: 'Remove',
|
||||||
|
chargeLineLabel: 'Row {index}',
|
||||||
removeChargeLineAria: 'Remove row {index}',
|
removeChargeLineAria: 'Remove row {index}',
|
||||||
maxChargesReached: 'V1 supports up to 10 charge lines.',
|
maxChargesReached: 'V1 supports up to 10 charge lines.',
|
||||||
previewDisclaimer: 'The frontend only previews amounts. Backend totals are authoritative.',
|
previewDisclaimer: 'The frontend only previews amounts. Backend totals are authoritative.',
|
||||||
|
|||||||
@@ -295,6 +295,7 @@ export default {
|
|||||||
total: 'Total',
|
total: 'Total',
|
||||||
addCharge: 'เพิ่มแถว',
|
addCharge: 'เพิ่มแถว',
|
||||||
removeCharge: 'ลบ',
|
removeCharge: 'ลบ',
|
||||||
|
chargeLineLabel: 'แถวที่ {index}',
|
||||||
removeChargeLineAria: 'ลบแถวที่ {index}',
|
removeChargeLineAria: 'ลบแถวที่ {index}',
|
||||||
maxChargesReached: 'V1 รองรับได้สูงสุด 10 รายการค่าใช้จ่าย',
|
maxChargesReached: 'V1 รองรับได้สูงสุด 10 รายการค่าใช้จ่าย',
|
||||||
previewDisclaimer: 'ยอดนี้เป็นเพียงตัวอย่าง ยอดสุดท้ายให้ยึด totals จากหลังบ้าน',
|
previewDisclaimer: 'ยอดนี้เป็นเพียงตัวอย่าง ยอดสุดท้ายให้ยึด totals จากหลังบ้าน',
|
||||||
|
|||||||
@@ -295,6 +295,7 @@ export default {
|
|||||||
total: 'Total',
|
total: 'Total',
|
||||||
addCharge: '新增一行',
|
addCharge: '新增一行',
|
||||||
removeCharge: '删除',
|
removeCharge: '删除',
|
||||||
|
chargeLineLabel: '第 {index} 行',
|
||||||
removeChargeLineAria: '删除第 {index} 行',
|
removeChargeLineAria: '删除第 {index} 行',
|
||||||
maxChargesReached: '第一版最多支持 10 条费用明细。',
|
maxChargesReached: '第一版最多支持 10 条费用明细。',
|
||||||
previewDisclaimer: '前端仅做金额预览,最终金额以后端返回 totals 为准。',
|
previewDisclaimer: '前端仅做金额预览,最终金额以后端返回 totals 为准。',
|
||||||
|
|||||||
@@ -258,16 +258,31 @@ describe('ReservationManualInvoiceView', () => {
|
|||||||
expect((wrapper.find('[data-testid="booking-room-rate-note"]').element as HTMLInputElement).placeholder).toBe(
|
expect((wrapper.find('[data-testid="booking-room-rate-note"]').element as HTMLInputElement).placeholder).toBe(
|
||||||
'e.g. including BF',
|
'e.g. including BF',
|
||||||
)
|
)
|
||||||
|
expect(wrapper.find('.control-caption').exists()).toBe(false)
|
||||||
expect(wrapper.find('[data-testid="manual-invoice-add-charge"]').text()).toContain('Add row')
|
expect(wrapper.find('[data-testid="manual-invoice-add-charge"]').text()).toContain('Add row')
|
||||||
expect(wrapper.find('[data-testid="manual-invoice-submit"]').text()).toContain('Confirm and generate PDF')
|
expect(wrapper.find('[data-testid="manual-invoice-submit"]').text()).toContain('Confirm and generate PDF')
|
||||||
|
expect(wrapper.text()).toContain('Row 1')
|
||||||
|
expect(wrapper.text()).not.toContain('第 1 行')
|
||||||
|
|
||||||
await wrapper.find('[data-testid="manual-invoice-add-charge"]').trigger('click')
|
await wrapper.find('[data-testid="manual-invoice-add-charge"]').trigger('click')
|
||||||
|
|
||||||
expect(wrapper.findAll('.remove-button')[1]?.text()).toContain('Remove')
|
expect(wrapper.findAll('.remove-button')[1]?.text()).toContain('Remove')
|
||||||
expect(wrapper.findAll('.remove-button')[1]?.attributes('aria-label')).toBe('Remove row 2')
|
expect(wrapper.findAll('.remove-button')[1]?.attributes('aria-label')).toBe('Remove row 2')
|
||||||
|
expect(wrapper.text()).toContain('Row 2')
|
||||||
expect(wrapper.text()).not.toContain('新增一行')
|
expect(wrapper.text()).not.toContain('新增一行')
|
||||||
expect(wrapper.text()).not.toContain('确认并生成 PDF')
|
expect(wrapper.text()).not.toContain('确认并生成 PDF')
|
||||||
expect(wrapper.text()).not.toContain('例如 includingBF')
|
expect(wrapper.text()).not.toContain('例如 includingBF')
|
||||||
|
expect(wrapper.text()).not.toContain('金额')
|
||||||
|
expect(wrapper.text()).not.toContain('说明')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('passes current locale to native date inputs', () => {
|
||||||
|
const wrapper = mountView({ locale: 'th-TH' })
|
||||||
|
|
||||||
|
expect(wrapper.find('[data-testid="document-booking-date"]').attributes('lang')).toBe('th-TH')
|
||||||
|
expect(wrapper.find('[data-testid="booking-arrival-date"]').attributes('lang')).toBe('th-TH')
|
||||||
|
expect(wrapper.find('[data-testid="booking-departure-date"]').attributes('lang')).toBe('th-TH')
|
||||||
|
expect(wrapper.find('[data-testid="document-due-date"]').attributes('lang')).toBe('th-TH')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('marks required fields and highlights empty inputs after submit', async () => {
|
it('marks required fields and highlights empty inputs after submit', async () => {
|
||||||
@@ -326,16 +341,11 @@ describe('ReservationManualInvoiceView', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('shows final-format date previews like the invoice prototype', async () => {
|
it('does not render final-format date preview blocks', () => {
|
||||||
const wrapper = mountView()
|
const wrapper = mountView()
|
||||||
|
|
||||||
await wrapper.find('[data-testid="booking-arrival-date"]').setValue('2026-08-02')
|
expect(wrapper.find('.date-preview').exists()).toBe(false)
|
||||||
await wrapper.find('[data-testid="booking-departure-date"]').setValue('2026-08-05')
|
expect(wrapper.text()).not.toContain('最终格式')
|
||||||
await wrapper.find('[data-testid="document-due-date"]').setValue('2026-07-22')
|
|
||||||
|
|
||||||
expect(wrapper.find('[data-testid="arrival-date-preview"]').text()).toContain('02/08/2026')
|
|
||||||
expect(wrapper.find('[data-testid="departure-date-preview"]').text()).toContain('05/08/2026')
|
|
||||||
expect(wrapper.find('[data-testid="due-date-preview"]').text()).toContain('22/07/2026')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('keeps primary reservation controls bound to the first charge line', async () => {
|
it('keeps primary reservation controls bound to the first charge line', async () => {
|
||||||
|
|||||||
@@ -206,18 +206,12 @@
|
|||||||
v-model="form.document.booking_date"
|
v-model="form.document.booking_date"
|
||||||
data-testid="document-booking-date"
|
data-testid="document-booking-date"
|
||||||
type="date"
|
type="date"
|
||||||
|
:lang="locale"
|
||||||
:class="validationClass('booking-date')"
|
:class="validationClass('booking-date')"
|
||||||
v-bind="validationAria('booking-date')"
|
v-bind="validationAria('booking-date')"
|
||||||
required
|
required
|
||||||
@input="markDocumentDatesEdited"
|
@input="markDocumentDatesEdited"
|
||||||
>
|
>
|
||||||
<output
|
|
||||||
class="date-preview"
|
|
||||||
for="invoice-booking-date"
|
|
||||||
>
|
|
||||||
<small>最终格式</small>
|
|
||||||
<strong>{{ formatDatePreview(form.document.booking_date) }}</strong>
|
|
||||||
</output>
|
|
||||||
</div>
|
</div>
|
||||||
<p
|
<p
|
||||||
v-if="fieldError('booking-date')"
|
v-if="fieldError('booking-date')"
|
||||||
@@ -279,18 +273,11 @@
|
|||||||
v-model="form.booking.arrival_date"
|
v-model="form.booking.arrival_date"
|
||||||
data-testid="booking-arrival-date"
|
data-testid="booking-arrival-date"
|
||||||
type="date"
|
type="date"
|
||||||
|
:lang="locale"
|
||||||
:class="validationClass('arrival-date')"
|
:class="validationClass('arrival-date')"
|
||||||
v-bind="validationAria('arrival-date')"
|
v-bind="validationAria('arrival-date')"
|
||||||
required
|
required
|
||||||
>
|
>
|
||||||
<output
|
|
||||||
class="date-preview"
|
|
||||||
data-testid="arrival-date-preview"
|
|
||||||
for="invoice-arrival-date"
|
|
||||||
>
|
|
||||||
<small>最终格式</small>
|
|
||||||
<strong>{{ formatDatePreview(form.booking.arrival_date) }}</strong>
|
|
||||||
</output>
|
|
||||||
</div>
|
</div>
|
||||||
<p
|
<p
|
||||||
v-if="fieldError('arrival-date')"
|
v-if="fieldError('arrival-date')"
|
||||||
@@ -315,18 +302,11 @@
|
|||||||
v-model="form.booking.departure_date"
|
v-model="form.booking.departure_date"
|
||||||
data-testid="booking-departure-date"
|
data-testid="booking-departure-date"
|
||||||
type="date"
|
type="date"
|
||||||
|
:lang="locale"
|
||||||
:class="validationClass('departure-date')"
|
:class="validationClass('departure-date')"
|
||||||
v-bind="validationAria('departure-date')"
|
v-bind="validationAria('departure-date')"
|
||||||
required
|
required
|
||||||
>
|
>
|
||||||
<output
|
|
||||||
class="date-preview"
|
|
||||||
data-testid="departure-date-preview"
|
|
||||||
for="invoice-departure-date"
|
|
||||||
>
|
|
||||||
<small>最终格式</small>
|
|
||||||
<strong>{{ formatDatePreview(form.booking.departure_date) }}</strong>
|
|
||||||
</output>
|
|
||||||
</div>
|
</div>
|
||||||
<p
|
<p
|
||||||
v-if="fieldError('departure-date')"
|
v-if="fieldError('departure-date')"
|
||||||
@@ -351,19 +331,12 @@
|
|||||||
v-model="form.document.due_date"
|
v-model="form.document.due_date"
|
||||||
data-testid="document-due-date"
|
data-testid="document-due-date"
|
||||||
type="date"
|
type="date"
|
||||||
|
:lang="locale"
|
||||||
:class="validationClass('due-date')"
|
:class="validationClass('due-date')"
|
||||||
v-bind="validationAria('due-date')"
|
v-bind="validationAria('due-date')"
|
||||||
required
|
required
|
||||||
@input="markDocumentDatesEdited"
|
@input="markDocumentDatesEdited"
|
||||||
>
|
>
|
||||||
<output
|
|
||||||
class="date-preview"
|
|
||||||
data-testid="due-date-preview"
|
|
||||||
for="invoice-due-date"
|
|
||||||
>
|
|
||||||
<small>最终格式</small>
|
|
||||||
<strong>{{ formatDatePreview(form.document.due_date) }}</strong>
|
|
||||||
</output>
|
|
||||||
</div>
|
</div>
|
||||||
<p
|
<p
|
||||||
v-if="fieldError('due-date')"
|
v-if="fieldError('due-date')"
|
||||||
@@ -381,7 +354,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="room-rate-control">
|
<div class="room-rate-control">
|
||||||
<label>
|
<label>
|
||||||
<span class="control-caption control-caption--required">金额</span>
|
|
||||||
<div class="number-control">
|
<div class="number-control">
|
||||||
<input
|
<input
|
||||||
v-model="primaryCharge.rate"
|
v-model="primaryCharge.rate"
|
||||||
@@ -408,7 +380,6 @@
|
|||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<span class="control-caption">说明</span>
|
|
||||||
<input
|
<input
|
||||||
v-model.trim="form.booking.room_rate_note"
|
v-model.trim="form.booking.room_rate_note"
|
||||||
data-testid="booking-room-rate-note"
|
data-testid="booking-room-rate-note"
|
||||||
@@ -549,7 +520,7 @@
|
|||||||
:data-testid="`charge-row-${index}`"
|
:data-testid="`charge-row-${index}`"
|
||||||
>
|
>
|
||||||
<header class="charge-line__header">
|
<header class="charge-line__header">
|
||||||
<strong>第 {{ index + 1 }} 行</strong>
|
<strong>{{ t('manualInvoice.chargeLineLabel', { index: index + 1 }) }}</strong>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="remove-button"
|
class="remove-button"
|
||||||
@@ -1472,17 +1443,6 @@ function formatMoney(value: number, currency: string): string {
|
|||||||
}).format(value)
|
}).format(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatDatePreview(value: string): string {
|
|
||||||
if (!value) {
|
|
||||||
return '—'
|
|
||||||
}
|
|
||||||
const [year, month, day] = value.split('-')
|
|
||||||
if (!year || !month || !day) {
|
|
||||||
return '—'
|
|
||||||
}
|
|
||||||
return `${day}/${month}/${year}`
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatGenerationStatus(status: string): string {
|
function formatGenerationStatus(status: string): string {
|
||||||
const key = `manualInvoice.statuses.${status}`
|
const key = `manualInvoice.statuses.${status}`
|
||||||
return te(key) ? t(key) : status
|
return te(key) ? t(key) : status
|
||||||
@@ -1744,15 +1704,13 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|||||||
box-shadow: 0 0 0 3px rgb(225 29 72 / 12%), inset 0 0 0 1px rgb(225 29 72 / 8%);
|
box-shadow: 0 0 0 3px rgb(225 29 72 / 12%), inset 0 0 0 1px rgb(225 29 72 / 8%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.date-control input.is-invalid + .date-preview,
|
|
||||||
.number-control input.is-invalid + .number-suffix {
|
.number-control input.is-invalid + .number-suffix {
|
||||||
border-color: #e11d48;
|
border-color: #e11d48;
|
||||||
background: #fff1f3;
|
background: #fff1f3;
|
||||||
color: #be123c;
|
color: #be123c;
|
||||||
}
|
}
|
||||||
|
|
||||||
.field-label--required::after,
|
.field-label--required::after {
|
||||||
.control-caption--required::after {
|
|
||||||
content: "*";
|
content: "*";
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
color: #e11d48;
|
color: #e11d48;
|
||||||
@@ -1778,19 +1736,20 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.date-control,
|
.date-control {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.number-control {
|
.number-control {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(0, 1fr) auto;
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.date-control input,
|
|
||||||
.number-control input {
|
.number-control input {
|
||||||
border-radius: 6px 0 0 6px;
|
border-radius: 6px 0 0 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.date-preview,
|
|
||||||
.number-suffix {
|
.number-suffix {
|
||||||
display: grid;
|
display: grid;
|
||||||
align-content: center;
|
align-content: center;
|
||||||
@@ -1801,20 +1760,6 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.date-preview {
|
|
||||||
min-width: 118px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.date-preview small {
|
|
||||||
color: var(--th-color-slate-500);
|
|
||||||
font-size: 9px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.date-preview strong {
|
|
||||||
color: var(--th-color-slate-600);
|
|
||||||
font-size: 11px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.number-suffix {
|
.number-suffix {
|
||||||
place-items: center;
|
place-items: center;
|
||||||
min-width: 62px;
|
min-width: 62px;
|
||||||
@@ -1836,12 +1781,6 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.control-caption {
|
|
||||||
color: var(--th-color-slate-500);
|
|
||||||
font-size: 10px;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-button,
|
.add-button,
|
||||||
.remove-button,
|
.remove-button,
|
||||||
.generate-button,
|
.generate-button,
|
||||||
@@ -2204,17 +2143,14 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
.date-control,
|
|
||||||
.number-control {
|
.number-control {
|
||||||
grid-template-columns: minmax(0, 1fr);
|
grid-template-columns: minmax(0, 1fr);
|
||||||
}
|
}
|
||||||
|
|
||||||
.date-control input,
|
|
||||||
.number-control input {
|
.number-control input {
|
||||||
border-radius: 6px 6px 0 0;
|
border-radius: 6px 6px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.date-preview,
|
|
||||||
.number-suffix {
|
.number-suffix {
|
||||||
min-height: 42px;
|
min-height: 42px;
|
||||||
border-top: 0;
|
border-top: 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user