gateway/modules/serviceCenter/services/serviceBilling/billingExhaustedNotify.py
2026-03-22 17:23:54 +01:00

74 lines
2.5 KiB
Python

# Copyright (c) 2025 Patrick Motsch
# All rights reserved.
"""
When the shared mandate pool (PREPAY_MANDATE) is exhausted, notify mandate admins.
Uses the central notifyMandateAdmins() function for recipient resolution and delivery.
Emails are throttled per mandate to avoid spam (one notification per cooldown window).
"""
from __future__ import annotations
import logging
import time
from typing import Dict
logger = logging.getLogger(__name__)
# mandate_id -> unix timestamp of last pool-exhausted notification email
_poolExhaustedEmailLastSent: Dict[str, float] = {}
_DEFAULT_COOLDOWN_SEC = 3600
def maybeEmailMandatePoolExhausted(
mandateId: str,
triggeringUserId: str,
triggeringUserLabel: str,
currentBalance: float,
requiredAmount: float,
cooldownSec: float = _DEFAULT_COOLDOWN_SEC,
) -> None:
"""
Send one notification per mandate per cooldown window when the pool is exhausted.
Args:
mandateId: Mandate whose pool is empty.
triggeringUserId: User who hit the block.
triggeringUserLabel: Display (e.g. email or username).
currentBalance: Pool balance (CHF).
requiredAmount: Minimum required (CHF).
cooldownSec: Minimum seconds between emails for this mandate.
"""
if not mandateId:
return
now = time.time()
last = _poolExhaustedEmailLastSent.get(mandateId, 0.0)
if last and (now - last) < cooldownSec:
logger.debug(
"Skip mandate pool exhausted email (cooldown): mandate=%s last=%.0fs ago",
mandateId,
now - last,
)
return
try:
from modules.shared.notifyMandateAdmins import notifyMandateAdmins
sent = notifyMandateAdmins(
mandateId,
"[PowerOn] Mandanten-Budget aufgebraucht",
"Budget aufgebraucht",
[
"Das gemeinsame Guthaben (Prepaid-Pool) für diesen Mandanten ist nicht mehr ausreichend.",
f"Aktuelles Guthaben: CHF {currentBalance:.2f}\n"
f"Benötigt (mindestens): CHF {requiredAmount:.2f}",
f"Ausgelöst durch: {triggeringUserLabel}",
"Bitte laden Sie das Mandats-Guthaben in der Billing-Verwaltung auf, "
"damit Benutzer wieder AI-Funktionen nutzen können.",
],
)
if sent > 0:
_poolExhaustedEmailLastSent[mandateId] = now
except Exception as e:
logger.error("maybeEmailMandatePoolExhausted failed: %s", e, exc_info=True)