diff --git a/DECISIONS.md b/DECISIONS.md index 598dc73e..8e2b319d 100644 --- a/DECISIONS.md +++ b/DECISIONS.md @@ -53,3 +53,8 @@ One line per decision: `[YYYY-MM-DD] : `. Appended by agents and [2026-07-09] #917 fix scoped to the current-year suggestion: "Sedan räkenskapsårets början" now resolves from the fiscal_periods row containing today, but the "Föregående räkenskapsårets start" custom option still derives from the recurring fiscal_year_start_month: the issue only covers the current-year date and a first-year company has no previous period row to resolve against. [2026-07-09] Issue #919 (duplicate guard should steer to matching): the match action lives INSIDE DuplicateBookingDialog (fetch to /api/reconciliation/bank/link + account resolution via /api/cash-accounts + resolveAccount, exactly the MatchVoucherDialog path) rather than in each call site or a new endpoint: both call sites (transactions page runCategorize + TransactionBookingDialog/JournalEntryForm) share one implementation and pass only the transaction context + an onMatched callback mirroring onLinked. Match is primary ONLY for ledger-only candidates (transaction_id null, the SIE-import case); sibling-transaction candidates keep "Bokför ändå" primary since N:1 matching is the edge case. No lib change: the candidate already carries the transaction_id discriminator, covered by existing tests. [2026-07-09] Demo/sandbox users could reach Stripe: an anonymous user on a sandbox company hit POST /api/billing/checkout and created a live Stripe customer (no subscription = no charge; exact tenant/customer IDs kept out of source control, see the incident PR). Root cause: neither billing/checkout nor billing/portal checked is_anonymous or is_sandbox, and withRouteContext lets anonymous users through (they are authenticated, just anonymously). Fix guards BOTH conditions in both routes (is_anonymous is the identity truth; guardSandbox matches the existing lib/sandbox/guard.ts "never charge a token" doctrine), belt-and-suspenders since anon and sandbox happen to co-occur today but are orthogonal. Anon check runs first (in-memory, no DB round trip). Also surfaced isDemo on GET /api/billing/status so the client hides the upgrade CTA instead of showing a button that 403s. Blast radius = exactly one company (no other sandbox/anon tenant had a stripe_customer_id). Left the stray company_subscriptions row + orphan Stripe customer for manual cleanup (prod write / external destructive action, not done unilaterally). +[2026-07-09] UI consistency pass: skipped the suppliers/page.tsx card-grid to Table conversion (did PageHeader + space-y-8 only): the suppliers.* i18n namespace has no column-header keys (nothing equivalent to customers.col_name/col_type), the pass forbade new i18n keys, and a data table whose primary name column has no header is worse than the existing card grid. +[2026-07-09] supplier-invoices/[id]: replaced the local formatAmount with shared formatCurrency at all direct render sites, but the two i18n-templated amounts (amount_registered_description embeds "kr", remaining_to_pay embeds "{currency}") now use the shared bare-number formatAmount from lib/utils instead: passing formatCurrency output there would double-print the currency, and message files were off-limits. +[2026-07-09] common.delete changed "Radera" to "Ta bort": grep proved the key has zero live call sites (every delete dialog uses feature-namespace keys), so this only affects future uses; convention going forward is Ta bort = detach/remove, Radera = irreversible destruction (kept in AccountDangerZone/CompanyDangerZone keys). +[2026-07-09] InvoiceEditor customer-card description kept only for the self-billing branch (issuer_card_description adds real info: who issues the invoice); the plain-invoice branch dropped its description as a title paraphrase per design.md forbidden patterns. +[2026-07-09] SalaryCalendar absence-type rainbow palette (red/amber/emerald/blue/indigo pills) left as-is in the UI consistency pass: those colors encode absence categories (data), not status chrome, and swapping them for the 3 semantic tokens would collapse 5 distinguishable categories; needs a proper categorical-palette decision instead of a mechanical fix. diff --git a/app/(dashboard)/bookkeeping/[id]/page.tsx b/app/(dashboard)/bookkeeping/[id]/page.tsx index 55c85e59..affdff14 100644 --- a/app/(dashboard)/bookkeeping/[id]/page.tsx +++ b/app/(dashboard)/bookkeeping/[id]/page.tsx @@ -339,7 +339,7 @@ export default function JournalEntryDetailPage({ params }: { params: Promise<{ i
-

+

{formatVoucher(entry)}

diff --git a/app/(dashboard)/bookkeeping/year-end/arsredovisning/page.tsx b/app/(dashboard)/bookkeeping/year-end/arsredovisning/page.tsx index 38de16de..7502bf33 100644 --- a/app/(dashboard)/bookkeeping/year-end/arsredovisning/page.tsx +++ b/app/(dashboard)/bookkeeping/year-end/arsredovisning/page.tsx @@ -10,9 +10,11 @@ import { Skeleton } from '@/components/ui/skeleton' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Textarea } from '@/components/ui/textarea' +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table' import { PageHeader } from '@/components/ui/page-header' import { ArrowLeft, FileDown, Plus, ExternalLink, Loader2, Save, CheckCircle2 } from 'lucide-react' import { useToast } from '@/components/ui/use-toast' +import { formatCurrency } from '@/lib/utils' import { FiscalYearSelector } from '@/components/common/FiscalYearSelector' import { DigitalInlamning, INLAMNING_COMING_SOON } from '@/components/bokslut/DigitalInlamning' import type { ArsredovisningData } from '@/lib/bokslut/arsredovisning/types' @@ -388,7 +390,7 @@ export default function ArsredovisningPage() {

-
+