- Updated CustomersPage.vue to use decimalString for standard discount percent. - Enhanced IncomingInvoicesPage.vue to format item quantities, unit prices, and tax rates using decimalString. - Improved ItemsPage.vue with new supplier price management and decimal formatting for prices. - Modified OrganizationSetupPage.vue to use a dropdown for default tax rates and ensure numeric input for payment days. - Updated OutgoingInvoicesPage.vue to apply decimal formatting for customer discounts and item details. - Enhanced PriceImportsPage.vue to include additional fields in the import format. - Improved PriceRulesPage.vue to use decimal input for markup percentages. - Updated QuotesPage.vue to apply decimal formatting for customer discounts and item details. - Enhanced SuppliersPage.vue to use decimal input for standard discount percent. - Added a new SQL migration to set default unit for items to 'Stck'. - Introduced format.ts for centralized decimal and currency formatting utilities.
66 KiB
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 Systemsorganization: technischer Name für eine Firma in Code, API und DatenbankFirmenschema: PostgreSQL-Schema einer Firma, z.B.company_<organization_id>User: globaler Benutzeraccount inpublicKunde: Kunde einer Firma innerhalb eines FirmenschemasVorgang: fachlicher Sammelbegriff für Aktivität, Aufgabe, Wiedervorlage, Termin oder interne Bearbeitung mit Bezug zu Kunden, Lieferanten, Dokumenten oder Belegenactivity: 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 wieae,oe,ueodersswerden nur in technischen Bezeichnern verwendet, wenn das nötig ist. - Beispiel: Datenbank/API
organization, UIFirma.
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
publicliegen 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:
usersorganizationsuser_organizationsorganization_domains, optional für SaaS-Subdomainsauth_identities, für Passwortlogin, SSO und externe Identity Providersessionsoderrefresh_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:
rolespermissionsrole_permissionsuser_rolessettingsnumber_rangesaudit_logcustomerssupplierscontactsaddressesitemsitem_price_sourcesitem_pricesprice_rulescustomer_price_termssupplier_price_termscash_discount_termsstock_movementsquotesquote_versionsquote_itemsincoming_invoicesincoming_invoice_itemsoutgoing_invoicesoutgoing_invoice_itemscommunicationsactivitiesactivity_linkstasksdocument_templatesdocumentsdocument_versionsimportsimport_mappingsapi_connectorswebhooks
Mandantenauflösung
Vorgeschlagener Ablauf:
- User meldet sich global an.
- Backend liest aus
public.user_organizations, welchen Firmen der User zugeordnet ist. - Falls der User mehrere Firmen hat, wählt er eine aktive Firma aus.
- Backend erstellt ein Token oder eine Session mit aktiver
organization_id. - Jeder REST-Request und jede WebSocket-Verbindung wird gegen diese aktive Firma geprüft.
- Datenbankzugriffe setzen kontrolliert das passende Firmenschema.
Für den öffentlichen Betrieb kann zusätzlich eine Subdomain genutzt werden:
firma-a.example.comfirma-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:
- Interessent gibt Firmenname und E-Mail-Adresse ein.
- Server legt einen globalen User in
public.usersan, falls die E-Mail noch nicht existiert. - Server legt einen Firmeneintrag in
public.organizationsmit Statuspending_approvalan. - Server legt die Zuordnung in
public.user_organizationsan. - 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.
- Ein Administrator prüft und schaltet die Firma frei.
- Bei Freischaltung erzeugt das Backend das Firmenschema, Basistabellen, Standardrollen, Standardrechte und Grundeinstellungen.
- Server erzeugt ein zufälliges Initialpasswort.
- Server sendet eine E-Mail an die registrierte Adresse.
- User meldet sich mit E-Mail-Adresse und Initialpasswort an.
- Beim ersten Login muss das Passwort sofort geändert werden.
- 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_approvalapprovedactiverejectedsuspended
Vorgeschlagene Bedeutung:
pending_approval: Registrierung eingegangen, aber noch nicht freigegebenapproved: durch Admin freigegeben, technische Anlage läuft oder steht bevoractive: Firma ist nutzbarrejected: Registrierung wurde abgelehntsuspended: 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_passwordentfernt.
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:
- Firmen-User gibt neue E-Mail-Adresse ein.
- Backend prüft Recht, z.B.
users.invite. - Falls die E-Mail in
public.usersnoch nicht existiert, wird ein globaler User angelegt. - Backend legt oder aktualisiert
public.user_organizations. - Im Firmenschema werden die gewünschten Rollen in
user_rolesvergeben. - Backend erzeugt ein zufälliges Initialpasswort oder ein neues Initialpasswort, falls der User noch kein aktives Passwort für diese Installation hat.
- Backend setzt
must_change_password. - Backend sendet eine Einladung per E-Mail.
- Eingeladener User meldet sich mit E-Mail und Initialpasswort an.
- 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:
- Installation richtet Backend und PostgreSQL ein.
- Beim ersten Start erkennt das Backend, dass keine Firma existiert.
- Setup-Maske fragt Firmenname, Admin-E-Mail und Initialpasswort ab oder erzeugt ein zufälliges Passwort.
- Backend erstellt genau eine Firma und genau ein Firmenschema.
- Admin-User wird als
ownerzugeordnet. - 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:
{
"enc": "v1",
"kid": "session-key-id",
"nonce": "...",
"ciphertext": "...",
"tag": "..."
}
Für WebSocket bedeutet das:
hellound 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:
- Master Key, außerhalb der Datenbank
- Firmenschlüssel pro Firma
- optional Bereichs- oder Tabellenschlüssel
- 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:
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:
- Client meldet sich per REST an.
- Client wählt aktive Firma.
- Backend gibt Access Token und optional separaten kurzlebigen WebSocket-Token.
- Client öffnet
wss://server/ws. - Client sendet eine
hello-Nachricht mit Token und gewünschter Protokollversion. - Backend prüft Token, Firma, Userstatus und Rechte.
- Backend bestätigt mit
hello_ack. - Client abonniert Topics.
Beispielnachrichten:
{
"type": "hello",
"protocol_version": 1,
"token": "<socket_token>"
}
{
"type": "subscribe",
"topic": "customers",
"since_sequence": 12345
}
{
"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,SameSiteCookie - 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
sequenceimchange_log - WebSocket-Events enthalten
sequence - Clients können nach Reconnect ab einer bekannten
sequencenachladen
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_outboxprotokollieren
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:
owneradminsalesaccountingpurchasingwarehouseviewer
Beispielrechte:
customers.readcustomers.writecustomers.deleteactivities.readactivities.writeactivities.deletequotes.readquotes.writequotes.deleteoutgoing_invoices.readoutgoing_invoices.writeoutgoing_invoices.deleteitems.readitems.writeitems.deleteprice_rules.writeimports.writesettings.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 Preisbedingungensupplier_price_terms: lieferantenspezifische Einkaufs-/Zahlungsbedingungencash_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ätbleibt 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:
communicationsspeichert strukturierte Kommunikation, sobald echte E-Mail-/Telefonie-Integration oder Nachrichtenarchivierung implementiert wird.activitiesbildet 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:
- Datei hochladen
- Format erkennen oder manuell auswählen
- Spalten/Felder zu Zielfeldern mappen
- Vorschau und Validierung
- Import ausführen
- Importbericht speichern
- Einkaufspreise aktualisieren
- Verkaufspreise neu berechnen
- 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:
dashboardcustomerssuppliersitemsstockquotesincoming_invoicesoutgoing_invoicescommunicationstasksimports
Beispiele für Server-Events:
snapshotcreatedupdateddeletedprice_recalculatedstock_changedimport_finishedinvoice_created_from_quotetask_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
sequencefü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/organizationGET /api/admin/organization-registrationsGET /api/admin/organization-registrations/{id}POST /api/admin/organization-registrations/{id}/approvePOST /api/admin/organization-registrations/{id}/rejectPOST /api/admin/organization-registrations/{id}/resend-initial-emailPOST /api/admin/organization-registrations/{id}/retry-provisioningPOST /api/auth/loginPOST /api/auth/change-initial-passwordGET /api/auth/organizationsPOST /api/auth/select-organizationGET /api/organizations/current/setupPUT /api/organizations/current/setupPOST /api/organizations/current/invitationsPOST /api/organizations/current/invitations/{id}/resendGET /api/organizations/current/usersPATCH /api/organizations/current/users/{user_id}/rolesPOST /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:
- User erstellt eine Ausgangsrechnung.
- Beim Hinzufügen einer Position fehlt ein Artikel oder der Preis ist falsch.
- User öffnet parallel Artikelfenster oder Vorgangsfenster.
- User legt den Artikel/Vorgang an oder korrigiert den Preis.
- Das Rechnungsfenster erhält sofort ein Live-Event.
- 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_atoder 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
scrollundgroups. - 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-LabelFirmennameemail, UI-LabelE-Mail-Adresseaccept_terms, UI-LabelNutzungsbedingungen 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:
{
"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:
FreischaltenAblehnenZurück zur ListeEinladungs-E-Mail erneut sendenSchema-Erstellung wiederholen
Freischalten:
- erzeugt oder aktiviert
public.organizations - erzeugt
company_<organization_id> - erzeugt Basistabellen, Standardrollen und Standardrechte
- ordnet ersten User als
ownerzu - 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}/approvePOST /api/v1/admin/organization-registrations/{id}/rejectPOST /api/v1/admin/organization-registrations/{id}/resend-initial-emailPOST /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-LabelE-Mail-Adressepassword, UI-LabelPasswort
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-LabelAktuelles Passwortnew_password, UI-LabelNeues Passwortnew_password_confirm, UI-LabelNeues 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_passwordwird 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/setupPUT /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-LabelE-Mail-Adresseroles, UI-LabelRollen
Aktionen:
Einladung sendenEinladung erneut sendenUser deaktivierenRollen ändern
API:
GET /api/v1/organizations/current/usersPOST /api/v1/organizations/current/invitationsPATCH /api/v1/organizations/current/users/{user_id}/rolesPOST /api/v1/organizations/current/invitations/{id}/resendPATCH /api/v1/organizations/current/users/{user_id}/rolesPOST /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:
idlevalidatingsubmittingsuccesserror
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
- Öffentliche Registrierungsseite
- Login-Seite
- Passwortänderung beim ersten Login
- Admin-Liste Registrierungen
- Admin-Detail mit Freischalten/Ablehnen
- Organization-Grunddaten-Setup
- 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:
idemaildisplay_namepassword_hash, nullable bei reinem SSO-Accountis_activemust_change_passwordinitial_password_expires_atcreated_atupdated_atlast_login_at
public.organizations
Globale Firmenliste zur Zuordnung von Usern zu Firmenschemas.
Felder:
iddisplay_nameschema_namestatusregistration_emailapproved_by_user_idapproved_atrejected_by_user_idrejected_atrejection_reasoncreated_atupdated_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_idorganization_idstatusinvited_by_user_idinvited_ataccepted_atcreated_atupdated_at
Rollen werden hier bewusst nicht gespeichert. Die eigentlichen Rollen liegen im Firmenschema.
Mögliche Statuswerte:
pending_invitationactivedisabled
public.auth_identities
Verknüpfung von Usern mit Login-Methoden.
Felder:
iduser_idproviderprovider_subjectemail_at_providercreated_atupdated_at
Beispiele für provider:
passwordoidcsamlmicrosoftgoogle
public.refresh_tokens
Session-/Token-Verwaltung.
Felder:
iduser_idorganization_id, nullable solange keine Firma gewählt isttoken_hashexpires_atrevoked_atrevoked_reasonuser_agentcreated_ipcreated_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:
iduser_idorganization_idtoken_hashexpires_atused_atrevoked_atcreated_at
public.session_keys
Metadaten zu aktiven Session-Schlüsseln. Rohes Schlüsselmaterial darf nicht im Klartext in PostgreSQL gespeichert werden.
Felder:
iduser_idorganization_idkey_idwrapped_key, falls serverseitige Wiederaufnahme nötig istalgorithmcreated_atexpires_atrevoked_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:
iduser_idorganization_idkeyrequest_hashresponse_statusresponse_body_jsonexpires_atcreated_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_payloadencryption_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:
idorganization_nameemailstatusorganization_idrequested_atdecided_by_user_iddecided_atdecision_note
Vorteil einer separaten Tabelle:
- abgelehnte Registrierungen bleiben nachvollziehbar
public.organizationsenthä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:
idorganization_idemailinvited_by_user_idstatusexpires_ataccepted_atcreated_user_idcreated_at
Mögliche Statuswerte:
pendingacceptedexpiredrevoked
public.email_outbox
Queue für systemseitige E-Mails.
Felder:
idrecipient_emailtemplatepayload_jsonstatusattempt_countlast_errorsend_aftersent_atcreated_at
Verwendung:
- Initialpasswort nach Admin-Freischaltung
- Einladung neuer Firmen-User
- erneuter Versand durch Administrator
- spätere Passwort-Reset-Mails
company_*.settings
Firmeneinstellungen.
Felder:
keyvalue_jsonupdated_by_user_idupdated_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:
idcodenamedescriptionis_system_rolecreated_atupdated_at
company_*.permissions
Atomare Rechte.
Felder:
idcodedescription
Beispiele:
customers.readcustomers.writecustomers.deletequotes.approveinvoices.finalizesettings.write
company_*.role_permissions
Zuordnung von Rollen zu Rechten.
Felder:
role_idpermission_id
company_*.user_roles
Zuordnung globaler User zu Rollen in dieser Firma.
Felder:
user_idrole_idcreated_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:
idcodepatterncounter_valuecounter_paddingreset_ruleis_activeupdated_at
Regeln:
patternmuss 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:
idactor_user_idactionentity_typeentity_idbefore_jsonafter_jsoncreated_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:
idsequenceentity_typeentity_idoperationpayload_jsoncreated_atcreated_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:
identity_typeentity_idtitlecurrent_version_idcreated_atcreated_by_user_id
document_versions:
iddocument_idversion_numberstorage_pathmime_typesha256size_bytescreated_atcreated_by_user_id
Implementierungsreihenfolge
Empfohlene Reihenfolge für die nächsten Entwicklungsschritte:
public-Migrationen für User, Firmen und Zuordnungen- SaaS-Registrierung mit Status
pending_approval - Admin-Freischaltung und Backend-Service zur Firmenschema-Erstellung
- Mailversand für Initialpasswort und Einladungen
- Firmenschema-Basismigrationen für Rollen, Rechte, Settings, Nummernkreise, Audit-Log und Change-Log
- Login ohne SSO, aber mit Architektur für spätere Provider
- erzwungener Passwortwechsel beim ersten Login
- Firmenauswahl nach Login
- User-Einladung in bestehende Firma
- mandantenbewusste REST- und WebSocket-Basisschicht
- erstes Fachmodul
customers - Live-Update für
customers - Dashboard-Grundlage
- 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
- SaaS-Betrieb mit vielen Firmen auf einer Instanz
- öffentlicher Server wird selbst betrieben
- jede Firma bekommt eigenes PostgreSQL-Schema
publicenthält nur globale User- und Firmenzuordnung- Rollen, Rechte und Einstellungen liegen im Firmenschema
- ein User kann mehreren Firmen angehören
- Zielmarkt erste Stufe: Deutschland
- REST-API zusätzlich zum WebSocket-Protokoll
- Webhooks einplanen
- Desktopclient mit Offline-Fähigkeit
- automatische Desktopclient-Updates einplanen
- Dokumente im Filesystem, Metadaten in PostgreSQL
- öffentliche Registrierung für SaaS
- Admin-Freischaltung neuer Firmen
- Registrierung mit Firmenname und E-Mail-Adresse
- E-Mail-Adresse ist Username
- Initialpasswort per E-Mail
- Passwortwechsel beim ersten Login verpflichtend
- neue Firmen-User werden per Einladung hinzugefügt
- lokale Version erlaubt genau eine Firma
- SaaS-Kommunikation ausschließlich über HTTPS/WSS
- lokale produktive Kommunikation ebenfalls ausschließlich über HTTPS/WSS
- Reverse Proxy übernimmt TLS-Terminierung
- WebSocket-Protokoll wird versioniert
- WebSocket-Verbindungen werden per Token authentifiziert
- Webhooks werden signiert
- fachliche Payloads werden zusätzlich zu HTTPS/WSS sessionbasiert verschlüsselt
- fachliche Daten werden in PostgreSQL nicht im Klartext gespeichert
- pro Firma wird ein eigener Datenschlüssel geplant
- technischer Mandantenname ist
organization - Schema-Namenskonvention ist
company_<organization_id>
Architektur
- Alle tabellen/namen sind englisch, werden aber in der UI deutsch angezeigt
- endgültigen Namen für Mandantenobjekt festlegen:
Firma,OrganisationoderCompany: organization - 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
publicdetaillieren - 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:
users,organizations,user_organizationsund Login/SSO/2FA- Rollen und atomare Rechte im Firmenschema
- Nummernkreise und revisionssichere Rechnungen
- Änderungslog für Live-Updates und spätere Offline-Synchronisation