Merge branch 'dev' into main
This commit is contained in:
@@ -6,6 +6,7 @@ export const createCaller = createCallerFactory({
|
||||
"postleitzahlen": await import("../src/pages/api/postleitzahlen.ts"),
|
||||
"unterlage": await import("../src/pages/api/unterlage.ts"),
|
||||
"admin/ausstellen": await import("../src/pages/api/admin/ausstellen.ts"),
|
||||
"admin/bedarfsausweis-ausstellen": await import("../src/pages/api/admin/bedarfsausweis-ausstellen.ts"),
|
||||
"admin/bestellbestaetigung": await import("../src/pages/api/admin/bestellbestaetigung.ts"),
|
||||
"admin/erinnern": await import("../src/pages/api/admin/erinnern.ts"),
|
||||
"admin/nicht-ausstellen": await import("../src/pages/api/admin/nicht-ausstellen.ts"),
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
import {
|
||||
CrossCircled,
|
||||
DotsVertical,
|
||||
Download,
|
||||
Pencil2,
|
||||
QuestionMarkCircled,
|
||||
} from "radix-svelte-icons";
|
||||
@@ -28,10 +27,8 @@
|
||||
import DashboardNotification from "./DashboardNotification.svelte";
|
||||
import { notifications } from "#components/NotificationProvider/shared.js";
|
||||
import { Bell } from "radix-svelte-icons";
|
||||
import { A13BerechnungRechnerischeLaufzeitHeizung } from "#lib/Berechnungen/BedarfsausweisWohnen/A13BerechnungRechnerischeLaufzeitHeizung.js";
|
||||
import mime from "mime"
|
||||
import { getGEGNachweisGewerbe } from "#lib/server/db.js";
|
||||
|
||||
import mime from "mime"
|
||||
|
||||
const progress = ausweis.ausgestellt ? 100 : ausweis.bestellt ? 66 : 33;
|
||||
|
||||
const ausweisart = getAusweisartFromId(ausweis.id);
|
||||
@@ -151,6 +148,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function ausstellen(post = false) {
|
||||
const notification = addNotification({
|
||||
message: "Ausweis wird ausgestellt.",
|
||||
@@ -158,33 +157,106 @@
|
||||
type: "info",
|
||||
timeout: 0,
|
||||
})
|
||||
try {
|
||||
await api.admin.ausstellen.GET.fetch({
|
||||
id_ausweis: ausweis.id,
|
||||
post
|
||||
}, {
|
||||
headers: {
|
||||
"Authorization": `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`
|
||||
|
||||
if (ausweisart === Enums.Ausweisart.BedarfsausweisWohnen) {
|
||||
const results: {data: string, name: string, type: "Sonstiges" | "Ausweis"}[] = []
|
||||
let i = 0
|
||||
const dateien = [...(bedarfsausweisAdditionalInput.files || []), ...(bedarfsausweisFileInput.files || [])];
|
||||
|
||||
if (dateien.length === 0) {
|
||||
addNotification({
|
||||
message: "Bitte laden sie vor dem Ausstellen einen Ausweis hoch.",
|
||||
timeout: 3000,
|
||||
type: "error",
|
||||
dismissable: true
|
||||
})
|
||||
return;
|
||||
}
|
||||
|
||||
for (const datei of dateien) {
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = async (ev) => {
|
||||
const result = reader.result;
|
||||
|
||||
if (!result) {
|
||||
addNotification({
|
||||
message: `Die Datei ${datei.name} konnte nicht verarbeitet werden.`,
|
||||
timeout: 3000,
|
||||
type: "error",
|
||||
dismissable: true
|
||||
})
|
||||
}
|
||||
|
||||
results.push({
|
||||
data: result as string,
|
||||
name: datei.name,
|
||||
type: i == dateien.length - 1 ? "Ausweis" : "Sonstiges"
|
||||
})
|
||||
i++
|
||||
|
||||
if (i === dateien.length) {
|
||||
try {
|
||||
await api.admin["bedarfsausweis-ausstellen"].POST.fetch({
|
||||
id_ausweis: ausweis.id,
|
||||
post,
|
||||
files: results
|
||||
}, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`
|
||||
}
|
||||
})
|
||||
updateNotification(notification, {
|
||||
message: "Ausweis ausgestellt.",
|
||||
subtext: "Der Ausweis wurde erfolgreich ausgestellt.",
|
||||
timeout: 3000,
|
||||
type: "success",
|
||||
})
|
||||
} catch(e) {
|
||||
updateNotification(notification, {
|
||||
message: "Das hat nicht geklappt.",
|
||||
subtext: e as string,
|
||||
timeout: 3000,
|
||||
type: "error",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
reader.readAsDataURL(datei)
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
await api.admin.ausstellen.GET.fetch({
|
||||
id_ausweis: ausweis.id,
|
||||
post
|
||||
}, {
|
||||
headers: {
|
||||
"Authorization": `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`
|
||||
}
|
||||
})
|
||||
|
||||
ausweis.ausgestellt = true;
|
||||
ausweis.ausgestellt = true;
|
||||
|
||||
updateNotification(notification, {
|
||||
message: "Ausweis ausgestellt.",
|
||||
subtext: "Der Ausweis wurde erfolgreich ausgestellt.",
|
||||
timeout: 3000,
|
||||
type: "success",
|
||||
})
|
||||
} catch(e) {
|
||||
updateNotification(notification, {
|
||||
message: "Das hat nicht geklappt.",
|
||||
subtext: e as string,
|
||||
timeout: 3000,
|
||||
type: "error",
|
||||
})
|
||||
updateNotification(notification, {
|
||||
message: "Ausweis ausgestellt.",
|
||||
subtext: "Der Ausweis wurde erfolgreich ausgestellt.",
|
||||
timeout: 3000,
|
||||
type: "success",
|
||||
})
|
||||
} catch(e) {
|
||||
updateNotification(notification, {
|
||||
message: "Das hat nicht geklappt.",
|
||||
subtext: e as string,
|
||||
timeout: 3000,
|
||||
type: "error",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let bedarfsausweisFileInput: HTMLInputElement;
|
||||
let bedarfsausweisAdditionalInput: HTMLInputElement;
|
||||
</script>
|
||||
|
||||
<div class="relative bg-base-200 border border-base-300 rounded-lg p-4 m-2">
|
||||
@@ -310,6 +382,12 @@
|
||||
</div>
|
||||
{/await}
|
||||
|
||||
{#if benutzer.rolle === Enums.BenutzerRolle.ADMIN && ausweisart === Enums.Ausweisart.BedarfsausweisWohnen}
|
||||
<span>Laden sie hier den Ausweis hoch</span>
|
||||
<input type="file" bind:this={bedarfsausweisFileInput}>
|
||||
<span>Laden sie hier zusätzliche Dokumente hoch</span>
|
||||
<input type="file" bind:this={bedarfsausweisAdditionalInput} multiple>
|
||||
{/if}
|
||||
|
||||
<div class="flex flex-row justify-start items-center mb-4">
|
||||
<a
|
||||
@@ -406,6 +484,7 @@
|
||||
{/if}
|
||||
|
||||
{#if benutzer.rolle === Enums.BenutzerRolle.ADMIN}
|
||||
|
||||
<button class="button text-sm" title="Ausstellen" on:click={() => ausstellen(false)}>A</button>
|
||||
<button class="button text-sm" title="Ausstellen mit Postversand" on:click={() => ausstellen(true)}>P</button>
|
||||
<button class="button text-sm" title="Stornieren" on:click={stornieren}>S</button>
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
BenutzerClient,
|
||||
ObjektKomplettClient,
|
||||
} from "#components/Ausweis/types.js";
|
||||
import { Benutzer } from "#lib/server/prisma.js";
|
||||
import { Benutzer, Rechnung } from "#lib/server/prisma.js";
|
||||
|
||||
export let lightTheme: boolean;
|
||||
export let benutzer: Benutzer;
|
||||
export let besteller: Benutzer;
|
||||
export let besteller: Benutzer | null;
|
||||
|
||||
let id: string;
|
||||
</script>
|
||||
@@ -63,7 +63,8 @@
|
||||
|
||||
<hr class="border-gray-600" />
|
||||
|
||||
Besteller
|
||||
{#if besteller}
|
||||
Besteller
|
||||
|
||||
<div class="flex flex-row mb-4">
|
||||
<div class="item-center mr-4">
|
||||
@@ -78,12 +79,14 @@
|
||||
</div>
|
||||
<div class="text-xs text-gray-500">
|
||||
<!-- Soll für den Aussteller sichtbar sein -->
|
||||
Telefon {besteller.telefon}
|
||||
Tel. 1: {besteller.telefon}<br>
|
||||
<!-- Tel. 2: {rechnung.telefon} -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="border-gray-600" />
|
||||
{/if}
|
||||
|
||||
|
||||
<!--
|
||||
Mitwirkende
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Benutzer } from "#lib/server/prisma";
|
||||
export interface Props {
|
||||
title: string;
|
||||
user: Benutzer;
|
||||
besteller: Benutzer;
|
||||
besteller: Benutzer | null;
|
||||
}
|
||||
|
||||
const { title, user, besteller } = Astro.props;
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
VerbrauchsausweisGewerbe,
|
||||
VerbrauchsausweisWohnen,
|
||||
} from "#lib/server/prisma.js";
|
||||
import { join } from "path"
|
||||
import { join } from "path";
|
||||
import { APIError, defineApiRoute } from "astro-typesafe-api/server";
|
||||
import { z } from "astro:content";
|
||||
import { transport } from "#lib/mail.js";
|
||||
@@ -21,9 +21,13 @@ import { BASE_URI } from "#lib/constants.js";
|
||||
import { getAnsichtsausweis, getDatenblatt, getAushang } from "#lib/server/ausweis.js";
|
||||
import { PutObjectCommand } from "@aws-sdk/client-s3";
|
||||
import { s3Client } from "#lib/s3.js";
|
||||
import { createInvoice, getLexOfficeRechnung, getLexOfficeVoucherNumber } from "#lib/server/invoice.js";
|
||||
import {
|
||||
createInvoice,
|
||||
getLexOfficeRechnung,
|
||||
getLexOfficeVoucherNumber,
|
||||
} from "#lib/server/invoice.js";
|
||||
import { tryCatch } from "#lib/tryCatch.js";
|
||||
import SFTPClient from 'ssh2-sftp-client';
|
||||
import SFTPClient from "ssh2-sftp-client";
|
||||
import {
|
||||
getBedarfsausweisWohnenKomplett,
|
||||
getVerbrauchsausweisGewerbeKomplett,
|
||||
@@ -35,7 +39,11 @@ import * as fs from 'fs';
|
||||
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)
|
||||
post: z
|
||||
.boolean()
|
||||
.describe("Ob der Ausweis auch per Post ausgestellt werden soll.")
|
||||
.optional()
|
||||
.default(false),
|
||||
}),
|
||||
output: z.void(),
|
||||
middleware: adminMiddleware,
|
||||
@@ -53,8 +61,8 @@ export const GET = defineApiRoute({
|
||||
objekt: Objekt & {
|
||||
benutzer: Benutzer | null;
|
||||
};
|
||||
},
|
||||
rechnung: Rechnung
|
||||
};
|
||||
rechnung: Rechnung;
|
||||
})
|
||||
| null = null;
|
||||
|
||||
@@ -78,15 +86,15 @@ export const GET = defineApiRoute({
|
||||
OR: [
|
||||
{ bedarfsausweis_wohnen: { id: id_ausweis } },
|
||||
{ verbrauchsausweis_wohnen: { id: id_ausweis } },
|
||||
{ verbrauchsausweis_gewerbe: { id: id_ausweis } }
|
||||
]
|
||||
{ verbrauchsausweis_gewerbe: { id: id_ausweis } },
|
||||
],
|
||||
},
|
||||
orderBy: {
|
||||
erstellt_am: "desc",
|
||||
},
|
||||
include: {
|
||||
benutzer: true
|
||||
}
|
||||
benutzer: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!rechnung) {
|
||||
@@ -108,7 +116,7 @@ export const GET = defineApiRoute({
|
||||
code: "BAD_REQUEST",
|
||||
message:
|
||||
"Die Rechnung konnte bei LexOffice nicht angelegt werden..",
|
||||
cause: error
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -125,7 +133,7 @@ export const GET = defineApiRoute({
|
||||
},
|
||||
});
|
||||
} else {
|
||||
voucherNumber = await getLexOfficeVoucherNumber(rechnung)
|
||||
voucherNumber = await getLexOfficeVoucherNumber(rechnung);
|
||||
}
|
||||
|
||||
const pdfAusweis = await getAnsichtsausweis(
|
||||
@@ -158,54 +166,56 @@ export const GET = defineApiRoute({
|
||||
// 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));
|
||||
const [pdfRechnung, pdfRechnungError] = await tryCatch(
|
||||
getLexOfficeRechnung(rechnung)
|
||||
);
|
||||
|
||||
if (pdfRechnungError) {
|
||||
throw new APIError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: "Rechnungs PDF konnte nicht generiert werden."
|
||||
})
|
||||
message: "Rechnungs PDF konnte nicht generiert werden.",
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (!pdfAusweis) {
|
||||
throw new APIError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: "Ausweis PDF konnte nicht generiert werden."
|
||||
})
|
||||
message: "Ausweis PDF konnte nicht generiert werden.",
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (!pdfDatenblatt) {
|
||||
throw new APIError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: "Datenblatt PDF konnte nicht generiert werden."
|
||||
})
|
||||
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,
|
||||
Body: Buffer.from(pdfRechnung),
|
||||
ACL: "private",
|
||||
});
|
||||
|
||||
|
||||
await s3Client.send(rechnungsCommand);
|
||||
|
||||
if (pdfAushang){
|
||||
@@ -222,24 +232,29 @@ export const GET = defineApiRoute({
|
||||
// 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());
|
||||
const copiedPages = await outputPdf.copyPages(
|
||||
doc,
|
||||
doc.getPageIndices()
|
||||
);
|
||||
for (const page of copiedPages) {
|
||||
outputPdf.addPage(page);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
await appendPdf(pdfDatenblatt);
|
||||
await appendPdf(pdfAusweis);
|
||||
|
||||
|
||||
if (pdfAushang){
|
||||
await appendPdf(pdfAushang);
|
||||
}
|
||||
|
||||
|
||||
const pdfBytes = await outputPdf.save();
|
||||
|
||||
const pdfCommand = new PutObjectCommand({
|
||||
@@ -248,38 +263,44 @@ export const GET = defineApiRoute({
|
||||
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',
|
||||
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));
|
||||
await sftp.put(
|
||||
Buffer.from(pdfBytes),
|
||||
join(cwd, "upload/api", dateiNameDruck)
|
||||
);
|
||||
} catch (err) {
|
||||
console.error('SFTP Upload failed:', err);
|
||||
console.error("SFTP Upload failed:", err);
|
||||
throw new APIError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: "Login zum Postversand Server war nicht erfolgreich."
|
||||
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>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,
|
||||
@@ -306,7 +327,9 @@ export const GET = defineApiRoute({
|
||||
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>
|
||||
<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>
|
||||
@@ -343,6 +366,7 @@ export const GET = defineApiRoute({
|
||||
fax 040 · 209339859
|
||||
</p>`;
|
||||
}
|
||||
|
||||
|
||||
const attachments: Attachment[] = [
|
||||
{
|
||||
@@ -374,15 +398,16 @@ export const GET = defineApiRoute({
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
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
|
||||
});
|
||||
|
||||
|
||||
if (ausweisart === Enums.Ausweisart.VerbrauchsausweisWohnen) {
|
||||
await prisma.verbrauchsausweisWohnen.update({
|
||||
where: {
|
||||
@@ -392,7 +417,9 @@ export const GET = defineApiRoute({
|
||||
ausgestellt: true,
|
||||
},
|
||||
});
|
||||
} else if (ausweisart === Enums.Ausweisart.VerbrauchsausweisGewerbe) {
|
||||
} else if (
|
||||
ausweisart === Enums.Ausweisart.VerbrauchsausweisGewerbe
|
||||
) {
|
||||
await prisma.verbrauchsausweisGewerbe.update({
|
||||
where: {
|
||||
id: ausweis.id,
|
||||
@@ -411,6 +438,6 @@ export const GET = defineApiRoute({
|
||||
},
|
||||
});
|
||||
}
|
||||
}, 3000)
|
||||
}, 3000);
|
||||
},
|
||||
});
|
||||
|
||||
372
src/pages/api/admin/bedarfsausweis-ausstellen.ts
Normal file
372
src/pages/api/admin/bedarfsausweis-ausstellen.ts
Normal file
@@ -0,0 +1,372 @@
|
||||
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"
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
let pdfRechnung, pdfRechnungError;
|
||||
|
||||
const rechnungsCheckCommand = new GetObjectCommand({
|
||||
Bucket: "ibc-pdfs",
|
||||
Key: `ID_${ausweis.id}_Rechnung.pdf`,
|
||||
});
|
||||
|
||||
// Hier müssen wir warten, damit wir sichergehen können, dass die Rechnung bei LexOffice existiert.
|
||||
if (rechnungsCheckCommand) {
|
||||
pdfRechnung = rechnungsCheckCommand;
|
||||
pdfRechnungError = null;
|
||||
} else {
|
||||
[pdfRechnung, pdfRechnungError] = await tryCatch(getLexOfficeRechnung(rechnung));
|
||||
}
|
||||
|
||||
if (pdfRechnungError) {
|
||||
throw new APIError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: "Rechnungs PDF konnte nicht generiert werden.",
|
||||
});
|
||||
}
|
||||
|
||||
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:image\/\w+;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}_Ausweis.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: name,
|
||||
Body: buffer,
|
||||
ACL: "private",
|
||||
});
|
||||
|
||||
await s3Client.send(command);
|
||||
}
|
||||
|
||||
if (!rechnungsCheckCommand && 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" : ""
|
||||
} 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>${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,
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -1,21 +1,10 @@
|
||||
---
|
||||
import DashboardLayout from "#layouts/DashboardLayout.astro";
|
||||
import UserLayout from "#layouts/DashboardLayout.astro";
|
||||
import { API_ACCESS_TOKEN_COOKIE_NAME } from "#lib/constants";
|
||||
import { Enums, prisma } from "#lib/server/prisma";
|
||||
import { createCaller } from "src/astro-typesafe-api-caller";
|
||||
import DashboardModule from "#modules/Dashboard/DashboardModule.svelte";
|
||||
import { Enums } from "#lib/server/prisma";
|
||||
import ImpersonateUserModule from "#modules/ImpersonateUserModule.svelte";
|
||||
import { getCurrentUser } from "#lib/server/user";
|
||||
|
||||
const caller = createCaller(Astro)
|
||||
|
||||
const params = Astro.params;
|
||||
|
||||
const user = await caller.user.self.GET.fetch(undefined, {
|
||||
headers: {
|
||||
"Authorization": `Bearer ${Astro.cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)?.value}`
|
||||
}
|
||||
});
|
||||
const user = await getCurrentUser(Astro)
|
||||
|
||||
if (!user) {
|
||||
return Astro.redirect("/auth/login")
|
||||
@@ -27,6 +16,6 @@ if (user.rolle !== Enums.BenutzerRolle.ADMIN) {
|
||||
|
||||
---
|
||||
|
||||
<DashboardLayout title="Impersonate User" {user}>
|
||||
<DashboardLayout title="Impersonate User" {user} besteller={null}>
|
||||
<ImpersonateUserModule client:load></ImpersonateUserModule>
|
||||
</DashboardLayout>
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
import { Enums, prisma } from "#lib/server/prisma";
|
||||
import UserLayout from "#layouts/DashboardLayout.astro";
|
||||
import { getCurrentUser } from "#lib/server/user";
|
||||
import moment from "moment";
|
||||
|
||||
const page = Number(Astro.url.searchParams.get("p"));
|
||||
|
||||
@@ -38,6 +40,15 @@ if (user.rolle === Enums.BenutzerRolle.USER) {
|
||||
SELECT id, updated_at FROM "GEGNachweisGewerbe" WHERE benutzer_id = ${user.id}
|
||||
ORDER BY updated_at DESC LIMIT 1 OFFSET ${page - 1}`;
|
||||
} else {
|
||||
const date = moment().subtract(2, "hours").toDate()
|
||||
|
||||
// SELECT id, updated_at FROM "VerbrauchsausweisWohnen" WHERE created_at >= ${date} AND bestellt = ${true} UNION ALL
|
||||
// SELECT id, updated_at FROM "VerbrauchsausweisGewerbe" WHERE created_at >= ${date} AND bestellt = ${true} UNION ALL
|
||||
// SELECT id, updated_at FROM "BedarfsausweisWohnen" WHERE created_at >= ${date} AND bestellt = ${true} UNION ALL
|
||||
// SELECT id, updated_at FROM "BedarfsausweisGewerbe" WHERE created_at >= ${date} AND bestellt = ${true} UNION ALL
|
||||
// SELECT id, updated_at FROM "GEGNachweisWohnen" WHERE created_at >= ${date} AND bestellt = ${true} UNION ALL
|
||||
// SELECT id, updated_at FROM "GEGNachweisGewerbe" WHERE created_at >= ${date} AND bestellt = ${true}
|
||||
|
||||
result =
|
||||
await prisma.$queryRaw`SELECT id, updated_at FROM "VerbrauchsausweisWohnen" UNION ALL
|
||||
SELECT id, updated_at FROM "VerbrauchsausweisGewerbe" UNION ALL
|
||||
@@ -48,11 +59,11 @@ if (user.rolle === Enums.BenutzerRolle.USER) {
|
||||
ORDER BY updated_at DESC LIMIT 1 OFFSET ${page - 1}`;
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
return Astro.redirect("/dashboard/objekte?p=1");
|
||||
if (result.length > 0) {
|
||||
return Astro.redirect(`/dashboard/objekte/${result[0].id}?p=${page}`)
|
||||
}
|
||||
|
||||
return Astro.redirect(`/dashboard/objekte/${result[0].id}?p=${page}`)
|
||||
---
|
||||
|
||||
<script></script>
|
||||
<UserLayout title="Objekte" {user} besteller={null}>
|
||||
<p>Keine Ausweise konnten gefunden werden.</p>
|
||||
</UserLayout>
|
||||
|
||||
Reference in New Issue
Block a user