This commit is contained in:
Matthias Hochmeister
2026-03-13 21:01:54 +01:00
parent ab29c43735
commit b7b4fe2fc9
14 changed files with 566 additions and 60 deletions

View File

@@ -1,5 +1,11 @@
import { Pool } from 'pg';
import { FdiskMember, FdiskAusbildung } from './types';
import {
FdiskMember,
FdiskAusbildung,
FdiskBefoerderung,
FdiskUntersuchung,
FdiskFahrgenehmigung,
} from './types';
function log(msg: string) {
console.log(`[db] ${new Date().toISOString()} ${msg}`);
@@ -63,6 +69,9 @@ export async function syncToDatabase(
pool: Pool,
members: FdiskMember[],
ausbildungen: FdiskAusbildung[],
befoerderungen: FdiskBefoerderung[],
untersuchungen: FdiskUntersuchung[],
fahrgenehmigungen: FdiskFahrgenehmigung[],
force = false
): Promise<void> {
const client = await pool.connect();
@@ -137,7 +146,7 @@ export async function syncToDatabase(
);
const cur = currentResult.rows[0];
// Update mitglieder_profile with FDISK data
// Update mitglieder_profile with FDISK data (core + extended profile fields)
const dienstgrad = mapDienstgrad(member.dienstgrad);
await client.query(
@@ -147,12 +156,21 @@ export async function syncToDatabase(
eintrittsdatum = COALESCE($3::date, eintrittsdatum),
austrittsdatum = $4::date,
geburtsdatum = COALESCE($5::date, geburtsdatum),
${dienstgrad ? 'dienstgrad = $6,' : ''}
geburtsort = COALESCE($6, geburtsort),
geschlecht = COALESCE($7, geschlecht),
beruf = COALESCE($8, beruf),
wohnort = COALESCE($9, wohnort),
plz = COALESCE($10, plz),
${dienstgrad ? 'dienstgrad = $11,' : ''}
updated_at = NOW()
WHERE user_id = ${dienstgrad ? '$7' : '$6'}`,
WHERE user_id = ${dienstgrad ? '$12' : '$11'}`,
dienstgrad
? [member.standesbuchNr, member.status, member.eintrittsdatum, member.abmeldedatum, member.geburtsdatum, dienstgrad, userId]
: [member.standesbuchNr, member.status, member.eintrittsdatum, member.abmeldedatum, member.geburtsdatum, userId]
? [member.standesbuchNr, member.status, member.eintrittsdatum, member.abmeldedatum, member.geburtsdatum,
member.geburtsort, member.geschlecht, member.beruf, member.wohnort, member.plz,
dienstgrad, userId]
: [member.standesbuchNr, member.status, member.eintrittsdatum, member.abmeldedatum, member.geburtsdatum,
member.geburtsort, member.geschlecht, member.beruf, member.wohnort, member.plz,
userId]
);
// Detect and log what changed
@@ -195,7 +213,6 @@ export async function syncToDatabase(
let ausbildungSkipped = 0;
for (const ausb of ausbildungen) {
// Find user_id by standesbuch_nr
const result = await client.query<{ user_id: string }>(
`SELECT user_id FROM mitglieder_profile WHERE fdisk_standesbuch_nr = $1`,
[ausb.standesbuchNr]
@@ -208,7 +225,6 @@ export async function syncToDatabase(
const userId = result.rows[0].user_id;
// xmax = 0 means a fresh INSERT (not an update of an existing row)
const upsertResult = await client.query<{ was_inserted: boolean }>(
`INSERT INTO ausbildung (user_id, kursname, kurs_datum, ablaufdatum, ort, bemerkung, fdisk_sync_key)
VALUES ($1, $2, $3::date, $4::date, $5, $6, $7)
@@ -227,13 +243,25 @@ export async function syncToDatabase(
log(`New Ausbildung: ${ausb.standesbuchNr}${ausb.kursname}${ausb.kursDatum ? ` (${ausb.kursDatum})` : ''}`);
ausbildungNew++;
} else {
log(`Updated Ausbildung: ${ausb.standesbuchNr}${ausb.kursname}${ausb.kursDatum ? ` (${ausb.kursDatum})` : ''}`);
ausbildungUpdated++;
}
}
await client.query('COMMIT');
log(`Ausbildungen: ${ausbildungNew} neu, ${ausbildungUpdated} unverändert, ${ausbildungSkipped} übersprungen`);
// Upsert Beförderungen
const befoerderungStats = await syncBefoerderungen(client, befoerderungen);
log(`Beförderungen: ${befoerderungStats.neu} neu, ${befoerderungStats.updated} unverändert, ${befoerderungStats.skipped} übersprungen`);
// Upsert Untersuchungen
const untersuchungStats = await syncUntersuchungen(client, untersuchungen);
log(`Untersuchungen: ${untersuchungStats.neu} neu, ${untersuchungStats.updated} unverändert, ${untersuchungStats.skipped} übersprungen`);
// Upsert Fahrgenehmigungen
const fahrgenStats = await syncFahrgenehmigungen(client, fahrgenehmigungen);
log(`Fahrgenehmigungen: ${fahrgenStats.neu} neu, ${fahrgenStats.updated} unverändert, ${fahrgenStats.skipped} übersprungen`);
await client.query('COMMIT');
} catch (err) {
await client.query('ROLLBACK');
throw err;
@@ -241,3 +269,119 @@ export async function syncToDatabase(
client.release();
}
}
async function syncBefoerderungen(
client: any,
befoerderungen: FdiskBefoerderung[]
): Promise<{ neu: number; updated: number; skipped: number }> {
let neu = 0, updated = 0, skipped = 0;
for (const b of befoerderungen) {
const result = await client.query<{ user_id: string }>(
`SELECT user_id FROM mitglieder_profile WHERE fdisk_standesbuch_nr = $1`,
[b.standesbuchNr]
);
if (result.rows.length === 0) { skipped++; continue; }
const userId = result.rows[0].user_id;
const upsertResult = await client.query<{ was_inserted: boolean }>(
`INSERT INTO befoerderungen (user_id, datum, dienstgrad, fdisk_sync_key)
VALUES ($1, $2::date, $3, $4)
ON CONFLICT (user_id, fdisk_sync_key) DO UPDATE SET
datum = EXCLUDED.datum,
dienstgrad = EXCLUDED.dienstgrad,
updated_at = NOW()
RETURNING (xmax = 0) AS was_inserted`,
[userId, b.datum, b.dienstgrad, b.syncKey]
);
if (upsertResult.rows[0]?.was_inserted) {
log(`New Beförderung: ${b.standesbuchNr}${b.dienstgrad}${b.datum ? ` (${b.datum})` : ''}`);
neu++;
} else {
updated++;
}
}
return { neu, updated, skipped };
}
async function syncUntersuchungen(
client: any,
untersuchungen: FdiskUntersuchung[]
): Promise<{ neu: number; updated: number; skipped: number }> {
let neu = 0, updated = 0, skipped = 0;
for (const u of untersuchungen) {
const result = await client.query<{ user_id: string }>(
`SELECT user_id FROM mitglieder_profile WHERE fdisk_standesbuch_nr = $1`,
[u.standesbuchNr]
);
if (result.rows.length === 0) { skipped++; continue; }
const userId = result.rows[0].user_id;
const upsertResult = await client.query<{ was_inserted: boolean }>(
`INSERT INTO untersuchungen (user_id, datum, anmerkungen, art, ergebnis, fdisk_sync_key)
VALUES ($1, $2::date, $3, $4, $5, $6)
ON CONFLICT (user_id, fdisk_sync_key) DO UPDATE SET
datum = EXCLUDED.datum,
anmerkungen = EXCLUDED.anmerkungen,
art = EXCLUDED.art,
ergebnis = EXCLUDED.ergebnis,
updated_at = NOW()
RETURNING (xmax = 0) AS was_inserted`,
[userId, u.datum, u.anmerkungen, u.art, u.ergebnis, u.syncKey]
);
if (upsertResult.rows[0]?.was_inserted) {
log(`New Untersuchung: ${u.standesbuchNr} — [${u.art}] ${u.ergebnis ?? '—'}${u.datum ? ` (${u.datum})` : ''} | ${u.anmerkungen ?? ''}`);
neu++;
} else {
updated++;
}
}
return { neu, updated, skipped };
}
async function syncFahrgenehmigungen(
client: any,
fahrgenehmigungen: FdiskFahrgenehmigung[]
): Promise<{ neu: number; updated: number; skipped: number }> {
let neu = 0, updated = 0, skipped = 0;
for (const f of fahrgenehmigungen) {
const result = await client.query<{ user_id: string }>(
`SELECT user_id FROM mitglieder_profile WHERE fdisk_standesbuch_nr = $1`,
[f.standesbuchNr]
);
if (result.rows.length === 0) { skipped++; continue; }
const userId = result.rows[0].user_id;
const upsertResult = await client.query<{ was_inserted: boolean }>(
`INSERT INTO fahrgenehmigungen (user_id, ausstellungsdatum, gueltig_bis, behoerde, nummer, klasse, fdisk_sync_key)
VALUES ($1, $2::date, $3::date, $4, $5, $6, $7)
ON CONFLICT (user_id, fdisk_sync_key) DO UPDATE SET
ausstellungsdatum = EXCLUDED.ausstellungsdatum,
gueltig_bis = EXCLUDED.gueltig_bis,
behoerde = EXCLUDED.behoerde,
nummer = EXCLUDED.nummer,
klasse = EXCLUDED.klasse,
updated_at = NOW()
RETURNING (xmax = 0) AS was_inserted`,
[userId, f.ausstellungsdatum, f.gueltigBis, f.behoerde, f.nummer, f.klasse, f.syncKey]
);
if (upsertResult.rows[0]?.was_inserted) {
log(`New Fahrgenehmigung: ${f.standesbuchNr} — [${f.klasse}]${f.ausstellungsdatum ? ` (${f.ausstellungsdatum})` : ''}`);
neu++;
} else {
updated++;
}
}
return { neu, updated, skipped };
}