# Part 4: Year-End Closing UI (Årsbokslut) ## Overview Part 4 implements the **user interface for year-end closing** — a 4-step wizard that guides the user through validating, previewing, and executing the annual closing per Bokföringslagen. This is a pure frontend implementation; all backend services, API routes, and database migrations were completed in Part 2. **Depends on Part 2:** period-service, year-end-service, fiscal period API routes (`GET`/`POST` at `/api/bookkeeping/fiscal-periods/[id]/year-end`). --- ## What Was Built ### Year-End Wizard Page **File:** `app/(dashboard)/bookkeeping/year-end/page.tsx` A single `'use client'` page containing a 4-step wizard at route `/bookkeeping/year-end`. The wizard maps directly to the existing API surface: | Step | Label | API Call | Purpose | |------|-------|----------|---------| | 0 | Välj period | `GET /api/bookkeeping/fiscal-periods` | Select which fiscal period to close | | 1 | Validering | `GET /api/bookkeeping/fiscal-periods/[id]/year-end` | Check readiness (errors + warnings) | | 2 | Förhandsgranskning | *(uses data from step 1)* | Review closing entry before committing | | 3 | Genomför | `POST /api/bookkeeping/fiscal-periods/[id]/year-end` | Execute with confirmation dialog | #### Step 0: Period Selection - Fetches all fiscal periods on mount - Pre-selects the first open (non-closed) period - Displays each period as a selectable card with: - Period name and date range (`period_start – period_end`) - Status badge: **Öppen** (default), **Låst** (outline), **Stängd** (secondary, disabled) - Closed periods are visually dimmed and not selectable #### Step 1: Validation - Calls `GET /api/bookkeeping/fiscal-periods/[id]/year-end` which returns `{ validation, preview }` from parallel `validateYearEndReadiness()` + `previewYearEndClosing()` - Displays a ready/not-ready banner: - Green `CheckCircle2` + "Perioden är redo för årsbokslut" when `validation.ready === true` - Red `AlertCircle` + "Perioden kan inte stängas ännu" when `validation.ready === false` - **Blocking errors** (red): draft entries, unbalanced trial balance, already closed, closing entry exists - **Warnings** (amber): voucher number gaps, no posted entries - Detail cards showing draft count and trial balance status - Voucher gap badges when gaps exist - "Validera igen" button to re-run checks after fixing issues - "Nästa" button gated on `validation.ready === true` #### Step 2: Preview Three cards displaying the preview data: 1. **Net result highlight** — Large centered number with color coding (green for profit, red for loss), closing account label (e.g. "2099 — Årets resultat") 2. **Result account summary** — Table of class 3–8 accounts being zeroed (account number, name, amount) 3. **Closing journal lines** — Expandable/collapsible table showing the full closing entry (account, description, debit, credit) with a totals row #### Step 3: Execute Pre-execution state: - Summary of actions: closing entry creation, period lock + close, next period + opening balances - Irreversibility warning banner (amber) referencing Bokföringslagen - "Genomför årsbokslut" button (destructive variant) opens a confirmation dialog Confirmation dialog (`Dialog` component): - Repeats period name and net result - "Avbryt" and "Stäng perioden" (destructive) buttons Post-execution success state: - `SuccessAnimation` overlay with celebration variant - Summary card showing: closing entry link, closed period badge, new period name, opening balances status - "Tillbaka till bokföring" navigation --- ### Bookkeeping Page Link **File:** `app/(dashboard)/bookkeeping/page.tsx` Added a header action button linking to the year-end wizard: ```tsx ``` The header was restructured from a plain `
` to a `flex items-center justify-between` layout to accommodate the button alongside the existing title and description. --- ## User Flow Diagram ``` /bookkeeping │ │ Click "Årsbokslut" button ▼ ┌─────────────────────────────────────────────────────┐ │ Step 0: Välj period │ │ ┌───────────────────────────────────┐ │ │ │ FY 2024 (2024-01-01 – 2024-12-31) │ [Öppen] │ │ └───────────────────────────────────┘ │ │ ┌───────────────────────────────────┐ │ │ │ FY 2023 (2023-01-01 – 2023-12-31) │ [Stängd] │ │ └───────────────────────────────────┘ │ │ [Nästa →] │ └────────────────────────┬────────────────────────────┘ │ GET /api/bookkeeping/fiscal-periods/[id]/year-end │ ▼ ┌─────────────────────────────────────────────────────┐ │ Step 1: Validering │ │ ✅ Perioden är redo för årsbokslut │ │ ─ or ─ │ │ ❌ 3 draft entries must be posted │ │ ⚠️ Voucher gaps: 5–7 │ │ │ │ [← Tillbaka] [Validera igen] [Nästa →] │ └────────────────────────┬────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────┐ │ Step 2: Förhandsgranskning │ │ │ │ Årets resultat: 150 000,00 kr │ │ Bokförs på 2099 — Årets resultat │ │ │ │ ┌─ Resultatkonton som nollställs ────────────────┐ │ │ │ 3001 Tjänsteintäkter -500 000,00 │ │ │ │ 5010 Lokalhyra 200 000,00 │ │ │ │ 6570 Bankavgifter 150 000,00 │ │ │ └────────────────────────────────────────────────┘ │ │ │ │ [← Tillbaka] [Nästa →] │ └────────────────────────┬────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────┐ │ Step 3: Genomför │ │ │ │ ⚠️ Denna åtgärd kan inte ångras │ │ │ │ [← Tillbaka] [Genomför årsbokslut] │ │ │ │ │ ┌─────────▼──────────┐ │ │ │ Bekräfta årsbokslut │ │ │ │ Stäng FY 2024? │ │ │ │ │ │ │ │ [Avbryt] [Stäng] │ │ │ └─────────┬──────────┘ │ │ │ │ │ POST /api/.../year-end │ │ │ │ │ ▼ │ │ ✅ Årsbokslutet är genomfört │ │ • Bokslutsverifikation [Visa] │ │ • Period stängd [Stängd] │ │ • Nytt räkenskapsår FY 2025 │ │ • Ingående balanser [Skapade] │ │ │ │ [← Tillbaka till bokföring] │ └─────────────────────────────────────────────────────┘ ``` --- ## State Management All state is local to the page component via `useState`: | State | Type | Purpose | |-------|------|---------| | `step` | `number` (0–3) | Current wizard step | | `periods` | `FiscalPeriod[]` | All fiscal periods from API | | `selectedPeriodId` | `string` | Currently selected period | | `validation` | `YearEndValidation \| null` | Validation result from API | | `preview` | `YearEndPreview \| null` | Preview result from API | | `result` | `YearEndResult \| null` | Execution result from API | | `loading` | `boolean` | Loading state for validation fetch | | `loadingPeriods` | `boolean` | Loading state for periods fetch | | `executing` | `boolean` | Loading state for POST execution | | `error` | `string \| null` | Error message banner | | `showConfirmDialog` | `boolean` | Confirmation dialog visibility | | `showLinesDetail` | `boolean` | Expandable closing lines table | | `showSuccess` | `boolean` | Success animation overlay | --- ## Reused Components | Component | From | Used for | |-----------|------|----------| | `Card`, `CardContent`, `CardHeader`, `CardTitle` | `components/ui/card.tsx` | All step containers | | `Button` | `components/ui/button.tsx` | Navigation, actions, links | | `Badge` | `components/ui/badge.tsx` | Period status, voucher gaps, success indicators | | `Table`, `TableBody`, `TableCell`, `TableHead`, `TableHeader`, `TableRow` | `components/ui/table.tsx` | Result accounts + closing lines | | `Dialog`, `DialogContent`, `DialogHeader`, `DialogTitle`, `DialogDescription`, `DialogFooter` | `components/ui/dialog.tsx` | Execution confirmation | | `Skeleton` | `components/ui/skeleton.tsx` | Loading states | | `SuccessAnimation` | `components/ui/success-animation.tsx` | Post-execution celebration overlay | | `useToast` | `components/ui/use-toast.tsx` | Error notifications | | `formatAmount()` | Inline helper (same pattern as `reports/page.tsx`) | Swedish locale number formatting | Lucide icons used: `CheckCircle2`, `AlertCircle`, `AlertTriangle`, `ArrowLeft`, `ArrowRight`, `Loader2`, `Lock`, `BookOpen`, `ChevronDown`, `ChevronUp`. --- ## API Endpoints Used No new API routes were created. The wizard consumes existing endpoints from Part 2: | Method | Path | Response | Used in step | |--------|------|----------|--------------| | `GET` | `/api/bookkeeping/fiscal-periods` | `{ data: FiscalPeriod[] }` | 0 (period list) | | `GET` | `/api/bookkeeping/fiscal-periods/[id]/year-end` | `{ data: { validation: YearEndValidation, preview: YearEndPreview } }` | 1 + 2 (validation + preview) | | `POST` | `/api/bookkeeping/fiscal-periods/[id]/year-end` | `{ data: YearEndResult }` | 3 (execution) | --- ## Files Changed/Created | File | Action | |------|--------| | `app/(dashboard)/bookkeeping/year-end/page.tsx` | **Created** — 4-step year-end closing wizard (700 lines) | | `app/(dashboard)/bookkeeping/page.tsx` | **Modified** — Added "Årsbokslut" link button in header, restructured header layout | **No new API routes.** No backend changes. No new dependencies. No database migrations. --- ## Verification Checklist - [x] `npx tsc --noEmit` — zero TypeScript errors - [x] `npm run build` — builds clean, `/bookkeeping/year-end` route registered - [x] `npx vitest run` — all 78 existing tests pass - [ ] Navigate to `/bookkeeping` → "Årsbokslut" button visible in header - [ ] Click "Årsbokslut" → wizard loads at step 0 with period selector - [ ] Closed periods appear dimmed and cannot be selected - [ ] Select open period → "Nästa" → validation step loads with skeleton, then shows results - [ ] Period with draft entries → red error "X draft journal entries must be posted or deleted" - [ ] Period with unbalanced trial balance → red error "Trial balance is not balanced" - [ ] Period with voucher gaps → amber warning with gap badges - [ ] Fully valid period → green "Perioden är redo för årsbokslut", "Nästa" enabled - [ ] Preview step → net result displayed with correct color, result accounts table, expandable closing lines - [ ] EF entity type → closing account shows "2010 — Eget kapital" - [ ] AB entity type → closing account shows "2099 — Årets resultat" - [ ] Execute step → irreversibility warning shown, "Genomför årsbokslut" opens confirmation dialog - [ ] Confirmation dialog → "Stäng perioden" triggers POST, success animation + summary displayed - [ ] Success state → shows new period name, closing entry link, opening balances badge