refactor(mitglieder): replace legacy status values (passiv/anwärter/ausgetreten/…) with aktiv/kind/jugend/reserve across backend, frontend, and sync

This commit is contained in:
Matthias Hochmeister
2026-04-15 19:43:18 +02:00
parent c1de8bd163
commit 260b71baf8
11 changed files with 84 additions and 67 deletions

View File

@@ -47,7 +47,7 @@ class MemberController {
pageSize,
} = req.query as Record<string, string | undefined>;
// Arrays can be sent as ?status[]=aktiv&status[]=passiv or CSV
// Arrays can be sent as ?status[]=aktiv&status[]=jugend or CSV
const statusParam = req.query['status'] as string | string[] | undefined;
const dienstgradParam = req.query['dienstgrad'] as string | string[] | undefined;

View File

@@ -0,0 +1,16 @@
-- Migration: 090_update_status_values
-- Replace old status values with FDISK-aligned values: aktiv, kind, jugend, reserve.
-- Old values passiv, ehrenmitglied, jugendfeuerwehr, anwärter, ausgetreten are removed.
-- Idempotent: safe to run multiple times.
-- 1. Drop existing CHECK constraint
ALTER TABLE mitglieder_profile DROP CONSTRAINT IF EXISTS mitglieder_profile_status_check;
-- 2. Migrate existing data
UPDATE mitglieder_profile SET status = 'jugend' WHERE status = 'jugendfeuerwehr';
UPDATE mitglieder_profile SET status = NULL
WHERE status IN ('passiv', 'ehrenmitglied', 'anwärter', 'ausgetreten');
-- 3. Re-add CHECK with new allowed values (NULL still allowed for profiles without FDISK sync)
ALTER TABLE mitglieder_profile ADD CONSTRAINT mitglieder_profile_status_check
CHECK (status IS NULL OR status IN ('aktiv', 'kind', 'jugend', 'reserve'));

View File

@@ -29,11 +29,9 @@ export const DIENSTGRAD_VALUES = [
export const STATUS_VALUES = [
'aktiv',
'passiv',
'ehrenmitglied',
'jugendfeuerwehr',
'anwärter',
'ausgetreten',
'kind',
'jugend',
'reserve',
] as const;
export const FUNKTION_VALUES = [
@@ -232,9 +230,7 @@ export type SelfUpdateMemberProfileData = z.infer<typeof SelfUpdateMemberProfile
export interface MemberStats {
total: number;
aktiv: number;
passiv: number;
ehrenmitglied: number;
jugendfeuerwehr: number;
anwärter: number;
ausgetreten: number;
kind: number;
jugend: number;
reserve: number;
}

View File

@@ -23,7 +23,7 @@ class AtemschutzService {
result = await pool.query(`
SELECT *
FROM atemschutz_uebersicht
WHERE mitglied_status IS NULL OR mitglied_status IN ('aktiv', 'anwärter')
WHERE mitglied_status IS NULL OR mitglied_status IN ('aktiv')
ORDER BY user_family_name, user_given_name
`);
} else {
@@ -301,7 +301,7 @@ class AtemschutzService {
) AS leistungstest_bald_faellig,
COUNT(*) FILTER (WHERE einsatzbereit = TRUE) AS einsatzbereit
FROM atemschutz_uebersicht
WHERE mitglied_status IS NULL OR mitglied_status IN ('aktiv', 'anwärter')
WHERE mitglied_status IS NULL OR mitglied_status IN ('aktiv')
`);
const row = result.rows[0] ?? {};

View File

@@ -630,24 +630,20 @@ class MemberService {
try {
const result = await pool.query(`
SELECT
COUNT(*)::INTEGER AS total,
COUNT(*) FILTER (WHERE status = 'aktiv')::INTEGER AS aktiv,
COUNT(*) FILTER (WHERE status = 'passiv')::INTEGER AS passiv,
COUNT(*) FILTER (WHERE status = 'ehrenmitglied')::INTEGER AS ehrenmitglied,
COUNT(*) FILTER (WHERE status = 'jugendfeuerwehr')::INTEGER AS jugendfeuerwehr,
COUNT(*) FILTER (WHERE status = 'anwärter')::INTEGER AS "anwärter",
COUNT(*) FILTER (WHERE status = 'ausgetreten')::INTEGER AS ausgetreten
COUNT(*)::INTEGER AS total,
COUNT(*) FILTER (WHERE status = 'aktiv')::INTEGER AS aktiv,
COUNT(*) FILTER (WHERE status = 'kind')::INTEGER AS kind,
COUNT(*) FILTER (WHERE status = 'jugend')::INTEGER AS jugend,
COUNT(*) FILTER (WHERE status = 'reserve')::INTEGER AS reserve
FROM mitglieder_profile
`);
return (result.rows[0] as MemberStats) ?? {
total: 0,
aktiv: 0,
passiv: 0,
ehrenmitglied: 0,
jugendfeuerwehr: 0,
'anwärter': 0,
ausgetreten: 0,
kind: 0,
jugend: 0,
reserve: 0,
};
} catch (error) {
logger.error('Error fetching member stats', { error });

View File

@@ -30,22 +30,22 @@ declare module 'multer' {
}
declare namespace Express {
namespace Multer {
interface File {
fieldname: string;
originalname: string;
encoding: string;
mimetype: string;
size: number;
destination: string;
filename: string;
path: string;
buffer: Buffer;
}
}
interface Request {
file?: {
fieldname: string;
originalname: string;
encoding: string;
mimetype: string;
size: number;
buffer: Buffer;
};
files?: {
fieldname: string;
originalname: string;
encoding: string;
mimetype: string;
size: number;
buffer: Buffer;
}[];
file?: Multer.File;
files?: Multer.File[];
}
}