Files
accounted/lib/invoices/__tests__/display.test.ts
T
78525bd391 fix(invoices): make self-billed invoices creditable and their dates visible (#1827)
A self-billed invoice has invoice_number null by design (the counterparty's
number lives in external_invoice_number), which broke the whole credit flow:
the confirm input was disabled and compared against null, the API minted the
literal number 'KR-null', and the credit-note PDF dropped its ML 17 kap 22
reference to the original. The editor also hid fakturadatum inside the
collapsed Forval panel, so self-billed invoices silently registered with
today's date and, being immutable, could not be corrected.

- creditConfirmNumber() falls back to external_invoice_number; the credit
  page uses it for reason default, subtitle, original row, preview, confirm
  label/placeholder/disabled, mismatch check and submit gate
- createCreditNote numbers 'KR-<external>' for self-billed originals and
  refuses with typed 400 INVOICE_CREDIT_NO_NUMBER when no number exists
- mark-sent and send select external_invoice_number and fall back for the
  credit-note PDF's reference to the original
- the Forval chip line now shows the invoice date in every mode, and
  self-billed mode renders fakturadatum + mottagningsdatum uncollapsed as
  transcription fields next to the external number

Fixes #1820


Claude-Session: https://claude.ai/code/session_01SyDuePXxUFowaPBKpAv8SF

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 13:18:57 +02:00

43 lines
1.9 KiB
TypeScript

import { describe, it, expect } from 'vitest'
import { creditConfirmNumber, isTextLikeLine } from '@/lib/invoices/display'
describe('creditConfirmNumber', () => {
it('uses our own invoice number when present', () => {
expect(
creditConfirmNumber({ invoice_number: 'F-2026010', external_invoice_number: null }),
).toBe('F-2026010')
})
it('falls back to the external number for self-billed invoices (issue #1820)', () => {
expect(
creditConfirmNumber({ invoice_number: null, external_invoice_number: 'SB-2026-17' }),
).toBe('SB-2026-17')
})
it('returns null when the invoice carries no number at all', () => {
expect(creditConfirmNumber({ invoice_number: null, external_invoice_number: null })).toBeNull()
expect(creditConfirmNumber({})).toBeNull()
})
})
describe('isTextLikeLine', () => {
it('is true for explicit text rows regardless of amounts', () => {
expect(isTextLikeLine({ line_type: 'text', quantity: 0, unit_price: 0 })).toBe(true)
expect(isTextLikeLine({ line_type: 'text', quantity: 2, unit_price: 100 })).toBe(true)
})
it('is true for product rows with no amounts (issue #1053 free-text line)', () => {
expect(isTextLikeLine({ line_type: 'product', quantity: 0, unit_price: 0 })).toBe(true)
expect(isTextLikeLine({ quantity: 0, unit_price: 0 })).toBe(true)
expect(isTextLikeLine({ quantity: null, unit_price: null })).toBe(true)
expect(isTextLikeLine({})).toBe(true)
})
it('is false as soon as the row carries a quantity or a price', () => {
expect(isTextLikeLine({ line_type: 'product', quantity: 1, unit_price: 0 })).toBe(false)
expect(isTextLikeLine({ line_type: 'product', quantity: 0, unit_price: 250 })).toBe(false)
expect(isTextLikeLine({ line_type: 'product', quantity: 0, unit_price: -250 })).toBe(false)
expect(isTextLikeLine({ quantity: 3, unit_price: 99.5 })).toBe(false)
})
})