feat(admin): add system logs viewer, tabbed data management, fix AT20 sync
This commit is contained in:
@@ -289,6 +289,41 @@ export async function syncToDatabase(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans the ausbildung table for AT20 courses with erfolgscode = 'mit Erfolg'
|
||||
* and upserts atemschutz_traeger records accordingly.
|
||||
* Must run AFTER syncToDatabase() has committed — i.e. on the same pool, outside the transaction.
|
||||
*/
|
||||
export async function syncAT20ToAtemschutz(pool: Pool): Promise<void> {
|
||||
// First, log a sample of what's actually stored so we can verify the filter strings match.
|
||||
const sample = await pool.query<{ kurs_kurzbezeichnung: string | null; erfolgscode: string | null; count: string }>(
|
||||
`SELECT kurs_kurzbezeichnung, erfolgscode, COUNT(*)::text AS count
|
||||
FROM ausbildung
|
||||
WHERE kurs_kurzbezeichnung IS NOT NULL
|
||||
GROUP BY kurs_kurzbezeichnung, erfolgscode
|
||||
ORDER BY count DESC
|
||||
LIMIT 20`
|
||||
);
|
||||
log(`AT20-Sync: kurs_kurzbezeichnung/erfolgscode distribution (top 20):`);
|
||||
for (const row of sample.rows) {
|
||||
log(` kurzbezeichnung=${JSON.stringify(row.kurs_kurzbezeichnung)} erfolgscode=${JSON.stringify(row.erfolgscode)} count=${row.count}`);
|
||||
}
|
||||
|
||||
const result = await pool.query<{ rowCount: number }>(
|
||||
`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 TRIM(a.kurs_kurzbezeichnung) = 'AT20'
|
||||
AND TRIM(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()`
|
||||
);
|
||||
log(`AT20-Sync: ${result.rowCount ?? 0} atemschutz_traeger rows upserted`);
|
||||
}
|
||||
|
||||
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 } from './db';
|
||||
import { syncToDatabase, syncAT20ToAtemschutz } from './db';
|
||||
|
||||
// In-memory log ring buffer — last 500 lines captured from all modules
|
||||
const LOG_BUFFER_MAX = 500;
|
||||
@@ -70,6 +70,7 @@ async function runSync(force = false): Promise<void> {
|
||||
const { members, ausbildungen, befoerderungen, untersuchungen, fahrgenehmigungen } = await scrapeAll(username, password);
|
||||
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);
|
||||
} finally {
|
||||
syncRunning = false;
|
||||
await pool.end();
|
||||
|
||||
Reference in New Issue
Block a user