158 lines
6.6 KiB
Python
158 lines
6.6 KiB
Python
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/<internnummer>.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))
|