import { AufnahmeClient, BenutzerClient, BildClient, ObjektClient, VerbrauchsausweisWohnenClient } from "#components/Ausweis/types.js"; import { endEnergieVerbrauchVerbrauchsausweis_2016 } from "#lib/Berechnungen/VerbrauchsausweisWohnen/VerbrauchsausweisWohnen_2016.js"; import { getEmpfehlungen } from "#lib/XML/getEmpfehlungen.js"; import { Enums } from "#lib/server/prisma.js"; import * as fs from "fs" import moment from "moment"; import { PDFDocument, PDFFont, PDFImage, PDFPage, StandardFonts } from "pdf-lib"; import { addCheckMark } from "./utils/checkbox.js"; import { addText } from "./utils/text.js"; import { addAnsichtsausweisLabel, addDatumGEG } from "./utils/helpers.js"; import { getS3File } from "#lib/s3.js"; /* -------------------------------- Pdf Tools ------------------------------- */ export async function pdfVerbrauchsausweisWohnen(ausweis: VerbrauchsausweisWohnenClient, aufnahme: AufnahmeClient, objekt: ObjektClient, bilder: BildClient[], user: BenutzerClient) { const VerbrauchsausweisWohnenGEG2024PDF = fs.readFileSync(new URL("../../../public/pdf/templates/GEG24_Wohngebaeude_ohne_pfeile.pdf", import.meta.url), "base64"); const pdf = await PDFDocument.load(VerbrauchsausweisWohnenGEG2024PDF) const pages = pdf.getPages() // const template = VerbrauchsausweisWohnen2016Template as Template; const berechnungen = await endEnergieVerbrauchVerbrauchsausweis_2016(ausweis, aufnahme, objekt); const empfehlungen = getEmpfehlungen(ausweis, aufnahme, objekt) const height = pages[0].getHeight() const font = await pdf.embedFont(StandardFonts.Helvetica) const bold = await pdf.embedFont(StandardFonts.HelveticaBold) pages[0].drawText(`ID: ${aufnahme.id || ""}`, { x: 211, y: height - 112.5, size: 10 }) pages[0].drawText(aufnahme.gebaeudetyp || "", { x: 211, y: height - 166, size: 10 }) pages[0].drawText(objekt.adresse || "", { x: 211, y: height - 194, size: 10 }) pages[0].drawText(aufnahme.gebaeudeteil || "", { x: 211, y: height - 214.5, size: 10 }) pages[0].drawText(aufnahme.baujahr_gebaeude?.toString() || "", { x: 211, y: height - 229.5, size: 10 }) pages[0].drawText(aufnahme.baujahr_heizung?.toString() || "", { x: 211, y: height - 250, size: 10 }) pages[0].drawText(aufnahme.einheiten?.toString() || "", { x: 211, y: height - 271.5, size: 10 }) pages[0].drawText(aufnahme.nutzflaeche?.toString() || "", { x: 211, y: height - 285, size: 10 }) pages[0].drawText(`${ausweis.brennstoff_1}, ${ausweis.brennstoff_2 || ""}`, { x: 211, y: height - 298.5, size: 10 }) if (ausweis.warmwasser_enthalten) { pages[0].drawText(`${ausweis.brennstoff_1}, ${ausweis.brennstoff_2 || ""}`, { x: 211, y: height - 312, size: 10 }) } const erneuerbareEnergienVerwendung = [] if (ausweis.alternative_heizung) { erneuerbareEnergienVerwendung.push("Heizung") } if (ausweis.alternative_kuehlung) { erneuerbareEnergienVerwendung.push("Kühlung") } if (ausweis.alternative_lueftung) { erneuerbareEnergienVerwendung.push("Lüftung") } if (ausweis.alternative_warmwasser) { erneuerbareEnergienVerwendung.push("Warmwasser") } pages[0].drawText(erneuerbareEnergienVerwendung.join(", "), { x: 358, y: height - 338, size: 8 }) if (ausweis.warmwasser_enthalten) { pages[0].drawText(`${ausweis.brennstoff_1}, ${ausweis.brennstoff_2 || ""}`, { x: 211, y: height - 299, size: 10 }) } if (aufnahme.lueftung === Enums.Lueftungskonzept.Fensterlueftung) { addCheckMark(pages[0], 213, height - 347) } else if (aufnahme.lueftung === Enums.Lueftungskonzept.Schachtlueftung) { addCheckMark(pages[0], 213, height - 358) } else if (aufnahme.lueftung === Enums.Lueftungskonzept.LueftungsanlageMitWaermerueckgewinnung) { addCheckMark(pages[0], 355, height - 347) } else if (aufnahme.lueftung === Enums.Lueftungskonzept.LueftungsanlageOhneWaermerueckgewinnung) { addCheckMark(pages[0], 355, height - 358) } // Kühlung if (aufnahme.kuehlung) { addCheckMark(pages[0], 213, height - 375.5) } else { addCheckMark(pages[0], 355, height - 386.5) } if (ausweis.ausstellgrund === Enums.Ausstellgrund.Neubau) { addCheckMark(pages[0], 213, height - 419) } else if (ausweis.ausstellgrund === Enums.Ausstellgrund.Vermietung || ausweis.ausstellgrund === Enums.Ausstellgrund.Verkauf) { addCheckMark(pages[0], 213, height - 430) } else if (ausweis.ausstellgrund === Enums.Ausstellgrund.Modernisierung) { addCheckMark(pages[0], 344.5, height - 419) } else if (ausweis.ausstellgrund === Enums.Ausstellgrund.Sonstiges) { addCheckMark(pages[0], 463, height - 419) } const bild = bilder && bilder.find(image => image.kategorie === Enums.BilderKategorie.Gebaeude); if (bild) { const file = await getS3File("ibc-images", `${bild.id}.jpg`); if (file) { let image: PDFImage; image = await pdf.embedJpg(file) pages[0].drawImage(image, { x: 460.5, y: height - 289, width: 111, height: 138 }) } } // Nach 82 aus Wohnfläche ermittelt if (aufnahme.flaeche == 0) { addCheckMark(pages[0], 274, height - 277) } // Checkmark Angabe energetische Qualität des Gebäudes. addCheckMark(pages[0], 43, height - 560) // Datenerhebung durch Eigentümer addCheckMark(pages[0], 298, height - 590) // Ausstellungsdatum pages[0].drawText(moment().format("DD.MM.YYYY"), { font, size: 10, x: 508, y: height - 771 }) // Gültig bis pages[0].drawText(moment().add(10, "years").format("DD.MM.YYYY"), { font: bold, size: 10, x: 90, y: height - 113 }) // Stempel und Unterschrift if (ausweis.ausgestellt) { const stempel = await pdf.embedPng(fs.readFileSync(new URL("../../../public/pdf/images/stempel-unterschrift.png", import.meta.url), "base64")); const stempelHeight = 60 pages[0].drawImage(stempel, { x: 450, y: height - 770, height: stempelHeight, width: stempel.width / (stempel.height / stempelHeight) }) } // Aussteller const aussteller = await pdf.embedPng(fs.readFileSync(new URL("../../../public/pdf/images/aussteller.png", import.meta.url), "base64")); pages[0].drawImage(aussteller, { x: 40, y: height - 770, width: 100, height: 50 }) /* -------------------------------- Seite 2 -------------------------------- */ const addEnergieverbrauchSkalaPfeile = async (page: PDFPage) => { const pfeilNachUnten = await pdf.embedPng(fs.readFileSync(new URL("../../../public/pdf/images/pfeil-nach-unten.png", import.meta.url), "base64")) const pfeilNachOben = await pdf.embedPng(fs.readFileSync(new URL("../../../public/pdf/images/pfeil-nach-oben.png", import.meta.url), "base64")) // Wir müssen den berechneten Wert zwischen 0 und 250 als Wert zwischen 0 und 1 festlegen const endenergieverbrauchTranslationPercentage = Math.min(250, Math.max(0, berechnungen?.endEnergieVerbrauchGesamt || 0)) / 250 const primaerenergieverbrauchTranslationPercentage = Math.min(250, Math.max(0, berechnungen?.primaerEnergieVerbrauchGesamt || 0)) / 250 const minTranslation = 68 const maxTranslation = 504 const endenergieverbrauchTranslationX = minTranslation + (maxTranslation - minTranslation) * endenergieverbrauchTranslationPercentage; const primaerenergieverbrauchTranslationX = minTranslation + (maxTranslation - minTranslation) * primaerenergieverbrauchTranslationPercentage; const pfeilWidth = 20 const margin = 5; // Buchstaben hinzufügen und hervorheben const klassen: [string, number][] = [ ["A+", 94], ["A", 136.5], ["B", 175], ["C", 215], ["D", 264], ["E", 320], ["F", 382], ["G", 447], ["H", 495], ] for (const klasse of klassen) { addText(page, klasse[0], klasse[1], height - 227, 12, berechnungen?.energieEffizienzKlasse === klasse[0] ? bold : font) } page.drawImage(pfeilNachUnten, { x: endenergieverbrauchTranslationX, y: height - 212, width: pfeilWidth, height: 30 }) const endEnergieVerbrauchGesamtText = `${berechnungen?.endEnergieVerbrauchGesamt.toString()}kWh/(m²a)`; const primaerEnergieVerbrauchGesamtText = `${berechnungen?.primaerEnergieVerbrauchGesamt.toString()}kWh/(m²a)`; if (endenergieverbrauchTranslationPercentage > 0.5) { page.drawText("Endenergieverbrauch", { x: endenergieverbrauchTranslationX - margin - font.widthOfTextAtSize("Endenergieverbrauch", 10), y: height - 193, size: 10 }) page.drawText(endEnergieVerbrauchGesamtText, { x: endenergieverbrauchTranslationX - margin - bold.widthOfTextAtSize(endEnergieVerbrauchGesamtText, 10), y: height - 207, size: 10, font: bold }) } else { page.drawText("Endenergieverbrauch", { x: endenergieverbrauchTranslationX + pfeilWidth + margin, y: height - 193, size: 10 }) page.drawText(endEnergieVerbrauchGesamtText, { x: endenergieverbrauchTranslationX + pfeilWidth + margin, y: height - 207, size: 10, font: bold }) } page.drawImage(pfeilNachOben, { x: primaerenergieverbrauchTranslationX, y: height - 297, width: pfeilWidth, height: 30 }) if (endenergieverbrauchTranslationPercentage > 0.5) { page.drawText("Primärenergieverbrauch", { x: primaerenergieverbrauchTranslationX - margin - font.widthOfTextAtSize("Primärenergieverbrauch", 10), y: height - 280, size: 10 }) page.drawText(primaerEnergieVerbrauchGesamtText, { x: primaerenergieverbrauchTranslationX - margin - bold.widthOfTextAtSize(primaerEnergieVerbrauchGesamtText, 10), y: height - 294, size: 10, font: bold }) } else { page.drawText("Primärenergieverbrauch", { x: primaerenergieverbrauchTranslationX + pfeilWidth + margin, y: height - 280, size: 10 }) page.drawText(primaerEnergieVerbrauchGesamtText, { x: primaerenergieverbrauchTranslationX + pfeilWidth + margin, y: height - 294, size: 10, font: bold }) } } addEnergieverbrauchSkalaPfeile(pages[2]) // CO2 Emissionen pages[2].drawText(berechnungen?.co2EmissionenGesamt.toString() || "", { x: 392, y: height - 166.5, font, size: 10 }) // Endenergieverbrauch pages[2].drawText(berechnungen?.endEnergieVerbrauchGesamt.toString() || "", { x: 455, y: height - 326, font, size: 10 }) /* -------------------------------- Seite 3 -------------------------------- */ // Verbräuche const addVerbrauchGenerator = () => { let i = 0; let yOffset = 14.6; const initialHeight = 435 const initialXOffset = 36; return (zeitraum_von?: string, zeitraum_bis?: string, energietraeger?: string, primaerfaktor?: string, energieverbrauch?: string, anteil_warmwasser?: string, anteil_heizung?: string, klimafaktor?: string) => { pages[2].drawText(zeitraum_von || "", { x: initialXOffset, y: initialHeight - (i * yOffset), size: 8, font }) pages[2].drawText(zeitraum_bis || "", { x: initialXOffset + 47, y: initialHeight - (i * yOffset), size: 8, font }) pages[2].drawText(energietraeger || "", { x: initialXOffset + 94, y: initialHeight - (i * yOffset), size: 8, font }) pages[2].drawText(primaerfaktor || "", { x: initialXOffset + 317, y: initialHeight - (i * yOffset), size: 8, font }) pages[2].drawText(energieverbrauch || "", { x: initialXOffset + 351, y: initialHeight - (i * yOffset), size: 8, font }) pages[2].drawText(anteil_warmwasser || "", { x: initialXOffset + 402, y: initialHeight - (i * yOffset), size: 8, font }) pages[2].drawText(anteil_heizung || "", { x: initialXOffset + 453, y: initialHeight - (i * yOffset), size: 8, font }) pages[2].drawText(klimafaktor || "", { x: initialXOffset + 504, y: initialHeight - (i * yOffset), size: 8, font }) i++; } } const addVerbrauch = addVerbrauchGenerator(); if (!ausweis.warmwasser_enthalten) { // Mit Warmwasserzuschlag addVerbrauch( moment(ausweis.startdatum).format("MM.YYYY"), moment(ausweis.startdatum).add(3, "years").format("MM.YYYY"), ausweis.brennstoff_1 || "", berechnungen?.brennstoff_1.primaerenergiefaktor.toString(), Math.round(berechnungen?.energieVerbrauchGesamt_1 || 0).toString(), "0", Math.round(berechnungen?.energieVerbrauchHeizung_1 || 0).toString(), berechnungen?.durchschnittsKlimafaktor.toString() ); } else { // Ohne Warmwasserzuschlag addVerbrauch( moment(ausweis.startdatum).format("MM.YYYY"), moment(ausweis.startdatum).add(3, "years").format("MM.YYYY"), ausweis.brennstoff_1 || "", berechnungen?.brennstoff_1.primaerenergiefaktor.toString(), Math.round(berechnungen?.energieVerbrauchGesamt_1 || 0).toString(), Math.round(berechnungen?.energieVerbrauchWarmwasser_1 || 0).toString(), Math.round(berechnungen?.energieVerbrauchHeizung_1 || 0).toString(), berechnungen?.durchschnittsKlimafaktor.toString() ); } if (ausweis.zusaetzliche_heizquelle) { addVerbrauch( moment(ausweis.startdatum).format("MM.YYYY"), moment(ausweis.startdatum).add(3, "years").format("MM.YYYY"), ausweis.brennstoff_2 || "", berechnungen?.brennstoff_2.primaerenergiefaktor.toString(), Math.round(berechnungen?.energieVerbrauchGesamt_2 || 0).toString(), Math.round(berechnungen?.energieVerbrauchWarmwasser_2 || 0).toString(), Math.round(berechnungen?.energieVerbrauchHeizung_2 || 0).toString(), berechnungen?.durchschnittsKlimafaktor.toString() ); } if (!ausweis.warmwasser_enthalten) { /** * Dezentrale Warmwasserversorgung - Pauschale Erhöhung um 20kWh/m² * @link https://www.bundesanzeiger.de/pub/publication/MRYM4nI84Sdlr0EIvvW?2 */ addVerbrauch( moment(ausweis.startdatum).format("MM.YYYY"), moment(ausweis.startdatum).add(3, "years").format("MM.YYYY"), "Warmwasserzuschlag", berechnungen?.primaerfaktorww.toString(), Math.round(berechnungen?.energieVerbrauchWarmwasser_1 || 0).toString(), Math.round(berechnungen?.energieVerbrauchWarmwasser_1 || 0).toString(), "0", "0" ); } if (aufnahme.leerstand && aufnahme.leerstand > 0) { /** * Leerstandszuschlag * @link https://www.bundesanzeiger.de/pub/publication/MRYM4nI84Sdlr0EIvvW?2 */ if (ausweis.warmwasser_enthalten && ausweis.warmwasser_anteil_bekannt) { addVerbrauch( moment(ausweis.startdatum).format("MM.YYYY"), moment(ausweis.startdatum).add(3, "years").format("MM.YYYY"), "Leerstandszuschlag", berechnungen?.brennstoff_1.primaerenergiefaktor.toString(), Math.round((berechnungen?.leerstandsZuschlagHeizung || 0) + (berechnungen?.leerstandsZuschlagWarmwasser || 0)).toString(), Math.round((berechnungen?.leerstandsZuschlagWarmwasser || 0)).toString(), Math.round((berechnungen?.leerstandsZuschlagHeizung || 0)).toString(), berechnungen?.durchschnittsKlimafaktor.toString() ); } else { addVerbrauch( moment(ausweis.startdatum).format("MM.YYYY"), moment(ausweis.startdatum).add(3, "years").format("MM.YYYY"), "Leerstandszuschlag", berechnungen?.brennstoff_1.primaerenergiefaktor.toString(), Math.round((berechnungen?.leerstandsZuschlagHeizung || 0) + (berechnungen?.leerstandsZuschlagWarmwasser || 0)).toString(), "0", "0", berechnungen?.durchschnittsKlimafaktor.toString() ); } } if (aufnahme.kuehlung) { /** * Kühlungszuschlag - Pauschale Erhöhung um 6kWh/m² * Primärenergiefaktor Strom * @link https://www.bundesanzeiger.de/pub/publication/MRYM4nI84Sdlr0EIvvW?2 */ addVerbrauch( moment(ausweis.startdatum).format("MM.YYYY"), moment(ausweis.startdatum).add(3, "years").format("MM.YYYY"), "Kühlungszuschlag", berechnungen?.primaerfaktorww.toString(), Math.round(berechnungen?.kuehlungsZuschlag || 0).toString(), "0", "0", "" ); } /* -------------------------------- Seite 4 -------------------------------- */ const splitToSize = (text: string, size: number, font: PDFFont, fontSize: number) => { const lines = [] let currentLine = "" for (const char of text) { if (font.widthOfTextAtSize(currentLine + char, fontSize) <= size) { currentLine += char; } else { lines.push(currentLine) currentLine = char; } } lines.push(currentLine) return lines.join("\n") } const addEmpfehlungenGenerator = () => { let i = 0; let yOffset = 43; const initialHeight = 568 const initialXOffset = 36; return (bauteil?: string, beschreibung?: string, alsEinzelmassnahme?: boolean, amortisationszeit?: string, kosten?: string) => { pages[3].drawText((i + 1).toString(), { x: initialXOffset, y: initialHeight - (i * yOffset), size: 8, font }) pages[3].drawText(splitToSize(bauteil || "", 70, font, 8), { x: initialXOffset + 25, y: initialHeight - (i * yOffset), size: 8, font, lineHeight: 10 }) pages[3].drawText(splitToSize(beschreibung || "", 230, font, 8), { x: initialXOffset + 98, y: initialHeight - (i * yOffset), size: 8, font, lineHeight: 10 }) pages[3].drawText(amortisationszeit || "", { x: initialXOffset + 403, y: initialHeight - (i * yOffset), size: 8, font }) pages[3].drawText(kosten || "", { x: initialXOffset + 451, y: initialHeight - (i * yOffset), size: 8, font }) i++; } } const addEmpfehlung = addEmpfehlungenGenerator() for (const empfehlung of empfehlungen) { addEmpfehlung(empfehlung.anlagenteil, empfehlung.description, true, empfehlung.amortisationszeit, empfehlung.kosten) } for (const page of pages) { addAnsichtsausweisLabel(page, font) addDatumGEG(page, font) } return pdf.save(); }