platform-core/modules/serviceCenter/services/serviceBilling/billingExhaustedNotify.py
ValueOn AG 4a60086c80
Some checks failed
Deploy Plattform-Core (Int) / test (push) Failing after 15s
Deploy Plattform-Core (Int) / deploy (push) Has been skipped
cp adapted to 2026 poweron
2026-06-09 09:53:31 +02:00

79 lines
2.8 KiB
Python

# Copyright (c) 2026 PowerOn AG
# 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, Optional
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,
frontendUrl: Optional[str] = None,
) -> 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.
frontendUrl: Optional frontend base URL for /billing/admin link (omit to send email without CTA).
"""
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.system.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.",
],
includeBillingAdminLink=True,
billingAdminButtonText="Guthaben aufladen",
frontendUrl=frontendUrl,
)
if sent > 0:
_poolExhaustedEmailLastSent[mandateId] = now
except Exception as e:
logger.error("maybeEmailMandatePoolExhausted failed: %s", e, exc_info=True)