112 lines
8.8 KiB
Markdown
112 lines
8.8 KiB
Markdown
# PIVOT 1.1.0 (meerdere scholen) – overdracht
|
||
|
||
Dit document vat samen hoe PIVOT in elkaar zit, welke afspraken er in de code gelden en wat er recent bijkwam.
|
||
Het is bedoeld voor wie verder werkt aan PIVOT (zelf, of samen met een AI-assistent: geef dan dit bestand en de
|
||
zip van het project mee). Lees ook **MEERDERE_SCHOLEN.md** (installatie, rollen) en **DATABASE.md** (tabellen).
|
||
|
||
> ⚠️ `config.ini` en de map `geheimen/` bevatten echte wachtwoorden en sleutels. Deel ze niet en zet ze nooit in
|
||
> een chat, ticket of e-mail. Geef een collega een kopie van het project **zonder** die bestanden en laat hem/haar
|
||
> een eigen `config.ini` invullen.
|
||
|
||
---
|
||
|
||
## 1. Architectuur in het kort
|
||
|
||
- **Flask-app** (`app.py`) met ±40 blueprints (`blueprints/`) en de logica in `services/`; HTML in `templates/`
|
||
(Jinja, `base.html` + `static/global.css`).
|
||
- **Multi-tenant** (`tenancy.py`): één centrale database (`Scholen`, `Gebruikers`, rechten, logs, ...) en één database
|
||
per school (toestellen, leerlingen, ...).
|
||
- `huidige_school()` = de school van het verzoek; `in_school(school)` / `met_school(fn)` voor achtergrondthreads.
|
||
- Instellingen per school = `Veld(...)` in `tenancy.VELDEN` (verschijnen vanzelf in 🔌 Koppelingen);
|
||
lezen met `school.get('SLEUTEL')` of `Config.SLEUTEL` (metaclass leest de huidige school).
|
||
- `school.cache` = per-school geheugen (wordt leeggemaakt bij wijzigingen aan de school).
|
||
- `school_singleton(cls, init)` = één object per school (gebruikt voor achtergrondtaken met `status`-dict).
|
||
- `google_credentials(scopes, subject)` = serviceaccount van de school, gedelegeerd naar `subject`.
|
||
- **Database** (`database.py`): `execute_query` (schooldatabase), `central_query` (centraal); `commit=True` geeft
|
||
het aantal rijen terug. Pools per database (`config.ini › [database] › pool_size`, standaard 3).
|
||
- **Rechten** (`rechten.py`): `ONDERDELEN` = lijst (code, naam, categorie); routes met `@recht_vereist('code')`,
|
||
templates met `{% if mag('code') %}`. Een nieuw onderdeel verschijnt vanzelf in 🔐 Gebruikers & rechten.
|
||
`log(...)` schrijft in het logboek.
|
||
- **Achtergrondtaken**: threads gestart in `app.py` (enkel in het echte proces, niet in de reloader):
|
||
afwezigheden, 2FA, Skore, GO! Pro (nachtelijk verzamelen), Gmail-handtekeningen (nachtelijk). Taken die maar één keer
|
||
per dag mogen lopen, "claimen" de dag met een `UPDATE ... WHERE` (werkt ook met meerdere processen).
|
||
- **Opstarten**: `tenancy.ensure_schema()` (centrale tabellen) en `services/tabelnamen.py` (zie §5).
|
||
|
||
## 2. Afspraken in de code
|
||
|
||
- Alles in het **Nederlands** (namen, meldingen, commentaar).
|
||
- Tabellen die een module zelf nodig heeft: `CREATE TABLE IF NOT EXISTS` in een `ensure_tables()`/`*_ensure()` in de
|
||
service, én dezelfde DDL in `sql/school.sql` of `sql/centraal.sql` + `sql/installatie_pivot_26-27.sql`, én een regel
|
||
in `DATABASE.md`. Kolommen die later bijkomen: `SHOW COLUMNS` + `ALTER TABLE` in de ensure-functie.
|
||
- Tabelnamen met hoofdletters moeten exact kloppen (Linux!); zie §5.
|
||
- **Templates**:
|
||
- `{% block main_class %}wide{% endblock %}` voor brede tabellen; tabellen met zoeken/sorteren/paginering in de
|
||
browser (helpers `perPagina(sleutel, standaard)` en `perPaginaKiezer(...)` uit `base.html`).
|
||
- In HTML-attributen (`onsubmit="..."`, `onchange`, `data-...`) altijd `|tojson|forceescape`, anders breekt het
|
||
attribuut op de aanhalingstekens (bevestigingsvragen werkten daardoor niet meer).
|
||
- Elementen die met het `hidden`-attribuut verborgen worden: zet `[hidden] { display: none !important; }` in de
|
||
pagina-CSS (knopstijlen overschrijven het anders).
|
||
- **CSV-exports**: `;` als scheidingsteken en UTF-8 met BOM (Excel). **Excel**: openpyxl.
|
||
- **Google-fouten** vertalen naar begrijpelijke meldingen (`vriendelijk(e)` in de services) mét de scope die ontbreekt.
|
||
|
||
## 3. Nieuw onderdeel toevoegen (checklist)
|
||
|
||
1. `services/<naam>_service.py` (logica, tabellen) en `blueprints/<naam>_bp.py` (routes).
|
||
2. Blueprint registreren in `app.py`.
|
||
3. Recht in `rechten.ONDERDELEN` (code, naam, categorie).
|
||
4. Link in het juiste blok van `templates/dashboard.html` (`{% if mag('code') %}`; voeg de code ook toe aan de
|
||
`mag(...)`-voorwaarde van het blok).
|
||
5. Nieuwe Google-scope? Toevoegen aan de lijst + tabel in `services/handleidingen_standaard.py` (de handleiding in
|
||
de database wordt **niet** automatisch bijgewerkt: zelf aanpassen in 📘 Handleidingen).
|
||
6. SQL-bestanden en `DATABASE.md` bijwerken.
|
||
|
||
## 4. Overzicht van recente onderdelen
|
||
|
||
| Onderdeel | Bestanden | Opmerkingen |
|
||
|---|---|---|
|
||
| IP-blokkades / whitelist | `ipblokkade_bp.py`, `services/ipblokkade.py` | gewone gebruikers met recht `ip_whitelist` zien/beheren enkel hun eigen school |
|
||
| Aanmelden | `blueprints/auth.py` | geen schoolkeuze meer: laatste school → domein → cookie → laagste instellingsnummer; domeinen mogen gedeeld worden door meerdere scholen |
|
||
| GO! Pro + verzameltabel | `gopro_bp.py`, `gopro_service.py`, `gopro*.html` | verzameltabel (centraal), rode lijst, verzonden, nachtelijk verzamelen; periode = lopend schooljaar (1/9–31/8) |
|
||
| Leerlingfacturatie | `facturatie_bp.py`, `facturatie_service.py` | Informat Invoice API (GraphQL); scope per toegangsgroep (`<GUID>_<installatie>`), wordt zelf gezocht |
|
||
| Internaat | `internaat_bp.py`, `internaat_service.py`, `aanwezigheid_service.py` | Internaat WEB API; aanwezigheden per week; Smartschool-check "voornaam achternaam" |
|
||
| Gmail | `gmail_delegatie_bp.py`, `gmail_labels_bp.py`, `handtekeningen_bp.py`, `contacten_bp.py`, `verzendlijsten_bp.py` | delegaties (+ overzicht), mails verplaatsen, handtekeningen (functie/telefoon uit Google, nachtelijk), contactlijsten in mailboxen + zichtbaarheid adresboek |
|
||
| Google | `google_licenties_bp.py`, `gedeelde_drives_bp.py` | licenties (customerId: domein of klant-ID), gedeelde drives: leden/rechten, "waar is iemand lid van", gebruiker vervangen (zelfde rol) |
|
||
| Smartschool | `sgr_groepen_bp.py`, `sgr_groepen_service.py` | subgroepen + leden van een hoofdgroep (standaard "groepen GO-Antwerpen") |
|
||
| Basisonderwijs | `basisonderwijs_bp.py`, `bingel_service.py` | blok enkel bij Onderwijsniveau = basis; Bingel-Excel (klas uit Informat-inschrijvingen, klasnummer alfabetisch) |
|
||
| Toestellen | `devices.py`, `inventaris.html`, `toestellen.html` | eigendom: alle leerlingen + filter met/zonder toestel, historie per leerling en per toestel, knoppen uitdelen/inleveren; Alle toestellen: blokkeren/deblokkeren in Google (kolom `Toestellen.Geblokkeerd`) |
|
||
| Dashboard | `dashboard.html` | blokken Gmail, Internaat, Basisonderwijs, Leerlingfacturatie, Links (website + Smartschool) |
|
||
|
||
Benodigde Google-scopes (domeinbrede delegatie) staan in 📘 Handleidingen; recent bijgekomen:
|
||
`gmail.modify`, `gmail.settings.basic`, `contacts`, `drive`. De bijbehorende API's (Gmail, People, Drive,
|
||
Enterprise License Manager) moeten aanstaan in het Cloud-project van het serviceaccount.
|
||
|
||
## 5. Omgeving en installatie-aandachtspunten
|
||
|
||
- **Windows → Linux**: MySQL op Windows bewaart tabelnamen in kleine letters; op Linux (`lower_case_table_names = 0`)
|
||
herstelt `services/tabelnamen.py` dat bij het opstarten en bij 🩺 Testen (enkel voor de tabellen die PIVOT kent;
|
||
zijn beide versies gevuld, dan enkel een melding in het logboek).
|
||
- **"1040 Too many connections"**: (aantal scholen + 1) × `pool_size` per PIVOT-proces moet onder `max_connections`
|
||
van MySQL blijven. Oplossing: `SET PERSIST max_connections = 500;` of `pool_size` verlagen.
|
||
- **Productie**: liefst nginx (HTTPS, Let's Encrypt) + gunicorn/waitress i.p.v. de Flask-ontwikkelserver, en de
|
||
poort van PIVOT enkel intern openzetten. Het logo van Gmail-handtekeningen (`/handtekening-logo/...`) moet wel
|
||
publiek bereikbaar zijn (de IP-blokkering slaat die route over).
|
||
|
||
## 6. Nog niet getest tegen de echte omgeving / openstaand
|
||
|
||
Onderstaande onderdelen werden enkel getest met nagebootste antwoorden; bij het eerste echte gebruik goed nakijken:
|
||
|
||
- **Bingel**: het veld met de klas in de Informat-inschrijvingen (`inschrKlassen`); de pagina toont de veldnamen
|
||
die Informat teruggeeft als iets leeg blijft.
|
||
- **SGR groepen**: formaat van `getAllGroupsAndClasses` (XML/base64) en de leden per groep.
|
||
- **Contactlijsten** (People API), **gedeelde drives** (Drive API), **handtekeningen** (Gmail sendAs).
|
||
- **Licenties**: terugval op de klant-ID als het schooldomein geen primair domein is.
|
||
- **Leerlingfacturatie**: automatisch zoeken van de scope (identityserver).
|
||
- Informat-fout "SqlDateTime overflow" bij één school = foute datum in Informat zelf (niet in PIVOT).
|
||
|
||
## 7. Verder werken
|
||
|
||
- Elke wijziging werd telkens als volledige zip van `Pivot_1.1.0` aangeleverd (zonder `__pycache__`).
|
||
- Bij een nieuwe vraag aan een AI-assistent: geef dit bestand, `DATABASE.md`, `MEERDERE_SCHOLEN.md` en de
|
||
betrokken bestanden mee (of de hele zip zonder `config.ini`/`geheimen/`), en beschrijf wat je wil met een
|
||
schermafbeelding erbij.
|