import { getAusweisartFromId } from "#components/Ausweis/types.js"; import { adminMiddleware } from "#lib/middleware/authorization.js"; import { Aufnahme, BedarfsausweisWohnen, Benutzer, Bild, Enums, Objekt, prisma, Rechnung, } from "#lib/server/prisma.js"; import { join } from "path"; import { APIError, defineApiRoute } from "astro-typesafe-api/server"; import { z } from "astro:content"; import { transport } from "#lib/mail.js"; import { PutObjectCommand, GetObjectCommand } from "@aws-sdk/client-s3"; import { s3Client } from "#lib/s3.js"; import { createInvoice, getLexOfficeRechnung, getLexOfficeVoucherNumber, } from "#lib/server/invoice.js"; import { tryCatch } from "#lib/tryCatch.js"; import SFTPClient from "ssh2-sftp-client"; import { getBedarfsausweisWohnenKomplett, } from "#lib/server/db.js"; import { PDFDocument } from "pdf-lib"; import isBase64 from "is-base64"; import Mail from "nodemailer/lib/mailer/index.js"; import mime from "mime" const streamToBuffer = async (stream) => { return new Promise((resolve, reject) => { const chunks = []; stream.on("data", (chunk) => chunks.push(chunk)); stream.on("error", reject); stream.on("end", () => resolve(Buffer.concat(chunks))); }); }; export const POST = defineApiRoute({ input: z.object({ id_ausweis: z.string(), post: z .boolean() .describe("Ob der Ausweis auch per Post ausgestellt werden soll.") .optional() .default(false), files: z.object({ data: z.string(), name: z.string(), type: z.enum(["Ausweis", "Sonstiges"]) }).array() }), output: z.void(), middleware: adminMiddleware, async fetch({ id_ausweis, post, files }, context) { const ausweisart = getAusweisartFromId(id_ausweis); let ausweis: | ((BedarfsausweisWohnen) & { aufnahme: Aufnahme & { bilder: Bild[]; objekt: Objekt & { benutzer: Benutzer | null; }; }; rechnung: Rechnung; }) | null = null; if (ausweisart === Enums.Ausweisart.BedarfsausweisWohnen) { ausweis = await getBedarfsausweisWohnenKomplett(id_ausweis); } else { throw new APIError({ code: "BAD_REQUEST", message: "Nur Bedarfsausweise werden von dieser Route unterstützt." }) } if (!ausweis) { throw new APIError({ code: "BAD_REQUEST", message: "Ausweis existiert nicht.", }); } const rechnung = await prisma.rechnung.findFirst({ where: { bedarfsausweis_wohnen: { id: id_ausweis } }, orderBy: { erstellt_am: "desc", }, include: { benutzer: true, }, }); if (!rechnung) { throw new APIError({ code: "BAD_REQUEST", message: "Die Rechnung wurde noch nicht erstellt, wir können nicht fortfahren.", }); } let voucherNumber: string = ""; if (!rechnung.lex_office_id) { const [result, error] = await tryCatch( createInvoice(ausweis, rechnung) ); if (error) { throw new APIError({ code: "BAD_REQUEST", message: "Die Rechnung konnte bei LexOffice nicht angelegt werden..", cause: error, }); } const { id, voucherNumber: lexOfficeVoucherNumber } = result; voucherNumber = lexOfficeVoucherNumber; await prisma.rechnung.update({ where: { id: rechnung.id, }, data: { lex_office_id: id, }, }); } else { voucherNumber = await getLexOfficeVoucherNumber(rechnung); } // Hier müssen wir warten, damit wir sichergehen können, dass die Rechnung bei LexOffice existiert. setTimeout(async () => { let pdfRechnung, pdfRechnungError, pdfSource; try { const rechnungsCheckCommand = new GetObjectCommand({ Bucket: "ibc-pdfs", Key: `ID_${ausweis.id}_Rechnung.pdf`, }); const s3Response = await s3Client.send(rechnungsCheckCommand); pdfRechnung = await streamToBuffer(s3Response.Body); } catch (error) { pdfRechnungError = error; } if (pdfRechnung && !pdfRechnungError) { pdfSource = 'S3'; } else { [pdfRechnung, pdfRechnungError] = await tryCatch(getLexOfficeRechnung(rechnung)); pdfSource = 'LexOffice'; } if (pdfRechnungError) { // console.error(pdfRechnungError); throw new APIError({ code: "INTERNAL_SERVER_ERROR", message: "Rechnungs PDF konnte nicht generiert werden."+pdfRechnungError, }); } const processedFiles: Mail.Attachment[] = [] for (const file of files) { const { data, name, type } = file; if (!isBase64(data, { mimeRequired: true })) { throw new APIError({ code: "BAD_REQUEST", message: `Das Attachment '${name}' kann nicht gespeichert werden.`, }); } const dataWithoutPrefix = data.replace(/^data:.*;base64,/, ""); const buffer = Buffer.from(dataWithoutPrefix, "base64"); const mimeType = mime.getType(name.split(".").pop() as string) if (!mimeType) { throw new APIError({ code: "BAD_REQUEST", message: `Die Datei ${name} hat einen ungültigen MimeType` }) } let filename: string; if (type === "Ausweis") { filename = `ID_${ausweis.id}_Energieausweis.pdf` } else { filename = `ID_${ausweis.id}_${name}`; } processedFiles.push({ content: buffer, contentDisposition: "attachment", contentType: mimeType as string, encoding: "binary", filename }) const command = new PutObjectCommand({ Bucket: "ibc-pdfs", Key: filename, Body: buffer, ACL: "private", }); await s3Client.send(command); } if (pdfRechnung) { processedFiles.push({ filename: `ID_${ausweis.id}_Rechnung.pdf`, encoding: "binary", content: Buffer.from(pdfRechnung), contentType: "application/pdf", }); } if (pdfSource == 'LexOffice' && pdfRechnung != null ){ const rechnungsCommand = new PutObjectCommand({ Bucket: "ibc-pdfs", Key: `ID_${ausweis.id}_Rechnung.pdf`, Body: Buffer.from(pdfRechnung), ACL: "private", }); await s3Client.send(rechnungsCommand); } // Falls Postversand angefragt wurde müssen wir die Dateien auf den Postserver hochladen if (post) { const dateiNameDruck = `1011000000000-AW_ID_${ausweis.id}.pdf`; const outputPdf = await PDFDocument.create(); async function appendPdf(buffer: Uint8Array) { const doc = await PDFDocument.load(buffer); const copiedPages = await outputPdf.copyPages( doc, doc.getPageIndices() ); for (const page of copiedPages) { outputPdf.addPage(page); } } for (const file of processedFiles) { await appendPdf(file.content as Buffer); } const pdfBytes = await outputPdf.save(); const pdfCommand = new PutObjectCommand({ Bucket: "ibc-pdfs", Key: dateiNameDruck, Body: pdfBytes, ACL: "private", }); await s3Client.send(pdfCommand); const sftp = new SFTPClient(); try { await sftp.connect({ host: "api.ppost.de", username: "jens.cornelsen@ib-cornelsen.de", password: "ANTQesWYjd", }); const cwd = await sftp.cwd(); await sftp.put( Buffer.from(pdfBytes), join(cwd, "upload/api", dateiNameDruck) ); } catch (err) { console.error("SFTP Upload failed:", err); throw new APIError({ code: "INTERNAL_SERVER_ERROR", message: "Login zum Postversand Server war nicht erfolgreich.", }); } finally { sftp.end(); } } let html: string; if (rechnung.status === Enums.Rechnungsstatus.paid) { html = `

Sehr geehrte*r ${rechnung.empfaenger},

im Anhang finden Sie Ihren geprüften Energieusweis inkl. Rechnung als PDF-Datei. ${ post ? "Zusätzlich haben wir Ihren Ausweis per Post verschickt" : "" } Den Rechnungsbetrag haben Sie bereits bezahlt. Vielen Dank.

Mit freundlichen Grüßen,
Dipl.-Ing. Jens Cornelsen

IB Cornelsen
Katendeich 5A
21035 Hamburg
www.online-energieausweis.org

fon 040 · 209339850
fax 040 · 209339859

`; } else { html = `

Sehr geehrte*r ${rechnung.empfaenger},

im Anhang finden Sie Ihren geprüften Energieusweis inkl. Rechnung als PDF-Datei. ${ post ? "Zusätzlich haben wir Ihren Ausweis per Post verschickt" : "" } Bitte beachten Sie unsere neue Bankverbindung. Bitte geben Sie als Verwendungszweck die Rechnungsnummer an (siehe unten). Vielen Dank.


Kreditinstitut:\t Volksbank eG
Empfänger:\t IB Cornelsen
IBAN:\t DE13 2519 3331 7209 0731 00
BIC:\t GENODEF1PAT
Betrag:\t ${rechnung.betrag}€
Verwendungszweck:\t ${voucherNumber}


Mit freundlichen Grüßen,
Dipl.-Ing. Jens Cornelsen

IB Cornelsen
Katendeich 5A
21035 Hamburg
www.online-energieausweis.org

fon 040 · 209339850
fax 040 · 209339859

`; } await transport.sendMail({ from: `"IBCornelsen" `, to: rechnung.email || rechnung.benutzer.email, bcc: "info@online-energieausweis.org", subject: `Ihr Originalausweis vom Ingenieurbüro Cornelsen (ID: ${ausweis.id})`, html, attachments: processedFiles, }); if (ausweisart === Enums.Ausweisart.BedarfsausweisWohnen) { await prisma.bedarfsausweisWohnen.update({ where: { id: ausweis.id, }, data: { ausgestellt: true, }, }); } }, 3000); }, });