Lizenz-Gate: Schueler ohne Lizenz gesperrt + Lehrer-Cockpit-Warnung

Teil 1 — Schueler-Lizenz-Gate (gilt fuer alle Klassen-Schueler ohne gueltige
Lizenz = zugewiesen, nicht storniert, nicht abgelaufen):
- Server-Gate zentral im Front-Controller (index.php + php/lib/license_gate.php):
  eingeloggte Klassen-Schueler ohne Lizenz sehen bei JEDER Sim-Route statt der
  Simulation eine Hinweis-Seite "Deine Lehrperson muss dir noch eine gueltige
  Lizenz zuweisen". Schnell-Ausstieg fuer Nicht-Schueler/Nicht-Sim (kein Cookie
  -> keine DB-Abfrage). Autodidakt ohne Klasse: nicht lizenzpflichtig.
- modules.php?student=1 liefert 'licensed'; Schueler-Cockpit (schueler.html)
  zeigt oben die Meldung und sperrt die Start-Buttons.

Teil 2 — Lehrer-Cockpit:
- Rote Warnleiste ganz oben, wenn Schueler:innen ohne gueltige Lizenz in der
  Klasse sind (+ Link "Jetzt zuweisen"). licenses?class_id=X liefert dazu die
  Liste 'unlicensed'.
- Lizenzen-Lasche: neue Sektion "Ohne gueltige Lizenz" mit Einzel- und
  Sammel-Zuweisung (auto_assign) — sonst fuehrt die Warnung ins Leere.

Kunden-Vereinfachung (Feedback): keine Auto-Erkennung/Dedup mehr (Ansprech-
partner koennen wechseln). Kunde = Freitext "Name/Verlag/Schule" + Mailadresse,
je Bestellung gespeichert. Editierbares Kundennummer-Feld + Autocomplete entfernt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-26 16:15:16 +02:00
parent eea5688c76
commit ff889865e1
7 changed files with 197 additions and 29 deletions
+6 -12
View File
@@ -145,16 +145,12 @@
<div class="hint-s">Geht in die Mail (für die Rechnung des Kunden).</div>
</div>
<div>
<label>Kunde — E-Mail</label>
<input type="email" id="iss-email" placeholder="kunde@schule.at" list="cust-hints" oninput="custSuggest(this.value)">
<label>Kunde — Name / Verlag / Schule</label>
<input type="text" id="iss-name" placeholder="z. B. Verlag XY · VS Musterstadt">
</div>
<div>
<label>Kundennummer (intern)</label>
<input type="text" id="iss-custno" placeholder="optional">
</div>
<div>
<label>Name / Verlag / Schule</label>
<input type="text" id="iss-name" placeholder="optional">
<label>E-Mail</label>
<input type="email" id="iss-email" placeholder="kunde@schule.at">
</div>
<div>
<label>Gültig von</label>
@@ -166,7 +162,6 @@
<div class="hint-s">Standard: 1. Okt. Folgejahr (merkt sich die letzte Wahl).</div>
</div>
</div>
<datalist id="cust-hints"></datalist>
<datalist id="order-hints"></datalist>
<div style="margin-top:.6rem;display:flex;align-items:center;gap:.7rem;flex-wrap:wrap">
<label style="display:flex;align-items:center;gap:.4rem;font-size:.75rem;font-weight:600;color:#4a4a4a">
@@ -446,7 +441,6 @@ async function issueBatch(){
var type = document.getElementById('iss-type').value;
var order = document.getElementById('iss-order').value.trim();
var email = document.getElementById('iss-email').value.trim();
var custno = document.getElementById('iss-custno').value.trim();
var name = document.getElementById('iss-name').value.trim();
var vf = document.getElementById('iss-from').value;
var vu = document.getElementById('iss-until').value;
@@ -454,11 +448,11 @@ async function issueBatch(){
var msg = document.getElementById('iss-msg');
if(count<1||count>5000){ msg.textContent='Anzahl 15000.'; return; }
var delivered = calcDelivered(count, bonus);
var who = name||email||custno||'(kein Kunde)';
var who = name||email||'(kein Kunde)';
if(count>=100 && !confirm(count+' bestellt → '+delivered+' Codes im Paket für „'+who+'"'+(order?' (Best.-Nr. '+order+')':'')+', gültig bis '+fmtDate(vu)+', Typ '+type+'.\n\nWirklich erzeugen?')) return;
msg.textContent = 'Erzeuge …';
var r = await api('licenses', {action:'issue_batch', count:count, bonus:bonus, sales_type:type, order_no:order,
customer:{email:email, customer_no:custno, name:name}, valid_from:vf, valid_until:vu});
customer:{email:email, name:name}, valid_from:vf, valid_until:vu});
if(r.error){ msg.innerHTML='<span style="color:#c07a6b">'+esc(r.error)+'</span>'; return; }
msg.innerHTML='<span style="color:#5a8a5e">✓ '+r.count+' Codes erzeugt (Bestellung #'+r.internal_no+', bestellt '+r.ordered+').</span>';
lastIssue = {batch_id:r.batch_id, internal_no:r.internal_no, ordered:r.ordered, codes:r.codes, count:r.count, valid_until:r.valid_until, token:r.redeem_token, order_no:order, email:email};