PIVOT 1.1.0
This commit is contained in:
commit
8a53234eb6
161 files changed
+31109
No files matched your search
@@ -0,0 +1,603 @@
|
||||
import sys
|
||||
from flask import Blueprint, render_template, request, session, redirect, url_for, flash, jsonify
|
||||
from database import execute_query
|
||||
from services import CIP_inleveren
|
||||
from services import CIP_uitdelen
|
||||
from blueprints.auth import login_required
|
||||
from rechten import recht_vereist
|
||||
from database import execute_query, execute_insert, get_db_connection
|
||||
from services import toestellen as tst
|
||||
from blueprints.google_admin import google_service
|
||||
from googleapiclient.errors import HttpError
|
||||
|
||||
|
||||
# We noemen de blueprint 'devices'
|
||||
devices_bp = Blueprint('devices', __name__)
|
||||
|
||||
#-------------------------------------------------
|
||||
# --- ROUTE TOESTELLEN (Inventaris Toestellen) ---
|
||||
#-------------------------------------------------
|
||||
@devices_bp.route("/inventaris", methods=["GET", "POST"])
|
||||
@login_required
|
||||
@recht_vereist('toestellen_beheren')
|
||||
def inventaris_toestellen():
|
||||
query = request.form.get("query", "") if request.method == "POST" else request.args.get("query", "")
|
||||
message = None
|
||||
|
||||
search_term = f"%{query}%"
|
||||
try:
|
||||
tst.ensure_geblokkeerd()
|
||||
geblokkeerd = "t.Geblokkeerd"
|
||||
except Exception as e: # bv. geen ALTER-rechten: pagina blijft werken
|
||||
sys.stderr.write(f"Kolom Toestellen.Geblokkeerd: {e}\n")
|
||||
geblokkeerd = "0"
|
||||
sql = f"""
|
||||
SELECT * FROM (
|
||||
SELECT t.Toestel_ID, t.Schooleigen_ID AS KAMXM_nummer, sp.Signpostnummer, t.Serienummer, t.Type,
|
||||
DATE_FORMAT(sp.Garantie, '%d-%m-%Y') AS Garantie, t.Status, {geblokkeerd} AS Geblokkeerd,
|
||||
{tst.LAATSTE_OPMERKING} AS Opmerking
|
||||
FROM Toestellen t
|
||||
LEFT JOIN Signpost_DB sp ON sp.Toestel_ID = t.Toestel_ID
|
||||
) x
|
||||
WHERE CONCAT_WS(' ', KAMXM_nummer, Signpostnummer, Serienummer, Type, Opmerking) LIKE %s
|
||||
ORDER BY KAMXM_nummer IS NULL, KAMXM_nummer
|
||||
"""
|
||||
|
||||
results = execute_query(sql, (search_term,))
|
||||
|
||||
|
||||
if query and not results:
|
||||
message = f"Geen resultaten gevonden voor '{query}'."
|
||||
elif not results:
|
||||
message = "Er zijn momenteel geen toestellen in de inventaris."
|
||||
|
||||
return render_template("toestellen.html", results=results, query=query, message=message)
|
||||
|
||||
@devices_bp.route("/google_import", methods=["POST"])
|
||||
@login_required
|
||||
@recht_vereist('toestellen_beheren')
|
||||
def google_import():
|
||||
"""Chromebooks uit Google Admin in de inventaris zetten: nieuwe serienummers toevoegen en een ontbrekend model
|
||||
aanvullen. Er wordt niets verwijderd of overschreven."""
|
||||
from config import Config
|
||||
# Leeg = alle Chromebooks van het domein; anders enkel deze OU's (met sub-OU's)
|
||||
ous = [o.strip() for o in str(Config.CHROMEBOOK_OUS or '').split(',') if o.strip()]
|
||||
try:
|
||||
toestellen = google_service.lijst_chromebooks(ous)
|
||||
except HttpError as e:
|
||||
fout = str(e)
|
||||
if 'INVALID_OU_ID' in fout or 'Invalid Input: OrgUnit' in fout or ('400' in fout and ous):
|
||||
flash(f"Een OU bestaat niet in Google: {', '.join(ous)}. Pas 'OU's van de Chromebooks' aan bij "
|
||||
"🔌 Koppelingen van deze school (volledig pad, bv. /ChromeBooks Medewerkers).", "error")
|
||||
else:
|
||||
flash(f"Chromebooks ophalen bij Google mislukt: {e}", "error")
|
||||
return redirect(url_for("devices.inventaris_toestellen"))
|
||||
except Exception as e:
|
||||
flash(f"Chromebooks ophalen bij Google mislukt: {e}", "error")
|
||||
return redirect(url_for("devices.inventaris_toestellen"))
|
||||
|
||||
try:
|
||||
tst.ensure_geblokkeerd()
|
||||
kol = "Geblokkeerd"
|
||||
except Exception:
|
||||
kol = "NULL AS Geblokkeerd"
|
||||
bestaand = {(r['Serienummer'] or '').strip().upper(): r for r in
|
||||
execute_query(f"SELECT Toestel_ID, Serienummer, Type, {kol} FROM Toestellen WHERE Serienummer IS NOT NULL")}
|
||||
nieuw = aangevuld = blok_bijgewerkt = 0
|
||||
fouten = []
|
||||
# Het veld Item-ID (Asset ID) uit Google wordt bewust NIET overgenomen: het wordt soms voor andere info gebruikt
|
||||
# (bv. een lokaal) en is dan niet uniek. Het toestelnummer van de school vul je in de inventaris of via Signpost.
|
||||
for d in toestellen:
|
||||
serie = d['serialNumber'].strip()
|
||||
model = (d.get('model') or '').strip()[:100] or None
|
||||
try:
|
||||
rij = bestaand.get(serie.upper())
|
||||
in_google = 1 if d.get('status') == 'DISABLED' else 0
|
||||
if not rij:
|
||||
nieuw_id = execute_insert("INSERT INTO Toestellen (Serienummer, Type, Status) VALUES (%s, %s, 'Beschikbaar')",
|
||||
(serie, model))
|
||||
if in_google and kol == "Geblokkeerd":
|
||||
tst.zet_geblokkeerd(nieuw_id, True)
|
||||
bestaand[serie.upper()] = {'Type': model}
|
||||
nieuw += 1
|
||||
continue
|
||||
if model and not rij['Type']:
|
||||
execute_query("UPDATE Toestellen SET Type = %s WHERE Toestel_ID = %s", (model, rij['Toestel_ID']),
|
||||
commit=True)
|
||||
aangevuld += 1
|
||||
# Blokkeerstatus overnemen uit Google (daar kan het ook rechtstreeks gewijzigd zijn)
|
||||
if rij.get('Geblokkeerd') is not None and int(rij['Geblokkeerd'] or 0) != in_google:
|
||||
tst.zet_geblokkeerd(rij['Toestel_ID'], in_google)
|
||||
blok_bijgewerkt += 1
|
||||
except Exception as e:
|
||||
fouten.append(f"{serie}: {e}")
|
||||
sys.stderr.write(f"Chromebook-import {serie}: {e}\n")
|
||||
bereik = f" uit {', '.join(ous)}" if ous else ""
|
||||
flash(f"{len(toestellen)} Chromebook(s) gevonden in Google{bereik}: {nieuw} nieuw in de inventaris, "
|
||||
f"{aangevuld} aangevuld, {len(toestellen) - nieuw - aangevuld - len(fouten)} stonden er al."
|
||||
+ (f" Blokkeerstatus bijgewerkt bij {blok_bijgewerkt} toestel(len)." if blok_bijgewerkt else ""), "success")
|
||||
if fouten:
|
||||
flash(f"{len(fouten)} toestel(len) konden niet verwerkt worden: " + "; ".join(fouten[:5])
|
||||
+ (" …" if len(fouten) > 5 else ""), "error")
|
||||
return redirect(url_for("devices.inventaris_toestellen"))
|
||||
|
||||
|
||||
@devices_bp.route("/google_info", methods=["GET"])
|
||||
@login_required
|
||||
@recht_vereist('toestellen_beheren')
|
||||
def google_info():
|
||||
"""JSON met gebruiksinfo uit Google Admin voor één toestel (voor de knop 'Google-info')."""
|
||||
try:
|
||||
info, fout = google_service.chromebook_info(request.args.get('serie', ''))
|
||||
except Exception as e:
|
||||
info, fout = None, f"Google gaf een fout: {e}"
|
||||
if fout:
|
||||
return jsonify(ok=False, fout=fout)
|
||||
# Naam van de leerling erbij als het e-mailadres in de leerlingenlijst staat
|
||||
emails = [g['email'] for g in info['gebruikers'] if g['email']]
|
||||
namen = {}
|
||||
if emails:
|
||||
try:
|
||||
# Gebruikersnaam staat er soms als volledig e-mailadres, soms enkel het deel vóór de @
|
||||
sleutels = list(dict.fromkeys([e.lower() for e in emails] + [e.split('@')[0].lower() for e in emails]))
|
||||
plek = ','.join(['%s'] * len(sleutels))
|
||||
for r in execute_query(f"SELECT Gebruikersnaam, Voornaam, Naam FROM Leerlingen WHERE Gebruikersnaam IN ({plek})",
|
||||
tuple(sleutels)):
|
||||
namen[(r['Gebruikersnaam'] or '').lower()] = f"{r.get('Voornaam') or ''} {r.get('Naam') or ''}".strip()
|
||||
except Exception:
|
||||
pass
|
||||
for g in info['gebruikers']:
|
||||
g['naam'] = namen.get(g['email'].lower()) or namen.get(g['email'].split('@')[0].lower(), '')
|
||||
return jsonify(ok=True, info=info)
|
||||
|
||||
|
||||
#-----------------------------------------------------
|
||||
# Functie achter bewerk toestel knop
|
||||
#-----------------------------------------------------
|
||||
@devices_bp.route("/bewerk_toestel", methods=["POST"])
|
||||
@login_required
|
||||
@recht_vereist('toestellen_beheren')
|
||||
def bewerk_toestel():
|
||||
"""Verwerkt de POST-gegevens om een toestelrecord bij te werken."""
|
||||
|
||||
# Gebruik de gegevens uit het formulier
|
||||
toestel_id = request.form.get("toestel_id")
|
||||
schooleigen_id = (request.form.get("schooleigen_id") or "").strip() or None
|
||||
serienummer = request.form.get("serienummer")
|
||||
signpostnummer = request.form.get("signpostnummer")
|
||||
type_toestel = request.form.get("type")
|
||||
garantie = request.form.get("garantie")
|
||||
opmerking = request.form.get("opmerking")
|
||||
query = request.form.get("query", "")
|
||||
|
||||
if not toestel_id:
|
||||
flash("Fout: Uniek nummer ontbreekt. Kan toestel niet bijwerken.", "error")
|
||||
return redirect(url_for("devices.inventaris_toestellen"))
|
||||
|
||||
try:
|
||||
tst.lees_datum(garantie) # eerst valideren, zodat er niets half bewaard wordt
|
||||
execute_query("UPDATE Toestellen SET Schooleigen_ID = %s, Serienummer = %s, Type = %s WHERE Toestel_ID = %s",
|
||||
(schooleigen_id, (serienummer or '').strip() or None, (type_toestel or '').strip() or None,
|
||||
toestel_id), commit=True)
|
||||
tst.bewaar_signpost(toestel_id, signpostnummer, garantie)
|
||||
tst.voeg_opmerking_toe(toestel_id, opmerking, session.get('user'))
|
||||
flash(f"Toestel {schooleigen_id or serienummer} succesvol bijgewerkt.", "success")
|
||||
|
||||
except ValueError as err:
|
||||
flash(str(err), "error")
|
||||
except Exception as err:
|
||||
flash(f"Databasefout bij het bewerken van het toestel: {err}", "error")
|
||||
|
||||
# BELANGRIJK: Gebruik de blueprint prefix 'devices.' in de redirect
|
||||
return redirect(url_for("devices.inventaris_toestellen", query=query))
|
||||
|
||||
|
||||
#-------------------------------------------------
|
||||
# --- ROUTE EIGENDOM (koppeling toestel-leerling) ---
|
||||
#-------------------------------------------------
|
||||
@devices_bp.route("/eigendom", methods=["GET", "POST"])
|
||||
@login_required
|
||||
@recht_vereist('eigendom_bekijken')
|
||||
def inventaris_eigendom():
|
||||
query = (request.form.get("query") or request.args.get("query") or "").strip()
|
||||
|
||||
# Alle huidige leerlingen, met het toestel dat ze nu hebben (of zonder toestel). Vertrokken leerlingen enkel
|
||||
# als ze nog een toestel hebben. Een leerling met twee toestellen staat er twee keer in.
|
||||
# Met zoekterm: elk woord moet voorkomen in internnummer, naam, gebruikersnaam, SS-nummer, toestel of opmerkingen.
|
||||
try:
|
||||
from services.sync_service import zorg_vertrokken_kolom
|
||||
zorg_vertrokken_kolom()
|
||||
vertrokken = "l.Vertrokken_op IS NULL OR "
|
||||
except Exception:
|
||||
vertrokken = ""
|
||||
sql = f"""
|
||||
SELECT * FROM (
|
||||
SELECT e.Eigendom_ID, l.Internnummer AS internnummer, l.Voornaam, l.Naam, l.Gebruikersnaam, l.SS_nummer,
|
||||
t.Schooleigen_ID AS kamxm_nummer, t.Serienummer, e.Datum_toekenning,
|
||||
IFNULL(e.Status, 0) AS locked, (e.Eigendom_ID IS NOT NULL) AS heeft_toestel,
|
||||
(SELECT GROUP_CONCAT(o.Opmerking ORDER BY o.Timestamp SEPARATOR ' | ')
|
||||
FROM Opmerkingen o WHERE o.Toestel_ID = t.Toestel_ID) AS opmerkingen
|
||||
FROM Leerlingen l
|
||||
LEFT JOIN Eigendom e ON e.Internnummer = l.Internnummer AND e.Datum_inlevering IS NULL
|
||||
LEFT JOIN Toestellen t ON t.Toestel_ID = e.Toestel_ID
|
||||
WHERE {vertrokken}e.Eigendom_ID IS NOT NULL
|
||||
) x
|
||||
"""
|
||||
woorden = query.split()[:5]
|
||||
if woorden:
|
||||
sql += " WHERE " + " AND ".join(
|
||||
["LOWER(CONCAT_WS(' ', internnummer, Voornaam, Naam, Gebruikersnaam, SS_nummer, IFNULL(kamxm_nummer, ''), "
|
||||
"IFNULL(Serienummer, ''), IFNULL(opmerkingen, ''))) LIKE %s"] * len(woorden))
|
||||
sql += " ORDER BY heeft_toestel DESC, Datum_toekenning DESC, Naam, Voornaam, internnummer"
|
||||
results = execute_query(sql, tuple(f"%{w.lower()}%" for w in woorden))
|
||||
|
||||
return render_template("inventaris.html", results=results, query=query)
|
||||
|
||||
@devices_bp.route("/eigendom/historie/<internnummer>", methods=["GET"])
|
||||
@login_required
|
||||
@recht_vereist('eigendom_bekijken')
|
||||
def eigendom_historie(internnummer):
|
||||
"""Alle toestellen die een leerling ooit gehad heeft (ook ingeleverde), nieuwste eerst."""
|
||||
from datetime import date
|
||||
rows = execute_query("""
|
||||
SELECT e.Eigendom_ID, e.Datum_toekenning, e.Datum_inlevering, IFNULL(e.Status, 0) AS locked,
|
||||
t.Schooleigen_ID, t.Serienummer, t.Type,
|
||||
(SELECT GROUP_CONCAT(o.Opmerking ORDER BY o.Timestamp SEPARATOR ' | ')
|
||||
FROM Opmerkingen o WHERE o.Toestel_ID = t.Toestel_ID) AS opmerkingen
|
||||
FROM Eigendom e INNER JOIN Toestellen t ON t.Toestel_ID = e.Toestel_ID
|
||||
WHERE e.Internnummer = %s
|
||||
ORDER BY e.Datum_inlevering IS NOT NULL, e.Datum_toekenning DESC, e.Eigendom_ID DESC""", (internnummer,)) or []
|
||||
leerling = (execute_query("SELECT Voornaam, Naam, Gebruikersnaam FROM Leerlingen WHERE Internnummer = %s",
|
||||
(internnummer,)) or [{}])[0]
|
||||
|
||||
def d(x):
|
||||
return x.strftime('%d-%m-%Y') if hasattr(x, 'strftime') else (str(x) if x else '')
|
||||
|
||||
uit = []
|
||||
for r in rows:
|
||||
begin, einde = r['Datum_toekenning'], r['Datum_inlevering']
|
||||
dagen = None
|
||||
if hasattr(begin, 'toordinal'):
|
||||
dagen = ((einde if hasattr(einde, 'toordinal') else date.today()) - begin).days
|
||||
uit.append({'toestel': r['Schooleigen_ID'] or '', 'serienummer': r['Serienummer'] or '', 'type': r['Type'] or '',
|
||||
'van': d(begin), 'tot': d(einde), 'huidig': not einde, 'dagen': dagen,
|
||||
'geblokkeerd': int(r['locked'] or 0) == 1, 'opmerkingen': r['opmerkingen'] or ''})
|
||||
return jsonify(ok=True, internnummer=internnummer,
|
||||
naam=f"{leerling.get('Voornaam') or ''} {leerling.get('Naam') or ''}".strip(),
|
||||
gebruikersnaam=leerling.get('Gebruikersnaam') or '', toestellen=uit)
|
||||
|
||||
|
||||
@devices_bp.route("/inventaris/historie/<int:toestel_id>", methods=["GET"])
|
||||
@login_required
|
||||
@recht_vereist('toestellen_beheren', 'eigendom_bekijken')
|
||||
def toestel_historie(toestel_id):
|
||||
"""Alle leerlingen die dit toestel ooit gehad hebben, nieuwste eerst."""
|
||||
from datetime import date
|
||||
rows = execute_query("""
|
||||
SELECT e.Internnummer, e.Datum_toekenning, e.Datum_inlevering, IFNULL(e.Status, 0) AS locked,
|
||||
l.Voornaam, l.Naam, l.Gebruikersnaam
|
||||
FROM Eigendom e LEFT JOIN Leerlingen l ON l.Internnummer = e.Internnummer
|
||||
WHERE e.Toestel_ID = %s
|
||||
ORDER BY e.Datum_inlevering IS NOT NULL, e.Datum_toekenning DESC, e.Eigendom_ID DESC""", (toestel_id,)) or []
|
||||
|
||||
def d(x):
|
||||
return x.strftime('%d-%m-%Y') if hasattr(x, 'strftime') else (str(x) if x else '')
|
||||
|
||||
uit = []
|
||||
for r in rows:
|
||||
begin, einde = r['Datum_toekenning'], r['Datum_inlevering']
|
||||
dagen = ((einde if hasattr(einde, 'toordinal') else date.today()) - begin).days if hasattr(begin, 'toordinal') else None
|
||||
uit.append({'internnummer': r['Internnummer'], 'naam': f"{r['Voornaam'] or ''} {r['Naam'] or ''}".strip(),
|
||||
'gebruikersnaam': r['Gebruikersnaam'] or '', 'van': d(begin), 'tot': d(einde), 'huidig': not einde,
|
||||
'dagen': dagen, 'geblokkeerd': int(r['locked'] or 0) == 1})
|
||||
return jsonify(ok=True, eigenaars=uit)
|
||||
|
||||
|
||||
#-------------------------------------------------
|
||||
# --- ROUTE UITDELEN ---
|
||||
#-------------------------------------------------
|
||||
@devices_bp.route('/uitdelen', methods=['GET'])
|
||||
@login_required
|
||||
@recht_vereist('toestellen_uitdelen')
|
||||
def uitdelen_page():
|
||||
return render_template('uitdelen.html', message=None)
|
||||
|
||||
@devices_bp.route('/zoek_leerlingen', methods=['GET'])
|
||||
@login_required
|
||||
@recht_vereist('toestellen_uitdelen', 'toestellen_beheren')
|
||||
def zoek_leerlingen():
|
||||
"""Suggesties voor het veld 'Internnummer' (max. 15): op internnummer, naam, voornaam of gebruikersnaam.
|
||||
Geeft ook het toestel dat de leerling nu heeft."""
|
||||
q = (request.args.get('q') or '').strip()
|
||||
if not q:
|
||||
return jsonify([])
|
||||
woorden = q.split()[:4]
|
||||
# Elk woord moet voorkomen in internnummer, gebruikersnaam of naam (in eender welke volgorde)
|
||||
# LOWER(): zoeken zonder onderscheid tussen hoofd- en kleine letters, ongeacht de collation van CONCAT_WS
|
||||
voorwaarden = " AND ".join(["LOWER(CONCAT_WS(' ', l.Internnummer, l.SS_nummer, l.Gebruikersnaam, l.Voornaam, l.Naam)) LIKE %s"] * len(woorden))
|
||||
rows = execute_query(
|
||||
"SELECT l.Internnummer, l.Voornaam, l.Naam, l.Gebruikersnaam, "
|
||||
" GROUP_CONCAT(COALESCE(t.Schooleigen_ID, t.Serienummer) SEPARATOR ', ') AS Toestellen "
|
||||
"FROM Leerlingen l "
|
||||
"LEFT JOIN Eigendom e ON e.Internnummer = l.Internnummer AND e.Datum_inlevering IS NULL "
|
||||
"LEFT JOIN Toestellen t ON t.Toestel_ID = e.Toestel_ID "
|
||||
f"WHERE {voorwaarden} "
|
||||
"GROUP BY l.Internnummer, l.Voornaam, l.Naam, l.Gebruikersnaam "
|
||||
"ORDER BY (l.Internnummer LIKE %s) DESC, l.Naam, l.Voornaam LIMIT 15",
|
||||
tuple(f"%{w.lower()}%" for w in woorden) + (f"{q}%",))
|
||||
return jsonify([{'intern': r['Internnummer'], 'naam': f"{r['Voornaam'] or ''} {r['Naam'] or ''}".strip(),
|
||||
'gebruiker': r['Gebruikersnaam'] or '', 'toestellen': r['Toestellen'] or ''} for r in rows])
|
||||
|
||||
|
||||
@devices_bp.route('/zoek_toestellen', methods=['GET'])
|
||||
@login_required
|
||||
@recht_vereist('toestellen_uitdelen', 'toestellen_beheren')
|
||||
def zoek_toestellen():
|
||||
"""Suggesties voor het zoekveld 'Toestelnummer' (max. 15): op toestelnummer of serienummer."""
|
||||
q = (request.args.get('q') or '').strip()
|
||||
if len(q) < 1:
|
||||
return jsonify([])
|
||||
# Uitdelen: beschikbare toestellen eerst; inleveren: uitgeleende toestellen eerst
|
||||
voorkeur = {'uitgeleend': "e.Internnummer IS NULL, ", 'beschikbaar': "e.Internnummer IS NOT NULL, "}.get(
|
||||
request.args.get('voorkeur'), "")
|
||||
rows = execute_query(
|
||||
"SELECT t.Schooleigen_ID, t.Serienummer, t.Type, t.Status, e.Internnummer "
|
||||
"FROM Toestellen t LEFT JOIN Eigendom e ON e.Toestel_ID = t.Toestel_ID AND e.Datum_inlevering IS NULL "
|
||||
"WHERE t.Schooleigen_ID LIKE %s OR t.Serienummer LIKE %s "
|
||||
f"ORDER BY {voorkeur}t.Schooleigen_ID IS NULL, (t.Schooleigen_ID LIKE %s) DESC, t.Schooleigen_ID, t.Serienummer "
|
||||
"LIMIT 15",
|
||||
(f"%{q}%", f"%{q}%", f"{q}%"))
|
||||
return jsonify([{'nummer': r['Schooleigen_ID'] or '', 'serie': r['Serienummer'] or '', 'type': r['Type'] or '',
|
||||
'status': r['Status'] or '', 'bij': r['Internnummer'] or ''} for r in rows])
|
||||
|
||||
# Functie van knop Ingeven (Uitdelen)
|
||||
@devices_bp.route('/uitdelen_submit', methods=['POST'])
|
||||
@login_required
|
||||
@recht_vereist('toestellen_uitdelen')
|
||||
def uitdelen_submit():
|
||||
internnummer = request.form.get('internnummer')
|
||||
#TODO--------------------------------------------
|
||||
try:
|
||||
internnummer= int(internnummer)
|
||||
|
||||
except:
|
||||
internnummer = str(internnummer)
|
||||
#--------------------------------------------
|
||||
serienummer = request.form.get('serienummer')
|
||||
action_type = request.form.get('action')
|
||||
|
||||
success = CIP_uitdelen.uitdelen(internnummer, serienummer)
|
||||
|
||||
if success:
|
||||
flash(f"Toestel met nummer {serienummer} succesvol uitgedeeld!", "success")
|
||||
else:
|
||||
# Als het mislukt, tonen we de foutmelding die in de service is gezet
|
||||
flash(CIP_uitdelen.foutmelding, "error")
|
||||
|
||||
return redirect(url_for('devices.uitdelen_page'))
|
||||
|
||||
#-------------------------------------------------
|
||||
# --- ROUTE INLEVEREN ---
|
||||
#-------------------------------------------------
|
||||
@devices_bp.route('/inleveren', methods=['GET'])
|
||||
@login_required
|
||||
@recht_vereist('toestellen_uitdelen')
|
||||
def inleveren_page():
|
||||
return render_template('inleveren.html', message=None)
|
||||
|
||||
# Functie van knop Ingeven (Inleveren)
|
||||
@devices_bp.route('/inleveren_submit', methods=['POST'])
|
||||
@login_required
|
||||
@recht_vereist('toestellen_uitdelen')
|
||||
def inleveren_submit():
|
||||
internnummer = request.form.get('internnummer')
|
||||
serienummer = request.form.get('serienummer')
|
||||
tab2_schade_text = request.form.get('schade_type', "")
|
||||
action_type = request.form.get('action')
|
||||
try:
|
||||
internnummer= int(internnummer)
|
||||
|
||||
except:
|
||||
internnummer = str(internnummer)
|
||||
if action_type:
|
||||
CIP_inleveren.Inleveren(internnummer, serienummer, action_type, tab2_schade_text)
|
||||
|
||||
return render_template('inleveren.html', message=CIP_inleveren.foutmelding)
|
||||
|
||||
#---------------------------------
|
||||
# --- STATUS UPDATES ---
|
||||
#---------------------------------
|
||||
@devices_bp.route("/update_toestel_status", methods=["POST"])
|
||||
@login_required
|
||||
@recht_vereist('toestellen_beheren')
|
||||
def wijzig_status():
|
||||
toestel_id = request.form.get("toestel_id")
|
||||
new_status = request.form.get("new_status")
|
||||
query = request.form.get("query", "")
|
||||
|
||||
if toestel_id and new_status in ["Beschikbaar", "Uitgeleend", "Defect"]:
|
||||
execute_query("UPDATE Toestellen SET Status = %s WHERE Toestel_ID = %s", (new_status, toestel_id), commit=True)
|
||||
rij = execute_query("SELECT Schooleigen_ID, Serienummer FROM Toestellen WHERE Toestel_ID = %s", (toestel_id,))
|
||||
label = (rij[0]['Schooleigen_ID'] or rij[0]['Serienummer']) if rij else toestel_id
|
||||
flash(f"Status van {label} gewijzigd naar {new_status}", "success")
|
||||
|
||||
return redirect(url_for("devices.inventaris_toestellen", query=query))
|
||||
|
||||
#-------------------------------------------------
|
||||
# --- ROUTE BLOKKEREN / DEBLOKKEREN ---
|
||||
#-------------------------------------------------
|
||||
@devices_bp.route('/eigendom/bewerken', methods=['POST'])
|
||||
@login_required
|
||||
@recht_vereist('toestellen_uitdelen')
|
||||
def eigendom_bewerken():
|
||||
"""Een lopende toekenning aanpassen: andere leerling en/of ander toestel."""
|
||||
query = request.form.get('query', '')
|
||||
terug = redirect(url_for('devices.inventaris_eigendom', query=query or None))
|
||||
eigendom_id = request.form.get('eigendom_id', type=int)
|
||||
rows = execute_query("SELECT * FROM Eigendom WHERE Eigendom_ID = %s AND Datum_inlevering IS NULL", (eigendom_id,))
|
||||
if not rows:
|
||||
flash("Deze toekenning bestaat niet (meer) of is al ingeleverd.", "error")
|
||||
return terug
|
||||
oud = rows[0]
|
||||
|
||||
leerling = tst.zoek_leerling((request.form.get('internnummer') or '').strip())
|
||||
if not leerling:
|
||||
flash("Internnummer niet gevonden in de leerlingenlijst.", "error")
|
||||
return terug
|
||||
toestel = tst.zoek_toestel((request.form.get('toestelnummer') or '').strip())
|
||||
if not toestel:
|
||||
flash("Toestelnummer (of serienummer) niet gevonden in de toestellenlijst.", "error")
|
||||
return terug
|
||||
|
||||
nieuw_toestel = toestel['Toestel_ID'] != oud['Toestel_ID']
|
||||
if nieuw_toestel:
|
||||
bezet = execute_query("SELECT Internnummer FROM Eigendom WHERE Toestel_ID = %s AND Datum_inlevering IS NULL "
|
||||
"AND Eigendom_ID <> %s", (toestel['Toestel_ID'], eigendom_id))
|
||||
if bezet:
|
||||
flash(f"Toestel {toestel.get('Schooleigen_ID') or toestel.get('Serienummer')} is al uitgeleend aan "
|
||||
f"{bezet[0]['Internnummer']}. Lever het eerst in.", "error")
|
||||
return terug
|
||||
if not nieuw_toestel and str(leerling['Internnummer']) == str(oud['Internnummer']):
|
||||
flash("Er is niets gewijzigd.", "info")
|
||||
return terug
|
||||
|
||||
execute_query("UPDATE Eigendom SET Internnummer = %s, Toestel_ID = %s WHERE Eigendom_ID = %s",
|
||||
(leerling['Internnummer'], toestel['Toestel_ID'], eigendom_id), commit=True)
|
||||
if nieuw_toestel:
|
||||
# Het vorige toestel is weer vrij, het nieuwe staat op uitgeleend
|
||||
execute_query("UPDATE Toestellen SET Status = 'Beschikbaar' WHERE Toestel_ID = %s AND Status = 'Uitgeleend'",
|
||||
(oud['Toestel_ID'],), commit=True)
|
||||
execute_query("UPDATE Toestellen SET Status = 'Uitgeleend' WHERE Toestel_ID = %s", (toestel['Toestel_ID'],),
|
||||
commit=True)
|
||||
naam = f"{leerling.get('Voornaam') or ''} {leerling.get('Naam') or ''}".strip() or leerling['Internnummer']
|
||||
flash(f"Toekenning aangepast: {toestel.get('Schooleigen_ID') or toestel.get('Serienummer')} → {naam} "
|
||||
f"({leerling['Internnummer']}).", "success")
|
||||
return terug
|
||||
|
||||
|
||||
@devices_bp.route('/toggle_lock', methods=['POST'])
|
||||
@login_required
|
||||
@recht_vereist('toestel_blokkeren')
|
||||
def toggle_lock():
|
||||
# Haal de gegevens op uit het formulier en strip ze direct
|
||||
internnummer = request.form.get('internnummer', '').strip()
|
||||
query = request.form.get('query', '')
|
||||
|
||||
# 1. Haal de huidige leerling/toestel koppeling op
|
||||
rows = execute_query(
|
||||
"SELECT e.Eigendom_ID, e.Status, e.Toestel_ID, t.Schooleigen_ID, t.Serienummer FROM Eigendom e "
|
||||
"JOIN Toestellen t ON t.Toestel_ID = e.Toestel_ID "
|
||||
"WHERE e.Internnummer = %s AND e.Datum_inlevering IS NULL ORDER BY e.Datum_toekenning DESC",
|
||||
(internnummer,))
|
||||
|
||||
if not rows:
|
||||
flash("Geen gegevens gevonden in de Eigendom tabel.", "error")
|
||||
return redirect(url_for('devices.inventaris_eigendom', query=query))
|
||||
|
||||
current_status = int(rows[0]['Status']) if rows[0]['Status'] is not None else 0
|
||||
eigendom_id = rows[0]['Eigendom_ID']
|
||||
kamxm_nummer = str(rows[0]['Schooleigen_ID'] or '').strip()
|
||||
|
||||
# 2. Serienummer van het toestel
|
||||
serienummer = str(rows[0]['Serienummer']).strip() if rows[0]['Serienummer'] else None
|
||||
|
||||
if not serienummer:
|
||||
flash(f"Kan geen serienummer vinden voor toestel {kamxm_nummer}.", "error")
|
||||
return redirect(url_for('devices.inventaris_eigendom', query=query))
|
||||
|
||||
# 3. Vraag het Device ID op bij Google met een 'gecleand' serienummer
|
||||
# We gebruiken strip() om de \t (tab) en spaties te verwijderen
|
||||
clean_serial = serienummer.strip()
|
||||
device_id, error = google_service.get_device_id_by_serial(clean_serial)
|
||||
|
||||
if device_id:
|
||||
# Als status 0 is (unlocked) -> moet hij naar disable (True)
|
||||
should_lock = (current_status == 0)
|
||||
success, api_message = google_service.toggle_device_status(device_id, lock=should_lock)
|
||||
|
||||
if success:
|
||||
new_status = 1 if should_lock else 0
|
||||
execute_query("UPDATE Eigendom SET Status=%s WHERE Eigendom_ID=%s", (new_status, eigendom_id), commit=True)
|
||||
try:
|
||||
tst.zet_geblokkeerd(rows[0]['Toestel_ID'], should_lock)
|
||||
except Exception as e:
|
||||
sys.stderr.write(f"Toestellen.Geblokkeerd bijwerken: {e}\n")
|
||||
flash(api_message, "success")
|
||||
else:
|
||||
flash(f"Google API Fout: {api_message}", "error")
|
||||
else:
|
||||
# Hier wordt de 'Invalid Input' nu voorkomen of getoond als het serienummer echt fout is
|
||||
flash(f"Google Zoekfout: {error}", "error")
|
||||
|
||||
return redirect(url_for('devices.inventaris_eigendom', query=query))
|
||||
|
||||
@devices_bp.route('/inventaris/blokkeren', methods=['POST'])
|
||||
@login_required
|
||||
@recht_vereist('toestel_blokkeren')
|
||||
def toestel_blokkeren():
|
||||
"""Blokkeert of deblokkeert één Chromebook in Google (knop in Alle toestellen).
|
||||
actie = 'blokkeren' of 'deblokkeren'. Met fetch (X-Requested-With) komt er JSON terug, anders een redirect."""
|
||||
from rechten import log
|
||||
ajax = request.headers.get('X-Requested-With') == 'fetch'
|
||||
query = request.form.get('query', '')
|
||||
actie = request.form.get('actie', '')
|
||||
|
||||
def klaar(ok, melding, geblokkeerd=None):
|
||||
if ajax:
|
||||
return jsonify(ok=ok, melding=melding, geblokkeerd=geblokkeerd)
|
||||
flash(melding, "success" if ok else "error")
|
||||
return redirect(url_for('devices.inventaris_toestellen', query=query))
|
||||
|
||||
if actie not in ('blokkeren', 'deblokkeren'):
|
||||
return klaar(False, "Onbekende actie.")
|
||||
blokkeren = actie == 'blokkeren'
|
||||
try:
|
||||
toestel_id = int(request.form.get('toestel_id', ''))
|
||||
except ValueError:
|
||||
return klaar(False, "Onbekend toestel.")
|
||||
rows = execute_query("SELECT Toestel_ID, Schooleigen_ID, Serienummer FROM Toestellen WHERE Toestel_ID = %s",
|
||||
(toestel_id,))
|
||||
if not rows:
|
||||
return klaar(False, "Toestel niet gevonden.")
|
||||
t = rows[0]
|
||||
label = t['Schooleigen_ID'] or t['Serienummer'] or str(toestel_id)
|
||||
if not (t['Serienummer'] or '').strip():
|
||||
return klaar(False, f"Toestel {label} heeft geen serienummer; vul het eerst in via ✏️ Bewerken.")
|
||||
try:
|
||||
toestel, fout = google_service.zoek_chromebook(t['Serienummer'])
|
||||
except Exception as e:
|
||||
toestel, fout = None, f"Google gaf een fout: {e}"
|
||||
if not toestel:
|
||||
return klaar(False, fout)
|
||||
status = toestel['status']
|
||||
if status == 'DEPROVISIONED':
|
||||
return klaar(False, f"Toestel {label} is uitgeschreven in Google Admin en kan niet (ge)blokkeerd worden.")
|
||||
gewenst = 'DISABLED' if blokkeren else 'ACTIVE'
|
||||
if status != gewenst:
|
||||
ok, melding = google_service.toggle_device_status(toestel['id'], lock=blokkeren)
|
||||
if not ok:
|
||||
return klaar(False, melding)
|
||||
try:
|
||||
tst.zet_geblokkeerd(toestel_id, blokkeren)
|
||||
except Exception as e:
|
||||
sys.stderr.write(f"Toestellen.Geblokkeerd bijwerken: {e}\n")
|
||||
log(f"Chromebook {label} {'geblokkeerd' if blokkeren else 'gedeblokkeerd'}")
|
||||
if status == gewenst:
|
||||
return klaar(True, f"Toestel {label} was in Google al {'geblokkeerd' if blokkeren else 'actief'}.", blokkeren)
|
||||
return klaar(True, f"Toestel {label} is {'geblokkeerd' if blokkeren else 'gedeblokkeerd'}.", blokkeren)
|
||||
|
||||
|
||||
#-------------------------------------------------
|
||||
# --- HELPER gebruikt in Toggle lock functie ---
|
||||
#-------------------------------------------------
|
||||
def get_serienummer_from_kamxm(kamxm_nummer):
|
||||
if not kamxm_nummer:
|
||||
return None
|
||||
result = execute_query("SELECT Serienummer FROM Toestellen WHERE Schooleigen_ID=%s", (kamxm_nummer,))
|
||||
if result and result[0]['Serienummer']:
|
||||
# Alvast een basis strip()
|
||||
return str(result[0]['Serienummer']).strip()
|
||||
return None
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user