feat(kontingent): Credits-Stellwerte je Lizenz + Lizenz beim Org-Anlegen
Bedienseite für das Credits-Monatskontingent des Monitors. Das Portal setzt jetzt alle Stellwerte, der Monitor bleibt Herr der Laufzeitfelder. - Lizenz-Modal und Budget-Formular um Abrechnungszeitraum (monthly/total), Übertrag-Checkbox und Warnschwelle erweitert, Vorbelegungen auf das beschlossene Modell (10.000 Credits, monatlich, Übertrag an, 0,20 USD). - Neue Organisation kann direkt eine Lizenz mit Kontingent anlegen (OrgCreate.license, verschachtelt). Org- und Lizenz-Insert laufen in EINER Transaktion, schlägt der Lizenz-Teil fehl, entsteht keine halbe Organisation. Gemeinsamer Helper insert_license_row statt Duplikation der Widerruf- und Audit-Logik. - PUT /api/token-usage/budget validiert credits_period (monthly|total) und budget_warning_percent (1 bis 100), credits_rollover wird wie unlimited_budget normalisiert. Laufzeitfelder bleiben bewusst außerhalb der Whitelist. - Overview und Org-Detail rechnen den Rest jetzt gegen verfügbar = Kontingent + Übertrag, dieselbe Bezugsgröße wie der Hard-Stop im Monitor. Neuer read-only Block Laufzeitwerte im Token-Subtab. - Startup-Check loggt einen deutlichen Fehler, wenn die licenses-Tabelle die Credits-Spalten noch nicht hat (falsche Deploy-Reihenfolge). - Migration migrations/2026-07-25_credits_period.py (idempotent, wortgleich mit der Monitor-Migration) für die Portal-Staging-DB, auf Live gilt: Monitor VOR Portal promoten. - 13 neue Modell-Tests, pytest grün (die zwei bekannten shared-Import-Fehlschläge bestehen unverändert seit vorher). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Dieser Commit ist enthalten in:
@@ -64,6 +64,16 @@ async def create_organization(
|
||||
(data.name, data.slug, now, now),
|
||||
)
|
||||
org_id = cursor.lastrowid
|
||||
|
||||
# Optional direkt eine Lizenz mit Credits-Kontingent anlegen. Beide Inserts
|
||||
# liegen VOR dem Commit, schlaegt der Lizenz-Teil fehl (z.B. fehlende
|
||||
# Spalten), entsteht keine halb angelegte Organisation.
|
||||
lic_id = None
|
||||
revoked_lics: list = []
|
||||
if data.license is not None:
|
||||
from routers.licenses import insert_license_row
|
||||
lic_id, revoked_lics = await insert_license_row(db, org_id, data.license)
|
||||
|
||||
await db.commit()
|
||||
|
||||
# output_language als organization_settings-Eintrag persistieren
|
||||
@@ -77,6 +87,11 @@ async def create_organization(
|
||||
action="create", resource_type="organization", resource_id=org_id,
|
||||
after=dict(new_row_obj),
|
||||
)
|
||||
if lic_id is not None:
|
||||
from routers.licenses import audit_license_creation
|
||||
await audit_license_creation(
|
||||
db, admin, get_client_ip(request), lic_id, revoked_lics
|
||||
)
|
||||
return await _enrich_org(db, new_row_obj)
|
||||
|
||||
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren