import csv import io from datetime import date, datetime from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify, Response from tenancy import huidige_school, per_school from blueprints.auth import login_required from rechten import recht_vereist from services.afwezigheden_service import AfwezighedenService, schooljaar_start afwezigheden_bp = Blueprint('afwezigheden', __name__) service = per_school(AfwezighedenService) _tables_ready = set() # scholen waarvoor de tabellen al gecontroleerd zijn def _ensure_tables(): if huidige_school().code not in _tables_ready: try: service.ensure_tables() _tables_ready.add(huidige_school().code) except Exception as e: flash(f"Kon de afwezigheidstabellen niet aanmaken: {e}", "error") def _filters(): """Leest de filters uit de URL, met het huidige schooljaar als standaard.""" def parse(v, standaard): try: return datetime.strptime(v, '%Y-%m-%d').date() if v else standaard except ValueError: return standaard van = parse(request.args.get('van'), schooljaar_start()) tot = parse(request.args.get('tot'), date.today()) if van > tot: van, tot = tot, van code = (request.args.get('code') or '').upper() or None if code and code not in service.codes: code = None try: minimum = max(1, int(request.args.get('min') or 1)) except ValueError: minimum = 1 return dict(van=van, tot=tot, code=code, zoek=(request.args.get('zoek') or '').strip(), minimum=minimum) #--------------------------------------------- # Overzicht #--------------------------------------------- @afwezigheden_bp.route('/', methods=['GET']) @login_required @recht_vereist('afwezigheden') def overzicht(): _ensure_tables() f = _filters() rows, fout, laatste = [], None, {'m': None, 'd': None} try: rows = service.overzicht(f['van'], f['tot'], f['code'], f['zoek'], f['minimum']) laatste = service.laatste_sync() except Exception as e: fout = f"Gegevens konden niet gelezen worden: {e}" totaal_halve_dagen = sum(r['Halve_dagen'] for r in rows) return render_template('afwezigheden.html', rows=rows, f=f, fout=fout, laatste=laatste, codes=service.codes, status=service.status, totaal_halve_dagen=totaal_halve_dagen) #--------------------------------------------- # Detail per leerling (JSON, voor het uitklappen in de tabel) #--------------------------------------------- @afwezigheden_bp.route('/leerling/.json', methods=['GET']) @login_required @recht_vereist('afwezigheden') def leerling(internnummer): f = _filters() dagen = service.detail(internnummer, f['van'], f['tot'], f['code']) return jsonify([{'datum': d['Datum'].strftime('%d-%m-%Y'), 'weekdag': ['ma', 'di', 'wo', 'do', 'vr', 'za', 'zo'][d['Datum'].weekday()], 'vm': d['VM'], 'nm': d['NM']} for d in dagen]) #--------------------------------------------- # Synchronisatie #--------------------------------------------- @afwezigheden_bp.route('/sync', methods=['POST']) @login_required @recht_vereist('afwezigheden', 'laatkomers') def sync(): if not huidige_school().heeft('SS_API_PLATFORM', 'SS_API_PW'): flash("Smartschool is nog niet ingesteld voor deze school: vul het platform (bv. school.smartschool.be) " "en het webservice-wachtwoord in via 🔌 Koppelingen van deze school.", "error") return redirect(url_for('afwezigheden.laatkomers' if request.form.get('terug') == 'laatkomers' else 'afwezigheden.overzicht')) _ensure_tables() volledig = request.form.get('volledig') == '1' if service.sync_in_background(volledig=volledig): flash("Synchronisatie met Smartschool gestart. Dit kan enkele minuten duren.", "info") else: flash("Er loopt al een synchronisatie.", "warning") if request.form.get('terug') == 'laatkomers': return redirect(url_for('afwezigheden.laatkomers', **{k: v for k, v in request.args.items()})) return redirect(url_for('afwezigheden.overzicht', **{k: v for k, v in request.args.items()})) @afwezigheden_bp.route('/status.json', methods=['GET']) @login_required @recht_vereist('afwezigheden', 'laatkomers') def status(): s = service.status return jsonify(bezig=s['bezig'], voortgang=s['voortgang'], totaal=s['totaal'], resultaat=s['laatste_resultaat'], fout=s['laatste_fout']) #--------------------------------------------- # Export #--------------------------------------------- @afwezigheden_bp.route('/export.csv', methods=['GET']) @login_required @recht_vereist('afwezigheden') def export_csv(): f = _filters() rows = service.overzicht(f['van'], f['tot'], f['code'], f['zoek'], f['minimum']) buf = io.StringIO() w = csv.writer(buf, delimiter=';') w.writerow(['Internnummer', 'Voornaam', 'Naam', 'Gebruikersnaam', 'Halve dagen', 'Dagen', 'Eerste', 'Laatste', 'Datums']) for r in rows: dagen = service.detail(r['Internnummer'], f['van'], f['tot'], f['code']) datums = ', '.join(d['Datum'].strftime('%d/%m') + ('' if d['VM'] and d['NM'] else (' VM' if d['VM'] else ' NM')) for d in sorted(dagen, key=lambda x: x['Datum'])) w.writerow([r['Internnummer'], r['Voornaam'] or '', r['Naam'] or '', r['Gebruikersnaam'] or '', r['Halve_dagen'], r['Dagen'], r['Eerste'].strftime('%d-%m-%Y'), r['Laatste'].strftime('%d-%m-%Y'), datums]) naam = f"afwezigheden_{f['code'] or '-'.join(service.codes)}_{f['van']:%Y%m%d}-{f['tot']:%Y%m%d}.csv" return Response('' + buf.getvalue(), mimetype='text/csv', headers={'Content-Disposition': f'attachment; filename={naam}'}) #--------------------------------------------- # Laatkomers #--------------------------------------------- @afwezigheden_bp.route('/laatkomers', methods=['GET']) @login_required @recht_vereist('laatkomers') def laatkomers(): """Alle leerlingen die dit schooljaar (of in de gekozen periode) te laat waren.""" _ensure_tables() f = _filters() rows, fout, laatste = [], None, {'m': None, 'd': None} try: rows = service.laatkomers(f['van'], f['tot']) laatste = service.laatste_sync() except Exception as e: fout = f"Gegevens konden niet gelezen worden: {e}" return render_template('laatkomers.html', rows=rows, f=f, fout=fout, laatste=laatste, codes=service.laat_codes, status=service.status, totaal=sum(r['Aantal'] for r in rows))