All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.svws_nrw.data.datenaustausch.DataUntis Maven / Gradle / Ivy

Go to download

Diese Bibliothek unterstützt bei dem Zugriff auf Datenbanken für die Schulverwaltungssoftware in NRW

There is a newer version: 1.0.1
Show newest version
package de.svws_nrw.data.datenaustausch;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException;

import de.svws_nrw.base.LogUtils;
import de.svws_nrw.base.untis.UntisGPU001;
import de.svws_nrw.base.untis.UntisGPU002;
import de.svws_nrw.base.untis.UntisGPU005;
import de.svws_nrw.base.untis.UntisGPU010;
import de.svws_nrw.base.untis.UntisGPU015;
import de.svws_nrw.base.untis.UntisGPU019;
import de.svws_nrw.core.adt.LongArrayKey;
import de.svws_nrw.core.adt.map.HashMap2D;
import de.svws_nrw.core.data.RGBFarbe;
import de.svws_nrw.core.data.SimpleOperationResponse;
import de.svws_nrw.core.data.fach.FachDaten;
import de.svws_nrw.core.data.gost.GostBlockungKurs;
import de.svws_nrw.core.data.gost.GostBlockungsergebnisKurs;
import de.svws_nrw.core.data.gost.GostBlockungsergebnisSchiene;
import de.svws_nrw.core.data.kurse.KursDaten;
import de.svws_nrw.core.data.lehrer.LehrerListeEintrag;
import de.svws_nrw.core.data.schueler.Schueler;
import de.svws_nrw.core.data.schule.Schuljahresabschnitt;
import de.svws_nrw.core.data.stundenplan.StundenplanListeEintragMinimal;
import de.svws_nrw.core.data.stundenplan.StundenplanSchiene;
import de.svws_nrw.core.data.stundenplan.StundenplanZeitraster;
import de.svws_nrw.core.logger.LogConsumerList;
import de.svws_nrw.core.logger.Logger;
import de.svws_nrw.core.types.Geschlecht;
import de.svws_nrw.core.types.fach.ZulaessigesFach;
import de.svws_nrw.core.utils.DateUtils;
import de.svws_nrw.core.utils.gost.GostBlockungsdatenManager;
import de.svws_nrw.core.utils.gost.GostBlockungsergebnisManager;
import de.svws_nrw.data.SimpleBinaryMultipartBody;
import de.svws_nrw.data.faecher.DataFaecherliste;
import de.svws_nrw.data.gost.DBUtilsGost;
import de.svws_nrw.data.gost.DataGostBlockungsergebnisse;
import de.svws_nrw.data.kataloge.DataKatalogRaeume;
import de.svws_nrw.data.kataloge.DataKatalogZeitraster;
import de.svws_nrw.data.kurse.DataKursliste;
import de.svws_nrw.data.lehrer.DataLehrerliste;
import de.svws_nrw.data.schule.DataSchuljahresabschnitte;
import de.svws_nrw.data.stundenplan.DataStundenplanRaeume;
import de.svws_nrw.data.stundenplan.DataStundenplanSchienen;
import de.svws_nrw.data.stundenplan.DataStundenplanZeitraster;
import de.svws_nrw.db.DBEntityManager;
import de.svws_nrw.db.dto.current.schild.katalog.DTOKatalogRaum;
import de.svws_nrw.db.dto.current.schild.klassen.DTOKlassen;
import de.svws_nrw.db.dto.current.schild.schueler.DTOSchueler;
import de.svws_nrw.db.dto.current.schild.schule.DTOEigeneSchule;
import de.svws_nrw.db.dto.current.schild.stundenplan.DTOStundenplan;
import de.svws_nrw.db.dto.current.schild.stundenplan.DTOStundenplanUnterricht;
import de.svws_nrw.db.dto.current.schild.stundenplan.DTOStundenplanUnterrichtKlasse;
import de.svws_nrw.db.dto.current.schild.stundenplan.DTOStundenplanUnterrichtLehrer;
import de.svws_nrw.db.dto.current.schild.stundenplan.DTOStundenplanUnterrichtRaum;
import de.svws_nrw.db.dto.current.schild.stundenplan.DTOStundenplanUnterrichtSchiene;
import de.svws_nrw.db.utils.ApiOperationException;
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;

/**
 * Diese Klasse stellt Methoden für den Import und Export von Untis-Daten
 * zur Vefügung.
 */
public final class DataUntis {

	private DataUntis() {
		throw new IllegalStateException("Instantiation of " + DataUntis.class.getName() + " not allowed");
	}

	private static void _importGPU001(final Logger logger, final DBEntityManager conn, final long idSchuljahresabschnitt,
			final String beginn, final String beschreibung, final List unterrichte, final int wochentyp, final boolean ignoreMissing)
			throws ApiOperationException {
		// Prüfe die ID des Schuljahreabschnitts
		logger.logLn("-> Prüfe, ob der Schuljahresabschnitt existiert... ");
		final Schuljahresabschnitt schuljahresabschnitt;
		try {
			schuljahresabschnitt = new DataSchuljahresabschnitte(conn).getByID(idSchuljahresabschnitt);
		} catch (final ApiOperationException e) {
			logger.logLn(2, "[Fehler] - Die ID des Schuljahresabschnitt %d ist ungültig.".formatted(idSchuljahresabschnitt));
			throw e;
		}
		// Bestimme die Lehrer
		final Map mapLehrerByKuerzel = DataLehrerliste.getLehrerListe(conn).stream()
				.collect(Collectors.toMap(l -> l.kuerzel, l -> l));
		// Bestimme die Fächer
		final Map mapFaecherByKuerzel = DataFaecherliste.getFaecherListe(conn).stream().collect(Collectors.toMap(f -> f.kuerzel, f -> f));
		// Bestimme die Klassen des Schuljahresabschnitts
		final List klassen = conn.queryList(DTOKlassen.QUERY_BY_SCHULJAHRESABSCHNITTS_ID, DTOKlassen.class, schuljahresabschnitt.id);
		final Map mapKlassenByKuerzel = klassen.stream().collect(Collectors.toMap(k -> k.Klasse, k -> k));
		// Bestimme die Kurse des Schuljahresabschnitts
		final List kurse = DataKursliste.getKursListenFuerAbschnitt(conn, idSchuljahresabschnitt, false);
		final HashMap2D mapKurseByKuerzelUndJahrgang = new HashMap2D<>();
		for (final KursDaten kurs : kurse)
			for (final long idJahrgang : kurs.idJahrgaenge)
				mapKurseByKuerzelUndJahrgang.put(kurs.kuerzel, idJahrgang, kurs);
		// Prüfe den Beginn des Stundenplan - ist dieser evtl. nach dem Schuljahr des angegebenen Schuljahresabschnitts?
		final int schuljahr = DateUtils.getSchuljahrFromDateISO8601(beginn);
		if (schuljahr > schuljahresabschnitt.schuljahr) {
			logger.logLn(2, "[Fehler] - Das angegebene Startdatum %s liegt nach dem Schuljahr %d des angegebenen Schuljahresabschnitts"
					.formatted(beginn, schuljahresabschnitt.schuljahr) + " und ist damit unzulässig.");
			throw new ApiOperationException(Status.CONFLICT, "Das angegebene Startdatum %s liegt nach dem Schuljahr %d des angegebenen Schuljahresabschnitts"
					.formatted(beginn, schuljahresabschnitt.schuljahr) + " und ist damit unzulässig.");
		}
		if (schuljahr < schuljahresabschnitt.schuljahr) {
			logger.logLn(2, "[Fehler] - Das angegebene Startdatum %s liegt vor dem Schuljahr %d des angegebenen Schuljahresabschnitts und ist damit unzulässig."
					.formatted(beginn, schuljahresabschnitt.schuljahr));
			throw new ApiOperationException(Status.CONFLICT, "Das angegebene Startdatum %s liegt vor dem Schuljahr %d des angegebenen Schuljahresabschnitts"
					.formatted(beginn, schuljahresabschnitt.schuljahr) + " und ist damit unzulässig.");
		}
		// Erstelle den neuen Stundenplan
		final long idStundenplan = conn.transactionGetNextID(DTOStundenplan.class);
		final DTOStundenplan dtoStundenplan = new DTOStundenplan(idStundenplan, idSchuljahresabschnitt, beginn, beschreibung, wochentyp);
		dtoStundenplan.Ende = "%04d-%02d-%02d".formatted(schuljahresabschnitt.schuljahr + 1, 7, 31);
		conn.transactionPersist(dtoStundenplan);
		conn.transactionFlush();
		// Übertrage den Katalog der Räume
		DataStundenplanRaeume.addRaeume(conn, dtoStundenplan, DataKatalogRaeume.getRaeume(conn));
		// Übertrage den Katalog mit dem Zeitraster
		DataStundenplanZeitraster.addZeitraster(conn, dtoStundenplan, DataKatalogZeitraster.getZeitraster(conn));
		// Erzeuge die Einträge für die Kurs-Schienen
		DataStundenplanSchienen.addSchienenFromKursliste(conn, idStundenplan, kurse);
		final List schienen = DataStundenplanSchienen.getSchienen(conn, idStundenplan);
		final HashMap2D mapSchienen = new HashMap2D<>();
		for (final StundenplanSchiene schiene : schienen)
			mapSchienen.put(schiene.idJahrgang, schiene.nummer, schiene);
		// Erzeuge die nötigen Einträge für den Stundenplan
		long next_uid = conn.transactionGetNextID(DTOStundenplanUnterricht.class);
		long next_lid = conn.transactionGetNextID(DTOStundenplanUnterrichtLehrer.class);
		long next_kid = conn.transactionGetNextID(DTOStundenplanUnterrichtKlasse.class);
		long next_rid = conn.transactionGetNextID(DTOStundenplanUnterrichtRaum.class);
		long next_sid = conn.transactionGetNextID(DTOStundenplanUnterrichtSchiene.class);
		final Set setKursUnterricht = new HashSet<>();
		int maxWochentyp = 0;
		for (final UntisGPU001 u : unterrichte) {
			logger.logLn("-> Importiere Unterricht: " + u.toString());
			// Bestimme den Zeitraster-Eintrag des neuen Stundenplans
			final StundenplanZeitraster zeitraster = DataStundenplanZeitraster.getOrCreateZeitrasterEintrag(conn, idStundenplan, u.wochentag, u.stunde);
			// Bestimme die Klasse des Unterrichtes
			final DTOKlassen klasse = mapKlassenByKuerzel.get(u.klasseKuerzel);
			if (klasse == null) {
				logger.logLn(2, "[Fehler] - Die Klasse mit dem Kürzel %s konnte nicht in der Datenbank gefunden werden.".formatted(u.klasseKuerzel));
				if (ignoreMissing) {
					logger.logLn(2, "Der Unterrichts-Eintrag wird ignoriert.");
					continue;
				}
				throw new ApiOperationException(Status.NOT_FOUND, "Die Klasse mit dem Kürzel %s konnte nicht in der Datenbank gefunden werden."
						.formatted(u.klasseKuerzel));
			}
			// Bestimme den Fachlehrer
			final LehrerListeEintrag lehrer = mapLehrerByKuerzel.get(u.lehrerKuerzel);
			if ((u.lehrerKuerzel != null) && (lehrer == null)) {
				logger.logLn(2, "[Fehler] - Der Lehrer mit dem Kürzel %s konnte nicht in der Datenbank gefunden werden.".formatted(u.lehrerKuerzel));
				if (ignoreMissing) {
					logger.logLn(2, "Der Unterrichts-Eintrag wird ignoriert.");
					continue;
				}
				throw new ApiOperationException(Status.NOT_FOUND,
						"Der Lehrer mit dem Kürzel %s konnte nicht in der Datenbank gefunden werden.".formatted(u.lehrerKuerzel));
			}
			// Prüfe, ob es sich um Kursunterricht handelt
			final KursDaten kurs = mapKurseByKuerzelUndJahrgang.getOrNull(u.fachKuerzel, klasse.Jahrgang_ID);
			if (kurs == null) {
				// Bestimme das Fach
				final FachDaten fach = mapFaecherByKuerzel.get(u.fachKuerzel);
				if (fach == null) {
					logger.logLn(2, "[Fehler] - Das Fach bzw. der Kurs mit dem Kürzel %s konnte nicht in der Datenbank gefunden werden."
							.formatted(u.fachKuerzel));
					if (ignoreMissing) {
						logger.logLn(2, "Der Unterrichts-Eintrag wird ignoriert.");
						continue;
					}
					throw new ApiOperationException(Status.NOT_FOUND, "Das Fach bzw. der Kurs mit dem Kürzel %s konnte nicht in der Datenbank gefunden werden."
							.formatted(u.fachKuerzel));
				}
				// Erstelle den Klassen-Unterricht ...
				final long uid = next_uid++;
				int wt = 0;
				if ((u.wochentyp != null) && !u.wochentyp.isBlank()) {
					try {
						wt = Integer.valueOf(u.wochentyp);
						if ((wt < 0) || (wt > 4))
							wt = 0;
					} catch (@SuppressWarnings("unused") final NumberFormatException nfe) {
						wt = 0;
					}
				}
				maxWochentyp = (maxWochentyp < wt) ? wt : maxWochentyp;
				final DTOStundenplanUnterricht dtoUnterricht = new DTOStundenplanUnterricht(uid, zeitraster.id, wt, fach.id);
				dtoUnterricht.Kurs_ID = null;
				conn.transactionPersist(dtoUnterricht);
				conn.transactionFlush();
				// ... Lehrer ...
				if (lehrer != null)
					conn.transactionPersist(new DTOStundenplanUnterrichtLehrer(next_lid++, uid, lehrer.id));
				// ... Klasse ...
				conn.transactionPersist(new DTOStundenplanUnterrichtKlasse(next_kid++, uid, klasse.ID));
				// ... Raum
				if (u.raumKuerzel != null)
					conn.transactionPersist(new DTOStundenplanUnterrichtRaum(next_rid++, uid,
							DataStundenplanRaeume.getOrCreateRaum(conn, idStundenplan, u.raumKuerzel).id));
			} else {
				// Prüfe, ob der Kursunterricht schon mit einem früheren Datensatz bearbeitet wurde
				int wt = 0;
				if ((u.wochentyp != null) && !u.wochentyp.isBlank()) {
					try {
						wt = Integer.valueOf(u.wochentyp);
						if ((wt < 0) || (wt > 4))
							wt = 0;
					} catch (@SuppressWarnings("unused") final NumberFormatException nfe) {
						wt = 0;
					}
				}
				final long[] key = { kurs.id, u.idUnterricht, u.wochentag, u.stunde, wt };
				if (!setKursUnterricht.add(new LongArrayKey(key))) {
					logger.logLn(2, "Unterricht mit der ID %d wurde für den Kurs '%s' mit der ID %d bereits für den Wochentag %d und der Stunde %d mit Wochentyp %d hinzugefügt."
							.formatted(u.idUnterricht, kurs.kuerzel, kurs.id, u.wochentag, u.stunde, wt) + " Überspringe diesen Eintrag...");
					continue;
				}
				// Erstelle den Kurs-Unterricht ...
				final long uid = next_uid++;
				maxWochentyp = (maxWochentyp < wt) ? wt : maxWochentyp;
				final DTOStundenplanUnterricht dtoUnterricht = new DTOStundenplanUnterricht(uid, zeitraster.id, wt, kurs.idFach);
				dtoUnterricht.Kurs_ID = kurs.id;
				conn.transactionPersist(dtoUnterricht);
				conn.transactionFlush();
				// ... Lehrer ...
				if (lehrer != null)
					conn.transactionPersist(new DTOStundenplanUnterrichtLehrer(next_lid++, uid, lehrer.id));
				// ... Schiene ...
				for (final long idJahrgang : kurs.idJahrgaenge) {
					for (final int schiene : kurs.schienen) {
						final StundenplanSchiene s = mapSchienen.getOrNull(idJahrgang, schiene);
						if (s == null)
							throw new ApiOperationException(Status.INTERNAL_SERVER_ERROR,
									"Interner Fehler beim Anlegen der Schienen für den Kursunterricht des Stundenplans.");
						conn.transactionPersist(new DTOStundenplanUnterrichtSchiene(next_sid++, uid, s.id));
					}
				}
				// ... Raum
				if (u.raumKuerzel != null)
					conn.transactionPersist(new DTOStundenplanUnterrichtRaum(next_rid++, uid,
							DataStundenplanRaeume.getOrCreateRaum(conn, idStundenplan, u.raumKuerzel).id));
			}
			conn.transactionFlush();
		}
		// Passe ggf. das Wochentyp-Modell an, falls Unterrichte mit einem Wochentyp eingelesen wurden.
		if (maxWochentyp != 0) {
			logger.logLn("-> Setze das Wochentyp-Modell auf %d.".formatted(maxWochentyp));
			dtoStundenplan.WochentypModell = maxWochentyp;
			conn.transactionPersist(dtoStundenplan);
			conn.transactionFlush();
			// TODO Fasse alle erzeugten Unterrichte als WT 0 zusammen, die bis auf den Wochentyp identisch sind und alle Wochentypen abdecken.
		}
	}


	/**
	 * Importiert die in dem Multipart übergebene Datei.
	 *
	 * @param conn        die Datenbank-Verbindung
	 * @param multipart   der Multipart-Body mmit der Datei
	 * @param ignoreMissing   wenn true, dann werden fehlende Klassen und Kurse ignoriert
	 *                        und protokolliert, es wird aber kein Fehler erzeugt.
	 *
	 * @return die HTTP-Response mit dem Log
	 */
	public static Response importGPU001(final DBEntityManager conn, final UntisGPU001MultipartBody multipart, final boolean ignoreMissing) {
		final Logger logger = new Logger();
		logger.copyConsumer(Logger.global());
		final LogConsumerList log = new LogConsumerList();
		logger.addConsumer(log);
		final SimpleOperationResponse daten = new SimpleOperationResponse();
		try {
			// Erstelle aus dem Multipart den String mit dem Inhalt der Textdatei
			logger.logLn("Lese die Datensätze aus der Text-Datei ein.");
			final String strGPU001 = new String(multipart.data, StandardCharsets.UTF_8);
			final List unterrichte = UntisGPU001.readCSV(strGPU001);
			final StundenplanListeEintragMinimal entry = new ObjectMapper().readValue(multipart.entry, StundenplanListeEintragMinimal.class);
			logger.logLn("Importiere den Stundenplan:");
			_importGPU001(logger, conn, entry.idSchuljahresabschnitt, entry.gueltigAb, entry.bezeichnung, unterrichte, 0, ignoreMissing);
			logger.logLn("Import beendet");
		} catch (@SuppressWarnings("unused") final IOException e) {
			logger.logLn(2, "Fehler beim Einlesen der Datensätze.");
			daten.success = false;
			daten.log = log.getStrings();
			return Response.status(Status.CONFLICT).type(MediaType.APPLICATION_JSON).entity(daten).build();
		} catch (final ApiOperationException e) {
			logger.logLn(2, "[FEHLER] beim Import");
			daten.success = false;
			daten.log = log.getStrings();
			return Response.status(e.getStatus()).type(MediaType.APPLICATION_JSON).entity(daten).build();
		}
		daten.success = true;
		daten.log = log.getStrings();
		return Response.status(Status.OK).type(MediaType.APPLICATION_JSON).entity(daten).build();
	}


	/**
	 * Importiert die in dem Multipart übergebene Datei.
	 *
	 * @param conn        die Datenbank-Verbindung
	 * @param multipart   der Multipart-Body mmit der Datei
	 *
	 * @return die HTTP-Response mit dem Log
	 */
	public static Response importGPU005(final DBEntityManager conn, final SimpleBinaryMultipartBody multipart) {
		final Logger logger = new Logger();
		logger.copyConsumer(Logger.global());
		final LogConsumerList log = new LogConsumerList();
		logger.addConsumer(log);

		boolean success = true;
		Status statusCode = Status.OK;
		try {
			final String csv = new String(multipart.data, StandardCharsets.UTF_8);
			logger.logLn("Importiere die Räume aus der Untis-Datei GPU005.txt:");
			importUntisRaeume(conn, logger, csv);
			logger.logLn("  Import beendet");
		} catch (final Exception e) {
			success = false;
			if (e instanceof final ApiOperationException aoe) {
				statusCode = aoe.getStatus();
			} else {
				logger.logLn("  [FEHLER] Unerwarteter Fehler: " + e.getLocalizedMessage());
				statusCode = Status.INTERNAL_SERVER_ERROR;
			}
		}
		final SimpleOperationResponse daten = new SimpleOperationResponse();
		daten.success = success;
		daten.log = log.getStrings();
		return Response.status(statusCode).type(MediaType.APPLICATION_JSON).entity(daten).build();
	}


	/**
	 * Importiert Räume aus der Untis-Datei GPU005.txt in das Datenbank-Schema, welches durch die übergebene
	 * Verbindung festgelegt ist.
	 *
	 * @param conn     die Datenbank-Verbindung.
	 * @param logger   der Logger für Rückmeldungen zum Import-Prozess
	 * @param csv      die CSV-Datei mit den Räumen (GPU005.txt)
	 *
	 * @return true im Erfolgsfall und false im Fehlerfall
	 *
	 * @throws ApiOperationException   im Fehlerfall
	 */
	public static boolean importUntisRaeume(final DBEntityManager conn, final Logger logger, final String csv) throws ApiOperationException {
		// Lese zunächst die Informationen zur Schule aus der SVWS-Datenbank und überprüfe die Schulform
		logger.logLn("-> Lese Informationen zu der Schule ein...");
		logger.modifyIndent(2);
		final DTOEigeneSchule schule = conn.querySingle(DTOEigeneSchule.class);
		if (schule == null) {
			logger.logLn("[Fehler] - Konnte die Informationen zur Schule nicht aus der Datenbank lesen");
			throw new ApiOperationException(Status.NOT_FOUND, "Konnte die Informationen zur Schule nicht aus der Datenbank lesen.");
		}
		logger.logLn("[OK]");
		logger.modifyIndent(-2);
		try {
			logger.logLn("-> Lese die Räume aus der CSV-Datei ein...");
			logger.modifyIndent(2);
			final List raeume = UntisGPU005.readCSV(csv);
			logger.logLn("[OK]");
			logger.modifyIndent(-2);

			conn.transactionBegin();

			logger.logLn("-> Lese die bereits im Katalog vorhandenen Räume ein...");
			logger.modifyIndent(2);
			final Map raeumeVorhanden = conn.queryAll(DTOKatalogRaum.class).stream().collect(Collectors.toMap(r -> r.Kuerzel, r -> r));
			logger.logLn("[OK]");
			logger.modifyIndent(-2);

			logger.logLn("-> Schreibe die Räume...");
			logger.modifyIndent(2);
			long id = conn.transactionGetNextID(DTOKatalogRaum.class);
			for (final UntisGPU005 raum : raeume) {
				if (raum.kuerzel == null) {
					logger.logLn("[Fehler] - Konnte die Informationen zur Schule nicht aus der Datenbank lesen");
					logger.modifyIndent(-2);
					throw new ApiOperationException(Status.BAD_REQUEST, "Jeder Raum muss ein gültiges Kürzel haben.");
				}
				if (raeumeVorhanden.containsKey(raum.kuerzel)) {
					logger.logLn("Raum '%s' wird nicht übernommen, da er bereits vorhanden ist.".formatted(raum.kuerzel));
					continue;
				}
				final DTOKatalogRaum dto = new DTOKatalogRaum(id++, raum.kuerzel, (raum.bezeichnung == null) ? raum.kuerzel : raum.bezeichnung,
						(raum.groesse == null) ? 40 : raum.groesse);
				conn.transactionPersist(dto);
				logger.logLn("Raum '%s' hinzugefügt.".formatted(raum.kuerzel));
			}

			if (!conn.transactionCommit()) {
				logger.logLn("[Fehler] Unerwarteter Fehler beim Schreiben in die Datenbank.");
				logger.modifyIndent(-2);
				throw new ApiOperationException(Status.INTERNAL_SERVER_ERROR);
			}
			logger.logLn("[OK]");
			logger.modifyIndent(-2);
			return true;
		} catch (final UnrecognizedPropertyException upe) {
			LogUtils.logStacktrace(logger, upe);
			logger.logLn("[Fehler] Konnte die Datei GPU005.txt nicht einlesen. Prüfen sie ggf. auch die Zeichenkodierung der Datei."
					+ " Diese muss UTF-8 (ohne BOM) sein.");
			logger.modifyIndent(-2);
			return false;
		} catch (@SuppressWarnings("unused") final Exception exception) {
			logger.logLn("[Fehler]");
			logger.modifyIndent(-2);
			return false;
		} finally {
			conn.transactionRollback();
		}
	}

	/**
	 * Erzeugt den Export eines Blockungsergebnisses für Untis, indem die Dateien GPU002.txt, GPU010.txt, GPU015.txt und GPU019.txt
	 * erzeugt werden und in einem Zip-File in der Response zurückgegeben werden.
	 *
	 * @param conn                  die Datenbank-Verbindung
	 * @param logger                der Logger
	 * @param idBlockungsergebnis   die ID des Blockungsergebnisses
	 * @param idUnterrichtStart     die erste ID für die Unterricht-IDs, welche in dem Untis-Export verwendet wird
	 *
	 * @return eine Response mit dem Zip-File
	 *
	 * @throws ApiOperationException im Fehlerfall
	 */
	public static Response exportUntisBlockungsergebnis(final DBEntityManager conn, final Logger logger, final long idBlockungsergebnis,
			final long idUnterrichtStart) throws ApiOperationException {
		DBUtilsGost.pruefeSchuleMitGOSt(conn);
		logger.logLn("-> Prüfe, ob das Blockungsergebnis gültig ist und die erste ID für den Unterricht > 0 ist...");
		logger.modifyIndent(2);
		logger.log("Prüfe die ID für den Unterricht auf Gültigkeit... ");
		if (idUnterrichtStart <= 0) {
			logger.logLn(0, "[Fehler]");
			logger.logLn("Die ID %d ist kleiner oder gleich 0".formatted(idUnterrichtStart));
			logger.modifyIndent(-2);
			throw new ApiOperationException(Status.BAD_REQUEST, "Die ID %d für die erster Unterricht ID ist kleiner oder gleich 0.");
		}
		logger.logLn("[OK]");

		logger.log("Lade das Ergebnis aus der Datenbank... ");
		final GostBlockungsergebnisManager ergebnisManager;
		final GostBlockungsdatenManager datenManager;
		try {
			ergebnisManager = DataGostBlockungsergebnisse.getErgebnismanagerFromID(conn, idBlockungsergebnis);
			datenManager = ergebnisManager.getParent();
		} catch (final Exception e) {
			logger.logLn(0, "[Fehler]");
			if (e instanceof final ApiOperationException aoe) {
				logger.logLn(aoe.getMessage());
				logger.modifyIndent(-2);
				throw aoe;
			}
			logger.modifyIndent(-2);
			throw new ApiOperationException(Status.INTERNAL_SERVER_ERROR, e, "Unerwarteter Fehler beim Einlesen der Blockung");
		}
		logger.logLn("[OK]");

		logger.log("Lade die Informationen zu den Schülern der Blockung aus der Datenbank... ");
		final List schueler = datenManager.schuelerGetListe();
		final List idsSchueler = schueler.stream().map(s -> s.id).toList();
		if (idsSchueler.isEmpty()) {
			logger.logLn("Keine Schüler in der Blockung vorhanden. Der Export wird abgebrochen.");
			logger.modifyIndent(-2);
			throw new ApiOperationException(Status.NOT_FOUND, "Keine Schüler in der Blockung vorhanden.");
		}
		final List dtosSchueler = conn.queryByKeyList(DTOSchueler.class, idsSchueler);
		if (dtosSchueler.size() != idsSchueler.size()) {
			logger.logLn("Es konnten nicht alle Schüler der Blockung in der Datenbank gefunden werden. Der Export wird abgebrochen.");
			logger.modifyIndent(-2);
			throw new ApiOperationException(Status.INTERNAL_SERVER_ERROR, "Es konnten nicht alle Schüler der Blockung in der Datenbank gefunden werden.");
		}
		final Map mapSchueler = dtosSchueler.stream().collect(Collectors.toMap(s -> s.ID, s -> s));
		logger.modifyIndent(-2);

		logger.logLn("-> Erstelle Liste der Unterrichte für GPU002.txt");
		logger.modifyIndent(2);
		final String csvUnterrichte;
		final @NotNull Map<@NotNull Long, @NotNull Long> mapKursZuUnterricht = new HashMap<>();
		try {
			final @NotNull List<@NotNull GostBlockungKurs> kurse = datenManager.kursGetListeSortiertNachKursartFachNummer();
			long idUnterricht = idUnterrichtStart;
			final List unterrichte = new ArrayList<>();
			for (final @NotNull GostBlockungKurs kurs : kurse) {
				final UntisGPU002 u = new UntisGPU002();
				u.idUnterricht = idUnterricht++;
				mapKursZuUnterricht.put(kurs.id, u.idUnterricht);
				u.wochenstunden = kurs.wochenstunden;
				u.wochenstundenKlasse = kurs.wochenstunden;
				u.wochenstundenLehrer = kurs.wochenstunden;
				u.klasseKuerzel = datenManager.getHalbjahr().jahrgang;
				u.lehrerKuerzel = kurs.lehrer.isEmpty() ? null : kurs.lehrer.get(0).kuerzel;
				u.fachKuerzel = datenManager.kursGetName(kurs.id);
				u.studentenZahl = ergebnisManager.getOfKursAnzahlSchueler(kurs.id);
				u.wochenTyp = "WA";
				u.jahreswert = 0.0048;
				final ZulaessigesFach fach = ZulaessigesFach.getByKuerzelASD(ergebnisManager.getFach(kurs.fach_id).kuerzel);
				final RGBFarbe fachFarbe = (fach == null) ? new RGBFarbe(255, 255, 255) : fach.getFarbe();
				u.farbeHintergrund = "" + ((fachFarbe.red * 65536) + (fachFarbe.green * 256) + fachFarbe.blue);
				u.kennzeichen = "n";
				u.doppelStdMin = 1;
				u.doppelStdMax = 1;
				u.studentenMaennlich = 0;
				u.studentenWeiblich = 0;
				for (final Schueler s : ergebnisManager.getOfKursSchuelermenge(kurs.id)) {
					switch (Geschlecht.fromValue(s.geschlecht)) {
						case Geschlecht.M -> u.studentenMaennlich++;
						case Geschlecht.W -> u.studentenWeiblich++;
						default -> {
							/* nicht zu tun */
						}
					}
				}
				u.eigenwert = "100000";
				u.eigenwertHunderttausendstel = "1";
				unterrichte.add(u);
			}
			csvUnterrichte = UntisGPU002.writeCSV(unterrichte);
		} catch (final Exception e) {
			logger.logLn("Fehler beim Erstellen der Datei GPU002.txt.");
			logger.modifyIndent(-2);
			throw new ApiOperationException(Status.INTERNAL_SERVER_ERROR, e, "Fehler beim Erstellen der Datei GPU002.txt.");
		}
		logger.logLn("OK");
		logger.modifyIndent(-2);

		logger.logLn("-> Erstelle Liste der Schüler für GPU010.txt");
		final String csvSchueler;
		logger.modifyIndent(2);
		try {
			final List gpu010 = new ArrayList<>();
			for (final Schueler s : schueler) {
				final DTOSchueler dtoSchueler = mapSchueler.get(s.id);
				final LocalDate date =
						((dtoSchueler.Geburtsdatum == null) || "".equals(dtoSchueler.Geburtsdatum)) ? null : LocalDate.parse(dtoSchueler.Geburtsdatum);
				final UntisGPU010 dto = new UntisGPU010();
				dto.geburtsdatum = (date == null) ? null : "%04d%02d%02d".formatted(date.getYear(), date.getMonthValue(), date.getDayOfMonth());
				dto.name = ((s.nachname == null) || ("".equals(s.nachname.trim())) ? "???" : s.nachname.trim().replace(" ", ""))
						+ "_" + (((s.vorname == null) || "".equals(s.vorname.trim())) ? "???" : s.vorname.trim().replace(" ", "").substring(0, 3))
						+ "_" + ((dto.geburtsdatum == null) ? "????????" : dto.geburtsdatum);
				dto.langname = ((s.nachname == null) || ("".equals(s.nachname.trim()))) ? "???" : s.nachname.trim();
				dto.vorname = ((s.vorname == null) || "".equals(s.vorname.trim())) ? "???" : s.vorname.trim();
				dto.klasse = datenManager.getHalbjahr().jahrgang;
				dto.geschlecht = switch (Geschlecht.fromValue(s.geschlecht)) {
					case Geschlecht.M -> "2";
					case Geschlecht.W -> "1";
					default -> null;
				};
				dto.emailAdresse = dtoSchueler.Email;
				dto.fremdschluessel = "" + s.id;
				gpu010.add(dto);
			}
			csvSchueler = UntisGPU010.writeCSV(gpu010);
		} catch (final Exception e) {
			logger.logLn("Fehler beim Erstellen der Datei GPU010.txt.");
			logger.modifyIndent(-2);
			throw new ApiOperationException(Status.INTERNAL_SERVER_ERROR, e, "Fehler beim Erstellen der Datei GPU002.txt.");
		}
		logger.logLn("OK");
		logger.modifyIndent(-2);

		logger.logLn("-> Erstelle Liste der Kurswahlen für GPU015.txt");
		final String csvKurswahlen;
		logger.modifyIndent(2);
		try {
			final List gpu015 = new ArrayList<>();
			for (final Schueler s : schueler) {
				final DTOSchueler dtoSchueler = mapSchueler.get(s.id);
				final LocalDate date =
						((dtoSchueler.Geburtsdatum == null) || "".equals(dtoSchueler.Geburtsdatum)) ? null : LocalDate.parse(dtoSchueler.Geburtsdatum);
				final String gebDatum = (date == null) ? null : "%04d%02d%02d".formatted(date.getYear(), date.getMonthValue(), date.getDayOfMonth());
				final String schuelerName = ((s.nachname == null) || ("".equals(s.nachname.trim())) ? "???" : s.nachname.trim().replace(" ", ""))
						+ "_" + (((s.vorname == null) || "".equals(s.vorname.trim())) ? "???" : s.vorname.trim().replace(" ", "").substring(0, 3))
						+ "_" + ((gebDatum == null) ? "????????" : gebDatum);

				for (final GostBlockungsergebnisKurs k : ergebnisManager.getOfSchuelerKursmenge(s.id)) {
					final UntisGPU015 dto = new UntisGPU015();
					dto.name = schuelerName;
					dto.idUnterricht = mapKursZuUnterricht.get(k.id);
					dto.fach = datenManager.kursGetName(k.id);
					dto.klasse = datenManager.getHalbjahr().jahrgang;
					dto.statistikKennzeichen = datenManager.schuelerGetOfFachFachwahl(s.id, k.fachID).istSchriftlich ? "S" : "M";
					dto.idsUnterrichteAlternativkurse = "";
					dto.kuerzelAlternativkurse = "";
					dto.prioAlternativkurse = "";
					String tilde = "";
					for (final GostBlockungKurs ak : datenManager.kursGetListeByFachUndKursart(k.fachID, k.kursart)) {
						if ("".equals(tilde))
							tilde = "~";
						final Long idUnterrichtAlternativ = mapKursZuUnterricht.get(ak.id);
						dto.idsUnterrichteAlternativkurse += tilde + idUnterrichtAlternativ;
						dto.kuerzelAlternativkurse += tilde + datenManager.kursGetName(ak.id);
						dto.prioAlternativkurse += tilde + "1";
					}
					gpu015.add(dto);
				}
			}
			csvKurswahlen = UntisGPU015.writeCSV(gpu015);
		} catch (final Exception e) {
			logger.logLn("Fehler beim Erstellen der Datei GPU015.txt.");
			logger.modifyIndent(-2);
			throw new ApiOperationException(Status.INTERNAL_SERVER_ERROR, e, "Fehler beim Erstellen der Datei GPU002.txt.");
		}
		logger.logLn("OK");
		logger.modifyIndent(-2);

		logger.logLn("-> Erstelle Liste der Schienen für GPU019.txt für die Kurs-Schienen-Zuordnung");
		final String csvSchienen;
		logger.modifyIndent(2);
		try {
			final @NotNull List<@NotNull GostBlockungKurs> kurse = datenManager.kursGetListeSortiertNachKursartFachNummer();
			final List gpu019 = new ArrayList<>();
			for (final @NotNull GostBlockungKurs kurs : kurse) {
				final UntisGPU019 dto = new UntisGPU019();
				// TODO evtl. Multi-Schienen-Kurse unterstützen
				final Set schienen = ergebnisManager.getOfKursSchienenmenge(kurs.id);
				final GostBlockungsergebnisSchiene schiene = schienen.iterator().next();
				dto.name = datenManager.schieneGet(schiene.id).bezeichnung;
				dto.art = "2";
				dto.anzahlWochenstunden = kurs.wochenstunden;
				dto.idUnterricht = mapKursZuUnterricht.get(kurs.id);
				dto.fach = datenManager.kursGetName(kurs.id);
				dto.klassen = datenManager.getHalbjahr().jahrgang;
				gpu019.add(dto);
			}
			csvSchienen = UntisGPU019.writeCSV(gpu019);
		} catch (final Exception e) {
			logger.logLn("Fehler beim Erstellen der Datei GPU019.txt.");
			logger.modifyIndent(-2);
			throw new ApiOperationException(Status.INTERNAL_SERVER_ERROR, e, "Fehler beim Erstellen der Datei GPU002.txt.");
		}
		logger.logLn("OK");
		logger.modifyIndent(-2);

		logger.logLn("-> Erzeuge den Zip-File mit den vier GPU-Dateien...");
		logger.modifyIndent(2);
		final String zipname = "BlockungExportUntis.zip";
		final byte[] zipdata;
		try {
			try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
				try (ZipOutputStream zos = new ZipOutputStream(baos)) {
					zos.putNextEntry(new ZipEntry("GPU002.txt"));
					zos.write(csvUnterrichte.getBytes(StandardCharsets.UTF_8));
					zos.closeEntry();
					baos.flush();
					zos.putNextEntry(new ZipEntry("GPU010.txt"));
					zos.write(csvSchueler.getBytes(StandardCharsets.UTF_8));
					zos.closeEntry();
					baos.flush();
					zos.putNextEntry(new ZipEntry("GPU015.txt"));
					zos.write(csvKurswahlen.getBytes(StandardCharsets.UTF_8));
					zos.closeEntry();
					baos.flush();
					zos.putNextEntry(new ZipEntry("GPU019.txt"));
					zos.write(csvSchienen.getBytes(StandardCharsets.UTF_8));
					zos.closeEntry();
					baos.flush();
				}
				zipdata = baos.toByteArray();
			}
		} catch (final IOException e) {
			logger.logLn("Fehler beim Erstellen der Zip-Datei.");
			logger.modifyIndent(-2);
			throw new ApiOperationException(Status.INTERNAL_SERVER_ERROR, e, "Fehler beim Erstellen der Zip-Datei");
		}
		logger.logLn("Zip-Datei wurde erstellt.");
		logger.modifyIndent(-2);
		return Response.ok(zipdata).header("Content-Disposition", "attachment; filename=\"" + zipname + "\"").build();
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy