Files
boc/aamos-billing/DESIGN.md
T
Bernt 05ed037fe8 pilot.landvex.com: HTTPS + Full Stack Verified
- DNS: pilot.landvex.com -> 16.170.83.169
- TLS: Let's Encrypt certificate (expires 2026-09-30)
- Nginx: reverse proxy with SSL termination
- API: https://pilot.landvex.com/api/v1/missions
- UI: https://pilot.landvex.com/
- Upload: POST /api/v1/missions/import (multipart/form-data)

Verified:
 https://pilot.landvex.com/health
 https://pilot.landvex.com/version
 https://pilot.landvex.com/api/v1/missions (list)
 https://pilot.landvex.com/api/v1/missions/:id (get)
 POST /api/v1/missions/import (video upload)
 UI loads with title 'LandveX Intelligence Lab'

Next: Pilot 001 — Break the system!
2026-07-02 17:34:19 +00:00

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*