feat(bookkeeping): Ny verifikat modal, ledger-style list, SIE no-underlag exemptions (#698)

* feat(bookkeeping): Ny verifikat modal, ledger-style list, SIE no-underlag exemptions

Verifikat UX
- "Ny verifikat" opens in a modal (NewJournalEntryDialog) instead of an inline tab;
  the review step renders inline in the dialog rather than stacking a second dialog.
- JournalEntryForm: konteringsrader are the focus, with a compact pre-filled metadata
  bar (datum/serie/text/valuta/period) on top; verifikationstext auto-fills from the
  first row's account.
- JournalEntryList: belopp shown on collapsed rows; expanded view is an aligned
  Konto/Benämning/Debet/Kredit table.

SIE imports no longer flood "Att hantera: saknade underlag"
- Import gains an opt-in (off by default) toggle to mark imported verifikat as "Inget
  underlag krävs"; a "Rekommenderas vid migrering" badge nudges it for historical years.
- Multi-select batch-mark in the list for selective cleanup.
- Filter-scoped bulk mark (POST /api/bookkeeping/no-doc-required/bulk-missing): marks
  every missing-doc verifikat matching the active filters across all pages, with a
  dry_run count to confirm scope — the scalable remedy for a post-import flood.
- Shared helper markEntriesNoDocRequired + per-entry batch route.

Tests: no-doc helper, batch route, bulk-missing route.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(bookkeeping): address PR #698 review findings

- JournalEntryForm: restore the explicit "no underlag" acknowledgement in the
  modal's inline review. When no document is attached, the confirm button reads
  "Bokför utan underlag" (BFL 5 kap 6-7 §§), equivalent to the blocking dialog the
  non-bare flow shows — the bare path no longer posts behind only a passive banner.
- batch no-doc route: guard the ownership query with source_type IN
  NEEDS_DOC_SOURCE_TYPES so a crafted request can't exempt non-document-requiring
  entries (defense in depth on top of company + posted scoping).
- bulk-missing route: resolve doc/exemption status by querying only the candidate
  ids (chunked) instead of loading the company's full document_attachments and
  journal_entry_no_doc_required tables into memory — data minimisation + bounded
  memory for large migrations (the most-repeated reviewer finding).

Triaged as non-issues (left as-is): partial-import exemption (gated on
result.success == zero errors), reason write-back (sidecar row is FK-linked and
carries the reason), and "bulk-exempting manual entries" (consistent with the
existing per-entry NoDocRequiredToggle). No DB migration — reuses the existing
journal_entry_no_doc_required table.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(bookkeeping): centralize bulk-missing date/series validation in Zod

Move the ISO-date and verifikationsserie format checks into the Zod schema so
malformed input is rejected with a clean 400 instead of being silently nulled
(or, for a shaped-but-invalid date, throwing a 500 via fetchAllRows). The date
refinement rejects values like 9999-99-99 / 2026-02-30 that a bare
/^\d{4}-\d{2}-\d{2}$/ regex lets through. Addresses the PR #698 reviewer nit on
split schema-vs-runtime validation. +2 route tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-06-08 20:41:30 +02:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 64991eb3c9
commit 4dfd790de5
16 changed files with 1327 additions and 228 deletions
+21 -1
View File
@@ -2908,7 +2908,24 @@
"no_doc_required_suggestion_interest": "Interest",
"no_doc_required_suggestion_internal_transfer": "Internal transfer",
"no_doc_required_suggestion_tax_payment": "Tax payment",
"no_doc_required_suggestion_salary": "Salary"
"no_doc_required_suggestion_salary": "Salary",
"account_column": "Account",
"description_column": "Description",
"sum_label": "Total",
"batch_select_all": "Select all ({count})",
"batch_select_row": "Select entry",
"batch_selected_count": "{count} selected",
"batch_mark_no_doc": "Mark as no document required",
"batch_clear_selection": "Clear",
"batch_no_doc_done_title": "Marked as no document required",
"batch_no_doc_done_description": "{count} entries updated.",
"batch_mark_all_missing": "Mark all without documents",
"bulk_mark_title": "Mark all without documents",
"bulk_mark_counting": "Counting entries...",
"bulk_mark_none": "No entries without documents match the filter.",
"bulk_mark_body": "{count} entries without a supporting document match the current filter. They will be marked \"No supporting document required\". You can undo this per entry afterwards.",
"bulk_mark_confirm": "Mark {count}",
"bulk_cancel": "Cancel"
},
"attachment_preview_sheet": {
"title": "Attachments",
@@ -3098,6 +3115,8 @@
"review_title": "Review journal entry",
"review_title_with_voucher": "Review journal entry ({voucher})",
"review_warning": "A journal entry is created and cannot be edited afterwards. Corrections are made via storno.",
"review_back": "Back",
"review_confirm": "Create entry",
"no_doc_dialog_title": "Document missing",
"no_doc_dialog_warning": "No document attached. Under the Swedish Accounting Act (BFL 5 kap. 6-7 §§) every accounting entry must have a verification as supporting document. You can attach one now or continue without.",
"no_doc_confirm": "Post without document",
@@ -3431,6 +3450,7 @@
"tab_journal": "Journal entries",
"tab_new_entry": "New journal entry",
"tab_accounts": "Chart of accounts",
"new_entry_dialog_title": "New journal entry",
"loading_source_voucher": "Loading source voucher...",
"copy_failed_title": "Could not copy journal entry",
"copy_source_missing": "Source voucher not found.",
+21 -1
View File
@@ -2908,7 +2908,24 @@
"no_doc_required_suggestion_interest": "Ränta",
"no_doc_required_suggestion_internal_transfer": "Intern överföring",
"no_doc_required_suggestion_tax_payment": "Skatteinbetalning",
"no_doc_required_suggestion_salary": "Lön"
"no_doc_required_suggestion_salary": "Lön",
"account_column": "Konto",
"description_column": "Benämning",
"sum_label": "Summa",
"batch_select_all": "Markera alla ({count})",
"batch_select_row": "Markera verifikat",
"batch_selected_count": "{count} markerade",
"batch_mark_no_doc": "Markera som inget underlag krävs",
"batch_clear_selection": "Avmarkera",
"batch_no_doc_done_title": "Markerade som inget underlag krävs",
"batch_no_doc_done_description": "{count} verifikat uppdaterade.",
"batch_mark_all_missing": "Markera alla utan underlag",
"bulk_mark_title": "Markera alla utan underlag",
"bulk_mark_counting": "Räknar verifikat...",
"bulk_mark_none": "Inga verifikat utan underlag matchar filtret.",
"bulk_mark_body": "{count} verifikat som saknar underlag matchar nuvarande filter. De markeras som \"Inget underlag krävs\". Du kan ångra per verifikat efteråt.",
"bulk_mark_confirm": "Markera {count}",
"bulk_cancel": "Avbryt"
},
"attachment_preview_sheet": {
"title": "Bilagor",
@@ -3098,6 +3115,8 @@
"review_title": "Granska verifikation",
"review_title_with_voucher": "Granska verifikation ({voucher})",
"review_warning": "En verifikation skapas och kan inte ändras efteråt. Korrigeringar görs genom storno.",
"review_back": "Tillbaka",
"review_confirm": "Skapa verifikat",
"no_doc_dialog_title": "Underlag saknas",
"no_doc_dialog_warning": "Inget underlag bifogat. Enligt bokföringslagen (BFL 5 kap. 6-7 §§) ska varje bokföringspost ha en verifikation som underlag. Du kan bifoga underlag nu eller fortsätta utan.",
"no_doc_confirm": "Bokför utan underlag",
@@ -3431,6 +3450,7 @@
"tab_journal": "Verifikationer",
"tab_new_entry": "Ny verifikation",
"tab_accounts": "Kontoplan",
"new_entry_dialog_title": "Ny verifikation",
"loading_source_voucher": "Laddar källverifikat...",
"copy_failed_title": "Kunde inte kopiera verifikat",
"copy_source_missing": "Källverifikatet hittades inte.",