Feature: Abo-Kündigung + Ablaufdatum + Dog-Auswahl nach Downgrade (SW by-v945)

This commit is contained in:
rene 2026-05-14 13:56:55 +02:00
parent 44b3fba191
commit 3b666c545f
10 changed files with 341 additions and 11 deletions

View file

@ -188,8 +188,15 @@ def start():
replace_existing=True,
misfire_grace_time=3600,
)
_scheduler.add_job(
_job_subscription_check,
CronTrigger(hour=3, minute=0),
id="subscription_check",
replace_existing=True,
misfire_grace_time=3600,
)
_scheduler.start()
logger.info("Scheduler gestartet — Health-Reminder 08:00, Giftköder-Archiv 03:00, Wetter-Alert 07:30, Meilenstein-Check 00:05, Event-Import So 02:00, Rassen-Seed monatlich 1. des Monats, Status-Report täglich 06:00, Moderation-Overdue 12:00, Quartalsbericht 1. Feb/Mai/Aug/Nov 07:00, Streak-Reminder 19:00, Rückruf-Check 08:00, Goldene-Gassi-Stunde 07:00, Jahrestags-Erinnerungen 09:00, Monatlicher-Rückblick 1. des Monats 10:00, Foto-Challenge Mo 08:00. OSM-Cache: on-demand (kein Prewarm).")
logger.info("Scheduler gestartet — Health-Reminder 08:00, Giftköder-Archiv 03:00, Wetter-Alert 07:30, Meilenstein-Check 00:05, Event-Import So 02:00, Rassen-Seed monatlich 1. des Monats, Status-Report täglich 06:00, Moderation-Overdue 12:00, Quartalsbericht 1. Feb/Mai/Aug/Nov 07:00, Streak-Reminder 19:00, Rückruf-Check 08:00, Goldene-Gassi-Stunde 07:00, Jahrestags-Erinnerungen 09:00, Monatlicher-Rückblick 1. des Monats 10:00, Foto-Challenge Mo 08:00, Abo-Check 03:00. OSM-Cache: on-demand (kein Prewarm).")
def stop():
@ -197,6 +204,82 @@ def stop():
logger.info("Scheduler gestoppt.")
# ------------------------------------------------------------------
# JOB: Abo-Ablauf prüfen (täglich 03:00)
# ------------------------------------------------------------------
async def _job_subscription_check():
"""Abgelaufene Abos auf Standard setzen; Warnmails 30 und 7 Tage vorher."""
from database import db as _db
from mailer import send_email, email_html
import html as _html
now = datetime.now(_TZ)
today = now.date()
with _db() as conn:
users = conn.execute(
"""SELECT id, name, email, subscription_tier, subscription_expires_at
FROM users
WHERE subscription_tier IN ('pro','breeder')
AND subscription_expires_at IS NOT NULL"""
).fetchall()
for u in users:
try:
expires = datetime.fromisoformat(u["subscription_expires_at"].replace('Z', '+00:00')).date()
days_left = (expires - today).days
tier_label = {"pro": "Ban Yaro Pro", "breeder": "Züchter"}.get(u["subscription_tier"], u["subscription_tier"])
# Abgelaufen → auf Standard setzen
if days_left < 0:
with _db() as conn:
dog_count = conn.execute(
"SELECT COUNT(*) FROM dogs WHERE user_id=? AND is_active!=0", (u["id"],)
).fetchone()[0]
needs_sel = 1 if dog_count > 1 else 0
conn.execute(
"""UPDATE users SET subscription_tier='standard',
needs_dog_selection=? WHERE id=?""",
(needs_sel, u["id"])
)
logger.info(f"Abo abgelaufen: {u['email']} → standard (needs_dog_selection={needs_sel})")
body = f"""
<p>Hallo {_html.escape(u['name'])},</p>
<p>dein <strong>{tier_label}</strong>-Abo ist heute abgelaufen.
Dein Account wurde auf den kostenlosen Tarif gesetzt.</p>
<p>Deine Daten sind vollständig erhalten. Du kannst jederzeit wieder upgraden.</p>"""
if needs_sel:
body += "<p><strong>Wichtig:</strong> Du hattest mehrere Hunde. Öffne die App und wähle deinen Haupthund aus — alle anderen Profile bleiben gespeichert.</p>"
html = email_html(body, cta_url="https://banyaro.app", cta_label="Ban Yaro öffnen")
await send_email(u["email"], f"Dein {tier_label}-Abo ist abgelaufen", html,
f"Hallo {u['name']},\ndein {tier_label}-Abo ist abgelaufen. Daten bleiben erhalten.")
# 30 Tage Warnung
elif days_left == 30:
body = f"""
<p>Hallo {_html.escape(u['name'])},</p>
<p>dein <strong>{tier_label}</strong>-Abo läuft in <strong>30 Tagen</strong>
(am {expires.strftime('%d.%m.%Y')}) ab.</p>
<p>Um weiterzumachen, überweise einfach den Jahresbetrag und schreib uns kurz
wir verlängern deinen Zugang sofort.</p>"""
html = email_html(body, cta_url="https://banyaro.app", cta_label="Abo verlängern")
await send_email(u["email"], f"Dein {tier_label}-Abo läuft in 30 Tagen ab", html,
f"Hallo {u['name']},\ndein {tier_label}-Abo läuft in 30 Tagen ab ({expires}).")
# 7 Tage Warnung
elif days_left == 7:
body = f"""
<p>Hallo {_html.escape(u['name'])},</p>
<p>dein <strong>{tier_label}</strong>-Abo läuft in <strong>7 Tagen</strong>
(am {expires.strftime('%d.%m.%Y')}) ab.</p>
<p>Jetzt verlängern und nahtlos weitermachen!</p>"""
html = email_html(body, cta_url="https://banyaro.app", cta_label="Abo verlängern")
await send_email(u["email"], f"Nur noch 7 Tage — {tier_label}-Abo läuft ab", html,
f"Hallo {u['name']},\nnur noch 7 Tage für dein {tier_label}-Abo.")
except Exception as e:
logger.warning(f"subscription_check Fehler für {u['email']}: {e}")
# ------------------------------------------------------------------
# JOB: Gesundheits-Erinnerungen
# ------------------------------------------------------------------