From b5b75e0d7779ab7a71e33104eb87f30cd00cd52b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reinhard=20X=2E=20F=C3=BCrst?= Date: Sat, 8 Aug 2026 12:57:22 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20Sonderf=C3=BChrungen=20automatisch=20im?= =?UTF-8?q?=20Register=20SoFue2=20nachtragen=20=E2=80=94=20Version=201.13.?= =?UTF-8?q?0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Beim Speichern eines Logbuch-Eintrags der Art 'SF' wird der passende Datensatz in SoFue2 über das Datum gesucht und dort stattgefunden=1, anzahl_echt, bezahlt und remarks gesetzt. Gibt es an dem Tag mehrere Führungen, gewinnt die zeitlich nächstgelegene. Das Ergebnis kommt als Feld 'sofue' aus der PHP-Bridge zurück und wird über dem Formular angezeigt — grün bei Erfolg, gelb, wenn keine Führung gefunden wurde. Der Logbuch-Eintrag wird in dem Fall trotzdem gespeichert. Die Meldung liegt in MainClient, weil LogbuchForm beim Bearbeiten durch onSaved() neu gemountet wird. Co-Authored-By: Claude Opus 5 (1M context) --- ANLEITUNG.md | 8 +++++++ CLAUDE.md | 2 ++ app/MainClient.tsx | 44 +++++++++++++++++++++++++++++++++-- app/api/logbuch/[id]/route.ts | 4 ++-- app/api/logbuch/route.ts | 1 + components/LogbuchForm.tsx | 12 ++++++---- lib/phpdb.ts | 8 +++---- package.json | 2 +- public/anleitung.html | 2 ++ types/logbuch.ts | 8 +++++++ 10 files changed, 78 insertions(+), 13 deletions(-) diff --git a/ANLEITUNG.md b/ANLEITUNG.md index f6c451f..1902fcb 100644 --- a/ANLEITUNG.md +++ b/ANLEITUNG.md @@ -80,6 +80,14 @@ Unterhalb der Kuppelauswahl gibt es drei Tabs: **Spende** – erscheint nur bei Sonderführung, rechts neben Name / Gruppe. Auswahl zwischen `bar`, `überw.`, `kasse` und `keine`; die Angabe ist bei Sonderführungen verpflichtend. Bei `bar` erscheint zusätzlich ein Feld **Betrag (€)**, in das der erhaltene Betrag eingetragen werden muss (Komma oder Punkt als Dezimaltrennzeichen, z. B. `25,50`). +> **Sonderführungen werden automatisch nachgetragen.** Beim Speichern eines Eintrags der Art +> Sonderführung wird die passende Führung im Sonderführungs-Register über das Datum gesucht und +> dort als *stattgefunden* markiert; Besucherzahl, Spende und Bemerkungen werden mit übernommen. +> Ein Nachtragen im Beo-Formular entfällt damit. Gibt es an dem Tag mehrere Führungen, wird die +> zeitlich nächstgelegene genommen. Wird gar keine gefunden, erscheint über dem Formular eine +> gelbe Meldung — der Logbuch-Eintrag ist trotzdem gespeichert, nur das Register muss dann von +> Hand gepflegt werden. + **BEOs** – beteiligte Beobachter. Der eigene Name ist automatisch vorausgewählt. Weitere BEOs können über das Suchfeld hinzugefügt werden; ein Klick auf × entfernt sie wieder. **Beobachtete Objekte** – nicht sichtbar bei BEOS und TD; bei Sonnenführungen fest auf „Sonne" gesetzt. Für alle anderen Arten: diff --git a/CLAUDE.md b/CLAUDE.md index 825f374..2ccca8b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -35,6 +35,8 @@ Next.js 16 App Router application. All pages are server components; interactive `ArtFuehrung` is stored as abbreviations in the DB (`RF`, `SF`, `PrF`, `BEOS`, `SonF`, `TD`, `Beob`, `ToT`, `Sonst`). Display names are in `ARTEN_MAP` in `types/logbuch.ts`. `BEOS` and `TD` hide the Besucher and Objekte fields. `SonF` pre-selects "Sonne" as the only object. `SF` (Sonderführung) additionally shows `SonderName` plus the mandatory `Spende` select (`bar`, `ueberw`, `kasse`, `keine` — labels in `SPENDE_MAP`); `SpendeBetrag` is only filled for `bar`. Both columns stay `NULL` for every other Art, enforced client-side in `LogbuchForm` and server-side in `spendeValues()` in `DB4js_all.php`. +Saving an `SF` entry also writes back into the Sonderführungs register `SoFue2` (`stattgefunden`, `anzahl_echt`, `bezahlt`, `remarks`) via `RepoSoFue::ausLogbuch()` in `DB4js_all.php` — matched by `DATE(wtermin)` against the entry's Beginn, nearest time wins. It runs in the dispatcher **after** the logbuch transaction commits and never throws; the outcome travels back as a `sofue` field in the API response and is shown above the form by `MainClient`. Note `SoFue2` is latin1 while `logbuch` is utf8mb4, and `remarks` holds only 100 chars — `cp1252Safe()` handles both. + ## Deployment `output: 'standalone'` is set in `next.config.ts` for Docker. The MySQL container name in production is `db` — set `DB_HOST=db` in the server's environment. diff --git a/app/MainClient.tsx b/app/MainClient.tsx index 84cf116..c5d91e5 100644 --- a/app/MainClient.tsx +++ b/app/MainClient.tsx @@ -2,13 +2,31 @@ import { useState } from 'react'; import { KUPPELN } from '@/types/logbuch'; -import type { Kuppel, LogbuchEintrag } from '@/types/logbuch'; +import type { Kuppel, LogbuchEintrag, SofueStatus } from '@/types/logbuch'; import LogbuchForm from '@/components/LogbuchForm'; import LogbuchList from '@/components/LogbuchList'; import Statistik from '@/components/Statistik'; import Fahrkosten from '@/components/Fahrkosten'; import packageJson from '@/package.json'; +function datumDe(iso?: string): string { + if (!iso) return ''; + const [j, m, t] = iso.slice(0, 10).split('-'); + return t && m && j ? `${t}.${m}.${j}` : iso; +} + +/** Rückmeldung zur Rückschreibung einer Sonderführung ins Register SoFue2. */ +function sofueMeldung(s: SofueStatus): string { + switch (s.status) { + case 'ok': + return `Sonderführung vom ${datumDe(s.wtermin ?? s.datum)} im Register als stattgefunden eingetragen.`; + case 'kein_termin': + return `Keine Sonderführung am ${datumDe(s.datum)} gefunden — das Sonderführungs-Register wurde nicht aktualisiert.`; + default: + return 'Das Sonderführungs-Register konnte nicht aktualisiert werden. Bitte den Eintrag dort von Hand nachtragen.'; + } +} + interface Props { kuerzel: string; beoId: number; @@ -22,6 +40,7 @@ export default function MainClient({ kuerzel, beoId, beoName, role }: Props) { const [refreshKey, setRefreshKey] = useState(0); const [editEntry, setEditEntry] = useState(null); const [backupState, setBackupState] = useState<'idle' | 'running' | 'ok' | 'error'>('idle'); + const [sofueHinweis, setSofueHinweis] = useState(null); const grafikSrc = '/api/statistik/grafik'; @@ -32,9 +51,10 @@ export default function MainClient({ kuerzel, beoId, beoName, role }: Props) { const currentUserBeo = { ID: beoId, Kuerzel: kuerzel, Name: beoName }; - function handleSaved() { + function handleSaved(sofue?: SofueStatus | null) { setRefreshKey((k) => k + 1); setEditEntry(null); + setSofueHinweis(sofue ?? null); } function handleEdit(entry: LogbuchEintrag) { @@ -148,6 +168,26 @@ export default function MainClient({ kuerzel, beoId, beoName, role }: Props) { Eintrag bearbeiten (ID {editEntry.ID}) )} + {sofueHinweis && sofueHinweis.status !== 'uebersprungen' && ( +
+ {sofueMeldung(sofueHinweis)} + +
+ )} void; + /** sofue: Ergebnis der Rückschreibung ins Sonderführungs-Register, sofern erfolgt. */ + onSaved: (sofue?: SofueStatus | null) => void; } function toLocalDatetimeValue(isoOrDatetime: string): string { @@ -214,6 +215,7 @@ export default function LogbuchForm({ kuppel, currentUserBeo, editEntry, onSaved body: JSON.stringify(body), }); if (!res.ok) throw new Error(await res.text()); + const daten: { sofue?: SofueStatus } = await res.json().catch(() => ({})); setSuccess(true); setTimeout(() => setSuccess(false), 5000); if (!editEntry) { @@ -227,7 +229,9 @@ export default function LogbuchForm({ kuppel, currentUserBeo, editEntry, onSaved setSpende(''); setSpendeBetragRaw(''); } - onSaved(); + // Die Meldung zur Sonderführung gehört nach oben: beim Bearbeiten wird dieses + // Formular durch onSaved() neu gemountet und verlöre eigenen State sofort. + onSaved(daten.sofue ?? null); } catch { setError('Fehler beim Speichern. Bitte erneut versuchen.'); } finally { @@ -440,7 +444,7 @@ export default function LogbuchForm({ kuppel, currentUserBeo, editEntry, onSaved {editEntry && (