Files
Pivot/blueprints/internaat_bp.py
T
2026-10-14 13:34:37 +02:00

230 lines
10 KiB
Python
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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"})