356 lines
9.6 KiB
Markdown
356 lines
9.6 KiB
Markdown
|
|
# 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
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
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:
|
||
|
|
|
||
|
|
1. **Validere** at regnskabsåret kan lukkes:
|
||
|
|
- Ikke allerede låst
|
||
|
|
- Advar om åbne perioder (men tillad lukning)
|
||
|
|
- Advar om ikke-afstemte transaktioner
|
||
|
|
|
||
|
|
2. **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`)
|
||
|
|
|
||
|
|
3. **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`)
|
||
|
|
|
||
|
|
4. **Opdatere status**:
|
||
|
|
- Sæt regnskabsår status til 'closed' eller 'locked'
|
||
|
|
- Luk alle tilhørende regnskabsperioder
|
||
|
|
- Sæt `openingBalancePosted: true` på 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
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
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
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
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
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
interface PostingValidation {
|
||
|
|
allowed: boolean;
|
||
|
|
reason?: string;
|
||
|
|
reasonDanish?: string;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Backend skal validere ved bogføring:
|
||
|
|
1. Find periode for transaktionsdato
|
||
|
|
2. Tjek om perioden er åben
|
||
|
|
3. Tjek om regnskabsåret er åbent
|
||
|
|
4. Returner fejl med dansk besked hvis ikke tilladt
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 4. Kontoplan (Chart of Accounts)
|
||
|
|
|
||
|
|
### 4.1 Data Model
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
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
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
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
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
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:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
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)
|
||
|
|
1. Company CRUD
|
||
|
|
2. Account CRUD med standard kontoplan
|
||
|
|
3. Transaction CRUD med validering
|
||
|
|
4. FiscalYear CRUD
|
||
|
|
|
||
|
|
### Fase 2: Periode-management
|
||
|
|
1. AccountingPeriod generering og CRUD
|
||
|
|
2. Periode-validering ved bogføring
|
||
|
|
3. Periode lukning/åbning
|
||
|
|
|
||
|
|
### Fase 3: Årsafslutning
|
||
|
|
1. Closing entries generering og bogføring
|
||
|
|
2. Opening balance beregning og bogføring
|
||
|
|
3. Dynamisk åbningsbalance-opdatering
|
||
|
|
|
||
|
|
### Fase 4: Moms
|
||
|
|
1. VATCode CRUD
|
||
|
|
2. VATPeriod generering
|
||
|
|
3. 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*
|