* fix(invoices): surface validation errors instead of a silent dead submit button
A missing unit (or any other Zod failure) blocked both Granska & skapa and
Spara som utkast with zero feedback: handleSubmit had no onInvalid callback,
the buttons stayed enabled, and the unit field rendered no inline error.
Reported by a byra user whose client could not save any invoice.
- onInvalid handler on all three submit paths: destructive toast plus scroll
to the first inline error
- inline error text under the unit select and quantity input (the only line
fields that had none)
- same treatment in NewRecurringScheduleDialog, including inline errors on
its item rows
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(supplier-invoices): stop defaulting 25 % moms for icke momsregistrerade companies
The registration form hard-coded vat_rate 0.25 on the initial line, added
rows, AI prefill fallback and konto defaults, regardless of
company_settings.vat_registered. A non-VAT-registered business that missed
the prefilled rate booked ingaende moms (2641) it has no right to deduct
(ML 8 kap. 3 \u00a7). The customer-invoice side already gates on the same flag;
the supplier side ignored it.
- form: read vat_registered from /api/settings; when false, all moms
controls (rate cells, per-line moms, totals rows) are hidden and every
line is forced to 0 %, including late AI prefills
- reverse charge keeps its rate controls: self-assessment is a separate
obligation from deduction
- route: 400 SI_CREATE_INVALID_INPUT when a non-registered company posts a
line with vat_rate/vat_amount > 0 (API/MCP defense in depth), and an
omitted vat_rate now defaults to 0 instead of 25 % for those companies
- tests: guard rejection, reverse-charge pass-through, 0-default; existing
POST tests updated for the new settings lookup
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(year-end): klarmarkera imported years already closed in a previous system
SIE-imported historical fiscal years land with is_closed = false and no
closing entry, so the year-end page lists every migrated year as pending
bokslut even though the bokslut was done in the old software. There was no
sanctioned way to mark them done: closePeriod hard-requires locked_at and
closing_entry_id.
- migration: fiscal_periods.closed_externally boolean (audit clarity:
distinguishes a year-end run here from a close done elsewhere)
- markPeriodClosedExternally(): closes + locks without a closing entry;
refuses already-closed periods, periods with their own closing entry,
periods that have not ended, and periods with unbooked bank transactions
(same stranding guard as lockPeriod); writes the immutable audit_log entry
- POST /api/bookkeeping/fiscal-periods/[id]/close-external (requireWrite)
- year-end page: one attn line on the preflight step with a confirm dialog
describing the outcome; the marked year drops out of the eligible list
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(invoices): searchable article picker on invoice lines
The article field was a plain Radix Select whose only matching is
label-prefix typeahead: for numbered articles that means number-only lookup,
and typing "skruv" found nothing. Byra feedback: name search would help a
lot for users with real article catalogs.
New ArticleCombobox (input-trigger dropdown, same pattern as
AccountCombobox): free-text search over name + article number,
diacritics-folded via foldText, keyboard navigation, pinned "Egen rad"
free-text option, browse-all on focus like the Select it replaces.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: log klarmarkera pg-test decision
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: address skeptic and compliance-review findings on PR #1641
- ArticleCombobox: keyboard focus no longer auto-opens the list, opening
highlights the committed selection, typing highlights the first match,
and re-selecting the current value is a no-op. Previously Tab+Enter
silently detached the article and wiped its revenue-account override.
- Supplier invoice prefill for icke momsregistrerade: the zeroing effect now
grosses the net amount up by the extracted rate before forcing 0 %, so the
booked cost and 2440 keep the full att-betala amount instead of
understating both by the moms.
- markPeriodClosedExternally: only migrated periods qualify (must contain
SIE-imported verifikat or no verifikat at all); the update carries an
is_closed=false predicate so a concurrent normal close cannot be
overwritten; confirm dialog now names the reporting consequences.
- Route comment: honest scope (this route only; v1/inbox/MCP sweep is a
follow-up) and current-law citation (13 kap. ML 2023:200).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: use roundOre for the icke-momsregistrerad gross-up (ratchet guard)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>