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('/', 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('//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('//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('//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('//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('//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())