From c1de8bd1633802c194e1a4499dea9efc80936aac Mon Sep 17 00:00:00 2001 From: Matthias Hochmeister Date: Wed, 15 Apr 2026 19:26:21 +0200 Subject: [PATCH] =?UTF-8?q?fix(dienstgrad):=20add=20ASB=E2=86=92Abschnitts?= =?UTF-8?q?sachbearbeiter,=20remove=20non-existent=20ranks=20(FA/FF/BOI/BA?= =?UTF-8?q?M=20variants),=20sync=20DB=20constraint,=20TS=20types,=20and=20?= =?UTF-8?q?display=20map?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...dd_abschnittssachbearbeiter_dienstgrad.sql | 66 +++++++++++++++++++ backend/src/models/member.model.ts | 14 ++-- frontend/src/pages/BestellungDetail.tsx | 22 +++---- frontend/src/pages/Mitglieder.tsx | 37 +++++------ frontend/src/types/member.types.ts | 14 ++-- sync/src/db.ts | 7 +- 6 files changed, 112 insertions(+), 48 deletions(-) create mode 100644 backend/src/database/migrations/090_add_abschnittssachbearbeiter_dienstgrad.sql diff --git a/backend/src/database/migrations/090_add_abschnittssachbearbeiter_dienstgrad.sql b/backend/src/database/migrations/090_add_abschnittssachbearbeiter_dienstgrad.sql new file mode 100644 index 0000000..e309dfd --- /dev/null +++ b/backend/src/database/migrations/090_add_abschnittssachbearbeiter_dienstgrad.sql @@ -0,0 +1,66 @@ +-- Migration 090: Add 'Abschnittssachbearbeiter' and remove non-existent ranks +-- (Feuerwehranwärter, Feuerwehrfrau variants, Brandoberinspektor, Brandamtmann) +-- from the mitglieder_profile dienstgrad CHECK constraint. + +-- Null out any existing rows with ranks that are being removed +UPDATE mitglieder_profile +SET dienstgrad = NULL +WHERE dienstgrad IN ( + 'Feuerwehranwärter', + 'Feuerwehrfrau', + 'Oberfeuerwehrfrau', + 'Hauptfeuerwehrfrau', + 'Brandoberinspektor', + 'Brandamtmann', + 'Ehren-Feuerwehrfrau', + 'Ehren-Oberfeuerwehrfrau', + 'Ehren-Hauptfeuerwehrfrau', + 'Ehren-Brandoberinspektor', + 'Ehren-Brandamtmann' +); + +ALTER TABLE mitglieder_profile + DROP CONSTRAINT IF EXISTS mitglieder_profile_dienstgrad_check; + +ALTER TABLE mitglieder_profile + ADD CONSTRAINT mitglieder_profile_dienstgrad_check + CHECK (dienstgrad IS NULL OR dienstgrad IN ( + -- Standard Dienstgrade + 'Jugendfeuerwehrmann', + 'Probefeuerwehrmann', + 'Feuerwehrmann', + 'Oberfeuerwehrmann', + 'Hauptfeuerwehrmann', + 'Löschmeister', + 'Oberlöschmeister', + 'Hauptlöschmeister', + 'Brandmeister', + 'Oberbrandmeister', + 'Hauptbrandmeister', + 'Brandinspektor', + 'Oberbrandinspektor', + 'Verwaltungsmeister', + 'Oberverwaltungsmeister', + 'Hauptverwaltungsmeister', + 'Verwalter', + 'Sachbearbeiter', + 'Abschnittssachbearbeiter', + -- Ehrendienstgrade + 'Ehren-Feuerwehrmann', + 'Ehren-Oberfeuerwehrmann', + 'Ehren-Hauptfeuerwehrmann', + 'Ehren-Löschmeister', + 'Ehren-Oberlöschmeister', + 'Ehren-Hauptlöschmeister', + 'Ehren-Brandmeister', + 'Ehren-Oberbrandmeister', + 'Ehren-Hauptbrandmeister', + 'Ehren-Brandinspektor', + 'Ehren-Oberbrandinspektor', + 'Ehren-Verwaltungsmeister', + 'Ehren-Oberverwaltungsmeister', + 'Ehren-Hauptverwaltungsmeister', + 'Ehren-Verwalter', + 'Ehren-Sachbearbeiter', + 'Ehren-Abschnittssachbearbeiter' + )); diff --git a/backend/src/models/member.model.ts b/backend/src/models/member.model.ts index 714ab9f..6e48138 100644 --- a/backend/src/models/member.model.ts +++ b/backend/src/models/member.model.ts @@ -6,13 +6,11 @@ import { z } from 'zod'; // ============================================================ export const DIENSTGRAD_VALUES = [ - 'Feuerwehranwärter', + 'Jugendfeuerwehrmann', + 'Probefeuerwehrmann', 'Feuerwehrmann', - 'Feuerwehrfrau', 'Oberfeuerwehrmann', - 'Oberfeuerwehrfrau', 'Hauptfeuerwehrmann', - 'Hauptfeuerwehrfrau', 'Löschmeister', 'Oberlöschmeister', 'Hauptlöschmeister', @@ -21,8 +19,12 @@ export const DIENSTGRAD_VALUES = [ 'Hauptbrandmeister', 'Brandinspektor', 'Oberbrandinspektor', - 'Brandoberinspektor', - 'Brandamtmann', + 'Verwaltungsmeister', + 'Oberverwaltungsmeister', + 'Hauptverwaltungsmeister', + 'Verwalter', + 'Sachbearbeiter', + 'Abschnittssachbearbeiter', ] as const; export const STATUS_VALUES = [ diff --git a/frontend/src/pages/BestellungDetail.tsx b/frontend/src/pages/BestellungDetail.tsx index 4183b59..82e48a0 100644 --- a/frontend/src/pages/BestellungDetail.tsx +++ b/frontend/src/pages/BestellungDetail.tsx @@ -63,25 +63,25 @@ import { ConfirmDialog, StatusChip, PageHeader } from '../components/templates'; // ── Helpers ── const DIENSTGRAD_KURZ: Record = { - 'Feuerwehranwärter': 'FA', 'Jugendfeuerwehrmann': 'JFM', 'Probefeuerwehrmann': 'PFM', - 'Feuerwehrmann': 'FM', 'Feuerwehrfrau': 'FF', - 'Oberfeuerwehrmann': 'OFM', 'Oberfeuerwehrfrau': 'OFF', - 'Hauptfeuerwehrmann': 'HFM', 'Hauptfeuerwehrfrau': 'HFF', + 'Jugendfeuerwehrmann': 'JFM', 'Probefeuerwehrmann': 'PFM', + 'Feuerwehrmann': 'FM', + 'Oberfeuerwehrmann': 'OFM', + 'Hauptfeuerwehrmann': 'HFM', 'Löschmeister': 'LM', 'Oberlöschmeister': 'OLM', 'Hauptlöschmeister': 'HLM', 'Brandmeister': 'BM', 'Oberbrandmeister': 'OBM', 'Hauptbrandmeister': 'HBM', - 'Brandinspektor': 'BI', 'Oberbrandinspektor': 'OBI', 'Brandoberinspektor': 'BOI', - 'Brandamtmann': 'BAM', + 'Brandinspektor': 'BI', 'Oberbrandinspektor': 'OBI', 'Verwaltungsmeister': 'VM', 'Oberverwaltungsmeister': 'OVM', 'Hauptverwaltungsmeister': 'HVM', 'Verwalter': 'V', - 'Ehren-Feuerwehrmann': 'E-FM', 'Ehren-Feuerwehrfrau': 'E-FF', - 'Ehren-Oberfeuerwehrmann': 'E-OFM', 'Ehren-Oberfeuerwehrfrau': 'E-OFF', - 'Ehren-Hauptfeuerwehrmann': 'E-HFM', 'Ehren-Hauptfeuerwehrfrau': 'E-HFF', + 'Sachbearbeiter': 'SB', 'Abschnittssachbearbeiter': 'ASB', + 'Ehren-Feuerwehrmann': 'E-FM', + 'Ehren-Oberfeuerwehrmann': 'E-OFM', + 'Ehren-Hauptfeuerwehrmann': 'E-HFM', 'Ehren-Löschmeister': 'E-LM', 'Ehren-Oberlöschmeister': 'E-OLM', 'Ehren-Hauptlöschmeister': 'E-HLM', 'Ehren-Brandmeister': 'E-BM', 'Ehren-Oberbrandmeister': 'E-OBM', 'Ehren-Hauptbrandmeister': 'E-HBM', - 'Ehren-Brandinspektor': 'E-BI', 'Ehren-Oberbrandinspektor': 'E-OBI', 'Ehren-Brandoberinspektor': 'E-BOI', - 'Ehren-Brandamtmann': 'E-BAM', + 'Ehren-Brandinspektor': 'E-BI', 'Ehren-Oberbrandinspektor': 'E-OBI', 'Ehren-Verwaltungsmeister': 'E-VM', 'Ehren-Oberverwaltungsmeister': 'E-OVM', 'Ehren-Hauptverwaltungsmeister': 'E-HVM', 'Ehren-Verwalter': 'E-V', + 'Ehren-Sachbearbeiter': 'ESB', 'Ehren-Abschnittssachbearbeiter': 'EASB', }; const kurzDienstgrad = (d?: string) => (d ? (DIENSTGRAD_KURZ[d] ?? d) : undefined); diff --git a/frontend/src/pages/Mitglieder.tsx b/frontend/src/pages/Mitglieder.tsx index c811ef4..c85b6b2 100644 --- a/frontend/src/pages/Mitglieder.tsx +++ b/frontend/src/pages/Mitglieder.tsx @@ -117,7 +117,7 @@ function Mitglieder() { } finally { setLoading(false); } - }, [debouncedSearch, selectedStatus, selectedDienstgrad, page]); + }, [debouncedSearch, selectedStatus, selectedDienstgrad, page, pageSize]); useEffect(() => { // Reset to page 0 when search changes @@ -137,8 +137,7 @@ function Mitglieder() { return; } fetchMembers(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [page, selectedStatus, selectedDienstgrad]); + }, [page, pageSize, selectedStatus, selectedDienstgrad]); // ---------------------------------------------------------------- // Event handlers @@ -275,6 +274,22 @@ function Mitglieder() { {/* Table */} + setPage(newPage)} + rowsPerPage={pageSize} + rowsPerPageOptions={[25, 50, 100, { value: -1, label: 'Alle' }]} + onRowsPerPageChange={(e) => { + setPageSize(parseInt(e.target.value, 10)); + setPage(0); + }} + labelRowsPerPage="Einträge pro Seite:" + labelDisplayedRows={({ from, to, count }) => + `${from}–${to} von ${count !== -1 ? count : `mehr als ${to}`}` + } + /> columns={[ { key: 'profile_picture_url', label: 'Foto', width: 56, sortable: false, searchable: false, render: (member) => { @@ -330,22 +345,6 @@ function Mitglieder() { stickyHeader /> - setPage(newPage)} - rowsPerPage={pageSize} - rowsPerPageOptions={[25, 50, 100, { value: -1, label: 'Alle' }]} - onRowsPerPageChange={(e) => { - setPageSize(parseInt(e.target.value, 10)); - setPage(0); - }} - labelRowsPerPage="Einträge pro Seite:" - labelDisplayedRows={({ from, to, count }) => - `${from}–${to} von ${count !== -1 ? count : `mehr als ${to}`}` - } - /> diff --git a/frontend/src/types/member.types.ts b/frontend/src/types/member.types.ts index 9724e4c..5dc1587 100644 --- a/frontend/src/types/member.types.ts +++ b/frontend/src/types/member.types.ts @@ -4,13 +4,11 @@ // ---------------------------------------------------------------- export const DIENSTGRAD_VALUES = [ - 'Feuerwehranwärter', + 'Jugendfeuerwehrmann', + 'Probefeuerwehrmann', 'Feuerwehrmann', - 'Feuerwehrfrau', 'Oberfeuerwehrmann', - 'Oberfeuerwehrfrau', 'Hauptfeuerwehrmann', - 'Hauptfeuerwehrfrau', 'Löschmeister', 'Oberlöschmeister', 'Hauptlöschmeister', @@ -19,8 +17,12 @@ export const DIENSTGRAD_VALUES = [ 'Hauptbrandmeister', 'Brandinspektor', 'Oberbrandinspektor', - 'Brandoberinspektor', - 'Brandamtmann', + 'Verwaltungsmeister', + 'Oberverwaltungsmeister', + 'Hauptverwaltungsmeister', + 'Verwalter', + 'Sachbearbeiter', + 'Abschnittssachbearbeiter', ] as const; export const STATUS_VALUES = [ diff --git a/sync/src/db.ts b/sync/src/db.ts index c795a4c..92581f5 100644 --- a/sync/src/db.ts +++ b/sync/src/db.ts @@ -17,15 +17,11 @@ function log(msg: string) { */ function mapDienstgrad(raw: string): string | null { const abbrevMap: Record = { - 'fa': 'Feuerwehranwärter', 'jfm': 'Jugendfeuerwehrmann', 'pfm': 'Probefeuerwehrmann', 'fm': 'Feuerwehrmann', - 'ff': 'Feuerwehrfrau', 'ofm': 'Oberfeuerwehrmann', - 'off': 'Oberfeuerwehrfrau', 'hfm': 'Hauptfeuerwehrmann', - 'hff': 'Hauptfeuerwehrfrau', 'lm': 'Löschmeister', 'olm': 'Oberlöschmeister', 'hlm': 'Hauptlöschmeister', @@ -34,13 +30,12 @@ function mapDienstgrad(raw: string): string | null { 'hbm': 'Hauptbrandmeister', 'bi': 'Brandinspektor', 'obi': 'Oberbrandinspektor', - 'boi': 'Brandoberinspektor', - 'bam': 'Brandamtmann', 'vm': 'Verwaltungsmeister', 'ovm': 'Oberverwaltungsmeister', 'hvm': 'Hauptverwaltungsmeister', 'v': 'Verwalter', 'sb': 'Sachbearbeiter', + 'asb': 'Abschnittssachbearbeiter', }; const normalized = raw.trim().toLowerCase().replace(/\*/g, '');