# Planung Firmen-Software

Stand: 2026-05-22

## Zielbild

Die Software wird als mehrplatzfähiges Firmensystem geplant:

- lokaler Betrieb mit eigenem Server bei einer Firma
- öffentlicher SaaS-Betrieb mit vielen Firmen auf einer Instanz
- Backend in Rust
- PostgreSQL als Datenbank
- Kommunikation zwischen Clients und Backend per WebSocket für Live-Daten
- zusätzliche REST-API für externe Zugriffe, Importe und Integrationen
- Webfrontend für Browser
- nativer Desktopclient für Linux, Windows und macOS
- gleiche fachliche Funktionen in Webclient und Desktopclient
- Live-Aktualisierung der UI, wenn Backend-Daten geändert werden
- verschlüsselte Kommunikation zwischen Clients und Backend
- Offline-Fähigkeit im Desktopclient
- automatische Updates für den Desktopclient

## Begriffe

Der Begriff `Kunde` wird nur für Kunden einer Firma verwendet, also z.B. für
Rechnungsempfänger.

Für die Betreiber-/Mandantenebene wird stattdessen `Firma` verwendet. Eine Firma
ist die Organisation, die das System nutzt und ein eigenes PostgreSQL-Schema
bekommt.

Technische Begriffe:

- `Firma`: fachlicher Mandant, also Nutzerorganisation des Systems
- `organization`: technischer Name für eine Firma in Code, API und Datenbank
- `Firmenschema`: PostgreSQL-Schema einer Firma, z.B. `company_<organization_id>`
- `User`: globaler Benutzeraccount in `public`
- `Kunde`: Kunde einer Firma innerhalb eines Firmenschemas
- `Vorgang`: fachlicher Sammelbegriff für Aktivität, Aufgabe, Wiedervorlage,
  Termin oder interne Bearbeitung mit Bezug zu Kunden, Lieferanten, Dokumenten
  oder Belegen
- `activity`: technischer Name für einen Vorgang in Code, API und Datenbank

Namensregel:

- Tabellen, Spalten, API-Felder und Code-Bezeichner sind englisch.
- Die UI zeigt deutsche Begriffe und Labels.
- Deutsche UI-Texte, Fehlermeldungen und Dokumentation verwenden echte Umlaute
  (`ä`, `ö`, `ü`, `Ä`, `Ö`, `Ü`) und `ß`. ASCII-Umschreibungen wie `ae`,
  `oe`, `ue` oder `ss` werden nur in technischen Bezeichnern verwendet, wenn
  das nötig ist.
- Beispiel: Datenbank/API `organization`, UI `Firma`.

## Mandantenmodell

Jede Firma bekommt ein eigenes PostgreSQL-Schema. Das trennt operative Daten im
SaaS-Betrieb klar voneinander und funktioniert gleichzeitig für lokale
Einzelfirmen-Installationen.

Wichtige Entscheidung:

- In `public` liegen nur globale Daten, die für Login und Firmenzuordnung
  notwendig sind.
- Alle fachlichen Daten, Rollen, Rechte, Einstellungen, Nummernkreise und Logs
  liegen im jeweiligen Firmenschema.
- Ein User kann mehreren Firmen zugeordnet sein.
- Eine Firma verwaltet nicht mehrere weitere Firmen/Mandanten in derselben
  Installation.

### `public`-Schema

`public` bleibt bewusst klein.

Geplante Tabellen:

- `users`
- `organizations`
- `user_organizations`
- `organization_domains`, optional für SaaS-Subdomains
- `auth_identities`, für Passwortlogin, SSO und externe Identity Provider
- `sessions` oder `refresh_tokens`

Nicht in `public`:

- Rollen
- Rechte
- Audit-Log
- Fachmodule
- Nummernkreise
- API-Zugangsdaten
- Firmeneinstellungen

Diese Daten gehören in das jeweilige Firmenschema.

### Firmenschema

Schema-Namensvorschlag:

- `company_<organization_id>`

`organization_id` ist eine technische, nicht sprechende Kennung. Schema-Namen
enthalten keine Firmennamen.

Geplante Tabellen je Firmenschema:

- `roles`
- `permissions`
- `role_permissions`
- `user_roles`
- `settings`
- `number_ranges`
- `audit_log`
- `customers`
- `suppliers`
- `contacts`
- `addresses`
- `items`
- `item_price_sources`
- `item_prices`
- `price_rules`
- `customer_price_terms`
- `supplier_price_terms`
- `cash_discount_terms`
- `stock_movements`
- `quotes`
- `quote_versions`
- `quote_items`
- `incoming_invoices`
- `incoming_invoice_items`
- `outgoing_invoices`
- `outgoing_invoice_items`
- `communications`
- `activities`
- `activity_links`
- `tasks`
- `document_templates`
- `documents`
- `document_versions`
- `imports`
- `import_mappings`
- `api_connectors`
- `webhooks`

### Mandantenauflösung

Vorgeschlagener Ablauf:

1. User meldet sich global an.
2. Backend liest aus `public.user_organizations`, welchen Firmen der User zugeordnet ist.
3. Falls der User mehrere Firmen hat, wählt er eine aktive Firma aus.
4. Backend erstellt ein Token oder eine Session mit aktiver `organization_id`.
5. Jeder REST-Request und jede WebSocket-Verbindung wird gegen diese aktive Firma
   geprüft.
6. Datenbankzugriffe setzen kontrolliert das passende Firmenschema.

Für den öffentlichen Betrieb kann zusätzlich eine Subdomain genutzt werden:

- `firma-a.example.com`
- `firma-b.example.com`

Die Subdomain darf aber nur eine Vorauswahl sein. Die finale Berechtigung kommt
aus `public.user_organizations`.

## Betriebsarten

### Eigener Server bei einer Firma

- eine Backend-Instanz
- eine PostgreSQL-Datenbank
- genau eine Firma
- optional mehrere User
- dieselbe Mandantenarchitektur wie im SaaS-Betrieb
- Installation per Docker Compose, Systemdienst oder Installationsprogramm
- keine öffentliche Selbstregistrierung
- Firmenschema wird während Installation oder beim ersten Start erzeugt
- erster Firmen-User wird während Installation oder Erstkonfiguration angelegt

Die lokale Version soll bewusst nur eine Firma verwalten können. Dadurch bleibt
die lokale Installation einfacher, nutzt intern aber trotzdem dieselbe
Firmenschema-Architektur wie der öffentliche Server.

### Öffentlicher SaaS-Server

- Betrieb durch uns
- eine oder mehrere Backend-Instanzen
- zentrale PostgreSQL-Datenbank
- viele Firmen, jeweils eigenes Schema
- TLS zwingend
- Backup, Monitoring, Rate-Limits und Mandantentrennung zwingend
- Benutzer können mehreren Firmen zugeordnet sein
- öffentliche Registrierung möglich
- Freischaltung neuer Firmen erfolgt durch einen Administrator
- vor Freischaltung ist kein produktiver Zugriff auf das Firmenschema möglich

## Registrierung und Freischaltung

### Öffentliche Registrierung

Für den öffentlichen Server gibt es eine Registrierungsmaske.

Pflichtangaben:

- Firmenname
- E-Mail-Adresse

Ablauf:

1. Interessent gibt Firmenname und E-Mail-Adresse ein.
2. Server legt einen globalen User in `public.users` an, falls die E-Mail noch
   nicht existiert.
3. Server legt einen Firmeneintrag in `public.organizations` mit Status `pending_approval`
   an.
4. Server legt die Zuordnung in `public.user_organizations` an.
5. Server erzeugt das Firmenschema entweder sofort im gesperrten Zustand oder
   erst nach Freischaltung. Bevorzugt: Schema erst nach Admin-Freischaltung
   erzeugen, damit abgelehnte Registrierungen keine operativen Strukturen
   hinterlassen.
6. Ein Administrator prüft und schaltet die Firma frei.
7. Bei Freischaltung erzeugt das Backend das Firmenschema, Basistabellen,
   Standardrollen, Standardrechte und Grundeinstellungen.
8. Server erzeugt ein zufälliges Initialpasswort.
9. Server sendet eine E-Mail an die registrierte Adresse.
10. User meldet sich mit E-Mail-Adresse und Initialpasswort an.
11. Beim ersten Login muss das Passwort sofort geändert werden.
12. Danach kann der User Firmendaten, Einstellungen, Nummernkreise, Vorlagen und
    weitere Stammdaten festlegen.

Die E-Mail-Adresse ist der Username.

### Admin-Freischaltung

Neue Firmenregistrierungen stehen in einer Admin-Oberfläche.

Admin-Aktionen:

- Registrierung ansehen
- Firma freischalten
- Registrierung ablehnen
- E-Mail erneut senden
- Firmenschema-Erstellung erneut versuchen, falls ein technischer Fehler auftrat

Statusmodell für `public.organizations`:

- `pending_approval`
- `approved`
- `active`
- `rejected`
- `suspended`

Vorgeschlagene Bedeutung:

- `pending_approval`: Registrierung eingegangen, aber noch nicht freigegeben
- `approved`: durch Admin freigegeben, technische Anlage läuft oder steht bevor
- `active`: Firma ist nutzbar
- `rejected`: Registrierung wurde abgelehnt
- `suspended`: Firma wurde nachträglich gesperrt

### Initialpasswort und Passwortwechsel

Initialpasswörter werden zufällig erzeugt und per E-Mail versendet.

Sicherheitsregeln:

- Passwort wird nie im Klartext gespeichert.
- Passwort-Hash liegt in `public.users.password_hash`.
- User bekommt Status `must_change_password`.
- Beim ersten Login sind nur Passwortänderung und Logout erlaubt.
- Initialpasswort sollte zeitlich ablaufen.
- Nach erfolgreicher Änderung wird `must_change_password` entfernt.

Optional spätere Verbesserung:

- Statt Klartextpasswort per E-Mail wird ein einmaliger Set-Password-Link
  verschickt. Das ist sicherer, aber der aktuell geplante Ablauf ist:
  zufälliges Passwort per E-Mail.

### User zur Firma einladen

Ein berechtigter Firmen-User kann weitere User einladen.

Ablauf:

1. Firmen-User gibt neue E-Mail-Adresse ein.
2. Backend prüft Recht, z.B. `users.invite`.
3. Falls die E-Mail in `public.users` noch nicht existiert, wird ein globaler
   User angelegt.
4. Backend legt oder aktualisiert `public.user_organizations`.
5. Im Firmenschema werden die gewünschten Rollen in `user_roles` vergeben.
6. Backend erzeugt ein zufälliges Initialpasswort oder ein neues Initialpasswort,
   falls der User noch kein aktives Passwort für diese Installation hat.
7. Backend setzt `must_change_password`.
8. Backend sendet eine Einladung per E-Mail.
9. Eingeladener User meldet sich mit E-Mail und Initialpasswort an.
10. Passwortänderung ist beim ersten Login zwingend.

Wenn ein User bereits in einer anderen Firma aktiv ist, darf dessen bestehendes
Passwort nicht ungefragt überschrieben werden. In diesem Fall sollte die
Einladung als Firmenzuordnung erfolgen und der User sich mit seinem bestehenden
Login anmelden. Für diesen Fall kann die Einladung einen Hinweis statt eines
neuen Passworts senden.

### Lokale Erstinstallation

Für lokale Installationen wird ein eigener Erstkonfigurationsprozess geplant.

Ablauf:

1. Installation richtet Backend und PostgreSQL ein.
2. Beim ersten Start erkennt das Backend, dass keine Firma existiert.
3. Setup-Maske fragt Firmenname, Admin-E-Mail und Initialpasswort ab oder erzeugt
   ein zufälliges Passwort.
4. Backend erstellt genau eine Firma und genau ein Firmenschema.
5. Admin-User wird als `owner` zugeordnet.
6. Danach wird der Setup-Modus dauerhaft deaktiviert.

Eine zweite Firma darf in der lokalen Version nicht angelegt werden.

## Sicherheit

Mindestanforderungen:

- TLS für öffentlichen Betrieb
- Zertifikatsverwaltung über Reverse Proxy, z.B. Caddy, Traefik oder Nginx
- zusätzliche sessionbasierte Nutzdatenverschlüsselung oberhalb von HTTPS/WSS
- keine fachlichen Nutzdaten unverschlüsselt über das Netzwerk
- keine fachlichen Nutzdaten unverschlüsselt in PostgreSQL
- Passwort-Hashing mit Argon2id oder vergleichbar
- Zwei-Faktor-Authentifizierung
- Single Sign-On
- rollenbasierte Rechte
- Rechte pro atomarer Aktion mit Lesen, Schreiben und Löschen
- firmenschema-bewusste Datenbankzugriffe
- Audit-Log je Firmenschema
- sichere Speicherung von API-Zugangsdaten
- Backups und Restore-Konzept je Firma
- Schutz gegen unautorisierte WebSocket-Abos
- Initialpasswörter laufen ab
- Passwortwechsel beim ersten Login erzwingen
- Admin-Freischaltung für SaaS-Registrierungen

## Kommunikation und Verschlüsselung

### Grundsatz

Alle produktiven Verbindungen zwischen Client und Backend müssen verschlüsselt
sein.

Protokolle:

- REST über HTTPS
- WebSocket über WSS

Unverschlüsselte Verbindungen sind nicht vorgesehen. Das gilt auch für lokale
Installationen. Für Entwicklung können gesonderte Debug-Schalter existieren, sie
dürfen aber nicht Teil des produktiven Betriebs sein.

Zusätzlich zu HTTPS/WSS werden fachliche Nutzdaten auf Anwendungsebene
verschlüsselt. TLS schützt den Transportkanal, die Anwendungsschicht-
Verschlüsselung schützt die eigentlichen Payloads innerhalb der Session.

Grundsatz:

- keine fachlichen Nutzdaten werden unverschlüsselt übertragen
- keine fachlichen Nutzdaten werden unverschlüsselt in PostgreSQL gespeichert
- Metadaten werden nur dann unverschlüsselt gespeichert, wenn sie technisch für
  Routing, Login, Rechteprüfung, Indizes oder Betrieb zwingend notwendig sind
- Klartext existiert nur kurzzeitig im Backend-Prozessspeicher während der
  Verarbeitung

### Sessionbasierte Nutzdatenverschlüsselung

Für jede angemeldete Client-Session wird ein eigener Session-Schlüssel
ausgehandelt oder vom Backend sicher bereitgestellt.

Ziel:

- REST-Payloads zusätzlich zu HTTPS verschlüsseln
- WebSocket-Payloads zusätzlich zu WSS verschlüsseln
- Replay und Manipulation über AEAD-Verfahren verhindern
- Session-Schlüssel beim Logout, Passwortwechsel, User-Sperre oder Ablauf
  ungültig machen

Empfohlenes Verfahren:

- asymmetrischer Schlüsselaustausch beim Login oder bei Firmenauswahl
- danach symmetrische Verschlüsselung pro Session
- AEAD-Verfahren, z.B. AES-256-GCM oder XChaCha20-Poly1305
- jede Nachricht bekommt Nonce und Authentifizierungstag
- Nonces dürfen pro Session-Schlüssel nie wiederverwendet werden
- Protokollversion und Nachrichtentyp werden als Additional Authenticated Data
  einbezogen

Abstraktes Nachrichtenformat:

```json
{
  "enc": "v1",
  "kid": "session-key-id",
  "nonce": "...",
  "ciphertext": "...",
  "tag": "..."
}
```

Für WebSocket bedeutet das:

- `hello` und Schlüsselaushandlung laufen vor verschlüsselten Fachnachrichten
- danach sind Subscribe-, Command- und Event-Payloads verschlüsselt
- technische Hüllfelder dürfen sichtbar bleiben, soweit sie für Routing nötig
  sind, z.B. `enc`, `kid`, `nonce`

Für REST bedeutet das:

- Authentifizierung und Sessionaufbau laufen über HTTPS
- fachliche Request- und Response-Bodies werden danach verschlüsselt
- Datei-Uploads werden ebenfalls clientseitig oder vor Speicherung verschlüsselt

### Datenbankverschlüsselung

PostgreSQL speichert keine fachlichen Nutzdaten im Klartext.

Geplantes Modell:

- feld- oder dokumentbasierte Verschlüsselung auf Anwendungsebene
- PostgreSQL sieht für fachliche Felder nur Ciphertext
- je Firma eigener Data Encryption Key
- je Datensatz oder je Feld eigene Nonce
- AEAD mit Authentifizierungstag
- Schlüsselmaterial liegt nicht im Firmenschema
- Master-Key kommt aus Server-Konfiguration, Secret Store oder später HSM/KMS

Schlüsselhierarchie:

1. Master Key, außerhalb der Datenbank
2. Firmenschlüssel pro Firma
3. optional Bereichs- oder Tabellenschlüssel
4. Datensatzverschlüsselung mit eigener Nonce

Konsequenzen:

- Volltextsuche auf verschlüsselten Feldern ist nicht direkt möglich
- Sortierung und Filterung auf verschlüsselten Klartextwerten ist eingeschränkt
- für benötigte Suche braucht es bewusst geplante Such-/Indexwerte
- solche Suchwerte dürfen keine sensiblen Klartexte enthalten
- Logs, Audit-Log und Change-Log dürfen keine entschlüsselten Nutzdaten enthalten

Pragmatische Einordnung:

- Passwörter werden nicht verschlüsselt, sondern gehasht.
- technische IDs, Statuswerte, Sequenzen und Foreign Keys können als notwendige
  Metadaten unverschlüsselt bleiben, wenn sie für Betrieb, Routing,
  Rechteprüfung und referenzielle Integrität nötig sind.
- besonders sensible Inhalte wie Adressen, Kommunikationsdaten, Rechnungsinhalte,
  Dokumentmetadaten, API-Secrets und Preise werden verschlüsselt gespeichert.

### Öffentlicher Server

Der öffentliche Server läuft hinter einem Reverse Proxy.

Empfohlene Aufgaben des Reverse Proxy:

- TLS-Terminierung
- Zertifikatsverwaltung
- HTTP-zu-HTTPS-Redirect
- Weiterleitung von WebSocket-Upgrades
- Rate-Limits für Login und Registrierung
- Request-Größenlimits für Uploads
- Security Header

Geeignete Optionen:

- Caddy
- Traefik
- Nginx

Empfehlung für den Start:

- Caddy oder Traefik, weil automatische Zertifikatsverwaltung einfacher ist.

Backend-intern:

- Backend lauscht nur im internen Netz oder auf localhost.
- Extern erreichbar ist nur der Reverse Proxy.
- Der Reverse Proxy leitet an das Backend weiter.

### Zertifikate

Öffentlicher SaaS-Betrieb:

- Zertifikate automatisch per ACME/Let's Encrypt
- automatische Erneuerung
- TLS mindestens Version 1.2, bevorzugt TLS 1.3
- HSTS nach stabiler Domain- und Zertifikatskonfiguration aktivieren

Lokaler Betrieb:

- ebenfalls HTTPS/WSS
- möglich über selbstsigniertes Zertifikat oder lokale Zertifizierungsstelle
- Installationsprogramm kann ein lokales Zertifikat erzeugen
- Desktopclient muss lokale Zertifikate sauber vertrauen können

Für lokale Installationen muss das Installationsprogramm die Zertifikatsfrage
lösen, z.B. durch lokale Zertifizierungsstelle, Zertifikatimport oder klaren
Administrationsdialog.

### REST-Kommunikation

REST wird für zustandsändernde Befehle, Dateiübertragungen und externe
Integrationen verwendet.

Eigenschaften:

- JSON als Standardformat
- HTTPS verpflichtend im SaaS-Betrieb
- Bearer Token im `Authorization`-Header
- API-Version im Pfad, z.B. `/api/v1/...`
- fachliche Request- und Response-Bodies werden nach Sessionaufbau zusätzlich
  verschlüsselt
- idempotente Endpunkte dort, wo Wiederholungen realistisch sind
- Request-ID pro Anfrage für Logging und Fehlersuche

Beispiel-Header:

```text
Authorization: Bearer <access_token>
X-Request-Id: <uuid>
```

### WebSocket-Kommunikation

WebSocket wird für Live-Updates, Subscriptions und schnelle UI-Aktualisierung
verwendet.

Öffentlicher Betrieb:

- ausschließlich `wss://`
- WebSocket-Verbindung erst nach Login und Firmenauswahl
- Authentifizierung über kurzlebigen Socket-Token oder Access Token
- aktive Firma ist im Token oder in der Socket-Startnachricht festgelegt
- jede Subscription wird gegen Rechte geprüft

Empfohlener Verbindungsaufbau:

1. Client meldet sich per REST an.
2. Client wählt aktive Firma.
3. Backend gibt Access Token und optional separaten kurzlebigen WebSocket-Token.
4. Client öffnet `wss://server/ws`.
5. Client sendet eine `hello`-Nachricht mit Token und gewünschter Protokollversion.
6. Backend prüft Token, Firma, Userstatus und Rechte.
7. Backend bestätigt mit `hello_ack`.
8. Client abonniert Topics.

Beispielnachrichten:

```json
{
  "type": "hello",
  "protocol_version": 1,
  "token": "<socket_token>"
}
```

```json
{
  "type": "subscribe",
  "topic": "customers",
  "since_sequence": 12345
}
```

```json
{
  "type": "event",
  "topic": "customers",
  "sequence": 12346,
  "operation": "updated",
  "entity_id": "..."
}
```

### Token und Sessions

Geplantes Modell:

- Access Token kurzlebig
- Refresh Token länger gültig, aber serverseitig widerrufbar
- Refresh Token wird nur gehasht gespeichert
- WebSocket-Token optional separat und sehr kurzlebig
- Passwortwechsel, User-Deaktivierung oder Firmen-Sperre widerrufen Sessions

Für den Webclient:

- Access Token möglichst nur im Speicher halten
- Refresh Token bevorzugt als `HttpOnly`, `Secure`, `SameSite` Cookie
- bei reinem API-Betrieb alternativ sichere Token-Speicherung bewusst festlegen

Für den Desktopclient:

- Refresh Token im Betriebssystem-Keychain speichern
- Linux: Secret Service/KWallet, falls verfügbar
- Windows: Credential Manager oder DPAPI
- macOS: Keychain
- keine Tokens im Klartext in Konfigurationsdateien

### Nachrichtenintegrität und Replay-Schutz

TLS schützt Transport und Integrität auf Verbindungsebene. Zusätzlich sollen
kritische Operationen nachvollziehbar und wiederholungssicher sein.

Maßnahmen:

- Request-ID pro REST-Anfrage
- serverseitige Idempotency Keys für kritische POST-Aktionen, z.B.
  Rechnungserstellung
- monoton steigende `sequence` im `change_log`
- WebSocket-Events enthalten `sequence`
- Clients können nach Reconnect ab einer bekannten `sequence` nachladen

### E-Mail-Kommunikation

System-E-Mails werden über eine Outbox versendet.

Anwendungsfälle:

- Initialpasswort nach Admin-Freischaltung
- Einladung neuer Firmen-User
- später Passwort-Reset
- später 2FA-/Sicherheitsbenachrichtigungen

Sicherheitsanforderungen:

- SMTP-Zugangsdaten nicht im Firmenschema im Klartext speichern
- zentrale SMTP-Konfiguration für SaaS-Betrieb
- lokale SMTP-Konfiguration für lokale Installation möglich
- Versandversuche und Fehler in `public.email_outbox` protokollieren

### API-Zugangsdaten von Lieferanten

Lieferanten-API-Zugangsdaten liegen im Firmenschema, müssen aber verschlüsselt
gespeichert werden.

Geplantes Modell:

- Verschlüsselung vor Speicherung in PostgreSQL
- Master-Key aus Server-Konfiguration oder Secret Store
- je Firma optional abgeleiteter Schlüssel
- Rotation des Master-Keys später einplanen
- Secrets nie im Audit-Log oder Change-Log im Klartext speichern

### Webhooks

Ausgehende Webhooks müssen signiert werden.

Anforderungen:

- HTTPS-Ziel-URLs bevorzugen
- HMAC-Signatur pro Webhook
- Timestamp im Signaturmaterial
- Retry mit Backoff
- Dead-Letter-Status nach wiederholtem Fehler
- kein Mitsenden sensibler Secrets

### Lokales Netzwerk und Server-Erkennung

Für den nativen Desktopclient müssen lokale Server auffindbar oder konfigurierbar
sein.

Optionen:

- manuelle Server-URL
- gespeicherte Serverprofile
- spätere automatische LAN-Erkennung

Empfehlung für den Start:

- manuelle Server-URL
- klare Anzeige, ob Verbindung verschlüsselt ist
- Warnung bei Zertifikats- oder Vertrauensproblemen

### Fehlerfälle und Reconnect

Clients müssen robuste Verbindungslogik haben.

Vorgaben:

- automatischer Reconnect mit Backoff
- Token-Refresh vor erneutem WebSocket-Aufbau
- nach Reconnect Sync über `since_sequence`
- sichtbarer Offline-/Verbindungsstatus in der UI
- keine stillschweigenden Datenverluste bei abgebrochenen Schreiboperationen

## Rollen und Rechte

Es gibt feste Standardrollen. Die Rechte selbst sind granular aufgebaut.

Rechtemodell:

- jedes fachliche Atom bekommt eigene Rechte
- mindestens `read`, `write`, `delete`
- Rechte werden im Firmenschema gespeichert
- feste Rollen bündeln Rechte
- die erste Person einer Firma ist der Besitzer (`owner`) und erhält beim
  Anlegen der Firma alle initial vorgesehenen Rollen/Rechte
- nur der Besitzer darf Benutzer einladen und Rollen/Rechte für andere Benutzer
  vergeben oder ändern
- alle weiteren Firmenbenutzer erhalten Rechte nicht automatisch, sondern erst
  durch manuelle Freischaltung im Admin-Fenster `Benutzerrechte`
- jede größere Funktion bekommt eigene atomare Rechte, z.B. Kunden,
  Lieferanten, Artikel, Angebote, Eingangsrechnungen, Ausgangsrechnungen,
  Aktivitäten, Kommunikation, Preislistenimporte, Preis-API-Abgleiche,
  Einstellungen, Benutzerverwaltung, Nummernkreise, Dokumente, Berichte und
  Audit-Log

Beispielrollen:

- `owner`
- `admin`
- `sales`
- `accounting`
- `purchasing`
- `warehouse`
- `viewer`

Beispielrechte:

- `customers.read`
- `customers.write`
- `customers.delete`
- `activities.read`
- `activities.write`
- `activities.delete`
- `quotes.read`
- `quotes.write`
- `quotes.delete`
- `outgoing_invoices.read`
- `outgoing_invoices.write`
- `outgoing_invoices.delete`
- `items.read`
- `items.write`
- `items.delete`
- `price_rules.write`
- `imports.write`
- `settings.write`

## Fachmodule

### Dashboard

Die Startmaske ist ein Dashboard mit Übersicht über laufende und offene Vorgänge.

Inhalte:

- offene Angebote
- fällige Aufgaben
- offene Eingangsrechnungen
- offene Ausgangsrechnungen
- aktuelle Importläufe
- Preisänderungen
- Warnungen aus Lagerbestand oder Preisberechnung
- offene/fällige Vorgänge
- Wiedervorlagen

### Kunden

Verwaltung von Firmen- und Privatkunden der jeweiligen Firma.

Geplante Daten:

- Kundennummer
- Name/Firma
- Kundentyp
- Rechnungsadresse
- Lieferadresse
- Ansprechpartner
- Kommunikationsdaten
- Zahlungsbedingungen
- Standardrabatt
- Skonto-Regel, falls abweichend von den Firmeneinstellungen
- USt-IdNr.
- Notizen
- Status

### Lieferanten

Verwaltung von Bezugsquellen und Einkaufskonditionen.

Geplante Daten:

- Lieferantennummer
- Name/Firma
- Adressen
- Ansprechpartner
- Zahlungsbedingungen
- Skonto-Regel, falls vom Lieferanten vorgegeben
- Artikelreferenzen
- Preislistenquellen
- API-Zugangsdaten

### Artikel und Lager

Zentrale Artikelverwaltung mit Preisberechnung und Lagerbestand.

Geplante Daten:

- Artikelnummer
- Bezeichnung
- Beschreibung
- Einheit
- Einkaufspreis
- Verkaufspreis
- Steuersatz
- Hersteller-Code / Herstellerartikelnummer
- mehrere Lieferantenreferenzen je Artikel
- externe Artikelnummer je Lieferant
- Einkaufspreis je Lieferant und externer Artikelnummer
- Währung je Lieferantenpreis
- Kennzeichnung bevorzugter Lieferant
- EAN/GTIN
- Preisgültigkeit
- Lagerbestand
- Mindestbestand
- Lagerbewegungen

Preisberechnung:

- Einkaufspreis fest festlegen
- Einkaufspreis aus Quelle übernehmen
- Einkaufspreis mal Multiplikator
- Staffelpreise
- günstigsten aktiven Lieferantenpreis ermitteln
- bevorzugten Lieferanten optional gegenüber dem günstigsten Preis priorisieren
- kundenspezifische Preise
- kundenbezogener Standardrabatt
- positionsbezogener Sonderpreis oder Sonderrabatt
- Skonto-Regeln für Zahlung innerhalb definierter Fristen
- Projektpreise
- Rundungsregeln
- Preisgültigkeit
- Preisänderungshistorie

Preis- und Zahlungsbedingungen:

- Kunden können einen festen Standardrabatt erhalten.
- Lieferanten können eigene Skonto- und Zahlungsbedingungen haben.
- Firmeneinstellungen definieren Standard-Skonto und Standard-Zahlungsziele.
- Kunden- oder Lieferanteneinstellungen überschreiben die Firmenstandards.
- Rechnungspositionen übernehmen zunächst den aktuellen Artikelpreis und die
  anwendbaren Rabattregeln.
- Der Einzelpreis einer Rechnungsposition darf manuell überschrieben werden,
  z.B. wegen Sonderabsprachen, Kulanz oder Spezialrabatt.
- Preisüberschreibungen müssen als solche gespeichert werden, inkl. optionalem
  Grund und User.

Geplante Tabellen:

- `customer_price_terms`: kundenspezifische Rabatte und Preisbedingungen
- `supplier_price_terms`: lieferantenspezifische Einkaufs-/Zahlungsbedingungen
- `cash_discount_terms`: Skonto-Regeln mit Prozent, Frist und Gültigkeit

`cash_discount_terms` ist bewusst eine eigene Tabelle, damit Firmeneinstellung,
Kunde, Lieferant und später ggf. einzelne Belege auf dieselben strukturierten
Skonto-Regeln verweisen können.

### Angebote

Angebote erstellen, versionieren, verwalten und in Rechnungen überführen.

Geplante Funktionen:

- frei konfigurierbarer Angebotsnummernkreis
- Kunde auswählen
- Positionen aus Artikeln oder Freitext
- Rabatte
- Steuern
- Zahlungs-/Lieferbedingungen
- Status: Entwurf, in Freigabe, freigegeben, gesendet, angenommen, abgelehnt, abgelaufen
- Angebotsversionen
- Angebotsvorlagen je Firma
- optionaler Freigabeprozess je nach Firmeneinstellung
- E-Mail-Versand aus dem System
- PDF-Erzeugung
- Umwandlung in Ausgangsrechnung

### Ausgangsrechnungen

Rechnungen an Kunden.

Geplante Funktionen:

- aus Angebot erzeugen
- direkt erstellen
- frei konfigurierbarer Rechnungsnummernkreis mit Pflicht-Platzhalter für Counter
- Positionen ausschließlich mit Artikelreferenz
- keine freien Text-/Freipositionen in Ausgangsrechnungen
- aktueller Artikelpreis wird beim Hinzufügen übernommen
- Einzelpreis und Rabatt pro Rechnungsposition dürfen überschrieben werden
- kundenbezogener Standardrabatt wird vorgeschlagen
- Skonto aus Firmeneinstellung oder Kundenregel wird vorgeschlagen
- Steuern passend für Deutschland
- Preisüberschreibungen mit Grund und User nachvollziehbar speichern
- Zahlungsstatus
- Mahnstatus
- PDF-Erzeugung
- revisionssichere Ablage nach Erstellung
- spätere Erweiterung für DATEV oder E-Rechnung möglich, aber nicht in der ersten Stufe

### Eingangsrechnungen

Rechnungen von Lieferanten.

Geplante Funktionen:

- Lieferant zuordnen
- Rechnungsdaten erfassen
- Positionen erfassen
- Dokument/PDF ablegen
- Zahlungsstatus
- Zahlungsziel und Skonto aus Lieferantenregel vorschlagen
- Kostenstellen oder Projekte, falls später benötigt
- Abgleich mit Artikeln und Lieferantenpreisen
- revisionssichere Ablage

### Kommunikation, Vorgänge, Aufgaben und Wiedervorlagen

Keine direkte E-Mail- oder Telefonie-Synchronisation in der ersten Stufe.

Begriffliche Entscheidung:

- UI-Hauptbegriff: `Vorgänge`
- technischer Name: `activities`
- `Aktivität` bleibt als Typ oder Kategorie möglich, ist aber als Hauptbegriff
  zu unscharf

Ein Vorgang beschreibt etwas, das fachlich passiert ist, gerade passiert oder
geplant ist. Dadurch lassen sich Kommunikationsnotizen, Aufgaben, Termine,
Wiedervorlagen und Bearbeitungsschritte in einer gemeinsamen Timeline anzeigen.

Geplante Vorgangstypen:

- E-Mail-Notiz
- Telefonnotiz
- interne Notiz
- Aufgabe
- Wiedervorlage
- Kalendertermin
- Systemereignis, z.B. Angebot angenommen oder Rechnung erstellt
- manueller Bearbeitungsschritt, z.B. Rückfrage geklärt

Geplante Daten:

- Titel
- Beschreibung/Notiz
- Vorgangstyp
- Status: offen, in Bearbeitung, erledigt, storniert
- Priorität: niedrig, normal, hoch, kritisch
- Fälligkeitsdatum
- Start-/Endzeit für Termine
- zuständiger User
- erstellt von
- erledigt von
- Tags/Kategorien
- Sichtbarkeit, z.B. intern oder für alle berechtigten User
- Bezug auf Kunde, Lieferant, Kontakt, Angebot, Rechnung, Artikel, Dokument oder Importlauf
- versionierte Anhänge
- technische Systemquelle, falls automatisch erzeugt

Verknüpfungen:

- Ein Vorgang kann mehrere Bezüge haben, z.B. Kunde + Angebot + Dokument.
- Bezüge werden nicht als viele optionale Spalten modelliert, sondern über eine
  Link-Tabelle.
- Beispiel: `activity_links(activity_id, entity_type, entity_id)`.

Live-Verhalten:

- neue oder geänderte Vorgänge erzeugen WebSocket-Events
- Dashboard zeigt offene/fällige Vorgänge
- Detailmasken zeigen eine Timeline der zugehörigen Vorgänge
- erledigte Vorgänge bleiben nachvollziehbar erhalten

Abgrenzung zu `communications`:

- `communications` speichert strukturierte Kommunikation, sobald echte
  E-Mail-/Telefonie-Integration oder Nachrichtenarchivierung implementiert wird.
- `activities` bildet die nutzbare Timeline und Aufgaben-/Wiedervorlagefunktion
  schon in der ersten Stufe ab.
- In der ersten Stufe können E-Mail- und Telefonnotizen direkt als Vorgänge
  geführt werden.

### Dokumente und Vorlagen

Dokumente werden auf dem Filesystem gespeichert. Metadaten und Versionen liegen in
PostgreSQL.

Geplante Funktionen:

- PDF-Ablage
- Uploads
- versionierte Anhänge
- editierbare Dokumentvorlagen
- Vorlagen je Firma
- Verknüpfung mit Kunden, Lieferanten, Angeboten und Rechnungen

Keine Volltextsuche in Dokumenten in der ersten Stufe.

## Artikelpreislisten und APIs

### Dateiimport

Unterstützte erste Formate:

- CSV
- XML
- JSON

XLSX bleibt eine mögliche spätere Erweiterung.

Import-Pipeline:

1. Datei hochladen
2. Format erkennen oder manuell auswählen
3. Spalten/Felder zu Zielfeldern mappen
4. Vorschau und Validierung
5. Import ausführen
6. Importbericht speichern
7. Einkaufspreise aktualisieren
8. Verkaufspreise neu berechnen
9. betroffene Clients per WebSocket informieren

Preislistenimporte berücksichtigen optional:

- Hersteller-Code (`manufacturer_code`)
- Lieferantennummer (`supplier_number`)
- externe Lieferanten-Artikelnummer (`supplier_item_number`)
- Lieferanten-Einkaufspreis (`purchase_price`)
- Währung (`currency`)

Wenn Lieferantennummer und externe Artikelnummer im Import vorhanden sind, wird
neben dem internen Artikel eine Lieferantenpreis-Verknüpfung aktualisiert. Die
interne Artikelnummer bleibt unabhängig davon der primäre Objekt-Identifier.

### Frei konfigurierbare Preislisten

Da Lieferantenpreislisten frei konfigurierbar sein sollen, braucht das System
Mapping-Vorlagen:

- Lieferant
- Format
- Feldzuordnung
- Identifikationsfeld, z.B. EAN, Artikelnummer oder Herstellerartikelnummer
- Währungsfeld
- Preisfeld
- Mengen-/Staffelfeld
- Gültigkeitsdatum
- Konfliktregel

### Frei konfigurierbare API-Connectoren

Geplante Connector-Konfiguration:

- Lieferant
- Basis-URL
- Authentifizierung
- Header
- Query-Parameter
- Abrufintervall
- Request-Template
- Response-Mapping
- Fehlerprotokoll
- Testabruf

Komplexere APIs werden wahrscheinlich dennoch einzelne Spezialadapter brauchen.
Die freie Konfiguration sollte daher einfache REST/JSON- und REST/XML-Fälle
abdecken, aber nicht als vollständiger Ersatz für jede Lieferanten-API geplant
werden.

## Rechnungen, Steuern und Archivierung

Zielmarkt ist Deutschland.

Erste Stufe:

- deutsche Steuersätze
- Steuerwerte so erfassen, dass eine spätere Übertragung oder Auswertung für
  Elster möglich ist
- frei konfigurierbare Nummernkreise
- Nummernkreis muss mindestens einen Counter-Platzhalter enthalten
- Rechnungen nach Erstellung unveränderlich behandeln
- Korrekturen über Storno/Korrekturrechnung statt Bearbeitung finaler Rechnung

Spätere Ausbaustufen:

- DATEV-Export
- XRechnung
- ZUGFeRD
- direkte Buchhaltungsintegration

## Backend-Architektur

Vorgeschlagene Hauptbereiche:

- Authentifizierung
- öffentliche Registrierung
- Admin-Freischaltung von Firmen
- SSO
- 2FA
- Firmenauswahl
- User-Einladungen
- E-Mail-Outbox
- Key-Management
- Session-Schlüsselverwaltung
- Payload-Verschlüsselung für REST und WebSocket
- Datenbankverschlüsselung auf Anwendungsebene
- Firmenschema-Auflösung
- rollenbasierte Autorisierung
- Migrationen für `public`
- Migrationen je Firmenschema
- REST-API
- WebSocket-Verbindungsverwaltung
- Event-Bus für Live-Updates
- Fachmodule
- Dateiimport
- API-Connectoren
- Dokumenterzeugung
- Dokumentablage im Filesystem
- Audit-Logging je Firmenschema
- Webhooks

## Socket-Kommunikation

Clients verbinden sich nach Login und Firmenauswahl mit dem Backend.

Das Socket-Protokoll ist versioniert. Jede Verbindung beginnt mit einer
`hello`-Nachricht. Ohne gültige Authentifizierung und bestätigte Protokollversion
werden keine Subscriptions angenommen.

Beispiele für Topics:

- `dashboard`
- `customers`
- `suppliers`
- `items`
- `stock`
- `quotes`
- `incoming_invoices`
- `outgoing_invoices`
- `communications`
- `tasks`
- `imports`

Beispiele für Server-Events:

- `snapshot`
- `created`
- `updated`
- `deleted`
- `price_recalculated`
- `stock_changed`
- `import_finished`
- `invoice_created_from_quote`
- `task_due`

Wichtig:

- jede WebSocket-Verbindung muss authentifiziert sein
- jede Verbindung hat genau eine aktive Firma
- jede Verbindung hat eine bestätigte Protokollversion
- jede Verbindung hat nach dem Handshake einen Session-Schlüssel für
  verschlüsselte Payloads
- jede Nachricht wird gegen Rechte im Firmenschema geprüft
- Events gehen nur an berechtigte User derselben Firma
- Clients müssen verlorene Verbindungen erkennen und neu synchronisieren können
- für Offline-Desktopclients braucht es einen Änderungslog oder Sync-Endpunkt
- Events enthalten eine `sequence` für Reconnect und Offline-Sync
- schreibende Aktionen laufen bevorzugt über REST, Events informieren danach alle
  betroffenen Clients

## REST-API und Webhooks

Zusätzlich zum WebSocket-Protokoll wird eine REST-API geplant.

Einsatz:

- öffentliche Registrierung im SaaS-Betrieb
- Admin-Freischaltung neuer Firmen
- Login
- Passwortwechsel beim ersten Login
- Firmenauswahl
- User-Einladungen
- CRUD-Operationen
- Dateiimport
- Dokumentdownload
- externe Integrationen
- Export vorhandener Daten

Webhooks:

- je Firma konfigurierbar
- Signatur je Webhook
- Retry-Strategie
- Ereignisse wie Angebot angenommen, Rechnung erstellt, Import abgeschlossen

Vorgeschlagene erste REST-Endpunkte:

- `POST /api/registration/organization`
- `GET /api/admin/organization-registrations`
- `GET /api/admin/organization-registrations/{id}`
- `POST /api/admin/organization-registrations/{id}/approve`
- `POST /api/admin/organization-registrations/{id}/reject`
- `POST /api/admin/organization-registrations/{id}/resend-initial-email`
- `POST /api/admin/organization-registrations/{id}/retry-provisioning`
- `POST /api/auth/login`
- `POST /api/auth/change-initial-password`
- `GET /api/auth/organizations`
- `POST /api/auth/select-organization`
- `GET /api/organizations/current/setup`
- `PUT /api/organizations/current/setup`
- `POST /api/organizations/current/invitations`
- `POST /api/organizations/current/invitations/{id}/resend`
- `GET /api/organizations/current/users`
- `PATCH /api/organizations/current/users/{user_id}/roles`
- `POST /api/organizations/current/users/{user_id}/disable`

Alle produktiven REST-Endpunkte werden versioniert, z.B. unter `/api/v1/...`.
Kritische POST-Endpunkte wie Rechnungserstellung oder Angebotsumwandlung sollen
Idempotency Keys unterstützen.

## Desktop-Offline-Konzept

Offline-Fähigkeit ist sinnvoll und wird eingeplant.

Vorgeschlagener Ansatz:

- lokaler Cache im Desktopclient
- lokale SQLite-Datenbank oder eingebetteter Speicher
- schreibende Offline-Aktionen zuerst nur für ausgewählte Bereiche erlauben
- Sync beim Wiederverbinden
- Konfliktstrategie pro Modul

Empfohlene erste Offline-Stufe:

- Lesen zuletzt synchronisierter Daten
- Vorgänge und Timelines anzeigen
- neue Notizen/Vorgangsentwürfe lokal speichern
- Aufgaben/Wiedervorlagen anzeigen
- Entwürfe lokal speichern
- keine finale Rechnungserstellung offline

Finale Rechnungen sollten wegen Nummernkreis, Revisionssicherheit und
Mehrbenutzerbetrieb nur online erstellt werden.

## Frontend-Planung

Webclient und Desktopclient sollen denselben fachlichen Umfang bekommen.

### Fensterbasiertes Arbeitsmodell

Die Anwendung wird fensterbasiert geplant. User sollen mehrere Arbeitskontexte
parallel offen halten können, z.B. Rechnung, Artikelliste, Kundendetail und
Vorgangstimeline.

Grundsätze:

- Listen, Detailfenster, Suchdialoge und Auswahlfelder sind eigene Fenster oder
  fensterähnliche Arbeitsbereiche.
- Mehrere Fenster können gleichzeitig geöffnet sein.
- Änderungen in einem Fenster müssen andere offene Fenster sofort erreichen.
- Auswahlfelder und Suchdialoge dürfen keine veralteten Daten anzeigen.
- Ein Fenster darf lokale Eingaben nicht verlieren, wenn im Hintergrund Daten
  aktualisiert werden.
- Bei Konflikten muss der User nachvollziehbar entscheiden können, ob lokale
  Änderungen beibehalten, neu geladen oder zusammengeführt werden.

Beispiel:

1. User erstellt eine Ausgangsrechnung.
2. Beim Hinzufügen einer Position fehlt ein Artikel oder der Preis ist falsch.
3. User öffnet parallel Artikelfenster oder Vorgangsfenster.
4. User legt den Artikel/Vorgang an oder korrigiert den Preis.
5. Das Rechnungsfenster erhält sofort ein Live-Event.
6. Artikel und Vorgang sind ohne Neuladen in der Rechnung auswählbar.

Technische Anforderungen:

- Jede Datenänderung erzeugt ein fachliches Live-Event mit Entity-Typ, ID,
  Änderungsart und Versions-/Sequenznummer.
- Frontends führen zentrale Stores/Caches je Firma, z.B. für Artikel, Kunden,
  Vorgänge und Preisregeln.
- Fenster abonnieren die Store-Änderungen statt eigene isolierte Listen zu
  halten.
- Auswahlkomponenten aktualisieren ihre Ergebnislisten bei Store-Updates.
- Auswahlkomponenten für große Stammdatenmengen, z.B. Kunden, Lieferanten und
  Artikel, zeigen nicht pauschal alle Datensätze an. Sie bieten eine Suche nach
  Nummer und Name und begrenzen die sichtbaren Treffer.
- Bei großen Listen werden nur betroffene Datensätze aktualisiert, nicht die
  komplette Maske neu gerendert.
- Detailfenster vergleichen `updated_at` oder eine Versionsnummer, bevor lokale
  Änderungen gespeichert werden.
- WebSocket-Reconnect nutzt `since_sequence`, damit verpasste Änderungen
  nachgeladen werden.

Erste betroffene Bereiche:

- Artikel- und Preisauswahl in Rechnungen und Angeboten
- Vorgänge/Timelines in Kunden-, Lieferanten-, Artikel- und Belegfenstern
- Kunden- und Lieferantenauswahl in Belegen
- Benutzer- und Rechteänderungen in offenen Einstellungsfenstern
- Importfortschritt und Preisneuberechnung

Gemeinsame Kernmasken:

- öffentliche Registrierungsmaske, nur SaaS
- Admin-Maske für Firmenfreischaltungen, nur Betreiber/Admin
- Login
- erzwungener Passwortwechsel
- Firmenauswahl
- Dashboard
- Kunden
- Lieferanten
- Artikel
- Lager
- Angebote
- Ausgangsrechnungen
- Eingangsrechnungen
- Kommunikation
- Vorgänge
- Aufgaben/Wiedervorlagen
- Dokumente
- Einstellungen
- Benutzerverwaltung und Einladungen
- Importassistent

Benutzereinstellungen:

- Benutzereinstellungen werden je Firma und je Benutzer gespeichert.
- Die Werte werden wie andere fachliche Einstellungen verschlüsselt abgelegt.
- Erste Einstellung: Navigationsdarstellung mit den Modi `scroll` und
  `groups`.
- Standard ist `scroll`, damit alle Menüpunkte auch bei kleinen Fenstern
  erreichbar bleiben.

## Erste UI-Seiten: Organization und User

Die ersten UI-Seiten bilden den Onboarding-Flow ab. Fachlich geht es um eine
Firma, technisch um `organization`.

### Seite: Öffentliche Registrierung

Ziel:

- Eine neue Organization für den öffentlichen SaaS-Betrieb registrieren.
- Einen ersten User mit E-Mail-Adresse vormerken.
- Noch kein produktiver Zugriff vor Admin-Freischaltung.

Route:

- Web: `/register`
- Desktop: nicht erforderlich für öffentliche SaaS-Registrierung in der ersten
  Stufe

Felder:

- `organization_name`, UI-Label `Firmenname`
- `email`, UI-Label `E-Mail-Adresse`
- `accept_terms`, UI-Label `Nutzungsbedingungen akzeptieren`, falls später nötig

Validierung:

- Firmenname ist Pflichtfeld
- Firmenname mindestens 2 Zeichen
- E-Mail ist Pflichtfeld
- E-Mail muss formal gültig sein
- Absenden mehrfach verhindern, solange Request läuft

Primäre Aktion:

- `Registrierung absenden`

API:

- `POST /api/v1/registration/organization`

Request:

```json
{
  "organization_name": "Muster GmbH",
  "email": "admin@example.com"
}
```

Erfolgszustand:

- UI zeigt, dass die Registrierung eingegangen ist.
- Hinweis: Freischaltung erfolgt durch Administrator.
- Kein Login-Link mit Zugriff suggerieren.

Fehlerzustände:

- E-Mail bereits registriert und bereits aktiver User
- Organization mit ähnlichem Namen existiert bereits
- Registrierung bereits offen
- Server nicht erreichbar
- Validierungsfehler

Sicherheits-/Betriebshinweise:

- Rate-Limit serverseitig
- keine Auskunft, ob eine E-Mail bereits bei einer anderen Firma aktiv ist
- Fehlermeldungen allgemein genug halten, um User Enumeration zu vermeiden

### Seite: Admin-Liste Registrierungen

Ziel:

- Betreiber-Admin sieht neue Organization-Registrierungen.
- Admin kann prüfen, freischalten oder ablehnen.

Route:

- Web: `/admin/organization-registrations`
- Desktop: später optional, erste Stufe Web

Tabelle:

- Firmenname
- E-Mail-Adresse
- Status
- Eingegangen am
- Entschieden am
- Entschieden von

Filter:

- offen
- freigeschaltet
- abgelehnt
- gesperrt

Aktionen:

- Details öffnen
- Freischalten
- Ablehnen
- E-Mail erneut senden, falls bereits freigeschaltet
- technischen Anlageversuch wiederholen

API:

- `GET /api/v1/admin/organization-registrations`

### Seite: Admin-Detail Registrierung

Ziel:

- Eine einzelne Registrierung prüfen und entscheiden.

Route:

- Web: `/admin/organization-registrations/:id`

Angezeigte Daten:

- Firmenname
- E-Mail-Adresse
- Status
- Zeitstempel
- technische Organization-ID, falls schon erzeugt
- Schema-Name, falls schon erzeugt
- Fehler der Schema-Erstellung, falls vorhanden

Aktionen:

- `Freischalten`
- `Ablehnen`
- `Zurück zur Liste`
- `Einladungs-E-Mail erneut senden`
- `Schema-Erstellung wiederholen`

Freischalten:

- erzeugt oder aktiviert `public.organizations`
- erzeugt `company_<organization_id>`
- erzeugt Basistabellen, Standardrollen und Standardrechte
- ordnet ersten User als `owner` zu
- erzeugt Initialpasswort
- setzt `must_change_password`
- legt E-Mail in `public.email_outbox`

Ablehnen:

- setzt Status `rejected`
- speichert optional internen Entscheidungsvermerk
- erzeugt kein Firmenschema

API:

- `GET /api/v1/admin/organization-registrations/{id}`
- `POST /api/v1/admin/organization-registrations/{id}/approve`
- `POST /api/v1/admin/organization-registrations/{id}/reject`
- `POST /api/v1/admin/organization-registrations/{id}/resend-initial-email`
- `POST /api/v1/admin/organization-registrations/{id}/retry-provisioning`

### Seite: Erster Login mit Initialpasswort

Ziel:

- Der erste User meldet sich mit E-Mail und Initialpasswort an.
- Danach muss sofort das Passwort geändert werden.

Route:

- Web: `/login`
- Desktop: Login-Dialog im nativen Client

Felder:

- `email`, UI-Label `E-Mail-Adresse`
- `password`, UI-Label `Passwort`

API:

- `POST /api/v1/auth/login`

Besondere Zustände:

- `must_change_password = true`: UI leitet direkt zur Passwortänderung weiter
- Organization noch nicht aktiv: UI zeigt neutralen Hinweis
- Initialpasswort abgelaufen: UI zeigt Hinweis auf erneute Einladung oder Support

### Seite: Passwort beim ersten Login ändern

Ziel:

- User ersetzt Initialpasswort durch eigenes Passwort.

Route:

- Web: `/change-initial-password`
- Desktop: eigener Dialog direkt nach Login

Felder:

- `current_password`, UI-Label `Aktuelles Passwort`
- `new_password`, UI-Label `Neues Passwort`
- `new_password_confirm`, UI-Label `Neues Passwort wiederholen`

Validierung:

- neues Passwort erfüllt Passwortrichtlinie
- Wiederholung stimmt überein
- neues Passwort darf nicht dem Initialpasswort entsprechen

API:

- `POST /api/v1/auth/change-initial-password`

Erfolgszustand:

- `must_change_password` wird entfernt
- Sessions mit altem Passwortstatus werden ungültig
- User wird zur Organization-Auswahl oder direkt ins Dashboard geleitet

### Seite: Organization-Grunddaten nach erstem Login

Ziel:

- Der erste User ergänzt nach Freischaltung die Stammdaten der Firma.

Route:

- Web: `/setup/organization`
- Desktop: Setup-Dialog nach Login

Felder erste Stufe:

- Firmenname
- Rechtsform
- Straße und Hausnummer
- PLZ
- Ort
- Land
- USt-IdNr., optional
- E-Mail der Firma
- Telefonnummer, optional
- Standard-Steuersatz
- Standard-Zahlungsziel

API:

- `GET /api/v1/organizations/current/setup`
- `PUT /api/v1/organizations/current/setup`

Erfolgszustand:

- Organization-Setup wird als abgeschlossen markiert
- User gelangt zum Dashboard

### Seite: User für Organization anlegen/einladen

Ziel:

- Ein berechtigter Firmen-User lädt weitere User ein.

Route:

- Web: `/settings/users`
- Desktop: `Benutzerrechte`

Felder Einladung:

- `email`, UI-Label `E-Mail-Adresse`
- `roles`, UI-Label `Rollen`

Aktionen:

- `Einladung senden`
- `Einladung erneut senden`
- `User deaktivieren`
- `Rollen ändern`

API:

- `GET /api/v1/organizations/current/users`
- `POST /api/v1/organizations/current/invitations`
- `PATCH /api/v1/organizations/current/users/{user_id}/roles`
- `POST /api/v1/organizations/current/invitations/{id}/resend`
- `PATCH /api/v1/organizations/current/users/{user_id}/roles`
- `POST /api/v1/organizations/current/users/{user_id}/disable`

Besonderheiten:

- Existierender globaler User bekommt keine Passwortüberschreibung.
- Neuer User bekommt Initialpasswort und `must_change_password`.
- Rollen werden im Firmenschema gespeichert.

### UI-Zustände und Komponenten

Gemeinsame Zustände:

- `idle`
- `validating`
- `submitting`
- `success`
- `error`

Wiederverwendbare Komponenten:

- Formularfeld mit Validierungsfehler
- Passwortfeld
- Statushinweis
- Tabellenliste mit Filter
- Bestätigungsdialog
- Fehlerbanner

Texte:

- UI-Texte deutsch
- technische IDs nur in Admin-Detailseiten anzeigen
- keine internen Schema- oder Security-Details auf öffentlichen Seiten

### Erste Implementierungsreihenfolge UI

1. Öffentliche Registrierungsseite
2. Login-Seite
3. Passwortänderung beim ersten Login
4. Admin-Liste Registrierungen
5. Admin-Detail mit Freischalten/Ablehnen
6. Organization-Grunddaten-Setup
7. Benutzerverwaltung mit Einladungen

## Erster Datenmodell-Entwurf

Dieser Abschnitt ist noch kein finales SQL-Schema, aber die Grundlage für die
ersten Migrationen.

### `public.users`

Globale Benutzeraccounts.

Felder:

- `id`
- `email`
- `display_name`
- `password_hash`, nullable bei reinem SSO-Account
- `is_active`
- `must_change_password`
- `initial_password_expires_at`
- `created_at`
- `updated_at`
- `last_login_at`

### `public.organizations`

Globale Firmenliste zur Zuordnung von Usern zu Firmenschemas.

Felder:

- `id`
- `display_name`
- `schema_name`
- `status`
- `registration_email`
- `approved_by_user_id`
- `approved_at`
- `rejected_by_user_id`
- `rejected_at`
- `rejection_reason`
- `created_at`
- `updated_at`

Der `schema_name` muss eindeutig sein und darf nur vom Backend erzeugt werden.
Vor der Admin-Freischaltung kann `schema_name` leer bleiben, wenn das
Firmenschema erst nach Freigabe erzeugt wird.

### `public.user_organizations`

Zuordnung von Usern zu Firmen.

Felder:

- `user_id`
- `organization_id`
- `status`
- `invited_by_user_id`
- `invited_at`
- `accepted_at`
- `created_at`
- `updated_at`

Rollen werden hier bewusst nicht gespeichert. Die eigentlichen Rollen liegen im
Firmenschema.

Mögliche Statuswerte:

- `pending_invitation`
- `active`
- `disabled`

### `public.auth_identities`

Verknüpfung von Usern mit Login-Methoden.

Felder:

- `id`
- `user_id`
- `provider`
- `provider_subject`
- `email_at_provider`
- `created_at`
- `updated_at`

Beispiele für `provider`:

- `password`
- `oidc`
- `saml`
- `microsoft`
- `google`

### `public.refresh_tokens`

Session-/Token-Verwaltung.

Felder:

- `id`
- `user_id`
- `organization_id`, nullable solange keine Firma gewählt ist
- `token_hash`
- `expires_at`
- `revoked_at`
- `revoked_reason`
- `user_agent`
- `created_ip`
- `created_at`

### `public.socket_tokens`

Kurzlebige Tokens für WebSocket-Verbindungen. Diese Tabelle ist optional, wenn
Socket-Tokens als signierte, sehr kurzlebige Tokens ohne serverseitige Speicherung
umgesetzt werden.

Felder:

- `id`
- `user_id`
- `organization_id`
- `token_hash`
- `expires_at`
- `used_at`
- `revoked_at`
- `created_at`

### `public.session_keys`

Metadaten zu aktiven Session-Schlüsseln. Rohes Schlüsselmaterial darf nicht im
Klartext in PostgreSQL gespeichert werden.

Felder:

- `id`
- `user_id`
- `organization_id`
- `key_id`
- `wrapped_key`, falls serverseitige Wiederaufnahme nötig ist
- `algorithm`
- `created_at`
- `expires_at`
- `revoked_at`

Wenn Session-Schlüssel nur im Arbeitsspeicher gehalten werden, kann diese Tabelle
entfallen oder nur Widerrufs-/Audit-Metadaten enthalten.

### `public.idempotency_keys`

Schutz gegen doppelte Ausführung kritischer REST-Aktionen.

Felder:

- `id`
- `user_id`
- `organization_id`
- `key`
- `request_hash`
- `response_status`
- `response_body_json`
- `expires_at`
- `created_at`

### Verschlüsselte Fachfelder

Für fachliche Tabellen wird ein einheitliches Muster verwendet.

Mögliche Feldstruktur:

- `*_ciphertext`
- `*_nonce`
- `*_tag`
- `*_key_id`

Alternativ kann ein JSONB- oder BYTEA-Container genutzt werden:

- `encrypted_payload`
- `encryption_meta`

Entscheidungskriterium:

- einzelne verschlüsselte Felder sind besser, wenn Teile eines Datensatzes
  getrennt aktualisiert werden
- ein verschlüsselter Payload-Container ist einfacher, wenn Datensätze meistens
  komplett gelesen und geschrieben werden

### `public.organization_registration_requests`

Optional separate Tabelle für SaaS-Registrierungen. Wenn die Registrierung direkt
in `public.organizations` abgebildet wird, ist diese Tabelle nicht zwingend nötig.

Felder:

- `id`
- `organization_name`
- `email`
- `status`
- `organization_id`
- `requested_at`
- `decided_by_user_id`
- `decided_at`
- `decision_note`

Vorteil einer separaten Tabelle:

- abgelehnte Registrierungen bleiben nachvollziehbar
- `public.organizations` enthält nur Firmen, die technisch angelegt werden sollen
- Admin-Freischaltung ist sauberer auditierbar

### `public.user_invitations`

Einladungen von Firmen-Usern an neue oder bestehende User.

Felder:

- `id`
- `organization_id`
- `email`
- `invited_by_user_id`
- `status`
- `expires_at`
- `accepted_at`
- `created_user_id`
- `created_at`

Mögliche Statuswerte:

- `pending`
- `accepted`
- `expired`
- `revoked`

### `public.email_outbox`

Queue für systemseitige E-Mails.

Felder:

- `id`
- `recipient_email`
- `template`
- `payload_json`
- `status`
- `attempt_count`
- `last_error`
- `send_after`
- `sent_at`
- `created_at`

Verwendung:

- Initialpasswort nach Admin-Freischaltung
- Einladung neuer Firmen-User
- erneuter Versand durch Administrator
- spätere Passwort-Reset-Mails

### `company_*.settings`

Firmeneinstellungen.

Felder:

- `key`
- `value_json`
- `updated_by_user_id`
- `updated_at`

Beispiele:

- Freigabeprozess für Angebote aktiv
- Standard-Steuersatz
- Standard-Zahlungsbedingungen
- Dokumentvorlage für Angebote
- Dokumentvorlage für Rechnungen

### `company_*.roles`

Feste Standardrollen, aber je Firma gespeichert, damit Rechte später erweitert
oder angepasst werden können.

Felder:

- `id`
- `code`
- `name`
- `description`
- `is_system_role`
- `created_at`
- `updated_at`

### `company_*.permissions`

Atomare Rechte.

Felder:

- `id`
- `code`
- `description`

Beispiele:

- `customers.read`
- `customers.write`
- `customers.delete`
- `quotes.approve`
- `invoices.finalize`
- `settings.write`

### `company_*.role_permissions`

Zuordnung von Rollen zu Rechten.

Felder:

- `role_id`
- `permission_id`

### `company_*.user_roles`

Zuordnung globaler User zu Rollen in dieser Firma.

Felder:

- `user_id`
- `role_id`
- `created_at`

Der `user_id` verweist logisch auf `public.users.id`. Ein echter Foreign Key über
Schemas hinweg ist möglich, muss aber bewusst entschieden werden.

### `company_*.number_ranges`

Konfigurierbare Nummernkreise.

Felder:

- `id`
- `code`
- `pattern`
- `counter_value`
- `counter_padding`
- `reset_rule`
- `is_active`
- `updated_at`

Regeln:

- `pattern` muss einen Counter-Platzhalter enthalten.
- Counter-Erhöhung muss transaktional und konkurrenzsicher erfolgen.
- Finale Rechnungsnummern dürfen nach Vergabe nicht wiederverwendet werden.
- Standardformat ist ein Präfix plus neunstelliger, gruppierter Zähler:
  `K000.000.001`, `L000.000.001`, `I000.000.001`,
  `A000.000.001`, `R000.000.001`.
- Standard-Nummernkreise:
  `customers = K{counter}`, `suppliers = L{counter}`,
  `items = I{counter}`, `activities = A{counter}`,
  `outgoing_invoices = R{counter}`.
- Ergänzende Nummernkreise nach gleichem Prinzip:
  `incoming_invoices = ER{counter}`, `quotes = AN{counter}`.

### `company_*.audit_log`

Revisions- und Nachvollziehbarkeitslog.

Felder:

- `id`
- `actor_user_id`
- `action`
- `entity_type`
- `entity_id`
- `before_json`
- `after_json`
- `created_at`

Für sehr große Installationen kann dieses Log später partitioniert werden.

### `company_*.change_log`

Grundlage für Live-Updates und Offline-Synchronisation.

Felder:

- `id`
- `sequence`
- `entity_type`
- `entity_id`
- `operation`
- `payload_json`
- `created_at`
- `created_by_user_id`

Nutzung:

- WebSocket-Events werden daraus erzeugt oder darauf gespiegelt.
- Desktopclients können seit ihrer letzten `sequence` Änderungen nachladen.
- Offline-Sync wird dadurch planbar.

### `company_*.documents` und `company_*.document_versions`

Metadaten für Dateien auf dem Filesystem.

`documents`:

- `id`
- `entity_type`
- `entity_id`
- `title`
- `current_version_id`
- `created_at`
- `created_by_user_id`

`document_versions`:

- `id`
- `document_id`
- `version_number`
- `storage_path`
- `mime_type`
- `sha256`
- `size_bytes`
- `created_at`
- `created_by_user_id`

## Implementierungsreihenfolge

Empfohlene Reihenfolge für die nächsten Entwicklungsschritte:

1. `public`-Migrationen für User, Firmen und Zuordnungen
2. SaaS-Registrierung mit Status `pending_approval`
3. Admin-Freischaltung und Backend-Service zur Firmenschema-Erstellung
4. Mailversand für Initialpasswort und Einladungen
5. Firmenschema-Basismigrationen für Rollen, Rechte, Settings, Nummernkreise,
   Audit-Log und Change-Log
6. Login ohne SSO, aber mit Architektur für spätere Provider
7. erzwungener Passwortwechsel beim ersten Login
8. Firmenauswahl nach Login
9. User-Einladung in bestehende Firma
10. mandantenbewusste REST- und WebSocket-Basisschicht
11. erstes Fachmodul `customers`
12. Live-Update für `customers`
13. Dashboard-Grundlage
14. Artikelmodul und CSV-Import

SSO, 2FA, Desktop-Offline und automatische Updates sind wichtig, sollten aber nach
der mandantenfähigen Grundarchitektur kommen. Andernfalls müssten sie später
gegen eine noch instabile Daten- und Sessionstruktur gebaut werden.

## TODOs

### Erledigte Planungsentscheidungen

- [x] SaaS-Betrieb mit vielen Firmen auf einer Instanz
- [x] öffentlicher Server wird selbst betrieben
- [x] jede Firma bekommt eigenes PostgreSQL-Schema
- [x] `public` enthält nur globale User- und Firmenzuordnung
- [x] Rollen, Rechte und Einstellungen liegen im Firmenschema
- [x] ein User kann mehreren Firmen angehören
- [x] Zielmarkt erste Stufe: Deutschland
- [x] REST-API zusätzlich zum WebSocket-Protokoll
- [x] Webhooks einplanen
- [x] Desktopclient mit Offline-Fähigkeit
- [x] automatische Desktopclient-Updates einplanen
- [x] Dokumente im Filesystem, Metadaten in PostgreSQL
- [x] öffentliche Registrierung für SaaS
- [x] Admin-Freischaltung neuer Firmen
- [x] Registrierung mit Firmenname und E-Mail-Adresse
- [x] E-Mail-Adresse ist Username
- [x] Initialpasswort per E-Mail
- [x] Passwortwechsel beim ersten Login verpflichtend
- [x] neue Firmen-User werden per Einladung hinzugefügt
- [x] lokale Version erlaubt genau eine Firma
- [x] SaaS-Kommunikation ausschließlich über HTTPS/WSS
- [x] lokale produktive Kommunikation ebenfalls ausschließlich über HTTPS/WSS
- [x] Reverse Proxy übernimmt TLS-Terminierung
- [x] WebSocket-Protokoll wird versioniert
- [x] WebSocket-Verbindungen werden per Token authentifiziert
- [x] Webhooks werden signiert
- [x] fachliche Payloads werden zusätzlich zu HTTPS/WSS sessionbasiert verschlüsselt
- [x] fachliche Daten werden in PostgreSQL nicht im Klartext gespeichert
- [x] pro Firma wird ein eigener Datenschlüssel geplant
- [x] technischer Mandantenname ist `organization`
- [x] Schema-Namenskonvention ist `company_<organization_id>`

### Architektur

- [x] Alle tabellen/namen sind englisch, werden aber in der UI deutsch angezeigt
- [x] endgültigen Namen für Mandantenobjekt festlegen: `Firma`, `Organisation` oder `Company`: organization
- [x] Schema-Namenskonvention final festlegen: company_<organization_id>
- [ ] `public`-Tabellen detailliert entwerfen
- [ ] Firmenschema-Tabellen detailliert entwerfen
- [ ] Migrationsstrategie für neue und bestehende Firmenschemas festlegen: erst am schluss nötig
- [ ] sichere Kapselung für firmenschema-bewusste Datenbankzugriffe entwerfen
- [ ] Socket-Protokoll versionieren
- [ ] REST-API-Versionierung festlegen
- [ ] Event-Topics und Berechtigungsprüfung definieren
- [ ] Fehler- und Reconnect-Verhalten für Clients definieren
- [ ] fensterbasiertes Live-Refresh- und Store-Konzept final spezifizieren
- [ ] Offline-Sync-Strategie für Desktopclient spezifizieren
- [ ] SaaS-Onboarding-Flow final spezifizieren
- [ ] lokale Erstinstallation final spezifizieren
- [ ] E-Mail-Versandarchitektur festlegen
- [ ] Token-Modell final festlegen: JWT, opaque Tokens oder Hybrid
- [ ] WebSocket-Handshake final spezifizieren
- [ ] Idempotency-Key-Strategie für kritische REST-Aktionen festlegen
- [ ] Secret-Verschlüsselung für Lieferanten-API-Zugangsdaten spezifizieren
- [ ] Reconnect- und `since_sequence`-Sync formal spezifizieren
- [ ] lokale Zertifikatsstrategie final festlegen
- [ ] Debug-/Entwicklungsmodus klar vom produktiven Verschlüsselungsmodell trennen
- [ ] Session-Key-Aushandlung final spezifizieren
- [ ] AEAD-Algorithmus final auswählen
- [ ] Schlüsselhierarchie für Master Key, Firmenschlüssel und Session-Schlüssel spezifizieren
- [ ] Key-Rotation und Re-Encryption-Konzept planen
- [ ] Such-/Indexstrategie für verschlüsselte Daten planen
- [ ] festlegen, welche technischen Metadaten unverschlüsselt bleiben dürfen

### Backend

- [ ] öffentliche Registrierungs-API implementieren
- [ ] Admin-Freischaltungs-API implementieren
- [ ] Organization-Registration-Detail-API implementieren
- [ ] Initial-E-Mail erneut senden implementieren
- [ ] Provisioning-Retry implementieren
- [ ] Admin-Oberfläche für Registrierungsfreigaben anbinden
- [ ] Initialpasswort-Erzeugung implementieren
- [ ] E-Mail-Outbox implementieren
- [ ] Passwortwechsel beim ersten Login erzwingen
- [ ] User-Einladungen implementieren
- [ ] lokalen Setup-Modus für genau eine Firma implementieren
- [ ] Authentifizierung implementieren
- [ ] SSO-Provider-Konzept festlegen
- [ ] 2FA-Verfahren festlegen
- [ ] Firmenauswahl nach Login implementieren
- [ ] Rollen- und Rechtesystem im Firmenschema implementieren
- [ ] Firmenschema-Erstellung implementieren
- [ ] Public-Migrationen implementieren
- [ ] Firmenschema-Migrationen implementieren
- [ ] Live-Event-Bus je Firma einbauen
- [ ] Audit-Logging je Firmenschema ergänzen
- [ ] Datei-Upload und Dokumentablage implementieren
- [ ] PDF-Erzeugung evaluieren
- [ ] REST-API-Grundstruktur implementieren
- [ ] Webhook-Versand implementieren
- [ ] HTTPS/WSS-Betrieb hinter Reverse Proxy dokumentieren
- [ ] Access-/Refresh-Token-Handling implementieren
- [ ] WebSocket-Token oder WebSocket-Auth implementieren
- [ ] WebSocket-Handshake mit Protokollversion implementieren
- [ ] Live-Event-Typen je Entity definieren
- [ ] `since_sequence`-Nachlade-API für verpasste Live-Events implementieren
- [ ] Idempotency Keys für kritische POST-Endpunkte implementieren
- [ ] Verschlüsselung gespeicherter API-Secrets implementieren
- [ ] Session-Key-Verwaltung implementieren
- [ ] REST-Payload-Verschlüsselung implementieren
- [ ] WebSocket-Payload-Verschlüsselung implementieren
- [ ] Datenbank-Verschlüsselungsservice implementieren
- [ ] Firmenschlüssel-Erzeugung bei Firmenschema-Anlage implementieren
- [ ] Entschlüsselung in Logs, Audit-Log und Change-Log verhindern

### Datenmodell

- [ ] Firmenmodell in `public` detaillieren
- [ ] User- und Firmenzuordnung detaillieren
- [ ] Registrierungsanforderungen detaillieren
- [ ] User-Einladungen detaillieren
- [ ] E-Mail-Outbox detaillieren
- [ ] Passwortstatus und Initialpasswort-Ablauf detaillieren
- [ ] Refresh-Token-Modell detaillieren
- [ ] Socket-Token-Modell detaillieren
- [ ] Session-Key-Metadaten detaillieren
- [ ] Idempotency-Key-Modell detaillieren
- [ ] verschlüsselte Feldstruktur festlegen
- [ ] unverschlüsselte technische Metadaten je Tabelle festlegen
- [ ] Such-/Indexfelder für verschlüsselte Daten modellieren
- [ ] Rollenmodell detaillieren
- [ ] Rechtemodell detaillieren
- [ ] Kundenmodell detaillieren
- [ ] Lieferantenmodell detaillieren
- [ ] Artikelmodell detaillieren
- [ ] Kundenpreisbedingungsmodell (`customer_price_terms`) detaillieren
- [ ] Lieferantenpreisbedingungsmodell (`supplier_price_terms`) detaillieren
- [ ] Skonto-Regelmodell (`cash_discount_terms`) detaillieren
- [ ] Lagerbewegungsmodell detaillieren
- [ ] Angebotsmodell detaillieren
- [ ] Angebotsversionsmodell detaillieren
- [ ] Ausgangsrechnungsmodell detaillieren
- [ ] Rechnungspositionsmodell mit Artikelpflicht und Preisüberschreibung detaillieren
- [ ] Eingangsrechnungsmodell detaillieren
- [ ] Kommunikationsmodell detaillieren
- [ ] Vorgangsmodell (`activities`) detaillieren
- [ ] Vorgangsverknüpfungen (`activity_links`) detaillieren
- [ ] Aufgaben-/Wiedervorlagemodell detaillieren
- [ ] Dokument- und Dokumentversionsmodell detaillieren
- [ ] Preisquellen- und Preisregelmodell detaillieren
- [ ] Rabatt- und Skonto-Vererbungsregeln definieren
- [ ] Importmapping-Modell detaillieren

### Artikelimporte und Preise

- [ ] Beispielpreislisten sammeln
- [ ] CSV-Import als ersten Importweg implementieren
- [ ] XML-Import planen
- [ ] JSON-Import planen
- [ ] Spalten-/Feldmapping definieren
- [ ] Importvalidierung definieren
- [ ] Importhistorie definieren
- [ ] Preisneuberechnung nach Import spezifizieren
- [ ] Staffelpreise modellieren
- [ ] kundenspezifische Preise modellieren
- [ ] Projektpreise modellieren
- [ ] Lieferanten-API-Connector-Schnittstelle entwerfen
- [ ] Grenzen frei konfigurierbarer API-Connectoren dokumentieren

### Frontends

- [ ] gemeinsames UI-Konzept für Web und Desktop definieren
- [ ] Login-Flow für beide Clients planen
- [ ] Firmenauswahl planen
- [ ] öffentliche Registrierungsseite planen
- [ ] Admin-Liste für Organization-Registrierungen planen
- [ ] Admin-Detail für Organization-Freischaltung planen
- [ ] Passwortänderung beim ersten Login planen
- [ ] Organization-Grunddaten-Setup planen
- [ ] Benutzerverwaltung und Einladungen planen
- [ ] Dashboard entwerfen
- [ ] Kundenmaske entwerfen
- [ ] Lieferantenmaske entwerfen
- [ ] Artikelmaske entwerfen
- [ ] Lagermaske entwerfen
- [ ] Angebotsmaske mit Versionierung entwerfen
- [ ] Rechnungsmasken entwerfen
- [ ] Artikelpflicht und Preisüberschreibung in Rechnungsmasken entwerfen
- [ ] Kommunikationsverlauf entwerfen
- [ ] Vorgangstimeline entwerfen
- [ ] Vorgangsmaske mit Typen, Status, Priorität und Verknüpfungen entwerfen
- [ ] Aufgaben-/Wiedervorlagemaske entwerfen
- [ ] Dokumentverwaltung entwerfen
- [ ] Importassistent entwerfen
- [ ] Live-Refresh-Verhalten je Maske festlegen
- [ ] fensterübergreifende Aktualisierung von Listen, Suchdialogen und Auswahlfeldern planen
- [ ] Konfliktverhalten bei parallelen Fensteränderungen planen
- [ ] Offline-Verhalten im Desktopclient je Maske festlegen
- [ ] sichere Token-Speicherung im Desktopclient planen
- [ ] sichere Session-Key-Speicherung im Desktopclient planen
- [ ] Verschlüsselung des lokalen Offline-Caches planen
- [ ] Verbindungsstatus und TLS-Warnungen im Desktopclient planen
- [ ] Update-Mechanismus für Desktopclient auswählen

### Betrieb

- [ ] Docker-Setup für Backend und PostgreSQL erweitern
- [ ] TLS/Reverse-Proxy-Konzept für öffentlichen Betrieb definieren
- [ ] Zertifikatsstrategie festlegen
- [ ] Caddy, Traefik oder Nginx als Startoption auswählen
- [ ] lokale Zertifikatserzeugung für Installationsprogramm planen
- [ ] Security Header und HTTPS-Redirect konfigurieren
- [ ] Backup- und Restore-Konzept je Firma erstellen
- [ ] Logging und Monitoring planen
- [ ] Konfiguration für lokalen und öffentlichen Betrieb trennen
- [ ] Update-Strategie für Backend festlegen
- [ ] Update-Strategie für Desktopclient festlegen
- [ ] Dateisystem-Speicherlayout für Dokumente definieren
- [ ] Speicherquoten je Firma planen

### Rechtliches und Buchhaltung

- [ ] deutsche Steuerlogik detaillieren
- [ ] Elster-relevante Datenfelder identifizieren
- [ ] Revisionssichere Rechnungsablage konzipieren
- [ ] Storno-/Korrekturrechnungsprozess definieren
- [ ] Nummernkreisregeln und Platzhalter definieren
- [ ] spätere DATEV-Erweiterung vorbereiten
- [ ] spätere E-Rechnungs-Erweiterung vorbereiten

## Nächster sinnvoller Schritt

Als nächstes sollte das Datenmodell für `public` und das Firmenschema konkretisiert
werden. Besonders wichtig sind:

1. `users`, `organizations`, `user_organizations` und Login/SSO/2FA
2. Rollen und atomare Rechte im Firmenschema
3. Nummernkreise und revisionssichere Rechnungen
4. Änderungslog für Live-Updates und spätere Offline-Synchronisation
