Skip to content

Releases: uuuxy/bibliothek

v3.0.0

Choose a tag to compare

@github-actions github-actions released this 17 Sep 19:14

Worum es in diesem Stand geht

Zwei große Stücke. Erstens führt das Programm alle Leser in einer Datei — Schülerinnen, Schüler und Kollegium stehen in derselben Tabelle, haben eine Akte, einen Ausweis aus einem Nummernkreis und tauchen in derselben Suche auf. Zweitens sind die Punkte abgearbeitet, die das Medienzentrum am 16.09.2026 in seiner Sichtung aufgeschrieben hat: Zugangs- und Abgangsbuch, der Bestand in der Trefferliste, ein gerechneter Ersatzwert statt einer festen Zahl, Filter und Sortierung in der Leserdatei — und die Ausweise und Etiketten aus der Zeit davor werden wieder gelesen.

Dazu kommt die Theke ohne Netz: Sie nimmt jetzt jede Buchform an, merkt sich einen Ausweis auch ohne Verbindung und bucht nach, was liegengeblieben ist.

Geänderte Abläufe

  • Leserdatei statt Schülerdatei. Kollegium und Schülerschaft stehen in einer Liste mit der Spalte „Art". Ein Kollege bekommt eine Akte mit seinen Ausleihen, eine Schul-E-Mail (Pflicht — damit kann niemand doppelt entstehen) und keine Rückgabefrist: Er wird nie überfällig, und gegen ihn entsteht keine Forderung. Doppelt stehende Kollegen lassen sich zusammenführen. „Neuen Leser anlegen" fragt zuerst nach der Art. Die Liste lässt sich nach Jahrgang filtern und über die Spaltenköpfe sortieren — beides am Server, damit nichts hinter der Kappung bei 500 Zeilen verschwindet; das Kollegium bleibt dabei oben.
  • Ein Ausweis, ein Leser. Jeder Ausweis trägt die Vorsilbe A-, und die Nummern kommen aus einer Quelle. Die Namenssuche an der Theke findet jeden Leser, nicht nur Schüler. Alte Nummern (S-, L-) werden weiter gelesen und nie neu vergeben.
  • Zugangs- und zwei Bestandsbücher. Unter „System → Bestandsbücher" stehen zwei Reiter: Welche Bücher sind im Zeitraum in den Bestand gekommen (mit Lieferant) und welche hinausgegangen (mit Grund) — getrennt nach Land und Schulträger, jedes mit einem Blatt zum Abheften. Beide Blätter sagen ausdrücklich, was NICHT darauf steht: Abgänge ohne bekanntes Datum, Exemplare ohne hinterlegte Bestellung und solche, die mit ihrem Titel gelöscht wurden.
  • Schadensersatz wird gerechnet, nicht geschätzt. Am Titel stehen Listenpreis und Auflage, am Exemplar ein Prozentwert für den Zustand. Daraus entsteht der Ersatzbetrag samt Herleitung — an jedem Exemplar in der Buchakte, im Melde-Dialog und im Bescheid, überall mit derselben Rechnung. Für Lernmittel gilt die Staffel des Landes, für die Schülerbücherei der Neuwert ohne Altersabschlag. In den Einstellungen wählt die Schule, ob der heutige Listenpreis oder der bezahlte Einkaufspreis die Grundlage ist.
  • Trefferliste an der Theke. Neben jedem Buch steht, wie viele Stück da sind und wie viele frei. Ein Titel ohne Exemplare sagt das, statt jemanden ins Regal zu schicken.
  • Theke ohne Netz. Sie nimmt alle Buchformen an, nicht nur B-; sie wird geleert, aber nicht gesperrt; kein Vollbild hält sie mehr an. Ein Ausweis ohne Verbindung wird gemerkt, und die Bücher gehen an ihn. Was beim Nachbuchen nicht durchging, steht danach an der Theke.
  • Rolle „Leitung". Vergebbar, mit allen Rechten außer den beiden Türen der Systempflege. Das Kollegium ist dagegen keine Rolle mehr, sondern der Grundzustand.
  • Kataloge. Die Auflage ist ein eigenes Feld; beim Anlegen warnt eine Dublettenkontrolle, wenn dieselbe Nummer in anderer Schreibweise schon im Katalog steht.

Behobene Fehler

  • Ausweise und Etiketten von früher waren unlesbar. Der gedruckte Strichcode trug eine Ziffer zu viel (ein Prüfzeichen, das im Code steckt). Gedruckt wird jetzt ohne; alte Karten und Etiketten werden beim Scannen zusätzlich ohne Prüfzeichen nachgeschlagen. Auf dem Etikett steht wieder dieselbe Nummer, die der Scanner liest.
  • Die Kamera ging nicht. Sie startet wieder, an einer Stelle statt an zweien, mit dem Format unserer eigenen Ausweise — und sie sagt, was sie gerade tut, statt still zu bleiben. Ein Bild ohne Treffer wird von einer kaputten Erkennung unterschieden.
  • Kalendertage. Mahnlauf, Briefe, Fristen des Bescheids und die Volljährigkeit rechnen am Kalendertag der Schule, nicht an dem der Datenbank oder der Sitzung.
  • Zahlungsweg der Altbriefe. Für ein Schulbuch des Landes nennen sie das Konto statt Bargeld; für ein Büchereibuch sagen sie, dass die Bankverbindung des Trägers fehlt, statt sich einen Weg auszudenken.
  • Ein Büchereibuch wurde nach der falschen Staffel ersetzt — für ein zehn Jahre altes Buch nannte der Brief 1,40 € statt 14,00 €.
  • Rechte und Datenschutz: Massenlöschen von Titeln verlangt jetzt das Löschrecht; aus dem Papierkorb kommt kein Passbild mehr; der Freitext eines erledigten Schadensfalls fällt mit der Anonymisierung.
  • Leserdatei: Stammdaten eines Kollegen ließen sich nicht ändern, Sperre und Passbild kamen nicht an, ein markierter Kollege fiel lautlos vom Etikettenbogen.
  • Rasterdurchgang 17.09.: Das Abgangsbuch verlor Zeilen, wenn ein Titel gelöscht wurde; der Jahrgangsfilter gab eine abgeschnittene Liste als vollständig aus; die Anzeige des Ersatzwerts hing an einem deutschen Satz des Servers; der Ausdruck der Bestandsbücher konnte einen anderen Zeitraum abdecken als der Bildschirm.
  • Kleinigkeiten: Escape in einem offenen Auswahlfeld schließt nicht mehr den ganzen Dialog; „Erneut versuchen" erscheint nur, wenn es etwas zu wiederholen gibt; ein Klassenwechsel rechnet das Abgangsjahr neu; ausgesonderte Exemplare bekommen kein Etikett mehr.

Neue und geänderte Schnittstellen

  • GET /api/bestand/zugangsbuch und /pdf (neu) — Zugänge eines Zeitraums, nach Topf getrennt.
  • GET /api/bestand/abgangsbuch und /pdf (neu) — Abgänge eines Zeitraums, mit den beiden Zahlen darüber, was nicht auf der Liste steht.
  • GET /api/buecher/exemplare/{id}/ersatzwert-vorschlag (neu) — Betragsvorschlag samt Herleitung.
  • GET /api/jahrgaenge (neu) — die besetzten Jahrgänge für den Filter.
  • GET /api/schueler — zusätzlich jahrgang, sortierung und richtung; art=alle liefert die Leserdatei.
  • GET /api/buecher/titel/{id}/exemplare — je Exemplar Beschädigungsgrad, Ersatzwert und Herleitung.
  • PUT /api/buecher/exemplare/{id}/status — nimmt den Beschädigungsgrad entgegen und antwortet mit dem neuen Ersatzwert.
  • POST /api/buecher/exemplare/{id}/defekt und GET /api/readergroups sind entfallen.

Datenbank

Beim ersten Start spielt dieser Stand die Migrationen 121 bis 128 ein. Das passiert von allein; Daten gehen dabei keine verloren, und erfunden wird nichts.

Die größte Änderung ist der Umbau der Lesertabelle (123–125): schueler ist seither eine Sicht auf leser. Wer eigene Abfragen gegen die Datenbank fährt, sollte das wissen — ein Schreibzugriff auf schueler trifft beim Kollegium keine Zeile.

Neu hinzu kommen die Auflage und der Listenpreis am Titel (126, 127), der Beschädigungsgrad am Exemplar (127) und das Abgangsdatum samt Auslöser (128). Altbestand bleibt dabei leer statt geschätzt: Ein vor heute ausgesondertes Exemplar hat kein Abgangsdatum, und das Abgangsbuch schreibt die Zahl dieser Fälle unter die Liste.

Vor dem Aufspielen eine Sicherung ziehen — wie immer, hier aber mit acht Migrationen auf einmal.

Bekannte offene Punkte

  • Steht ein Kollege in der Warteschlange für ein Buch, rückt er möglicherweise nie nach: Die Abfrage dahinter kennt nur Schülerinnen und Schüler. Ob das jemanden betrifft, ist noch nicht an einer echten Datenbank gemessen (docs/OFFEN.md 5.19).
  • GO-2026-6452 (Panic im Excel-Leser) betrifft dieses Programm nachweislich nicht — der verwundbare Weg ist durch die Entpackgrenzen unerreichbar. Die Ausnahme steht mit Nachweis und Wiedervorlage in security/vuln-ausnahmen.json.

Docker-Image: ghcr.io/uuuxy/bibliothek:3.0.0 (Build im Workflow 'Docker Image veröffentlichen (ghcr.io)' zum selben Tag).

Commits seit v2.13.0

  • docs(release): die erste Zahl steigt bei einem Formwechsel — v3.0.0 ist das Beispiel (b6947c1)
  • docs(release): welche Zahl steigt — und warum v2.14.0 trotz Umbau eine Minor-Version ist (d6584ea)
  • fix(ci): der Nachweis einer Vuln-Ausnahme sieht nicht mehr aus wie eine Compiler-Meldung (0193737)
  • docs(offen): die Schwachstelle im Excel-Leser trifft uns nicht — mit Wiedervorlage (8053e50)
  • fix(sicherheit): GO-2026-6452 am Code nachgemessen statt geglaubt — und das Vuln-Gate lernt benannte Ausnahmen (2c636cc)
  • docs(offen): Rasterdurchgang über den 17.09.2026 — vier Funde behoben, eine Achse offen (479b431)
  • feat(menü): Zugangs- und Abgangsbuch stehen unter System, als ein Punkt "Bestandsbücher" (6a40612)
  • refactor(ersatzwert): die Oberfläche liest ein Feld statt eines deutschen Satzes (f7e0d10)
  • fix(leserdatei): eine gekappte Liste sagt es auch dann, wenn nach Jahrgang gefiltert ist (46dff85)
  • fix(abgangsbuch): was gelöscht wurde, steht unter der Liste — und das Blatt deckt den Zeitraum des Bildschirms ab (8be9457)
  • docs(offen): die neun PRs sind entschieden — übrig bleibt das Löschen der Branches (8312114)
  • docs(theke): warum CodeQL am Barcode-Schreiber XSS meldet — und warum es keins ist (42a12b4)
  • fix(cover): der Ausbruch aus uploads wird vom Betriebssystem verweigert, nicht von einer Textprüfung (8edd894)
  • fix(e2e): der Sitzungsablauf-Test wartet nicht mehr auf einen Knopf, der verschwunden ist (1dd6017)
  • feat(bestand): das Zugangsbuch — die Gegenrichtung, im selben Bauteil (9.3 d) (2388af2)
  • fix(abgangsbuch): Blatt und Bildschirm nennen den Topf mit demselben Wort (1939c3e)
  • docs(medienzentrum): die Antwort auf die Sichtung — und zwei Sätze, die in unserer Liste fehlten (1dea442)
  • docs(offen): der Block oben sagt, was der Abend gebracht hat (3791a49)
  • feat(bestand): das Abgangsbuch — Liste und Blatt zum Abheften (9.3 d) (2a41473)
  • feat(bestand): das Abgangsdatum am Exemplar — der fehlende Teil des Abgangsbuchs (Migration 128) (1e31d2e)
  • fix(briefe): der Zahlungsweg der beiden Altbriefe — Konto statt Barge...
Read more

v2.13.0

Choose a tag to compare

@github-actions github-actions released this 15 Sep 21:45

Worum es in diesem Stand geht

Der Server-Teil des Offline-Betriebs der Theke, das Mahnverfahren bis zum Schadensersatz-Bescheid direkt aus den überfälligen Büchern, eine Ausweisnummer je Person über Schüler und Kollegium und die Personenart Lehrkraft/LiV. Die Theke selbst bucht noch wie bisher: Die neue Nachbuch-Tür und die Barcode-Liste nutzt sie erst mit Stufe 3 des Offline-Baus.

Geänderte Abläufe

  • Theke – ohne Netz: Ein Scan hält Person, Absicht und Scan-Zeitpunkt beim Scannen fest, nicht erst nach der hängenden Anfrage. Nur ein gescheiterter Versand kommt in die Warteschlange. „Buch zurückgeben" in der Akte ist auch ohne Netz eine Rückgabe; mit geladener Lehrkraft geht ein Buch an sie. Kann die Warteschlange nicht speichern, sagt die Theke „NICHT gespeichert". Beim Nachsenden gilt nur als erledigt, was der Server wie gescannt gebucht hat; ein liegengebliebener Eintrag wird nach einer Minute erneut versucht.
  • Theke – Ausweise: Nummern mit S- und L- heißen nur noch „Ausweis" und werden unter Schülern und Kollegium gesucht. Eine Littera-Ersatznummer L-… auf einem Schülerausweis lädt damit den Schüler.
  • Mahnwesen: Der vierte Reiter heißt „Schadensersatz" und zeigt je Kind die Forderungen ohne Bescheid mit dem nächsten Schritt. Der Bescheid lässt sich direkt aus den überfälligen Büchern erstellen; Verlust und Brief werden zusammen gebucht. In der Schülerakte steht „Bescheid erstellen" an der Gebühren-Karte; „Melden" öffnet keinen Elternbrief mehr.
  • Ausleihe – Frist: Wer am oder nach dem Rückgabetermin seiner Klasse ein Schulbuch bekommt, hat den Stichtag des folgenden Schuljahres als Frist.
  • Benutzerverwaltung: Personenart Lehrkraft oder LiV, sichtbar in Formular und Liste. Ein Kollegiumskonto hat immer eine. An der Theke leiht als Lehrkraft aus, wer eine Personenart hat.
  • Littera-Übernahme: Die Kartennummern der Ausweise kommen im Personenlauf an; eine Ersatznummer weicht einer vergebenen Nummer aus; Referendare kommen als LiV ins Kollegium.

Behobene Fehler

  • Theke – Idempotenz: Eine zweite Anfrage mit demselben Schlüssel, die nach der Buchung und vor dem Speichern der Antwort kam, buchte neu (das Buch wurde zurückgenommen). Der Schlüssel wird jetzt vor der Arbeit reserviert.
  • Theke: Nach dem Zusammenführen zweier Schüler hängt die Theke auf das Ziel um. „Einmalig ignorieren" erscheint nur mit dem Recht, Schülerdaten zu ändern. Bestätigt der Server die Abmeldung nicht, steht ein Hinweis auf der Anmeldemaske.
  • Schülerakte: Scheitert der Abruf von Vormerkungen, Gebühren oder Bescheiden, steht das in der Akte statt leerer Karten.
  • Lehrkraft-Ausleihe: Ein Datenbankfehler erscheint nicht mehr als „Lehrerprofil nicht gefunden".
  • Nachbuch-Tür (Rasterdurchgang 15.09.): Der Bewegungsstempel läuft nie rückwärts. Die Tür liest, was unter dem Schlüssel steht: Ein online schon vollständig gebuchter Scan wird nicht erneut ausgeliehen, eine wiederholte Portion erzeugt keine falschen Meldungen. Der Uhrversatz des Theken-Rechners wird herausgerechnet. Die Barcode-Liste führt auch ausgesonderte Exemplare; Littera-Etiketten rechnet die Theke mit denselben Prüffällen wie der Server zurück.
  • Benutzerliste: Die Personenart unter dem Namen hat die Schriftgröße der Zelle.

Neue und geänderte Schnittstellen

  • POST /api/action/nachbuchen (neu): Portion der Offline-Warteschlange mit gesendet_am (Pflicht); Ergebnis je Eintrag, dazu uhr_versatz_sekunden.
  • GET /api/action/buchbarcodes (neu): Barcodes aller Exemplare, mit ETag (304) und gzip.
  • GET /api/action/nachbuch-meldungen, GET …/anzahl, POST …/{id}/quittieren (neu).
  • GET /api/bescheide/ausstehend (neu); Gebührenzeilen tragen bescheid_id.
  • POST /api/action: 409 in_arbeit, solange eine Buchung mit demselben Schlüssel läuft; POST /api/action/batch antwortet dann 503 je Eintrag.
  • Benutzer: Feld personenart. Schüler und Benutzer: eine Ausweisnummer, die schon eine Person der anderen Liste trägt, ergibt 409.

Datenbank-Migrationen

  • 116 Bewegungsstempel: ausleihen.erfasst_am, buecher_exemplare.letzte_bewegung_am (aus den Ausleihen zurückgerechnet).
  • 117 Tabelle nachbuch_meldungen.
  • 118 Eine Ausweisnummer je Person über Schüler und Kollegium (Trigger). Ein schon bestehendes Doppel meldet die Migration als Warnung, sie bricht nicht ab.
  • 119 benutzer.personenart; jedes Kollegiumskonto wird Lehrkraft.
  • 120 Ein Kollegiumskonto hat immer eine Personenart (Trigger).

Beim Einspielen

  1. Vorher lesend prüfen, ob eine Ausweisnummer bei einem Schüler und im Kollegium zugleich steht (am 15.09. waren es 0):
    docker exec bibliothek-db psql -U postgres -d bibliothek -c "SELECT count(*) FROM schueler s JOIN benutzer b ON b.barcode_id = s.barcode_id WHERE s.deleted_at IS NULL;"
  2. git pull, dann ./update.sh.
  3. Bis die Ausleihe an die Rolle gebunden ist (entschieden am 15.09., noch nicht gebaut): Wer als Admin oder Mitarbeiter an der Theke für sich selbst ausleihen soll, braucht in der Benutzerverwaltung die Personenart „Lehrkraft".

Docker-Image: ghcr.io/uuuxy/bibliothek:2.13.0 (Build im Workflow 'Docker Image veröffentlichen (ghcr.io)' zum selben Tag).

Commits seit v2.12.0

  • fix(theke): die Ausleih-Kennung aus der Online-Antwort wird streng als UUID geprüft (47ec1c8)
  • docs(offen): Tür-Funde aus dem Rasterdurchgang erledigt, Vorgaben für Stufe 3 nachgezogen, Plan Leserdatei als nächster Schritt (5f8d578)
  • feat(theke): Littera-Etiketten rechnet die Theke wie der Server zurück, mit gemeinsamen Prüffällen (5190ceb)
  • fix(theke): die Barcode-Liste führt auch ausgesonderte Exemplare (05f1bcf)
  • fix(theke): die Nachbuch-Tür rechnet den Uhrversatz des Theken-Rechners heraus (154b025)
  • fix(theke): die Nachbuch-Tür liest die Antwort unter dem Schlüssel, nicht nur ihr Vorhandensein (6ea6f7b)
  • fix(theke): der Bewegungsstempel läuft nie rückwärts (9939023)
  • docs(offen): Leserdatei und Rolle Leitung entschieden, ersetzt „Rollennamen bleiben“ (Peter, 15.09.2026) (e28a6bf)
  • docs(offen): Rollennamen bleiben — der Admin vergibt die Rolle je Person, die Rechte je Rolle im Rechte-Editor (Peter, 15.09.2026) (1f92d58)
  • test(hygiene): Rollenvergleich im Benutzerformular als Ausnahme mit Grund (01a0b09)
  • fix(benutzer): die Personenart unter dem Namen liegt auf der Zellen-Schriftgröße (1527551)
  • docs(offen): Raster-Funde erledigt, Frage zu den Rollennamen eingetragen (225e3f7)
  • feat(theke): Migration 120 — wer als Lehrkraft ausleiht, entscheidet die Personenart (a6451e4)
  • docs(offen): Rasterdurchgang über Migration 118 und 119 eingetragen (a1196b4)
  • test(raster): Rasterdurchgang über Migration 118 und 119 — Sperr-Test ehrlich, Theken-Befund nachgestellt (1d8ceec)
  • docs(offen): Personenart gebaut, offen bleibt das Umtragen zwischen Schülern und Kollegium (1c8b81b)
  • feat(benutzer): Migration 119 — Personenart im Kollegium, Lehrkraft oder LiV (9de96fe)
  • docs(offen): Personenart Schüler, Lehrkraft, Referendar entschieden (0fcc9d8)
  • feat(schema): Migration 118 — eine Ausweisnummer gehört genau einer Person, über Schüler und Kollegium (6d3f47c)
  • docs(offen): Littera zählt Bücher und Leser getrennt — Neudruck, Offline-Form, Rückweg (d81bcbf)
  • fix(littera): die Ersatznummer L-… weicht einer schon vergebenen Nummer aus (5ce2f46)
  • docs(offen): Rasterdurchgang über die Stufe-2-Commits eingetragen, am echten Postgres nachgestellt (d473873)
  • test(raster): Nachstellungen der offenen Funde vom 15.09.2026 hinter dem Build-Tag raster (0cf859c)
  • fix(littera): die Kartennummern der Ausweise kommen im Personenlauf an (0366a64)
  • fix(theke): S- und L- heißen nur noch „Ausweis" — gesucht wird unter Schülern und Lehrkräften (dae218f)
  • feat(theke): Buch-Barcodes für die Offline-Einordnung — vollständig, mit Stand-Merker (b0cd224)
  • feat(theke): Nachbuch-Tür — der Server bucht die Wirklichkeit, jede Abweichung wird gemeldet (00a2596)
  • feat(theke): Migration 117 — Nachbuch-Meldungen mit Liste, Quittieren, Frist, Wächter, Auskunft und Tilgung (4e898c9)
  • feat(schema): Migration 116 — Bewegungsstempel an Exemplar und Ausleihe (1064464)
  • refactor(theke): Rückholen als Transaktions-Baustein — Umlauf und Forderung in der Transaktion des Aufrufers (f8ba815)
  • refactor(theke): tote Tür „Reserviert für:" fällt — samt dem NULL-Scan dahinter (90e7554)
  • docs: erledigt.md gelöscht, Erledigtes fällt aus OFFEN.md — die Geschichte steht in Git (9b59de6)
  • docs(offen): Block „Was jetzt dran ist" in einfachen Worten an den Anfang (6818c90)
  • docs(offen): Prod-Zählung zur toten Tür eingetragen, Server-Pfad der Anleitungen als Drift notiert (785c229)
  • docs(offen, erledigt, sweeps): Fristen-Bindung erledigt, Idempotenz-Fund als tote Tür neu gefasst (e95a09b)
  • fix(idempotenz): Übernahmefrist, Bearbeitungsfrist und Speicherfrist hängen aneinander (6739a00)
  • docs(offen, erledigt, sweeps): Rasterdurchgang 15.09. eingetragen, Abmelde-Gate erledigt (dfa2429)
  • test(auth): Abmelde-Statusliste des Clients ist die Menge der Antworten mit Löschcookie (55ec612)
  • docs(offen, erledigt, handbuch): Mahnverfahren Stufe 2 gebaut, 4.18 entschieden, 5.3 und 5.13 nachgezogen (b9288ac)
  • feat(mahnwesen): Bescheid-Dialog zeigt die überfälligen Bücher und bucht sie mit dem Brief (4147a3b)
  • feat(bescheid): Bescheid direkt aus den überfälligen Büchern, Verlust und Brief in einer Transaktion (07f6fd5)
  • refactor(schaden): meldeSchaden als Transaktionsfunktion, VERLUST bei „nicht zurückgegeben", Fund beendet die Forderung (f854247)
  • test(e2e): Schadensfall-Ablauf ohne PDF-Fenster nach dem Melden (24a7afc)
  • test(pii): GET /api/bescheide/ausstehend im Antwort-Gate eingeordnet (bfa5896)
  • docs(offen, erledigt, handbuch): Mahnverfahren Stufe 1 gebaut, Stufe 2 als Entscheidung 4.18 (f791d58)
  • feat(akte): „Bescheid erstellen" an der Gebühren-Karte, kein Altbrief nach dem Melden (33e92c4)
  • feat(mahnwesen): Reiter „Schadensersatz" mit Forderungen ohne Bescheid u...
Read more

v2.12.0

Choose a tag to compare

@github-actions github-actions released this 14 Sep 11:30

Worum es in diesem Stand geht

Ein Prüfdurchgang über Register, Issues und Dokumentation, und danach drei Fehler, die still jemandem schaden konnten: An der Theke ging nach einem Klick auf „Ausleihe" der nächste Scan ins Leere. Wer sich ohne Netz abmeldete, war nach dem nächsten Neuladen wieder angemeldet. Und die Ersatzforderung aus der Schülerakte hätte Forderungen doppelt verlangt, sobald ein Landes-Bescheid existiert. Dazu steht alles Offene jetzt an einem Ort: docs/OFFEN.md.

Behobene Fehler

  • Theke – Scanfeld: Nach einem Klick auf den Menüpunkt „Ausleihe" und nach der Rückkehr aus einem anderen Bereich hat das Scanfeld wieder den Fokus. Bisher lief der nächste Scan ohne Meldung ins Leere, und das Enter des Scanners löste den Menüknopf erneut aus.
  • Abmelden ohne Netz: Meldet sich jemand ab, während das Netz weg ist oder der Proxy mit 502/504 antwortet, gilt die Abmeldung trotzdem. Bisher blieb das Sitzungs-Cookie im Browser, und nach dem nächsten Neuladen mit Netz war die vorige Person wieder angemeldet. Die Theke merkt sich die ausstehende Abmeldung und holt sie beim nächsten Start nach.
  • Abmelden, Tab sofort zu: Die Abmeldung erreicht den Server auch, wenn die Seite direkt nach dem Klick geschlossen oder neu geladen wird.
  • Schülerakte – Ersatzforderung: Die Ersatzforderung übernimmt keine Forderung mehr, die schon auf einem Schadensersatz-Bescheid steht. Sonst hätten Eltern für dieselbe Forderung zwei Zahlungsaufforderungen mit zwei Zahlungswegen bekommen. Stehen alle offenen Forderungen auf einem Bescheid, sagt die Meldung das. Ohne Wirkung, solange kein Bescheid existiert.
  • Theke – Sperr-Dialog: Der Dialog „Ausleihe blockiert" mit „Einmalig ignorieren" erscheint bei jeder übergehbaren Sperre, auch bei einem offenen Schadensfall. Bisher hing er am Wortlaut der Meldung, und die Schadens-Sperre öffnete ihn nicht. Die Überfällig-Sperre eines Geräts zeigt jetzt das Fehlerbanner statt eines Dialogs, dessen Knopf nichts bewirkte.
  • Geräte: Ein verliehenes Gerät, das danach als defekt gemeldet wurde, lässt sich zurückgeben; ein gesperrter Schüler kann sein Gerät zurückgeben. Ein Gerät geht nur an eine aktive Lehrkraft; eine unbekannte Kennung ergab bisher einen 500.
  • Katalog – ISBN: Eine doppelte ISBN wird wieder als Dublette gemeldet („Ein Buch mit dieser ISBN existiert bereits") statt „buch konnte nicht erstellt werden". Der Name des Constraints hatte sich mit der Umbenennung der Tabelle geändert.
  • Ungültige Kennungen: Eine Kennung aus Adresse, Abfrage oder Rumpf, die keine UUID ist (auch die Form urn:uuid:…), ergibt 400 statt 500 aus der Datenbank. Betroffen waren unter anderem Fehlbestand, Vormerkungen, Inventur, Zusammenführen und die vier Buch-Routen hinter POST/PUT /api/books/.
  • Cover-Upload: Die Datei wird erst abgelegt, wenn feststeht, dass es das Buch gibt; das alte Cover wird erst nach dem gelungenen Speichern gelöscht.

Geänderte Schnittstellen

  • POST /api/action: Bei einer übergehbaren Sperre trägt die 403-Antwort den Header X-Sperre: uebergehbar. Der Body bleibt {"error": …}.
  • Kennungen, die keine UUID sind: 400 statt 500, in Query, Body und Pfad. PUT /api/books/{id}, PUT …/{id}/cover, POST …/{id}/refresh-cover, POST …/{id}/cover-upload und GET /api/lookup/{isbn} sind jetzt eigene Routen mit Platzhalter.
  • GET /api/print/rechnung/{schueler_id}: 404 mit Hinweis, wenn alle offenen Forderungen auf einem Bescheid stehen.

Betrieb

  • Alles Offene steht in docs/OFFEN.md mit Reihenfolge, Erledigtes in docs/erledigt.md. Das Befund-Register und die Issues #593 bis #600 sind dort aufgegangen.
  • scripts/entferne_demo_daten.sql entfernt die Demo-Daten von einem benutzten System, mit Vorschau jeder Verflechtung; geschrieben wird nur mit -v ausfuehren=ja. Auf dem Server am 13.09.2026 gelaufen.
  • SECURITY.md: Die Notfall-Anleitung bei kaputtem APP_ENCRYPTION_KEY riet, die Zeile zu entfernen; der Compose-Default, auf den sie sich verließ, existiert seit dem 05.09.2026 nicht mehr. Jetzt: den alten gültigen Wert zurückschreiben.
  • DEPLOYMENT.md: Das Image-Workflow prüft die CI des getaggten Commits nicht, nur das Release tut es. Die sechs Umgebungsvariablen, die Compose nicht durchreicht, sind gekennzeichnet (offen, OFFEN.md 4.7).
  • Handbuch: Ohne Netz speichert die Theke heute nur Bücher mit Barcode B-…. Was während eines Ausfalls zu tun ist, steht unter „Wenn etwas nicht geht".
  • Werkzeuge: security-scan.sh (ZAP) erreicht den lokalen Stack und meldet sich per Cookie an; api_inventar.sh liest keine Testdateien mehr; die Swagger-Spezifikation nennt keinen festen Host.

Datenbank-Migrationen

Keine.

Beim Einspielen

Normal einspielen: git pull, dann ./update.sh. Keine Vorabprüfung nötig.

Docker-Image: ghcr.io/uuuxy/bibliothek:2.12.0 (Build im Workflow 'Docker Image veröffentlichen (ghcr.io)' zum selben Tag).

Commits seit v2.11.0

  • fix(auth): Abmelden gilt auch, wenn der Server die Abmeldung nicht erhält (5501920)
  • fix(theke): nach dem Wechsel zur Ausleihe hat das Scanfeld wieder den Fokus (cf6f3da)
  • fix(druck): die Ersatzforderung übernimmt keine Forderung von einem Bescheid (436de45)
  • docs(offen): acht Entscheidungen vom 13.09.2026 eingetragen (abd7552)
  • docs(deployment): das Image-Workflow prüft die CI des Tags nicht (4cbd925)
  • docs(handbuch, fachkonzept): ohne Netz speichert die Theke nur B-Bücher (512585b)
  • docs: alles Offene an einem Ort — OFFEN.md und erledigt.md (6a85a71)
  • docs(befunde): Offline-Folgefragen der Theke entschieden (a841e6c)
  • docs(befunde): Offline-Betrieb der Theke — Messung, Entscheidungen, Grenze (9489614)
  • docs(befunde): Sperr-Dialog der Theke erledigt (f4719c9)
  • fix(theke): Sperr-Dialog hängt am Merkmal, nicht am Wortlaut (e107465)
  • docs(befunde): Geräte-Posten der Theke erledigt (f0be09e)
  • fix(service): Geräte-Ausleihe nur an eine aktive Lehrkraft (cc9e6c8)
  • fix(service): Geräte-Rückgabe trotz Sperre möglich (3899cf1)
  • docs(befunde, sweeps): Kennungs-Lücken im Pfad und in urn-Form erledigt (398cf36)
  • fix(inventur): Cover-Upload schreibt erst, wenn das Buch feststeht (ddee580)
  • fix(inventur): Buch-Kennung im Pfad ist 400, nicht 500 aus Postgres (35afea9)
  • fix(api, inventur): Kennung in urn-Form ist 400, nicht 500 aus Postgres (acce59e)
  • docs(befunde): Demo-Daten auf dem Server entfernt (ccd8807)
  • docs(befunde, sweeps): Klasse Kennung ungeprüft an die Datenbank (8ac23e8)
  • fix(api): ungültige Kennung ist 400, nicht 500 aus Postgres (02db8b6)
  • docs(befunde): Abmelde-Lücke erledigt, zwei Nebenfunde eingetragen (5692c9f)
  • fix(auth): Abmelden kommt beim Server an, auch wenn der Tab sofort schließt (95e7a24)
  • feat(skripte): Demo-Daten von einem benutzten System entfernen (566b9fe)
  • docs(befunde): Werkzeug-Funde erledigt, UUID-Klasse und ZAP-Lauf 13.09. eingetragen (d903cfe)
  • fix(werkzeuge): ZAP-Scan erreicht den lokalen Stack und meldet sich an (61ea135)
  • fix(swagger): Spezifikation nennt keinen festen Host mehr (e502c3c)
  • fix(werkzeuge): API-Inventar liest keine Testdateien mehr (ed4c6c1)
  • docs(fach): Handbuch, Fachkonzept, Datenschutz und Invarianten an den Code angeglichen (b216587)
  • docs(betrieb): Anleitungen an Compose, Guard und Werkzeuge angeglichen (0f79d0b)
  • docs(befunde): Prüflauf 13.09. — Register an Code, Server und Issues abgeglichen (ee68e1c)
  • fix(inventur): eine doppelte ISBN wird wieder als Dublette erkannt (260b143)

Full Changelog: v2.11.0...v2.12.0

v2.11.0

Choose a tag to compare

@github-actions github-actions released this 13 Sep 07:37

Worum es in diesem Stand geht

Zwei fachliche Bausteine und ein Prüfdurchgang. Beim Schadensersatz schließt sich der Kreis: Kommt ein Buch zurück, für das schon eine Forderung steht, endet sie – oder die Theke sagt, dass der Fall bereits bei der Schulaufsicht liegt. Im Bestellwesen sind die beiden Haushalte (Land und Schulträger) jetzt überall getrennt ausgewiesen. Dazu kommen die Funde aus dem Rasterdurchgang vom 12.09.: Meldungen, die „alles in Ordnung“ oder „nichts da“ sagten, obwohl etwas schiefgegangen war.

Neue Funktionen

  • Theke – Rückkehr eines abgerechneten Buchs: Wird ein Lernmittel gescannt, für das eine Schadensersatz-Forderung offen ist, wird die Forderung storniert und die Theke meldet das. Liegt der Bescheid schon bei der Schulaufsicht, erscheint stattdessen der Hinweis, die Aufsicht unverzüglich zu informieren. Die Forderung bleibt dann offen. Bisher meldete der Scan nur „Buch reaktiviert“, Forderung und Sperre blieben stehen.
  • Schülerakte (Verwaltung): Im Reiter „Ausleihen & Historie“ steht eine Karte mit den Schadensersatz-Bescheiden: Referenznummer, Positionen, Betrag, Briefdatum, Frist, Zustand und Nachdruck (dasselbe PDF unter derselben Nummer).
  • Bestellwesen (Verwaltung): Der Bestellbericht trennt Lernmittelfreiheit (Land), Schülerbücherei (Schulträger) und Alt-Bestellungen ohne eindeutige Zuordnung, jeweils mit eigener Summe. Die Bestellhistorie lässt sich nach Topf filtern, auch nach „ohne Zuordnung“, damit sich diese Alt-Bestellungen finden und nachtragen lassen. Die Kennzahlen sind nach Topf aufgeteilt.

Behobene Fehler

  • Schadensersatz-Bescheid: Das Verleihjahr zählt Schuljahre. Bisher zählte es Ausleihen (ein Buch mit vier Ausleihen in einem Schuljahr stand im 4. Verleihjahr) oder Kalenderjahre (ein Kauf am 31. Juli blieb bis Silvester im 1. Verleihjahr). Die Staffel ergab dadurch falsche Prozentsätze.
  • Theke: Eine Fremdrückgabe (das Buch eines Mitschülers) in offener Sitzung scheitert nicht mehr an Sperre, Ausleihlimit oder Vormerkung des Kindes, das gerade an der Theke steht.
  • Abmelden: Kann der Server die Sitzung wegen eines Datenbank-Aussetzers nicht widerrufen, antwortet er mit 503 statt „ok“. Das Cookie wird trotzdem gelöscht.
  • Anmeldung: Wer während eines Datenbank-Aussetzers die Seite neu lädt, bleibt angemeldet. Die 503-Meldung nennt nur noch den Satz für die Bibliothek, keine technische Ursache mehr.
  • Buchakte: Der Reiter „Ausleiher“ zeigt auch Exemplare, die eine Lehrkraft ausgeliehen hat (Klasse „Lehrer“). Kommt der Titel nicht an, steht dort „Titel nicht geladen“ statt „Buch nicht gefunden“.
  • Vormerkung: Eine zweite Vormerkung desselben Schülers auf denselben Titel meldet „Dieser Schüler hat den Titel bereits vorgemerkt“ statt einer Störung.
  • Schülerdatei, Papierkorb: Ein zweites Löschen verschiebt die 180-Tage-Frist bis zur Anonymisierung nicht mehr. Scheitert das Wiederherstellen an einem aktiven Datensatz mit gleichem Namen und Geburtsdatum, gleichem Barcode oder gleicher LUSD-ID, nennt die Meldung den Grund und den Weg heraus statt „interner Datenbankfehler“.
  • Schülerausweis: Klassen 12T und 13T gelten bis zum Ende von Jahrgang 13. Bisher lief der Ausweis in 12T ein Jahr zu früh ab.
  • Abgänger: Der Knopf „An Klassenleitungen mailen“ erscheint nur, wenn das Recht zum Mailen vorhanden ist. Bisher kam nach dem Versanddialog ein 403.
  • Listen (Verwaltung): Schülerdatei-Suche, Ehemalige, offene Anliegen, Klassensatz-Reservierungen und Geräte zeigen bei einem Ladefehler die Fehlermeldung mit „Erneut versuchen“ statt „keine Einträge“. Die Suchen zeigen nach einem Fehler nicht mehr die Treffer der vorigen Suche.
  • Kollegiums-Portal (angemeldet): Scheitert das Nachladen der eigenen Anliegen, bleibt die Liste stehen. Bisher verschwand ein gerade abgeschickter Wunsch vom Bildschirm.
  • System-Seite (Datenschutz): Der Wächter „Ehemalige mit offenen Vorgängen“ sieht auch Altzeilen ohne Abgangsdatum, genau wie die Löschuhr.
  • LMF-Planer: Der leere Entwurf beginnt in der 2. Stunde, wie der Vorschlag des Servers. Eine Zeile ohne Klassen hat in der Vorschau dieselbe Form wie im gespeicherten Plan.
  • Barrierefreiheit: Im Auswahlfeld liest der Screenreader die per Pfeiltaste markierte Zeile vor.

Geänderte Schnittstellen

  • GET /api/lmf-termine liefert das Feld ohne_rueckgabe_termin nicht mehr. Wer noch fehlt, zeigt der Planer unter „Noch nicht im Plan“.
  • GET /api/bestellhistorie nimmt mittel=land|schultraeger|ohne, ein unbekannter Wert ergibt 400.
  • POST /api/auth/logout: 503, wenn der Widerruf nicht gelang.
  • Wiederherstellen eines Schülers: 409 bei aktivem Zwilling. Vormerkung anlegen: 409, wenn sie schon besteht.

Betrieb

  • Das Werkzeug migrate-fotos wird im Image gebaut (docker compose exec backend ./migrate-fotos). Das 14-MB-Binary im Repo ist entfernt.
  • DB_HOST und SMTP_SENDER sind aus beiden Compose-Dateien entfernt. Kein Code hat sie gelesen, die Änderung hat also keine Wirkung.
  • docs/DEPLOYMENT.md nennt für JWT- und AES-Schlüssel jetzt openssl rand -hex 32. Die frühere Anleitung (Base64 bzw. 16 Byte) war falsch.

Datenbank-Migrationen

  • 115_lmf_plan_zusicherungen: Die Art einer Planzeile muss zur Art ihres Plans passen, die Position ist je Plan eindeutig, und die letzte Stunde des Rückgabe-Plans liegt im Schultag. Der Unique-Index ersetzt idx_lmf_termine_plan. Verletzt der Bestand eine der Regeln, bricht die Migration mit einer Meldung samt Abfrage ab.

Beim Einspielen

Normal einspielen: git pull, dann ./update.sh.

Die Vorabprüfung für Migration 115 lief am 13.09.2026 auf dem Server mit dem Ergebnis 0|0|0 (65 Planzeilen, 1 Plan). Wer später einspielt, prüft vorher noch einmal. Drei Nullen bedeuten, dass alles in Ordnung ist:

docker exec bibliothek-db psql -U postgres -d bibliothek -At -c "SELECT (SELECT count(*) FROM lmf_termine t JOIN lmf_plaene p ON p.id=t.plan_id WHERE t.art<>p.art), (SELECT count(*) FROM (SELECT plan_id, position FROM lmf_termine GROUP BY 1,2 HAVING count(*)>1) d), (SELECT count(*) FROM lmf_plaene WHERE letzte_stunde > stunden_je_tag);"

Docker-Image: ghcr.io/uuuxy/bibliothek:2.11.0 (Build im Workflow 'Docker Image veröffentlichen (ghcr.io)' zum selben Tag).

Commits seit v2.10.0

  • test(littera, inventur): kaputter Export und kaputte Quelle sind Fehler, kein leeres Ergebnis (223b98f)
  • refactor(auth): Drosselung und Anmeldeprüfung des LoginHandler in zwei Helfer (4d2e48e)
  • refactor(ui): Kaestchen, Radio und Segmente nutzen die Massskala statt fester Pixelwerte (1aefe51)
  • docs(befunde): Posten des 12.09. ausgetragen, zwei Fragen an Peter nachgetragen (92d6577)
  • docs(deployment): tote Compose-Variablen raus, zwei falsche Anleitungen berichtigt (e5d8e21)
  • chore(image): migrate-fotos wird gebaut, statt als Binary im Repo zu liegen (357d28f)
  • test(gates): der Fehlerausgang-Scanner sieht die UND-Kette und den neutralen Ersatzwert (5882d0c)
  • fix(listen): eine leere Arbeitsliste heißt „nichts zu tun", nicht „nicht geladen" (dfc9913)
  • fix(schuelerdatei): die beiden Suchen zeigen nach einem Fehler nicht die Treffer von vorher (05391fe)
  • fix(abgaenger): der Mail-Knopf erscheint nur mit dem Recht, das die Route verlangt (143926d)
  • fix(ausweis): 12T gilt bis zum Ende der Oberstufe, nicht bis Jahrgang 12 (59beb42)
  • fix(papierkorb): das Wiederherstellen nennt den Zwilling, statt 500 zu melden (39b58be)
  • fix(papierkorb): das zweite Löschen stellt die 180-Tage-Uhr nicht neu (473bea9)
  • fix(vormerkung): die zweite Vormerkung ist ein Konflikt, keine Störung (4638bca)
  • fix(akte): der Ausleiher-Reiter zeigt auch die Lehrkraft, die den Titel hat (cd252bd)
  • feat(lmf): die vier Zusicherungen des Planers stehen geschrieben, nicht verabredet (0482278)
  • fix(auth): das Abmelden meldet, was wirklich widerrufen wurde (039145f)
  • fix(api): der 503 der Sitzungsprüfung nennt den Satz, nicht die Ursache (2c8fe70)
  • docs(sweeps): neue Bugklasse „Gate meldet an sich selbst vorbei" (eed4ffd)
  • refactor(bescheid): RueckkehrBefund.Betroffen fällt — nie erreicht, und die Frage stellt niemand so (2e455aa)
  • chore(hooks): der Pre-Push-Hook fragt golangci-lint — das erste Gate der CI (f9c5383)
  • test(omnibox): der Gerüst-Typ des Rückkehr-Tests fällt — er hielt golangci-lint auf (06af04b)
  • refactor(lmf): „wer fehlt noch" steht im Planer — das Portal liefert es nicht mehr mit (5666c0f)
  • test(frontend): jsdoms Lücken stehen einmal — main war grün gedruckt und rot beendet (f67f2df)
  • docs(befunde): Paket-5-Posten ausgetragen, und der Hinweis „wer fehlt" hängt an der falschen Tür (631908e)
  • test(lmf): die Wartezeit der Vorschau steht einmal — der Test folgt ihr (c672183)
  • fix(lmf): der leere Entwurf beginnt in der 2. Stunde — wie der Vorschlag des Servers (cc7bc39)
  • fix(lmf): die Vorschau liefert eine Zeile ohne Klassen als [] — wie der gespeicherte Plan (a5d2a6f)
  • fix(dsgvo): der Wächter „Ehemalige mit offenen Vorgängen" rechnet wie die Löschuhr (fa4a211)
  • fix(a11y): der Screenreader folgt der Pfeiltasten-Markierung im Auswahlfeld (d62c8f3)
  • refactor(ui): die Tastaturbedienung des Auswahlfelds wird prüfbar — eigenes Modul, eigener Test (8a444e6)
  • test(bestellwesen): der DNB-Titel mit Haken wird zum Lernmittel — das letzte offene Gate aus #596 (39cbaaf)
  • feat(bestellwesen): Historie nach Topf filtern, Kennzahlen nach Topf aufteilen (9f0fca8)
  • feat(bestellwesen): der Bestellbericht trennt die beiden Töpfe — mit eigener Summe je Topf (14e6a56)
  • feat(akte): die Schülerakte zeigt die Schadensersatz-Bescheide (006587c)
  • docs(befunde): Verleihjahr ausgetragen, Folge des falschen Aussonderungs-Grunds nachgetragen (8ba2fad)
  • fix(bescheid): das Verleihjahr zählt Schuljahre — nicht Ausleihen und nicht Kalenderjahre (c7c5718)
  • docs(konzept): der Rückgabe-Hook ist gebaut — und sitzt woanders, als der Bauplan sagte (930ea1a)
  • feat(bescheid): das abgeschriebene Buch kommt zurück — die Forderu...
Read more

v2.10.0

Choose a tag to compare

@github-actions github-actions released this 11 Sep 06:48

Geänderte Abläufe

  • System-Seite (Betriebsbereitschaft): Neuer Bereich „Schadensersatz-Bescheid“. Er nennt die Pflichtangaben, die für einen Bescheid noch fehlen (Nummer des Schulamtsbereichs, Schulnummer, Aufsichtsbehörde, Schulleitung, Anschrift der Schule). Stufe Warnung, keine Alarm-Mail.

Behobene Fehler

  • Theke: Ein gesperrtes Kind kann in offener Sitzung sein eigenes Buch zurückgeben. Bisher wurde die Rückgabe abgewiesen und ging nur über „Sperre übergehen“; das Protokoll hielt dann eine übergangene Sperre fest. Neue Ausleihen an ein gesperrtes Kind bleiben gesperrt.
  • Anmeldung: Ist die Datenbank beim Prüfen der Sitzung kurz nicht erreichbar, antwortet der Server mit 503 statt 401. Bisher meldete ein solcher Aussetzer alle Arbeitsplätze ab.
  • Anmeldung (Kollegium): Wer die Selbstanmeldung beantragt hat, liest auch beim zweiten Versuch „Zugang beantragt“ statt „user account is deactivated“. Ein bewusst deaktiviertes Konto bekommt „Konto deaktiviert — bitte an die Bibliothek wenden“.

Datenbank-Migrationen

Keine.

Beim Einspielen

Wer v2.9.0 noch nicht eingespielt hat: vorher die Prüfung auf doppelte E-Mail-Adressen ausführen (Migration 113 aus v2.9.0 bricht sonst ab). Leere Ausgabe = in Ordnung.

docker exec bibliothek-db psql -U postgres -d bibliothek -c "SELECT lower(email), count(*) FROM benutzer GROUP BY 1 HAVING count(*) > 1;"

Docker-Image: ghcr.io/uuuxy/bibliothek:2.10.0 (Build im Workflow 'Docker Image veröffentlichen (ghcr.io)' zum selben Tag).

Commits seit v2.9.0

  • fix(theke): eigene Rückgabe in offener Sitzung scheitert nicht mehr an der Sperre (214a5ec)
  • docs(sweeps): Klasse „Timer überlebt den Abbau“ (71d16a8)
  • feat(system): Betriebsbereitschaft meldet fehlende Angaben für den Schadensersatz-Bescheid (387a994)
  • fix(frontend): keyboardNav räumt den Scroll-Timer beim Abbau (7e52ebb)
  • fix(auth): Selbstanmeldung meldet auch beim zweiten Versuch „Zugang beantragt“ (d9d84fd)
  • fix(auth): Datenbank-Ausfall bei der Token-Prüfung ergibt 503 statt 401 (5cc80b8)
  • docs(befunde): Geisterbuch-Fall bei der Ausleihe bleibt so (c9605f2)

Full Changelog: v2.9.0...v2.10.0

v2.9.0 – Korrekturen aus der Prüfung vom 10.09.2026

Choose a tag to compare

@github-actions github-actions released this 11 Sep 05:25

Korrekturen aus einer Prüfung des gesamten Programms am 10.09.2026. Dieser Stand enthält keine neuen Funktionen, aber einige geänderte Abläufe (unten zuerst).

Geänderte Abläufe

  • Schaden melden (Schülerakte): Der Dialog fragt, ob das Buch nicht zurückgegeben oder beschädigt zurückgegeben wurde. Die Angabe steht im Schadensersatz-Bescheid.
  • Schadensersatz-Bescheid (Mahnwesen): Ein Bescheid kann nur Lernmittel enthalten. Forderungen für Bücher der Schülerbücherei sind im Dialog gesperrt, bis die Rechnung für den Schulträger gebaut ist. Ein Bescheid, dessen Forderungen bezahlt oder storniert sind, gilt als erledigt: Er wird nicht mehr als überfällig angezeigt und kann nicht übergeben werden.
  • Listenimport (Datenverwaltung): Steht eine ISBN schon im Katalog, bleiben die vorhandenen Angaben erhalten. Der Import füllt nur leere Felder und legt die Exemplare an.
  • Einstellungen → Ausleihe: Der Lernmittel-Stichtag muss im Format MM-TT eingetragen werden, z. B. 07-31. Andere Werte werden abgelehnt.
  • Benutzerverwaltung: Eine E-Mail-Adresse kann nur einem Konto gehören, unabhängig von der Groß- und Kleinschreibung.

Behobene Fehler

Schüler und Theke

  • Beim Zusammenführen zweier Schüler werden Schadensersatz-Bescheide mitgenommen.
  • In der Omnibox öffnet Enter nur den markierten Treffer, auch nachdem sich die Trefferliste geändert hat.
  • Wird ein bereitgelegtes Exemplar ausgesondert, als defekt markiert oder gelöscht, geht die Vormerkung zurück auf „wartend". Wird eine abholbereite Vormerkung gelöscht, bekommt der Nächste auf der Warteliste das Exemplar.
  • LUSD-Import: Die Bestätigung eines Massenabgangs gilt nur für die Datei, für die der Server sie verlangt hat.
  • Abgängerliste: Ein Ladefehler wird als Fehler angezeigt, nicht als „Alle Abgänger entlastet".
  • Ein zurückgekehrter Schüler mit offenen Vorgängen wird entsperrt, sobald alle Vorgänge erledigt sind (beim nächsten LUSD-Import). Vorher blieb er dauerhaft gesperrt.
  • Ein Leseclub-Zieldatum in der Vergangenheit wird nicht mehr als Rückgabefrist verwendet.

Bestand und Bestellwesen

  • Neue Titel ohne Jahrgangsangabe bekommen Jahrgang 5–10 statt 0–0.
  • Der Cover-Abgleich überschreibt keine von Hand hochgeladenen Cover mehr.
  • Ein bestelltes Exemplar, das über die Buchakte freigegeben oder ausgesondert wird, verlässt den Zulauf. Ausgesonderte Exemplare erscheinen nicht mehr im Wareneingang und können dort nicht eingebucht werden.

Betrieb

  • Die Anleitung zur Wiederherstellung (docs/resilience_and_recovery.md) holt das nächtliche Backup aus dem Docker-Volume. Bisher griff sie die Vorab-Sicherung des letzten Deploys.
  • Die Rollback-Anleitung, die update.sh bei einem Fehlschlag ausgibt, setzt Code und Datenbank auf den Stand vor dem Update zurück.

Datenbank-Migrationen

  • 111: Regel: Ein Exemplar im Zulauf ist weder ausleihbar noch ausgesondert. Widersprüchliche Zeilen werden vorher bereinigt.
  • 112: Eine abholbereite Vormerkung geht automatisch auf „wartend", wenn ihr Exemplar ausgesondert, unausleihbar oder gelöscht wird. Bestehende Fälle werden bereinigt.
  • 113: E-Mail-Adressen sind eindeutig, unabhängig von der Groß- und Kleinschreibung. Die Migration bricht ab, wenn eine Adresse bereits in zwei Schreibweisen vorkommt.
  • 114: Titel mit Jahrgang 0–0 bekommen 5–10.

Beim Einspielen

  1. Vorher auf dem Server prüfen, dass keine E-Mail-Adresse doppelt vorkommt. Eine leere Ausgabe ist in Ordnung:
    docker exec bibliothek-db psql -U postgres -d bibliothek -c "SELECT lower(email), count(*) FROM benutzer GROUP BY 1 HAVING count(*) > 1;"
    
    Kommen Zeilen zurück, je Paar ein Konto löschen oder anpassen. Sonst startet der Server nach dem Update nicht.
  2. Danach wie üblich: git pull, dann ./update.sh.
  3. Die Vorab-Sicherung von update.sh heißt ab diesem Stand vordeploy_<Zeitstempel>.sql.gz. Ältere backup_*-Dateien im Ordner backups werden weiter nach der bisherigen Frist gelöscht.

Offene Punkte stehen in docs/befunde.md.

v2.8.0 – Schadensersatz-Bescheid aus dem Programm

Choose a tag to compare

@uuuxy uuuxy released this 10 Sep 20:35

Schadensersatz-Bescheid — der Brief kommt aus dem Programm

Das Musteranschreiben der Schule ist Wort für Wort nachgebaut und wird aus den echten Forderungen gefüllt. Was bisher in Word von Hand ausgefüllt wurde, entsteht jetzt in einem Schritt.

Wo es liegt. Im Mahnwesen: einen Schüler markieren, dann „Schadensersatz-Bescheid" in der Auswahlleiste. Der Knopf erscheint nur bei genau einem markierten Kind — jeder Betrag ist eine Ermessensentscheidung, jede Referenznummer wird unwiderruflich vergeben. Die Briefe stehen im vierten Reiter „Bescheide"; die Zahl daran nennt nur die abgelaufenen Fristen, also das, was zu tun ist.

Der Dialog zeigt die offenen Forderungen mit einem Betragsvorschlag nach der Staffel der Schule (1. Verleihjahr voller Kaufpreis, dann 80/60/40/20 %, ab dem sechsten Jahr 10 %) und der Herleitung daneben. Beträge sind änderbar, Positionen abwählbar. „Bescheid erstellen" vergibt die Nummer, setzt die Frist als Datum und öffnet den Brief. Fehlen Angaben, sagt der Dialog welche.

Der Brief trägt beide Fallgruppen des Formulars mit Kästchen und Tabelle, Fristsatz, Gesamtbetrag, Zahlstelle, Bankverbindung, Referenz-Nr., Androhung, Unterschrift und Schlussabsatz. Zwei Dinge fand erst das Ansehen des fertigen PDFs: Ein ş wurde zum Punkt, und der Seitenumbruch riss die Tabelle entzwei. Beides behoben, beides hat jetzt ein Gate.

Zusicherungen, jede am echten Postgres belegt und rot gesehen

  • Eine Referenznummer wird nie zweimal vergeben — auch bei acht gleichzeitigen Briefen. Mit einem gewöhnlichen MAX+1 bekamen sie nur drei verschiedene Nummern.
  • Ein abgewiesener Brief verbraucht keine Nummer.
  • Eine Forderung steht auf höchstens einem Bescheid.
  • Der Nachdruck ist derselbe Brief, auch nach einem Umzug.
  • Die Übergabe geht erst nach Fristablauf und nur einmal.
  • Der Bescheid überlebt die DSGVO-Löschung als Beleg ohne Person; die Auskunft nennt ihn, die Tilgung leert den Empfänger.

Einstellungen → Schadensersatz: Nummer des Schulamtsbereichs, Schulnummer, Aufsichtsbehörde und Schulleitung trägt die Schule ein; Frist, Zahlstelle und Bankverbindung sind aus dem Musterschreiben vorbelegt.

Offen: die Anbindung an den Mahnlauf für viele Kinder auf einmal (bewusst nicht gebaut — siehe oben), der Rückgabe-Hook nach Übergabe und das Abräumen der drei alten Briefe.

v2.7.0 – Rückweg für den Topf einer Bestellung

Choose a tag to compare

@uuuxy uuuxy released this 10 Sep 18:14

Rückweg für den Topf einer Bestellung

Eine Bestellung im falschen Topf ließ sich bisher nicht korrigieren. Für die Berichte, die nach Topf rechnen, hätte sie dauerhaft im falschen Block gestanden, und Alt-Bestellungen „ohne Zuordnung" wären es für immer geblieben.

  • Bestelldetail → „Topf ändern": Auswahl Lernmittelfreiheit (Land) oder Schülerbücherei (Schulträger), Grund ist Pflicht. Nur mit dem Recht, das auch bestellen darf.
  • Was sich ändert und was nicht: Korrigiert wird allein die eigene Zuordnung. Das Anschreiben, das der Händler bekommen hat, und die Kundennummer auf dem Beleg bleiben, wie sie rausgingen.
  • Protokoll: Jede Änderung steht im Admin-Audit-Log mit vorherigem Topf, neuem Topf und Grund. Eine Wiederholung ohne Änderung protokolliert nichts.
  • Gate am Live-Pfad: Spalte, Protokoll und unangetasteter Beleg; abgewiesene Anfragen (ohne Grund, unbekannter Topf) bewirken nichts. In beiden Richtungen rot gesehen.

Konzept ohne Rechts- und Regionalbezüge

Das Konzept zu Landes- und Kreismitteln heißt jetzt docs/mittel_konzept.md und beschreibt, was das Verfahren der Schule verlangt, ohne Paragrafen, Erlass-Nummern oder Behördennamen zu zitieren. Alle Entscheidungen, der Bauplan und die Code-Fundstellen bleiben. Formulare an Schüler und Eltern sowie ihre Quellen sind davon bewusst ausgenommen; dort gilt: prüfen statt streichen.

Offen (zweiter Schnitt): Berichte in zwei Blöcken Land/Schulträger und Topf-Filter in der Historie.

v2.6.1 – Raster-Durchgang: Pflichtangaben, Blanking, Vokabular

Choose a tag to compare

@uuuxy uuuxy released this 10 Sep 17:30

Raster-Durchgang über die Änderungen vom 09. und 10.09.

Drei Korrekturen aus dem Durchgang mit Daniels Raster (zwölf Fragen) über die Änderungen der beiden Tage. Alle drei neuen Gates wurden rot gesehen, bevor sie grün wurden.

Auskunft nach Art. 15: drei falsche Angaben korrigiert

Die Auskunft geht als PDF an die betroffene Person, deshalb wiegt eine falsche Angabe dort schwerer als eine fehlende — sie sieht wie eine geprüfte aus.

  • Die Paragrafen waren falsch etikettiert: § 83 HSchG regelt die Erhebung und Verarbeitung personenbezogener Daten durch Schulen, § 153 HSchG die Lernmittelfreiheit. Der Text klammerte beide unter „(Lernmittelfreiheit)". Verzeichnis der Verarbeitungstätigkeiten und Sicherheitsdoku hatten es die ganze Zeit richtig; abgewichen ist der Code.
  • Die Datenbank hieß „Landesschülerdatenbank LUSD". Richtig ist Lehrer- und Schülerdatenbank. Auch die eigene Doku nannte sie falsch.
  • „Protokolle 24 Monate" stand starr im Text, obwohl die Aufbewahrung seit dem 16.08. eine Einstellung mit Untergrenze 6 ist. Die Auskunft liest jetzt dieselbe Quelle wie Löschjob und Rückstands-Wächter.

Das war der dritte Fall derselben Klasse in dieser Funktion. Der Unit-Test war alle drei Male grün, weil er die Formulierung prüft und nicht den Leser. Das neue Gate steht deshalb am Live-Pfad: es setzt jede Frist auf einen krummen Wert und verlangt genau ihn in der Antwort.

Zweite Kundennummer wurde still gelöscht

Die Kundennummer für die Schülerbücherei war ein einfaches Textfeld. Jede Anfrage ohne dieses Feld löschte damit die hinterlegte Nummer, und die Antwort spiegelte die Eingabe zurück statt des gespeicherten Stands. Aufgefallen wäre der Verlust erst, wenn der Händler die Rechnung auf das falsche Kundenkonto stellt. Jetzt bedeutet ein fehlendes Feld „unverändert", ein leeres „ausdrücklich gelöscht". Belegt über die echten Türen: anlegen, lesen, ändern mit Feld, ändern ohne Feld, leeren.

Ein Vokabular für den Topf

Die Bezeichnung des Topfs stand doppelt — in Go für Anschreiben und Mail, in JavaScript für Warenkorb und Chips. Wäre eine abgedriftet, hätte die Bibliothekskraft eine andere Bezeichnung gesehen als der Händler auf dem Papier. Ein Paritäts-Gate hält Werte und Beschriftung deckungsgleich.

Kleineres

  • Die Backup-Rotation hörte bei einem unlesbaren Verzeichnis lautlos auf; sie meldet es jetzt, statt die Platte volllaufen zu lassen.
  • Der Invarianten-Katalog versprach für den Topf zu viel: Der Wertebereich ist datenbankseitig gesichert, die Pflicht bei neuen Bestellungen hält allein der Wächter im Code. Jetzt getrennt ausgewiesen.
  • Das Bestell-Anschreiben an den Händler nennt nur noch den Topf, ohne Eigentums- und Behördenangabe. Der Aufdruck auf den Büchern ist davon unberührt, er bleibt eine eigene Einstellung.

Offen: Eine Bestellung im falschen Topf lässt sich nachträglich nicht korrigieren. Für die getrennten Berichte im zweiten Schnitt wird das relevant.

v2.6.0 – Bestellwesen: Eine Bestellung = ein Topf

Choose a tag to compare

@uuuxy uuuxy released this 10 Sep 16:18

Bestellwesen: Eine Bestellung = ein Topf (Migration 109)

Jede Bestellung trägt ab jetzt, aus welchen Mitteln sie bezahlt wird: Lernmittelfreiheit (Land) oder Schülerbücherei (Schulträger). Der Händler richtet danach Nachlass und Rechnungsweg; bisher behauptete jedes Anschreiben „für unsere Schulbibliothek", auch für Lernmittel-Klassensätze. Grundlage sind die Antworten der EDV-Servicestelle für Schulbibliotheken (10.09.2026): getrennte Bestellungen sind gewollt, die Rechnung für Kreismittel geht an die Schule, eine 5-%-Regelung gibt es im Schulamtsbezirk nicht.

Warenkorb und Bestellung

  • Der Warenkorb zeigt zwei Abschnitte mit eigener Summe; der Titel schlägt den Topf über sein Lernmittel-Kennzeichen vor, eine Position lässt sich per Knopf in den anderen Abschnitt schieben (falsch gekennzeichneter Titel).
  • „Bestellung auslösen" erzeugt je Abschnitt eine eigene Bestellung an denselben Händler — zwei Mails, zwei Anschreiben, zwei Bestätigungslinks. Jede Bestellung hat ihren eigenen Doppelklick-Schutz; scheitert die zweite, bleibt nur sie im Warenkorb.
  • Ohne Topf nimmt der Server keine Bestellung an (400); die Datenbank hält den Wertebereich als zweite Tür.

Vermerk auf Anschreiben und Mail

  • Betreffzeile und Text des Anschreibens nennen den Topf („Bestellung im Rahmen der Lernmittelfreiheit" / „Bestellung für die Schülerbücherei") mit der Bitte, den Vermerk auf die Rechnung zu übernehmen. Ein Gate prüft den Inhaltsstrom des fertigen PDFs: der Vermerk des Topfs, nie der andere.
  • Die Händler-Mail kennt den Platzhalter {{.Mittel}}; fehlt er in einer umformulierten Vorlage, ergänzt der Versand Betreff und Text automatisch. Die unveränderte Seed-Vorlage wird durch die Migration nachgezogen.
  • Ein Lieferant kann eine zweite Kundennummer für die Schülerbücherei führen; auf dem Beleg steht die Nummer des Topfs.

Bestand und Katalog

  • Alt-Bestellungen sind rückwirkend zugeordnet, wo es aus den Positionen eindeutig ist; gemischte und solche mit gelöschten Titeln stehen als „ohne Zuordnung" — nie geraten.
  • Bestellhistorie und Bestelldetail zeigen den Topf als Chip.
  • Ein über die DNB neu angelegter Titel wird im Staging-Fenster gefragt, ob er Lernmittel ist (PUT /api/buecher/titel/{id}/lernmittel). Bisher blieb jedes neue Schulbuch dauerhaft ein Bücherei-Titel.

Offen (zweiter Schnitt): Berichte (Monat/Jahr/Lieferantenabrechnung) in zwei Blöcken Land/Kreis und Topf-Filter in der Historie.

Gates: api/bestellung_mittel_pg_test.go, api/bestellung_mittel_backfill_pg_test.go, api/order_pdf_mittel_test.go (rot bewiesen), api/bestellmail_mittel_test.go, api/titel_lernmittel_pg_test.go, orderStore.test.js. Doku: FACHKONZEPT §7, HANDBUCH „Bestellwesen", invarianten.md §7, schadensersatz_konzept.md 7.3/7.4.