diff --git a/client/src/tests/manualInvoiceView.spec.ts b/client/src/tests/manualInvoiceView.spec.ts index fa1ced9..e39521b 100644 --- a/client/src/tests/manualInvoiceView.spec.ts +++ b/client/src/tests/manualInvoiceView.spec.ts @@ -209,6 +209,97 @@ describe('ReservationManualInvoiceView', () => { ) }) + it('renders the invoice.html style page sections and sticky action bar', () => { + const wrapper = mountView() + + expect(wrapper.find('[data-testid="manual-invoice-prototype-page"]').exists()).toBe(true) + expect(wrapper.text()).toContain('创建 Invoice') + expect(wrapper.text()).toContain('01') + expect(wrapper.text()).toContain('Basic Information') + expect(wrapper.text()).toContain('02') + expect(wrapper.text()).toContain('Please refer to the following reservation') + expect(wrapper.text()).toContain('03') + expect(wrapper.text()).toContain('Description&Amount') + expect(wrapper.find('[data-testid="manual-invoice-action-bar"]').exists()).toBe(true) + }) + + it('shows final-format date previews like the invoice prototype', async () => { + const wrapper = mountView() + + await wrapper.find('[data-testid="booking-arrival-date"]').setValue('2026-08-02') + await wrapper.find('[data-testid="booking-departure-date"]').setValue('2026-08-05') + 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 () => { + const wrapper = mountView() + + await wrapper.find('[data-testid="primary-room-rate"]').setValue('3200') + await wrapper.find('[data-testid="primary-room-count"]').setValue('4') + await wrapper.find('[data-testid="primary-night-count"]').setValue('3') + + expect((wrapper.find('[data-testid="charge-rate-0"]').element as HTMLInputElement).value).toBe('3200') + expect((wrapper.find('[data-testid="charge-quantity-0"]').element as HTMLInputElement).value).toBe('4') + expect((wrapper.find('[data-testid="charge-nights-0"]').element as HTMLInputElement).value).toBe('3') + expect(wrapper.find('[data-testid="charge-amount-0"]').text()).toContain('38,400.00') + }) + + it('submits comma-formatted numeric inputs as normalized numbers', async () => { + vi.mocked(service.generateManualReservationInvoice).mockResolvedValue(createResult()) + const wrapper = mountView() + + await wrapper.find('[data-testid="recipient-company-code"]').setValue('QBD') + await fillMinimumInvoiceForm(wrapper) + await wrapper.find('[data-testid="booking-extra-bed-rate"]').setValue('1,200') + await wrapper.find('[data-testid="charge-rate-0"]').setValue('3,000') + await wrapper.find('[data-testid="manual-invoice-submit"]').trigger('click') + await flushPromises() + + expect(service.generateManualReservationInvoice).toHaveBeenCalledWith( + expect.objectContaining({ + invoice_payload: expect.objectContaining({ + booking: expect.objectContaining({ + extra_bed_rate: 1200, + }), + charges: [ + expect.objectContaining({ + rate: 3000, + }), + ], + }), + }), + ) + }) + + it('clears directory contact text when switching company back to manual input', async () => { + const wrapper = mountView() + + await wrapper.find('[data-testid="recipient-company-code"]').setValue('HANATOUR') + await wrapper.find('[data-testid="recipient-contact-id"]').setValue('HANATOUR_BOLAM_JO') + expect((wrapper.find('[data-testid="recipient-email"]').element as HTMLInputElement).value).toBe( + 'melissa0609@hanatour.com', + ) + + await wrapper.find('[data-testid="recipient-company-code"]').setValue('MANUAL') + + expect((wrapper.find('[data-testid="recipient-contact-id"]').element as HTMLSelectElement).value).toBe('MANUAL') + expect((wrapper.find('input[placeholder="输入 Company"]').element as HTMLInputElement).value).toBe('') + expect((wrapper.find('input[placeholder="输入 Attention"]').element as HTMLInputElement).value).toBe('') + expect((wrapper.find('[data-testid="recipient-email"]').element as HTMLInputElement).value).toBe('') + }) + + it('keeps invoice date out of the keyboard focus order while preserving the payload field', () => { + const wrapper = mountView() + const invoiceDate = wrapper.find('[data-testid="document-invoice-date"]') + + expect(invoiceDate.attributes('type')).toBe('hidden') + expect(invoiceDate.attributes('required')).toBeUndefined() + }) + it('links company and attention seed data while allowing manual overrides before submit', async () => { vi.mocked(service.generateManualReservationInvoice).mockResolvedValue(createResult()) const wrapper = mountView() diff --git a/client/src/views/reservation/ReservationManualInvoiceView.vue b/client/src/views/reservation/ReservationManualInvoiceView.vue index 76b6888..2565d9d 100644 --- a/client/src/views/reservation/ReservationManualInvoiceView.vue +++ b/client/src/views/reservation/ReservationManualInvoiceView.vue @@ -1,438 +1,610 @@