189 lines
7.1 KiB
Python
189 lines
7.1 KiB
Python
"""Urenoverzicht: berekent lesuren per groep/vak en per leerling.
|
|
|
|
Roosterblokken zijn wekelijks: per schooljaar telt een blok zo vaak mee als er
|
|
lesdagen op die weekdag zijn (vrije dagen/vakanties/studiedagen tellen niet mee),
|
|
minus de keren dat het blok via een uitzondering vervalt. Een blok met een
|
|
geldigheidsperiode (geldig_van/geldig_tot) telt alleen binnen dat bereik mee.
|
|
"""
|
|
from datetime import timedelta
|
|
|
|
from rest_framework.decorators import api_view, permission_classes
|
|
from rest_framework.response import Response
|
|
|
|
from core.audit import audit_log
|
|
from core.models import KalenderDag, Persoon, Roosterblok, Rol, Schooljaar, Subgroep, Tijdslot
|
|
from core.permissions import StaffDataPermission, module_permission_class
|
|
|
|
from .models import UrenMomentopname
|
|
|
|
UrenModuleEnabled = module_permission_class("uren")
|
|
|
|
|
|
def _duur_uren(ts):
|
|
s, e = ts.start_tijd, ts.eind_tijd
|
|
return ((e.hour * 60 + e.minute) - (s.hour * 60 + s.minute)) / 60.0
|
|
|
|
|
|
def bereken_uren(sj):
|
|
"""Bereken het volledige urenoverzicht voor een schooljaar (dict)."""
|
|
vrije = set(KalenderDag.objects.filter(schooljaar=sj).values_list("datum", flat=True))
|
|
lesdagen = {0: 0, 1: 0, 2: 0, 3: 0, 4: 0}
|
|
d = sj.start_datum
|
|
while d <= sj.eind_datum:
|
|
wd = d.weekday()
|
|
if wd <= 4 and d not in vrije:
|
|
lesdagen[wd] += 1
|
|
d += timedelta(days=1)
|
|
|
|
blokken = list(
|
|
Roosterblok.objects.filter(schooljaar=sj, sjabloon=False)
|
|
.select_related("tijdslot", "activiteit", "groep", "subgroep", "leerling", "leerling__groep")
|
|
.prefetch_related("uitzonderingen")
|
|
)
|
|
subgroepen = {s.id: s for s in Subgroep.objects.prefetch_related("leden").all()}
|
|
sub_leden = {sid: set(s.leden.values_list("id", flat=True)) for sid, s in subgroepen.items()}
|
|
leerlingen = list(Persoon.objects.filter(rol=Rol.LEERLING).select_related("groep"))
|
|
|
|
def _tel_weekdag(van, tot, wd):
|
|
n = 0
|
|
d2 = van
|
|
while d2 <= tot:
|
|
if d2.weekday() == wd and d2 not in vrije:
|
|
n += 1
|
|
d2 += timedelta(days=1)
|
|
return n
|
|
|
|
def occurrences(blok):
|
|
wd = blok.tijdslot.dag
|
|
van = max(blok.geldig_van or sj.start_datum, sj.start_datum)
|
|
tot = min(blok.geldig_tot or sj.eind_datum, sj.eind_datum)
|
|
if van > tot:
|
|
return 0
|
|
if blok.geldig_van is None and blok.geldig_tot is None:
|
|
basis = lesdagen.get(wd, 0)
|
|
else:
|
|
basis = _tel_weekdag(van, tot, wd)
|
|
weg = 0
|
|
for u in blok.uitzonderingen.all():
|
|
if u.vervalt and van <= u.datum <= tot and u.datum.weekday() == wd and u.datum not in vrije:
|
|
weg += 1
|
|
return max(basis - weg, 0)
|
|
|
|
def groep_van_blok(b):
|
|
if b.groep_id:
|
|
return b.groep
|
|
if b.subgroep_id:
|
|
s = subgroepen.get(b.subgroep_id)
|
|
return s.groep if s else None
|
|
if b.leerling_id:
|
|
return b.leerling.groep
|
|
return None
|
|
|
|
blok_uren = {}
|
|
groep_map = {}
|
|
for b in blokken:
|
|
u = _duur_uren(b.tijdslot) * occurrences(b)
|
|
blok_uren[b.id] = u
|
|
g = groep_van_blok(b)
|
|
if g:
|
|
gm = groep_map.setdefault(g.id, {"naam": g.naam, "vakken": {}})
|
|
gm["vakken"][b.activiteit.naam] = gm["vakken"].get(b.activiteit.naam, 0) + u
|
|
|
|
per_groep = []
|
|
for gm in groep_map.values():
|
|
vakken = [{"vak": k, "uren": round(v, 1)} for k, v in sorted(gm["vakken"].items())]
|
|
per_groep.append({
|
|
"groep": gm["naam"],
|
|
"totaal": round(sum(x["uren"] for x in vakken), 1),
|
|
"vakken": vakken,
|
|
})
|
|
per_groep.sort(key=lambda x: x["groep"])
|
|
|
|
stages = []
|
|
alle_ts = []
|
|
try:
|
|
from modules.stage.models import Stage
|
|
alle_ts = list(Tijdslot.objects.all())
|
|
stages = list(Stage.objects.filter(actief=True))
|
|
except Exception:
|
|
pass
|
|
|
|
per_leerling = []
|
|
for l in leerlingen:
|
|
vakken = {}
|
|
for b in blokken:
|
|
hoort = (
|
|
(b.groep_id and l.groep_id == b.groep_id)
|
|
or (b.subgroep_id and l.id in sub_leden.get(b.subgroep_id, set()))
|
|
or (b.leerling_id == l.id)
|
|
)
|
|
if hoort:
|
|
vakken[b.activiteit.naam] = vakken.get(b.activiteit.naam, 0) + blok_uren[b.id]
|
|
stage_uren = 0
|
|
for st in stages:
|
|
if st.leerling_id != l.id:
|
|
continue
|
|
for ts in alle_ts:
|
|
if st.dekt_tijdslot(ts):
|
|
stage_uren += _duur_uren(ts) * lesdagen.get(ts.dag, 0)
|
|
vak_list = [{"vak": k, "uren": round(v, 1)} for k, v in sorted(vakken.items())]
|
|
totaal = sum(x["uren"] for x in vak_list) + stage_uren
|
|
if vak_list or stage_uren:
|
|
per_leerling.append({
|
|
"leerling": l.volledige_naam,
|
|
"groep": l.groep.naam if l.groep else None,
|
|
"vakken": vak_list,
|
|
"stage_uren": round(stage_uren, 1),
|
|
"totaal": round(totaal, 1),
|
|
})
|
|
per_leerling.sort(key=lambda x: x["leerling"])
|
|
|
|
return {
|
|
"schooljaar": sj.naam,
|
|
"lesdagen": lesdagen,
|
|
"per_groep": per_groep,
|
|
"per_leerling": per_leerling,
|
|
}
|
|
|
|
|
|
@api_view(["GET"])
|
|
@permission_classes([StaffDataPermission, UrenModuleEnabled])
|
|
def urenoverzicht(request):
|
|
sjid = request.query_params.get("schooljaar")
|
|
if not sjid:
|
|
return Response({"detail": "Geef een schooljaar op."}, status=400)
|
|
try:
|
|
sj = Schooljaar.objects.get(pk=sjid)
|
|
except Schooljaar.DoesNotExist:
|
|
return Response({"detail": "Schooljaar niet gevonden."}, status=404)
|
|
return Response(bereken_uren(sj))
|
|
|
|
|
|
@api_view(["GET", "POST"])
|
|
@permission_classes([StaffDataPermission, UrenModuleEnabled])
|
|
def momentopnames(request):
|
|
"""GET: lijst van momentopnames (samenvatting). POST: bewaar huidige stand."""
|
|
sjid = request.query_params.get("schooljaar") or request.data.get("schooljaar")
|
|
if not sjid:
|
|
return Response({"detail": "Geef een schooljaar op."}, status=400)
|
|
try:
|
|
sj = Schooljaar.objects.get(pk=sjid)
|
|
except Schooljaar.DoesNotExist:
|
|
return Response({"detail": "Schooljaar niet gevonden."}, status=404)
|
|
|
|
if request.method == "POST":
|
|
payload = bereken_uren(sj)
|
|
opname = UrenMomentopname.objects.create(schooljaar=sj, payload=payload)
|
|
audit_log(request, "create", f"UrenMomentopname:{opname.pk}", {"schooljaar": sj.pk})
|
|
return Response({"id": opname.id, "gemaakt_op": opname.gemaakt_op}, status=201)
|
|
|
|
opnames = UrenMomentopname.objects.filter(schooljaar=sj)
|
|
# Bouw een leerling x momentopname-tabel met totaal-uren.
|
|
kolommen = []
|
|
leerling_rijen = {}
|
|
for o in opnames:
|
|
kolommen.append({"id": o.id, "gemaakt_op": o.gemaakt_op})
|
|
for l in (o.payload or {}).get("per_leerling", []):
|
|
leerling_rijen.setdefault(l["leerling"], {})[o.id] = l["totaal"]
|
|
rijen = [{"leerling": naam, "totalen": tot} for naam, tot in sorted(leerling_rijen.items())]
|
|
return Response({"schooljaar": sj.naam, "momentopnames": kolommen, "leerlingen": rijen})
|