-- ═══════════════════════════════════════════════════════════════════════════ -- AAMOS Ledger Engine — PostgreSQL Schema -- Etapp 1 — Vertikal stängning -- Principer: Audit First · Tenant Isolation · Deterministic · Reproducible -- ═══════════════════════════════════════════════════════════════════════════ -- ── Chart of Accounts (Kontoplan) ─────────────────────────────────────────── -- Separerad från Rule Engine per arkitekturprincipen. -- Stöder: BAS (SE), IFRS, US GAAP, lokala kontoplaner CREATE TABLE IF NOT EXISTS ledger_accounts ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id TEXT NOT NULL, account_number TEXT NOT NULL, -- t.ex. '1910', '3000' name TEXT NOT NULL, account_type TEXT NOT NULL, -- 'asset'|'liability'|'equity'|'revenue'|'expense' normal_balance TEXT NOT NULL, -- 'debit'|'credit' coa_standard TEXT NOT NULL DEFAULT 'BAS', -- 'BAS'|'IFRS'|'USGAAP'|'custom' parent_account TEXT, -- för kontostruktur/gruppering vat_code TEXT, -- koppling till momsregel is_active BOOLEAN NOT NULL DEFAULT TRUE, metadata JSONB NOT NULL DEFAULT '{}', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), UNIQUE (tenant_id, account_number, coa_standard) ); -- ── Journal Entries (Verifikationer) ──────────────────────────────────────── -- Ledgerns sanning. IMMUTABLE efter posted. -- Alla fält för full spårbarhet per BYGGPLAN 0.001. CREATE TABLE IF NOT EXISTS ledger_journal_entries ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id TEXT NOT NULL, entry_number BIGINT, -- löpnummer per tenant+räkenskapsår fiscal_year INTEGER NOT NULL, -- t.ex. 2026 period TEXT NOT NULL, -- 'YYYY-MM' entry_date DATE NOT NULL, description TEXT NOT NULL, reference TEXT, -- externt ref (fakturanr, kvittonr) source_type TEXT NOT NULL, -- 'manual'|'import_sie'|'import_csv'|'bank'|'system' source_id TEXT, -- ID i källsystemet status TEXT NOT NULL DEFAULT 'draft', -- 'draft'|'posted'|'voided' void_reason TEXT, voided_at TIMESTAMPTZ, voided_by TEXT, -- Observability / Trace trace_id TEXT NOT NULL, correlation_id TEXT NOT NULL, user_id TEXT NOT NULL, decision_source TEXT NOT NULL DEFAULT 'user', -- 'user'|'system'|'agent' -- Metadata metadata JSONB NOT NULL DEFAULT '{}', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), posted_at TIMESTAMPTZ, CONSTRAINT chk_status CHECK (status IN ('draft','posted','voided')), CONSTRAINT chk_source CHECK (source_type IN ('manual','import_sie','import_csv','bank','system','agent')) ); CREATE SEQUENCE IF NOT EXISTS ledger_entry_number_seq START 1; -- ── Journal Lines (Verifikationsrader) ────────────────────────────────────── -- Dubbelbokföring: sum(debit) == sum(credit) enforced i applikationskoden. CREATE TABLE IF NOT EXISTS ledger_journal_lines ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), entry_id UUID NOT NULL REFERENCES ledger_journal_entries(id) ON DELETE CASCADE, tenant_id TEXT NOT NULL, line_number INTEGER NOT NULL, -- rad 1, 2, 3… account_number TEXT NOT NULL, account_name TEXT, -- denormaliserat för rapporter debit NUMERIC(18,2), credit NUMERIC(18,2), currency TEXT NOT NULL DEFAULT 'SEK', amount_base NUMERIC(18,2), -- belopp i basvaluta vat_code TEXT, -- momsregel vid tidpunkten vat_amount NUMERIC(18,2), cost_center TEXT, project_code TEXT, description TEXT, metadata JSONB NOT NULL DEFAULT '{}', CONSTRAINT chk_debit_credit CHECK ( (debit IS NOT NULL AND credit IS NULL) OR (debit IS NULL AND credit IS NOT NULL) ), CONSTRAINT chk_positive_debit CHECK (debit IS NULL OR debit > 0), CONSTRAINT chk_positive_credit CHECK (credit IS NULL OR credit > 0) ); -- ── Audit Log ──────────────────────────────────────────────────────────────── -- Varje förändring i ledgern genererar en auditpost. -- APPEND ONLY — aldrig DELETE eller UPDATE. CREATE TABLE IF NOT EXISTS ledger_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, -- 'journal_entry'|'account'|'period' entity_id TEXT NOT NULL, action TEXT NOT NULL, -- 'created'|'posted'|'voided'|'imported' decision_source TEXT NOT NULL, before_state JSONB, after_state JSONB, ip_address TEXT, session_id TEXT, ts TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- ── Periods ────────────────────────────────────────────────────────────────── -- Workflow-styrd periodstängning. CREATE TABLE IF NOT EXISTS ledger_periods ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id TEXT NOT NULL, fiscal_year INTEGER NOT NULL, period TEXT NOT NULL, -- 'YYYY-MM' status TEXT NOT NULL DEFAULT 'open', -- 'open'|'review'|'approved'|'closed' opened_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), closed_at TIMESTAMPTZ, closed_by TEXT, workflow_id UUID, -- referens till workflow engine trial_balance JSONB, -- snapshot vid stängning trace_id TEXT NOT NULL, metadata JSONB NOT NULL DEFAULT '{}', UNIQUE (tenant_id, fiscal_year, period), CONSTRAINT chk_period_status CHECK (status IN ('open','review','approved','closed')) ); -- ── Indexes ─────────────────────────────────────────────────────────────────── CREATE INDEX IF NOT EXISTS idx_journal_tenant_period ON ledger_journal_entries (tenant_id, fiscal_year, period); CREATE INDEX IF NOT EXISTS idx_journal_status ON ledger_journal_entries (tenant_id, status); CREATE INDEX IF NOT EXISTS idx_journal_date ON ledger_journal_entries (tenant_id, entry_date); CREATE INDEX IF NOT EXISTS idx_journal_reference ON ledger_journal_entries (tenant_id, reference); CREATE INDEX IF NOT EXISTS idx_lines_entry ON ledger_journal_lines (entry_id); CREATE INDEX IF NOT EXISTS idx_lines_account ON ledger_journal_lines (tenant_id, account_number); CREATE INDEX IF NOT EXISTS idx_audit_entity ON ledger_audit_log (tenant_id, entity_type, entity_id); CREATE INDEX IF NOT EXISTS idx_audit_trace ON ledger_audit_log (trace_id); CREATE INDEX IF NOT EXISTS idx_accounts_tenant ON ledger_accounts (tenant_id, account_number); CREATE INDEX IF NOT EXISTS idx_periods_tenant ON ledger_periods (tenant_id, fiscal_year, period); -- ── BAS 2024 — Bas-konton för Wavult Group (seed) ─────────────────────────── -- Läggs in via seed-script, inte här. Schema är rent.