Files
accounted/lib/invoices/display.ts
T
Jakob Wennberg 97907a5a5c fix: article ordering, free-text rows, invoice back-nav, onboarding resilience (#1053) (#1056)
* fix: article number ordering, free-text rows, invoice back-nav, onboarding resilience (#1053)

Four fixes from Discord feedback in issue #1053:

- Articles now order by article number with numeric-aware comparison
  ('2' before '10', unnumbered last, name tiebreak) in the invoice
  editor's article picker and as the register's default sort, via a
  shared lib/articles/sort.ts. Name order put article "1" last.

- Invoice rows with no amounts (quantity 0, unit price 0) render as
  pure text rows on the PDF, the invoice detail page, and the review
  step via shared isTextLikeLine(), instead of printing
  "0 / 0,00 SEK / 0,00 SEK". Display-only; booking untouched.

- The invoice editor navigates with router.replace after saving, so
  the detail page's back arrow returns to the list instead of
  reopening a fresh editor from history.

- A transient query failure no longer reads as "no companies" /
  "onboarding not done": getActiveCompanyId throws
  CompanyContextError('resolution_failed') instead of returning null,
  the Edge middleware fails open on a degraded resolution (no
  onboarding redirect, no cookie clearing, no locale overwrite), and
  the dashboard page only redirects to /onboarding on a positively
  read incomplete/missing settings row. This is the likely cause of
  the completed onboarding wizard reappearing.

Fixes #1053

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: CLAUDE.md tenancy line matches actual resolution order (prefs-first, cookie not read)

The middleware stopped reading the gnubok-company-id cookie when
user_preferences became authoritative (RLS parity); the stale doc line
still described cookie-first order and misled review tooling.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 14:45:43 +02:00

35 lines
1.3 KiB
TypeScript

export const INVOICE_NUMBER_DRAFT_LABEL = '(Utkast)'
export function invoiceNumberDisplay(value: string | null | undefined): string {
return value ?? INVOICE_NUMBER_DRAFT_LABEL
}
/**
* The number to show for an invoice. Self-billing invoices we received carry
* the counterparty's number in `external_invoice_number` (our own
* `invoice_number` is null by design), so fall back to it before the draft
* label.
*/
export function invoiceDisplayNumber(invoice: {
invoice_number?: string | null
external_invoice_number?: string | null
}): string {
return invoice.invoice_number ?? invoice.external_invoice_number ?? INVOICE_NUMBER_DRAFT_LABEL
}
/**
* True when an invoice line should render as a pure text row: description
* only, no quantity/unit/price/amount columns. Explicit text rows
* (line_type 'text') always qualify; so do product rows carrying no amounts
* at all (quantity and unit price both zero/absent). Users write free-text
* lines via the article picker's "Egen rad (fri text)" and leave antal/pris
* at zero; printing "0 / 0,00 SEK / 0,00 SEK" on those is noise (issue #1053).
*/
export function isTextLikeLine(item: {
line_type?: 'product' | 'text' | null
quantity?: number | null
unit_price?: number | null
}): boolean {
return item.line_type === 'text' || (!item.quantity && !item.unit_price)
}