v1.27: Zaehleranlage ohne Haus-Feld, Ablesedatum Pflicht
This commit is contained in:
@@ -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
|
||||
|
||||
+5
-3
@@ -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]) })
|
||||
|
||||
+58
-27
@@ -52,6 +52,10 @@ function ZaehlerDialog({ meter }: { meter?: Meter }) {
|
||||
const [nummer, setNummer] = useState(meter?.nummer ?? '')
|
||||
const [art, setArt] = useState<ZaehlerArt>(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<string>(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 }) {
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="z-stand">Aktueller Zählerstand</Label>
|
||||
<Input
|
||||
id="z-stand"
|
||||
inputMode="decimal"
|
||||
placeholder="z. B. 48231,4"
|
||||
value={stand}
|
||||
onChange={(e) => setStand(e.target.value)}
|
||||
/>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="z-stand">Aktueller Zählerstand</Label>
|
||||
<Input
|
||||
id="z-stand"
|
||||
inputMode="decimal"
|
||||
placeholder="z. B. 48231,4"
|
||||
value={stand}
|
||||
onChange={(e) => setStand(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
{!bearbeiten && (
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="z-datum">Ablesedatum</Label>
|
||||
<Input
|
||||
id="z-datum"
|
||||
type="date"
|
||||
value={standAm}
|
||||
onChange={(e) => setStandAm(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div className="space-y-1.5">
|
||||
@@ -139,17 +163,24 @@ function ZaehlerDialog({ meter }: { meter?: Meter }) {
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{unitId !== 'haus' && (
|
||||
<p className="text-xs text-slate-400">Das Haus folgt automatisch der gewählten Wohneinheit.</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="z-haus">Haus</Label>
|
||||
<Input
|
||||
id="z-haus"
|
||||
value={building}
|
||||
onChange={(e) => setBuilding(e.target.value)}
|
||||
disabled={unitId !== 'haus'}
|
||||
title={unitId !== 'haus' ? 'Das Haus wird aus der Wohneinheit übernommen.' : undefined}
|
||||
/>
|
||||
</div>
|
||||
{unitId === 'haus' && (
|
||||
<div className="space-y-1.5">
|
||||
<Label>Haus</Label>
|
||||
<Select value={building} onValueChange={setBuilding}>
|
||||
<SelectTrigger><SelectValue /></SelectTrigger>
|
||||
<SelectContent>
|
||||
{buildings.map((b) => (
|
||||
<SelectItem key={b.name} value={b.name}>{b.adresse}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-xs text-slate-400">Nur nötig für Zähler ohne Wohnungsbezug.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{fehler && (
|
||||
<div className="rounded-lg bg-red-50 px-3 py-2 text-sm text-red-700 dark:bg-red-950 dark:text-red-300" role="alert">
|
||||
@@ -159,7 +190,7 @@ function ZaehlerDialog({ meter }: { meter?: Meter }) {
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setOpen(false)}>Abbrechen</Button>
|
||||
<Button onClick={submit} disabled={!nummer.trim() || !stand.trim() || sende}>
|
||||
<Button onClick={submit} disabled={!nummer.trim() || !stand.trim() || (!bearbeiten && !standAm) || sende}>
|
||||
{sende ? 'Wird gespeichert …' : bearbeiten ? 'Speichern' : 'Zähler anlegen'}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
@@ -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<string | null>
|
||||
|
||||
Reference in New Issue
Block a user