This commit is contained in:
Moritz Utcke
2025-04-20 17:21:46 -04:00
parent f9555c7a1e
commit e21a829cb6
19 changed files with 350 additions and 233 deletions

View File

@@ -11,6 +11,7 @@ import {
VerbrauchsausweisGewerbe,
VerbrauchsausweisWohnen,
} 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";
@@ -20,19 +21,22 @@ import { PutObjectCommand } from "@aws-sdk/client-s3";
import { s3Client } from "#lib/s3.js";
import { createInvoice, getLexOfficeRechnung } from "#lib/server/invoice.js";
import { tryCatch } from "#lib/tryCatch.js";
import SFTPClient from 'ssh2-sftp-client';
import {
getBedarfsausweisWohnenKomplett,
getVerbrauchsausweisGewerbeKomplett,
getVerbrauchsausweisWohnenKomplett,
} from "#lib/server/db.js";
import { PDFDocument } from "pdf-lib";
export const GET = defineApiRoute({
input: z.object({
id_ausweis: z.string(),
post: z.boolean().describe("Ob der Ausweis auch per Post ausgestellt werden soll.").optional().default(false)
}),
output: z.void(),
middleware: adminMiddleware,
async fetch({ id_ausweis }, context) {
async fetch({ id_ausweis, post }, context) {
const ausweisart = getAusweisartFromId(id_ausweis);
let ausweis:
@@ -95,7 +99,11 @@ export const GET = defineApiRoute({
);
if (error) {
return;
throw new APIError({
code: "BAD_REQUEST",
message:
"Die Rechnung konnte bei LexOffice nicht angelegt werden..",
});
}
const { id, voucherNumber } = result;
@@ -126,181 +134,235 @@ export const GET = defineApiRoute({
ausweis.aufnahme.objekt.benutzer
);
const [pdfRechnung, pdfRechnungError] = await tryCatch(getLexOfficeRechnung(rechnung));
// TODO: Das ist immer noch scheiße, LexOffice ist doof
// Hier müssen wir warten, damit wir sichergehen können, dass die Rechnung bei LexOffice existiert.
setTimeout(async () => {
const [pdfRechnung, pdfRechnungError] = await tryCatch(getLexOfficeRechnung(rechnung));
if (pdfRechnungError) {
throw new APIError({
code: "INTERNAL_SERVER_ERROR",
message: "Rechnungs PDF konnte nicht generiert werden."
})
}
if (!pdfAusweis) {
throw new APIError({
code: "INTERNAL_SERVER_ERROR",
message: "Ausweis PDF konnte nicht generiert werden."
})
}
if (!pdfDatenblatt) {
throw new APIError({
code: "INTERNAL_SERVER_ERROR",
message: "Datenblatt PDF konnte nicht generiert werden."
})
}
const ausweisCommand = new PutObjectCommand({
Bucket: "ibc-pdfs",
Key: `ID_${ausweis.id}_Energieausweis.pdf`,
Body: pdfAusweis,
ACL: "private",
});
await s3Client.send(ausweisCommand);
const datenblattCommand = new PutObjectCommand({
Bucket: "ibc-pdfs",
Key: `ID_${ausweis.id}_Datenblatt.pdf`,
Body: pdfDatenblatt,
ACL: "private",
});
await s3Client.send(datenblattCommand);
const rechnungsCommand = new PutObjectCommand({
Bucket: "ibc-pdfs",
Key: `ID_${ausweis.id}_Rechnung.pdf`,
Body: pdfDatenblatt,
ACL: "private",
});
await s3Client.send(rechnungsCommand);
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. 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. Nachfolgend finden Sie unsere Bankverbindung. Bitte geben Sie als Verwendungszweck die Rechnungsnummer an (siehe unten). Vielen Dank.</p>
<br>
<table>
<tr><td>Kreditinstitut</td><td>:</td><td>\t Commerzbank AG</td>
<tr><td>Empfänger</td><td>:</td><td>\t IB Cornelsen</td>
<tr><td>IBAN</td><td>:<td>\t DE81 2004 0000 0348 6008 00</td>
<tr><td>BIC</td><td>:</td><td>\t COBADEFFXXX</td>
<tr><td>Betrag</td><td>:</td><td>\t <b>${rechnung.betrag}€</b></td>
<tr><td>Verwendungszweck</td><td>:</td><td>\t <b>${rechnung.id}</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: [{
filename: `ID_${ausweis.id}_Energieausweis.pdf`,
encoding: "binary",
content: Buffer.from(pdfAusweis),
contentType: "application/pdf",
contentDisposition: "attachment",
}, {
filename: `ID_${ausweis.id}_Datenblatt.pdf`,
encoding: "binary",
content: Buffer.from(pdfDatenblatt),
contentType: "application/pdf",
contentDisposition: "attachment",
}, {
filename: `ID_${ausweis.id}_Rechnung.pdf`,
encoding: "binary",
content: Buffer.from(pdfRechnung),
contentType: "application/pdf",
contentDisposition: "attachment",
}]
});
if (ausweisart === Enums.Ausweisart.VerbrauchsausweisWohnen) {
await prisma.verbrauchsausweisWohnen.update({
where: {
id: ausweis.id,
},
data: {
ausgestellt: true,
},
if (pdfRechnungError) {
throw new APIError({
code: "INTERNAL_SERVER_ERROR",
message: "Rechnungs PDF konnte nicht generiert werden."
})
}
if (!pdfAusweis) {
throw new APIError({
code: "INTERNAL_SERVER_ERROR",
message: "Ausweis PDF konnte nicht generiert werden."
})
}
if (!pdfDatenblatt) {
throw new APIError({
code: "INTERNAL_SERVER_ERROR",
message: "Datenblatt PDF konnte nicht generiert werden."
})
}
const ausweisCommand = new PutObjectCommand({
Bucket: "ibc-pdfs",
Key: `ID_${ausweis.id}_Energieausweis.pdf`,
Body: pdfAusweis,
ACL: "private",
});
} else if (ausweisart === Enums.Ausweisart.VerbrauchsausweisGewerbe) {
await prisma.verbrauchsausweisGewerbe.update({
where: {
id: ausweis.id,
},
data: {
ausgestellt: true,
},
await s3Client.send(ausweisCommand);
const datenblattCommand = new PutObjectCommand({
Bucket: "ibc-pdfs",
Key: `ID_${ausweis.id}_Datenblatt.pdf`,
Body: pdfDatenblatt,
ACL: "private",
});
} else if (ausweisart === Enums.Ausweisart.BedarfsausweisWohnen) {
await prisma.bedarfsausweisWohnen.update({
where: {
id: ausweis.id,
},
data: {
ausgestellt: true,
},
await s3Client.send(datenblattCommand);
const rechnungsCommand = new PutObjectCommand({
Bucket: "ibc-pdfs",
Key: `ID_${ausweis.id}_Rechnung.pdf`,
Body: pdfDatenblatt,
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);
}
}
await appendPdf(pdfDatenblatt);
await appendPdf(pdfAusweis);
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" : ""} Nachfolgend finden Sie unsere Bankverbindung. Bitte geben Sie als Verwendungszweck die Rechnungsnummer an (siehe unten). Vielen Dank.</p>
<br>
<table>
<tr><td>Kreditinstitut</td><td>:</td><td>\t Commerzbank AG</td>
<tr><td>Empfänger</td><td>:</td><td>\t IB Cornelsen</td>
<tr><td>IBAN</td><td>:<td>\t DE81 2004 0000 0348 6008 00</td>
<tr><td>BIC</td><td>:</td><td>\t COBADEFFXXX</td>
<tr><td>Betrag</td><td>:</td><td>\t <b>${rechnung.betrag}€</b></td>
<tr><td>Verwendungszweck</td><td>:</td><td>\t <b>${rechnung.lex_office_id}</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: [{
filename: `ID_${ausweis.id}_Energieausweis.pdf`,
encoding: "binary",
content: Buffer.from(pdfAusweis),
contentType: "application/pdf",
contentDisposition: "attachment",
}, {
filename: `ID_${ausweis.id}_Datenblatt.pdf`,
encoding: "binary",
content: Buffer.from(pdfDatenblatt),
contentType: "application/pdf",
contentDisposition: "attachment",
}, {
filename: `ID_${ausweis.id}_Rechnung.pdf`,
encoding: "binary",
content: Buffer.from(pdfRechnung),
contentType: "application/pdf",
contentDisposition: "attachment",
}]
});
if (ausweisart === Enums.Ausweisart.VerbrauchsausweisWohnen) {
await prisma.verbrauchsausweisWohnen.update({
where: {
id: ausweis.id,
},
data: {
ausgestellt: true,
},
});
} else if (ausweisart === Enums.Ausweisart.VerbrauchsausweisGewerbe) {
await prisma.verbrauchsausweisGewerbe.update({
where: {
id: ausweis.id,
},
data: {
ausgestellt: true,
},
});
} else if (ausweisart === Enums.Ausweisart.BedarfsausweisWohnen) {
await prisma.bedarfsausweisWohnen.update({
where: {
id: ausweis.id,
},
data: {
ausgestellt: true,
},
});
}
}, 3000)
},
});

View File

@@ -3,16 +3,17 @@ import { adminMiddleware } from "#lib/middleware/authorization.js";
import { mollieClient } from "#lib/mollie.js";
import { getPrismaAusweisAdapter } from "#lib/server/ausweis.js";
import { Prisma, prisma } from "#lib/server/prisma.js";
import { RefundStatus } from "@mollie/api-client";
import { APIError, defineApiRoute } from "astro-typesafe-api/server";
import { z } from "zod";
export const PUT = defineApiRoute({
input: z.object({
uid_ausweis: UUidWithPrefix
ausweis_id: UUidWithPrefix
}),
middleware: adminMiddleware,
async fetch(input, context, transfer) {
const adapter = getPrismaAusweisAdapter(input.uid_ausweis) as Prisma.VerbrauchsausweisWohnenDelegate;
const adapter = getPrismaAusweisAdapter(input.ausweis_id) as Prisma.VerbrauchsausweisWohnenDelegate;
if (!adapter) {
throw new APIError({
@@ -23,7 +24,7 @@ export const PUT = defineApiRoute({
const ausweis = await adapter.findUnique({
where: {
uid: input.uid_ausweis
id: input.ausweis_id
}
})
@@ -36,7 +37,7 @@ export const PUT = defineApiRoute({
const response = await adapter.findUnique({
where: {
uid: input.uid_ausweis
id: input.ausweis_id
},
select: {
rechnung: true
@@ -46,7 +47,7 @@ export const PUT = defineApiRoute({
if (!response || !response.rechnung) {
await adapter.update({
where: {
uid: input.uid_ausweis
id: input.ausweis_id
},
data: {
storniert: true
@@ -63,7 +64,7 @@ export const PUT = defineApiRoute({
await adapter.update({
where: {
uid: input.uid_ausweis
id: input.ausweis_id
},
data: {
storniert: true,
@@ -94,10 +95,17 @@ export const PUT = defineApiRoute({
value: rechnung.betrag.toFixed(2)
},
metadata: {
rechnung_uid: rechnung.uid
rechnung_id: rechnung.id
},
testmode: true
})
if (refund.status === RefundStatus.failed) {
throw new APIError({
code: "INTERNAL_SERVER_ERROR",
message: "Rückerstattung konnte nicht durchgeführt werden, Mollie hat die Rückerstattung abgelehnt."
})
}
}
},
})

View File

@@ -30,7 +30,7 @@ export const GET = defineApiRoute({
// Falls der Nutzer nicht existiert, wird eine Fehlermeldung zurückgegeben.
const user = await prisma.benutzer.findUnique({
where: {
email: input.email,
email: input.email.toLowerCase(),
},
});

View File

@@ -1,5 +1,5 @@
import { z } from "zod";
import { prisma } from "#lib/server/prisma";
import { prisma } from "#lib/server/prisma.js";
import { APIError, defineApiRoute } from "astro-typesafe-api/server";
export const GET = defineApiRoute({

View File

@@ -65,6 +65,9 @@ export const PUT = defineApiRoute({
const ausweis = await adapter.findUnique({
where: {
id: ausweis_id
},
include: {
rechnung: true
}
})
@@ -75,6 +78,13 @@ export const PUT = defineApiRoute({
});
}
if (ausweis.rechnung) {
throw new APIError({
code: "BAD_REQUEST",
message: "Eine Rechnung für diesen Ausweis existiert bereits.",
});
}
if (ausweis.benutzer_id !== user.id) {
throw new APIError({
code: "UNAUTHORIZED",

View File

@@ -1,4 +1,5 @@
import { UUidWithPrefix } from "#components/Ausweis/types.js";
import { VALID_UUID_PREFIXES } from "#lib/constants.js";
import { generatePrefixedId } from "#lib/db.js";
import { authorizationHeaders, authorizationMiddleware } from "#lib/middleware/authorization.js";
import { prisma } from "#lib/server/prisma.js";
@@ -52,7 +53,7 @@ export const PUT = defineApiRoute({
})
}
const id = generatePrefixedId(9, "VW");
const id = generatePrefixedId(9, VALID_UUID_PREFIXES.VerbrauchsausweisWohnen);
const createdAusweis = await prisma.verbrauchsausweisWohnen.create({
data: {

View File

@@ -20,7 +20,7 @@ export const GET: APIRoute = async (Astro) => {
const file = await getS3File("ibc-images", `${image.id}.jpg`);
if (!file) {
const file = await getS3File("ibc-images", `${image.name}.jpg`)
const file = await getS3File("ibc-images", image.name)
if (!file) {
return new Response(null, { status: 404 })

View File

@@ -5,7 +5,7 @@ import AusweisLayout from "#layouts/AusweisLayoutPruefung.astro";
import { Enums } from "#lib/client/prisma";
import { getCurrentUser } from "#lib/server/user";
import { getAusweisartFromId } from "#components/Ausweis/types";
import { getAufnahme, getBedarfsausweisWohnen, getBilder, getObjekt, getUnterlagen, getVerbrauchsausweisGewerbe, getVerbrauchsausweisWohnen } from "#lib/server/db";
import { getAufnahme, getBedarfsausweisWohnen, getBilder, getObjekt, getRechnung, getUnterlagen, getVerbrauchsausweisGewerbe, getVerbrauchsausweisWohnen } from "#lib/server/db";
// Man sollte nur auf diese Seite kommen, wenn ein Ausweis bereits vorliegt und in der Datenbank abgespeichert wurde.
@@ -15,7 +15,7 @@ const user = await getCurrentUser(Astro) || {}
const params = new URLSearchParams(await Astro.request.text());
const searchParams = Astro.url.searchParams;
let ausweis, aufnahme, objekt, ausweisart, bilder, unterlagen, partner_code;
let ausweis, aufnahme, objekt, ausweisart, bilder, unterlagen, partner_code, rechnung = null;
if (!params.has("ausweis") || !params.has("aufnahme") || !params.has("objekt") || !params.has("bilder") || !params.has("ausweisart")) {
// Rechnung und Ausweis als GET parameter
@@ -40,6 +40,9 @@ if (!params.has("ausweis") || !params.has("aufnahme") || !params.has("objekt") |
objekt = await getObjekt(aufnahme?.objekt_id)
bilder = await getBilder(ausweis.aufnahme_id)
unterlagen = await getUnterlagen(ausweis.aufnahme_id)
if (ausweis.rechnung_id) {
rechnung = await getRechnung(ausweis.rechnung_id)
}
} else {
// Nichts ist vorhanden
return Astro.redirect("/404")
@@ -65,6 +68,6 @@ if (!params.has("ausweis") || !params.has("aufnahme") || !params.has("objekt") |
---
<AusweisLayout title="Kundendaten Aufnehmen - IBCornelsen">
<KundendatenModule {user} {ausweis} {objekt} {aufnahme} {bilder} {ausweisart} {unterlagen} {partner_code} aktiveBezahlmethode={Enums.Bezahlmethoden.paypal} client:only ></KundendatenModule>
<KundendatenModule {user} {ausweis} {objekt} {aufnahme} {bilder} {rechnung} {ausweisart} {unterlagen} {partner_code} aktiveBezahlmethode={Enums.Bezahlmethoden.paypal} client:only ></KundendatenModule>
</AusweisLayout>