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:
57
migrations/2026-07-25_credits_period.py
Normale Datei
57
migrations/2026-07-25_credits_period.py
Normale Datei
@@ -0,0 +1,57 @@
|
||||
"""Migration 2026-07-25: Credits-Perioden-Spalten in licenses.
|
||||
|
||||
Legt die fünf Spalten des Credits-Monatskontingents an, exakt dieselben
|
||||
Statements wie die Monitor-Migration (AegisSight-Monitor, src/database.py,
|
||||
Block "Credits-Periode"). Idempotent, ein zweiter Lauf ist ein No-op.
|
||||
|
||||
Hintergrund: Der Monitor legt diese Spalten beim Start selbst an. Die
|
||||
Portal-Staging-Umgebung nutzt aber eine EIGENE Datenbank, auf der kein
|
||||
Monitor läuft, deshalb braucht sie dieses Skript. Auf der Live-DB ist es
|
||||
nur ein Fallback für den Fall, dass das Portal vor dem Monitor deployt wird.
|
||||
|
||||
Ausführung:
|
||||
DB_PATH=/home/claude-dev/AegisSight-Monitor-staging/data/osint.db python3 migrations/2026-07-25_credits_period.py
|
||||
DB_PATH=/home/claude-dev/osint-data/osint.db python3 migrations/2026-07-25_credits_period.py
|
||||
"""
|
||||
import os
|
||||
import sqlite3
|
||||
import sys
|
||||
|
||||
# Spaltenname -> ALTER-Statement, wortgleich mit der Monitor-Migration
|
||||
COLUMNS = {
|
||||
"credits_period": "ALTER TABLE licenses ADD COLUMN credits_period TEXT DEFAULT 'monthly'",
|
||||
"credits_period_start": "ALTER TABLE licenses ADD COLUMN credits_period_start TEXT",
|
||||
"credits_rollover": "ALTER TABLE licenses ADD COLUMN credits_rollover INTEGER DEFAULT 0",
|
||||
"credits_carried": "ALTER TABLE licenses ADD COLUMN credits_carried REAL DEFAULT 0",
|
||||
"budget_warning_sent": "ALTER TABLE licenses ADD COLUMN budget_warning_sent INTEGER DEFAULT 0",
|
||||
}
|
||||
|
||||
|
||||
def main(db_path: str) -> int:
|
||||
if not os.path.exists(db_path):
|
||||
print(f"FEHLER: DB nicht gefunden: {db_path}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
conn = sqlite3.connect(db_path, timeout=60)
|
||||
conn.execute("PRAGMA busy_timeout = 60000")
|
||||
conn.execute("PRAGMA journal_mode = WAL")
|
||||
|
||||
print(f"Migration auf {db_path}")
|
||||
|
||||
cols = [c[1] for c in conn.execute("PRAGMA table_info(licenses)")]
|
||||
for name, stmt in COLUMNS.items():
|
||||
if name not in cols:
|
||||
conn.execute(stmt)
|
||||
print(f" + licenses.{name} Spalte hinzugefügt")
|
||||
else:
|
||||
print(f" = licenses.{name} war bereits da")
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
print("Migration abgeschlossen.")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
db_path = os.environ.get("DB_PATH", "/home/claude-dev/osint-data/osint.db")
|
||||
sys.exit(main(db_path))
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren