Files
online-energieausweis/src/lib/pdf/pdfVerbrauchsausweisWohnen.ts
2025-02-23 22:58:17 +11:00

565 lines
17 KiB
TypeScript

import { AufnahmeClient, BenutzerClient, BildClient, ObjektClient, UploadedGebaeudeBild, 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, RotationTypes, StandardFonts, TextAlignment } from "pdf-lib";
import { addCheckMark } from "./utils/checkbox.js";
import { addText } from "./utils/text.js";
import { addAnsichtsausweisLabel, addDatumGEG } from "./utils/helpers.js";
import { fileURLToPath } from "url";
import { PERSISTENT_DIR } from "#lib/server/constants.js";
/* -------------------------------- Pdf Tools ------------------------------- */
export async function pdfVerbrauchsausweisWohnen(ausweis: VerbrauchsausweisWohnenClient, aufnahme: AufnahmeClient, objekt: ObjektClient, bilder: BildClient[], user: BenutzerClient) {
const VerbrauchsausweisWohnenGEG2024PDF = fs.readFileSync(new URL("./templates/GEG24_Wohngebaeude_ohne_pfeile_form.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)
const form = pdf.getForm()
form.updateFieldAppearances(font)
const fillFormField = (name: string, value: string, fontSize: number = 8, alignment: TextAlignment = TextAlignment.Left) => {
const field = form.getTextField(name)
field.setFontSize(fontSize)
field.setText(value)
field.setAlignment(alignment)
}
const toggleCheck = (name: string, checked: boolean = true) => {
const field = form.getCheckBox(name)
if (checked) {
field.check()
} else {
field.uncheck()
}
}
const gebaeudetyp = fillFormField("gebaeudetyp", aufnahme.gebaeudetyp || "")
const adresse = fillFormField("adresse", objekt.adresse || "")
const gebaeudeteil = fillFormField("gebaeudeteil", aufnahme.gebaeudeteil || "")
const baujahr_gebaeude = fillFormField("baujahr_gebaeude", aufnahme.baujahr_gebaeude?.toString())
const baujahr_heizung = fillFormField("baujahr_heizung", aufnahme.baujahr_heizung?.toString())
const einheiten = fillFormField("einheiten", (aufnahme.einheiten || 1).toString())
const nutzflaeche = fillFormField("nutzflaeche", `${aufnahme.nutzflaeche?.toString()}`)
fillFormField("energietraeger_heizung", `${aufnahme.brennstoff_1}, ${aufnahme.brennstoff_2 || ""}`)
if (ausweis.warmwasser_enthalten) {
fillFormField("energietraeger_warmwasser", `${aufnahme.brennstoff_1}, ${aufnahme.brennstoff_2 || ""}`)
}
if (aufnahme.durchlauf_erhitzer && !ausweis.warmwasser_enthalten) {
fillFormField("energietraeger_warmwasser", "Strommix");
}
toggleCheck("fensterlueftung", aufnahme.lueftung == Enums.Lueftungskonzept.Fensterlueftung)
toggleCheck("schachtlueftung", aufnahme.lueftung == Enums.Lueftungskonzept.Schachtlueftung)
toggleCheck("lueftungsanlage_ohne_waermerueckgewinnung", aufnahme.lueftung == Enums.Lueftungskonzept.LueftungsanlageOhneWaermerueckgewinnung)
toggleCheck("lueftungsanlage_waermerueckgewinnung", aufnahme.lueftung == Enums.Lueftungskonzept.LueftungsanlageMitWaermerueckgewinnung)
toggleCheck("anlass_neubau", ausweis.ausstellgrund == "Neubau")
toggleCheck("anlass_vermietung", ausweis.ausstellgrund == "Vermietung" || ausweis.ausstellgrund == "Verkauf")
toggleCheck("anlass_modernisierung", ausweis.ausstellgrund == "Modernisierung")
toggleCheck("anlass_sonstiges", ausweis.ausstellgrund == "Sonstiges")
const bild = bilder && bilder.find(image => image.kategorie === Enums.BilderKategorie.Gebaeude);
if (bild) {
const path = `${PERSISTENT_DIR}/images/${bild.uid}.jpg`;
if (fs.existsSync(path)) {
const file = fs.readFileSync(fileURLToPath(new URL(path, import.meta.url)))
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("./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("./images/aussteller.png", import.meta.url), "base64"));
pages[0].drawImage(aussteller, {
x: 40,
y: height - 770,
width: 100,
height: 50
})
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 - 337,
size: 8
})
// Kühlung
if (aufnahme.kuehlung) {
addCheckMark(pages[0], 354, height - 376.5)
}
/* -------------------------------- Seite 2 -------------------------------- */
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 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"),
aufnahme.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"),
aufnahme.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"),
aufnahme.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)
}
pdf.getForm().flatten()
return pdf.save();
}