06/05 Optimize app: add email notification
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
"""
|
||||
Migration: add budget alert columns.
|
||||
|
||||
Run once after deploying the budget alerts feature:
|
||||
python scripts/add_budget_alert_columns.py
|
||||
|
||||
Adds to `budgets`:
|
||||
alert_sent_80 BOOLEAN NOT NULL DEFAULT FALSE
|
||||
alert_sent_100 BOOLEAN NOT NULL DEFAULT FALSE
|
||||
|
||||
Adds to `users`:
|
||||
budget_alerts_enabled BOOLEAN NOT NULL DEFAULT FALSE
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from app import create_app
|
||||
from app.extensions import db
|
||||
|
||||
app = create_app()
|
||||
|
||||
with app.app_context():
|
||||
with db.engine.connect() as conn:
|
||||
# budgets table
|
||||
for col, default in [('alert_sent_80', '0'), ('alert_sent_100', '0')]:
|
||||
try:
|
||||
conn.execute(db.text(
|
||||
f"ALTER TABLE budgets ADD COLUMN {col} TINYINT(1) NOT NULL DEFAULT {default}"
|
||||
))
|
||||
print(f"Added budgets.{col}")
|
||||
except Exception as e:
|
||||
if 'Duplicate column' in str(e) or '1060' in str(e):
|
||||
print(f"budgets.{col} already exists — skipping")
|
||||
else:
|
||||
raise
|
||||
|
||||
# users table
|
||||
try:
|
||||
conn.execute(db.text(
|
||||
"ALTER TABLE users ADD COLUMN budget_alerts_enabled TINYINT(1) NOT NULL DEFAULT 0"
|
||||
))
|
||||
print("Added users.budget_alerts_enabled")
|
||||
except Exception as e:
|
||||
if 'Duplicate column' in str(e) or '1060' in str(e):
|
||||
print("users.budget_alerts_enabled already exists — skipping")
|
||||
else:
|
||||
raise
|
||||
|
||||
conn.commit()
|
||||
|
||||
print("Done.")
|
||||
@@ -0,0 +1,64 @@
|
||||
"""
|
||||
Daily cron safety-net: check all budgets for the current month and send any
|
||||
unsent 80% / 100% alert emails.
|
||||
|
||||
Run daily (e.g. 9 AM) so alerts fire even if the triggering transaction was
|
||||
imported via a provider sync rather than entered manually through the UI.
|
||||
|
||||
python scripts/check_budget_alerts.py
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from datetime import date
|
||||
from app import create_app
|
||||
from app.extensions import db
|
||||
|
||||
app = create_app()
|
||||
|
||||
with app.app_context():
|
||||
from app.models.user import User
|
||||
from app.models.budget import Budget
|
||||
from app.services.alert_service import (
|
||||
_smtp_configured, _send_single_alert_email
|
||||
)
|
||||
from app.services.budget_service import get_month_spending
|
||||
|
||||
user = User.query.first()
|
||||
if not user or not user.budget_alerts_enabled:
|
||||
print("Budget alerts disabled — nothing to do.")
|
||||
sys.exit(0)
|
||||
|
||||
if not _smtp_configured():
|
||||
print("SMTP not configured — nothing to do.")
|
||||
sys.exit(0)
|
||||
|
||||
month_str = date.today().strftime('%Y-%m')
|
||||
symbol = user.currency_symbol or '$'
|
||||
budgets = Budget.query.filter_by(month=month_str).all()
|
||||
sent = 0
|
||||
|
||||
for b in budgets:
|
||||
limit = float(b.limit_amount) + float(b.rollover_amount or 0)
|
||||
if limit <= 0:
|
||||
continue
|
||||
spent = get_month_spending(b.category_id, month_str)
|
||||
pct = spent / limit * 100
|
||||
cat = b.category
|
||||
name = cat.name if cat else 'Unknown'
|
||||
|
||||
if pct >= 100 and not b.alert_sent_100:
|
||||
_send_single_alert_email(name, spent, limit, pct, symbol, threshold=100)
|
||||
b.alert_sent_100 = True
|
||||
sent += 1
|
||||
elif pct >= 80 and not b.alert_sent_80:
|
||||
_send_single_alert_email(name, spent, limit, pct, symbol, threshold=80)
|
||||
b.alert_sent_80 = True
|
||||
sent += 1
|
||||
|
||||
if sent:
|
||||
db.session.commit()
|
||||
|
||||
print(f"Budget alert check complete — {sent} email(s) sent.")
|
||||
Reference in New Issue
Block a user