"""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 from rest_framework.response import Response from core.models import KalenderDag, Persoon, Roosterblok, Rol, Schooljaar, Subgroep, Tijdslot from .models import UrenMomentopname 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) .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"]) 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"]) 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) 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})