1537 lines
57 KiB
Markdown
1537 lines
57 KiB
Markdown
|
|
# aamos-billing — Designdokument
|
||
|
|
## LandveX Enterprise Platform — Faktureringsmodul
|
||
|
|
|
||
|
|
**Version:** 1.0
|
||
|
|
**Datum:** 2026-07-02
|
||
|
|
**Status:** Design / för granskning
|
||
|
|
**Ansvarig:** LandveX Architecture Team
|
||
|
|
**Beroenden:** aamos-ledger (bokföringsmotor), Hermes Event Fabric
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 1. Översikt
|
||
|
|
|
||
|
|
Faktureringsmodulen (`aamos-billing`) hanterar kommunala fakturor, avtal, återkommande debitering och integration med externa ekonomisystem. Den är byggd som en självständig mikrotjänst som kommunicerar med ledgern via events (Hermes) och ett väldefinierat API.
|
||
|
|
|
||
|
|
### 1.1 Designprinciper (samma som ledger)
|
||
|
|
|
||
|
|
| Princip | Beskrivning |
|
||
|
|
|---------|-------------|
|
||
|
|
| **Audit First** | Varje operation genererar auditpost |
|
||
|
|
| **Tenant Isolation** | `tenant_id` filtrerar all data, kunddata korsas aldrig |
|
||
|
|
| **Hermes Events** | Ekonomiska händelser publiceras via event fabric |
|
||
|
|
| **Immutable Journal** | Bokförda fakturor ändras aldrig; kreditering = ny post |
|
||
|
|
| **Determinism** | Alla beslut reproducerbara och spårbara |
|
||
|
|
| **No Direct Dependencies** | Inga hårda beroenden till andra moduler utöver Hermes |
|
||
|
|
|
||
|
|
### 1.2 Modulens placering i plattformen
|
||
|
|
|
||
|
|
```
|
||
|
|
┌─────────────────────────────────────────────────────────────┐
|
||
|
|
│ LandveX Enterprise │
|
||
|
|
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
|
||
|
|
│ │ Identity │ │ Hermes │ │ Ouroboros UI │ │
|
||
|
|
│ │ Service │ │ Events │ │ (React/Vue) │ │
|
||
|
|
│ └──────┬──────┘ └──────┬──────┘ └──────────┬──────────┘ │
|
||
|
|
│ │ │ │ │
|
||
|
|
│ ┌──────┴────────────────┴────────────────────┴──────────┐ │
|
||
|
|
│ │ API Gateway / Reverse Proxy │ │
|
||
|
|
│ └──────┬────────────────┬────────────────────┬──────────┘ │
|
||
|
|
│ │ │ │ │
|
||
|
|
│ ┌──────▼──────┐ ┌──────▼──────┐ ┌─────────▼──────────┐ │
|
||
|
|
│ │ aamos- │ │ aamos- │ │ aamos-billing │ │
|
||
|
|
│ │ ledger │◄─┤ billing │ │ (DENNA MODUL) │ │
|
||
|
|
│ │ (3250) │ │ (3260) │ │ Port: 3260 │ │
|
||
|
|
│ │ Bokföring │ │ Fakturering │ │ │ │
|
||
|
|
│ └─────────────┘ └──────┬──────┘ └────────────────────┘ │
|
||
|
|
│ │ │
|
||
|
|
│ ┌───────────┼───────────┐ │
|
||
|
|
│ ▼ ▼ ▼ │
|
||
|
|
│ ┌────────┐ ┌────────┐ ┌────────┐ │
|
||
|
|
│ │ Visma │ │Agresso │ │ SIE4 │ │
|
||
|
|
│ │ │ │ │ │ Export │ │
|
||
|
|
│ └────────┘ └────────┘ └────────┘ │
|
||
|
|
└─────────────────────────────────────────────────────────────┘
|
||
|
|
```
|
||
|
|
|
||
|
|
### 1.3 Kommunikationsmönster
|
||
|
|
|
||
|
|
- **aamos-billing → aamos-ledger:** Via Hermes events (`billing.invoice.created` → ledger skapar journalpost)
|
||
|
|
- **aamos-ledger → aamos-billing:** Via Hermes events (`finance.journal.posted` → billing uppdaterar fakturastatus)
|
||
|
|
- **UI → aamos-billing:** REST API (JSON)
|
||
|
|
- **aamos-billing → Externa system:** Adapter-mönster (pluggbara exporters)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 2. Datamodell
|
||
|
|
|
||
|
|
### 2.1 Entiteter — Översikt
|
||
|
|
|
||
|
|
```
|
||
|
|
┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
|
||
|
|
│ billing_agreements│ │ billing_invoices │ │billing_invoice_lines│
|
||
|
|
│ (Avtal) │◄────┤ (Fakturor) │◄────┤ (Fakturarader) │
|
||
|
|
└─────────────────────┘ └─────────────────────┘ └─────────────────────┘
|
||
|
|
│ │
|
||
|
|
│ ┌──────┴──────┐
|
||
|
|
│ ▼ ▼
|
||
|
|
│ ┌─────────────┐ ┌─────────────┐
|
||
|
|
│ │billing_payments│ │billing_reminders│
|
||
|
|
│ │(Betalningar) │ │(Påminnelser) │
|
||
|
|
│ └─────────────┘ └─────────────┘
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
┌─────────────────────┐
|
||
|
|
│billing_schedules │
|
||
|
|
│(Schemaläggning) │
|
||
|
|
└─────────────────────┘
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2.2 Tabell: `billing_agreements` — Avtal
|
||
|
|
|
||
|
|
```sql
|
||
|
|
CREATE TABLE IF NOT EXISTS billing_agreements (
|
||
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
|
|
tenant_id TEXT NOT NULL,
|
||
|
|
|
||
|
|
-- Avtalsidentitet
|
||
|
|
agreement_number TEXT NOT NULL, -- t.ex. "KOM-2026-001"
|
||
|
|
agreement_type TEXT NOT NULL, -- 'kommunalt'|'kommersiellt'|'internt'
|
||
|
|
status TEXT NOT NULL DEFAULT 'draft', -- 'draft'|'active'|'suspended'|'terminated'|'expired'
|
||
|
|
|
||
|
|
-- Parter
|
||
|
|
customer_type TEXT NOT NULL, -- 'municipality'|'company'|'individual'
|
||
|
|
customer_org_nr TEXT, -- org.nr för företag/kommun
|
||
|
|
customer_name TEXT NOT NULL,
|
||
|
|
customer_address TEXT,
|
||
|
|
customer_postal TEXT,
|
||
|
|
customer_city TEXT,
|
||
|
|
customer_contact TEXT, -- kontaktperson
|
||
|
|
customer_email TEXT,
|
||
|
|
customer_phone TEXT,
|
||
|
|
customer_reference TEXT, -- er referens (t.ex. "Projektledare Svensson")
|
||
|
|
|
||
|
|
-- Avtalsvillkor
|
||
|
|
title TEXT NOT NULL,
|
||
|
|
description TEXT,
|
||
|
|
start_date DATE NOT NULL,
|
||
|
|
end_date DATE,
|
||
|
|
auto_renew BOOLEAN NOT NULL DEFAULT FALSE,
|
||
|
|
renewal_period_months INTEGER, -- t.ex. 12 för årlig förlängning
|
||
|
|
termination_notice_days INTEGER DEFAULT 90, -- uppsägningstid i dagar
|
||
|
|
|
||
|
|
-- Ekonomi
|
||
|
|
currency TEXT NOT NULL DEFAULT 'SEK',
|
||
|
|
billing_frequency TEXT NOT NULL, -- 'monthly'|'quarterly'|'semi_annual'|'annual'|'one_time'
|
||
|
|
billing_day INTEGER NOT NULL DEFAULT 1, -- dag i månaden för fakturering
|
||
|
|
payment_terms_days INTEGER NOT NULL DEFAULT 30, -- nettodagar
|
||
|
|
late_fee_percent NUMERIC(5,2) DEFAULT 8.0, -- dröjsmålsränta (%)
|
||
|
|
vat_rate NUMERIC(5,2) NOT NULL DEFAULT 25.0,
|
||
|
|
|
||
|
|
-- Bokföring
|
||
|
|
revenue_account TEXT NOT NULL DEFAULT '3000', -- intäktskonto
|
||
|
|
receivable_account TEXT NOT NULL DEFAULT '1510', -- kundfordringskonto
|
||
|
|
vat_account TEXT NOT NULL DEFAULT '2610', -- momskonto
|
||
|
|
|
||
|
|
-- Integration
|
||
|
|
external_system_id TEXT, -- ID i externt system (Visma, Agresso)
|
||
|
|
external_system TEXT, -- 'visma'|'agresso'|'fortnox'|'custom'
|
||
|
|
|
||
|
|
-- Metadata & spårbarhet
|
||
|
|
metadata JSONB NOT NULL DEFAULT '{}',
|
||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
|
|
created_by TEXT NOT NULL,
|
||
|
|
updated_by TEXT NOT NULL,
|
||
|
|
|
||
|
|
-- Audit
|
||
|
|
trace_id TEXT NOT NULL,
|
||
|
|
|
||
|
|
UNIQUE (tenant_id, agreement_number)
|
||
|
|
);
|
||
|
|
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_agreements_tenant_status
|
||
|
|
ON billing_agreements (tenant_id, status);
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_agreements_customer
|
||
|
|
ON billing_agreements (tenant_id, customer_org_nr);
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_agreements_dates
|
||
|
|
ON billing_agreements (tenant_id, start_date, end_date);
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2.3 Tabell: `billing_agreement_lines` — Avtalsrader (återkommande poster)
|
||
|
|
|
||
|
|
```sql
|
||
|
|
CREATE TABLE IF NOT EXISTS billing_agreement_lines (
|
||
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
|
|
agreement_id UUID NOT NULL REFERENCES billing_agreements(id) ON DELETE CASCADE,
|
||
|
|
tenant_id TEXT NOT NULL,
|
||
|
|
|
||
|
|
line_number INTEGER NOT NULL, -- rad 1, 2, 3...
|
||
|
|
description TEXT NOT NULL, -- t.ex. "Drift av VA-nät"
|
||
|
|
quantity NUMERIC(12,2) NOT NULL DEFAULT 1,
|
||
|
|
unit TEXT, -- 'st'|'tim'|'mån'|'år'|'m²'
|
||
|
|
unit_price NUMERIC(18,2) NOT NULL, -- pris per enhet exkl. moms
|
||
|
|
vat_rate NUMERIC(5,2), -- överstyrning per rad, NULL = arv från avtal
|
||
|
|
discount_percent NUMERIC(5,2) DEFAULT 0, -- rabatt %
|
||
|
|
discount_amount NUMERIC(18,2), -- fast rabattbelopp
|
||
|
|
|
||
|
|
-- Bokföring per rad
|
||
|
|
revenue_account TEXT, -- NULL = arv från avtal
|
||
|
|
cost_center TEXT,
|
||
|
|
project_code TEXT,
|
||
|
|
|
||
|
|
-- Aktivering
|
||
|
|
active_from DATE NOT NULL DEFAULT CURRENT_DATE,
|
||
|
|
active_until DATE,
|
||
|
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||
|
|
|
||
|
|
metadata JSONB NOT NULL DEFAULT '{}',
|
||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
|
|
|
||
|
|
UNIQUE (agreement_id, line_number)
|
||
|
|
);
|
||
|
|
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_agreement_lines_agreement
|
||
|
|
ON billing_agreement_lines (agreement_id);
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_agreement_lines_active
|
||
|
|
ON billing_agreement_lines (agreement_id, is_active, active_from, active_until);
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2.4 Tabell: `billing_invoices` — Fakturor
|
||
|
|
|
||
|
|
```sql
|
||
|
|
CREATE TABLE IF NOT EXISTS billing_invoices (
|
||
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
|
|
tenant_id TEXT NOT NULL,
|
||
|
|
|
||
|
|
-- Fakturaidentitet
|
||
|
|
invoice_number TEXT NOT NULL, -- t.ex. "F-2026-00042"
|
||
|
|
agreement_id UUID REFERENCES billing_agreements(id),
|
||
|
|
invoice_type TEXT NOT NULL DEFAULT 'standard', -- 'standard'|'credit'|'proforma'|'reminder'
|
||
|
|
status TEXT NOT NULL DEFAULT 'draft', -- 'draft'|'sent'|'paid'|'partially_paid'|'overdue'|'cancelled'|'credited'
|
||
|
|
|
||
|
|
-- Kundinfo (denormaliserat — snapshot vid fakturering)
|
||
|
|
customer_org_nr TEXT,
|
||
|
|
customer_name TEXT NOT NULL,
|
||
|
|
customer_address TEXT,
|
||
|
|
customer_postal TEXT,
|
||
|
|
customer_city TEXT,
|
||
|
|
customer_contact TEXT,
|
||
|
|
customer_email TEXT,
|
||
|
|
customer_reference TEXT,
|
||
|
|
|
||
|
|
-- Datum
|
||
|
|
invoice_date DATE NOT NULL,
|
||
|
|
due_date DATE NOT NULL,
|
||
|
|
delivery_date DATE, -- leveransdatum (momsregel)
|
||
|
|
sent_at TIMESTAMPTZ,
|
||
|
|
paid_at TIMESTAMPTZ,
|
||
|
|
credited_at TIMESTAMPTZ,
|
||
|
|
|
||
|
|
-- Belopp
|
||
|
|
currency TEXT NOT NULL DEFAULT 'SEK',
|
||
|
|
subtotal NUMERIC(18,2) NOT NULL DEFAULT 0, -- netto
|
||
|
|
vat_total NUMERIC(18,2) NOT NULL DEFAULT 0,
|
||
|
|
total NUMERIC(18,2) NOT NULL DEFAULT 0, -- inkl. moms
|
||
|
|
amount_paid NUMERIC(18,2) NOT NULL DEFAULT 0,
|
||
|
|
amount_due NUMERIC(18,2) NOT NULL DEFAULT 0,
|
||
|
|
|
||
|
|
-- Moms-specifikation (JSONB för flexibilitet)
|
||
|
|
vat_specification JSONB NOT NULL DEFAULT '[]',
|
||
|
|
-- Exempel: [{"rate": 25.0, "base": 10000.00, "vat": 2500.00}, {"rate": 12.0, ...}]
|
||
|
|
|
||
|
|
-- Bokföring
|
||
|
|
journal_entry_id UUID, -- referens till ledger_journal_entries
|
||
|
|
ledger_posted_at TIMESTAMPTZ, -- när bokförd
|
||
|
|
|
||
|
|
-- Kreditering
|
||
|
|
credited_by_invoice_id UUID, -- referens till kreditfaktura
|
||
|
|
original_invoice_id UUID, -- om detta är en kreditfaktura
|
||
|
|
|
||
|
|
-- Påminnelse
|
||
|
|
reminder_count INTEGER NOT NULL DEFAULT 0,
|
||
|
|
last_reminder_at TIMESTAMPTZ,
|
||
|
|
reminder_fee NUMERIC(18,2) DEFAULT 0,
|
||
|
|
interest_fee NUMERIC(18,2) DEFAULT 0,
|
||
|
|
|
||
|
|
-- PDF / dokument
|
||
|
|
pdf_generated_at TIMESTAMPTZ,
|
||
|
|
pdf_url TEXT, -- S3/MinIO-länk
|
||
|
|
|
||
|
|
-- Extern integration
|
||
|
|
external_system_id TEXT, -- ID i Visma/Agresso
|
||
|
|
external_system TEXT,
|
||
|
|
exported_at TIMESTAMPTZ,
|
||
|
|
|
||
|
|
-- Metadata & spårbarhet
|
||
|
|
metadata JSONB NOT NULL DEFAULT '{}',
|
||
|
|
notes TEXT, -- interna anteckningar
|
||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
|
|
created_by TEXT NOT NULL,
|
||
|
|
updated_by TEXT NOT NULL,
|
||
|
|
|
||
|
|
trace_id TEXT NOT NULL,
|
||
|
|
correlation_id TEXT NOT NULL,
|
||
|
|
|
||
|
|
UNIQUE (tenant_id, invoice_number)
|
||
|
|
);
|
||
|
|
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_invoices_tenant_status
|
||
|
|
ON billing_invoices (tenant_id, status);
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_invoices_agreement
|
||
|
|
ON billing_invoices (agreement_id);
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_invoices_dates
|
||
|
|
ON billing_invoices (tenant_id, invoice_date, due_date);
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_invoices_overdue
|
||
|
|
ON billing_invoices (tenant_id, status, due_date)
|
||
|
|
WHERE status IN ('sent', 'partially_paid');
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_invoices_journal
|
||
|
|
ON billing_invoices (journal_entry_id);
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_invoices_external
|
||
|
|
ON billing_invoices (tenant_id, external_system, external_system_id);
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2.5 Tabell: `billing_invoice_lines` — Fakturarader
|
||
|
|
|
||
|
|
```sql
|
||
|
|
CREATE TABLE IF NOT EXISTS billing_invoice_lines (
|
||
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
|
|
invoice_id UUID NOT NULL REFERENCES billing_invoices(id) ON DELETE CASCADE,
|
||
|
|
tenant_id TEXT NOT NULL,
|
||
|
|
|
||
|
|
line_number INTEGER NOT NULL,
|
||
|
|
description TEXT NOT NULL,
|
||
|
|
quantity NUMERIC(12,2) NOT NULL DEFAULT 1,
|
||
|
|
unit TEXT,
|
||
|
|
unit_price NUMERIC(18,2) NOT NULL,
|
||
|
|
line_total NUMERIC(18,2) NOT NULL, -- quantity * unit_price (netto)
|
||
|
|
|
||
|
|
vat_rate NUMERIC(5,2) NOT NULL,
|
||
|
|
vat_amount NUMERIC(18,2) NOT NULL,
|
||
|
|
line_total_incl_vat NUMERIC(18,2) NOT NULL, -- line_total + vat_amount
|
||
|
|
|
||
|
|
-- Koppling till avtalsrad
|
||
|
|
agreement_line_id UUID REFERENCES billing_agreement_lines(id),
|
||
|
|
|
||
|
|
-- Bokföring
|
||
|
|
revenue_account TEXT NOT NULL,
|
||
|
|
cost_center TEXT,
|
||
|
|
project_code TEXT,
|
||
|
|
|
||
|
|
metadata JSONB NOT NULL DEFAULT '{}',
|
||
|
|
|
||
|
|
UNIQUE (invoice_id, line_number)
|
||
|
|
);
|
||
|
|
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_invoice_lines_invoice
|
||
|
|
ON billing_invoice_lines (invoice_id);
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_invoice_lines_account
|
||
|
|
ON billing_invoice_lines (tenant_id, revenue_account);
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2.6 Tabell: `billing_payments` — Inbetalningar
|
||
|
|
|
||
|
|
```sql
|
||
|
|
CREATE TABLE IF NOT EXISTS billing_payments (
|
||
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
|
|
tenant_id TEXT NOT NULL,
|
||
|
|
|
||
|
|
invoice_id UUID NOT NULL REFERENCES billing_invoices(id),
|
||
|
|
payment_date DATE NOT NULL,
|
||
|
|
amount NUMERIC(18,2) NOT NULL,
|
||
|
|
currency TEXT NOT NULL DEFAULT 'SEK',
|
||
|
|
|
||
|
|
-- Betalningskälla
|
||
|
|
payment_method TEXT NOT NULL, -- 'bank_transfer'|'swish'|'card'|'cash'|'autogiro'|'other'
|
||
|
|
payment_reference TEXT, -- OCR-nummer, betalningsreferens
|
||
|
|
bank_transaction_id TEXT, -- referens från banken
|
||
|
|
|
||
|
|
-- Avstämningsstatus
|
||
|
|
reconciliation_status TEXT NOT NULL DEFAULT 'unreconciled', -- 'unreconciled'|'matched'|'reconciled'
|
||
|
|
reconciled_at TIMESTAMPTZ,
|
||
|
|
reconciled_by TEXT,
|
||
|
|
|
||
|
|
-- Bokföring
|
||
|
|
journal_entry_id UUID, -- referens till ledger
|
||
|
|
|
||
|
|
metadata JSONB NOT NULL DEFAULT '{}',
|
||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
|
|
created_by TEXT NOT NULL,
|
||
|
|
trace_id TEXT NOT NULL,
|
||
|
|
|
||
|
|
UNIQUE (tenant_id, bank_transaction_id)
|
||
|
|
);
|
||
|
|
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_payments_invoice
|
||
|
|
ON billing_payments (invoice_id);
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_payments_reconciliation
|
||
|
|
ON billing_payments (tenant_id, reconciliation_status);
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_payments_date
|
||
|
|
ON billing_payments (tenant_id, payment_date);
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2.7 Tabell: `billing_reminders` — Påminnelser / Inkasso
|
||
|
|
|
||
|
|
```sql
|
||
|
|
CREATE TABLE IF NOT EXISTS billing_reminders (
|
||
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
|
|
tenant_id TEXT NOT NULL,
|
||
|
|
invoice_id UUID NOT NULL REFERENCES billing_invoices(id),
|
||
|
|
|
||
|
|
reminder_number INTEGER NOT NULL, -- 1, 2, 3...
|
||
|
|
reminder_type TEXT NOT NULL, -- 'reminder'|'debt_collection'|'legal'
|
||
|
|
sent_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
|
|
due_date DATE NOT NULL,
|
||
|
|
|
||
|
|
-- Avgifter
|
||
|
|
reminder_fee NUMERIC(18,2) NOT NULL DEFAULT 0,
|
||
|
|
interest_fee NUMERIC(18,2) NOT NULL DEFAULT 0,
|
||
|
|
total_demand NUMERIC(18,2) NOT NULL, -- fakturabelopp + avgifter
|
||
|
|
|
||
|
|
-- Status
|
||
|
|
status TEXT NOT NULL DEFAULT 'sent', -- 'sent'|'paid'|'escalated'|'cancelled'
|
||
|
|
|
||
|
|
-- Kommunikation
|
||
|
|
sent_via TEXT, -- 'email'|'post'|'sms'
|
||
|
|
email_address TEXT,
|
||
|
|
pdf_url TEXT,
|
||
|
|
|
||
|
|
metadata JSONB NOT NULL DEFAULT '{}',
|
||
|
|
created_by TEXT NOT NULL,
|
||
|
|
trace_id TEXT NOT NULL
|
||
|
|
);
|
||
|
|
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_reminders_invoice
|
||
|
|
ON billing_reminders (invoice_id);
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_reminders_status
|
||
|
|
ON billing_reminders (tenant_id, status);
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2.8 Tabell: `billing_schedules` — Schemaläggning (cron-jobb)
|
||
|
|
|
||
|
|
```sql
|
||
|
|
CREATE TABLE IF NOT EXISTS billing_schedules (
|
||
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
|
|
tenant_id TEXT NOT NULL,
|
||
|
|
agreement_id UUID NOT NULL REFERENCES billing_agreements(id) ON DELETE CASCADE,
|
||
|
|
|
||
|
|
-- Schemaläggning
|
||
|
|
schedule_type TEXT NOT NULL, -- 'recurring'|'one_time'
|
||
|
|
frequency TEXT NOT NULL, -- 'monthly'|'quarterly'|'semi_annual'|'annual'
|
||
|
|
day_of_month INTEGER NOT NULL DEFAULT 1,
|
||
|
|
next_run_date DATE NOT NULL,
|
||
|
|
last_run_date DATE,
|
||
|
|
last_invoice_id UUID REFERENCES billing_invoices(id),
|
||
|
|
|
||
|
|
-- Status
|
||
|
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||
|
|
paused_until DATE,
|
||
|
|
|
||
|
|
-- Resultat
|
||
|
|
runs_count INTEGER NOT NULL DEFAULT 0,
|
||
|
|
failures_count INTEGER NOT NULL DEFAULT 0,
|
||
|
|
last_error TEXT,
|
||
|
|
|
||
|
|
metadata JSONB NOT NULL DEFAULT '{}',
|
||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
|
|
|
||
|
|
UNIQUE (tenant_id, agreement_id, schedule_type)
|
||
|
|
);
|
||
|
|
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_schedules_next_run
|
||
|
|
ON billing_schedules (next_run_date, is_active)
|
||
|
|
WHERE is_active = TRUE;
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2.9 Tabell: `billing_audit_log` — Audit trail
|
||
|
|
|
||
|
|
```sql
|
||
|
|
CREATE TABLE IF NOT EXISTS billing_audit_log (
|
||
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
|
|
tenant_id TEXT NOT NULL,
|
||
|
|
trace_id TEXT NOT NULL,
|
||
|
|
correlation_id TEXT NOT NULL,
|
||
|
|
user_id TEXT NOT NULL,
|
||
|
|
entity_type TEXT NOT NULL, -- 'agreement'|'invoice'|'payment'|'reminder'
|
||
|
|
entity_id TEXT NOT NULL,
|
||
|
|
action TEXT NOT NULL, -- 'created'|'updated'|'sent'|'paid'|'cancelled'|'credited'|'reminded'
|
||
|
|
decision_source TEXT NOT NULL DEFAULT 'user', -- 'user'|'system'|'agent'
|
||
|
|
before_state JSONB,
|
||
|
|
after_state JSONB,
|
||
|
|
ip_address TEXT,
|
||
|
|
session_id TEXT,
|
||
|
|
ts TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||
|
|
);
|
||
|
|
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_billing_audit_entity
|
||
|
|
ON billing_audit_log (tenant_id, entity_type, entity_id);
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_billing_audit_trace
|
||
|
|
ON billing_audit_log (trace_id);
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_billing_audit_ts
|
||
|
|
ON billing_audit_log (tenant_id, ts);
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2.10 Tabell: `billing_export_jobs` — Export-jobb (SIE4, PDF, etc.)
|
||
|
|
|
||
|
|
```sql
|
||
|
|
CREATE TABLE IF NOT EXISTS billing_export_jobs (
|
||
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
|
|
tenant_id TEXT NOT NULL,
|
||
|
|
|
||
|
|
job_type TEXT NOT NULL, -- 'sie4'|'pdf'|'csv'|'visma'|'agresso'
|
||
|
|
job_status TEXT NOT NULL DEFAULT 'pending', -- 'pending'|'running'|'completed'|'failed'
|
||
|
|
|
||
|
|
-- Filter
|
||
|
|
period_from TEXT, -- 'YYYY-MM'
|
||
|
|
period_to TEXT,
|
||
|
|
fiscal_year INTEGER,
|
||
|
|
invoice_ids UUID[], -- specifika fakturor
|
||
|
|
agreement_ids UUID[], -- specifika avtal
|
||
|
|
|
||
|
|
-- Resultat
|
||
|
|
file_url TEXT, -- S3/MinIO-länk
|
||
|
|
file_size_bytes INTEGER,
|
||
|
|
record_count INTEGER,
|
||
|
|
error_message TEXT,
|
||
|
|
|
||
|
|
-- Metadata
|
||
|
|
requested_by TEXT NOT NULL,
|
||
|
|
started_at TIMESTAMPTZ,
|
||
|
|
completed_at TIMESTAMPTZ,
|
||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
|
|
trace_id TEXT NOT NULL
|
||
|
|
);
|
||
|
|
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_export_jobs_tenant
|
||
|
|
ON billing_export_jobs (tenant_id, job_status);
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 3. API — REST Endpoints
|
||
|
|
|
||
|
|
### 3.1 Konventioner
|
||
|
|
|
||
|
|
- **Bas-URL:** `https://api.landvex.com/billing` (port 3260 internt)
|
||
|
|
- **Auth:** JWT via `Authorization: Bearer <token>`
|
||
|
|
- **Tenant:** `X-Tenant-ID: <tenant>`
|
||
|
|
- **Trace:** `X-Trace-ID`, `X-Correlation-ID`
|
||
|
|
- **Svar:** `{ ok: true/false, data: ..., error: ..., meta: {...} }`
|
||
|
|
- **Paginering:** `?page=1&limit=50`, svar inkluderar `meta.total`, `meta.page`, `meta.pages`
|
||
|
|
|
||
|
|
### 3.2 Avtal (Agreements)
|
||
|
|
|
||
|
|
```
|
||
|
|
GET /api/billing/agreements
|
||
|
|
Query: ?status=active&customer_org_nr=559141-7042&page=1&limit=50
|
||
|
|
Response: { ok: true, agreements: [...], meta: { total: 120, page: 1, pages: 3 } }
|
||
|
|
|
||
|
|
GET /api/billing/agreements/:id
|
||
|
|
Response: { ok: true, agreement: {...}, lines: [...], schedules: [...] }
|
||
|
|
|
||
|
|
POST /api/billing/agreements
|
||
|
|
Body: { agreement_number, agreement_type, customer_type, customer_org_nr,
|
||
|
|
customer_name, customer_address, ..., title, start_date, end_date,
|
||
|
|
billing_frequency, billing_day, payment_terms_days, ... }
|
||
|
|
Response: 201 { ok: true, agreement: {...} }
|
||
|
|
|
||
|
|
PUT /api/billing/agreements/:id
|
||
|
|
Body: { ...uppdaterbara fält... }
|
||
|
|
Response: { ok: true, agreement: {...} }
|
||
|
|
|
||
|
|
POST /api/billing/agreements/:id/activate
|
||
|
|
Body: { activation_date }
|
||
|
|
Response: { ok: true, agreement: { status: 'active' } }
|
||
|
|
|
||
|
|
POST /api/billing/agreements/:id/terminate
|
||
|
|
Body: { termination_date, reason }
|
||
|
|
Response: { ok: true, agreement: { status: 'terminated' } }
|
||
|
|
|
||
|
|
POST /api/billing/agreements/:id/suspend
|
||
|
|
Body: { reason, resume_date }
|
||
|
|
Response: { ok: true, agreement: { status: 'suspended' } }
|
||
|
|
|
||
|
|
POST /api/billing/agreements/:id/resume
|
||
|
|
Response: { ok: true, agreement: { status: 'active' } }
|
||
|
|
|
||
|
|
DELETE /api/billing/agreements/:id
|
||
|
|
Response: { ok: true } (endast om status=draft och inga fakturor)
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3.3 Avtalsrader (Agreement Lines)
|
||
|
|
|
||
|
|
```
|
||
|
|
GET /api/billing/agreements/:id/lines
|
||
|
|
Response: { ok: true, lines: [...] }
|
||
|
|
|
||
|
|
POST /api/billing/agreements/:id/lines
|
||
|
|
Body: { description, quantity, unit, unit_price, vat_rate,
|
||
|
|
revenue_account, cost_center, project_code, ... }
|
||
|
|
Response: 201 { ok: true, line: {...} }
|
||
|
|
|
||
|
|
PUT /api/billing/agreements/:id/lines/:lineId
|
||
|
|
Body: { ... }
|
||
|
|
Response: { ok: true, line: {...} }
|
||
|
|
|
||
|
|
DELETE /api/billing/agreements/:id/lines/:lineId
|
||
|
|
Response: { ok: true }
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3.4 Fakturor (Invoices)
|
||
|
|
|
||
|
|
```
|
||
|
|
GET /api/billing/invoices
|
||
|
|
Query: ?status=sent&period=2026-07&customer_org_nr=...&overdue=true&page=1
|
||
|
|
Response: { ok: true, invoices: [...], meta: {...} }
|
||
|
|
|
||
|
|
GET /api/billing/invoices/:id
|
||
|
|
Response: { ok: true, invoice: {...}, lines: [...], payments: [...], reminders: [...] }
|
||
|
|
|
||
|
|
POST /api/billing/invoices
|
||
|
|
Body: {
|
||
|
|
invoice_type: 'standard',
|
||
|
|
customer_org_nr, customer_name, customer_address, ...,
|
||
|
|
invoice_date, due_date, delivery_date,
|
||
|
|
lines: [
|
||
|
|
{ description, quantity, unit, unit_price, vat_rate,
|
||
|
|
revenue_account, cost_center, project_code }
|
||
|
|
],
|
||
|
|
notes
|
||
|
|
}
|
||
|
|
Response: 201 { ok: true, invoice: {...} }
|
||
|
|
|
||
|
|
PUT /api/billing/invoices/:id
|
||
|
|
Body: { ...uppdaterbara fält (endast draft)... }
|
||
|
|
Response: { ok: true, invoice: {...} }
|
||
|
|
|
||
|
|
POST /api/billing/invoices/:id/send
|
||
|
|
Body: { send_via: 'email'|'post', email_address, cc_addresses: [] }
|
||
|
|
Response: { ok: true, invoice: { status: 'sent', sent_at: ... } }
|
||
|
|
|
||
|
|
POST /api/billing/invoices/:id/credit
|
||
|
|
Body: { reason, credit_date, credit_lines: [...] }
|
||
|
|
Response: { ok: true, credit_invoice: {...}, original_invoice: {...} }
|
||
|
|
|
||
|
|
POST /api/billing/invoices/:id/cancel
|
||
|
|
Body: { reason }
|
||
|
|
Response: { ok: true, invoice: { status: 'cancelled' } }
|
||
|
|
|
||
|
|
POST /api/billing/invoices/:id/remind
|
||
|
|
Body: { reminder_type: 'reminder'|'debt_collection', reminder_fee, interest_fee }
|
||
|
|
Response: { ok: true, reminder: {...}, invoice: {...} }
|
||
|
|
|
||
|
|
DELETE /api/billing/invoices/:id
|
||
|
|
Response: { ok: true } (endast draft)
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3.5 Betalningar (Payments)
|
||
|
|
|
||
|
|
```
|
||
|
|
GET /api/billing/payments
|
||
|
|
Query: ?invoice_id=...&reconciliation_status=unreconciled&page=1
|
||
|
|
Response: { ok: true, payments: [...] }
|
||
|
|
|
||
|
|
POST /api/billing/payments
|
||
|
|
Body: { invoice_id, payment_date, amount, payment_method,
|
||
|
|
payment_reference, bank_transaction_id }
|
||
|
|
Response: 201 { ok: true, payment: {...} }
|
||
|
|
|
||
|
|
POST /api/billing/payments/:id/reconcile
|
||
|
|
Body: { bank_transaction_id }
|
||
|
|
Response: { ok: true, payment: { reconciliation_status: 'reconciled' } }
|
||
|
|
|
||
|
|
POST /api/billing/payments/import
|
||
|
|
Body: { format: 'bankgiro'|'swish'|'csv', file_data: 'base64...' }
|
||
|
|
Response: { ok: true, imported: 42, matched: 38, unmatched: 4 }
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3.6 Schemaläggning (Schedules)
|
||
|
|
|
||
|
|
```
|
||
|
|
GET /api/billing/schedules
|
||
|
|
Query: ?agreement_id=...&is_active=true
|
||
|
|
Response: { ok: true, schedules: [...] }
|
||
|
|
|
||
|
|
POST /api/billing/schedules
|
||
|
|
Body: { agreement_id, frequency, day_of_month, next_run_date }
|
||
|
|
Response: 201 { ok: true, schedule: {...} }
|
||
|
|
|
||
|
|
POST /api/billing/schedules/:id/run-now
|
||
|
|
Response: { ok: true, invoice: {...} }
|
||
|
|
|
||
|
|
POST /api/billing/schedules/:id/pause
|
||
|
|
Body: { until_date }
|
||
|
|
Response: { ok: true, schedule: { is_active: false, paused_until: ... } }
|
||
|
|
|
||
|
|
POST /api/billing/schedules/:id/resume
|
||
|
|
Response: { ok: true, schedule: { is_active: true } }
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3.7 Rapportering & Export
|
||
|
|
|
||
|
|
```
|
||
|
|
GET /api/billing/reports/aging
|
||
|
|
Query: ?as_of_date=2026-07-31&customer_org_nr=...
|
||
|
|
Response: { ok: true, buckets: [
|
||
|
|
{ range: '0-30', amount: 450000, invoices: 12 },
|
||
|
|
{ range: '31-60', amount: 120000, invoices: 5 },
|
||
|
|
{ range: '61-90', amount: 85000, invoices: 3 },
|
||
|
|
{ range: '90+', amount: 32000, invoices: 2 }
|
||
|
|
]}
|
||
|
|
|
||
|
|
GET /api/billing/reports/revenue
|
||
|
|
Query: ?period_from=2026-01&period_to=2026-06&group_by=month|customer|agreement
|
||
|
|
Response: { ok: true, periods: [...] }
|
||
|
|
|
||
|
|
GET /api/billing/reports/outstanding
|
||
|
|
Query: ?status=sent,partially_paid&page=1
|
||
|
|
Response: { ok: true, invoices: [...], total_outstanding: 687000 }
|
||
|
|
|
||
|
|
POST /api/billing/exports/sie4
|
||
|
|
Body: { period_from, period_to, fiscal_year, invoice_ids: [] }
|
||
|
|
Response: 202 { ok: true, job_id: '...', status: 'pending' }
|
||
|
|
|
||
|
|
POST /api/billing/exports/pdf
|
||
|
|
Body: { invoice_ids: [...] }
|
||
|
|
Response: 202 { ok: true, job_id: '...', status: 'pending' }
|
||
|
|
|
||
|
|
POST /api/billing/exports/csv
|
||
|
|
Body: { format: 'invoices'|'payments'|'agreements', filters: {...} }
|
||
|
|
Response: 202 { ok: true, job_id: '...', status: 'pending' }
|
||
|
|
|
||
|
|
POST /api/billing/exports/visma
|
||
|
|
Body: { period_from, period_to, options: {...} }
|
||
|
|
Response: 202 { ok: true, job_id: '...', status: 'pending' }
|
||
|
|
|
||
|
|
POST /api/billing/exports/agresso
|
||
|
|
Body: { period_from, period_to, options: {...} }
|
||
|
|
Response: 202 { ok: true, job_id: '...', status: 'pending' }
|
||
|
|
|
||
|
|
GET /api/billing/exports/:jobId
|
||
|
|
Response: { ok: true, job: { status: 'completed', file_url: '...', record_count: 42 } }
|
||
|
|
|
||
|
|
GET /api/billing/exports/:jobId/download
|
||
|
|
Response: File download (stream)
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3.8 Dashboard & Översikt
|
||
|
|
|
||
|
|
```
|
||
|
|
GET /api/billing/dashboard
|
||
|
|
Response: {
|
||
|
|
ok: true,
|
||
|
|
summary: {
|
||
|
|
total_agreements: 45,
|
||
|
|
active_agreements: 38,
|
||
|
|
invoices_this_month: 12,
|
||
|
|
total_outstanding: 687000,
|
||
|
|
overdue_amount: 237000,
|
||
|
|
overdue_count: 8,
|
||
|
|
payments_this_month: 890000
|
||
|
|
},
|
||
|
|
alerts: [
|
||
|
|
{ type: 'overdue', severity: 'high', message: '3 fakturor > 90 dagar', count: 3 },
|
||
|
|
{ type: 'draft', severity: 'medium', message: '5 utkast väntar', count: 5 }
|
||
|
|
]
|
||
|
|
}
|
||
|
|
|
||
|
|
GET /api/billing/dashboard/activities
|
||
|
|
Query: ?limit=20
|
||
|
|
Response: { ok: true, activities: [
|
||
|
|
{ ts, type: 'invoice_sent', description, user_id, entity_id }
|
||
|
|
]}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3.9 Webhooks (för externa system)
|
||
|
|
|
||
|
|
```
|
||
|
|
GET /api/billing/webhooks
|
||
|
|
POST /api/billing/webhooks
|
||
|
|
Body: { url, events: ['invoice.sent', 'invoice.paid', 'payment.received'], secret }
|
||
|
|
PUT /api/billing/webhooks/:id
|
||
|
|
DELETE /api/billing/webhooks/:id
|
||
|
|
POST /api/billing/webhooks/:id/test
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 4. Event-schema (Hermes)
|
||
|
|
|
||
|
|
### 4.1 Events som billing EMITTAR
|
||
|
|
|
||
|
|
| Event | Beskrivning | Konsumenter |
|
||
|
|
|-------|-------------|-------------|
|
||
|
|
| `billing.agreement.created` | Nytt avtal skapat | ledger (förbered konto), audit |
|
||
|
|
| `billing.agreement.activated` | Avtal aktiverat | ledger, notification |
|
||
|
|
| `billing.agreement.terminated` | Avtal uppsagt | ledger, notification |
|
||
|
|
| `billing.invoice.created` | Faktura skapad | ledger (bokför), notification |
|
||
|
|
| `billing.invoice.sent` | Faktura skickad | notification, CRM |
|
||
|
|
| `billing.invoice.paid` | Faktura betald | ledger (bokför betalning), notification |
|
||
|
|
| `billing.invoice.overdue` | Faktura förfallen | notification, reminder-engine |
|
||
|
|
| `billing.invoice.credited` | Kreditfaktura skapad | ledger |
|
||
|
|
| `billing.invoice.cancelled` | Faktura annullerad | ledger |
|
||
|
|
| `billing.payment.received` | Betalning mottagen | ledger, invoice (uppdatera status) |
|
||
|
|
| `billing.payment.reconciled` | Betalning avstämd | ledger |
|
||
|
|
| `billing.reminder.sent` | Påminnelse skickad | notification |
|
||
|
|
| `billing.export.completed` | Export klar | notification |
|
||
|
|
| `billing.schedule.executed` | Schemalagd körning | audit |
|
||
|
|
|
||
|
|
### 4.2 Events som billing KONSUMERAR
|
||
|
|
|
||
|
|
| Event | Källa | Åtgärd |
|
||
|
|
|-------|-------|--------|
|
||
|
|
| `finance.journal.posted` | ledger | Uppdatera `invoice.ledger_posted_at`, `payment.journal_entry_id` |
|
||
|
|
| `finance.period.closed` | ledger | Pausa schemaläggning för stängd period |
|
||
|
|
|
||
|
|
### 4.3 Exempel: Event-envelope
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||
|
|
"trace_id": "abc123",
|
||
|
|
"correlation_id": "inv-2026-00042",
|
||
|
|
"event_type": "billing.invoice.created",
|
||
|
|
"source": "aamos-billing",
|
||
|
|
"tenant_id": "landvex",
|
||
|
|
"user_id": "user@landvex.com",
|
||
|
|
"entity_type": "invoice",
|
||
|
|
"entity_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
|
||
|
|
"decision_source": "user",
|
||
|
|
"payload": {
|
||
|
|
"invoice_number": "F-2026-00042",
|
||
|
|
"agreement_id": "...",
|
||
|
|
"customer_org_nr": "559141-7042",
|
||
|
|
"total": 125000.00,
|
||
|
|
"vat_total": 25000.00,
|
||
|
|
"currency": "SEK",
|
||
|
|
"due_date": "2026-08-01"
|
||
|
|
},
|
||
|
|
"ts": "2026-07-02T10:30:00Z",
|
||
|
|
"schema_version": "1.0"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 5. Integration med Ekonomisystem
|
||
|
|
|
||
|
|
### 5.1 Adapter-arkitektur
|
||
|
|
|
||
|
|
```
|
||
|
|
┌─────────────────────────────────────────────────────────────┐
|
||
|
|
│ aamos-billing │
|
||
|
|
│ ┌─────────────────────────────────────────────────────┐ │
|
||
|
|
│ │ Export Engine (abstrakt) │ │
|
||
|
|
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
|
||
|
|
│ │ │ SIE4 │ │ Visma │ │Agresso │ │ Fortnox │ │ │
|
||
|
|
│ │ │ Exporter│ │ Adapter │ │ Adapter │ │ Adapter │ │ │
|
||
|
|
│ │ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │ │
|
||
|
|
│ └───────┼───────────┼───────────┼───────────┼───────┘ │
|
||
|
|
│ │ │ │ │ │
|
||
|
|
│ ▼ ▼ ▼ ▼ │
|
||
|
|
│ SIE4-fil Visma API Agresso API Fortnox API │
|
||
|
|
│ (.si) (REST/SOAP) (REST/SOAP) (REST/OAuth) │
|
||
|
|
└─────────────────────────────────────────────────────────────┘
|
||
|
|
```
|
||
|
|
|
||
|
|
### 5.2 SIE4 Export
|
||
|
|
|
||
|
|
- **Format:** SIE4 (svensk standard för ekonomidata)
|
||
|
|
- **Innehåll:** Verifikationer för fakturor och betalningar
|
||
|
|
- **Trigger:** Manuell export eller schemalagd (månadsvis)
|
||
|
|
- **Filnamn:** `SIE4_<tenant>_<period>_<timestamp>.si`
|
||
|
|
- **Befintlig kod:** Återanvänd `sie4-export.mjs` från aamos-ledger
|
||
|
|
|
||
|
|
### 5.3 Visma Integration
|
||
|
|
|
||
|
|
- **API:** Visma.net ERP API (REST/OAuth2)
|
||
|
|
- **Funktioner:**
|
||
|
|
- Skicka fakturor till Visma (tvåvägs)
|
||
|
|
- Hämta kundregister
|
||
|
|
- Synkronisera betalningar
|
||
|
|
- **Konfiguration:** `visma_client_id`, `visma_client_secret`, `visma_tenant_id`
|
||
|
|
|
||
|
|
### 5.4 Agresso Integration
|
||
|
|
|
||
|
|
- **API:** Agresso Business World API (SOAP/REST)
|
||
|
|
- **Funktioner:**
|
||
|
|
- Exportera fakturor som Agresso-importfiler
|
||
|
|
- Hämta huvudboksdata
|
||
|
|
- **Konfiguration:** `agresso_wsdl_url`, `agresso_username`, `agresso_password`
|
||
|
|
|
||
|
|
### 5.5 Fortnox Integration
|
||
|
|
|
||
|
|
- **API:** Fortnox API v3 (REST/OAuth2)
|
||
|
|
- **Funktioner:**
|
||
|
|
- Skapa fakturor i Fortnox
|
||
|
|
- Hämta betalningsstatus
|
||
|
|
- Synkronisera kunder
|
||
|
|
- **Konfiguration:** `fortnox_client_id`, `fortnox_client_secret`, `fortnox_access_token`
|
||
|
|
|
||
|
|
### 5.6 Konfigurationstabell
|
||
|
|
|
||
|
|
```sql
|
||
|
|
CREATE TABLE IF NOT EXISTS billing_integration_configs (
|
||
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
|
|
tenant_id TEXT NOT NULL,
|
||
|
|
system_name TEXT NOT NULL, -- 'visma'|'agresso'|'fortnox'|'custom'
|
||
|
|
is_active BOOLEAN NOT NULL DEFAULT FALSE,
|
||
|
|
config JSONB NOT NULL, -- system-specifik konfig
|
||
|
|
last_sync_at TIMESTAMPTZ,
|
||
|
|
last_sync_status TEXT, -- 'success'|'failed'
|
||
|
|
last_error TEXT,
|
||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
|
|
UNIQUE (tenant_id, system_name)
|
||
|
|
);
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 6. UI-design — Skärmar och Flöden
|
||
|
|
|
||
|
|
### 6.1 Designprinciper för UI
|
||
|
|
|
||
|
|
- **Mobil-first:** Fungerar på alla skärmstorlekar
|
||
|
|
- **Tydliga åtgärder:** Primära knappar (skapa, skicka) framhävs
|
||
|
|
- **Statusfärger:** Grön (betald), gul (skickad), röd (förfallen), grå (utkast)
|
||
|
|
- **Kontextuell hjälp:** Tooltips och hjälptexter vid varje fält
|
||
|
|
- **Bulk-åtgärder:** Markera flera fakturor och skicka/påminn/export
|
||
|
|
- **Sök & Filter:** Snabb åtkomst till data
|
||
|
|
|
||
|
|
### 6.2 Skärmöversikt
|
||
|
|
|
||
|
|
```
|
||
|
|
┌─────────────────────────────────────────────────────────────────┐
|
||
|
|
│ LandveX Billing [🔍 Sök] [👤 Admin] [⚙️] │
|
||
|
|
├─────────────────────────────────────────────────────────────────┤
|
||
|
|
│ [📊 Dashboard] [📋 Fakturor] [📄 Avtal] [💰 Betalningar] │
|
||
|
|
│ [📈 Rapporter] [⚙️ Inställningar] │
|
||
|
|
├─────────────────────────────────────────────────────────────────┤
|
||
|
|
│ │
|
||
|
|
│ INNEHÅLL (varierar per vy) │
|
||
|
|
│ │
|
||
|
|
└─────────────────────────────────────────────────────────────────┘
|
||
|
|
```
|
||
|
|
|
||
|
|
### 6.3 Skärm 1: Dashboard (Start)
|
||
|
|
|
||
|
|
**Syfte:** Översikt och snabbåtkomst till viktig information.
|
||
|
|
|
||
|
|
**Innehåll:**
|
||
|
|
- **KPI-kort (4 st):**
|
||
|
|
- "Aktiva avtal" — 38 st (↗ +2 denna månad)
|
||
|
|
- "Obetalt belopp" — 687 000 kr (8 fakturor)
|
||
|
|
- "Förfallet belopp" — 237 000 kr (3 > 90 dagar) 🔴
|
||
|
|
- "Inbetalningar denna månad" — 890 000 kr
|
||
|
|
- **Varningar/Alerts:**
|
||
|
|
- Röd: "3 fakturor förfallna > 90 dagar"
|
||
|
|
- Gul: "5 fakturor i utkast väntar på att skickas"
|
||
|
|
- Blå: "2 schemalagda körningar idag"
|
||
|
|
- **Snabbåtgärder:**
|
||
|
|
- [+ Skapa faktura]
|
||
|
|
- [+ Nytt avtal]
|
||
|
|
- [📤 Skicka utkast]
|
||
|
|
- [📥 Importera betalningar]
|
||
|
|
- **Aktivitetsflöde (senaste 10):**
|
||
|
|
- "Faktura F-2026-0042 skickad till Kund AB"
|
||
|
|
- "Betalning 45 000 kr mottagen för F-2026-0038"
|
||
|
|
- "Avtal KOM-2026-005 aktiverat"
|
||
|
|
- **Graf:** Inbetalningar per månad (stapeldiagram, senaste 12 mån)
|
||
|
|
|
||
|
|
### 6.4 Skärm 2: Fakturor (Lista)
|
||
|
|
|
||
|
|
**Syfte:** Hantera alla fakturor — sök, filtrera, skicka, påminn.
|
||
|
|
|
||
|
|
**Innehåll:**
|
||
|
|
- **Filter-rad:**
|
||
|
|
- Status: [Alla ▼] [Utkast] [Skickad] [Betald] [Förfallen] [Krediterad]
|
||
|
|
- Period: [2026-07 ▼]
|
||
|
|
- Kund: [🔍 Sök kund...]
|
||
|
|
- Belopp: [Min] — [Max]
|
||
|
|
- [🔍 Sök] [❌ Rensa]
|
||
|
|
- **Tabell:**
|
||
|
|
| ☑️ | Faktura | Kund | Datum | Förfallo | Belopp | Status | Åtgärder |
|
||
|
|
|----|---------|------|-------|----------|--------|--------|----------|
|
||
|
|
| ☐ | F-2026-0045 | Kommun X | 2026-07-01 | 2026-08-01 | 125 000 kr | 🟡 Skickad | [📧] [💰] [⚙️] |
|
||
|
|
| ☐ | F-2026-0044 | Företag Y | 2026-06-15 | 2026-07-15 | 45 000 kr | 🔴 Förfallen | [📧] [💰] [⚙️] |
|
||
|
|
| ☐ | F-2026-0043 | Kommun Z | 2026-06-01 | 2026-07-01 | 89 000 kr | 🟢 Betald | [👁️] |
|
||
|
|
- **Bulk-åtgärder (när rader markerade):**
|
||
|
|
- [📤 Skicka valda] [📧 Påminn valda] [📥 Exportera] [🗑️ Ta bort]
|
||
|
|
- **Paginering:** « 1 2 3 ... 12 »
|
||
|
|
|
||
|
|
### 6.5 Skärm 3: Faktura — Detaljvy
|
||
|
|
|
||
|
|
**Syfte:** Visa och hantera en enskild faktura.
|
||
|
|
|
||
|
|
**Innehåll:**
|
||
|
|
- **Header:**
|
||
|
|
- Fakturanummer: F-2026-0045
|
||
|
|
- Status-badge: 🟡 Skickad
|
||
|
|
- Åtgärds-knappar: [📧 Skicka påminnelse] [💰 Registrera betalning] [🔄 Kreditera] [🖨️ PDF]
|
||
|
|
- **Kundinfo (kort):**
|
||
|
|
- Kommun X (org.nr 212000-1234)
|
||
|
|
- Storgatan 1, 123 45 Staden
|
||
|
|
- Kontakt: Anna Svensson, anna@kommunx.se
|
||
|
|
- **Fakturainfo:**
|
||
|
|
- Fakturadatum: 2026-07-01
|
||
|
|
- Förfallodatum: 2026-08-01 (15 dagar kvar)
|
||
|
|
- Leveransdatum: 2026-07-01
|
||
|
|
- Vår referens: Projektledare Nilsson
|
||
|
|
- Er referens: Anna Svensson
|
||
|
|
- **Fakturarader (tabell):**
|
||
|
|
| # | Beskrivning | Antal | Enhet | á-pris | Moms% | Moms | Totalt |
|
||
|
|
|---|-------------|-------|-------|--------|-------|------|--------|
|
||
|
|
| 1 | Drift av VA-nät | 1 | mån | 100 000 kr | 25% | 25 000 kr | 125 000 kr |
|
||
|
|
- **Summering (höger):**
|
||
|
|
- Netto: 100 000 kr
|
||
|
|
- Moms: 25 000 kr
|
||
|
|
- **Att betala: 125 000 kr**
|
||
|
|
- Betalt: 0 kr
|
||
|
|
- Kvar att betala: 125 000 kr
|
||
|
|
- **Betalningshistorik:**
|
||
|
|
- (tom om obetald)
|
||
|
|
- **Påminnelser:**
|
||
|
|
- (tom om inga skickade)
|
||
|
|
- **Bokföring:**
|
||
|
|
- Bokförd: Ja (verifikat #1234, 2026-07-01)
|
||
|
|
- [Visa i ledger]
|
||
|
|
- **Audit-logg (expandable):**
|
||
|
|
- 2026-07-01 10:30 — Skapad av admin@landvex.com
|
||
|
|
- 2026-07-01 11:00 — Skickad via e-post till anna@kommunx.se
|
||
|
|
- **Flikar:** [Faktura] [Betalningar] [Påminnelser] [Bokföring] [Historik]
|
||
|
|
|
||
|
|
### 6.6 Skärm 4: Skapa Faktura (Wizard)
|
||
|
|
|
||
|
|
**Syfte:** Guidad process för att skapa ny faktura.
|
||
|
|
|
||
|
|
**Steg 1: Välj kund**
|
||
|
|
- Sök kund (autocomplete från avtal/kundregister)
|
||
|
|
- Eller: [+ Ny kund]
|
||
|
|
- Visa kundinfo-kort när vald
|
||
|
|
- [Nästa ▶]
|
||
|
|
|
||
|
|
**Steg 2: Fakturainformation**
|
||
|
|
- Fakturadatum: [2026-07-02 ▼]
|
||
|
|
- Förfallodatum: [2026-08-02 ▼] (auto = +30 dagar)
|
||
|
|
- Leveransdatum: [2026-07-02 ▼]
|
||
|
|
- Vår referens: [Projektledare Nilsson]
|
||
|
|
- Er referens: [Anna Svensson]
|
||
|
|
- Betalningsvillkor: [30 dagar netto ▼]
|
||
|
|
- [⬅️ Föregående] [Nästa ▶]
|
||
|
|
|
||
|
|
**Steg 3: Fakturarader**
|
||
|
|
- [+ Lägg till rad]
|
||
|
|
- Rad-formulär:
|
||
|
|
- Beskrivning: [Drift av VA-nät]
|
||
|
|
- Antal: [1]
|
||
|
|
- Enhet: [mån ▼]
|
||
|
|
- á-pris (exkl. moms): [100 000]
|
||
|
|
- Moms%: [25 ▼]
|
||
|
|
- Intäktskonto: [3000 ▼]
|
||
|
|
- Kostnadsställe: [--- ▼]
|
||
|
|
- Projekt: [--- ▼]
|
||
|
|
- Summering uppdateras live
|
||
|
|
- [⬅️ Föregående] [Nästa ▶]
|
||
|
|
|
||
|
|
**Steg 4: Granska & Skicka**
|
||
|
|
- Förhandsvisning av faktura (PDF-liknande layout)
|
||
|
|
- [🖨️ Förhandsgranska PDF]
|
||
|
|
- [💾 Spara som utkast]
|
||
|
|
- [📧 Skicka nu]
|
||
|
|
- [📧 Skicka senare — välj datum]
|
||
|
|
|
||
|
|
### 6.7 Skärm 5: Avtal (Lista)
|
||
|
|
|
||
|
|
**Syfte:** Hantera avtal — skapa, ändra, förläng, säg upp.
|
||
|
|
|
||
|
|
**Innehåll:**
|
||
|
|
- **Filter:**
|
||
|
|
- Status: [Alla ▼] [Utkast] [Aktivt] [Uppsagt] [Avslutat]
|
||
|
|
- Typ: [Alla ▼] [Kommunalt] [Kommersiellt] [Internt]
|
||
|
|
- Kund: [🔍 Sök...]
|
||
|
|
- [🔍 Sök] [❌ Rensa]
|
||
|
|
- **Tabell:**
|
||
|
|
| Avtalsnr | Kund | Typ | Period | Frekvens | Nästa faktura | Status |
|
||
|
|
|----------|------|-----|--------|----------|---------------|--------|
|
||
|
|
| KOM-2026-001 | Kommun X | Kommunalt | 2026-01 — 2026-12 | Månadsvis | 2026-08-01 | 🟢 Aktiv |
|
||
|
|
| KOM-2026-005 | Företag Y | Kommersiellt | 2026-03 — 2027-03 | Kvartalsvis | 2026-10-01 | 🟢 Aktiv |
|
||
|
|
- **Åtgärder per rad:**
|
||
|
|
- [👁️ Visa] [✏️ Redigera] [📄 Skapa faktura] [⏸️ Pausa] [🛑 Säg upp]
|
||
|
|
- **[+ Nytt avtal]**
|
||
|
|
|
||
|
|
### 6.8 Skärm 6: Avtal — Detaljvy
|
||
|
|
|
||
|
|
**Syfte:** Visa och hantera ett enskilt avtal.
|
||
|
|
|
||
|
|
**Innehåll:**
|
||
|
|
- **Header:**
|
||
|
|
- Avtalsnummer: KOM-2026-001
|
||
|
|
- Status: 🟢 Aktiv
|
||
|
|
- Åtgärder: [✏️ Redigera] [📄 Skapa faktura] [⏸️ Pausa] [🛑 Säg upp]
|
||
|
|
- **Avtalsinfo:**
|
||
|
|
- Kund: Kommun X (org.nr 212000-1234)
|
||
|
|
- Typ: Kommunalt
|
||
|
|
- Period: 2026-01-01 — 2026-12-31
|
||
|
|
- Auto-förlängning: Ja (12 mån)
|
||
|
|
- Uppsägningstid: 90 dagar
|
||
|
|
- Betalningsvillkor: 30 dagar
|
||
|
|
- **Avtalsrader:**
|
||
|
|
| # | Beskrivning | Antal | Enhet | á-pris | Moms% | Status |
|
||
|
|
|---|-------------|-------|-------|--------|-------|--------|
|
||
|
|
| 1 | Drift av VA-nät | 1 | mån | 100 000 kr | 25% | ✅ Aktiv |
|
||
|
|
| 2 | Underhåll | 1 | mån | 25 000 kr | 25% | ✅ Aktiv |
|
||
|
|
- **[+ Lägg till rad]**
|
||
|
|
- **Fakturahistorik:**
|
||
|
|
| Faktura | Datum | Belopp | Status |
|
||
|
|
|---------|-------|--------|--------|
|
||
|
|
| F-2026-0045 | 2026-07-01 | 125 000 kr | 🟡 Skickad |
|
||
|
|
| F-2026-0038 | 2026-06-01 | 125 000 kr | 🟢 Betald |
|
||
|
|
- **Schemaläggning:**
|
||
|
|
- Nästa körning: 2026-08-01
|
||
|
|
- Frekvens: Månadsvis (dag 1)
|
||
|
|
- [▶️ Kör nu] [⏸️ Pausa] [⚙️ Ändra schema]
|
||
|
|
- **Flikar:** [Översikt] [Rader] [Fakturor] [Schema] [Historik]
|
||
|
|
|
||
|
|
### 6.9 Skärm 7: Betalningar
|
||
|
|
|
||
|
|
**Syfte:** Registrera och avstämma betalningar.
|
||
|
|
|
||
|
|
**Innehåll:**
|
||
|
|
- **Import:** [📥 Importera från bank] [📥 Importera CSV]
|
||
|
|
- **Filter:**
|
||
|
|
- Status: [Alla ▼] [Oavstämd] [Avstämd]
|
||
|
|
- Period: [2026-07 ▼]
|
||
|
|
- [🔍 Sök]
|
||
|
|
- **Tabell:**
|
||
|
|
| Datum | Faktura | Kund | Belopp | Metod | Referens | Status |
|
||
|
|
|-------|---------|------|--------|-------|----------|--------|
|
||
|
|
| 2026-07-02 | F-2026-0038 | Kommun X | 125 000 kr | Bankgiro | OCR 12345 | 🟢 Avstämd |
|
||
|
|
| 2026-07-01 | — | Okänd | 45 000 kr | Bankgiro | OCR 99999 | 🔴 Oavstämd |
|
||
|
|
- **Åtgärder:**
|
||
|
|
- Avstämd: [👁️ Visa]
|
||
|
|
- Oavstämd: [🔗 Matcha faktura] [✏️ Redigera] [🗑️ Ta bort]
|
||
|
|
|
||
|
|
### 6.10 Skärm 8: Rapporter
|
||
|
|
|
||
|
|
**Syfte:** Generera och exportera rapporter.
|
||
|
|
|
||
|
|
**Innehåll:**
|
||
|
|
- **Rapport-typer (kort):**
|
||
|
|
- [📊 Förfallna fordringar] — Aging-report
|
||
|
|
- [📈 Intäktsrapport] — Per period/kund/avtal
|
||
|
|
- [📋 Obetalda fakturor] — Lista med filter
|
||
|
|
- [📤 Export] — SIE4, PDF, CSV, Visma, Agresso
|
||
|
|
- **Förfallna fordringar (exempel):**
|
||
|
|
- Filter: Per [månad ▼] | Kund: [Alla ▼]
|
||
|
|
- Graf: Stapeldiagram per bucket (0-30, 31-60, 61-90, 90+)
|
||
|
|
- Tabell: Detaljer per kund
|
||
|
|
- [📥 Exportera till Excel] [📥 Exportera till PDF]
|
||
|
|
- **Export-dialog:**
|
||
|
|
- Format: [SIE4 ▼] [PDF ▼] [CSV ▼] [Visma ▼] [Agresso ▼]
|
||
|
|
- Period: [Från] — [Till]
|
||
|
|
- [▶️ Generera]
|
||
|
|
- Status: "Genererar..." → "Klar! [Ladda ner]"
|
||
|
|
|
||
|
|
### 6.11 Skärm 9: Inställningar
|
||
|
|
|
||
|
|
**Syfte:** Konfigurera modulen och integrationer.
|
||
|
|
|
||
|
|
**Innehåll:**
|
||
|
|
- **Flikar:** [Allmänt] [Integrationer] [Moms] [Användare] [Avancerat]
|
||
|
|
- **Allmänt:**
|
||
|
|
- Standard betalningsvillkor: [30 dagar]
|
||
|
|
- Standard dröjsmålsränta: [8%]
|
||
|
|
- Fakturanummer-serie: [F-YYYY-NNNNN]
|
||
|
|
- Avtalsnummer-serie: [KOM-YYYY-NNN]
|
||
|
|
- **Integrationer:**
|
||
|
|
- Visma: [Anslut] / [✅ Ansluten] [⚙️ Konfigurera] [🔄 Synka nu]
|
||
|
|
- Agresso: [Anslut] / [✅ Ansluten]
|
||
|
|
- Fortnox: [Anslut] / [❌ Ej konfigurerad]
|
||
|
|
- **Moms:**
|
||
|
|
- Standard moms: [25% ▼]
|
||
|
|
- Momssatser: [25%], [12%], [6%], [0%]
|
||
|
|
- **Användare & Roller:**
|
||
|
|
- Lista användare med roller (admin, accountant, viewer)
|
||
|
|
- [+ Lägg till användare]
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 7. Arbetsflöden (Workflows)
|
||
|
|
|
||
|
|
### 7.1 Flöde: Skapa och skicka faktura
|
||
|
|
|
||
|
|
```
|
||
|
|
[Användare] ──► [Skapa faktura] ──► [Fyll i kund, rader] ──► [Granska]
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
[Spara utkast]
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
[Skicka faktura]
|
||
|
|
│
|
||
|
|
┌───────────────────────┼───────────────────────┐
|
||
|
|
▼ ▼ ▼
|
||
|
|
[E-post] [Post] [PDF nedladdning]
|
||
|
|
│ │ │
|
||
|
|
▼ ▼ ▼
|
||
|
|
[Status: sent] [Status: sent] [Status: sent]
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
[Hermes: billing.invoice.sent]
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
[Ledger: bokför kundfordran]
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
[Status: posted i ledger]
|
||
|
|
```
|
||
|
|
|
||
|
|
### 7.2 Flöde: Återkommande fakturering (schemalagd)
|
||
|
|
|
||
|
|
```
|
||
|
|
[Cron-jobb varje dag 06:00]
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
[ billing_schedules: next_run_date <= idag AND is_active ]
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
[ För varje schema: ]
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
[ Skapa faktura från avtalsrader ]
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
[ Sätt status = 'draft' ]
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
[ Uppdatera next_run_date (nästa månad/kvartal/år) ]
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
[ Hermes: billing.schedule.executed ]
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
[ Notification: "Ny faktura genererad: F-2026-0045" ]
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
[ Användare granskar och skickar ]
|
||
|
|
```
|
||
|
|
|
||
|
|
### 7.3 Flöde: Betalning och avstämning
|
||
|
|
|
||
|
|
```
|
||
|
|
[ Bankgiro-fil / Swish-rapport / Manuell inmatning ]
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
[ Import till billing_payments ]
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
[ Auto-match: payment_reference (OCR) ↔ invoice_number ]
|
||
|
|
│
|
||
|
|
├─────────────────┬─────────────────┐
|
||
|
|
▼ ▼ ▼
|
||
|
|
[Matchad] [Delvis matchad] [Ej matchad]
|
||
|
|
│ │ │
|
||
|
|
▼ ▼ ▼
|
||
|
|
[ Uppdatera invoice ] [ Uppdatera ] [ Markera som ]
|
||
|
|
[ amount_paid ] [ partially_paid ] [ unreconciled ]
|
||
|
|
│ │ │
|
||
|
|
▼ ▼ ▼
|
||
|
|
[ Om amount_paid >= total ] [ Manuell matchning ]
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
[ Status: paid ]
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
[ Hermes: billing.payment.received ]
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
[ Ledger: bokför betalning (1910 → 1510) ]
|
||
|
|
```
|
||
|
|
|
||
|
|
### 7.4 Flöde: Påminnelse och inkasso
|
||
|
|
|
||
|
|
```
|
||
|
|
[Cron-jobb: dagligen, 07:00]
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
[ invoices: due_date < idag AND status IN ('sent', 'partially_paid') ]
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
[ För varje förfallen faktura: ]
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
[ Räkna dagar sedan due_date ]
|
||
|
|
│
|
||
|
|
├─────────────┬─────────────┬─────────────┐
|
||
|
|
▼ ▼ ▼ ▼
|
||
|
|
[1-30 dagar] [31-60 dagar] [61-90 dagar] [90+ dagar]
|
||
|
|
│ │ │ │
|
||
|
|
▼ ▼ ▼ ▼
|
||
|
|
[Inget] [Påminnelse 1] [Påminnelse 2] [Inkasso]
|
||
|
|
│ │ │
|
||
|
|
▼ ▼ ▼
|
||
|
|
[Skicka mail] [Skicka mail] [Skicka till]
|
||
|
|
[+ påminnelse-] [+ påminnelse-] [inkassobyrå]
|
||
|
|
[avgift 60 kr] [avgift 60 kr]
|
||
|
|
│ │
|
||
|
|
▼ ▼
|
||
|
|
[billing_reminders] [billing_reminders]
|
||
|
|
[reminder_number=1] [reminder_number=2]
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
[Hermes: billing.reminder.sent]
|
||
|
|
```
|
||
|
|
|
||
|
|
### 7.5 Flöde: Kreditfaktura
|
||
|
|
|
||
|
|
```
|
||
|
|
[Användare väljer faktura] ──► [Kreditera]
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
[Ange anledning]
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
[Välj rader att kreditera]
|
||
|
|
[eller hela fakturan]
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
[Skapa kreditfaktura (KF-2026-0001)]
|
||
|
|
[invoice_type = 'credit']
|
||
|
|
[original_invoice_id = F-2026-0045]
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
[Negativa belopp på rader]
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
[Status: sent]
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
[Uppdatera original: credited_by_invoice_id]
|
||
|
|
[Original status: credited]
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
[Hermes: billing.invoice.credited]
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
[Ledger: bokför kreditering]
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 8. Teknisk Arkitektur
|
||
|
|
|
||
|
|
### 8.1 Teknisk Stack
|
||
|
|
|
||
|
|
| Komponent | Teknik |
|
||
|
|
|-----------|--------|
|
||
|
|
| Runtime | Node.js 20+ (ESM) |
|
||
|
|
| Framework | Express.js |
|
||
|
|
| Databas | PostgreSQL 15+ |
|
||
|
|
| Cache | Redis (Hermes + sessions) |
|
||
|
|
| Auth | JWT (RS256/HS256), samma som ledger |
|
||
|
|
| Validering | Joi |
|
||
|
|
| PDF | Puppeteer / Playwright (HTML → PDF) |
|
||
|
|
| E-post | Nodemailer / AWS SES |
|
||
|
|
| Fil-lagring | S3 / MinIO |
|
||
|
|
| Cron | node-cron / bullmq |
|
||
|
|
|
||
|
|
### 8.2 Projektstruktur
|
||
|
|
|
||
|
|
```
|
||
|
|
aamos-billing/
|
||
|
|
├── index.mjs # Express-app, routes
|
||
|
|
├── package.json
|
||
|
|
├── schema.sql # PostgreSQL-schema
|
||
|
|
├── auth.mjs # JWT + RBAC (återanvänd från ledger)
|
||
|
|
├── validation.mjs # Joi-scheman
|
||
|
|
├── hermes.mjs # Event-publishing (återanvänd från ledger)
|
||
|
|
├── config.mjs # Konfiguration
|
||
|
|
│
|
||
|
|
├── routes/
|
||
|
|
│ ├── agreements.mjs # Avtal-endpoints
|
||
|
|
│ ├── invoices.mjs # Faktura-endpoints
|
||
|
|
│ ├── payments.mjs # Betalnings-endpoints
|
||
|
|
│ ├── schedules.mjs # Schemaläggning
|
||
|
|
│ ├── reports.mjs # Rapporter
|
||
|
|
│ ├── exports.mjs # Export-jobb
|
||
|
|
│ └── dashboard.mjs # Dashboard-data
|
||
|
|
│
|
||
|
|
├── services/
|
||
|
|
│ ├── invoiceService.mjs # Affärslogik fakturor
|
||
|
|
│ ├── agreementService.mjs # Affärslogik avtal
|
||
|
|
│ ├── paymentService.mjs # Affärslogik betalningar
|
||
|
|
│ ├── scheduleService.mjs # Cron-jobb & schemaläggning
|
||
|
|
│ ├── reminderService.mjs # Påminnelsemotor
|
||
|
|
│ ├── pdfService.mjs # PDF-generering
|
||
|
|
│ ├── emailService.mjs # E-postutskick
|
||
|
|
│ └── exportService.mjs # Export-motorer
|
||
|
|
│
|
||
|
|
├── exporters/
|
||
|
|
│ ├── sie4Exporter.mjs # SIE4-export
|
||
|
|
│ ├── vismaAdapter.mjs # Visma-integration
|
||
|
|
│ ├── agressoAdapter.mjs # Agresso-integration
|
||
|
|
│ ├── fortnoxAdapter.mjs # Fortnox-integration
|
||
|
|
│ └── csvExporter.mjs # CSV-export
|
||
|
|
│
|
||
|
|
├── templates/
|
||
|
|
│ ├── invoice-template.html # Faktura-PDF mall
|
||
|
|
│ ├── reminder-template.html # Påminnelse-PDF mall
|
||
|
|
│ └── email/
|
||
|
|
│ ├── invoice-sent.html
|
||
|
|
│ ├── payment-received.html
|
||
|
|
│ └── reminder.html
|
||
|
|
│
|
||
|
|
├── ui/ # Frontend (återanvänd Ouroboros-stil)
|
||
|
|
│ ├── index.html
|
||
|
|
│ ├── invoices.html
|
||
|
|
│ ├── agreements.html
|
||
|
|
│ ├── payments.html
|
||
|
|
│ ├── reports.html
|
||
|
|
│ └── settings.html
|
||
|
|
│
|
||
|
|
├── tests/
|
||
|
|
│ ├── agreements.test.mjs
|
||
|
|
│ ├── invoices.test.mjs
|
||
|
|
│ ├── payments.test.mjs
|
||
|
|
│ └── exports.test.mjs
|
||
|
|
│
|
||
|
|
└── Dockerfile
|
||
|
|
```
|
||
|
|
|
||
|
|
### 8.3 Miljövariabler
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Server
|
||
|
|
BILLING_PORT=3260
|
||
|
|
NODE_ENV=production
|
||
|
|
|
||
|
|
# Database
|
||
|
|
DATABASE_URL=postgresql://user:pass@localhost:5432/billing
|
||
|
|
|
||
|
|
# Redis (Hermes)
|
||
|
|
REDIS_URL=redis://127.0.0.1:6379
|
||
|
|
HERMES_CHANNEL=aamos:hermes
|
||
|
|
|
||
|
|
# Auth
|
||
|
|
JWT_PUBLIC_KEY_PATH=/secrets/jwt.pub.pem
|
||
|
|
JWT_SECRET_KEY_PATH=/secrets/jwt.key.pem
|
||
|
|
|
||
|
|
# File storage (S3/MinIO)
|
||
|
|
S3_ENDPOINT=https://s3.landvex.com
|
||
|
|
S3_BUCKET=billing-documents
|
||
|
|
S3_ACCESS_KEY=...
|
||
|
|
S3_SECRET_KEY=...
|
||
|
|
|
||
|
|
# Email
|
||
|
|
SMTP_HOST=smtp.landvex.com
|
||
|
|
SMTP_PORT=587
|
||
|
|
SMTP_USER=...
|
||
|
|
SMTP_PASS=...
|
||
|
|
EMAIL_FROM=billing@landvex.com
|
||
|
|
|
||
|
|
# Integrations
|
||
|
|
VISMA_CLIENT_ID=...
|
||
|
|
VISMA_CLIENT_SECRET=...
|
||
|
|
AGRESSO_WSDL_URL=...
|
||
|
|
FORTNOX_CLIENT_ID=...
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 9. Säkerhet
|
||
|
|
|
||
|
|
### 9.1 Åtkomstkontroll (RBAC)
|
||
|
|
|
||
|
|
| Roll | Rättigheter |
|
||
|
|
|------|-------------|
|
||
|
|
| **admin** | Full åtkomst — alla operationer |
|
||
|
|
| **accountant** | Skapa/redigera fakturor, avtal, betalningar. Stänga perioder. |
|
||
|
|
| **viewer** | Läsa all data, generera rapporter. Inga ändringar. |
|
||
|
|
| **billing_operator** | Skapa och skicka fakturor. Registrera betalningar. Ej bokföring. |
|
||
|
|
|
||
|
|
### 9.2 Säkerhetsåtgärder
|
||
|
|
|
||
|
|
- **Input-validering:** Alla endpoints valideras med Joi
|
||
|
|
- **SQL-injektion:** Parameteriserade queries (pg)
|
||
|
|
- **XSS:** Output encoding, CSP-headers
|
||
|
|
- **CSRF:** Token-baserat skydd för state-changing requests
|
||
|
|
- **Rate limiting:** 100 req/15min generellt, 10 req/15min för auth
|
||
|
|
- **Audit logging:** Alla operationer loggas i `billing_audit_log`
|
||
|
|
- **Kryptering:** Kunddata i vila (DB), TLS i transport
|
||
|
|
- **Filuppladdning:** Validering av filtyp och storlek, virusscan
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 10. Implementeringsplan
|
||
|
|
|
||
|
|
### Etapp 1: Kärna (vecka 1-2)
|
||
|
|
- [ ] Databasschema (migrations)
|
||
|
|
- [ ] Grundläggande CRUD för avtal och fakturor
|
||
|
|
- [ ] PDF-generering
|
||
|
|
- [ ] E-postutskick
|
||
|
|
- [ ] Enkel dashboard
|
||
|
|
|
||
|
|
### Etapp 2: Automatisering (vecka 3-4)
|
||
|
|
- [ ] Schemaläggning (cron)
|
||
|
|
- [ ] Påminnelsemotor
|
||
|
|
- [ ] Betalningsimport och avstämning
|
||
|
|
- [ ] Hermes-integration
|
||
|
|
|
||
|
|
### Etapp 3: Integrationer (vecka 5-6)
|
||
|
|
- [ ] SIE4-export
|
||
|
|
- [ ] Visma-adapter
|
||
|
|
- [ ] Agresso-adapter
|
||
|
|
- [ ] CSV-export
|
||
|
|
|
||
|
|
### Etapp 4: UI & Polish (vecka 7-8)
|
||
|
|
- [ ] Komplett UI (HTML/CSS/JS)
|
||
|
|
- [ ] Rapporter och grafer
|
||
|
|
- [ ] Bulk-åtgärder
|
||
|
|
- [ ] Tester och dokumentation
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 11. Bilagor
|
||
|
|
|
||
|
|
### 11.1 Fakturanummer-serie
|
||
|
|
|
||
|
|
```
|
||
|
|
Format: {prefix}-{YYYY}-{NNNNN}
|
||
|
|
Exempel: F-2026-00042, KF-2026-00001 (kredit)
|
||
|
|
|
||
|
|
Prefix:
|
||
|
|
F = Standardfaktura
|
||
|
|
KF = Kreditfaktura
|
||
|
|
PF = Proformafaktura
|
||
|
|
R = Påminnelse
|
||
|
|
```
|
||
|
|
|
||
|
|
### 11.2 Avtalsnummer-serie
|
||
|
|
|
||
|
|
```
|
||
|
|
Format: {prefix}-{YYYY}-{NNN}
|
||
|
|
Exempel: KOM-2026-001, KOM-2026-005
|
||
|
|
|
||
|
|
Prefix:
|
||
|
|
KOM = Kommunalt avtal
|
||
|
|
KOM = Kommersiellt avtal
|
||
|
|
INT = Internt avtal
|
||
|
|
```
|
||
|
|
|
||
|
|
### 11.3 Moms-konton (BAS 2024)
|
||
|
|
|
||
|
|
| Moms | Konto | Beskrivning |
|
||
|
|
|------|-------|-------------|
|
||
|
|
| 25% | 2610 | Utgående moms, 25% |
|
||
|
|
| 12% | 2620 | Utgående moms, 12% |
|
||
|
|
| 6% | 2630 | Utgående moms, 6% |
|
||
|
|
| 0% | — | Momsexempt |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
*Dokumentversion 1.0 — 2026-07-02*
|
||
|
|
*Nästa granskning: Efter Etapp 1-komplettering*
|