bae705aa97
- Add NFC ePassport roadmap (ICAO 9303, eIDAS) - Add TensorFlow.js edge face detection (BlazeFace) - Add structured audit logger (GDPR-compliant) - Risk scoring support Part of KYC Apple Native UX v1.1.0
430 lines
16 KiB
Markdown
430 lines
16 KiB
Markdown
# ARC-001 — Canonical Domain Model
|
||
|
||
**Status:** Accepted
|
||
**Datum:** 2026-06-01
|
||
**Beslutsfattare:** AAMOS Architecture Team
|
||
|
||
---
|
||
|
||
## Kontext
|
||
|
||
AAMOS är ett verksamhetsoperativsystem vars primära designkrav är att moduler (Ekonomi, CRM, HR, Projekt, Kvalitet, Inköp, Kontrakt) kan samexistera, utbyta data och bygga på varandras entiteter utan att skapa n×m-mappningsproblem.
|
||
|
||
Utan ett kanoniskt objektsystem uppstår ofrånkomligen:
|
||
|
||
- **Semantisk drift** — samma begrepp (t.ex. "Person") modelleras på 4 olika sätt i 4 moduler, vilket omöjliggör korsmodulärsökning och AI-kontext
|
||
- **Integrationsskuld** — varje ny modul måste skriva egna adaptrar mot alla befintliga moduler
|
||
- **Auditbrist** — när ett objekt existerar på flera ställen blir spårbarhet svårt att garantera
|
||
|
||
Ekonomimodulen är referensimplementationen. Alla val i detta ADR speglas redan (eller ska speglas) i Ekonomimodulens databasschema.
|
||
|
||
---
|
||
|
||
## Beslut
|
||
|
||
AAMOS definierar tolv universella kanoniska objekt. Varje modul **MÅSTE** använda dessa objekt som primärreferenser och får inte definiera alternativa entiteter för samma semantiska koncept.
|
||
|
||
### Fundamentala fält (alla objekt)
|
||
|
||
Varje kanoniskt objekt bär alltid dessa fält:
|
||
|
||
| Fält | Typ | Beskrivning |
|
||
|------|-----|-------------|
|
||
| `id` | `UUID v7` | Globalt unikt, tidsordnat |
|
||
| `tenant_id` | `UUID` | Isoleringsdomän (se ARC-003) |
|
||
| `created_at` | `timestamptz` | ISO 8601, UTC |
|
||
| `updated_at` | `timestamptz` | ISO 8601, UTC, auto-update |
|
||
| `created_by` | `UUID` | Referens till Person.id eller system-ID |
|
||
| `deleted_at` | `timestamptz\|null` | Soft-delete, null = aktiv |
|
||
| `metadata` | `jsonb` | Fri nyckel-värde-extension (modul-specifika fält) |
|
||
| `version` | `integer` | Optimistic locking, inkrementeras vid varje UPDATE |
|
||
|
||
> **UUID v7** används för att primary key-index förblir klusterordnade i PostgreSQL och förhindrar page splits vid hög insertfrekvens.
|
||
|
||
---
|
||
|
||
### Objekt 1 — Entity
|
||
|
||
> Abstrakt basobjekt. Alla övriga kanoniska objekt **ärver** Entity-kontraktet (de fundamentala fälten ovan). Entity instansieras aldrig direkt — det är ett kontrakt, inte en tabell.
|
||
|
||
**Syfte:** Definiera det minsta kontraktet som gör ett objekt spårbart, isolerat och versionerat.
|
||
|
||
**Relationer:**
|
||
- Varje konkret objekt implementerar Entity-kontraktet
|
||
- Entity-kontraktet gör det möjligt för generiska tjänster (audit, sök, event-routing) att hantera alla objekt uniformt
|
||
|
||
---
|
||
|
||
### Objekt 2 — Organization
|
||
|
||
> Bolag, juridisk person, koncern, avdelning, team, kostnadsställe.
|
||
|
||
| Fält | Typ | Beskrivning |
|
||
|------|-----|-------------|
|
||
| `name` | `text` | Officiellt namn |
|
||
| `org_number` | `text\|null` | Organisationsnummer (landsspecifikt) |
|
||
| `org_type` | `enum` | `company`, `department`, `team`, `group`, `subsidiary` |
|
||
| `parent_id` | `UUID\|null` | Självrefererande hierarki |
|
||
| `country_code` | `char(2)` | ISO 3166-1 alpha-2 |
|
||
| `tax_id` | `text\|null` | Momsregistreringsnummer |
|
||
| `currency_code` | `char(3)` | ISO 4217 (primärvaluta) |
|
||
| `status` | `enum` | `active`, `inactive`, `merged`, `dissolved` |
|
||
| `external_refs` | `jsonb` | `{erp_id, crm_id, ...}` |
|
||
|
||
**Relationer:**
|
||
- Organization → Organization (parent_id, hierarkisk)
|
||
- Organization ↔ Person (many-to-many via `memberships`)
|
||
- Organization → Location (many)
|
||
- Organization → Contract (many)
|
||
|
||
**Ekonomimodulen-mappning:**
|
||
`accounts.owner_org_id → Organization.id`
|
||
`journal_entries.entity_id → Organization.id` (när motpart är bolag)
|
||
|
||
---
|
||
|
||
### Objekt 3 — Person
|
||
|
||
> Anställd, kontaktperson, ägare, kund, leverantörskontakt, systemanvändare.
|
||
|
||
| Fält | Typ | Beskrivning |
|
||
|------|-----|-------------|
|
||
| `first_name` | `text` | |
|
||
| `last_name` | `text` | |
|
||
| `display_name` | `text` | Beräknat eller angivet |
|
||
| `email` | `text\|null` | Primär e-post |
|
||
| `phone` | `text\|null` | E.164-format |
|
||
| `national_id` | `encrypted_text\|null` | Personnummer — krypterat i vila |
|
||
| `person_type` | `enum` | `employee`, `contact`, `customer`, `owner`, `system` |
|
||
| `primary_org_id` | `UUID\|null` | → Organization.id |
|
||
| `user_account_id` | `UUID\|null` | → auth-systemets User.id |
|
||
| `gdpr_consent` | `jsonb` | Samtyckesregister per ändamål |
|
||
|
||
**Relationer:**
|
||
- Person → Organization (many-to-many via memberships)
|
||
- Person → Location (many, adress-koppling)
|
||
- Person → Task (assignee)
|
||
- Person → Contract (signatory)
|
||
|
||
**Ekonomimodulen-mappning:**
|
||
`suppliers.contact_person_id → Person.id`
|
||
`expense_reports.submitted_by → Person.id`
|
||
|
||
---
|
||
|
||
### Objekt 4 — Asset
|
||
|
||
> Anläggningstillgång, resursobjekt, produkt, inventariepost, licenspost.
|
||
|
||
| Fält | Typ | Beskrivning |
|
||
|------|-----|-------------|
|
||
| `name` | `text` | |
|
||
| `asset_type` | `enum` | `fixed_asset`, `inventory`, `product`, `license`, `resource`, `intangible` |
|
||
| `serial_number` | `text\|null` | |
|
||
| `acquisition_date` | `date\|null` | |
|
||
| `acquisition_cost` | `numeric(18,4)\|null` | |
|
||
| `currency_code` | `char(3)\|null` | |
|
||
| `owner_org_id` | `UUID\|null` | → Organization.id |
|
||
| `location_id` | `UUID\|null` | → Location.id |
|
||
| `status` | `enum` | `active`, `disposed`, `under_maintenance`, `transferred` |
|
||
| `depreciation_policy` | `jsonb\|null` | Avskrivningsregler (Ekonomi) |
|
||
|
||
**Relationer:**
|
||
- Asset → Organization (owner)
|
||
- Asset → Location (plats)
|
||
- Asset → Transaction (inköp, avskrivning)
|
||
- Asset → Contract (leasing, garanti)
|
||
|
||
**Ekonomimodulen-mappning:**
|
||
`fixed_assets` tabell är en direkt implementation av Asset med utökad `depreciation_policy`.
|
||
|
||
---
|
||
|
||
### Objekt 5 — Location
|
||
|
||
> Adress, arbetsplats, lagerplats, land, leveransadress.
|
||
|
||
| Fält | Typ | Beskrivning |
|
||
|------|-----|-------------|
|
||
| `name` | `text\|null` | T.ex. "Huvudkontor Stockholm" |
|
||
| `location_type` | `enum` | `address`, `workplace`, `warehouse`, `country`, `virtual` |
|
||
| `street` | `text\|null` | |
|
||
| `city` | `text\|null` | |
|
||
| `postal_code` | `text\|null` | |
|
||
| `country_code` | `char(2)` | ISO 3166-1 |
|
||
| `region` | `text\|null` | Stat/landsdel |
|
||
| `coordinates` | `point\|null` | PostGIS `POINT(lon lat)` |
|
||
| `parent_id` | `UUID\|null` | Hierarki (land → stad → adress) |
|
||
|
||
**Relationer:**
|
||
- Location → Organization (many)
|
||
- Location → Person (hemadress, arbetsplats)
|
||
- Location → Asset (plats)
|
||
|
||
---
|
||
|
||
### Objekt 6 — Project
|
||
|
||
> Projekt, uppdrag, kostnadsbärare, kampanj, budgetpost.
|
||
|
||
| Fält | Typ | Beskrivning |
|
||
|------|-----|-------------|
|
||
| `name` | `text` | |
|
||
| `code` | `text\|null` | Projektnamn/nummer för referens |
|
||
| `project_type` | `enum` | `internal`, `client`, `rnd`, `infrastructure`, `overhead` |
|
||
| `status` | `enum` | `planning`, `active`, `on_hold`, `completed`, `cancelled` |
|
||
| `owner_id` | `UUID` | → Person.id |
|
||
| `org_id` | `UUID` | → Organization.id |
|
||
| `budget` | `numeric(18,4)\|null` | |
|
||
| `currency_code` | `char(3)\|null` | |
|
||
| `start_date` | `date\|null` | |
|
||
| `end_date` | `date\|null` | |
|
||
| `parent_project_id` | `UUID\|null` | Delprojekt-hierarki |
|
||
|
||
**Relationer:**
|
||
- Project → Organization (ägande)
|
||
- Project → Person (projektledare, team)
|
||
- Project → Task (nedbrytning)
|
||
- Project → Transaction (kostnader, intäkter)
|
||
- Project → Contract (kontrakt kopplade till projekt)
|
||
|
||
**Ekonomimodulen-mappning:**
|
||
`cost_centers.project_id → Project.id`
|
||
`budget_lines.project_id → Project.id`
|
||
|
||
---
|
||
|
||
### Objekt 7 — Task
|
||
|
||
> Arbetsuppgift, aktivitet, ärende, batchjobb, processuppgift.
|
||
|
||
| Fält | Typ | Beskrivning |
|
||
|------|-----|-------------|
|
||
| `title` | `text` | |
|
||
| `description` | `text\|null` | |
|
||
| `task_type` | `enum` | `manual`, `automated`, `approval`, `review`, `notification` |
|
||
| `status` | `enum` | `pending`, `in_progress`, `blocked`, `completed`, `cancelled` |
|
||
| `priority` | `enum` | `low`, `medium`, `high`, `critical` |
|
||
| `assignee_id` | `UUID\|null` | → Person.id |
|
||
| `project_id` | `UUID\|null` | → Project.id |
|
||
| `parent_task_id` | `UUID\|null` | Subtask-hierarki |
|
||
| `due_date` | `timestamptz\|null` | |
|
||
| `completed_at` | `timestamptz\|null` | |
|
||
| `workflow_instance_id` | `UUID\|null` | → Workflow.id |
|
||
|
||
**Relationer:**
|
||
- Task → Person (assignee, creator)
|
||
- Task → Project (tillhörighet)
|
||
- Task → Workflow (processdrivet)
|
||
- Task → Document (bilagor)
|
||
|
||
---
|
||
|
||
### Objekt 8 — Workflow
|
||
|
||
> Processinstans. Kopplas till Workflow Engine (framtida).
|
||
|
||
| Fält | Typ | Beskrivning |
|
||
|------|-----|-------------|
|
||
| `workflow_definition_id` | `text` | Referens till process-definition |
|
||
| `workflow_version` | `text` | Versionerad processdefinition |
|
||
| `status` | `enum` | `pending`, `running`, `waiting`, `completed`, `failed`, `cancelled` |
|
||
| `context` | `jsonb` | Runtime-kontext för processinstansen |
|
||
| `started_at` | `timestamptz\|null` | |
|
||
| `completed_at` | `timestamptz\|null` | |
|
||
| `initiator_id` | `UUID` | → Person.id eller system |
|
||
| `current_step` | `text\|null` | Stegidentifierare |
|
||
|
||
**Relationer:**
|
||
- Workflow → Task (genererade uppgifter)
|
||
- Workflow → Document (processade dokument)
|
||
- Workflow → Transaction (utlösta transaktioner)
|
||
|
||
**Ekonomimodulen-mappning:**
|
||
Fakturaflöde (inkommen faktura → godkännande → betalning) är en Workflow-instans.
|
||
|
||
---
|
||
|
||
### Objekt 9 — Document
|
||
|
||
> Faktura, kvitto, kontrakt, rapport, fil, attest.
|
||
|
||
| Fält | Typ | Beskrivning |
|
||
|------|-----|-------------|
|
||
| `title` | `text` | |
|
||
| `document_type` | `enum` | `invoice`, `receipt`, `contract`, `report`, `attachment`, `specification`, `certificate` |
|
||
| `mime_type` | `text\|null` | `application/pdf`, `image/jpeg`, etc. |
|
||
| `storage_key` | `text\|null` | S3/blob-sökväg |
|
||
| `checksum` | `text\|null` | SHA-256 av filinnehållet |
|
||
| `size_bytes` | `bigint\|null` | |
|
||
| `source_system` | `text\|null` | Ursprungssystem |
|
||
| `related_entity_id` | `UUID\|null` | Polymorf referens |
|
||
| `related_entity_type` | `text\|null` | T.ex. `Transaction`, `Contract` |
|
||
| `classification` | `enum` | `public`, `internal`, `confidential`, `restricted` (se ARC-003) |
|
||
| `extracted_data` | `jsonb\|null` | AI-extraherade fält (se ARC-002) |
|
||
|
||
**Relationer:**
|
||
- Document → Transaction (underlag)
|
||
- Document → Contract (kontraktsdokument)
|
||
- Document → Workflow (processunderlag)
|
||
|
||
**Ekonomimodulen-mappning:**
|
||
`supplier_invoices.document_id → Document.id`
|
||
`expense_reports.receipt_document_ids → Document.id[]`
|
||
|
||
---
|
||
|
||
### Objekt 10 — Transaction
|
||
|
||
> Ekonomisk händelse. `JournalEntry` är en Transaction med `transaction_type = journal_entry`.
|
||
|
||
| Fält | Typ | Beskrivning |
|
||
|------|-----|-------------|
|
||
| `transaction_type` | `enum` | `journal_entry`, `payment`, `invoice`, `credit_note`, `accrual`, `depreciation`, `transfer` |
|
||
| `amount` | `numeric(18,4)` | Absolutbelopp |
|
||
| `currency_code` | `char(3)` | ISO 4217 |
|
||
| `base_currency_amount` | `numeric(18,4)\|null` | Omräknat till baskvaluta |
|
||
| `exchange_rate` | `numeric(12,8)\|null` | |
|
||
| `status` | `enum` | `draft`, `posted`, `voided`, `pending_approval` |
|
||
| `transaction_date` | `date` | Transaktionsdatum (affärsdatum) |
|
||
| `value_date` | `date\|null` | Valuteringsdatum |
|
||
| `reference` | `text\|null` | Externt referensnummer |
|
||
| `counterparty_id` | `UUID\|null` | → Organization.id eller Person.id |
|
||
| `project_id` | `UUID\|null` | → Project.id |
|
||
| `document_id` | `UUID\|null` | → Document.id (underlag) |
|
||
| `lines` | `jsonb` | Transaktionsrader med konton och belopp |
|
||
|
||
**Relationer:**
|
||
- Transaction → Organization (motpart)
|
||
- Transaction → Person (utförare)
|
||
- Transaction → Document (verifikat)
|
||
- Transaction → Project (kostnadsbärare)
|
||
- Transaction → Asset (anläggningstillgångsrörelser)
|
||
|
||
**Ekonomimodulen-mappning:**
|
||
`journal_entries` är Transactions med `transaction_type = journal_entry`.
|
||
Ledger Engine håller transaktionsrader i `lines`-jsonb eller separata rader beroende på volym.
|
||
|
||
---
|
||
|
||
### Objekt 11 — Event
|
||
|
||
> Systemevent, domänevent. Detta är Hermes Event Fabric-payloaden.
|
||
|
||
| Fält | Typ | Beskrivning |
|
||
|------|-----|-------------|
|
||
| `event_type` | `text` | Namnrymd: `aamos.ekonomi.transaction.posted` |
|
||
| `event_version` | `text` | Semver av event-schemat |
|
||
| `source_service` | `text` | Originerande tjänst |
|
||
| `correlation_id` | `UUID` | Spårnings-ID genom systemet |
|
||
| `causation_id` | `UUID\|null` | ID på det event som orsakade detta |
|
||
| `subject_type` | `text` | Typ av ämnesobjekt |
|
||
| `subject_id` | `UUID` | ID på ämnesobjektet |
|
||
| `payload` | `jsonb` | Eventnyttolast |
|
||
| `published_at` | `timestamptz` | Publiceringstidpunkt |
|
||
|
||
**Namnrymdskonvention:**
|
||
`aamos.<modul>.<objekt>.<verb>` — t.ex. `aamos.ekonomi.transaction.posted`, `aamos.crm.contact.created`
|
||
|
||
**Relationer:**
|
||
- Event refererar till vilket som helst kanoniskt objekt via `subject_id`
|
||
- Events persisteras i JSONL-fallback för replay
|
||
|
||
---
|
||
|
||
### Objekt 12 — Contract
|
||
|
||
> Avtal, SLA, leverantörsavtal, anställningsavtal, ramavtal, licens.
|
||
|
||
| Fält | Typ | Beskrivning |
|
||
|------|-----|-------------|
|
||
| `title` | `text` | |
|
||
| `contract_type` | `enum` | `supplier`, `customer`, `employment`, `sla`, `license`, `framework`, `nda` |
|
||
| `status` | `enum` | `draft`, `pending_signature`, `active`, `expired`, `terminated`, `renewed` |
|
||
| `counterparty_org_id` | `UUID\|null` | → Organization.id |
|
||
| `counterparty_person_id` | `UUID\|null` | → Person.id |
|
||
| `owner_org_id` | `UUID` | → Organization.id (intern part) |
|
||
| `start_date` | `date\|null` | |
|
||
| `end_date` | `date\|null` | |
|
||
| `auto_renewal` | `boolean` | |
|
||
| `value` | `numeric(18,4)\|null` | Kontraktsvärde |
|
||
| `currency_code` | `char(3)\|null` | |
|
||
| `document_id` | `UUID\|null` | → Document.id (signerat PDF) |
|
||
| `terms` | `jsonb\|null` | Strukturerade avtalsvillkor |
|
||
|
||
**Relationer:**
|
||
- Contract → Organization (parter)
|
||
- Contract → Person (signatärer)
|
||
- Contract → Document (kontraktsdokument)
|
||
- Contract → Transaction (betalningsplan, fakturor)
|
||
- Contract → Asset (leasade tillgångar)
|
||
|
||
---
|
||
|
||
## Ekonomimodulens fullständiga mappning
|
||
|
||
```
|
||
Ekonomimodul → Kanonisk typ
|
||
──────────────────────────────────────────
|
||
JournalEntry → Transaction (type=journal_entry)
|
||
Supplier → Organization (org_type=company)
|
||
Customer → Organization (org_type=company)
|
||
SupplierInvoice → Document (type=invoice) + Transaction
|
||
ExpenseReport → Document (type=report) + Transaction[]
|
||
FixedAsset → Asset (type=fixed_asset)
|
||
CostCenter → Project (type=overhead/internal)
|
||
Employee → Person (type=employee)
|
||
ApprovalFlow → Workflow
|
||
Receipt → Document (type=receipt)
|
||
SupplierContract → Contract (type=supplier)
|
||
```
|
||
|
||
---
|
||
|
||
## Konsekvenser
|
||
|
||
### Positiva
|
||
- Ny modul definierar bara sina egna **extensions** i `metadata`, inte nya bastyper
|
||
- AI-kontextfönster kan laddas med enhetliga objektrepresentationer
|
||
- Korsmodulär sökning och audit fungerar utan adaptrar
|
||
- Event-routing är självbeskrivande via `subject_type`
|
||
|
||
### Negativa / risker
|
||
- **Migrationsarbete:** Ekonomimodulen måste verifiera att befintliga tabeller uppfyller kontraktet
|
||
- **UUID v7 kräver** att databasen kan generera dem (`pgcrypto` eller app-sida)
|
||
- `metadata jsonb` kan bli en dumpingsplats — kräver disciplin och review
|
||
|
||
---
|
||
|
||
## Implementation
|
||
|
||
### Fas 1 (Ekonomimodulen, nu)
|
||
1. Verifiera att alla tabeller har `tenant_id`, `version`, `deleted_at`
|
||
2. Lägg till `metadata jsonb DEFAULT '{}'` på tabeller som saknar det
|
||
3. Dokumentera mappning i modulens README
|
||
|
||
### Fas 2 (inför CRM)
|
||
1. Skapa dedikerade tabeller för Organization och Person i `core`-schema
|
||
2. Ekonomimodulen FK:ar till `core.organizations` och `core.persons`
|
||
|
||
### Fas 3 (plattformsnivå)
|
||
1. GraphQL/REST-lager för kanoniska objekt
|
||
2. Unified search-index över alla objekt
|
||
3. AI-kontext-builder som hämtar kanonisk representation
|
||
|
||
---
|
||
|
||
## Öppna frågor
|
||
|
||
1. **Ska kanoniska objekt bo i ett delat `core`-schema eller replikeras per modul?**
|
||
Förslag: Shared schema för Person, Organization, Location. Replika med CDC för moduler som behöver hög läsprestanda.
|
||
|
||
2. **UUID v7 vs ULID?**
|
||
UUID v7 är nu RFC-standard och stöds bättre i PostgreSQL-ekosystemet. ULID är alternativ om befintlig kod är svår att migrera.
|
||
|
||
3. **`metadata`-schemavalidering?**
|
||
Modul-specifika JSON Schema-fragment bör definieras och valideras vid INSERT/UPDATE.
|
||
|
||
4. **Event-schema-registret?**
|
||
Hermes bör ha ett schema-register som validerar event-payloads mot `event_version`. Se ARC-002.
|