Zum Hauptinhalt springen

Datenmodell

Entity-Relationship-Übersicht

tenants (1) ──┬── profiles (N)           ← User-Tenant-Zuordnung
├── tenant_settings (1) ← Konfiguration
├── suppliers (N) ──── green_coffee_lots (N)
├── machines (N) ──── maintenance_tasks (N)
├── roast_profiles (N) ──── product_variants (N)
│ └──── roast_log_entries (N) ──── roasted_barrels (N)
│ └── packing_positions (N)
├── b2b_customers (N) ──── b2b_orders (N) ──── b2b_order_positions (N)
│ └──── b2b_invoices (N) ── b2b_invoice_positions (N)
├── pricing_agreements (N)
├── forecast_positions (N)
├── fixed_costs (N)
└── shipments (N)

Rückverfolgungskette

Die zentrale Kette für EU-VO 178/2002 Compliance:

supplier → green_coffee_lots → roast_log_entries → roasted_barrels → packing_positions → product_variants → b2b_order_positions → b2b_customers

Kein Glied darf NULL sein (FK NOT NULL Constraints).

Kerntabellen

tenants

Wurzel der Multi-Tenancy. Jeder Tenant = eine Rösterei.

SpalteTypBeschreibung
idUUID (PK)Tenant-ID
nameTEXTRösterei-Name
planTEXTtrial / starter / pro / cancelled
trial_ends_atTIMESTAMPTZEnde der Testphase
stripe_customer_idTEXTStripe-Kundennummer
stripe_subscription_idTEXTStripe-Abo-ID
suspended_atTIMESTAMPTZSuspendierungszeitpunkt

green_coffee_lots

Rohkaffee-Bestände.

SpalteTypBeschreibung
idUUID (PK)
tenant_idUUID (FK)
lot_numberTEXTAuto: LOT-NNNN
nameTEXTKaffee-Bezeichnung
supplier_idUUID (FK)
origin_countryTEXTHerkunftsland
raw_weight_kgNUMERIC(10,2)Liefergewicht
purchase_price_per_kgNUMERIC(10,2)Einkaufspreis
statusENUMActive / Depleted / Blocked
processingENUMWashed / Natural / Honey / ...

Computed: remaining_kg = raw_weight_kg - SUM(roast_log_entries.raw_weight_g)/1000

roast_log_entries

Einzelne Röstungen.

SpalteTypBeschreibung
idUUID (PK)
roast_display_idTEXTAuto: PROFILE-DATE-RNNNN-MACHINE
profile_idUUID (FK)Röstprofil
machine_idUUID (FK)Röstmaschine
lot_idUUID (FK, nullable)Einzelnes Lot (NULL bei Blend)
lot_weightsJSONBBlend: [{lot_id, weight_g}]
raw_weight_gINTEGERRohgewicht in Gramm
roasted_weight_gINTEGERRöstgewicht in Gramm
roast_loss_pctNUMERICAuto-berechnet
quality_checkENUMOK / Review / Rejected
barrel_idUUID (FK)Zugehörige Tonne
roast_dateDATERöstdatum

Blend-Handling: Entweder lot_id ODER lot_weights ist gesetzt (XOR).

roasted_barrels

Tonnen-Sessions.

SpalteTypBeschreibung
idUUID (PK)
physical_barrelTEXTName der physischen Tonne
profile_idUUID (FK)Röstprofil
session_dateDATEDatum der Session
closed_atTIMESTAMPTZWann geschlossen

Computed Status:

  • Open: closed_at IS NULL
  • Resting: closed_at IS NOT NULL AND jetzt < session_date + rest_days
  • Ready: Ruhezeit abgelaufen AND available_g > 0
  • Empty: available_g = 0

Constraint: UNIQUE(tenant_id, physical_barrel, session_date) — 1 Profil + 1 Tonne + 1 Tag

b2b_invoices

Rechnungen (GoBD-konform).

SpalteTypBeschreibung
idUUID (PK)
invoice_numberTEXTNULL bis Finalisierung, dann RE-YYYY-NNN
invoice_statusENUMDraft / Finalized / Cancelled
payment_statusENUMOpen / Paid / Overdue
total_netNUMERIC(10,2)Nettobetrag
tax_rateNUMERIC(5,2)MwSt-Satz
tax_amountNUMERIC(10,2)MwSt-Betrag
total_grossNUMERIC(10,2)Bruttobetrag
cancelled_by_invoice_idUUID (self-ref)Storno-Kette

Immutabilität: Finalisierte Rechnungen sind unveränderlich. Storno = neue Gutschrift-Rechnung.

Enums

-- Lot-Status
CREATE TYPE lot_status AS ENUM ('Active', 'Depleted', 'Blocked');

-- Aufbereitung
CREATE TYPE processing_method AS ENUM ('Washed', 'Natural', 'Honey', 'Anaerobic', 'WetHulled', 'Other');

-- Röststil
CREATE TYPE roast_style AS ENUM ('Espresso', 'Filter', 'Omni');

-- Qualitätsprüfung
CREATE TYPE quality_check AS ENUM ('OK', 'Review', 'Rejected');

-- Lieferstatus
CREATE TYPE delivery_status AS ENUM ('Pending', 'Shipped', 'Delivered');

-- Rechnungsstatus
CREATE TYPE invoice_status AS ENUM ('Draft', 'Finalized', 'Cancelled');

-- Zahlungsstatus
CREATE TYPE payment_status AS ENUM ('Open', 'Paid', 'Overdue');

-- Zahlungsbedingungen
CREATE TYPE payment_terms AS ENUM ('Prepaid', 'Net14', 'Net30', 'Net60', 'CashOnDelivery');

-- Kundentyp
CREATE TYPE customer_type AS ENUM ('B2B', 'Reseller', 'Gastro', 'Office');

-- Rollen
CREATE TYPE role_type AS ENUM ('admin', 'roester', 'packer', 'buero', 'readonly');

-- Preisbasis
CREATE TYPE price_basis AS ENUM ('PerKg', 'PerUnit', 'Flat');

-- Lieferintervall
CREATE TYPE delivery_interval AS ENUM ('Weekly', 'Biweekly', 'Monthly', 'OnDemand');

-- Versandstatus
CREATE TYPE shipment_status AS ENUM ('LabelCreated', 'InTransit', 'Delivered', 'Undeliverable', 'Cancelled');

Datentyp-Regeln

DatenartTypNiemals
GeldbeträgeNUMERIC(10,2)float / real
GewichteNUMERIC(10,2)float / real
ProzenteNUMERIC(5,2)float / real
ZeitstempelTIMESTAMPTZTIMESTAMP (ohne tz)
IDsUUIDINTEGER / SERIAL

Soft-Delete Pattern

Tabellen mit deleted_at TIMESTAMPTZ:

  • green_coffee_lots, profiles, roast_log_entries, product_variants
  • packing_positions, roasted_barrels, b2b_orders, pricing_agreements

Aktive Einträge: WHERE deleted_at IS NULL (mit partiellem Index).

2-Tier Delete: Soft Delete (reversibel) → Hard Delete (permanent, nach DSGVO-Prüfung).