Backend (.NET 10): - EventFlow CQRS/Event Sourcing with PostgreSQL - GraphQL.NET API with mutations and queries - Custom ReadModelSqlGenerator for snake_case PostgreSQL columns - Hangfire for background job processing - Integration tests with isolated test databases Frontend (React/Vite): - Initial project structure 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
9.6 KiB
9.6 KiB
Backend Krav - Dansk Bogføringssystem
Overblik
Dette dokument beskriver backend-kravene for det danske bogføringssystem. Frontend er implementeret i React/TypeScript med Ant Design og Zustand state management.
1. Regnskabsår (Fiscal Years)
1.1 Data Model
interface FiscalYear {
id: string;
companyId: string;
name: string; // "2025" eller "2024/2025"
startDate: string; // ISO date "YYYY-MM-DD"
endDate: string; // ISO date "YYYY-MM-DD"
status: 'open' | 'closed' | 'locked';
openingBalancePosted: boolean;
closingDate?: string; // Når året blev lukket
closedBy?: string; // Bruger ID
createdAt: string;
updatedAt: string;
}
1.2 API Endpoints
| Endpoint | Metode | Beskrivelse |
|---|---|---|
/api/fiscal-years |
GET | Hent alle regnskabsår for aktiv virksomhed |
/api/fiscal-years |
POST | Opret nyt regnskabsår |
/api/fiscal-years/:id |
GET | Hent specifikt regnskabsår |
/api/fiscal-years/:id |
PATCH | Opdater regnskabsår |
/api/fiscal-years/:id/close |
POST | Luk regnskabsår (årsafslutning) |
/api/fiscal-years/:id/reopen |
POST | Genåbn lukket regnskabsår |
/api/fiscal-years/:id/lock |
POST | Lås regnskabsår permanent |
1.3 Forretningsregler
Oprettelse
- Nye regnskabsår må IKKE overlappe med eksisterende
- Regnskabsår der "rører" ved grænsen (slutter/starter samme dag) er TILLADT
- Understøt både kalenderår (jan-dec) og skæve regnskabsår (fx jul-jun)
- Valider at regnskabsåret er mellem 300-400 dage (advarsel, ikke fejl)
Årsafslutning (Year-End Closing)
KRITISK: Ved årsafslutning skal backend:
-
Validere at regnskabsåret kan lukkes:
- Ikke allerede låst
- Advar om åbne perioder (men tillad lukning)
- Advar om ikke-afstemte transaktioner
-
Oprette lukkeposter (closing entries):
- Luk alle indtægtskonti til resultatoverførselskonto
- Luk alle udgiftskonti til resultatoverførselskonto
- Gem som normale transaktioner med særlig markering (
isClosingEntry: true)
-
Generere åbningsbalancer til næste regnskabsår:
- Beregn slutsaldo for alle balancekonti (aktiver, passiver, egenkapital)
- Nulstil resultatkonti (indtægter, udgifter, vareforbrug, personale, finansielle)
- Opret åbningsposteringer i næste regnskabsår (
isOpeningBalance: true)
-
Opdatere status:
- Sæt regnskabsår status til 'closed' eller 'locked'
- Luk alle tilhørende regnskabsperioder
- Sæt
openingBalancePosted: truepå næste regnskabsår
Genåbning
- Kun 'closed' regnskabsår kan genåbnes (ikke 'locked')
- Ved genåbning skal åbningsbalancer i næste år opdateres automatisk
- Log hvem der genåbnede og hvornår
1.4 Dynamiske Åbningsbalancer
Frontend forventer at åbningsbalancer opdateres automatisk når der bogføres i et tidligere år (som Dinero):
Scenarie:
1. Regnskabsår 2024 er lukket, 2025 er åbent
2. Revisor finder fejl og bogfører korrektion i 2024
3. Backend skal automatisk genberegne åbningsbalancen for 2025
2. Regnskabsperioder (Accounting Periods)
2.1 Data Model
interface AccountingPeriod {
id: string;
fiscalYearId: string;
companyId: string;
name: string; // "Januar 2025", "Q1 2025"
periodNumber: number; // 1-12 for månedlig
startDate: string;
endDate: string;
status: 'future' | 'open' | 'closed' | 'locked';
closedAt?: string;
closedBy?: string;
reopenedAt?: string;
reopenedBy?: string;
lockedAt?: string;
lockedBy?: string;
createdAt: string;
updatedAt: string;
}
type PeriodFrequency = 'monthly' | 'quarterly' | 'half-yearly' | 'yearly';
2.2 API Endpoints
| Endpoint | Metode | Beskrivelse |
|---|---|---|
/api/periods |
GET | Hent perioder (filtrer på fiscalYearId) |
/api/periods/:id |
PATCH | Opdater periode |
/api/periods/:id/close |
POST | Luk periode |
/api/periods/:id/reopen |
POST | Genåbn periode |
/api/periods/:id/lock |
POST | Lås periode |
2.3 Forretningsregler
- Perioder genereres automatisk ved oprettelse af regnskabsår
- Understøt: månedlig (12), kvartalsvis (4), halvårlig (2), årlig (1)
- Perioder kan kun lukkes i rækkefølge (periode 2 kræver periode 1 lukket)
- Låste perioder kan IKKE genåbnes
3. Transaktioner med Periode-Reference
3.1 Udvidet Transaction Model
interface Transaction {
id: string;
companyId: string;
fiscalYearId: string; // PÅKRÆVET - hvilket regnskabsår
periodId: string; // PÅKRÆVET - hvilken periode
journalEntryNumber: number;
date: string;
description: string;
reference?: string;
lines: TransactionLine[];
isVoided: boolean;
isReconciled: boolean;
isClosingEntry?: boolean; // NY - markering for lukkepost
isOpeningBalance?: boolean; // NY - markering for åbningsbalance
createdAt: string;
updatedAt: string;
createdBy: string;
}
3.2 Forretningsregler for Bogføring
interface PostingValidation {
allowed: boolean;
reason?: string;
reasonDanish?: string;
}
Backend skal validere ved bogføring:
- Find periode for transaktionsdato
- Tjek om perioden er åben
- Tjek om regnskabsåret er åbent
- Returner fejl med dansk besked hvis ikke tilladt
4. Kontoplan (Chart of Accounts)
4.1 Data Model
interface Account {
id: string;
companyId: string;
accountNumber: string; // "1000", "3900"
name: string;
type: AccountType;
parentId?: string;
description?: string;
vatCodeId?: string;
isActive: boolean;
isSystemAccount: boolean; // Kan ikke slettes
balance: number; // Beregnet felt
createdAt: string;
updatedAt: string;
}
type AccountType =
| 'asset' // Aktiver
| 'liability' // Passiver
| 'equity' // Egenkapital
| 'revenue' // Indtægter
| 'cogs' // Vareforbrug
| 'expense' // Udgifter
| 'personnel' // Personaleomkostninger
| 'financial' // Finansielle poster
| 'extraordinary'; // Ekstraordinære poster
4.2 Systemkonti
Følgende konti skal oprettes automatisk:
3900- Overført resultat (equity) - bruges til årsafslutning- Åbningsbalancekonto for primo-posteringer
5. Moms (VAT)
5.1 Data Model
interface VATCode {
id: string;
companyId: string;
code: string; // "S25", "K25"
name: string;
rate: number; // 0.25 for 25%
type: 'sales' | 'purchase' | 'eu_sales' | 'eu_purchase' | 'reverse_charge';
accountId: string; // Momskonto
isActive: boolean;
}
interface VATPeriod {
id: string;
companyId: string;
startDate: string;
endDate: string;
dueDate: string; // Indberetningsfrist
status: 'open' | 'submitted' | 'paid';
salesVAT: number; // Beregnet
purchaseVAT: number; // Beregnet
netVAT: number; // salesVAT - purchaseVAT
submittedAt?: string;
paidAt?: string;
}
5.2 SKAT Integration (Fremtidig)
- Momsindberetning til SKAT via NemVirksomhed API
- Kvartalsvise eller månedlige momsperioder baseret på virksomhedsstørrelse
6. Virksomhed (Company)
6.1 Data Model
interface Company {
id: string;
name: string;
cvr: string; // Dansk CVR-nummer
address?: string;
postalCode?: string;
city?: string;
country: string; // Default "DK"
fiscalYearStart: number; // Måned 1-12 (default 1 for januar)
currency: string; // Default "DKK"
vatRegistered: boolean;
vatPeriodFrequency: 'monthly' | 'quarterly' | 'half-yearly';
createdAt: string;
updatedAt: string;
}
7. Fejlhåndtering
Alle fejlbeskeder skal være tilgængelige på både engelsk og dansk:
interface APIError {
code: string;
message: string;
messageDanish: string;
details?: Record<string, unknown>;
}
Eksempler på fejlkoder
| Kode | Engelsk | Dansk |
|---|---|---|
PERIOD_LOCKED |
Period is locked | Perioden er låst |
FISCAL_YEAR_LOCKED |
Fiscal year is locked | Regnskabsåret er låst |
OVERLAP_EXISTS |
Overlaps with existing fiscal year | Overlapper med eksisterende regnskabsår |
UNBALANCED_ENTRY |
Debit and credit must be equal | Debet og kredit skal være ens |
NO_COMPANY_SELECTED |
No company selected | Ingen virksomhed valgt |
8. Prioriteret Implementeringsrækkefølge
Fase 1: Grundlæggende (MVP)
- Company CRUD
- Account CRUD med standard kontoplan
- Transaction CRUD med validering
- FiscalYear CRUD
Fase 2: Periode-management
- AccountingPeriod generering og CRUD
- Periode-validering ved bogføring
- Periode lukning/åbning
Fase 3: Årsafslutning
- Closing entries generering og bogføring
- Opening balance beregning og bogføring
- Dynamisk åbningsbalance-opdatering
Fase 4: Moms
- VATCode CRUD
- VATPeriod generering
- Momsberegning og -rapportering
9. Tekniske Krav
Authentication
- JWT-baseret authentication
- Multi-tenant support (bruger kan have adgang til flere virksomheder)
Database
- PostgreSQL anbefales
- Soft delete på alle entiteter
- Audit trail (createdBy, updatedBy, deletedBy)
API Format
- RESTful JSON API
- GraphQL som alternativ (valgfrit)
- Pagination på liste-endpoints
- Filtering og sorting support
Valuta
- Alle beløb i øre/cents (integers) for at undgå floating point problemer
- Frontend konverterer til/fra DKK ved visning
Sidst opdateret: 17. januar 2026