# 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 ` - **Tenant:** `X-Tenant-ID: ` - **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___.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*