# 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_` - `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` 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 X-Request-Id: ``` ### 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": "" } ``` ```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 - Lieferant - Herstellerartikelnummer - EAN/GTIN - Preisgültigkeit - Lagerbestand - Mindestbestand - Lagerbewegungen Preisberechnung: - Einkaufspreis fest festlegen - Einkaufspreis aus Quelle übernehmen - Einkaufspreis mal Multiplikator - Staffelpreise - 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 ### 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_` - 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_` ### 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_ - [ ] `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