89 lines
3.1 KiB
Python
89 lines
3.1 KiB
Python
"""Routines: terugkerende taken van de CRM (e-mail ophalen, nieuwsbrieven versturen, …).
|
|
|
|
Een routinetype is een functie met @register; een routine (tabel jobs) is een ingestelde instantie van een type
|
|
met een interval en eigen instellingen (config, JSON). Er kunnen meerdere routines van hetzelfde type zijn, bijv.
|
|
twee keer e-mail ophalen met andere mailboxen en een ander interval.
|
|
|
|
Uitvoeren: `python app.py scheduler` (blijft draaien, kijkt elke 30 s; de Docker-container crm-routines) of
|
|
`python app.py tick` (één keer, voor cron elke minuut). Een routine draait nooit twee keer tegelijk (running_since
|
|
werkt als slot; na STALE_MINUTES geldt een slot als achtergebleven na een crash).
|
|
|
|
Een nieuw type toevoegen, in app.py:
|
|
|
|
@jobs.register('rapport', 'Weekrapport', 'Mailt elke week de nieuwe leads.', interval=7 * 24 * 60,
|
|
fields=[jobs.Field('to', 'Naar', 'email')])
|
|
def job_rapport(job, config):
|
|
... # gewone app-context: db(), q(), notify(), mail…
|
|
return '12 leads gemeld' # komt in het logboek; een exception = status fout
|
|
|
|
Een standaardroutine die er altijd moet zijn, krijgt `default=True`: die maakt app.py bij het starten één keer
|
|
aan (actief, met het standaardinterval).
|
|
"""
|
|
from dataclasses import dataclass, field
|
|
|
|
STALE_MINUTES = 60
|
|
KEEP_RUNS = 200 # logregels per routine
|
|
|
|
UNITS = [(1, 'minuten'), (60, 'uur'), (24 * 60, 'dagen')]
|
|
|
|
|
|
@dataclass
|
|
class Field:
|
|
"""Instelling van een routinetype. kind: text, number, email, bool, mailboxes (meerkeuze uit de mailboxen)."""
|
|
key: str
|
|
label: str
|
|
kind: str = 'text'
|
|
default: object = ''
|
|
hint: str = ''
|
|
|
|
|
|
@dataclass
|
|
class JobType:
|
|
key: str
|
|
label: str
|
|
description: str
|
|
run: object
|
|
interval: int = 60 # standaardinterval in minuten
|
|
fields: list = field(default_factory=list)
|
|
default: bool = False # bij het starten één keer aanmaken
|
|
|
|
|
|
TYPES = {}
|
|
|
|
|
|
def register(key, label, description='', interval=60, fields=(), default=False):
|
|
def wrap(fn):
|
|
TYPES[key] = JobType(key, label, description, fn, interval, list(fields), default)
|
|
return fn
|
|
return wrap
|
|
|
|
|
|
def defaults(kind):
|
|
return {f.key: f.default for f in TYPES[kind].fields}
|
|
|
|
|
|
def clean_config(kind, form):
|
|
"""Instellingen uit een formulier (request.form) volgens de velden van het type."""
|
|
out = {}
|
|
for f in TYPES[kind].fields:
|
|
if f.kind == 'bool':
|
|
out[f.key] = bool(form.get(f.key))
|
|
elif f.kind == 'number':
|
|
try:
|
|
out[f.key] = int(form.get(f.key) or f.default or 0)
|
|
except ValueError:
|
|
out[f.key] = f.default
|
|
elif f.kind == 'mailboxes':
|
|
out[f.key] = [int(v) for v in form.getlist(f.key) if v.isdigit()]
|
|
else:
|
|
out[f.key] = (form.get(f.key) or '').strip()
|
|
return out
|
|
|
|
|
|
def interval_text(minutes):
|
|
for size, unit in reversed(UNITS):
|
|
if minutes % size == 0 and minutes >= size:
|
|
n = minutes // size
|
|
return {'minuten': f'{n} min', 'uur': f'{n} uur', 'dagen': f'{n} dag' + ('en' if n != 1 else '')}[unit]
|
|
return f'{minutes} min'
|