"""Internaat: lijst van alle internen van de actieve school (Informat Internaat WEB API).""" import csv import io import time from datetime import date from flask import Blueprint, render_template, request, Response, redirect, url_for, flash from blueprints.auth import login_required from rechten import recht_vereist from services.facturatie_service import schooljaren from services.internaat_service import InternaatService, InternaatFout from tenancy import huidige_school internaat_bp = Blueprint('internaat', __name__) def _keuzes(): jaren = schooljaren() schooljaar = request.args.get('schooljaar') if request.args.get('schooljaar') in jaren else jaren[0] try: peildatum = date.fromisoformat(request.args.get('peildatum') or '') except ValueError: peildatum = date.today() return schooljaar, jaren, peildatum @internaat_bp.route("/internen", methods=["GET"]) @login_required @recht_vereist('internen') def internen(): school = huidige_school() schooljaar, jaren, peildatum = _keuzes() rijen, fout, opgehaald = [], None, None if not school.heeft('INFORMAT_ID', 'INFORMAT_SECRET'): fout = "Informat is nog niet ingesteld voor deze school (🔌 Koppelingen › Informat)." else: try: rijen, t = InternaatService().internen(schooljaar, peildatum, vernieuw=bool(request.args.get('vernieuw'))) opgehaald = time.strftime('%d-%m-%Y %H:%M', time.localtime(t)) except InternaatFout as e: fout = str(e) except Exception as e: fout = f"Internen ophalen mislukt: {e}" internaten = sorted({r['internaat'] for r in rijen}) return render_template("internen.html", rijen=rijen, fout=fout, opgehaald=opgehaald, schooljaar=schooljaar, jaren=jaren, peildatum=peildatum, internaten=internaten, school_naam=school.naam) @internaat_bp.route("/internen/smartschool.json", methods=["POST"]) @login_required @recht_vereist('internen') def internen_smartschool(): """Welke internen bestaan als gebruiker in Smartschool? {pid: gebruikersnaam | null}""" from flask import jsonify from services.internaat_service import smartschool_status personen = (request.get_json(silent=True) or {}).get('personen') or [] try: return jsonify(ok=True, status=smartschool_status(personen[:2000])) except InternaatFout as e: return jsonify(ok=False, fout=str(e)) except Exception as e: return jsonify(ok=False, fout=f"Smartschool kon niet bevraagd worden: {e}") @internaat_bp.route("/internen/export.csv", methods=["GET"]) @login_required @recht_vereist('internen') def export(): schooljaar, _, peildatum = _keuzes() try: rijen, _ = InternaatService().internen(schooljaar, peildatum) except Exception as e: flash(str(e), "error") return redirect(url_for('internaat.internen', schooljaar=schooljaar)) internaat = request.args.get('internaat') or '' zoek = (request.args.get('zoek') or '').strip().lower() if internaat: rijen = [r for r in rijen if r['internaat'] == internaat] if zoek: rijen = [r for r in rijen if zoek in ' '.join(str(r[k]) for k in ('naam', 'voornaam', 'stamnummer', 'kamer', 'vestiging', 'gemeente')).lower()] datum = lambda d: d.strftime('%d/%m/%Y') if d else '' buf = io.StringIO() w = csv.writer(buf, delimiter=';') w.writerow(['Naam', 'Voornaam', 'Geboortedatum', 'Geslacht', 'Stamnummer', 'Internaat', 'Vestiging', 'Kamer', 'Ingeschreven van', 'Ingeschreven tot', 'Financiering', 'Woonplaats', 'Contact 1', 'Gsm 1', 'E-mail 1', 'Contact 2', 'Gsm 2', 'E-mail 2', 'Smartschool']) ss_cache = huidige_school().cache.get('ss_internen') or {} def ss(r): hit = ss_cache.get((str(r['id']), r['voornaam'], r['naam'])) return '' if not hit else (hit[1] or 'niet gevonden') for r in rijen: o = (r['ouders'] + [{}, {}])[:2] w.writerow([r['naam'], r['voornaam'], datum(r['geboortedatum']), r['geslacht'], r['stamnummer'], r['internaat'], r['vestiging'], r['kamer'], datum(r['begin']), datum(r['einde']), r['financiering'], r['gemeente']] + sum(([f"{x.get('naam', '')} ({x['type']})" if x.get('type') else x.get('naam', ''), x.get('gsm', ''), x.get('email', '')] for x in o), []) + [ss(r)]) return Response('' + buf.getvalue(), mimetype='text/csv; charset=utf-8', headers={ 'Content-Disposition': f"attachment; filename=Internen_{huidige_school().code}_{peildatum:%Y%m%d}.csv"}) # --------------------------------------------------------------------- # Aanwezigheden (register per week: ochtend en avond) # --------------------------------------------------------------------- from datetime import timedelta from flask import jsonify, session from services import aanwezigheid_service as aw def _week(): try: d = date.fromisoformat(request.args.get('week') or '') except ValueError: d = date.today() return aw.maandag_van(d) def _internen_van_week(maandag): """Internen die in de week ingeschreven zijn (peildatum: vandaag als die in de week valt, anders woensdag).""" vandaag = date.today() peildatum = vandaag if maandag <= vandaag <= maandag + timedelta(days=6) else maandag + timedelta(days=2) rijen, _ = InternaatService().internen(aw.schooljaar_van(peildatum), peildatum) meerdere = len({r['internaat'] for r in rijen}) > 1 for r in rijen: r['groep'] = aw.groep_van(r, meerdere) r['pid'] = str(r['id']) return rijen @internaat_bp.route("/aanwezigheden", methods=["GET"]) @login_required @recht_vereist('internaat_aanwezigheid') def aanwezigheden(): school = huidige_school() maandag = _week() rijen, fout, data = [], None, {} try: rijen = _internen_van_week(maandag) data = aw.week(maandag) except InternaatFout as e: fout = str(e) except Exception as e: fout = f"Internen ophalen mislukt: {e}" groepen = {} for r in sorted(rijen, key=lambda r: (r['groep'].lower(), r['naam'].lower(), r['voornaam'].lower())): groepen.setdefault(r['groep'], []).append(r) dagen = [maandag + timedelta(days=i) for i in range(7)] return render_template("aanwezigheden.html", groepen=groepen, data=data, dagen=dagen, maandag=maandag, fout=fout, school_naam=school.naam, vandaag=date.today(), dagnamen=aw.DAGEN, dagnamen_voluit=aw.DAGEN_VOLUIT, aantal=len(rijen), vorige=maandag - timedelta(days=7), volgende=maandag + timedelta(days=7), weeknr=maandag.isocalendar()[1]) @internaat_bp.route("/aanwezigheden/zet", methods=["POST"]) @login_required @recht_vereist('internaat_aanwezigheid') def aanwezigheid_zet(): d = request.get_json(silent=True) or {} try: datum = date.fromisoformat(str(d.get('datum') or '')) aw.zet(datum, str(d.get('persoon') or '')[:64], d.get('moment'), d.get('status'), d.get('naam'), session.get('naam') or session.get('user')) except ValueError as e: return jsonify(ok=False, fout=str(e) or 'Ongeldige gegevens.'), 400 except Exception as e: return jsonify(ok=False, fout=f"Bewaren mislukt: {e}"), 500 return jsonify(ok=True) @internaat_bp.route("/aanwezigheden/export.xlsx", methods=["GET"]) @login_required @recht_vereist('internaat_aanwezigheid') def aanwezigheden_export(): """Excel van de week, met dezelfde filters als op het scherm (?groep=&zoek=).""" from openpyxl import Workbook from openpyxl.styles import Font, PatternFill, Alignment maandag = _week() try: rijen = _internen_van_week(maandag) data = aw.week(maandag) except Exception as e: flash(str(e), "error") return redirect(url_for('internaat.aanwezigheden', week=maandag.isoformat())) groep, zoek = request.args.get('groep') or '', (request.args.get('zoek') or '').strip().lower() rijen = [r for r in rijen if (not groep or r['groep'] == groep) and (not zoek or zoek in f"{r['naam']} {r['voornaam']}".lower())] rijen.sort(key=lambda r: (r['groep'].lower(), r['naam'].lower(), r['voornaam'].lower())) labels = {'aanwezig': 'Aanwezig', 'afwezig': 'Afwezig'} kleur = {'aanwezig': PatternFill('solid', fgColor='D7EDDF'), 'afwezig': PatternFill('solid', fgColor='F6D5CF')} wb = Workbook() ws = wb.active ws.title = f"Week {maandag.isocalendar()[1]}" kop = ['Naam', 'Voornaam', 'Groep', 'Kamer'] for i, dag in enumerate(aw.DAGEN_VOLUIT): d = maandag + timedelta(days=i) kop += [f"{dag} {d:%d/%m} ochtend", f"{dag} {d:%d/%m} avond"] ws.append(kop) for c in ws[1]: c.font = Font(bold=True) c.alignment = Alignment(wrap_text=True, vertical='top') vorige_groep = None for r in rijen: if r['groep'] != vorige_groep: ws.append([f"Groep: {r['groep']}"]) ws.cell(ws.max_row, 1).font = Font(bold=True) vorige_groep = r['groep'] rij = [r['naam'], r['voornaam'], r['groep'], r['kamer']] for i in range(7): for m in aw.MOMENTEN: rij.append(labels.get(data.get(f"{r['pid']}:{i}:{m}"), '')) ws.append(rij) for c in ws[ws.max_row][4:]: for k, f in kleur.items(): if c.value == labels[k]: c.fill = f ws.column_dimensions['A'].width = 22 ws.column_dimensions['B'].width = 16 ws.column_dimensions['C'].width = 16 ws.column_dimensions['D'].width = 22 from openpyxl.utils import get_column_letter for i in range(5, 5 + 14): ws.column_dimensions[get_column_letter(i)].width = 13 ws.freeze_panes = 'C2' buf = io.BytesIO() wb.save(buf) return Response(buf.getvalue(), mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', headers={'Content-Disposition': f"attachment; filename=Aanwezigheden_{huidige_school().code}_" f"{maandag:%Y}-W{maandag.isocalendar()[1]:02d}.xlsx"})