fix(sync): fix Untersuchungen column parsing and sync Leistungstest/Atemschutztauglichkeit dates to atemschutz profile
This commit is contained in:
@@ -324,6 +324,58 @@ export async function syncAT20ToAtemschutz(pool: Pool): Promise<void> {
|
||||
log(`AT20-Sync: ${result.rowCount ?? 0} atemschutz_traeger rows upserted`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates atemschutz_traeger from the untersuchungen table:
|
||||
* 1) Latest "Atemschutz Leistungstest" with bestanden → leistungstest_datum / leistungstest_bestanden
|
||||
* 2) Latest "Atemschutztauglichkeit" → untersuchung_datum / untersuchung_ergebnis
|
||||
* Must run AFTER syncToDatabase() has committed.
|
||||
*/
|
||||
export async function syncUntersuchungenToAtemschutz(pool: Pool): Promise<void> {
|
||||
// --- Leistungstest (Finnentest) ---
|
||||
const ltResult = await pool.query(
|
||||
`INSERT INTO atemschutz_traeger (id, user_id, leistungstest_datum, leistungstest_gueltig_bis, leistungstest_bestanden)
|
||||
SELECT uuid_generate_v4(), u.user_id, u.max_datum, u.max_datum + INTERVAL '1 year', true
|
||||
FROM (
|
||||
SELECT user_id, MAX(datum) AS max_datum
|
||||
FROM untersuchungen
|
||||
WHERE TRIM(art) = 'Atemschutz Leistungstest'
|
||||
AND TRIM(ergebnis) ILIKE '%bestanden%'
|
||||
GROUP BY user_id
|
||||
) u
|
||||
ON CONFLICT (user_id) DO UPDATE
|
||||
SET leistungstest_datum = EXCLUDED.leistungstest_datum,
|
||||
leistungstest_gueltig_bis = EXCLUDED.leistungstest_gueltig_bis,
|
||||
leistungstest_bestanden = true,
|
||||
updated_at = NOW()`
|
||||
);
|
||||
log(`Untersuchungen→Atemschutz: ${ltResult.rowCount ?? 0} Leistungstest rows synced`);
|
||||
|
||||
// --- Atemschutztauglichkeit (G26 medical exam) ---
|
||||
// Map FDISK ergebnis text to the DB enum (tauglich / bedingt_tauglich / nicht_tauglich)
|
||||
const atResult = await pool.query(
|
||||
`INSERT INTO atemschutz_traeger (id, user_id, untersuchung_datum, untersuchung_ergebnis)
|
||||
SELECT uuid_generate_v4(), sub.user_id, sub.datum,
|
||||
CASE
|
||||
WHEN sub.ergebnis ILIKE '%nicht%tauglich%' THEN 'nicht_tauglich'
|
||||
WHEN sub.ergebnis ILIKE '%bedingt%tauglich%' THEN 'bedingt_tauglich'
|
||||
WHEN sub.ergebnis ILIKE '%tauglich%' THEN 'tauglich'
|
||||
ELSE NULL
|
||||
END
|
||||
FROM (
|
||||
SELECT DISTINCT ON (user_id) user_id, datum, ergebnis
|
||||
FROM untersuchungen
|
||||
WHERE TRIM(art) = 'Atemschutztauglichkeit'
|
||||
AND datum IS NOT NULL
|
||||
ORDER BY user_id, datum DESC
|
||||
) sub
|
||||
ON CONFLICT (user_id) DO UPDATE
|
||||
SET untersuchung_datum = EXCLUDED.untersuchung_datum,
|
||||
untersuchung_ergebnis = COALESCE(EXCLUDED.untersuchung_ergebnis, atemschutz_traeger.untersuchung_ergebnis),
|
||||
updated_at = NOW()`
|
||||
);
|
||||
log(`Untersuchungen→Atemschutz: ${atResult.rowCount ?? 0} Atemschutztauglichkeit rows synced`);
|
||||
}
|
||||
|
||||
async function syncAusbildungen(
|
||||
client: PoolClient,
|
||||
ausbildungen: FdiskAusbildung[],
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'dotenv/config';
|
||||
import * as http from 'http';
|
||||
import { Pool } from 'pg';
|
||||
import { scrapeAll } from './scraper';
|
||||
import { syncToDatabase, syncAT20ToAtemschutz } from './db';
|
||||
import { syncToDatabase, syncAT20ToAtemschutz, syncUntersuchungenToAtemschutz } from './db';
|
||||
|
||||
// In-memory log ring buffer — last 500 lines captured from all modules
|
||||
const LOG_BUFFER_MAX = 500;
|
||||
@@ -71,6 +71,7 @@ async function runSync(force = false): Promise<void> {
|
||||
await syncToDatabase(pool, members, ausbildungen, befoerderungen, untersuchungen, fahrgenehmigungen, force);
|
||||
log(`Sync complete — ${members.length} members, ${ausbildungen.length} Ausbildungen, ${befoerderungen.length} Beförderungen, ${untersuchungen.length} Untersuchungen, ${fahrgenehmigungen.length} Fahrgenehmigungen`);
|
||||
await syncAT20ToAtemschutz(pool);
|
||||
await syncUntersuchungenToAtemschutz(pool);
|
||||
} finally {
|
||||
syncRunning = false;
|
||||
await pool.end();
|
||||
|
||||
@@ -1087,14 +1087,12 @@ async function scrapeMemberUntersuchungen(
|
||||
|
||||
const results: FdiskUntersuchung[] = [];
|
||||
for (const row of dataRows) {
|
||||
const valueCols: string[] = [];
|
||||
for (let ci = dateColIdx + 1; ci < row.cells.length; ci++) {
|
||||
const v = cellText(row.cells[ci]);
|
||||
if (v !== null) valueCols.push(v);
|
||||
}
|
||||
const anmerkungen = valueCols[0] ?? null;
|
||||
const art = valueCols[1] ?? null;
|
||||
const ergebnis = valueCols[2] ?? null;
|
||||
// Fixed column offsets from the date column.
|
||||
// FDISK table layout: [date] sep [anmerkungen] sep [art] sep [ergebnis] sep [buttons]
|
||||
// Data columns are at +2, +4, +6 from dateColIdx (separator cols in between).
|
||||
const anmerkungen = cellText(row.cells[dateColIdx + 2]) ?? null;
|
||||
const art = cellText(row.cells[dateColIdx + 4]) ?? null;
|
||||
const ergebnis = cellText(row.cells[dateColIdx + 6]) ?? null;
|
||||
if (!art) continue;
|
||||
const datum = parseDate(row.cells[dateColIdx]);
|
||||
const syncKey = `${standesbuchNr}::${art}::${datum ?? ''}`;
|
||||
|
||||
Reference in New Issue
Block a user