Files
online-energieausweis/src/pages/api/admin/bedarfsausweis-ausstellen.ts
2025-05-02 17:31:28 +02:00

398 lines
9.5 KiB
TypeScript

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<ArrayBufferLike>) {
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 = `
<p>Sehr geehrte*r ${rechnung.empfaenger},</p>
<p>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.</p>
<p>
Mit freundlichen Grüßen,
<br>
Dipl.-Ing. Jens Cornelsen
<br>
<br>
<strong>IB Cornelsen</strong>
<br>
Katendeich 5A
<br>
21035 Hamburg
<br>
www.online-energieausweis.org
<br>
<br>
fon 040 · 209339850
<br>
fax 040 · 209339859
</p>`;
} else {
html = `
<p>Sehr geehrte*r ${rechnung.empfaenger},</p>
<p>im Anhang finden Sie Ihren geprüften Energieusweis inkl. Rechnung als PDF-Datei. ${
post ? "Zusätzlich haben wir Ihren Ausweis per Post verschickt" : ""
} <b>Bitte beachten Sie unsere neue Bankverbindung.</b> Bitte geben Sie als Verwendungszweck die Rechnungsnummer an (siehe unten). Vielen Dank.</p>
<br>
<table>
<tr><td>Kreditinstitut</td><td>:</td><td>\t Volksbank eG</td>
<tr><td>Empfänger</td><td>:</td><td>\t IB Cornelsen</td>
<tr><td>IBAN</td><td>:<td>\t DE13 2519 3331 7209 0731 00</td>
<tr><td>BIC</td><td>:</td><td>\t GENODEF1PAT</td>
<tr><td>Betrag</td><td>:</td><td>\t <b>${rechnung.betrag}€</b></td>
<tr><td>Verwendungszweck</td><td>:</td><td>\t <b>${voucherNumber}</b></td>
</table>
<br>
<br>
<p>
Mit freundlichen Grüßen,
<br>
Dipl.-Ing. Jens Cornelsen
<br>
<br>
<strong>IB Cornelsen</strong>
<br>
Katendeich 5A
<br>
21035 Hamburg
<br>
www.online-energieausweis.org
<br>
<br>
fon 040 · 209339850
<br>
fax 040 · 209339859
</p>`;
}
await transport.sendMail({
from: `"IBCornelsen" <info@online-energieausweis.org>`,
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);
},
});