Ablauf einer Ausleihe
Jede Bestellung hat einen Status. Im normalen Ablauf sind es diese sechs:
- Angefragt
REQUESTEDMaterial und Zeitraum werden im Shop gewählt und als Anfrage abgeschickt. Die Admins der Abteilung erhalten eine E-Mail.
- Bestätigt
APPROVEDEin Admin bestätigt die Anfrage. Ob schon eine offene Anfrage Material blockiert, stellt die Abteilung ein (
reserveOnRequestInternal/reserveOnRequestExternal). - Bereitgestellt
READYDas Material wird gerüstet. Jeder Position werden die konkreten Exemplare zugewiesen, auf Wunsch mit Foto und Hinweis zur Abholung.
- Ausgeliehen
ACTIVEDas Material ist abgeholt. Optional wird ein Übergabefoto gespeichert.
- Zurückgebracht
RETURNEDBei der Rückgabe werden pro Exemplar Zustand, Sauberkeit und Schäden erfasst.
- Abgeschlossen
COMPLETEDDie Rückgabe ist kontrolliert, das Material ist wieder frei.
Eine Anfrage kann auch abgelehnt (REJECTED, mit Begründung) oder storniert (CANCELLED) werden. In beiden Fällen ist das Material sofort wieder frei.
Externe können ohne Konto als Gast bestellen (isGuestOrder). Dafür reichen Name, E-Mail und eine Bemerkung. Bestätigung und Bereitstellung kommen per E-Mail.
Verfügbarkeit
Material darf nicht doppelt vergeben werden, wenn sich zwei Ausleihen überschneiden. lib/checkAvailability.ts rechnet deshalb für jedes Material und jeden Zeitraum aus, wie viele Exemplare noch frei sind.
Bestehende Ausleihe
Neue Anfrage
Was zählt zum Bestand
Nur Hauptexemplare im Status AVAILABLE oder RENTED. Exemplare in Wartung oder verlorene zählen nicht, ebenso Bündel, bei denen ein Teil fehlt.
Was Material blockiert
Bestellungen in APPROVED, READY und ACTIVE immer. Offene Anfragen (REQUESTED) nur, wenn die Abteilung das so eingestellt hat, getrennt für interne und externe Anfragen.
Pufferzeiten
Jede Bestellung wird vorne um die Vorbereitungszeit (prepTime…Hours) und hinten um die Wiedereinlagerung (restockTime…Hours) verlängert. Beides stellt die Abteilung ein, auch hier getrennt für intern und extern.
Bündel und Baugruppen
Bestellte Mengen werden auf ganze Bündel aufgerundet. Eine Baugruppe ist nur so oft verfügbar wie ihr knappster Bestandteil und reserviert ihre Bestandteile mit.
// Zwei Zeiträume überschneiden sich, wenn
// startA < endB und endA > startB, jeweils mit Puffer.
const reqStart = start - prepHours;
const reqEnd = end + restockHours;
const conflicting = orders.filter((o) => {
const eff = withBuffers(o); // Puffer je nach intern/extern
return reqStart < eff.end && reqEnd > eff.start;
});
const reserved = sum(conflicting, bundleAware) + viaBaugruppen;
const available = Math.max(0, totalItems - reserved);Dazu kommen Vorlaufzeiten: Ist eine Anfrage knapper als leadTimeWarning…Hours, zeigt der Warenkorb einen Hinweis. Ist sie knapper als leadTimeBlock…Hours, lehnt der Server sie ab.
Inventar
Ein Material beschreibt eine Sorte, zum Beispiel «Blachenzelt 4x4m», mit Kategorie, Gewicht, Massen, Lieferant, Kaufpreis, Garantie, Lebensdauer und bis zu drei Fotos. Jedes einzelne Stück im Lager ist ein MaterialItem mit einer 8-stelligen ID wie 8A3F9B2C.
Zustand
Pro Material wird festgelegt, wonach es bei der Rückgabe beurteilt wird. Bei einem Zelt sind das etwa Zustand, Sauberkeit und Dichtigkeit, bei einem Werkzeug nur der Zustand.
// Material: legt die Kriterien fest
customConditionFields: ["Zustand", "Sauberkeit", "Wasserdichtigkeit"]
// MaterialItem: speichert die Werte (0 bis 10)
conditions: { "Zustand": 8, "Sauberkeit": 6, "Wasserdichtigkeit": 10 }Bündel und Baugruppen
Mit bundleSize lassen sich Bündel abbilden («10 Heringe = 1 Bund»). Baugruppen (isBaugruppe) fassen mehrere Materialien mit festen Mengen zu einem Set zusammen. Exemplare können ausserdem ineinander verpackt sein.
Lagerorte
Ein Lagerort hat zwei Stufen: den Standort (SITE, zum Beispiel ein Gebäude) und den Platz darin (SPOT, zum Beispiel ein Regal).
Historie
MaintenanceLog speichert Reparaturen und Kontrollen. MaterialItemHistory speichert jede Änderung an einem Exemplar mit altem und neuem Wert und wer sie gemacht hat.
Abteilungen und Rollen
Jede Abteilung sieht nur ihre eigenen Mitglieder, ihr Material, ihre Lagerorte und Bestellungen. Kategorien, Vorlauf- und Pufferzeiten stellt jede Abteilung selbst ein.
| Rolle | Darf |
|---|---|
USER | Material ausleihen, eigene Bestellungen ansehen, Fundkiste nutzen. |
GRUPPENLEITER | Zusätzlich die Mitglieder der eigenen Gruppe sehen und betreuen. |
ADMIN | Material, Bestellungen, Mitglieder, Gruppen und Einstellungen der Abteilung verwalten. |
SUPERADMIN | Alle Abteilungen und Benutzer verwalten. |
Neue Mitglieder kommen über eine Einladung oder eine Beitrittsanfrage, die ein Admin annimmt. Wer eine neue Abteilung erfasst, wird dort Admin. Wer bei der Registrierung eine neue Gruppe anlegt, wird deren Gruppenleiter.
Fundkiste und Ankündigungen
Fundkiste
Für einen Anlass, etwa das Herbstlager, eröffnet die Abteilung eine Fundkiste. Dort landen Fundsachen, aus Versehen Mitgenommenes, übrig gebliebenes Essen und Vermisstes. Wer etwas erkennt, meldet sich mit ein paar Angaben, auch ohne Konto. Wer etwas erfasst oder abholt, bekommt einen eigenen Link. Abgelaufenes Essen und alte Fundkisten werden automatisch archiviert.
Ankündigungen
Admins können kurze Mitteilungen auf die Startseite stellen, zum Beispiel «Materialraum bis 20.10. geschlossen». Eine Mitteilung ist als Info, Wichtig oder Dringend markiert und für alle oder nur für Mitglieder der Abteilung sichtbar.
E-Mails und Kalender
E-Mails werden über Resend verschickt (lib/mail.ts). In den Einstellungen wählt jede Person, welche sie bekommen will.
Ausleihe
- Neue Bestellung (an die Admins)
- Statusänderung (an die bestellende Person)
- Nachträglich hinzugefügtes Material
- Bestätigung und Bereitstellung für Gäste
- Erinnerung an die Rückgabe
Konto
- Bestätigungscode bei der Registrierung
- Passwort zurücksetzen
- Einladungen und Beitrittsanfragen
- Kontaktformular
Fundkiste
- Eintrag erfasst
- Neuer Eintrag (an die Admins)
- Meldung bestätigt
- Jemand hat einen Gegenstand erkannt
- Abholtermin bestätigt
Kalender
Bestellungen lassen sich mit dem eigenen Google Kalender verbinden. Die E-Mails zu bestätigten, bereitgestellten und laufenden Bestellungen enthalten ausserdem eine .ics-Datei für jede andere Kalender-App.
Verwaltung
QR-Etiketten
Für jedes Exemplar lässt sich ein QR-Etikett drucken. Pro Material wird eingestellt, ob Name, ID, Datum und Gewicht darauf stehen.
Excel
Inventar und Bestellungen lassen sich als .xlsx herunterladen. Beim Import einer bestehenden Liste ordnet man die Spalten in einem Dialog zu.
Statistik
Auswertungen zu Bestellungen, meistgenutztem Material, aktiven Gruppen, Schäden und Verlusten, dazu ein Protokoll aller Aktivitäten.
Tägliche Prüfungen
Zwei Cron-Jobs auf Vercel laufen jeden Tag. /api/cron/returns erinnert an überfällige Rückgaben, /api/cron/maintenance meldet Material am Ende seiner Lebensdauer und räumt die Fundkiste auf.
Bilder
Fotos werden schon im Browser vorbereitet, damit der Upload auch mit schlechtem Netz klappt:
- Zuschneiden und drehen (
ImageCropper.tsx). - Auf höchstens 1920 × 1920 Pixel verkleinern (
lib/imageCompressor.ts). - Ein 400 Pixel breites Vorschaubild als WebP erzeugen (
lib/clientImageCompressor.ts). - In Cloudflare R2 speichern, von dort werden die Bilder ausgeliefert.
Für ältere Bilder ohne Vorschaubild gibt es das Skript scripts/generate_thumbnails.ts.
Sicherheit und Betrieb
Anmeldung
Mit E-Mail und Passwort oder mit Google. Passwörter werden mit bcryptjs gehasht. Nach mehreren Fehlversuchen beim Login oder beim Zurücksetzen des Passworts wird gebremst.
Datenschutz
Gespeichert werden nur Name, E-Mail, Telefonnummer und die Ausleihen. Konten werden auf Wunsch gelöscht. Massgebend ist das Schweizer Datenschutzgesetz.
Hosting
Die App läuft auf Vercel, die Datenbank bei Neon. Alle Verbindungen sind verschlüsselt. Die Cron-Jobs lassen sich nur mit einem geheimen Schlüssel (CRON_SECRET) auslösen.
Änderungen an der Datenbank
Änderungen am Datenbankschema laufen über Prisma-Migrationen. Vercel spielt sie bei jedem Deploy auf die Produktion automatisch ein.
Technik
- Next.js 16 und React 19: Grundgerüst der Webseite
- TypeScript: Programmiersprache
- Tailwind CSS 4: Gestaltung
- PostgreSQL bei Neon: Datenbank
- Prisma 7: Datenbankzugriff und Migrationen
- Vercel: Hosting und tägliche Cron-Jobs
- Cloudflare R2: Speicher für Bilder
- Resend: Versand der E-Mails
- Google: Login und Kalender
Weitere Bibliotheken: FullCalendar, Recharts, read-excel-file, write-excel-file, Papa Parse, qrcode.react, react-easy-crop, Lucide Icons, bcryptjs, next-themes
Fragen oder Ideen? Schreib uns über das Kontaktformular.