PIVOT 1.1.0
This commit is contained in:
commit
8a53234eb6
161 files changed
+31109
No files matched your search
@@ -0,0 +1,229 @@
|
||||
"""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"})
|
||||
Reference in new issue
Block a user