Files
2026-10-14 13:34:37 +02:00

319 lines
12 KiB
Python

from datetime import date, datetime, timedelta
from decimal import Decimal, InvalidOperation
from functools import wraps
from flask import Blueprint, render_template, request, redirect, url_for, flash, session, abort
from tenancy import huidige_school, per_school
from blueprints.auth import login_required
from services import personeel_service as P
personeel_bp = Blueprint('personeel', __name__)
_tables_ready = set() # scholen waarvoor de tabellen al gecontroleerd zijn
def _ensure_tables():
if huidige_school().code not in _tables_ready:
try:
P.ensure_tables()
_tables_ready.add(huidige_school().code)
except Exception as e:
flash(f"Kon de personeelstabellen niet aanmaken: {e}", "error")
def _me():
return (session.get('user') or '').lower(), session.get('naam') or session.get('user')
def rol_vereist(rol):
def deco(f):
@wraps(f)
def wrapped(*a, **kw):
if rol not in P.rollen(session):
flash("Je hebt geen toegang tot deze pagina.", "error")
return redirect(url_for('personeel.mijn'))
return f(*a, **kw)
return wrapped
return deco
@personeel_bp.context_processor
def _ctx():
r = P.rollen(session) if session.get('auth') else set()
return {'p_rollen': r, 'SOORTEN': P.SOORTEN, 'STATUS': P.STATUS, 'DAGDELEN': P.DAGDELEN,
'p_te_beoordelen': P.aantal_te_beoordelen() if 'directie' in r else 0}
def _link(aid):
return url_for('personeel.detail', aid=aid, _external=True)
def _mag_zien(item):
me, _ = _me()
r = P.rollen(session)
return (item['Email'] == me or 'secretariaat' in r
or ('directie' in r and P.SOORTEN[item['Soort']]['goedkeuring']))
def _laad(aid):
item = P.get(aid)
if not item:
abort(404)
if not _mag_zien(item):
flash("Je hebt geen toegang tot deze aanvraag.", "error")
return None
return item
#---------------------------------------------
# Mijn meldingen en aanvragen
#---------------------------------------------
@personeel_bp.route('/', methods=['GET'])
@login_required
def mijn():
_ensure_tables()
me, _ = _me()
try:
items = P.mijn(me)
except Exception as e:
items = []
flash(f"Kon je aanvragen niet ophalen: {e}", "error")
return render_template('personeel_mijn.html', items=items, vandaag=date.today())
#---------------------------------------------
# Nieuwe melding / aanvraag
#---------------------------------------------
@personeel_bp.route('/nieuw', methods=['GET', 'POST'])
@login_required
def nieuw():
_ensure_tables()
soort = request.values.get('soort', 'afwezigheid')
if soort not in P.SOORTEN:
soort = 'afwezigheid'
me, mijn_naam = _me()
is_secr = 'secretariaat' in P.rollen(session)
form = request.form
if request.method == 'POST':
fouten = []
try:
van = datetime.strptime(form.get('van', ''), '%Y-%m-%d').date()
tot = datetime.strptime(form.get('tot') or form.get('van', ''), '%Y-%m-%d').date()
if tot < van:
fouten.append("De einddatum ligt voor de begindatum.")
except ValueError:
van = tot = None
fouten.append("Kies een geldige datum.")
titel = (form.get('titel') or '').strip()
if soort == 'afwezigheid' and not titel:
titel = 'Afwezig'
if not titel:
fouten.append("Geef een titel op.")
dagdeel = form.get('dagdeel', 'dag')
if dagdeel not in P.DAGDELEN:
dagdeel = 'dag'
lesuren = (form.get('lesuren') or '').strip()
if dagdeel == 'uren' and not lesuren:
fouten.append("Vul in welke lesuren het gaat (bv. 3-5).")
klassen = form.getlist('klassen')
extra = [k.strip() for k in (form.get('klassen_extra') or '').split(',') if k.strip()]
klassen = list(dict.fromkeys(klassen + extra))
if soort == 'uitstap' and not klassen:
fouten.append("Kies welke klassen mee op uitstap gaan.")
kost = None
if (form.get('kost') or '').strip():
try:
kost = Decimal(form['kost'].replace(',', '.').replace('€', '').strip())
except InvalidOperation:
fouten.append("De kost is geen geldig bedrag.")
# Secretariaat kan melden namens een leerkracht
email, naam = me, mijn_naam
namens = (form.get('namens') or '').strip().lower()
if namens and namens != me:
if not is_secr:
fouten.append("Je kan enkel voor jezelf een melding maken.")
else:
email, naam = namens, namens
try:
from services.google_service import GoogleAdminService
u = GoogleAdminService().get_user_info(namens)
if not u:
fouten.append(f"{namens} bestaat niet in Google Workspace.")
else:
email = u['primaryEmail'].lower()
naam = (u.get('name') or {}).get('fullName') or email
except Exception:
pass
if fouten:
for f in fouten:
flash(f, "error")
else:
data = dict(soort=soort, email=email, naam=naam, titel=titel[:255],
omschrijving=(form.get('omschrijving') or '').strip() or None,
locatie=(form.get('locatie') or '').strip()[:255] or None,
van=van, tot=tot, dagdeel=dagdeel, lesuren=lesuren[:50] or None,
klassen=klassen, begeleiders=(form.get('begeleiders') or '').strip() or None, kost=kost)
aid = P.aanmaken(data, me, mijn_naam)
P.mail_nieuw(P.get(aid), _link(aid))
if P.SOORTEN[soort]['goedkeuring']:
flash("Je aanvraag is ingediend. De directie krijgt een melding; je ontvangt een mail zodra er een beslissing is.", "success")
else:
flash("De afwezigheid is gemeld aan het secretariaat.", "success")
return redirect(url_for('personeel.detail', aid=aid))
return render_template('personeel_nieuw.html', soort=soort, form=form, klassen=P.klassen(),
is_secr=is_secr, vandaag=date.today())
#---------------------------------------------
# Detail + acties
#---------------------------------------------
@personeel_bp.route('/<int:aid>', methods=['GET'])
@login_required
def detail(aid):
_ensure_tables()
item = _laad(aid)
if item is None:
return redirect(url_for('personeel.mijn'))
me, _ = _me()
return render_template('personeel_detail.html', item=item, reacties=P.reacties(aid),
is_eigenaar=item['Email'] == me, vandaag=date.today())
@personeel_bp.route('/<int:aid>/beslissing', methods=['POST'])
@login_required
@rol_vereist('directie')
def beslissing(aid):
item = _laad(aid)
if item is None:
return redirect(url_for('personeel.mijn'))
actie = request.form.get('actie')
tekst = (request.form.get('tekst') or '').strip()
if actie not in ('goedgekeurd', 'geweigerd', 'vraag'):
abort(400)
if not P.SOORTEN[item['Soort']]['goedkeuring'] or item['Status'] not in ('ingediend', 'vraag'):
flash("Over deze aanvraag kan geen beslissing (meer) genomen worden.", "error")
return redirect(url_for('personeel.detail', aid=aid))
if actie in ('geweigerd', 'vraag') and not tekst:
flash("Geef wat uitleg mee bij een weigering of vraag.", "error")
return redirect(url_for('personeel.detail', aid=aid))
me, naam = _me()
P.wijzig_status(aid, actie, me, naam, actie, tekst or None)
P.mail_beslissing(P.get(aid), actie, tekst, _link(aid))
flash({'goedgekeurd': "Aanvraag goedgekeurd.", 'geweigerd': "Aanvraag geweigerd.",
'vraag': "Je vraag is naar de leerkracht gestuurd."}[actie], "success")
nxt = request.form.get('volgende')
return redirect(url_for('personeel.beoordelen') if nxt else url_for('personeel.detail', aid=aid))
@personeel_bp.route('/<int:aid>/antwoord', methods=['POST'])
@login_required
def antwoord(aid):
item = _laad(aid)
if item is None:
return redirect(url_for('personeel.mijn'))
me, naam = _me()
tekst = (request.form.get('tekst') or '').strip()
if item['Email'] != me or item['Status'] != 'vraag' or not tekst:
flash("Antwoorden kan enkel op je eigen aanvraag met een openstaande vraag.", "error")
return redirect(url_for('personeel.detail', aid=aid))
P.wijzig_status(aid, 'ingediend', me, naam, 'antwoord', tekst)
P.mail_antwoord(P.get(aid), tekst, _link(aid))
flash("Je antwoord is verstuurd; de aanvraag wacht opnieuw op de directie.", "success")
return redirect(url_for('personeel.detail', aid=aid))
@personeel_bp.route('/<int:aid>/commentaar', methods=['POST'])
@login_required
def commentaar(aid):
item = _laad(aid)
if item is None:
return redirect(url_for('personeel.mijn'))
tekst = (request.form.get('tekst') or '').strip()
if tekst:
me, naam = _me()
P.commentaar(aid, me, naam, tekst)
return redirect(url_for('personeel.detail', aid=aid))
@personeel_bp.route('/<int:aid>/annuleren', methods=['POST'])
@login_required
def annuleren(aid):
item = _laad(aid)
if item is None:
return redirect(url_for('personeel.mijn'))
me, naam = _me()
if not (item['Email'] == me or 'secretariaat' in P.rollen(session)):
flash("Je kan deze aanvraag niet annuleren.", "error")
elif item['Status'] in ('geannuleerd', 'geweigerd'):
flash("Deze aanvraag is al afgesloten.", "error")
else:
P.mail_geannuleerd(item, _link(aid))
P.wijzig_status(aid, 'geannuleerd', me, naam, 'geannuleerd', (request.form.get('tekst') or '').strip() or None)
flash("Geannuleerd.", "success")
return redirect(url_for('personeel.detail', aid=aid))
@personeel_bp.route('/<int:aid>/vervanging', methods=['POST'])
@login_required
@rol_vereist('secretariaat')
def vervanging(aid):
item = _laad(aid)
if item is None:
return redirect(url_for('personeel.mijn'))
me, naam = _me()
P.vervanging(aid, request.form.get('ok') == '1', request.form.get('info'), me, naam)
terug = request.form.get('terug')
return redirect(terug if terug and terug.startswith('/') else url_for('personeel.detail', aid=aid))
#---------------------------------------------
# Directie: te beoordelen
#---------------------------------------------
@personeel_bp.route('/beoordelen', methods=['GET'])
@login_required
@rol_vereist('directie')
def beoordelen():
_ensure_tables()
return render_template('personeel_beoordelen.html', open_items=P.te_beoordelen(), recent=P.recent_beslist())
#---------------------------------------------
# Secretariaat: dagoverzicht
#---------------------------------------------
@personeel_bp.route('/dagoverzicht', methods=['GET'])
@login_required
@rol_vereist('secretariaat')
def dagoverzicht():
_ensure_tables()
try:
dag = datetime.strptime(request.args.get('datum', ''), '%Y-%m-%d').date()
except ValueError:
dag = date.today()
weergave = request.args.get('weergave', 'dag')
if weergave == 'week':
van = dag - timedelta(days=dag.weekday())
tot = van + timedelta(days=4)
stap = timedelta(days=7)
else:
weergave = 'dag'
if dag.weekday() >= 5: # weekend → volgende maandag
dag = dag + timedelta(days=7 - dag.weekday())
van = tot = dag
stap = timedelta(days=1)
vorige, volgende = van - stap, van + stap
if weergave == 'dag':
while vorige.weekday() >= 5:
vorige -= timedelta(days=1)
while volgende.weekday() >= 5:
volgende += timedelta(days=1)
return render_template('personeel_dagoverzicht.html', dagen=P.dagoverzicht(van, tot), weergave=weergave,
dag=dag, van=van, tot=tot, vorige=vorige, volgende=volgende, vandaag=date.today())