feat: counterparty templates, Skatteverket extension, complete VAT form (#117)

* feat: separate AR/AP/accounting into distinct nav groups (#92)

Split the flat "Finans" sidebar group into three visually distinct
sections — Försäljning (AR), Inköp (AP), and Redovisning — so users
coming from Fortnox immediately find customer invoicing and supplier
invoices as top-level concepts.

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

* feat: journal entry detail view, correction chain, and account name display

- Add journal entry detail page at /bookkeeping/[id] with full entry view
- Add correction chain API and component showing storno relationships
- Add JournalEntryStatusBadge component for entry status display
- Show debit/credit account names in template picker and review dialogs
- Expand client-side BAS account name mapping with additional accounts
- Show account codes on transaction inbox suggestion buttons

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

* fix: address review feedback — N+1 query, duplicate name, nav dedup

- Batch reverse-lookup into single query per BFS iteration (was N+1)
- Differentiate account 2393 from 2893 in display names
- Extract shared loop for desktop/mobile nav group rendering

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

* feat: counterparty templates, Skatteverket extension, VAT form completeness, and UI cleanup

- Add counterparty-based categorization templates (learned from user approvals
  and auto-ingestion) with fuzzy matching in the mapping engine
- Add Skatteverket extension for direct VAT declaration submission via API
- Complete VAT declaration form with all 30 SKV 4700 boxes (ruta 08, 35-42, 50, 60-62)
- Fix ruta 49 formula to include import VAT (ruta 60+61+62)
- Simplify dashboard UI: remove redundant icons from stat cards, customer cards,
  invoice list, supplier invoices; use Badge variants consistently
- Add SkatteverketPanel component to reports page
- Add categorization_templates and skatteverket_tokens migrations
- Update tests and helpers for new types

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

* fix: address PR review feedback — VAT detection, migration timestamps, dedup

- Fix detectVatTreatment to derive actual rate (12%/6%) from VAT line
  description instead of hardcoding standard_25
- Rename skatteverket_tokens migration to 20260324120001 to avoid
  duplicate timestamp with categorization_templates (fixes Supabase
  deployment failure)
- Make refreshAccessToken accept previousRefreshCount param to enforce
  refresh limit contract at the type level
- Fix rate limiter TOCTOU by claiming slot before await
- Extract formatRedovisare/formatRedovisningsperiod to shared
  lib/skatteverket/format.ts — eliminates duplication between
  mappers.ts and SkatteverketPanel.tsx

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-03-24 13:41:12 +01:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 3050743013
commit d8e0a22495
33 changed files with 3097 additions and 158 deletions
@@ -34,6 +34,10 @@ vi.mock('@/lib/bookkeeping/mapping-engine', () => ({
saveUserMappingRule: (...args: unknown[]) => mockSaveUserMappingRule(...args),
}))
vi.mock('@/lib/bookkeeping/counterparty-templates', () => ({
upsertCounterpartyTemplate: vi.fn().mockResolvedValue(undefined),
}))
import { POST } from '../route'
describe('POST /api/transactions/[id]/categorize', () => {
@@ -6,6 +6,7 @@ import { buildMappingResultFromCategory } from '@/lib/bookkeeping/category-mappi
import { getTemplateById, buildMappingResultFromTemplate, validateTemplateForEntity } from '@/lib/bookkeeping/booking-templates'
import { createTransactionJournalEntry } from '@/lib/bookkeeping/transaction-entries'
import { saveUserMappingRule } from '@/lib/bookkeeping/mapping-engine'
import { upsertCounterpartyTemplate } from '@/lib/bookkeeping/counterparty-templates'
import { validateBody } from '@/lib/api/validate'
import { CategorizeTransactionSchema } from '@/lib/api/schemas'
import type { Transaction, TransactionCategory, EntityType } from '@/types'
@@ -286,6 +287,15 @@ export async function POST(
}
}
// Upsert counterparty template for future auto-matching
try {
await upsertCounterpartyTemplate(
supabase, user.id, transaction as Transaction, mappingResult, 'user_approved'
)
} catch {
// Non-critical
}
// Link receipt document to journal entry if both exist
if (journalEntryId && transaction.receipt_id) {
try {