konzept billing ext

This commit is contained in:
ValueOn AG 2026-03-21 01:34:59 +01:00
parent d5679f6e07
commit 4c7cc4069a

View file

@ -46,9 +46,10 @@ Mandant (Kostenstelle)
|--------|--------------|--------------|---------| |--------|--------------|--------------|---------|
| `PREPAY_MANDATE` | Vorbezahltes Guthaben auf Mandantenebene | Mandant | Gemeinsames Budget für alle User | | `PREPAY_MANDATE` | Vorbezahltes Guthaben auf Mandantenebene | Mandant | Gemeinsames Budget für alle User |
| `PREPAY_USER` | Vorbezahltes Guthaben pro User | User (im Mandanten-Kontext) | Individuelles Budget | | `PREPAY_USER` | Vorbezahltes Guthaben pro User | User (im Mandanten-Kontext) | Individuelles Budget |
| `CREDIT_POSTPAY` | Nutzung auf Kredit mit Nachzahlung | Mandant (Rechnungsadresse erforderlich) | Monatsrechnung |
| `UNLIMITED` | Keine Kostenlimitierung | - | Nur für interne Mandanten | | `UNLIMITED` | Keine Kostenlimitierung | - | Nur für interne Mandanten |
**Hinweis:** Das frühere Modell `CREDIT_POSTPAY` ist **nicht mehr** Teil des Produkts. Alte gespeicherte Werte werden beim Lesen auf `PREPAY_MANDATE` normalisiert (ohne DB-Migration).
### Modell: PREPAY_MANDATE ### Modell: PREPAY_MANDATE
``` ```
@ -74,7 +75,7 @@ Mandant "SOHA Treuhand AG"
``` ```
Mandant "ROOT" (Bootstrap-Mandant) Mandant "ROOT" (Bootstrap-Mandant)
├── Abrechnungsmodell: PREPAY_USER ├── Abrechnungsmodell: PREPAY_USER
├── Startguthaben für neue User: 10.00 CHF ├── Startguthaben bei neuem User nur hier (Root): z. B. 5.00 CHF (defaultUserCredit)
└── Nutzer: └── Nutzer:
├── User A: Guthaben 10.00 CHF → verbraucht 3.50 → Rest 6.50 CHF ├── User A: Guthaben 10.00 CHF → verbraucht 3.50 → Rest 6.50 CHF
└── User B: Guthaben 25.00 CHF → verbraucht 12.00 → Rest 13.00 CHF └── User B: Guthaben 25.00 CHF → verbraucht 12.00 → Rest 13.00 CHF
@ -82,26 +83,13 @@ Mandant "ROOT" (Bootstrap-Mandant)
**Eigenschaften:** **Eigenschaften:**
- Jeder User hat eigenes Guthaben im Mandanten-Kontext - Jeder User hat eigenes Guthaben im Mandanten-Kontext
- Neuer User erhält Startguthaben (konfigurierbar) - **Automatisches** Startguthaben nur, wenn ein User dem **Root-Mandanten** neu zugeordnet wird; bei allen anderen Mandanten wird das Nutzerkonto mit 0 CHF angelegt (Aufladung z. B. per Admin/Stripe)
- User kann Guthaben aufladen - User kann Guthaben aufladen
- Bei Guthaben = 0 → AI-Features blockiert für diesen User - Bei Guthaben = 0 → AI-Features blockiert für diesen User
### Modell: CREDIT_POSTPAY ### Modell: CREDIT_POSTPAY (entfernt)
``` Nicht mehr unterstützt. Siehe Hinweis in der Modell-Übersicht.
Mandant "Enterprise AG"
├── Abrechnungsmodell: CREDIT_POSTPAY
├── Rechnungsadresse: Enterprise AG, Musterstrasse 1, 8000 Zürich
├── Kreditlimit: 2000.00 CHF (optional)
├── Aktueller Saldo: -450.00 CHF (Schulden)
└── Monatsende: Rechnung über 450.00 CHF
```
**Eigenschaften:**
- Nutzung ohne Vorauszahlung
- Monatliche Abrechnung
- Optionales Kreditlimit
- Für verifizierte Geschäftskunden
--- ---
@ -110,18 +98,7 @@ Mandant "Enterprise AG"
### Neue Tabellen ### Neue Tabellen
```python ```python
# modules/datamodels/datamodelBilling.py # modules/datamodels/datamodelBilling.py (vereinfacht, Stand Implementierung)
class BillingAddress(BaseModel):
"""Rechnungsadresse für CREDIT_POSTPAY Mandanten"""
company: str # Firmenname
street: str # Strasse und Hausnummer
zip: str # PLZ
city: str # Ort
country: str # Land (Default: "CH")
vatNumber: Optional[str] # MwSt-Nummer (optional)
class BillingAccount(BaseModel): class BillingAccount(BaseModel):
"""Abrechnungskonto pro Mandant oder User-Mandant-Kombination""" """Abrechnungskonto pro Mandant oder User-Mandant-Kombination"""
@ -130,8 +107,7 @@ class BillingAccount(BaseModel):
mandateId: str # FK → Mandate mandateId: str # FK → Mandate
userId: Optional[str] # FK → User (nur bei PREPAY_USER) userId: Optional[str] # FK → User (nur bei PREPAY_USER)
accountType: str # MANDATE | USER accountType: str # MANDATE | USER
balance: float # Aktuelles Guthaben/Saldo in CHF balance: float # Aktuelles Guthaben in CHF (Prepaid)
creditLimit: Optional[float] # Kreditlimit in CHF (nur CREDIT_POSTPAY)
warningThreshold: float # Warnschwelle in CHF warningThreshold: float # Warnschwelle in CHF
lastWarningAt: Optional[datetime] # Letzte Warnung gesendet lastWarningAt: Optional[datetime] # Letzte Warnung gesendet
enabled: bool # Account aktiv enabled: bool # Account aktiv
@ -161,15 +137,11 @@ class BillingSettings(BaseModel):
id: str # UUID id: str # UUID
mandateId: str # FK → Mandate (UNIQUE) mandateId: str # FK → Mandate (UNIQUE)
billingModel: str # PREPAY_MANDATE | PREPAY_USER | CREDIT_POSTPAY | UNLIMITED billingModel: str # PREPAY_MANDATE | PREPAY_USER | UNLIMITED
# Konfiguration # Konfiguration
defaultUserCredit: float # Startguthaben in CHF für neue User (bei PREPAY_USER) defaultUserCredit: float # Startguthaben nur sinnvoll Root + PREPAY_USER (Bootstrap setzt z. B. 5 CHF); sonst typ. 0
warningThresholdPercent: float # Warnschwelle in % (z.B. 10) warningThresholdPercent: float # Warnschwelle in % (z.B. 10)
blockOnZeroBalance: bool # AI blockieren bei Guthaben = 0
# Rechnungsadresse (erforderlich für CREDIT_POSTPAY)
billingAddress: Optional[BillingAddress]
# Benachrichtigungen # Benachrichtigungen
notifyEmails: List[str] # Email-Adressen für Monatsberichte notifyEmails: List[str] # Email-Adressen für Monatsberichte
@ -322,13 +294,12 @@ class BillingService:
return BillingCheckResult(allowed=True) return BillingCheckResult(allowed=True)
if account.balance < estimatedCost: if account.balance < estimatedCost:
if self.settings.blockOnZeroBalance: return BillingCheckResult(
return BillingCheckResult( allowed=False,
allowed=False, reason='INSUFFICIENT_BALANCE',
reason='INSUFFICIENT_BALANCE', currentBalance=account.balance,
currentBalance=account.balance, requiredAmount=estimatedCost
requiredAmount=estimatedCost )
)
return BillingCheckResult(allowed=True, currentBalance=account.balance) return BillingCheckResult(allowed=True, currentBalance=account.balance)
@ -473,9 +444,8 @@ def _setupRootMandate():
id=generateUuid(), id=generateUuid(),
mandateId=rootMandate.id, mandateId=rootMandate.id,
billingModel='PREPAY_USER', billingModel='PREPAY_USER',
defaultUserCredit=10.0, # 10 CHF Startguthaben defaultUserCredit=5.0, # 5 CHF Startguthaben (siehe DEFAULT_USER_CREDIT_CHF im Code)
warningThresholdPercent=20, warningThresholdPercent=20,
blockOnZeroBalance=True,
notifyOnWarning=True notifyOnWarning=True
) )
@ -518,7 +488,7 @@ def _setupNewUserInRoot(user: User):
mandateId=rootMandate.id, mandateId=rootMandate.id,
userId=user.id, userId=user.id,
accountType='USER', accountType='USER',
balance=settings.defaultUserCredit, # 10 CHF balance=settings.defaultUserCredit, # z. B. 5 CHF Default
warningThreshold=settings.defaultUserCredit * 0.2, # 20% warningThreshold=settings.defaultUserCredit * 0.2, # 20%
enabled=True enabled=True
) )
@ -788,7 +758,6 @@ Das Automation-Editor Modal wird in drei Tabs aufgeteilt:
- Übersicht aller Mandanten mit Billing-Status - Übersicht aller Mandanten mit Billing-Status
- Abrechnungsmodell ändern - Abrechnungsmodell ändern
- Guthaben manuell aufladen (Stripe-Vorbereitung für Zukunft) - Guthaben manuell aufladen (Stripe-Vorbereitung für Zukunft)
- Rechnungsadresse verwalten (für CREDIT_POSTPAY)
- Email-Empfänger verwalten - Email-Empfänger verwalten
``` ```
@ -801,7 +770,7 @@ Das Automation-Editor Modal wird in drei Tabs aufgeteilt:
│ │ ──────────────────────────────────────────────── │ │ │ │ ──────────────────────────────────────────────── │ │
│ │ ROOT PREPAY_USER n/a ✓ │ │ │ │ ROOT PREPAY_USER n/a ✓ │ │
│ │ SOHA Treuhand PREPAY_MANDATE CHF 299 ✓ │ │ │ │ SOHA Treuhand PREPAY_MANDATE CHF 299 ✓ │ │
│ │ Enterprise AG CREDIT_POSTPAY CHF -450 ✓ │ │ │ │ Enterprise AG PREPAY_MANDATE CHF 1200 ✓ │ │
│ │ Test GmbH PREPAY_MANDATE CHF 5 ⚠ │ │ │ │ Test GmbH PREPAY_MANDATE CHF 5 ⚠ │ │
│ └──────────────────────────────────────────────────┘ │ │ └──────────────────────────────────────────────────┘ │
│ │ │ │
@ -812,11 +781,6 @@ Das Automation-Editor Modal wird in drei Tabs aufgeteilt:
│ │ Abrechnungsmodell: [PREPAY_MANDATE ▼] │ │ │ │ Abrechnungsmodell: [PREPAY_MANDATE ▼] │ │
│ │ │ │ │ │ │ │
│ │ Warnschwelle: [10] % │ │ │ │ Warnschwelle: [10] % │ │
│ │ Blockieren bei 0: [✓] │ │
│ │ │ │
│ │ Rechnungsadresse (für CREDIT_POSTPAY): │ │
│ │ [Firma: ________________] [Strasse: _________] │ │
│ │ [PLZ: ____] [Ort: _______] [MwSt-Nr: ________] │ │
│ │ │ │ │ │ │ │
│ │ Benachrichtigungs-Emails: │ │ │ │ Benachrichtigungs-Emails: │ │
│ │ [admin@soha.ch, billing@soha.ch ] │ │ │ │ [admin@soha.ch, billing@soha.ch ] │ │
@ -975,7 +939,7 @@ PowerOn Platform
1. **Root-Mandant Setup** 1. **Root-Mandant Setup**
- `PREPAY_USER` Modell - `PREPAY_USER` Modell
- 10 CHF Startguthaben für neue User - 5 CHF automatisches Startguthaben nur für neu im Root-Mandanten angelegte User (`DEFAULT_USER_CREDIT_CHF`)
- RBAC-Berechtigungen für alle LLM-Provider - RBAC-Berechtigungen für alle LLM-Provider
2. **Bestehende User migrieren** 2. **Bestehende User migrieren**
@ -1064,7 +1028,7 @@ Jedes AICore-Plugin definiert seine eigenen Preise via `calculatePriceCHF()`. Di
|---------|--------------| |---------|--------------|
| **AICore** | Plugin-System für LLM-Provider Integration | | **AICore** | Plugin-System für LLM-Provider Integration |
| **Billing Account** | Konto für Guthaben/Saldo (pro Mandant oder User) | | **Billing Account** | Konto für Guthaben/Saldo (pro Mandant oder User) |
| **Billing Address** | Rechnungsadresse, erforderlich für CREDIT_POSTPAY | | **parseBillingModelFromStoredValue** | Mappt unbekannte/legacy `billingModel`-Strings auf `PREPAY_MANDATE` |
| **Connector** | AICore-Plugin für einen spezifischen Provider | | **Connector** | AICore-Plugin für einen spezifischen Provider |
| **Kostenstelle** | Mandant, dem Kosten zugeordnet werden | | **Kostenstelle** | Mandant, dem Kosten zugeordnet werden |
| **Mandate** | Mandant/Tenant in der Multi-Tenant-Architektur | | **Mandate** | Mandant/Tenant in der Multi-Tenant-Architektur |
@ -1133,9 +1097,9 @@ frontend_nyla/src/
### Phase 1: Backend Grundstruktur ### Phase 1: Backend Grundstruktur
- [ ] `datamodelBilling.py` erstellen (BillingAccount, BillingTransaction, BillingSettings, UsageStatistics, BillingAddress) - [x] `datamodelBilling.py` (BillingAccount, BillingTransaction, BillingSettings, UsageStatistics)
- [ ] `interfaceDbBilling.py` erstellen (CRUD-Operationen) - [x] `interfaceDbBilling.py` (CRUD-Operationen)
- [ ] Datenbank-Migrations-Script erstellen - [x] Kein separates Migrations-Script nötig (Legacy-Werte werden beim Lesen normalisiert)
- [ ] AICore-Plugins: Preise von USD auf CHF umstellen (`calculatepriceCHF` mit echten CHF-Werten) - [ ] AICore-Plugins: Preise von USD auf CHF umstellen (`calculatepriceCHF` mit echten CHF-Werten)
### Phase 2: Service-Layer ### Phase 2: Service-Layer