From 26df8b427e12dd4734ac78fa6709d79a552cdc92 Mon Sep 17 00:00:00 2001 From: Matthias Hochmeister Date: Sat, 18 Apr 2026 18:00:54 +0200 Subject: [PATCH] fix(mitglieder): improve Fahrgenehmigungen labels, pagination, and AT20 sync --- backend/src/routes/admin.routes.ts | 5 +++ backend/src/services/atemschutz.service.ts | 34 +++++++++++++++++++ .../src/pages/BuchhaltungTransaktionForm.tsx | 2 +- frontend/src/pages/MitgliedDetail.tsx | 18 +++++----- frontend/src/pages/Mitglieder.tsx | 17 +++------- 5 files changed, 54 insertions(+), 22 deletions(-) diff --git a/backend/src/routes/admin.routes.ts b/backend/src/routes/admin.routes.ts index e255780..675103a 100644 --- a/backend/src/routes/admin.routes.ts +++ b/backend/src/routes/admin.routes.ts @@ -20,6 +20,7 @@ import { requirePermission } from '../middleware/rbac.middleware'; import { auditExport } from '../middleware/audit.middleware'; import auditService, { AuditAction, AuditResourceType, AuditFilters } from '../services/audit.service'; import cleanupService from '../services/cleanup.service'; +import atemschutzService from '../services/atemschutz.service'; import userService from '../services/user.service'; import pool from '../config/database'; import logger from '../utils/logger'; @@ -207,6 +208,10 @@ router.post( try { const response = await axios.post(`${FDISK_SYNC_URL}/trigger`, req.body, { timeout: 5000 }); res.json({ success: true, data: response.data }); + // Fire-and-forget: sync AT20 courses to atemschutz_traeger after FDISK data is written + atemschutzService.syncLehrgangFromKurse().catch(err => + logger.error('AT20 Atemschutz-Sync fehlgeschlagen', { error: err }) + ); } catch (err: unknown) { if (axios.isAxiosError(err) && err.response?.status === 409) { res.status(409).json({ success: false, message: 'Sync already in progress' }); diff --git a/backend/src/services/atemschutz.service.ts b/backend/src/services/atemschutz.service.ts index 6aa094a..05cd678 100644 --- a/backend/src/services/atemschutz.service.ts +++ b/backend/src/services/atemschutz.service.ts @@ -216,6 +216,40 @@ class AtemschutzService { } } + // ========================================================================= + // SYNC LEHRGANG FROM FDISK COURSES (AT20) + // ========================================================================= + + /** + * Scans the ausbildung table for AT20 courses with erfolgscode = 'mit Erfolg' + * and upserts atemschutz_traeger records accordingly: + * - No record: creates one with atemschutz_lehrgang=true + lehrgang_datum + * - Record exists, lehrgang false: sets lehrgang=true + date (if unset) + * - Record exists, lehrgang already true: no-op (preserves manual data) + */ + async syncLehrgangFromKurse(): Promise<{ processed: number }> { + try { + const result = await pool.query(` + INSERT INTO atemschutz_traeger (id, user_id, atemschutz_lehrgang, lehrgang_datum) + SELECT uuid_generate_v4(), a.user_id, true, MIN(a.kurs_datum) + FROM ausbildung a + WHERE a.kurs_kurzbezeichnung = 'AT20' + AND a.erfolgscode = 'mit Erfolg' + GROUP BY a.user_id + ON CONFLICT (user_id) DO UPDATE + SET atemschutz_lehrgang = true, + lehrgang_datum = COALESCE(atemschutz_traeger.lehrgang_datum, EXCLUDED.lehrgang_datum), + updated_at = NOW() + `); + const processed = result.rowCount ?? 0; + logger.info('AT20 Atemschutz-Sync abgeschlossen', { processed }); + return { processed }; + } catch (error) { + logger.error('AtemschutzService.syncLehrgangFromKurse fehlgeschlagen', { error }); + throw new Error('AT20 Atemschutz-Sync fehlgeschlagen'); + } + } + // ========================================================================= // EXPIRING CERTIFICATIONS // ========================================================================= diff --git a/frontend/src/pages/BuchhaltungTransaktionForm.tsx b/frontend/src/pages/BuchhaltungTransaktionForm.tsx index b6c044b..5c7b7fa 100644 --- a/frontend/src/pages/BuchhaltungTransaktionForm.tsx +++ b/frontend/src/pages/BuchhaltungTransaktionForm.tsx @@ -245,7 +245,7 @@ export default function BuchhaltungTransaktionForm() {