diff --git a/docs/ABRECHNUNG.md b/docs/ABRECHNUNG.md index 3ef161d..b8dfd77 100644 --- a/docs/ABRECHNUNG.md +++ b/docs/ABRECHNUNG.md @@ -61,11 +61,9 @@ Der Wechsel passiert träge bei der nächsten Lizenzprüfung, nicht über einen Zeitplan. Ein verpasster Monatswechsel wird dadurch beim nächsten Zugriff nachgeholt, und ohne Nutzung wird ohnehin nichts verbraucht. -`credits_rollover` überträgt ungenutzte Credits in den Folgemonat, gedeckelt -auf ein Monatskontingent. Der Übertrag steht in `credits_carried` und zählt beim -Hard-Stop mit. Das ist empfehlenswert, weil OSINT-Nutzung ereignisgetrieben ist. -Bricht eine Krise aus, braucht der Kunde in einer Woche mehr als sonst im -Quartal, und genau dann darf das System nicht dichtmachen. +Ungenutzte Credits verfallen zum Periodenende. Ein Übertrag in den Folgemonat +war kurz vorgesehen und wurde als Produktentscheidung 07/2026 wieder entfernt, +es gilt der harte Monatsdeckel wie verkauft. Eine Bestandslizenz ohne Periodenmarke bekommt beim ersten Zugriff die aktuelle Periode eingetragen, **ohne** den Verbrauch zurückzusetzen. Sonst würden ihr @@ -93,7 +91,6 @@ nächste Schritt. | `credits_total` | Kontingent je Periode | 10000 | | `cost_per_credit` | nur für `BILLING_MODE=actual` und die interne Rechnung | 0.20 | | `credits_period` | `monthly` oder `total` | monthly | -| `credits_rollover` | Übertrag erlauben | 1 | | `budget_warning_percent` | Warnschwelle in Prozent | 80 | | `unlimited_budget` | Kontingent aushebeln | 0 | diff --git a/src/database.py b/src/database.py index e6ff7bf..21c5ea8 100644 --- a/src/database.py +++ b/src/database.py @@ -877,10 +877,9 @@ async def init_db(): # Beginn der laufenden Periode als YYYY-MM. Leer = beim naechsten # Zugriff auf den aktuellen Monat gesetzt, ohne Verbrauch zu loeschen. await db.execute("ALTER TABLE licenses ADD COLUMN credits_period_start TEXT") - # Ungenutzte Credits in die Folgeperiode uebertragen. Aus = harter - # Monatsdeckel wie verkauft, An = faengt Krisenspitzen ab. - await db.execute("ALTER TABLE licenses ADD COLUMN credits_rollover INTEGER DEFAULT 0") - await db.execute("ALTER TABLE licenses ADD COLUMN credits_carried REAL DEFAULT 0") + # Ungenutzte Credits verfallen zum Periodenende. Ein Uebertrag in den + # Folgemonat war kurz vorgesehen und wurde als Produktentscheidung + # 07/2026 wieder entfernt (harter Monatsdeckel wie verkauft). # Verhindert, dass die Warnschwelle bei jeder Buchung erneut meldet. await db.execute("ALTER TABLE licenses ADD COLUMN budget_warning_sent INTEGER DEFAULT 0") await db.commit() diff --git a/src/services/license_service.py b/src/services/license_service.py index 5c25238..4dcc5cc 100644 --- a/src/services/license_service.py +++ b/src/services/license_service.py @@ -41,8 +41,8 @@ async def roll_credit_period(db: aiosqlite.Connection, lic: dict) -> dict: Das ist robuster, weil ein verpasster Monatswechsel beim naechsten Zugriff ohnehin nachgeholt wird und ohne Nutzung auch nichts verbraucht wird. - Bei aktivem Uebertrag wandert ungenutztes Guthaben in die Folgeperiode, - gedeckelt auf ein Monatskontingent, damit es nicht unbegrenzt anwaechst. + Ungenutzte Credits verfallen zum Periodenende (kein Uebertrag, + Produktentscheidung 07/2026). Returns: Das ggf. aktualisierte Lizenz-dict (in-place ergaenzt). @@ -70,33 +70,21 @@ async def roll_credit_period(db: aiosqlite.Connection, lic: dict) -> dict: if started == period: return lic - total = lic.get("credits_total") or 0 - carried_old = lic.get("credits_carried") or 0 - used = lic.get("credits_used") or 0 - - if lic.get("credits_rollover"): - carried_new = max(0.0, (total + carried_old) - used) - carried_new = min(carried_new, float(total)) # hoechstens ein Monat - else: - carried_new = 0.0 - await db.execute( """UPDATE licenses - SET credits_used = 0, credits_carried = ?, credits_period_start = ?, + SET credits_used = 0, credits_period_start = ?, budget_warning_sent = 0 WHERE id = ?""", - (round(carried_new, 2), period, lic["id"]), + (period, lic["id"]), ) await db.commit() lic["credits_used"] = 0 - lic["credits_carried"] = carried_new lic["credits_period_start"] = period lic["budget_warning_sent"] = 0 logger.info( - f"Lizenz {lic['id']}: neue Periode {period}, Verbrauch zurueckgesetzt " - f"(Uebertrag {round(carried_new, 2)} Einheiten)" + f"Lizenz {lic['id']}: neue Periode {period}, Verbrauch zurueckgesetzt" ) return lic @@ -185,10 +173,10 @@ async def check_license(db: aiosqlite.Connection, organization_id: int) -> dict: unlimited_budget = bool(lic_dict.get("unlimited_budget")) credits_total = lic_dict.get("credits_total") credits_used = lic_dict.get("credits_used") or 0 - credits_carried = lic_dict.get("credits_carried") or 0 credits_period = lic_dict.get("credits_period") or "monthly" - # Verfuegbar ist das Kontingent plus ein etwaiger Uebertrag aus dem Vormonat. - credits_available = (credits_total or 0) + credits_carried + # Verfuegbar ist genau das Kontingent. Ungenutzte Credits verfallen zum + # Periodenende (kein Uebertrag, Produktentscheidung 07/2026). + credits_available = credits_total or 0 # STAGING_MODE: kein Token-Budget-Hard-Stop, immer unlimited if _staging_mode(): @@ -219,7 +207,6 @@ async def check_license(db: aiosqlite.Connection, organization_id: int) -> dict: pass # Budget-Check (Hard-Stop bei aufgebrauchtem Guthaben, ausser unlimited). - # Geprueft wird gegen das verfuegbare Guthaben, also inklusive Uebertrag. budget_exceeded = False if not unlimited_budget and credits_available > 0: if credits_used >= credits_available: @@ -245,7 +232,6 @@ async def check_license(db: aiosqlite.Connection, organization_id: int) -> dict: "unlimited_budget": False, "credits_total": credits_total, "credits_used": credits_used, - "credits_carried": credits_carried, "credits_available": credits_available, "credits_period": credits_period, } @@ -262,7 +248,6 @@ async def check_license(db: aiosqlite.Connection, organization_id: int) -> dict: "unlimited_budget": unlimited_budget, "credits_total": credits_total, "credits_used": credits_used, - "credits_carried": credits_carried, "credits_available": credits_available, "credits_period": credits_period, } @@ -437,7 +422,7 @@ async def _charge_credits( ) used_new = (lic.get("credits_used") or 0) + credits_consumed - available = (lic.get("credits_total") or 0) + (lic.get("credits_carried") or 0) + available = lic.get("credits_total") or 0 logger.info( f"charge_usage_to_tenant[{key}] Tenant {tenant_id}: "