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:
co-authored by
Claude Opus 4.8
parent
64991eb3c9
commit
4dfd790de5
@@ -4,6 +4,7 @@ import { useState, useEffect, useRef } from 'react'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import {
|
||||
Select,
|
||||
@@ -45,6 +46,7 @@ export interface ImportExecuteOptions {
|
||||
importTransactions: boolean
|
||||
updateAccountNames: boolean
|
||||
voucherSeries: string
|
||||
markImportedNoDocRequired: boolean
|
||||
}
|
||||
|
||||
export default function ImportReviewStep({
|
||||
@@ -62,6 +64,7 @@ export default function ImportReviewStep({
|
||||
importTransactions: true,
|
||||
updateAccountNames: true,
|
||||
voucherSeries: 'B',
|
||||
markImportedNoDocRequired: false,
|
||||
})
|
||||
const [defaultSeries, setDefaultSeries] = useState<string | null>(null)
|
||||
const [existingSeries, setExistingSeries] = useState<Set<string>>(new Set())
|
||||
@@ -146,6 +149,15 @@ export default function ImportReviewStep({
|
||||
const mappedCount = mappings.filter((m) => m.targetAccount).length
|
||||
const hasOpeningBalances = preview.openingBalanceTotal > 0
|
||||
const hasTransactions = preview.voucherCount > 0
|
||||
// An import whose fiscal year already ended in a prior calendar year is a
|
||||
// historical/migration import — the underlag live in the old system, so the
|
||||
// exemption is especially apt. Nudges (does not force) the toggle.
|
||||
const isHistoricalImport = (() => {
|
||||
if (!preview.fiscalYearEnd) return false
|
||||
const end = new Date(preview.fiscalYearEnd)
|
||||
const startOfThisYear = new Date(new Date().getFullYear(), 0, 1)
|
||||
return !isNaN(end.getTime()) && end < startOfThisYear
|
||||
})()
|
||||
// Identity-mapped accounts whose #KONTO name differs from the BAS default —
|
||||
// mirrors the filter in syncMappedAccounts, so the count matches what the
|
||||
// import would actually rename/create with a custom name.
|
||||
@@ -353,6 +365,33 @@ export default function ImportReviewStep({
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* No-underlag exemption — keeps a multi-year migration from flooding
|
||||
"Att hantera: saknade underlag" with thousands of items. */}
|
||||
<div className="flex items-start justify-between border-t pt-6">
|
||||
<div className="space-y-0.5 pr-4">
|
||||
<Label htmlFor="mark-no-doc-required" className="font-medium flex items-center gap-2">
|
||||
Markera som "Inget underlag krävs"
|
||||
{isHistoricalImport && (
|
||||
<Badge variant="secondary" className="text-[10px] font-normal">
|
||||
Rekommenderas vid migrering
|
||||
</Badge>
|
||||
)}
|
||||
</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Märker alla importerade verifikationer som att de inte behöver något
|
||||
separat underlag — underlagen finns kvar i ditt tidigare system. Annars
|
||||
hamnar de under "Att hantera: saknade underlag". Kan ändras per
|
||||
verifikation efteråt.
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
id="mark-no-doc-required"
|
||||
checked={options.markImportedNoDocRequired}
|
||||
onCheckedChange={(checked) => updateOption('markImportedNoDocRequired', checked)}
|
||||
disabled={!options.importTransactions || !hasTransactions}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user