Files
Pivot/blueprints/database_bp.py
2026-10-14 14:39:01 +02:00

187 lines
8.0 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
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.
"""
Beheer › Database: tabellen van de school aanvullen vanuit Excel.
Toestellen:
GET /beheer/database/toestellen → pagina (upload + sjabloon)
GET /beheer/database/toestellen/sjabloon.xlsx → sjabloon downloaden
POST /beheer/database/toestellen/controleren → Excel lezen en voorbeeld tonen (niets bewaard)
POST /beheer/database/toestellen/importeren → bevestigen: het voorbeeld uitvoeren
Eigendom (toekenningen toestel ↔ leerling): dezelfde vier routes onder /beheer/database/eigendom
"""
import json
from flask import Blueprint, render_template, request, Response, redirect, url_for, flash, session
from blueprints.auth import login_required
from database import execute_query
from rechten import recht_vereist, log
from services import eigendom_import as ei
from services import toestellen_import as ti
from tenancy import huidige_school
database_bp = Blueprint('database_beheer', __name__)
@database_bp.route('/toestellen', methods=['GET'])
@login_required
@recht_vereist('database_toestellen')
def toestellen():
try:
aantal = execute_query("SELECT COUNT(*) AS n FROM Toestellen")[0]['n']
except Exception:
aantal = None
return render_template('database_toestellen.html', plan=None, aantal=aantal, kolommen=ti.KOLOMMEN,
statussen=ti.STATUSSEN, max_rijen=ti.MAX_RIJEN)
@database_bp.route('/toestellen/sjabloon.xlsx', methods=['GET'])
@login_required
@recht_vereist('database_toestellen')
def toestellen_sjabloon():
school = huidige_school()
data = ti.maak_sjabloon(school.naam)
return Response(data, mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
headers={'Content-Disposition': f"attachment; filename=Sjabloon_toestellen_{school.code}.xlsx"})
@database_bp.route('/toestellen/controleren', methods=['POST'])
@login_required
@recht_vereist('database_toestellen')
def toestellen_controleren():
terug = redirect(url_for('database_beheer.toestellen'))
bestand = request.files.get('bestand')
if not bestand or not bestand.filename:
flash("Kies eerst een Excel-bestand.", "error")
return terug
if not bestand.filename.lower().endswith(('.xlsx', '.xlsm')):
flash("Enkel Excel-bestanden (.xlsx) kunnen ingelezen worden. Sla een .xls of .csv eerst op als .xlsx.", "error")
return terug
modus = 'overschrijven' if request.form.get('modus') == 'overschrijven' else 'aanvullen'
try:
rijen, onbekend = ti.lees_excel(bestand)
except ti.ImportFout as e:
flash(str(e), "error")
return terug
if not rijen:
flash("Het bestand bevat geen toestellen (enkel kolomkoppen of lege rijen).", "warning")
return terug
try:
plan = ti.maak_plan(rijen, modus)
except Exception as e:
flash(f"Controleren mislukt: {e}", "error")
return terug
return render_template('database_toestellen.html', plan=plan, bestandsnaam=bestand.filename, onbekend=onbekend,
rijen_json=json.dumps(rijen, ensure_ascii=False), kolommen=ti.KOLOMMEN,
statussen=ti.STATUSSEN, max_rijen=ti.MAX_RIJEN, aantal=None)
@database_bp.route('/toestellen/importeren', methods=['POST'])
@login_required
@recht_vereist('database_toestellen')
def toestellen_importeren():
terug = redirect(url_for('database_beheer.toestellen'))
modus = 'overschrijven' if request.form.get('modus') == 'overschrijven' else 'aanvullen'
try:
rijen = json.loads(request.form.get('rijen') or '[]')
if not isinstance(rijen, list) or len(rijen) > ti.MAX_RIJEN or not all(isinstance(r, dict) for r in rijen):
raise ValueError
except ValueError:
flash("De gegevens van het voorbeeld zijn ongeldig. Upload het bestand opnieuw.", "error")
return terug
try:
plan, nieuw, bijgewerkt = ti.voer_uit(rijen, modus, session.get('user'))
except Exception as e:
flash(f"Importeren mislukt, er is niets bewaard: {e}", "error")
return terug
t = plan['tellers']
log(f"Toestellen geïmporteerd uit Excel: {nieuw} nieuw, {bijgewerkt} bijgewerkt ({modus})")
ongewijzigd = f", {t['ongewijzigd']} ongewijzigd" if t['ongewijzigd'] else ""
flash(f"Import klaar: {nieuw} toestel(len) toegevoegd, {bijgewerkt} bijgewerkt{ongewijzigd}.", "success")
if t['fout']:
flash(f"{t['fout']} rij(en) met een fout werden overgeslagen.", "warning")
return terug
# ---------------------------------------------------------------------------
# Eigendom
# ---------------------------------------------------------------------------
@database_bp.route('/eigendom', methods=['GET'])
@login_required
@recht_vereist('database_eigendom')
def eigendom():
try:
aantal = execute_query("SELECT COUNT(*) AS n FROM Eigendom WHERE Datum_inlevering IS NULL")[0]['n']
except Exception:
aantal = None
return render_template('database_eigendom.html', plan=None, aantal=aantal, kolommen=ei.KOLOMMEN,
max_rijen=ei.MAX_RIJEN)
@database_bp.route('/eigendom/sjabloon.xlsx', methods=['GET'])
@login_required
@recht_vereist('database_eigendom')
def eigendom_sjabloon():
school = huidige_school()
data = ei.maak_sjabloon(school.naam)
return Response(data, mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
headers={'Content-Disposition': f"attachment; filename=Sjabloon_eigendom_{school.code}.xlsx"})
@database_bp.route('/eigendom/controleren', methods=['POST'])
@login_required
@recht_vereist('database_eigendom')
def eigendom_controleren():
terug = redirect(url_for('database_beheer.eigendom'))
bestand = request.files.get('bestand')
if not bestand or not bestand.filename:
flash("Kies eerst een Excel-bestand.", "error")
return terug
if not bestand.filename.lower().endswith(('.xlsx', '.xlsm')):
flash("Enkel Excel-bestanden (.xlsx) kunnen ingelezen worden. Sla een .xls of .csv eerst op als .xlsx.", "error")
return terug
modus = 'overschrijven' if request.form.get('modus') == 'overschrijven' else 'aanvullen'
try:
rijen, onbekend = ei.lees_excel(bestand)
except ei.ImportFout as e:
flash(str(e), "error")
return terug
if not rijen:
flash("Het bestand bevat geen toekenningen (enkel kolomkoppen of lege rijen).", "warning")
return terug
try:
plan = ei.maak_plan(rijen, modus)
except Exception as e:
flash(f"Controleren mislukt: {e}", "error")
return terug
return render_template('database_eigendom.html', plan=plan, bestandsnaam=bestand.filename, onbekend=onbekend,
rijen_json=json.dumps(rijen, ensure_ascii=False), kolommen=ei.KOLOMMEN,
max_rijen=ei.MAX_RIJEN, aantal=None)
@database_bp.route('/eigendom/importeren', methods=['POST'])
@login_required
@recht_vereist('database_eigendom')
def eigendom_importeren():
terug = redirect(url_for('database_beheer.eigendom'))
modus = 'overschrijven' if request.form.get('modus') == 'overschrijven' else 'aanvullen'
try:
rijen = json.loads(request.form.get('rijen') or '[]')
if not isinstance(rijen, list) or len(rijen) > ei.MAX_RIJEN or not all(isinstance(r, dict) for r in rijen):
raise ValueError
except ValueError:
flash("De gegevens van het voorbeeld zijn ongeldig. Upload het bestand opnieuw.", "error")
return terug
try:
plan, nieuw, bijgewerkt = ei.voer_uit(rijen, modus, session.get('user'))
except Exception as e:
flash(f"Importeren mislukt, er is niets bewaard: {e}", "error")
return terug
t = plan['tellers']
log(f"Eigendom geïmporteerd uit Excel: {nieuw} nieuw, {bijgewerkt} bijgewerkt ({modus})")
ongewijzigd = f", {t['ongewijzigd']} ongewijzigd" if t['ongewijzigd'] else ""
flash(f"Import klaar: {nieuw} toekenning(en) toegevoegd, {bijgewerkt} bijgewerkt{ongewijzigd}.", "success")
if t['fout']:
flash(f"{t['fout']} rij(en) met een fout werden overgeslagen.", "warning")
return terug