#!/usr/bin/env python3 """ atlas_reminders.py — Operator burden-remover. Fires based on date-pattern matching: - 1e van de maand: Inkomsten xlsx update reminder - 14d + 3d vóór BTW deadlines: aangifte reminder - Friday-end-of-month: personal financial summary - Quarterly mid-last-month: plan revision reminder - Annual prep windows Each reminder is an email to info@your-domain.tld via TransIP SMTP. Schedule: Atlas-Reminders-Daily at 07:45 — checks all conditions, fires what matches. """ import sys import smtplib import json from pathlib import Path from datetime import date, datetime, timedelta from email.mime.text import MIMEText SMTP_HOST = "smtp.transip.email" SMTP_PORT = 587 SMTP_USER = "your@your-domain.tld" SMTP_PASS = "" MAIL_FROM = "your@your-domain.tld" MAIL_TO = "info@your-domain.tld" LOG = Path(r"C:\Users\Gebruiker\.config\atlas\atlas_reminders.log") def log(msg): LOG.parent.mkdir(parents=True, exist_ok=True) with open(LOG, "a", encoding="utf-8") as f: f.write(f"[{datetime.now().isoformat(timespec='seconds')}] {msg}\n") def send_html(subject, html_body): msg = MIMEText(html_body, "html", "utf-8") msg["Subject"] = subject msg["From"] = MAIL_FROM msg["To"] = MAIL_TO try: with smtplib.SMTP(SMTP_HOST, SMTP_PORT, timeout=30) as s: s.starttls() s.login(SMTP_USER, SMTP_PASS) s.send_message(msg) log(f"sent: {subject}") return True except Exception as e: log(f"send FAIL: {e}") return False def reminder_card(title, urgency, body_html): color = {"high": "#dc2626", "med": "#f59e0b", "low": "#3b82f6"}[urgency] return f"""
{urgency} priority reminder

{title}

{body_html}
""" def check_monthly_inkomsten(today): """1e van de maand: update Inkomsten xlsx voor afgesloten maand.""" if today.day not in (1, 2, 5): return None last_month = today.replace(day=1) - timedelta(days=1) body = f"""

Vandaag ({today.strftime('%d %B')}) is een check-dag voor de Inkomsten uit onderneming 2026.xlsx.

Actie: open Knab → download afschrift {last_month.strftime('%B %Y')} → folder 07_Finance/2026/03_bank/ → en update de Betaalcontrole sheet met:

Atlas Command BRIEF tab leest de xlsx automatisch (06:30) → refresht je live YTD/MRR/concentration cijfers.

Reminder doel: voorkom "xlsx niet bijgewerkt sinds vorige maand" patroon (gebeurde mei 2026).

""" return ("📊 Monthly: Inkomsten xlsx update", "med", reminder_card("Update Inkomsten xlsx voor afgelopen maand", "med", body)) def check_btw_deadline(today): """14d en 3d vóór BTW kwartaal-deadlines.""" btw_deadlines = [ date(today.year, 4, 30), # Q1 date(today.year, 7, 31), # Q2 date(today.year, 10, 31), # Q3 date(today.year + 1, 1, 31), # Q4 ] quarters = ["Q1", "Q2", "Q3", "Q4"] for d, q in zip(btw_deadlines, quarters): delta = (d - today).days if delta in (14, 3): urgency = "high" if delta == 3 else "med" body = f"""

BTW aangifte {q} {today.year} deadline: {d.strftime('%d %B %Y')} — over {delta} dagen.

Actie:

  1. Open Odoo (:8069) → Boekhouding → BTW
  2. Genereer aangifte-overzicht voor {q}
  3. Controleer met Knab + ING + Revolut totalen
  4. Login Belastingdienst (Mijn Belastingdienst Zakelijk) → aangifte indienen
  5. BTW-bedrag overmaken vanuit Knab vóór {d.strftime('%d %B')}

Boete bij te late aangifte: vanaf €68. Bij te late betaling: 4% rente op jaarbasis.

""" return (f"⏳ BTW {q} deadline {delta}d - actie nodig", urgency, reminder_card(f"BTW aangifte {q} — {delta} dagen", urgency, body)) return None def check_quarterly_review(today): """15e van laatste maand van kwartaal: revise Q+1 plan.""" quarterly_review_days = [ date(today.year, 3, 15), date(today.year, 6, 15), date(today.year, 9, 15), date(today.year, 12, 15), ] if today not in quarterly_review_days: return None quarter = (today.month - 1) // 3 + 1 body = f"""

Mid-laatste-maand Q{quarter} — tijd voor Q{quarter+1 if quarter < 4 else 1} planning.

Acties:

Tip: gebruik [[06.5 - Master Cockpit & 90-day System]] als template.

""" return (f"🔁 Q{quarter+1 if quarter<4 else 1} planning tijd", "med", reminder_card(f"Quarterly review — Q{quarter+1 if quarter<4 else 1} plan opstellen", "med", body)) def check_holiday_booking(today): """Vrij van eerste juni-week: reminder om vakantie te boeken voor zomer.""" triggers = [date(today.year, 6, 5), date(today.year, 11, 25)] # June + late Nov if today not in triggers: return None body = f"""

Solo founder herinnering: plan je vakantie zelf in, anders gebeurt het niet.

Suggestie 2026 windows (zie [[06.5 - Master Cockpit & 90-day System]] §7):

Actie: blokkeer in Nextcloud Kalender + Apple Calendar. Stuur "out of office" voor Atlas Agency klanten. Werkplek-pakket levering = plan voor of na vakantie.

""" return ("🌴 Vakantie boeking reminder", "low", reminder_card("Plan je zomer/winter vakantie nu", "low", body)) def check_savings(today): """26e van maand: na vakantiegeld / na payday → savings reminder.""" if today.day != 26: return None body = """

Het is de 26e — payday is geweest (25e is NL salaris-norm).

Actie: open Knab → check 3 spaar-pots:

Niet sparen = niet halen. Auto-transfer instellen via Knab is one-time setup, daarna fire-and-forget.

Bron: [[06.5 - Master Cockpit]] §7 savings goals tabel.

""" return ("💰 Savings reminder (26e)", "low", reminder_card("Spaar-pots check", "low", body)) def check_annual_prep(today): """1 maart: IB aangifte voorjaars-window opent. 1 juni: deadline 1 mei is voorbij.""" if today not in (date(today.year, 3, 1), date(today.year, 4, 15)): return None body = f"""

IB aangifte 2025 voor {today.year} — deadline 1 mei {today.year}.

Acties:

  1. Stuur boekhouder Inkomsten + Kosten 2025 xlsx (al beschikbaar in 07_Finance)
  2. Verzamel BTW aangiften 2025 (4 kwartalen)
  3. Verzamel zakelijke kosten-bonnetjes 2025 (05_overig/ + 02_uitgaven/)
  4. Vraag KOR-check (kleine ondernemingsregeling) bij boekhouder
  5. Plan voorlopige IB-aanslag voor {today.year} samen met boekhouder
""" return ("📋 IB aangifte voorjaarsstart", "high", reminder_card("IB aangifte 2025 voorbereiden", "high", body)) def main(): today = date.today() log(f"reminders check for {today.isoformat()}") cards = [] for check in [check_monthly_inkomsten, check_btw_deadline, check_quarterly_review, check_holiday_booking, check_savings, check_annual_prep]: result = check(today) if result: subject, urgency, html = result cards.append((subject, urgency, html)) log(f" triggered: {subject}") if not cards: log(" no reminders today") return # Combine all cards into one email body_html = f"""

🔔 Atlas Reminders — {today.strftime('%A %d %B %Y')}

{''.join(c[2] for c in cards)}
Atlas Reminders · auto-fired by daily 07:45 schtask · disable via Disable-ScheduledTask Atlas-Reminders-Daily
""" subject = f"🔔 Atlas Reminders ({len(cards)}) — {today.strftime('%d %b')}" send_html(subject, body_html) if __name__ == "__main__": main()