import { AufnahmeClient, BenutzerClient, BildClient, ObjektClient, VerbrauchsausweisGewerbeClient } from "#components/Ausweis/types.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, addRegistriernummer } from "./utils/helpers.js"; import { getS3File } from "#lib/s3.js"; import { endEnergieVerbrauchVerbrauchsausweisGewerbe_2016_Server } from "#lib/Berechnungen/VerbrauchsausweisGewerbe/VerbrauchsausweisGewerbe_2016_Server.js"; export async function pdfVerbrauchsausweisGewerbe(ausweis: VerbrauchsausweisGewerbeClient, aufnahme: AufnahmeClient, objekt: ObjektClient, bilder: BildClient[], user: BenutzerClient, vorschau = true) { const VerbrauchsausweisWohnenGEG2024PDF = fs.readFileSync(new URL("../../../public/pdf/templates/GEG24_Nichtwohngebaeude.pdf", import.meta.url), "base64"); const pdf = await PDFDocument.load(VerbrauchsausweisWohnenGEG2024PDF) const pages = pdf.getPages() // const template = VerbrauchsausweisWohnen2016Template as Template; const berechnungen = await endEnergieVerbrauchVerbrauchsausweisGewerbe_2016_Server(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) const form = pdf.getForm() form.updateFieldAppearances(font) pages[0].drawText(`ID: ${ausweis.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.nutzflaeche?.toString() || "", { x: 211, y: height - 271.5, size: 10 }) pages[0].drawText(`${ausweis.brennstoff_1}, ${ausweis.brennstoff_2 || ""}`, { x: 211, y: height - 285, 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 - 325, 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 - 334) } else if (aufnahme.lueftung === Enums.Lueftungskonzept.Schachtlueftung) { addCheckMark(pages[0], 213, height - 345) } else if (aufnahme.lueftung === Enums.Lueftungskonzept.LueftungsanlageMitWaermerueckgewinnung) { addCheckMark(pages[0], 355, height - 334) } else if (aufnahme.lueftung === Enums.Lueftungskonzept.LueftungsanlageOhneWaermerueckgewinnung) { addCheckMark(pages[0], 355, height - 345) } // Kühlung if (aufnahme.kuehlung) { addCheckMark(pages[0], 213, height - 362.5) } else { addCheckMark(pages[0], 355, height - 373.5) } if (ausweis.ausstellgrund === Enums.Ausstellgrund.Neubau) { addCheckMark(pages[0], 213, height - 406) } else if (ausweis.ausstellgrund === Enums.Ausstellgrund.Vermietung) { addCheckMark(pages[0], 213, height - 417) } else if (ausweis.ausstellgrund === Enums.Ausstellgrund.Modernisierung) { addCheckMark(pages[0], 344.5, height - 406) } else if (ausweis.ausstellgrund === Enums.Ausstellgrund.Sonstiges) { addCheckMark(pages[0], 463, height - 417) } // Aushangpflicht // addCheckMark(pages[0], 463, height - 406) 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 }) } } // Checkmark Angabe energetische Qualität des Gebäudes. addCheckMark(pages[0], 40, height - 550) // Datenerhebung durch Eigentümer addCheckMark(pages[0], 295, height - 580) // Ausstellungsdatum pages[0].drawText(moment().format("DD.MM.YYYY"), { font, size: 10, x: 508, y: height - 752 }) // 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 || !vorschau) { 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 - 750, 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 - 750, width: 100, height: 50 }) // /* -------------------------------- Seite 2 -------------------------------- */ // const co2Emissionen = fillFormField("co2emissionen", berechnungen?.co2EmissionenGesamt.toString(), 8, TextAlignment.Center) const addEnergieverbrauchSkalaPfeile = async (page: PDFPage) => { const pfeilNachUnten = await pdf.embedPng(fs.readFileSync(new URL("../../../public/images/pfeil-nach-unten.png", import.meta.url), "base64")) const pfeilNachOben = await pdf.embedPng(fs.readFileSync(new URL("../../../public/images/pfeil-nach-oben.png", import.meta.url), "base64")) // Wir müssen den berechneten Wert zwischen 0 und 1000 als Wert zwischen 0 und 1 festlegen const endenergieverbrauchTranslationPercentage = Math.min(1000, Math.max(0, berechnungen?.endEnergieVerbrauchGesamt || 0)) / berechnungen?.vergleichsWertWaerme * 2 const stromVerbrauchTranslationPercentage = Math.min(1000, Math.max(0, berechnungen?.endEnergieVerbrauchStrom || 0)) / berechnungen?.vergleichsWertStrom * 2 const vergleichsWertWaermeTranslationPercentage = Math.min(1000, Math.max(0, berechnungen?.vergleichsWertWaerme || 0)) / berechnungen?.vergleichsWertWaerme * 2 const vergleichsWertStromTranslationPercentage = Math.min(1000, Math.max(0, berechnungen?.vergleichsWertStrom || 0)) / berechnungen?.vergleichsWertStrom * 2 const minTranslation = 78 const maxTranslation = 512 const endenergieverbrauchTranslationX = minTranslation + (maxTranslation - minTranslation) * endenergieverbrauchTranslationPercentage; const stromVerbrauchTranslationX = minTranslation + (maxTranslation - minTranslation) * stromVerbrauchTranslationPercentage; const vergleichsWertWaermeTranslationX = minTranslation + (maxTranslation - minTranslation) * vergleichsWertWaermeTranslationPercentage; const vergleichsWertStromTranslationX = minTranslation + (maxTranslation - minTranslation) * vergleichsWertStromTranslationPercentage; const pfeilWidth = 20 const margin = 5; page.drawImage(pfeilNachUnten, { x: endenergieverbrauchTranslationX, y: height - 210, width: pfeilWidth, height: 30 }) page.drawImage(pfeilNachOben, { x: vergleichsWertWaermeTranslationX, y: height - 293, width: pfeilWidth, height: 30 }) const MaxvergleichsWertWaerme = berechnungen?.vergleichsWertWaerme * 2; const MaxvergleichsWertWaermeText = `> ${MaxvergleichsWertWaerme.toString()}`; const MaxvergleichswertStrom = berechnungen?.vergleichsWertStrom * 2; const MaxvergleichswertStromText = `> ${MaxvergleichswertStrom.toString()}`; const endEnergieVerbrauchGesamtText = `${berechnungen?.endEnergieVerbrauchGesamt.toString()}kWh/(m²a)`; const stromVerbrauchGesamtText = `${berechnungen?.endEnergieVerbrauchStrom.toString()}kWh/(m²a)`; const vergleichswertWaermeText = `${berechnungen?.vergleichsWertWaerme.toString()}kWh/(m²a)` const vergleichswertStromText = `${berechnungen?.vergleichsWertStrom.toString()}kWh/(m²a)` page.drawText("0", { x: 0, y: height - 233, size: 10 }) page.drawText(MaxvergleichsWertWaermeText, { x: vergleichsWertWaermeTranslationX * 2, y: height - 233, size: 10 }) page.drawText("0", { x: 0, y: height - 377, size: 10 }) page.drawText(MaxvergleichswertStromText, { x: vergleichsWertStromTranslationX * 2, y: height - 2377, size: 10 }) if (endenergieverbrauchTranslationPercentage > 0.5) { page.drawText("Endenergieverbrauch Wärme", { x: endenergieverbrauchTranslationX - margin - font.widthOfTextAtSize("Endenergieverbrauch", 10), y: height - 191, size: 10 }) page.drawText(endEnergieVerbrauchGesamtText, { x: endenergieverbrauchTranslationX - margin - bold.widthOfTextAtSize(endEnergieVerbrauchGesamtText, 10), y: height - 205, size: 10, font: bold }) } else { page.drawText("Endenergieverbrauch Wärme", { x: endenergieverbrauchTranslationX + pfeilWidth + margin, y: height - 191, size: 10 }) page.drawText(endEnergieVerbrauchGesamtText, { x: endenergieverbrauchTranslationX + pfeilWidth + margin, y: height - 205, size: 10, font: bold }) } if (vergleichsWertWaermeTranslationPercentage > 0.5) { page.drawText("Vergleichswert Wärme", { x: vergleichsWertWaermeTranslationX - margin - font.widthOfTextAtSize("Vergleichswert", 10), y: height - 275, size: 10 }) page.drawText(vergleichswertWaermeText, { x: vergleichsWertWaermeTranslationX - margin - bold.widthOfTextAtSize(vergleichswertWaermeText, 10), y: height - 289, size: 10, font: bold }) } else { page.drawText("Vergleichswert Wärme", { x: vergleichsWertWaermeTranslationX + pfeilWidth + margin, y: height - 275, size: 10 }) page.drawText(vergleichswertWaermeText, { x: vergleichsWertWaermeTranslationX + pfeilWidth + margin, y: height - 289, size: 10, font: bold }) } page.drawImage(pfeilNachUnten, { x: stromVerbrauchTranslationX, y: height - 354, width: pfeilWidth, height: 30 }) page.drawImage(pfeilNachOben, { x: vergleichsWertStromTranslationX, y: height - 437, width: pfeilWidth, height: 30 }) if (endenergieverbrauchTranslationPercentage > 0.5) { page.drawText("Endenergieverbrauch Strom", { x: stromVerbrauchTranslationX - margin - font.widthOfTextAtSize("Primärenergieverbrauch", 10), y: height - 335, size: 10 }) page.drawText(stromVerbrauchGesamtText, { x: stromVerbrauchTranslationX - margin - bold.widthOfTextAtSize(stromVerbrauchGesamtText, 10), y: height - 349, size: 10, font: bold }) } else { page.drawText("Endenergieverbrauch Strom", { x: stromVerbrauchTranslationX + pfeilWidth + margin, y: height - 335, size: 10 }) page.drawText(stromVerbrauchGesamtText, { x: stromVerbrauchTranslationX + pfeilWidth + margin, y: height - 349, size: 10, font: bold }) } if (vergleichsWertWaermeTranslationPercentage > 0.5) { page.drawText("Vergleichswert Strom", { x: vergleichsWertStromTranslationX - margin - font.widthOfTextAtSize("Vergleichswert", 10), y: height - 420, size: 10 }) page.drawText(vergleichswertStromText, { x: vergleichsWertStromTranslationX - margin - bold.widthOfTextAtSize(vergleichswertStromText, 10), y: height - 434, size: 10, font: bold }) } else { page.drawText("Vergleichswert Strom", { x: vergleichsWertStromTranslationX + pfeilWidth + margin, y: height - 420, size: 10 }) page.drawText(vergleichswertStromText, { x: vergleichsWertStromTranslationX + pfeilWidth + margin, y: height - 434, size: 10, font: bold }) } } addEnergieverbrauchSkalaPfeile(pages[2]) if (ausweis.warmwasser_enthalten) { addCheckMark(pages[2], 41, height - 293) } if (ausweis.kuehlung_enthalten) { addCheckMark(pages[2], 41, height - 305) } if (ausweis.stromverbrauch_enthaelt_heizung) { addCheckMark(pages[2], 41, height - 456) } if (ausweis.stromverbrauch_enthaelt_warmwasser) { addCheckMark(pages[2], 131, height - 456) } if (ausweis.stromverbrauch_enthaelt_lueftung) { addCheckMark(pages[2], 218, height - 456) } if (ausweis.stromverbrauch_enthaelt_beleuchtung) { addCheckMark(pages[2], 281, height - 456) } if (ausweis.stromverbrauch_enthaelt_kuehlung) { addCheckMark(pages[2], 422, height - 456) } if (ausweis.stromverbrauch_enthaelt_sonstige) { addCheckMark(pages[2], 492, height - 456) } addText(pages[2], berechnungen?.primaerEnergieVerbrauchGesamt.toString() || "", 475, height - 637, 10, font) addText(pages[2], berechnungen?.co2EmissionenGesamt.toString() || "", 475, height - 656, 10, font) // const primaerenergiebedarfIst = fillFormField("primaerenergiebedarf_ist", berechnungen?.primaerEnergieVerbrauchGesamt.toString()) /* -------------------------------- Seite 3 -------------------------------- */ // Verbräuche const addVerbrauchGenerator = () => { let i = 0; let yOffset = 14.6; const initialHeight = 297 const initialXOffset = 36; return (zeitraum_von?: string, zeitraum_bis?: string, energietraeger?: string, primaerfaktor?: string, energieverbrauch?: string, anteil_warmwasser?: string, anteil_kaelte?: number, anteil_heizung?: string, klimafaktor?: string, strom?: number) => { 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 + 232, y: initialHeight - (i * yOffset), size: 8, font }) pages[2].drawText(energieverbrauch || "", { x: initialXOffset + 275, y: initialHeight - (i * yOffset), size: 8, font }) pages[2].drawText(anteil_warmwasser || "", { x: initialXOffset + 325, y: initialHeight - (i * yOffset), size: 8, font }) pages[2].drawText(anteil_kaelte?.toString() || "", { x: initialXOffset + 378, y: initialHeight - (i * yOffset), size: 8, font }) pages[2].drawText(anteil_heizung || "", { x: initialXOffset + 430, y: initialHeight - (i * yOffset), size: 8, font }) pages[2].drawText(klimafaktor || "", { x: initialXOffset + 464, y: initialHeight - (i * yOffset), size: 8, font }) pages[2].drawText(strom?.toString() || "", { 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", berechnungen?.endEnergieVerbrauchKuehlungsZuschlag_1, Math.round(berechnungen?.energieVerbrauchHeizung_1 || 0).toString(), berechnungen?.durchschnittsKlimafaktor.toString(), berechnungen?.energieVerbrauchStrom ); } 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(), berechnungen?.endEnergieVerbrauchKuehlungsZuschlag_1, Math.round(berechnungen?.energieVerbrauchHeizung_1 || 0).toString(), berechnungen?.durchschnittsKlimafaktor.toString(), berechnungen?.energieVerbrauchStrom ); } 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(), berechnungen?.endEnergieVerbrauchKuehlungsZuschlag_2, Math.round(berechnungen?.energieVerbrauchHeizung_2 || 0).toString(), berechnungen?.durchschnittsKlimafaktor.toString(), 0 ); } // TODO 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?.brennstoff_1.primaerenergiefaktor.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)), 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", "", // TODO // berechnungen?.primaerfaktorww.toString(), Math.round(berechnungen?.kuehlungsZuschlag_1 || 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 = 562 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 (let i = 0; i < pages.length; i++) { const page = pages[i]; if (vorschau) { addAnsichtsausweisLabel(page, font) } addDatumGEG(page, font) if (i !== pages.length - 1) { addRegistriernummer(page, font, ausweis.registriernummer || "") } } // pdf.getForm().flatten() return pdf.save(); }