From 3753cec87c60a313151e0f1176726e8412ea3502 Mon Sep 17 00:00:00 2001 From: ah-admin Date: Sun, 4 Oct 2026 17:22:14 +0200 Subject: [PATCH] v1.27: Zaehleranlage ohne Haus-Feld, Ablesedatum Pflicht --- kimi.md | 8 ++++ server/src/index.js | 8 ++-- src/pages/Zaehler.tsx | 85 +++++++++++++++++++++++++++++-------------- src/store/store.tsx | 1 + 4 files changed, 72 insertions(+), 30 deletions(-) diff --git a/kimi.md b/kimi.md index e4ef26d..c8ab7de 100644 --- a/kimi.md +++ b/kimi.md @@ -189,6 +189,14 @@ tar -czf mp-dist.tar.gz -C dist . ## 8. Verlauf / offene Punkte +**Erledigt 04.10. (v1.27):** Zähleranlage ohne Haus-Feld, mit Ablesedatum. +POST /api/meters akzeptiert standAm (Validierung via zaehlerVerlauf.parseDatum, +Default heute); stand_am + erster Verlaufseintrag (erstbefund) nutzen das Datum. +Frontend ZaehlerDialog: Haus-Textfeld entfernt – Haus folgt der Wohneinheit, +nur bei „Allgemeinzähler (ohne Wohnung)" erscheint eine Haus-Auswahl +(buildings-Select, Anzeige b.adresse). Ablesedatum (type=date, Default heute, +Pflicht) nur bei Neuanlage; Frontend wandelt YYYY-MM-DD → TT.MM.JJJJ. + **Erledigt 04.10. (v1.26):** Haus-Formular ohne Namensfeld, Adresse in Einzelfelder. buildings um strasse/hausnummer/plz/ort/lage/wohnflaeche/grundstuecksflaeche erweitert (7 idempotente ALTERs, quadratmeter bleibt Alt-Spalte); name (PK) und diff --git a/server/src/index.js b/server/src/index.js index 86a9757..f47b418 100644 --- a/server/src/index.js +++ b/server/src/index.js @@ -552,11 +552,13 @@ const ZAEHLER_ARTEN = ['Strom', 'Wasser', 'Gas', 'Wärme', 'Sonstiges'] app.post('/api/meters', auth, adminOnly, async (req, res, next) => { try { - const { nummer, art, stand, unitId, building } = req.body || {} + const { nummer, art, stand, unitId, building, standAm } = req.body || {} if (!String(nummer || '').trim()) return res.status(400).json({ fehler: 'Zählernummer fehlt.' }) if (!ZAEHLER_ARTEN.includes(art)) return res.status(400).json({ fehler: 'Ungültige Zählerart.' }) const standZahl = Number(stand) if (!Number.isFinite(standZahl) || standZahl < 0) return res.status(400).json({ fehler: 'Ungültiger Zählerstand.' }) + const datum = String(standAm || '').trim() || deutschesDatum() + if (!zaehlerVerlauf.parseDatum(datum)) return res.status(400).json({ fehler: 'Ungültiges Ablesedatum.' }) // Wohnung angegeben → Haus aus der Wohnung ableiten; sonst muss das Haus im Body stehen let einheitId = unitId ? String(unitId) : null let haus = String(building || '').trim() @@ -571,11 +573,11 @@ app.post('/api/meters', auth, adminOnly, async (req, res, next) => { const { rows } = await pool.query( `INSERT INTO meters (id, unit_id, building, nummer, art, stand, stand_am, geaendert_am) VALUES ($1,$2,$3,$4,$5,$6,$7,$8) RETURNING *`, - [id, einheitId, haus, String(nummer).trim(), art, standZahl, deutschesDatum(), deutschesDatumZeit()], + [id, einheitId, haus, String(nummer).trim(), art, standZahl, datum, deutschesDatumZeit()], ) // Anfangsstand als erster Verlaufseintrag await zaehlerVerlauf.standEintragen({ - meter: rows[0], stand: standZahl, standAm: deutschesDatum(), + meter: rows[0], stand: standZahl, standAm: datum, erfasstVon: req.user.name, quelle: 'erstbefund', }) res.json({ meter: mapMeter(rows[0]) }) diff --git a/src/pages/Zaehler.tsx b/src/pages/Zaehler.tsx index 1d12fda..4c45e22 100644 --- a/src/pages/Zaehler.tsx +++ b/src/pages/Zaehler.tsx @@ -52,6 +52,10 @@ function ZaehlerDialog({ meter }: { meter?: Meter }) { const [nummer, setNummer] = useState(meter?.nummer ?? '') const [art, setArt] = useState(meter?.art ?? 'Strom') const [stand, setStand] = useState(String(meter?.stand ?? '').replace('.', ',')) + const [standAm, setStandAm] = useState(() => { + const d = new Date() + return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}` + }) const [unitId, setUnitId] = useState(meter?.unitId ?? 'haus') const [building, setBuilding] = useState(meter?.building ?? buildings[0]?.name ?? '') const [sende, setSende] = useState(false) @@ -60,16 +64,23 @@ function ZaehlerDialog({ meter }: { meter?: Meter }) { const submit = async () => { const standZahl = Number(stand.replace(',', '.')) if (!nummer.trim() || !Number.isFinite(standZahl) || standZahl < 0 || sende) return + if (!bearbeiten && !standAm) return setSende(true) setFehler(null) - const payload = { - nummer: nummer.trim(), - art, - stand: standZahl, - unitId: unitId === 'haus' ? null : unitId, - building, + let err: string | null + if (bearbeiten) { + err = await updateZaehler(meter!.id, { + nummer: nummer.trim(), art, stand: standZahl, + unitId: unitId === 'haus' ? null : unitId, building, + }) + } else { + err = await addZaehler({ + nummer: nummer.trim(), art, stand: standZahl, + // „YYYY-MM-DD" (Datumseingabe) → „TT.MM.JJJJ" (Serverformat) + standAm: standAm.split('-').reverse().join('.'), + unitId: unitId === 'haus' ? null : unitId, building, + }) } - const err = bearbeiten ? await updateZaehler(meter!.id, payload) : await addZaehler(payload) setSende(false) if (err) return setFehler(err) setOpen(false) @@ -115,15 +126,28 @@ function ZaehlerDialog({ meter }: { meter?: Meter }) { -
- - setStand(e.target.value)} - /> +
+
+ + setStand(e.target.value)} + /> +
+ {!bearbeiten && ( +
+ + setStandAm(e.target.value)} + /> +
+ )}
@@ -139,17 +163,24 @@ function ZaehlerDialog({ meter }: { meter?: Meter }) { ))} + {unitId !== 'haus' && ( +

Das Haus folgt automatisch der gewählten Wohneinheit.

+ )}
-
- - setBuilding(e.target.value)} - disabled={unitId !== 'haus'} - title={unitId !== 'haus' ? 'Das Haus wird aus der Wohneinheit übernommen.' : undefined} - /> -
+ {unitId === 'haus' && ( +
+ + +

Nur nötig für Zähler ohne Wohnungsbezug.

+
+ )}
{fehler && (
@@ -159,7 +190,7 @@ function ZaehlerDialog({ meter }: { meter?: Meter }) {
- diff --git a/src/store/store.tsx b/src/store/store.tsx index ec6d388..bfee025 100644 --- a/src/store/store.tsx +++ b/src/store/store.tsx @@ -123,6 +123,7 @@ interface Store extends Bootstrap { nummer: string art: ZaehlerArt stand: number + standAm: string // Ablesedatum des Anfangsstands (TT.MM.JJJJ) unitId: string | null building: string }) => Promise