Ausweis und weitere Änderungen

This commit is contained in:
Moritz Utcke
2025-02-18 18:17:02 +11:00
parent 8486421f12
commit c8ba17fab3
38 changed files with 1771 additions and 330 deletions

View File

@@ -30,6 +30,7 @@
"js-interpolate": "^1.3.2", "js-interpolate": "^1.3.2",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
"mime-types": "^2.1.35",
"moment": "^2.30.1", "moment": "^2.30.1",
"moment-timezone": "^0.5.46", "moment-timezone": "^0.5.46",
"nodemailer": "^6.10.0", "nodemailer": "^6.10.0",
@@ -56,6 +57,7 @@
"@types/is-base64": "^1.1.3", "@types/is-base64": "^1.1.3",
"@types/js-cookie": "^3.0.6", "@types/js-cookie": "^3.0.6",
"@types/jsonwebtoken": "^9.0.7", "@types/jsonwebtoken": "^9.0.7",
"@types/mime-types": "^2.1.4",
"@types/nodemailer": "^6.4.17", "@types/nodemailer": "^6.4.17",
"@types/uuid": "^9.0.8", "@types/uuid": "^9.0.8",
"@typescript-eslint/eslint-plugin": "^5.62.0", "@typescript-eslint/eslint-plugin": "^5.62.0",
@@ -540,6 +542,8 @@
"@types/mime": ["@types/mime@1.3.5", "", {}, "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="], "@types/mime": ["@types/mime@1.3.5", "", {}, "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="],
"@types/mime-types": ["@types/mime-types@2.1.4", "", {}, "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w=="],
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
"@types/nlcst": ["@types/nlcst@2.0.3", "", { "dependencies": { "@types/unist": "*" } }, "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA=="], "@types/nlcst": ["@types/nlcst@2.0.3", "", { "dependencies": { "@types/unist": "*" } }, "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA=="],

View File

@@ -44,6 +44,7 @@
"js-interpolate": "^1.3.2", "js-interpolate": "^1.3.2",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
"mime-types": "^2.1.35",
"moment": "^2.30.1", "moment": "^2.30.1",
"moment-timezone": "^0.5.46", "moment-timezone": "^0.5.46",
"nodemailer": "^6.10.0", "nodemailer": "^6.10.0",
@@ -70,6 +71,7 @@
"@types/is-base64": "^1.1.3", "@types/is-base64": "^1.1.3",
"@types/js-cookie": "^3.0.6", "@types/js-cookie": "^3.0.6",
"@types/jsonwebtoken": "^9.0.7", "@types/jsonwebtoken": "^9.0.7",
"@types/mime-types": "^2.1.4",
"@types/nodemailer": "^6.4.17", "@types/nodemailer": "^6.4.17",
"@types/uuid": "^9.0.8", "@types/uuid": "^9.0.8",
"@typescript-eslint/eslint-plugin": "^5.62.0", "@typescript-eslint/eslint-plugin": "^5.62.0",

View File

@@ -5,22 +5,26 @@ export const createCaller = createCallerFactory({
"postleitzahlen": await import("../src/pages/api/postleitzahlen.ts"), "postleitzahlen": await import("../src/pages/api/postleitzahlen.ts"),
"aufnahme/[uid]": await import("../src/pages/api/aufnahme/[uid].ts"), "aufnahme/[uid]": await import("../src/pages/api/aufnahme/[uid].ts"),
"aufnahme": await import("../src/pages/api/aufnahme/index.ts"), "aufnahme": await import("../src/pages/api/aufnahme/index.ts"),
"admin/ausstellen": await import("../src/pages/api/admin/ausstellen.ts"),
"auth/access-token": await import("../src/pages/api/auth/access-token.ts"), "auth/access-token": await import("../src/pages/api/auth/access-token.ts"),
"auth/forgot-password": await import("../src/pages/api/auth/forgot-password.ts"), "auth/forgot-password": await import("../src/pages/api/auth/forgot-password.ts"),
"auth/refresh-token": await import("../src/pages/api/auth/refresh-token.ts"), "auth/refresh-token": await import("../src/pages/api/auth/refresh-token.ts"),
"bilder/[uid]": await import("../src/pages/api/bilder/[uid].ts"),
"bedarfsausweis-wohnen/[uid]": await import("../src/pages/api/bedarfsausweis-wohnen/[uid].ts"), "bedarfsausweis-wohnen/[uid]": await import("../src/pages/api/bedarfsausweis-wohnen/[uid].ts"),
"bedarfsausweis-wohnen": await import("../src/pages/api/bedarfsausweis-wohnen/index.ts"), "bedarfsausweis-wohnen": await import("../src/pages/api/bedarfsausweis-wohnen/index.ts"),
"geg-nachweis-verbrauchsausweis-wohnen/[uid]": await import("../src/pages/api/geg-nachweis-verbrauchsausweis-wohnen/[uid].ts"),
"geg-nachweis-verbrauchsausweis-wohnen": await import("../src/pages/api/geg-nachweis-verbrauchsausweis-wohnen/index.ts"),
"bilder/[uid]": await import("../src/pages/api/bilder/[uid].ts"),
"objekt": await import("../src/pages/api/objekt/index.ts"), "objekt": await import("../src/pages/api/objekt/index.ts"),
"rechnung": await import("../src/pages/api/rechnung/index.ts"),
"ticket": await import("../src/pages/api/ticket/index.ts"), "ticket": await import("../src/pages/api/ticket/index.ts"),
"rechnung": await import("../src/pages/api/rechnung/index.ts"),
"user": await import("../src/pages/api/user/index.ts"), "user": await import("../src/pages/api/user/index.ts"),
"user/self": await import("../src/pages/api/user/self.ts"), "user/self": await import("../src/pages/api/user/self.ts"),
"verbrauchsausweis-gewerbe/[uid]": await import("../src/pages/api/verbrauchsausweis-gewerbe/[uid].ts"), "verbrauchsausweis-gewerbe/[uid]": await import("../src/pages/api/verbrauchsausweis-gewerbe/[uid].ts"),
"verbrauchsausweis-gewerbe": await import("../src/pages/api/verbrauchsausweis-gewerbe/index.ts"), "verbrauchsausweis-gewerbe": await import("../src/pages/api/verbrauchsausweis-gewerbe/index.ts"),
"webhooks/mollie": await import("../src/pages/api/webhooks/mollie.ts"),
"verbrauchsausweis-wohnen/[uid]": await import("../src/pages/api/verbrauchsausweis-wohnen/[uid].ts"), "verbrauchsausweis-wohnen/[uid]": await import("../src/pages/api/verbrauchsausweis-wohnen/[uid].ts"),
"verbrauchsausweis-wohnen": await import("../src/pages/api/verbrauchsausweis-wohnen/index.ts"), "verbrauchsausweis-wohnen": await import("../src/pages/api/verbrauchsausweis-wohnen/index.ts"),
"webhooks/mollie": await import("../src/pages/api/webhooks/mollie.ts"),
"objekt/[uid]/bilder": await import("../src/pages/api/objekt/[uid]/bilder.ts"), "objekt/[uid]/bilder": await import("../src/pages/api/objekt/[uid]/bilder.ts"),
"objekt/[uid]": await import("../src/pages/api/objekt/[uid]/index.ts"), "objekt/[uid]": await import("../src/pages/api/objekt/[uid]/index.ts"),
"objekt/[uid]/unterlagen": await import("../src/pages/api/objekt/[uid]/unterlagen.ts"),
}) })

View File

@@ -72,7 +72,7 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
> >
<option disabled selected>Bitte auswählen</option> <option disabled selected>Bitte auswählen</option>
{#if ausweisart=="VerbrauchsausweisWohnen"} {#if ausweisart==Enums.Ausweisart.VerbrauchsausweisWohnen || ausweisart === Enums.Ausweisart.GEGNachweisVerbrauchsausweisWohnen}
<option value="Einfamilienhaus">Einfamilienhaus</option> <option value="Einfamilienhaus">Einfamilienhaus</option>
<option value="Freistehendes Einfamilienhaus">Freistehendes Einfamilienhaus</option> <option value="Freistehendes Einfamilienhaus">Freistehendes Einfamilienhaus</option>
<option value="Freistehendes Zweifamilienhaus">Freistehendes Zweifamilienhaus</option> <option value="Freistehendes Zweifamilienhaus">Freistehendes Zweifamilienhaus</option>
@@ -84,7 +84,7 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
<option value="Atrium-Bungalow">Atrium-Bungalow</option> <option value="Atrium-Bungalow">Atrium-Bungalow</option>
<option value="Winkelbungalow">Winkelbungalow</option> <option value="Winkelbungalow">Winkelbungalow</option>
{:else if ausweisart=="VerbrauchsausweisGewerbe"} {:else if ausweisart==Enums.Ausweisart.VerbrauchsausweisGewerbe}
<option value="Verwaltungsgebäude (allgemein)">Verwaltungsgebäude (allgemein)</option> <option value="Verwaltungsgebäude (allgemein)">Verwaltungsgebäude (allgemein)</option>
<option value="Parlaments- und Gerichtsgebäude">Parlaments- und Gerichtsgebäude</option> <option value="Parlaments- und Gerichtsgebäude">Parlaments- und Gerichtsgebäude</option>
<option value="Ministerien u. Ämter u. Behörden">Ministerien u. Ämter u. Behörden</option> <option value="Ministerien u. Ämter u. Behörden">Ministerien u. Ämter u. Behörden</option>
@@ -200,7 +200,7 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
className={auditHeizungGebaeudeBaujahr(aufnahme) className={auditHeizungGebaeudeBaujahr(aufnahme)
? "linked" ? "linked"
: ""} : ""}
bind:tags={aufnahme.baujahr_heizung} bind:tags={aufnahme.baujahr_klima}
/> />
<div class="help-label"> <div class="help-label">

View File

@@ -1,45 +1,49 @@
<script lang="ts"> <script lang="ts">
export let progress: number = 0; export let progress: number = 0;
export let step1: string = ''; export let steps: string[] = [
export let step2: string = ''; "Gebäudedaten",
export let step3: string = ''; "Kundendaten",
"Kaufbestätigung",
];
export let active: number;
</script> </script>
<div class="grid grid-cols-3 self-start justify-between">
<div class="grid grid-cols-3 self-start">
<div class="col-span-3 relative"> <div class="col-span-3 relative">
<div
<div class="w-[calc(100%-5rem)] ml-[2.5rem] absolute mt-[0.5rem] bg-gray-200 h-3 rounded-lg"> class="w-[calc(100%-5rem)] ml-[2.5rem] absolute mt-[0.5rem] bg-gray-200 h-3 rounded-lg"
<div class="bg-green-600 left-0 h-3 absolute" style={`width: ${progress}%;`}></div> >
<div
class="bg-green-600 left-0 h-3 absolute"
style={`width: ${progress}%;`}
></div>
</div>
</div> </div>
{#each steps as step, i}
<div class="phase">
<div class="point" class:active={i === active}>{i + 1}</div>
<div>{step}</div>
</div> </div>
{/each}
<div class="phase justify-self-start">
<div class="{step1} point">1</div>
<div class="">Gebäudedaten</div>
</div> </div>
<div class="phase">
<div class="{step2} point">2</div>
<div class="">Kundendaten</div>
</div>
<div class="phase justify-self-end">
<div class="{step3} point">3</div>
<div class="">Kaufbestätigung</div>
</div>
</div>
<style lang="postcss"> <style lang="postcss">
.phase{@apply grid grid-cols-1 items-center justify-items-center z-10; .phase {
.point{@apply rounded-full w-8 h-8 text-white font-bold bg-gray-300 text-center pt-1 ring-white ring-4;} @apply grid grid-cols-1 items-center justify-items-center z-10;
.step{@apply bg-secondary} .point {
} @apply rounded-full w-8 h-8 text-white font-bold bg-gray-300 text-center pt-1 ring-white ring-4;
}
.active {
@apply bg-secondary;
}
}
:nth-child(1 of .phase) {
@apply justify-self-start;
}
.phase:last-of-type {
@apply justify-self-end;
}
</style> </style>

View File

@@ -8,6 +8,7 @@ import {
Objekt, Objekt,
Rechnung, Rechnung,
Tickets, Tickets,
Unterlage,
VerbrauchsausweisGewerbe, VerbrauchsausweisGewerbe,
VerbrauchsausweisWohnen, VerbrauchsausweisWohnen,
} from "@ibcornelsen/database/client"; } from "@ibcornelsen/database/client";
@@ -148,3 +149,5 @@ export function getAusweisartFromUUID(uid: string): Enums.Ausweisart | null {
return null; return null;
} }
export type UnterlageClient = Omit<Unterlage, "id" | "objekt_id">

View File

@@ -13,6 +13,10 @@
import { CheckCircled, CrossCircled, Image } from "radix-svelte-icons"; import { CheckCircled, CrossCircled, Image } from "radix-svelte-icons";
import ChevronDown from "radix-svelte-icons/src/lib/icons/ChevronDown.svelte"; import ChevronDown from "radix-svelte-icons/src/lib/icons/ChevronDown.svelte";
import { Event } from "@ibcornelsen/database/client"; import { Event } from "@ibcornelsen/database/client";
import { api } from "astro-typesafe-api/client";
import Cookies from "js-cookie";
import { API_ACCESS_TOKEN_COOKIE_NAME } from "#lib/constants.js";
import NotificationWrapper from "./Notifications/NotificationWrapper.svelte";
export let ausweis: VerbrauchsausweisWohnenClient; export let ausweis: VerbrauchsausweisWohnenClient;
@@ -24,6 +28,25 @@
ReturnType<typeof endEnergieVerbrauchVerbrauchsausweis_2016> ReturnType<typeof endEnergieVerbrauchVerbrauchsausweis_2016>
>; >;
async function ausweisAusstellen(uid: string) {
try {
await api.admin.ausstellen.GET.fetch({
uid
}, {
headers: {
"Authorization": `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`
}
})
} catch(e) {
addNotification({
title: "Das hat nicht geklappt.",
description: e.cause.statusText,
timeout: 3000,
type: "error",
})
}
}
const ausweisArt = getAusweisartFromUUID(ausweis.uid) // TODO: Das ist ein Platzhalter, hier muss die Ausweisart aus dem Ausweisobjekt kommen const ausweisArt = getAusweisartFromUUID(ausweis.uid) // TODO: Das ist ein Platzhalter, hier muss die Ausweisart aus dem Ausweisobjekt kommen
@@ -449,8 +472,8 @@
let infoVisible = false; let infoVisible = false;
</script> </script>
<div class="border rounded-box"> <div class="border rounded-lg bg-base-200">
<table class="table table-row"> <table class="">
<tbody> <tbody>
<tr> <tr>
<td><button on:click={() => infoVisible = !infoVisible}><ChevronDown size={22} class="transition-all {infoVisible ? "" : "rotate-180"}"></ChevronDown></button></td> <td><button on:click={() => infoVisible = !infoVisible}><ChevronDown size={22} class="transition-all {infoVisible ? "" : "rotate-180"}"></ChevronDown></button></td>
@@ -676,12 +699,12 @@
<td title="Gebäudebilder anzeigen" <td title="Gebäudebilder anzeigen"
><!-- Open the modal using ID.showModal() method --> ><!-- Open the modal using ID.showModal() method -->
<button class="btn btn-square" on:click={() => bilderModal.showModal()}><Image size={22}></Image></button> <button class="btn btn-square" on:click={() => bilderModal.showModal()}><Image size={22}></Image></button>
<dialog bind:this={bilderModal} class="modal"> <dialog bind:this={bilderModal} class="modal p-4 rounded-lg">
<div class="modal-box flex flex-row gap-4 items-center justify-center"> <div class="modal-box flex flex-row gap-4 items-center justify-center">
{#if bilder.length === 0} {#if bilder.length === 0}
<div class="flex flex-col gap-4 items-center justify-center"> <div class="flex flex-col gap-4 items-center justify-center">
<p>Für diesen Ausweis sind noch keine Bilder vorhanden.</p> <p>Für diesen Ausweis sind noch keine Bilder vorhanden.</p>
<button class="btn btn-primary" tabindex="0">Erinnerung Verschicken</button> <button class="button" tabindex="0">Erinnerung Verschicken</button>
</div> </div>
{:else} {:else}
{#each bilder as image} {#each bilder as image}
@@ -693,7 +716,7 @@
{/if} {/if}
</div> </div>
<form method="dialog" class="modal-backdrop"> <form method="dialog" class="modal-backdrop">
<button>close</button> <button class="button">Close</button>
</form> </form>
</dialog></td </dialog></td
> >
@@ -874,3 +897,5 @@
</div> </div>
</div> </div>
</div> </div>
<NotificationWrapper></NotificationWrapper>

View File

@@ -2,11 +2,12 @@
</script> </script>
<div class="dropdown dropdown-hover dropdown-bottom"> <div class="relative group border-r px-2">
<div role="button" tabindex="0"> <div>
<slot></slot> <slot></slot>
</div> </div>
<div class="dropdown-content bg-gray-700 text-white z-10 menu w-max shadow rounded-lg">
<div class="absolute right-[50%] translate-x-[50%] z-10 mt-2 p-2 w-max origin-top-right rounded-md bg-gray-700 text-white ring-1 shadow-lg ring-black/5 focus:outline-hidden bg-grey-200 hidden group-hover:block">
<slot name="tooltip"></slot> <slot name="tooltip"></slot>
</div> </div>
</div> </div>

View File

@@ -0,0 +1,133 @@
<script lang="ts">
import { BedarfsausweisWohnenClient, ObjektClient, UnterlageClient, VerbrauchsausweisGewerbeClient, VerbrauchsausweisWohnenClient } from "./Ausweis/types.js";
import { Trash, Upload } from "radix-svelte-icons";
import HelpLabel from "#components/labels/HelpLabel.svelte";
export let kategorie: string = "";
export let files: (UnterlageClient & { data: string | ArrayBuffer })[] = [];
export let max: number = Infinity;
export let min: number = 1;
export let name: string = "";
export let ausweis: VerbrauchsausweisWohnenClient | VerbrauchsausweisGewerbeClient | BedarfsausweisWohnenClient;
export let objekt: ObjektClient;
import mime from "mime-types";
function getAllFiles(this: HTMLInputElement) {
const fileArray = this.files || [];
if (fileArray.length == max) {
this.value = "";
return;
}
for (let i = 0; i < fileArray.length; i++) {
const file = fileArray[i];
if (i == max) {
break;
}
const reader = new FileReader();
reader.onload = () => {
if (reader.readyState != reader.DONE) {
return;
}
if (!reader.result) {
return;
}
const mimeType = mime.types[file.name.split(".").pop() as string]
files.push({ data: reader.result, kategorie, name: file.name, mime: mimeType });
files = files;
if (i == (Math.min(fileArray.length, max) - 1)) {
this.value = "";
}
}
reader.readAsArrayBuffer(file);
}
}
let fileUpload: HTMLInputElement;
export const upload = () => {
fileUpload.click()
}
</script>
<div class="flex flex-col gap-4">
<!-- Falls die maximale Anzahl Dokumente erreicht wurde grauen wir den input aus und zeigen einen Hilfstext -->
{#if files.filter((file) => file.kategorie === kategorie).length === max}
<span class="bg-base-200 border px-4 py-2">Maximale Anzahl Dokumente wurde erreicht.</span>
{:else if max > 1}
<div class="input-standard">
<input type="file" class="file-input file-input-ghost h-[38px]" bind:this={fileUpload} {name} multiple on:change={getAllFiles} />
<div class="help-label">
<HelpLabel>
<slot />
</HelpLabel>
</div>
</div>
{:else}
<div class="input-standard">
<input type="file" class="file-input file-input-ghost h-[38px]" bind:this={fileUpload} {name} on:change={getAllFiles} />
<div class="help-label">
<HelpLabel>
<slot />
</HelpLabel>
</div>
</div>
{/if}
<div class="grid grid-cols-2 gap-2">
{#each files as file, i}
{#if file.kategorie == kategorie}
<div class="relative group">
<div
class="h-full max-h-96 w-full rounded-lg border-2 group-hover:contrast-50 object-cover transition-all text-center items-center flex p-4 text-opacity-75 text-black"
>{file.name}</div>
<div class="invisible group-hover:visible absolute left-[50%] top-[50%] translate-x-[-50%] translate-y-[-50%] flex flex-row gap-2">
<button
type="button"
class="rounded-full w-[30px] h-[30px] flex items-center justify-center p-0 bg-[rgba(0,0,0,0.4)]"
on:click={() => {
delete files[i];
files = files.filter((x) => x);
}}
>
<Trash size={20} color="#fff"></Trash>
</button>
</div>
</div>
{/if}
{/each}
<!-- Wir zeigen Platzhalter an, damit der Nutzer sieht wie viele Bilder er hochladen soll -->
{#each { length: Math.max(0, Math.min(max, 4) - files.filter(image => image.kategorie === kategorie).length) } as _, i}
<div class="relative group">
<img
src="/placeholder.png"
alt={kategorie}
class="h-full max-h-32 w-full rounded-lg border-2 group-hover:contrast-50 object-cover transition-all"
class:opacity-35={i >= min}
/>
<div class="invisible group-hover:visible absolute left-[50%] top-[50%] translate-x-[-50%] translate-y-[-50%] flex flex-row gap-2">
<button
type="button"
class="rounded-full w-[30px] h-[30px] flex items-center justify-center p-0 bg-[rgba(0,0,0,0.4)]"
on:click={upload}
>
<Upload size={20} color="#fff"></Upload>
</button>
</div>
</div>
{/each}
</div>
</div>

View File

@@ -5,7 +5,7 @@
import { RotateCounterClockwise, Trash, Upload } from "radix-svelte-icons"; import { RotateCounterClockwise, Trash, Upload } from "radix-svelte-icons";
export let images: UploadedGebaeudeBild[] = []; export let images: UploadedGebaeudeBild[] = [];
export let max: number = 4; export let max: number = Infinity;
export let min: number = 1; export let min: number = 1;
export let name: string = ""; export let name: string = "";
export let ausweis: VerbrauchsausweisWohnenClient | VerbrauchsausweisGewerbeClient | BedarfsausweisWohnenClient; export let ausweis: VerbrauchsausweisWohnenClient | VerbrauchsausweisGewerbeClient | BedarfsausweisWohnenClient;
@@ -74,7 +74,7 @@
{/each} {/each}
<!-- Wir zeigen Platzhalter an, damit der Nutzer sieht wie viele Bilder er hochladen soll --> <!-- Wir zeigen Platzhalter an, damit der Nutzer sieht wie viele Bilder er hochladen soll -->
{#each { length: max - images.filter(image => image.kategorie === kategorie).length } as _, i} {#each { length: Math.max(0, Math.min(max, 4) - images.filter(image => image.kategorie === kategorie).length) } as _, i}
<div class="relative group"> <div class="relative group">
<img <img
src="/placeholder.png" src="/placeholder.png"

View File

@@ -1,7 +1,7 @@
--- ---
import "../style/global.css"; import "../style/global.css";
import "../style/formular.css"; import "../style/formular.css";
import "../../svelte-dialogs.config" import "../../svelte-dialogs.config";
import Header from "#components/design/header/AusweisHeader.astro"; import Header from "#components/design/header/AusweisHeader.astro";
import Footer from "#components/design/footer/Footer.astro"; import Footer from "#components/design/footer/Footer.astro";
import SidebarLeft from "#components/design/sidebars/SidebarLeft.astro"; import SidebarLeft from "#components/design/sidebars/SidebarLeft.astro";
@@ -13,52 +13,59 @@ export interface Props {
const { title } = Astro.props; const { title } = Astro.props;
--- ---
<script>
window.addEventListener("scroll", (event) => {
let scroll = window.scrollY;
const skala = document.getElementById("skala")
if(scroll>=400){ <script>
const skala = document.getElementById("skala");
if (!skala?.classList.contains("no-scroll")) {
window.addEventListener("scroll", (event) => {
let scroll = window.scrollY;
if (scroll >= 400) {
if (skala) { if (skala) {
skala.classList.add('2xl:fixed','2xl:py-4','2xl:top-0','2xl:z-20'); skala.classList.add(
skala.classList.remove('w-full'); "2xl:fixed",
"2xl:py-4",
"2xl:top-0",
"2xl:z-20"
);
skala.classList.remove("w-full");
skala.style.borderBottom = "3px solid #e6e6e6"; skala.style.borderBottom = "3px solid #e6e6e6";
} }
const performanceBox =
const performanceBox = document.getElementById('performance-box') document.getElementById("performance-box");
if (performanceBox) { if (performanceBox) {
performanceBox.style.maxWidth = "688.5px"; performanceBox.style.maxWidth = "688.5px";
} }
const progressBox = document.getElementById('progress-box'); const progressBox = document.getElementById("progress-box");
if (progressBox) { if (progressBox) {
progressBox.style.maxWidth = "688.5px" progressBox.style.maxWidth = "688.5px";
} }
document.getElementById('formInput-1')?.classList.add('2xl:mt-[370px]'); document
.getElementById("formInput-1")
?.classList.add("2xl:mt-[370px]");
} else {
}else{
if (skala) { if (skala) {
skala.classList.remove('2xl:fixed','2xl:py-4','2xl:top-0','2xl:z-20') skala.classList.remove(
skala.classList.add('w-full') "2xl:fixed",
"2xl:py-4",
"2xl:top-0",
"2xl:z-20"
);
skala.classList.add("w-full");
skala.style.borderBottom = "none"; skala.style.borderBottom = "none";
} }
document
document.getElementById('formInput-1')?.classList.remove('2xl:mt-[370px]'); .getElementById("formInput-1")
?.classList.remove("2xl:mt-[370px]");
}
});
} }
});
</script> </script>
<!DOCTYPE html>
<html lang="de"> <html lang="de">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
@@ -82,7 +89,10 @@ window.addEventListener("scroll", (event) => {
content="✅ Jetzt Ihren Energieausweis online erstellen. Erhalten Sie Ihren online Energieausweis rechtssicher und nach aktueller GEG (vormals EnEV) vom Diplom Ingenieur geprüft." content="✅ Jetzt Ihren Energieausweis online erstellen. Erhalten Sie Ihren online Energieausweis rechtssicher und nach aktueller GEG (vormals EnEV) vom Diplom Ingenieur geprüft."
/> />
<meta property="og:url" content="https://online-energieausweis.org/" /> <meta property="og:url" content="https://online-energieausweis.org/" />
<meta property="og:site_name" content="Energieausweis online erstellen" /> <meta
property="og:site_name"
content="Energieausweis online erstellen"
/>
<meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:card" content="summary_large_image" />
<meta <meta
@@ -98,12 +108,11 @@ window.addEventListener("scroll", (event) => {
content="https://online-energieausweis.org/images/energieausweis-online-erstellen.jpg" content="https://online-energieausweis.org/images/energieausweis-online-erstellen.jpg"
/> />
<title> <title>
{title || 'Energieausweis online erstellen - Online Energieausweis'} {title || "Energieausweis online erstellen - Online Energieausweis"}
</title> </title>
</head> </head>
<body> <body>
<Header /> <Header />
<main <main
@@ -113,20 +122,17 @@ window.addEventListener("scroll", (event) => {
md:grid-cols-[minmax(1fr,1fr)] md:gap-2 md:p-0 md:grid-cols-[minmax(1fr,1fr)] md:gap-2 md:p-0
lg:grid-cols-[minmax(250px,250px)1fr] lg:gap-3 lg:p-4 lg:grid-cols-[minmax(250px,250px)1fr] lg:gap-3 lg:p-4
xl:grid-cols-[minmax(350px,350px)1fr] xl:gap-4 xl:p-6 xl:grid-cols-[minmax(350px,350px)1fr] xl:gap-4 xl:p-6
2xl:grid-cols-[minmax(300px,300px)1fr] 2xl:gap-5 2xl:p-6 2xl:grid-cols-[minmax(300px,300px)1fr] 2xl:gap-5 2xl:p-6"
"> >
<SidebarLeft /> <SidebarLeft />
<article class="box rounded-tl-none p-2 lg:p-12"> <article class="box rounded-tl-none p-2 lg:p-12">
<slot /> <slot />
</article> </article>
</main> </main>
<Footer /> <Footer />
<NotificationWrapper client:load /> <NotificationWrapper client:load />
</body> </body>
</html> </html>

View File

@@ -98,24 +98,17 @@ export async function endEnergieVerbrauchVerbrauchsausweisGewerbe_2016(ausweis:
// Endenergieverbrauch // Endenergieverbrauch
// Um den EEV auszurechnen, müssen die Verbräuche zu kWh konvertiert werden. // Um den EEV auszurechnen, müssen die Verbräuche zu kWh konvertiert werden.
let umrechnungsfaktor = 1, primaerfaktor = 1, coe = 1; let brennstoff_1 = { coe: 0, energietraeger: "", einheit: "", umrechnungsfaktor: 0, primaerenergiefaktor: 0 }, brennstoff_2 = { coe: 0, energietraeger: "", einheit: "", umrechnungsfaktor: 0, primaerenergiefaktor: 0 };
let umrechnungsfaktor_1 = 1, primaerfaktor_1 = 1, coe_1 = 1;
if (ausweis.brennstoff_1 && ausweis.einheit_1) { if (ausweis.brennstoff_1 && ausweis.einheit_1) {
let result = getHeizwertfaktor(ausweis.brennstoff_1, ausweis.einheit_1); brennstoff_1 = getHeizwertfaktor(ausweis.brennstoff_1, ausweis.einheit_1);
umrechnungsfaktor = result.umrechnungsfaktor
primaerfaktor = result.primaerenergiefaktor
coe = result.coe
} }
if (ausweis.brennstoff_2 && ausweis.einheit_2) { if (ausweis.brennstoff_2 && ausweis.einheit_2) {
let result = getHeizwertfaktor(ausweis.brennstoff_2, ausweis.einheit_2); brennstoff_2 = getHeizwertfaktor(ausweis.brennstoff_2, ausweis.einheit_2);
umrechnungsfaktor_1 = result.umrechnungsfaktor
primaerfaktor_1 = result.primaerenergiefaktor
coe_1 = result.coe
} }
let energieVerbrauchGesamt_1 = ((ausweis.verbrauch_1 || 0) + (ausweis.verbrauch_2 || 0) + (ausweis.verbrauch_3 || 0)) * umrechnungsfaktor; let energieVerbrauchGesamt_1 = ((ausweis.verbrauch_1 || 0) + (ausweis.verbrauch_2 || 0) + (ausweis.verbrauch_3 || 0)) * brennstoff_1?.umrechnungsfaktor;
let energieVerbrauchGesamt_2 = ((ausweis.verbrauch_4 || 0) + (ausweis.verbrauch_5 || 0) + (ausweis.verbrauch_6 || 0)) * umrechnungsfaktor_1; let energieVerbrauchGesamt_2 = ((ausweis.verbrauch_4 || 0) + (ausweis.verbrauch_5 || 0) + (ausweis.verbrauch_6 || 0)) * brennstoff_2?.umrechnungsfaktor;
let energieVerbrauchWarmwasser_1 = 0; let energieVerbrauchWarmwasser_1 = 0;
let energieVerbrauchWarmwasser_2 = 0; let energieVerbrauchWarmwasser_2 = 0;
@@ -180,7 +173,7 @@ export async function endEnergieVerbrauchVerbrauchsausweisGewerbe_2016(ausweis:
let endEnergieVerbrauch_1 = (energieVerbrauchHeizungBereinigt_1 + energieVerbrauchWarmwasser_1 + kuehlungsZuschlag_1) / (3 * nutzflaeche); let endEnergieVerbrauch_1 = (energieVerbrauchHeizungBereinigt_1 + energieVerbrauchWarmwasser_1 + kuehlungsZuschlag_1) / (3 * nutzflaeche);
let endEnergieVerbrauch_2 = (energieVerbrauchHeizungBereinigt_2 + energieVerbrauchWarmwasser_2 + kuehlungsZuschlag_2) / (3 * nutzflaeche); let endEnergieVerbrauch_2 = (energieVerbrauchHeizungBereinigt_2 + energieVerbrauchWarmwasser_2 + kuehlungsZuschlag_2) / (3 * nutzflaeche);
let energieVerbrauchStrom = ausweis.strom_1 + ausweis.strom_2 + ausweis.strom_3; let energieVerbrauchStrom = (ausweis.strom_1 || 0) + (ausweis.strom_2 || 0) + (ausweis.strom_3 || 0);
let leerstandsZuschlagStrom = leerstand * energieVerbrauchStrom; let leerstandsZuschlagStrom = leerstand * energieVerbrauchStrom;
let endEnergieVerbrauchStrom = (energieVerbrauchStrom + leerstandsZuschlagStrom) / (3 * nutzflaeche); let endEnergieVerbrauchStrom = (energieVerbrauchStrom + leerstandsZuschlagStrom) / (3 * nutzflaeche);
@@ -192,19 +185,19 @@ export async function endEnergieVerbrauchVerbrauchsausweisGewerbe_2016(ausweis:
let endEnergieVerbrauchKuehlungsZuschlag_1 = kuehlungsZuschlag_1 / (3 * nutzflaeche); let endEnergieVerbrauchKuehlungsZuschlag_1 = kuehlungsZuschlag_1 / (3 * nutzflaeche);
let endEnergieVerbrauchKuehlungsZuschlag_2 = kuehlungsZuschlag_2 / (3 * nutzflaeche); let endEnergieVerbrauchKuehlungsZuschlag_2 = kuehlungsZuschlag_2 / (3 * nutzflaeche);
let primaerEnergieVerbrauch_1 = endEnergieVerbrauch_1 * primaerfaktor; let primaerEnergieVerbrauch_1 = endEnergieVerbrauch_1 * brennstoff_1.primaerenergiefaktor;
let primaerEnergieVerbrauch_2 = endEnergieVerbrauch_2 * primaerfaktor_1; let primaerEnergieVerbrauch_2 = endEnergieVerbrauch_2 * brennstoff_2.primaerenergiefaktor;
let primaerEnergieVerbrauchLeerstandsZuschlag = endEnergieVerbrauchLeerstandsZuschlag * primaerfaktor; let primaerEnergieVerbrauchLeerstandsZuschlag = endEnergieVerbrauchLeerstandsZuschlag * brennstoff_1.primaerenergiefaktor;
let primaerEnergieVerbrauchKuehlungsZuschlag_1 = endEnergieVerbrauchKuehlungsZuschlag_1 * primaerfaktor; let primaerEnergieVerbrauchKuehlungsZuschlag_1 = endEnergieVerbrauchKuehlungsZuschlag_1 * brennstoff_1.primaerenergiefaktor;
let primaerEnergieVerbrauchKuehlungsZuschlag_2 = endEnergieVerbrauchKuehlungsZuschlag_2 * primaerfaktor_1; let primaerEnergieVerbrauchKuehlungsZuschlag_2 = endEnergieVerbrauchKuehlungsZuschlag_2 * brennstoff_2.primaerenergiefaktor;
let co2Emissionen_1 = endEnergieVerbrauch_1 * coe; let co2Emissionen_1 = endEnergieVerbrauch_1 * brennstoff_1.coe;
let co2Emissionen_2 = endEnergieVerbrauch_2 * coe_1; let co2Emissionen_2 = endEnergieVerbrauch_2 * brennstoff_2.coe;
let co2EmissionenLeerstandsZuschlag = endEnergieVerbrauchLeerstandsZuschlag * coe; let co2EmissionenLeerstandsZuschlag = endEnergieVerbrauchLeerstandsZuschlag * brennstoff_1.coe;
let co2EmissionenKuehlungsZuschlag_1 = endEnergieVerbrauchKuehlungsZuschlag_1 * coe; let co2EmissionenKuehlungsZuschlag_1 = endEnergieVerbrauchKuehlungsZuschlag_1 * brennstoff_1.coe;
let co2EmissionenKuehlungsZuschlag_2 = endEnergieVerbrauchKuehlungsZuschlag_2 * coe_1; let co2EmissionenKuehlungsZuschlag_2 = endEnergieVerbrauchKuehlungsZuschlag_2 * brennstoff_2.coe;
let endEnergieVerbrauchGesamt = endEnergieVerbrauch_1 + endEnergieVerbrauch_2 + endEnergieVerbrauchLeerstandsZuschlag; let endEnergieVerbrauchGesamt = endEnergieVerbrauch_1 + endEnergieVerbrauch_2 + endEnergieVerbrauchLeerstandsZuschlag;
let primaerEnergieVerbrauchGesamt = primaerEnergieVerbrauch_1 + primaerEnergieVerbrauch_2 + primaerEnergieVerbrauchLeerstandsZuschlag + primaerEnergieVerbrauchStrom; let primaerEnergieVerbrauchGesamt = primaerEnergieVerbrauch_1 + primaerEnergieVerbrauch_2 + primaerEnergieVerbrauchLeerstandsZuschlag + primaerEnergieVerbrauchStrom;
@@ -214,26 +207,22 @@ export async function endEnergieVerbrauchVerbrauchsausweisGewerbe_2016(ausweis:
// Return all the steps that this function includes for debugging as a json object // Return all the steps that this function includes for debugging as a json object
return { return {
umrechnungsfaktor : umrechnungsfaktor, brennstoff_1,
primaerfaktor : primaerfaktor, brennstoff_2,
coe : coe,
umrechnungsfaktor_1 : umrechnungsfaktor_1,
primaerfaktor_1 : primaerfaktor_1,
coe_1 : coe_1,
kuehlungsZuschlag_1 : Math.round(kuehlungsZuschlag_1), kuehlungsZuschlag_1 : Math.round(kuehlungsZuschlag_1),
kuehlungsZuschlag_2: Math.round(kuehlungsZuschlag_2), kuehlungsZuschlag_2: Math.round(kuehlungsZuschlag_2),
durchschnittsKlimafaktor : Math.round(durchschnittsKlimafaktor,2), durchschnittsKlimafaktor : Math.round(durchschnittsKlimafaktor * 100) / 100,
Klimafaktor_1 : klimafaktoren[0], Klimafaktor_1 : klimafaktoren[0],
Klimafaktor_2 : klimafaktoren[1], Klimafaktor_2 : klimafaktoren[1],
Klimafaktor_3 : klimafaktoren[2], Klimafaktor_3 : klimafaktoren[2],
anteil_heizung_1: 1 - ((ausweis.anteil_warmwasser_1 || 0) / 100), anteil_heizung_1: 1 - ((ausweis.anteil_warmwasser_1 || 0) / 100),
anteil_heizung_2: 1 - ((ausweis.anteil_warmwasser_2 || 0) / 100), anteil_heizung_2: 1 - ((ausweis.anteil_warmwasser_2 || 0) / 100),
verbrauch_1_kwh: ausweis.verbrauch_1 * umrechnungsfaktor, verbrauch_1_kwh: (ausweis.verbrauch_1 || 0) * brennstoff_1.umrechnungsfaktor,
verbrauch_2_kwh: ausweis.verbrauch_2 * umrechnungsfaktor, verbrauch_2_kwh: (ausweis.verbrauch_2 || 0) * brennstoff_1.umrechnungsfaktor,
verbrauch_3_kwh: ausweis.verbrauch_3 * umrechnungsfaktor, verbrauch_3_kwh: (ausweis.verbrauch_3 || 0) * brennstoff_1.umrechnungsfaktor,
verbrauch_4_kwh: ausweis.verbrauch_4 * umrechnungsfaktor_1, verbrauch_4_kwh: (ausweis.verbrauch_4 || 0) * brennstoff_2.umrechnungsfaktor,
verbrauch_5_kwh: ausweis.verbrauch_5 * umrechnungsfaktor_1, verbrauch_5_kwh: (ausweis.verbrauch_5 || 0) * brennstoff_2.umrechnungsfaktor,
verbrauch_6_kwh: ausweis.verbrauch_6 * umrechnungsfaktor_1, verbrauch_6_kwh: (ausweis.verbrauch_6 || 0) * brennstoff_2.umrechnungsfaktor,
klimafaktoren : klimafaktoren, klimafaktoren : klimafaktoren,
nutzflaeche : Math.round(nutzflaeche), nutzflaeche : Math.round(nutzflaeche),
leerstand : leerstand, leerstand : leerstand,
@@ -250,12 +239,12 @@ export async function endEnergieVerbrauchVerbrauchsausweisGewerbe_2016(ausweis:
energieVerbrauchWarmwasser_2: Math.round(energieVerbrauchWarmwasser_2), energieVerbrauchWarmwasser_2: Math.round(energieVerbrauchWarmwasser_2),
energieVerbrauchHeizung_1: energieVerbrauchHeizung_1, energieVerbrauchHeizung_1: energieVerbrauchHeizung_1,
energieVerbrauchHeizung_2: energieVerbrauchHeizung_2, energieVerbrauchHeizung_2: energieVerbrauchHeizung_2,
anteil_warmwasser_1: ausweis.anteil_warmwasser_1 / 100, anteil_warmwasser_1: (ausweis.anteil_warmwasser_1 || 0) / 100,
anteil_warmwasser_2: ausweis.anteil_warmwasser_2 / 100, anteil_warmwasser_2: (ausweis.anteil_warmwasser_2 || 0) / 100,
energieVerbrauchHeizungBereinigt_1: Math.round(energieVerbrauchHeizungBereinigt_1), energieVerbrauchHeizungBereinigt_1: Math.round(energieVerbrauchHeizungBereinigt_1),
energieVerbrauchHeizungBereinigt_2: Math.round(energieVerbrauchHeizungBereinigt_2), energieVerbrauchHeizungBereinigt_2: Math.round(energieVerbrauchHeizungBereinigt_2),
durchschnittsEnergieVerbrauchHeizungBereingt: Math.round(durchschnittsEnergieVerbrauchHeizungBereingt), durchschnittsEnergieVerbrauchHeizungBereingt: Math.round(durchschnittsEnergieVerbrauchHeizungBereingt),
faktorDurchschnittsEnergieVerbrauchHeizungBereinigt: Math.round(faktorDurchschnittsEnergieVerbrauchHeizungBereinigt,3), faktorDurchschnittsEnergieVerbrauchHeizungBereinigt: Math.round(faktorDurchschnittsEnergieVerbrauchHeizungBereinigt * 1000) / 1000,
endEnergieVerbrauch_1: Math.round(endEnergieVerbrauch_1), endEnergieVerbrauch_1: Math.round(endEnergieVerbrauch_1),
endEnergieVerbrauch_2: Math.round(endEnergieVerbrauch_2), endEnergieVerbrauch_2: Math.round(endEnergieVerbrauch_2),
energieVerbrauchStrom: energieVerbrauchStrom, energieVerbrauchStrom: energieVerbrauchStrom,

View File

@@ -1,8 +1,8 @@
import { AufnahmeClient, ObjektClient, VerbrauchsausweisWohnenClient } from "#components/Ausweis/types.js"; import { AufnahmeClient, BedarfsausweisWohnenClient, ObjektClient, VerbrauchsausweisGewerbeClient, VerbrauchsausweisWohnenClient } from "#components/Ausweis/types.js";
import { Enums } from "@ibcornelsen/database/client"; import { Enums } from "@ibcornelsen/database/client";
import moment from "moment"; import moment from "moment";
export function getEmpfehlungen(ausweis: VerbrauchsausweisWohnenClient, aufnahme: AufnahmeClient, objekt: ObjektClient): { export function getEmpfehlungen(ausweis: VerbrauchsausweisWohnenClient | VerbrauchsausweisGewerbeClient | BedarfsausweisWohnenClient, aufnahme: AufnahmeClient, objekt: ObjektClient): {
title: string, title: string,
description: string, description: string,
anlagenteil: string, anlagenteil: string,

View File

@@ -60,3 +60,5 @@ export const SERVICES: Record<
Telefonberatung: 0, Telefonberatung: 0,
}, },
}; };
export const BASE_URI = process.env.NODE_ENV == "production" ? "https://online-energieausweis.org" : "http://localhost:3000";

11
src/lib/mail.ts Normal file
View File

@@ -0,0 +1,11 @@
import * as nodemailer from "nodemailer";
export const transport = nodemailer.createTransport({
host: "smtp.ionos.de",
port: 465,
secure: true,
auth: {
user: "info@online-energieausweis.org",
pass: "Katendeich5a2024!",
},
});

View File

@@ -92,3 +92,19 @@ export async function maybeAuthorizationMiddleware(input: any, ctx: TypesafeAPIC
export const authorizationHeaders = { export const authorizationHeaders = {
Authorization: z.string() Authorization: z.string()
} }
export async function adminMiddleware(input: any, ctx: TypesafeAPIContextWithRequest<any>) {
try {
const user = await authorizationMiddleware(input, ctx)
if (user.rolle === "ADMIN") {
return user
}
} catch(e) {
}
throw new APIError({
code: "FORBIDDEN",
"message": "Diese Route ist für Admins vorbehalten."
})
}

View File

@@ -1,13 +1,13 @@
import { AufnahmeClient, BenutzerClient, ObjektClient, UploadedGebaeudeBild, VerbrauchsausweisWohnenClient } from "#components/Ausweis/types.js"; import { AufnahmeClient, BenutzerClient, ObjektClient, UploadedGebaeudeBild, VerbrauchsausweisGewerbeClient, VerbrauchsausweisWohnenClient } from "#components/Ausweis/types.js";
import { endEnergieVerbrauchVerbrauchsausweisGewerbe_2016 } from "#lib/Berechnungen/VerbrauchsausweisGewerbe/VerbrauchsausweisGewerbe_2016.js"; import { endEnergieVerbrauchVerbrauchsausweisGewerbe_2016 } from "#lib/Berechnungen/VerbrauchsausweisGewerbe/VerbrauchsausweisGewerbe_2016.js";
import { getEmpfehlungen } from "#lib/XML/getEmpfehlungen.js"; import { getEmpfehlungen } from "#lib/XML/getEmpfehlungen.js";
import { Enums } from "@ibcornelsen/database/server"; import { Enums } from "@ibcornelsen/database/server";
import * as fs from "fs" import * as fs from "fs"
import moment from "moment"; import moment from "moment";
import { PDFDocument, PDFFont, PDFName, PDFNumber, PDFPage, StandardFonts, TextAlignment } from "pdf-lib"; import { PDFDocument, PDFFont, PDFImage, PDFName, PDFNumber, PDFPage, StandardFonts, TextAlignment } from "pdf-lib";
export async function pdfVerbrauchsausweisGewerbe(ausweis: VerbrauchsausweisWohnenClient, aufnahme: AufnahmeClient, objekt: ObjektClient, bilder: UploadedGebaeudeBild[], user: BenutzerClient) { export async function pdfVerbrauchsausweisGewerbe(ausweis: VerbrauchsausweisGewerbeClient, aufnahme: AufnahmeClient, objekt: ObjektClient, bilder: UploadedGebaeudeBild[], user: BenutzerClient) {
const VerbrauchsausweisWohnenGEG2024PDF = fs.readFileSync(new URL("./templates/GEG24_Nichtwohngebaeude.pdf", import.meta.url), "base64"); const VerbrauchsausweisWohnenGEG2024PDF = fs.readFileSync(new URL("./templates/GEG24_Nichtwohngebaeude.pdf", import.meta.url), "base64");
const pdf = await PDFDocument.load(VerbrauchsausweisWohnenGEG2024PDF) const pdf = await PDFDocument.load(VerbrauchsausweisWohnenGEG2024PDF)
const pages = pdf.getPages() const pages = pdf.getPages()
@@ -15,7 +15,6 @@ export async function pdfVerbrauchsausweisGewerbe(ausweis: VerbrauchsausweisWohn
// const template = VerbrauchsausweisWohnen2016Template as Template; // const template = VerbrauchsausweisWohnen2016Template as Template;
const berechnungen = await endEnergieVerbrauchVerbrauchsausweisGewerbe_2016(ausweis, aufnahme, objekt); const berechnungen = await endEnergieVerbrauchVerbrauchsausweisGewerbe_2016(ausweis, aufnahme, objekt);
console.log(berechnungen);
const empfehlungen = getEmpfehlungen(ausweis, aufnahme, objekt) const empfehlungen = getEmpfehlungen(ausweis, aufnahme, objekt)
@@ -44,6 +43,50 @@ export async function pdfVerbrauchsausweisGewerbe(ausweis: VerbrauchsausweisWohn
} }
} }
pages[0].drawText(aufnahme.gebaeudetyp || "", {
x: 181,
y: height - 191,
size: 10
})
pages[0].drawText(objekt.adresse || "", {
x: 181,
y: height - 210,
size: 10
})
pages[0].drawText(aufnahme.gebaeudeteil || "", {
x: 181,
y: height - 226,
size: 10
})
pages[0].drawText(aufnahme.baujahr_gebaeude?.toString() || "", {
x: 181,
y: height - 242.5,
size: 10
})
pages[0].drawText(aufnahme.baujahr_heizung?.toString() || "", {
x: 181,
y: height - 259,
size: 10
})
pages[0].drawText(aufnahme.nutzflaeche?.toString() || "", {
x: 181,
y: height - 295,
size: 10
})
pages[0].drawText(`${aufnahme.brennstoff_1}, ${aufnahme.brennstoff_2 || ""}`, {
x: 181,
y: height - 314,
size: 10
})
// const gebaeudetyp = fillFormField("gebaeudetyp", aufnahme.gebaeudetyp || "") // const gebaeudetyp = fillFormField("gebaeudetyp", aufnahme.gebaeudetyp || "")
// const adresse = fillFormField("adresse", objekt.adresse) // const adresse = fillFormField("adresse", objekt.adresse)
@@ -76,7 +119,22 @@ export async function pdfVerbrauchsausweisGewerbe(ausweis: VerbrauchsausweisWohn
// toggleCheck("anlass_sonstiges", ausweis.ausstellgrund == "Sonstiges") // toggleCheck("anlass_sonstiges", ausweis.ausstellgrund == "Sonstiges")
// const gebaeudeBild = bilder.find(image => image.kategorie === "Gebaeude"); const gebaeudeBild = bilder.find(image => image.kategorie === Enums.BilderKategorie.Gebaeude);
if (gebaeudeBild) {
let image: PDFImage;
try {
image = await pdf.embedJpg(gebaeudeBild?.base64)
} catch(e) {
image = await pdf.embedPng(gebaeudeBild?.base64)
}
pages[0].drawImage(image, {
x: 424.5,
y: height - 321,
width: 111,
height: 143
})
}
// /* -------------------------------- Seite 2 -------------------------------- */ // /* -------------------------------- Seite 2 -------------------------------- */
@@ -248,50 +306,44 @@ export async function pdfVerbrauchsausweisGewerbe(ausweis: VerbrauchsausweisWohn
const addVerbrauch = addVerbrauchGenerator(); const addVerbrauch = addVerbrauchGenerator();
if (!ausweis.warmwasser_enthalten) {
// Mit Warmwasserzuschlag
addVerbrauch( addVerbrauch(
moment(ausweis.startdatum).format("MM.YYYY"), moment(ausweis.startdatum).format("MM.YYYY"),
moment(ausweis.startdatum).add(1, "year").format("MM.YYYY"), moment(ausweis.startdatum).add(3, "years").format("MM.YYYY"),
aufnahme.brennstoff_1, aufnahme.brennstoff_1 || "",
berechnungen?.primaerfaktor.toString(), berechnungen?.brennstoff_1.primaerenergiefaktor.toString(),
Math.round(berechnungen?.verbrauch_1_kwh).toString(), Math.round(berechnungen?.energieVerbrauchGesamt_1 || 0).toString(),
Math.round( "0",
berechnungen?.anteil_warmwasser_1 * berechnungen?.verbrauch_1_kwh Math.round(berechnungen?.energieVerbrauchHeizung_1 || 0).toString(),
).toString(), berechnungen?.durchschnittsKlimafaktor.toString()
Math.round(
berechnungen?.anteil_heizung_1 * berechnungen?.verbrauch_1_kwh
).toString(),
berechnungen?.klimafaktoren[0].klimafaktor.toString()
); );
} else {
// Ohne Warmwasserzuschlag
addVerbrauch( addVerbrauch(
moment(ausweis.startdatum).add(1, "year").format("MM.YYYY"), moment(ausweis.startdatum).format("MM.YYYY"),
moment(ausweis.startdatum).add(2, "year").format("MM.YYYY"), moment(ausweis.startdatum).add(3, "years").format("MM.YYYY"),
aufnahme.brennstoff_1, aufnahme.brennstoff_1 || "",
berechnungen?.primaerfaktor.toString(), berechnungen?.brennstoff_1.primaerenergiefaktor.toString(),
Math.round(berechnungen?.verbrauch_2_kwh).toString(), Math.round(berechnungen?.energieVerbrauchGesamt_1 || 0).toString(),
Math.round( Math.round(berechnungen?.energieVerbrauchWarmwasser_1 || 0).toString(),
berechnungen?.anteil_warmwasser_1 * berechnungen?.verbrauch_2_kwh Math.round(berechnungen?.energieVerbrauchHeizung_1 || 0).toString(),
).toString(), berechnungen?.durchschnittsKlimafaktor.toString()
Math.round(
berechnungen?.anteil_heizung_1 * berechnungen?.verbrauch_2_kwh
).toString(),
berechnungen?.klimafaktoren[0].klimafaktor.toString()
); );
}
if (ausweis.zusaetzliche_heizquelle) {
addVerbrauch( addVerbrauch(
moment(ausweis.startdatum).add(2, "year").format("MM.YYYY"), moment(ausweis.startdatum).format("MM.YYYY"),
moment(ausweis.startdatum).add(3, "year").format("MM.YYYY"), moment(ausweis.startdatum).add(3, "years").format("MM.YYYY"),
aufnahme.brennstoff_1, aufnahme.brennstoff_2 || "",
berechnungen?.primaerfaktor.toString(), berechnungen?.brennstoff_2.primaerenergiefaktor.toString(),
Math.round(berechnungen?.verbrauch_3_kwh).toString(), Math.round(berechnungen?.energieVerbrauchGesamt_2 || 0).toString(),
Math.round( Math.round(berechnungen?.energieVerbrauchWarmwasser_2 || 0).toString(),
berechnungen?.anteil_warmwasser_1 * berechnungen?.verbrauch_3_kwh Math.round(berechnungen?.energieVerbrauchHeizung_2 || 0).toString(),
).toString(), berechnungen?.durchschnittsKlimafaktor.toString()
Math.round(
berechnungen?.anteil_heizung_1 * berechnungen?.verbrauch_3_kwh
).toString(),
berechnungen?.klimafaktoren[0].klimafaktor.toString()
); );
}
/* -------------------------------- Seite 4 -------------------------------- */ /* -------------------------------- Seite 4 -------------------------------- */

View File

@@ -4,7 +4,7 @@ import { getEmpfehlungen } from "#lib/XML/getEmpfehlungen.js";
import { Enums } from "@ibcornelsen/database/server"; import { Enums } from "@ibcornelsen/database/server";
import * as fs from "fs" import * as fs from "fs"
import moment from "moment"; import moment from "moment";
import { PDFDocument, PDFFont, PDFPage, RotationTypes, StandardFonts, TextAlignment } from "pdf-lib"; import { PDFDocument, PDFFont, PDFImage, PDFPage, RotationTypes, StandardFonts, TextAlignment } from "pdf-lib";
/* -------------------------------- Pdf Tools ------------------------------- */ /* -------------------------------- Pdf Tools ------------------------------- */
@@ -74,11 +74,22 @@ export async function pdfVerbrauchsausweisWohnen(ausweis: VerbrauchsausweisWohne
toggleCheck("anlass_modernisierung", ausweis.ausstellgrund == "Modernisierung") toggleCheck("anlass_modernisierung", ausweis.ausstellgrund == "Modernisierung")
toggleCheck("anlass_sonstiges", ausweis.ausstellgrund == "Sonstiges") toggleCheck("anlass_sonstiges", ausweis.ausstellgrund == "Sonstiges")
console.log(bilder); const gebaeudeBild = bilder.find(image => image.kategorie === Enums.BilderKategorie.Gebaeude);
const gebaeudeBild = bilder.find(image => image.kategorie === "Gebaeude");
if (gebaeudeBild) {
let image: PDFImage;
try {
image = await pdf.embedJpg(gebaeudeBild?.base64)
} catch(e) {
image = await pdf.embedPng(gebaeudeBild?.base64)
}
pages[0].drawImage(image, {
x: 460.5,
y: height - 289,
width: 111,
height: 138
})
}
/* -------------------------------- Seite 2 -------------------------------- */ /* -------------------------------- Seite 2 -------------------------------- */
@@ -244,50 +255,109 @@ export async function pdfVerbrauchsausweisWohnen(ausweis: VerbrauchsausweisWohne
const addVerbrauch = addVerbrauchGenerator(); const addVerbrauch = addVerbrauchGenerator();
if (!ausweis.warmwasser_enthalten) {
// Mit Warmwasserzuschlag
addVerbrauch( addVerbrauch(
moment(ausweis.startdatum).format("MM.YYYY"), moment(ausweis.startdatum).format("MM.YYYY"),
moment(ausweis.startdatum).add(1, "year").format("MM.YYYY"), moment(ausweis.startdatum).add(3, "years").format("MM.YYYY"),
aufnahme.brennstoff_1, aufnahme.brennstoff_1,
berechnungen?.primaerfaktorww.toString(), berechnungen?.brennstoff_1.primaerenergiefaktor.toString(),
Math.round(berechnungen?.verbrauch_1_kwh).toString(), Math.round(berechnungen?.energieVerbrauchGesamt_1).toString(),
Math.round( "0",
berechnungen?.anteil_warmwasser_1 * berechnungen?.verbrauch_1_kwh Math.round(berechnungen?.energieVerbrauchHeizung_1).toString(),
).toString(), berechnungen?.durchschnittsKlimafaktor.toString()
Math.round(
berechnungen?.anteil_heizung_1 * berechnungen?.verbrauch_1_kwh
).toString(),
berechnungen?.klimafaktoren[0].klimafaktor.toString()
); );
} else {
// Ohne Warmwasserzuschlag
addVerbrauch( addVerbrauch(
moment(ausweis.startdatum).add(1, "year").format("MM.YYYY"), moment(ausweis.startdatum).format("MM.YYYY"),
moment(ausweis.startdatum).add(2, "year").format("MM.YYYY"), moment(ausweis.startdatum).add(3, "years").format("MM.YYYY"),
aufnahme.brennstoff_1, aufnahme.brennstoff_1,
berechnungen?.primaerfaktorww.toString(), berechnungen?.brennstoff_1.primaerenergiefaktor.toString(),
Math.round(berechnungen?.verbrauch_2_kwh).toString(), Math.round(berechnungen?.energieVerbrauchGesamt_1).toString(),
Math.round( Math.round(berechnungen?.energieVerbrauchWarmwasser_1).toString(),
berechnungen?.anteil_warmwasser_1 * berechnungen?.verbrauch_2_kwh Math.round(berechnungen?.energieVerbrauchHeizung_1).toString(),
).toString(), berechnungen?.durchschnittsKlimafaktor.toString()
Math.round(
berechnungen?.anteil_heizung_1 * berechnungen?.verbrauch_2_kwh
).toString(),
berechnungen?.klimafaktoren[0].klimafaktor.toString()
); );
}
if (ausweis.zusaetzliche_heizquelle) {
addVerbrauch( addVerbrauch(
moment(ausweis.startdatum).add(2, "year").format("MM.YYYY"), moment(ausweis.startdatum).format("MM.YYYY"),
moment(ausweis.startdatum).add(3, "year").format("MM.YYYY"), moment(ausweis.startdatum).add(3, "years").format("MM.YYYY"),
aufnahme.brennstoff_1, aufnahme.brennstoff_2,
berechnungen?.primaerfaktorww.toString(), berechnungen?.brennstoff_2.primaerenergiefaktor.toString(),
Math.round(berechnungen?.verbrauch_3_kwh).toString(), Math.round(berechnungen?.energieVerbrauchGesamt_2).toString(),
Math.round( Math.round(berechnungen?.energieVerbrauchWarmwasser_2).toString(),
berechnungen?.anteil_warmwasser_1 * berechnungen?.verbrauch_3_kwh Math.round(berechnungen?.energieVerbrauchHeizung_2).toString(),
).toString(), berechnungen?.durchschnittsKlimafaktor.toString()
Math.round(
berechnungen?.anteil_heizung_1 * berechnungen?.verbrauch_3_kwh
).toString(),
berechnungen?.klimafaktoren[0].klimafaktor.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).toString(),
Math.round(berechnungen?.energieVerbrauchWarmwasser_1).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 + berechnungen?.leerstandsZuschlagWarmwasser).toString(),
Math.round(berechnungen?.leerstandsZuschlagWarmwasser).toString(),
Math.round(berechnungen?.leerstandsZuschlagHeizung).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 + berechnungen?.leerstandsZuschlagWarmwasser).toString(),
"0",
"0",
berechnungen?.durchschnittsKlimafaktor.toString()
);
}
}
if (ausweis.wird_gekuehlt) {
/**
* 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"),
"Leerstandszuschlag",
berechnungen?.primaerfaktorww.toString(),
Math.round(berechnungen?.kuehlungsZuschlag).toString(),
"0",
"0",
"1.8"
);
}
/* -------------------------------- Seite 4 -------------------------------- */ /* -------------------------------- Seite 4 -------------------------------- */

View File

@@ -18,9 +18,6 @@
bilder: UploadedGebaeudeBild[], bilder: UploadedGebaeudeBild[],
events: Event[] events: Event[]
}[]; }[];
console.log(ausweise);
</script> </script>
<div class="gap-4 flex flex-col"> <div class="gap-4 flex flex-col">

View File

@@ -191,7 +191,7 @@
<h2 class="text-primary text-xl"> <h2 class="text-primary text-xl">
Verbrauchsausweis Wohnen {PRICES.VerbrauchsausweisWohnen[0]} Verbrauchsausweis Wohnen {PRICES.VerbrauchsausweisWohnen[0]}
</h2> </h2>
<Progressbar step2={'step'}/> <Progressbar active={1}/>
</div> </div>
</div> </div>

View File

@@ -85,7 +85,7 @@
<h1 class="text-secondary text-3xl m-0">Energiesausweis erstellen</h1> <h1 class="text-secondary text-3xl m-0">Energiesausweis erstellen</h1>
<h2 class="text-primary text-xl">Verbrauchsausweis Gewerbe {PRICES.VerbrauchsausweisGewerbe[0]}</h2> <h2 class="text-primary text-xl">Verbrauchsausweis Gewerbe {PRICES.VerbrauchsausweisGewerbe[0]}</h2>
<Progressbar step1={'step'} /> <Progressbar active={0} />
</div> </div>
</div> </div>

View File

@@ -227,9 +227,9 @@ lg:grid-cols-2 lg:gap-x-6
<div id="progress-box" class="w-full box relative px-4 py-3 text-center order-2 self-stretch"> <div id="progress-box" class="w-full box relative px-4 py-3 text-center order-2 self-stretch">
<h1 class="text-secondary text-3xl m-0">Energiesausweis erstellen</h1> <h1 class="text-secondary text-3xl m-0">Energieausweis erstellen</h1>
<h2 class="text-primary text-xl">{ausweisart} {PRICES.VerbrauchsausweisWohnen[0]}</h2> <h2 class="text-primary text-xl">{ausweisart} {PRICES.VerbrauchsausweisWohnen[0]}</h2>
<Progressbar step1={'step'}/> <Progressbar active={0}/>
</div> </div>
</div> </div>

View File

@@ -0,0 +1,372 @@
<script lang="ts">
import Ausweisart from "#components/Ausweis/Ausweisart.svelte";
import GebaeudeDaten from "#components/Ausweis/GebaeudeDaten.svelte";
import {
VerbrauchsausweisWohnenClient,
ObjektClient,
AufnahmeClient,
BenutzerClient,
UploadedGebaeudeBild,
UnterlageClient,
BedarfsausweisWohnenClient,
VerbrauchsausweisGewerbeClient,
} from "#components/Ausweis/types.js";
import Bereich from "#components/labels/Bereich.svelte";
import { Enums } from "@ibcornelsen/database/client";
import InputLabel from "#components/labels/InputLabel.svelte";
import HelpLabel from "#components/labels/HelpLabel.svelte";
import Progressbar from "#components/Ausweis/Progressbar.svelte";
import FileGrid from "#components/FileGrid.svelte";
import Hilfe from "#components/Ausweis/Hilfe.svelte";
import Overlay from "#components/Overlay.svelte";
import EmbeddedAuthFlowModule from "#modules/EmbeddedAuthFlowModule.svelte";
import { validateAccessTokenClient } from "#client/lib/validateAccessToken.js";
import { api } from "astro-typesafe-api/client";
import { API_ACCESS_TOKEN_COOKIE_NAME } from "#lib/constants.js";
import { exclude } from "#lib/exclude.js";
import Cookies from "js-cookie";
export async function ausweisSpeichern(
ausweis: VerbrauchsausweisWohnenClient | VerbrauchsausweisGewerbeClient | BedarfsausweisWohnenClient,
objekt: ObjektClient,
aufnahme: AufnahmeClient,
unterlagen: (UnterlageClient & { data?: string | ArrayBuffer })[],
ausweisart: Enums.Ausweisart
) {
if (objekt.uid) {
await api.objekt._uid.PATCH.fetch({
...exclude(objekt, ["uid"])
}, {
params: {
uid: objekt.uid
},
headers: {
"Authorization": `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`
}
})
} else {
const { uid } = await api.objekt.PUT.fetch({
...exclude(objekt, ["uid"])
}, {
headers: {
"Authorization": `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`
}
})
objekt.uid = uid;
}
if (aufnahme.uid) {
await api.aufnahme._uid.PATCH.fetch({
...exclude(aufnahme, ["uid"])
}, {
params: {
uid: aufnahme.uid
},
headers: {
"Authorization": `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`
}
})
} else {
const { uid } = await api.aufnahme.PUT.fetch({
aufnahme,
uid_objekt: objekt.uid
}, {
headers: {
"Authorization": `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`
}
})
aufnahme.uid = uid
}
let patchRoute: any;
let putRoute: any;
if (ausweisart == Enums.Ausweisart.VerbrauchsausweisWohnen) {
patchRoute = api["verbrauchsausweis-wohnen"]._uid.PATCH
putRoute = api["verbrauchsausweis-wohnen"].PUT
} else if (ausweisart == Enums.Ausweisart.VerbrauchsausweisGewerbe) {
patchRoute = api["verbrauchsausweis-gewerbe"]._uid.PATCH
putRoute = api["verbrauchsausweis-gewerbe"].PUT
} else if (ausweisart == Enums.Ausweisart.BedarfsausweisWohnen) {
patchRoute = api["bedarfsausweis-wohnen"]._uid.PATCH
putRoute = api["bedarfsausweis-wohnen"].PUT
} else if (ausweisart == Enums.Ausweisart.GEGNachweisVerbrauchsausweisWohnen) {
patchRoute = api["geg-nachweis-verbrauchsausweis-wohnen"]._uid.PATCH
putRoute = api["geg-nachweis-verbrauchsausweis-wohnen"].PUT
}
if (ausweis.uid) {
await patchRoute.fetch({
...exclude(ausweis, ["uid"])
}, {
params: {
uid: ausweis.uid
},
headers: {
"Authorization": `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`
}
})
} else {
const { uid } = await putRoute.fetch({
ausweis,
uid_aufnahme: aufnahme.uid
}, {
headers: {
"Authorization": `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`
}
})
ausweis.uid = uid;
}
for (const unterlage of unterlagen) {
if (unterlage.uid) {
continue;
}
const response = await api.objekt._uid.unterlagen.PUT.fetch({
data: unterlage.data,
kategorie: unterlage.kategorie,
mime: unterlage.mime,
name: unterlage.name
}, {
params: {
uid: objekt.uid
},
headers: {
"Authorization": `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`
}
})
unterlage.uid = response.uid
}
return {
uid_ausweis: ausweis.uid,
uid_aufnahme: aufnahme.uid,
uid_objekt: objekt.uid
}
}
async function ausweisAbschicken() {
if (!(await validateAccessTokenClient())) {
loginOverlayHidden = false;
return;
}
ausweisSpeichern(ausweis, objekt, aufnahme, unterlagen, Enums.Ausweisart.VerbrauchsausweisGewerbe)
loginOverlayHidden = true;
}
async function spaeterWeitermachen() {
if (!(await validateAccessTokenClient())) {
loginOverlayHidden = false;
return;
}
loginOverlayHidden = true;
}
let loginOverlayHidden = true;
export let ausweis: VerbrauchsausweisWohnenClient;
export let objekt: ObjektClient;
export let aufnahme: AufnahmeClient;
export let user: BenutzerClient = {} as BenutzerClient;
export let bilder: UploadedGebaeudeBild[] = [];
export let plaene: (UnterlageClient & { data: string | ArrayBuffer })[] =
[];
export let unterlagen: (UnterlageClient & {
data: string | ArrayBuffer;
})[] = [];
if (Object.keys(ausweis).length === 0) {
const localStorageAusweis = localStorage.getItem("ausweis");
if (localStorageAusweis) {
ausweis = JSON.parse(localStorageAusweis)
}
}
if (Object.keys(aufnahme).length === 0) {
const localStorageAufnahme = localStorage.getItem("aufnahme");
if (localStorageAufnahme) {
aufnahme = JSON.parse(localStorageAufnahme)
}
}
if (Object.keys(objekt).length === 0) {
const localStorageObjekt = localStorage.getItem("objekt");
if (localStorageObjekt) {
objekt = JSON.parse(localStorageObjekt)
}
}
$: {
if (ausweis.uid && objekt.uid && aufnahme.uid) {
localStorage.setItem(ausweis.uid, JSON.stringify(ausweis))
localStorage.setItem(objekt.uid, JSON.stringify(objekt))
localStorage.setItem(aufnahme.uid, JSON.stringify(aufnahme))
} else {
localStorage.setItem("ausweis", JSON.stringify(ausweis))
localStorage.setItem("aufnahme", JSON.stringify(aufnahme))
localStorage.setItem("objekt", JSON.stringify(objekt))
}
}
const ausweisart = Enums.Ausweisart.GEGNachweisVerbrauchsausweisWohnen;
function automatischAusfüllen() {}
</script>
<div
id="skala"
class="bg-white grid grid-cols-1 p-4 lg:grid-cols-2 lg:gap-x-6 no-scroll"
>
<div
id="progress-box"
class="w-full box relative px-4 py-3 text-center order-2 self-stretch"
>
<h1 class="text-secondary text-3xl m-0">GEG Nachweis Anfragen</h1>
<h2 class="text-primary text-xl">Verbrauchsausweis Wohnen</h2>
<Progressbar
active={0}
steps={["Gebäudedaten", "Kundendaten", "Bestätigung"]}
/>
</div>
</div>
<form
id="formInput-1"
on:submit={ausweisAbschicken}
name="ausweis"
data-test="ausweis"
>
<div id="formular-box" class="formular-boxen ring-0">
<!-- A Prüfung der Ausweisart -->
<Bereich bereich="A" title="Prüfung der Ausweisart">
<Ausweisart bind:objekt bind:aufnahme bind:ausweis {ausweisart} />
</Bereich>
<!-- B Eingabe der Gebäudeadresse - Angaben zu Wohnfläche, Keller und Dachgeschoss -->
<Bereich
bereich="B"
title="Eingabe der Gebäudeadresse - Angaben zu Wohnfläche, Keller und Dachgeschoss"
><GebaeudeDaten bind:aufnahme bind:objekt {ausweisart} /></Bereich
>
<Bereich bereich="C" title="Beschreibung des Bauvorhabens">
<div class="bereich-box">
<div class="input-standard order-3 md:order-5 xl:order-3">
<InputLabel
title="Angaben zur Heizung, Lüftung, Energieerezugung, Qualität und Aufbau der Gebäudehülle usw."
></InputLabel>
<textarea class="rounded-e-none" rows="10"></textarea>
<div class="help-label">
<HelpLabel>
Geben Sie kurz an: Heizung, Lüftung, regenerative
Energie (Solar, PV) und die Gebäudehülle (Qualität,
Aufbau).
</HelpLabel>
</div>
</div>
</div>
</Bereich>
<Bereich bereich="D" title="Gebäudepläne & Unterlagen">
<div
class="bereich-box grid grid-cols-1 lg:grid-cols-2 gap-x-6 mt-6"
>
<div class="md:box md:card mb-0">
<div class="font-bold mb-2">Pläne</div>
<div>
Hier können sie Grundrisspläne, Ansichtspläne und
Schnitte hochladen. Die Dateien können entweder im PDF
Format oder als Bild hochgeladen werden.
</div>
</div>
<div class="md:box md:card mb-0 mt-6 md:mt-0">
<div>
<strong
>Bitte laden Sie hier mind. 1 Dokument hoch:</strong
>
</div>
<FileGrid
max={Infinity}
min={1}
name={"plaene"}
bind:files={plaene}
bind:ausweis
bind:objekt
></FileGrid>
</div>
</div>
<div
class="bereich-box grid grid-cols-1 lg:grid-cols-2 gap-x-6 mt-6"
>
<div class="md:box md:card mb-0">
<div class="font-bold mb-2">Unterlagen</div>
<div>
Hier können sie weitere Unterlagen wie z.B.
Baugenehmigungen, U-Wert Berechnungen, Anlagentechnik
oder ihren alten Energieausweis hochladen. Die Dateien
können entweder im PDF Format oder als Bild hochgeladen
werden.
</div>
</div>
<div class="md:box md:card mb-0 mt-6 md:mt-0">
<FileGrid
max={Infinity}
min={0}
name={"unterlagen"}
bind:files={unterlagen}
bind:ausweis
bind:objekt
></FileGrid>
</div>
</div>
</Bereich>
</div>
<div
class="grid grid-cols-[1fr_min-content_min-content_min-content] grid-rows-[min_content_1fr] gap-x-2 self-start justify-self-end mt-8"
>
<div></div>
<Hilfe />
<button class="button" type="button" on:click={spaeterWeitermachen}
>Später Weitermachen
</button>
<div>
<Overlay bind:hidden={loginOverlayHidden}>
<div class="bg-white w-full max-w-screen-sm py-8">
<EmbeddedAuthFlowModule onLogin={ausweisAbschicken}
></EmbeddedAuthFlowModule>
</div>
</Overlay>
<button
on:click={ausweisAbschicken}
type="button"
class="button"
data-cy="weiter">Weiter</button
>
</div>
</div>
</form>

View File

@@ -0,0 +1,80 @@
---
import AusweisLayout from "#layouts/AusweisLayoutDaten.astro";
import { AufnahmeClient, ObjektClient, UploadedGebaeudeBild, VerbrauchsausweisWohnenClient } from "#components/Ausweis/types";
import { createCaller } from "../../../astro-typesafe-api-caller.js";
import { API_ACCESS_TOKEN_COOKIE_NAME } from "#lib/constants.js";
import { validateAccessTokenServer } from "#server/lib/validateAccessToken.js";
import GEGNachweisVerbrauchsausweisWohnenModule from "#modules/angebot-anfragen/GEGNachweisVerbrauchsausweisWohnenModule.svelte";
const uid = Astro.url.searchParams.get("uid");
let ausweis: VerbrauchsausweisWohnenClient = {} as VerbrauchsausweisWohnenClient;
let aufnahme: AufnahmeClient = {} as AufnahmeClient;
let objekt: ObjektClient = {} as ObjektClient;
let bilder: UploadedGebaeudeBild[] = []
const valid = validateAccessTokenServer(Astro);
const caller = createCaller(Astro);
if (uid) {
if (!valid) {
return Astro.redirect(
`/auth/login?redirect=${Astro.url.toString()}`
);
}
try {
ausweis = await caller["verbrauchsausweis-wohnen"]._uid.GET.fetch(null, {
headers: {
authorization: `Bearer ${Astro.cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)?.value}`
},
params: {
uid
}
});
aufnahme = await caller.aufnahme._uid.GET.fetch(null, {
headers: {
authorization: `Bearer ${Astro.cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)?.value}`
},
params: {
uid: ausweis.uid_aufnahme
}
})
objekt = await caller.objekt._uid.GET.fetch(null, {
headers: {
authorization: `Bearer ${Astro.cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)?.value}`
},
params: {
uid: ausweis.uid_objekt
}
})
bilder = await caller.objekt._uid.bilder.GET.fetch(null, {
headers: {
authorization: `Bearer ${Astro.cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)?.value}`
},
params: {
uid: ausweis.uid_objekt
}
})
if (!ausweis) {
// Der Ausweis scheint nicht zu existieren.
// Wir leiten auf die generische Ausweisseite ohne UID weiter.
return Astro.redirect(
"/energieausweis-erstellen/verbrauchsausweis-wohnen"
);
}
} catch(e) {
return Astro.redirect(
"/energieausweis-erstellen/verbrauchsausweis-wohnen"
);
}
}
---
<AusweisLayout title="Verbrauchsausweis erstellen">
<GEGNachweisVerbrauchsausweisWohnenModule client:only {ausweis} {objekt} {aufnahme} {bilder} />
</AusweisLayout>

View File

@@ -0,0 +1,209 @@
import { getAusweisartFromUUID } from "#components/Ausweis/types.js";
import { adminMiddleware } from "#lib/middleware/authorization.js";
import { pdfDatenblattVerbrauchsausweisWohnen } from "#lib/pdf/pdfDatenblattVerbrauchsausweisWohnen.js";
import { pdfVerbrauchsausweisWohnen } from "#lib/pdf/pdfVerbrauchsausweisWohnen.js";
import { Enums, prisma } from "@ibcornelsen/database/server";
import { APIError, defineApiRoute } from "astro-typesafe-api/server";
import { z } from "astro:content";
import { fileURLToPath } from "url";
import * as fs from "fs";
import { transport } from "#lib/mail.js";
import { BASE_URI } from "#lib/constants.js";
export const GET = defineApiRoute({
input: z.object({
uid: z.string(),
}),
output: z.void(),
middleware: adminMiddleware,
async fetch({ uid }, context, user) {
const ausweisart = getAusweisartFromUUID(uid);
if (ausweisart === "VerbrauchsausweisWohnen") {
const ausweis = await prisma.verbrauchsausweisWohnen.findUnique({
where: {
uid,
},
include: {
aufnahme: {
include: {
objekt: {
include: {
gebaeude_bilder: true,
benutzer: true,
},
},
},
},
},
});
if (!ausweis) {
throw new APIError({
code: "BAD_REQUEST",
message: "Ausweis existiert nicht.",
});
}
const rechnung = await prisma.rechnung.findFirst({
where: {
aufnahme_id: ausweis.aufnahme.id,
},
orderBy: {
erstellt_am: "desc",
},
});
if (!rechnung) {
throw new APIError({
code: "BAD_REQUEST",
message:
"Die Rechnung wurde noch nicht erstellt, wir können nicht fortfahren.",
});
}
// TODO
// SECTION: Rechnung erstellen (LexOffice)
// Wir wollen die Rechnung an lex office versenden, und uns die ID von da holen.
// Falls die Rechnung bereits existiert ist das nicht nötig.
// if (!$rechnung->lex_office_id) {
// [$lex_office_id, $renr] = createInvoice($ausweis, $rechnung);
// sleep(1); // TODO: Nach der Umstellung von LexOffice auf etwas anderes MUSS das hier unbedingt entfernt werden!
// if (!$lex_office_id || !$renr) {
// die("Bei der Erstellung der Rechnung ist etwas schiefgelaufen - Möglicherweise ist etwas mit LexOffice?");
// }
// $rechnung->lex_office_id = $lex_office_id;
// $rechnung->rechnungsnummer = $renr;
// $rechnung->save();
// }
// TODO
// if ($ausweis->erledigt != 2) {$ausweis->erstellungsdatum = date("Y-m-d H:i:s");}
// $ausweis->erledigt = 2;
// $ausweis->save();
const pdfAusweis = await pdfVerbrauchsausweisWohnen(
ausweis,
ausweis.aufnahme,
ausweis.aufnahme.objekt,
ausweis.aufnahme.objekt.gebaeude_bilder,
ausweis.aufnahme.objekt.benutzer
);
const pdfDatenblatt = await pdfDatenblattVerbrauchsausweisWohnen(
ausweis,
ausweis.aufnahme,
ausweis.aufnahme.objekt,
ausweis.aufnahme.objekt.benutzer
);
const pdfAusweisPath = fileURLToPath(
new URL(
`../../../../persistent/generated/Ausweis-${ausweis.uid}.pdf`,
import.meta.url
)
);
const pdfDatenblattPath = fileURLToPath(
new URL(
`../../../../persistent/generated/Datenblatt-${ausweis.uid}.pdf`,
import.meta.url
)
);
fs.writeFileSync(pdfAusweisPath, pdfAusweis);
fs.writeFileSync(pdfDatenblattPath, pdfDatenblatt);
let text: string;
if (rechnung.status === Enums.Rechnungsstatus.paid) {
text = `
<p>Sehr geehrte*r ${user.vorname} ${user.name},</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 {
text = `
<p>Sehr geehrte*r ${user.vorname} ${user.name},</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.uid}</b></td>
</table>
<br>
<p>
Alternativ können Sie auch direkt online zahlen indem Sie auf den entsprechenden Link klicken:
</p>
<br>
<table>
<tr><td>Per Einzuglastschrift zahlen</td> <td>:</td> <td><a href='${BASE_URI}/energieausweis-erstellen/kaufabschluss-fortsetzen?uid=${ausweis.uid}&p=SEPA'>jetzt per ELV bezahlen</a></td></tr>
<tr><td>Per Sofortüberweisung zahlen</td> <td>:</td> <td><a href='${BASE_URI}/energieausweis-erstellen/kaufabschluss-fortsetzen?uid=${ausweis.uid}&p=Sofort'>jetzt per Sofortüberweisung bezahlen</a></td></tr>
<tr><td>Über PayPal zahlen</td> <td>:</td> <td><a href='${BASE_URI}/energieausweis-erstellen/kaufabschluss-fortsetzen?uid=${ausweis.uid}&p=PayPal'>jetzt per Paypal bezahlen</a></td></tr>
<tr><td>Per Giropay zahlen</td> <td>:</td> <td><a href='${BASE_URI}/energieausweis-erstellen/kaufabschluss-fortsetzen?uid=${ausweis.uid}&p=Giropay'>jetzt per Giropay bezahlen</a></td></tr>
<tr><td>Per Visa oder MasterCard zahlen</td> <td>:</td> <td><a href='${BASE_URI}/energieausweis-erstellen/kaufabschluss-fortsetzen?uid=${ausweis.uid}&p=Kreditkarte'>jetzt per Kreditkarte bezahlen</a></td></tr>
</table>
<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: user.email,
subject: `Ihr Originalausweis vom Ingenieurbüro Cornelsen (ID: ${ausweis.uid})`,
text,
});
}
},
});

View File

@@ -5,6 +5,7 @@ import { prisma } from "@ibcornelsen/database/server";
import { decodeToken, encodeToken } from "#lib/auth/token.js"; import { decodeToken, encodeToken } from "#lib/auth/token.js";
import { TokenType } from "#lib/auth/types.js"; import { TokenType } from "#lib/auth/types.js";
import { hashPassword } from "#lib/password.js"; import { hashPassword } from "#lib/password.js";
import { transport } from "#lib/mail.js";
export const GET = defineApiRoute({ export const GET = defineApiRoute({
input: z.object({ input: z.object({
@@ -33,16 +34,6 @@ export const GET = defineApiRoute({
uid: user.uid uid: user.uid
}) })
const transport = nodemailer.createTransport({
host: "smtp.ionos.de",
port: 465,
secure: true,
auth: {
user: "info@online-energieausweis.org",
pass: "Katendeich5a2024!"
}
})
const info = await transport.sendMail({ const info = await transport.sendMail({
from: `"IBCornelsen" <info@online-energieausweis.org>`, from: `"IBCornelsen" <info@online-energieausweis.org>`,
to: input.email, to: input.email,

View File

@@ -1,12 +1,12 @@
import { BedarfsausweisWohnenClient, OptionalNullable, UUidWithPrefix, ZodOverlap } from "#components/Ausweis/types.js"; import { BedarfsausweisWohnenClient, OptionalNullable, UUidWithPrefix, VerbrauchsausweisWohnenClient, ZodOverlap } from "#components/Ausweis/types.js";
import { exclude } from "#lib/exclude.js"; import { exclude } from "#lib/exclude.js";
import { authorizationHeaders, authorizationMiddleware } from "#lib/middleware/authorization.js"; import { authorizationHeaders, authorizationMiddleware } from "#lib/middleware/authorization.js";
import { BedarfsausweisWohnenSchema, prisma } from "@ibcornelsen/database/server"; import { BedarfsausweisWohnenSchema, prisma, VerbrauchsausweisWohnenSchema } from "@ibcornelsen/database/server";
import { APIError, defineApiRoute } from "astro-typesafe-api/server"; import { APIError, defineApiRoute } from "astro-typesafe-api/server";
import { z } from "zod"; import { z } from "zod";
export const PATCH = defineApiRoute({ export const PATCH = defineApiRoute({
input: BedarfsausweisWohnenSchema.omit({ input: VerbrauchsausweisWohnenSchema.omit({
uid: true, uid: true,
id: true, id: true,
benutzer_id: true, benutzer_id: true,
@@ -18,7 +18,7 @@ export const PATCH = defineApiRoute({
}, },
middleware: authorizationMiddleware, middleware: authorizationMiddleware,
async fetch(input, ctx, user) { async fetch(input, ctx, user) {
const objekt = await prisma.bedarfsausweisWohnen.findUnique({ const objekt = await prisma.verbrauchsausweisWohnen.findUnique({
where: { where: {
uid: ctx.params.uid, uid: ctx.params.uid,
benutzer: { benutzer: {
@@ -34,7 +34,7 @@ export const PATCH = defineApiRoute({
}) })
} }
await prisma.bedarfsausweisWohnen.update({ await prisma.verbrauchsausweisWohnen.update({
where: { where: {
uid: ctx.params.uid uid: ctx.params.uid
}, },
@@ -61,7 +61,7 @@ export const DELETE = defineApiRoute({
// Wir holen uns den Bedarfsausweis // Wir holen uns den Bedarfsausweis
// Dieser MUSS mit dem Nutzer verknüpft sein. // Dieser MUSS mit dem Nutzer verknüpft sein.
const ausweis = await prisma.bedarfsausweisWohnen.findUnique({ const ausweis = await prisma.verbrauchsausweisWohnen.findUnique({
where: { where: {
uid, uid,
}, },
@@ -155,7 +155,7 @@ export const GET = defineApiRoute({
} }
} }
}, },
output: ZodOverlap<OptionalNullable<BedarfsausweisWohnenClient>>(BedarfsausweisWohnenSchema.merge(z.object({ output: ZodOverlap<OptionalNullable<VerbrauchsausweisWohnenClient>>(VerbrauchsausweisWohnenSchema.merge(z.object({
uid_aufnahme: UUidWithPrefix, uid_aufnahme: UUidWithPrefix,
uid_objekt: UUidWithPrefix, uid_objekt: UUidWithPrefix,
uid_benutzer: UUidWithPrefix.optional() uid_benutzer: UUidWithPrefix.optional()
@@ -175,7 +175,7 @@ export const GET = defineApiRoute({
}) })
} }
const ausweis = await prisma.bedarfsausweisWohnen.findUnique({ const ausweis = await prisma.verbrauchsausweisWohnen.findUnique({
where: { where: {
uid, uid,
benutzer_id: user.id benutzer_id: user.id

View File

@@ -0,0 +1,217 @@
import { BedarfsausweisWohnenClient, OptionalNullable, UUidWithPrefix, ZodOverlap } from "#components/Ausweis/types.js";
import { exclude } from "#lib/exclude.js";
import { authorizationHeaders, authorizationMiddleware } from "#lib/middleware/authorization.js";
import { BedarfsausweisWohnenSchema, prisma } from "@ibcornelsen/database/server";
import { APIError, defineApiRoute } from "astro-typesafe-api/server";
import { z } from "zod";
export const PATCH = defineApiRoute({
input: BedarfsausweisWohnenSchema.omit({
uid: true,
id: true,
benutzer_id: true,
aufnahme_id: true,
}),
output: z.void(),
headers: {
"Authorization": z.string()
},
middleware: authorizationMiddleware,
async fetch(input, ctx, user) {
const objekt = await prisma.bedarfsausweisWohnen.findUnique({
where: {
uid: ctx.params.uid,
benutzer: {
id: user.id
}
}
})
if (!objekt) {
throw new APIError({
code: "NOT_FOUND",
message: "Ausweis konnte nicht gefunden werden oder gehört einem anderen Benutzer."
})
}
await prisma.bedarfsausweisWohnen.update({
where: {
uid: ctx.params.uid
},
data: input
})
},
})
export const DELETE = defineApiRoute({
meta: {
description: "Storniert einen Ausweis"
},
headers: authorizationHeaders,
middleware: authorizationMiddleware,
async fetch(input, ctx, user) {
const { uid } = ctx.params;
if (!UUidWithPrefix.safeParse(uid).success) {
throw new APIError({
code: "BAD_REQUEST",
message: "UID konnte nicht verifiziert werden."
})
}
// Wir holen uns den Bedarfsausweis
// Dieser MUSS mit dem Nutzer verknüpft sein.
const ausweis = await prisma.bedarfsausweisWohnen.findUnique({
where: {
uid,
},
include: {
aufnahme: {
select: {
storniert: true
}
}
}
});
if (!ausweis) {
// Falls wir den Ausweis nicht finden können, werfen wir einen Fehler
throw new APIError({
code: "NOT_FOUND",
message: "Ausweis konnte nicht gefunden werden.",
});
}
// Wir dürfen den Ausweis nur stornieren, wenn er noch nicht ausgestellt wurde
// Außerdem müssen wir schauen, ob wir Admin oder der Besitzer des Ausweises sind.
if ((ausweis.benutzer_id !== user.id) && user.rolle !== "ADMIN") {
// Falls der Ausweis nicht dem Nutzer gehört, werfen wir einen Fehler
throw new APIError({
code: "FORBIDDEN",
message: "Ausweis gehört nicht dem Nutzer.",
});
}
// if (ausweis.erledigt) {
// // Falls der Ausweis bereits ausgestellt wurde, werfen wir einen Fehler
// throw new TRPCError({
// code: "BAD_REQUEST",
// message: "Ausweis wurde bereits ausgestellt.",
// });
// }
if (ausweis.aufnahme.storniert) {
// Falls der Ausweis bereits storniert ist, werfen wir einen Fehler
throw new APIError({
code: "BAD_REQUEST",
message: "Ausweis wurde bereits storniert.",
});
}
await prisma.aufnahme.update({
where: {
id: ausweis.aufnahme_id
},
data: {
storniert: true
}
})
// Wir erstellen ein Event, dass der Ausweis storniert wurde
// Dann können wir das in der Historie anzeigen
await prisma.event.create({
data: {
title: "Ausweis storniert",
description: ((user.rolle === "ADMIN") && (ausweis.benutzer_id !== user.id)) ? "Ausweis wurde von einem Administrator storniert." : "Ausweis wurde vom Besitzer storniert.",
benutzer: {
connect: {
id: user.id
}
},
aufnahme: {
connect: {
id: ausweis.aufnahme_id
}
}
}
})
},
})
export const GET = defineApiRoute({
meta: {
description: "Gibt ein spezifisches Gebäude des Benutzers zurück.",
tags: ["Gebäude"],
headers: {
"Authorization": {
description: "Ein gültiger Authentifizierungstoken",
required: true,
allowEmptyValue: false,
examples: {
Bearer: {
value: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}
}
}
},
output: ZodOverlap<OptionalNullable<BedarfsausweisWohnenClient>>(BedarfsausweisWohnenSchema.merge(z.object({
uid_aufnahme: UUidWithPrefix,
uid_objekt: UUidWithPrefix,
uid_benutzer: UUidWithPrefix.optional()
})).omit({
id: true,
aufnahme_id: true,
benutzer_id: true
})),
middleware: authorizationMiddleware,
async fetch(input, context, user) {
const { uid } = context.params;
if (!uid) {
throw new APIError({
code: "BAD_REQUEST",
message: "Missing uid in request params"
})
}
const ausweis = await prisma.bedarfsausweisWohnen.findUnique({
where: {
uid,
benutzer_id: user.id
},
include: {
benutzer: {
select: {
uid: true
}
},
aufnahme: {
select: {
uid: true,
objekt: {
select: {
uid: true
}
}
}
}
}
});
if (!ausweis) {
// Falls wir den Ausweis nicht finden können, werfen wir einen Fehler
throw new APIError({
code: "NOT_FOUND",
message: "Ausweis konnte nicht gefunden werden.",
});
}
return {
uid_aufnahme: ausweis.aufnahme.uid,
uid_objekt: ausweis.aufnahme.objekt.uid,
uid_benutzer: ausweis.benutzer?.uid,
...exclude(ausweis, ["id", "aufnahme_id", "benutzer_id", "aufnahme"])
}
},
});

View File

@@ -0,0 +1,146 @@
import { UUidWithPrefix } from "#components/Ausweis/types.js";
import { authorizationHeaders, authorizationMiddleware } from "#lib/middleware/authorization.js";
import { BedarfsausweisWohnenSchema, prisma, VerbrauchsausweisWohnenSchema } from "@ibcornelsen/database/server";
import { APIError, defineApiRoute } from "astro-typesafe-api/server";
import { z } from "zod";
export const PUT = defineApiRoute({
meta: {
contentTypes: ["application/json"],
description:
"Erstellt einen neuen GEG Nachweis für Wohngebäude.",
tags: ["GEG Nachweis", "Verbrauchsausweis Wohnen"],
},
input: z.object({
ausweis: VerbrauchsausweisWohnenSchema.omit({
id: true,
benutzer_id: true,
uid: true,
aufnahme_id: true
}),
uid_aufnahme: UUidWithPrefix
}),
output: z.object({
uid: UUidWithPrefix,
objekt_uid: UUidWithPrefix,
aufnahme_uid: UUidWithPrefix,
}),
headers: authorizationHeaders,
middleware: authorizationMiddleware,
async fetch(input, ctx, user) {
const aufnahme = await prisma.aufnahme.findUnique({
where: {
uid: input.uid_aufnahme
}
})
if (!aufnahme || aufnahme.benutzer_id !== user.id) {
throw new APIError({
code: "FORBIDDEN",
message: "Aufnahme konnte nicht gefunden werden oder gehört nicht zu diesem Benutzer."
})
}
const createdAusweis = await prisma.verbrauchsausweisWohnen.create({
data: {
...input.ausweis,
benutzer: {
connect: {
id: user.id,
},
},
aufnahme: {
connect: {
uid: aufnahme.uid,
},
},
},
select: {
uid: true,
aufnahme: {
select: {
uid: true,
objekt: {
select: {
uid: true,
},
},
},
},
},
});
return {
uid: createdAusweis.uid,
objekt_uid: createdAusweis.aufnahme.objekt.uid,
aufnahme_uid: createdAusweis.aufnahme.uid,
};
},
});
export const GET = defineApiRoute({
meta: {
description: "Gibt ein spezifisches Gebäude des Benutzers zurück.",
tags: ["Gebäude"],
headers: {
Authorization: {
description: "Ein gültiger Authentifizierungstoken",
required: true,
allowEmptyValue: false,
examples: {
Bearer: {
value: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
},
},
},
},
},
middleware: authorizationMiddleware,
async fetch(input, context, user) {
const { uid } = context.params;
const ausweis = await prisma.verbrauchsausweisWohnen.findUnique({
where: {
uid,
},
include: {
benutzer: true,
aufnahme: {
include: {
objekt: {
include: {
gebaeude_bilder: true,
},
},
rechnungen: true,
events: {
include: {
benutzer: {
select: {
uid: true,
},
},
},
orderBy: {
date: "asc",
},
},
},
},
},
});
if (
!ausweis ||
(ausweis.benutzer_id !== null && ausweis.benutzer_id !== user.id)
) {
// Falls wir den Ausweis nicht finden können, werfen wir einen Fehler
throw new APIError({
code: "NOT_FOUND",
message: "Ausweis konnte nicht gefunden werden.",
});
}
return ausweis;
},
});

View File

@@ -0,0 +1,116 @@
import { authorizationMiddleware } from "#lib/middleware/authorization.js";
import { prisma, UnterlageSchema } from "@ibcornelsen/database/server";
import { APIError, defineApiRoute } from "astro-typesafe-api/server";
import { z } from "zod";
import { fileURLToPath } from "url";
import { writeFileSync } from "fs";
export const PUT = defineApiRoute({
input: UnterlageSchema.omit({
objekt_id: true,
id: true,
uid: true
}).merge(z.object({
data: z.string(),
})),
output: z.object({
uid: z.string({ description: "Die UID der Unterlage." })
}),
middleware: authorizationMiddleware,
async fetch({ data, name, kategorie, mime }, ctx, user) {
if (mime !== "application/pdf" && mime !== "image/png" && mime !== "image/jpeg") {
throw new APIError({
code: "BAD_REQUEST",
message: "Nicht unterstützter mimetype, unterstützt werden 'image/jpeg', 'image/png', 'application/pdf'."
})
}
let objekt = await prisma.objekt.findUnique({
where: {
uid: ctx.params.uid,
benutzer_id: user.id
},
});
if (!objekt) {
throw new APIError({
code: "NOT_FOUND",
message: "Objekt nicht gefunden oder gehört einem anderen Benutzer.",
});
}
const buffer = Buffer.from(data, "base64");
const unterlage = await prisma.unterlage.create({
data: {
kategorie: kategorie,
objekt: {
connect: {
id: objekt.id,
},
},
},
select: {
uid: true,
},
});
const filePath = fileURLToPath(new URL(`../../../../../persistent/unterlagen/${unterlage.uid}`, import.meta.url));
try {
writeFileSync(filePath, buffer)
} catch(e) {
// Unterlage wurde nicht gespeichert, wir löschen den Eintrag wieder
await prisma.unterlage.delete({
where: {
uid: unterlage.uid
}
})
// Und geben einen Fehler zurück
throw new APIError({
code: "INTERNAL_SERVER_ERROR",
message: "Unterlage konnte nicht gespeichert werden.",
});
}
return {
uid: unterlage.uid
};
},
})
export const GET = defineApiRoute({
middleware: authorizationMiddleware,
output: z.array(UnterlageSchema.pick({
kategorie: true,
uid: true
})),
async fetch(input, ctx, user) {
const { uid } = ctx.params;
const objekt = await prisma.objekt.findUnique({
where: {
uid,
benutzer_id: user.id
},
select: {
benutzer_id: true,
unterlagen: {
select: {
kategorie: true,
uid: true
}
}
}
})
if (!objekt) {
throw new APIError({
code: "FORBIDDEN",
message: "Objekt existiert nicht oder gehört einem anderen Benutzer."
})
}
return objekt.unterlagen
},
})

View File

@@ -3,8 +3,8 @@ import { API_ACCESS_TOKEN_COOKIE_NAME } from "#lib/constants.js";
import { validateAccessTokenServer } from "#server/lib/validateAccessToken.js"; import { validateAccessTokenServer } from "#server/lib/validateAccessToken.js";
import { prisma } from "@ibcornelsen/database/server"; import { prisma } from "@ibcornelsen/database/server";
import { APIError, defineApiRoute } from "astro-typesafe-api/server"; import { APIError, defineApiRoute } from "astro-typesafe-api/server";
import { fileURLToPath } from "bun";
import * as fs from "fs"; import * as fs from "fs";
import { fileURLToPath } from "url";
export const GET = defineApiRoute({ export const GET = defineApiRoute({
async fetch(input, context, transfer) { async fetch(input, context, transfer) {

View File

@@ -1,25 +0,0 @@
---
import UserLayout from "../../../layouts/UserLayout.astro";
import { validateAccessTokenServer } from "#server/lib/validateAccessToken";
import DashboardAusweisePruefenModule from "#modules/Dashboard/DashboardAusweisePruefenModule.svelte";
import { prisma } from "@ibcornelsen/database/server";
import { createCaller } from "#lib/caller";
const accessTokenValid = await validateAccessTokenServer(Astro);
if (!accessTokenValid) {
return Astro.redirect("/auth/login")
}
// TODO: Nutzer darf nur auf diese Seite, wenn er die Rolle "admin" hat
const caller = createCaller(Astro);
const ausweise = await caller.v1.verbrauchsausweisWohnen.getMany({
limit: 25,
});
---
<UserLayout title="Dashboard">
<DashboardAusweisePruefenModule ausweise={ausweise} client:load></DashboardAusweisePruefenModule>
</UserLayout>

View File

@@ -3,7 +3,7 @@ import { AufnahmeClient, BenutzerClient, ObjektClient, UploadedGebaeudeBild, Ver
import UserLayout from "#layouts/UserLayout.astro"; import UserLayout from "#layouts/UserLayout.astro";
import { API_ACCESS_TOKEN_COOKIE_NAME } from "#lib/constants"; import { API_ACCESS_TOKEN_COOKIE_NAME } from "#lib/constants";
import DashboardAusweisePruefenModule from "#modules/Dashboard/DashboardAusweisePruefenModule.svelte"; import DashboardAusweisePruefenModule from "#modules/Dashboard/DashboardAusweisePruefenModule.svelte";
import { prisma } from "@ibcornelsen/database/server"; import { Enums, prisma } from "@ibcornelsen/database/server";
import { createCaller } from "src/astro-typesafe-api-caller"; import { createCaller } from "src/astro-typesafe-api-caller";
const caller = createCaller(Astro) const caller = createCaller(Astro)
@@ -20,6 +20,10 @@ try {
return Astro.redirect("/auth/login") return Astro.redirect("/auth/login")
} }
if (user.rolle !== Enums.BenutzerRolle.ADMIN) {
return Astro.redirect("/dashboard")
}
const ausweise = await prisma.verbrauchsausweisWohnen.findMany({ const ausweise = await prisma.verbrauchsausweisWohnen.findMany({
where: { where: {
benutzer: { benutzer: {

View File

@@ -74,9 +74,9 @@ export const GET: APIRoute = async (Astro) => {
let pdf: Uint8Array<ArrayBufferLike> | null = null; let pdf: Uint8Array<ArrayBufferLike> | null = null;
if (ausweisart === Enums.Ausweisart.VerbrauchsausweisWohnen) { if (ausweisart === Enums.Ausweisart.VerbrauchsausweisWohnen) {
pdf = await pdfVerbrauchsausweisWohnen(ausweis, aufnahme, objekt, bilder, user); pdf = await pdfVerbrauchsausweisWohnen(ausweis as VerbrauchsausweisWohnenClient, aufnahme, objekt, bilder, user);
} else if (ausweisart === Enums.Ausweisart.VerbrauchsausweisGewerbe) { } else if (ausweisart === Enums.Ausweisart.VerbrauchsausweisGewerbe) {
pdf = await pdfVerbrauchsausweisGewerbe(ausweis, aufnahme, objekt, bilder, user); pdf = await pdfVerbrauchsausweisGewerbe(ausweis as VerbrauchsausweisGewerbeClient, aufnahme, objekt, bilder, user);
} }
return new Response(pdf, { return new Response(pdf, {
@@ -92,10 +92,10 @@ export const POST: APIRoute = async (Astro) => {
const caller = createCaller(Astro); const caller = createCaller(Astro);
const ausweis = JSON.parse(params.get("ausweis")); const ausweis = JSON.parse(params.get("ausweis") || "{}");
const aufnahme = JSON.parse(params.get("aufnahme")); const aufnahme = JSON.parse(params.get("aufnahme") || "{}");
const objekt = JSON.parse(params.get("objekt")); const objekt = JSON.parse(params.get("objekt") || "{}");
const bilder = JSON.parse(params.get("bilder")); const bilder = JSON.parse(params.get("bilder") || "{}");
const ausweisart: Enums.Ausweisart = params.get("ausweisart") const ausweisart: Enums.Ausweisart = params.get("ausweisart")
let user: BenutzerClient = {}; let user: BenutzerClient = {};

View File

@@ -1,22 +1,28 @@
#formular-box {
@apply bg-white;
}
#formular-box{@apply bg-white;} .formular-boxen {
@apply w-full ml-[2px] ring-2 ring-formular-rahmen py-4 px-0 sm:px-2 md:px-4 rounded-lg;
}
.formular-boxen{@apply w-full ml-[2px] ring-2 ring-formular-rahmen py-4 px-0 sm:px-2 md:px-4 rounded-lg} .formular-abschnitt1 {
@apply text-[1.25rem] py-[1px] px-[10px] ml-[2px] text-secondary/80 bg-black/15 ring-2 ring-black/30 rounded-sm font-bold;
}
.formular-abschnitt2 {
@apply [font-size:_clamp(16px,1vw,28px)] pl-1 justify-self-start
md:[font-size:_clamp(20px,1.25vw,36px)];
}
.bereichs-label {
@apply px-4 sm:px-0 mt-6 mb-3;
}
.formular-abschnitt1{@apply text-[1.25rem] py-[1px] px-[10px] ml-[2px] text-secondary/80 bg-black/15 ring-2 ring-black/30 rounded-sm font-bold;} .collapseBereich {
.formular-abschnitt2{@apply [font-size:_clamp(16px,1vw,28px)] pl-1 justify-self-start @apply transition-all ease-in-out duration-200 h-[auto];
md:[font-size:_clamp(20px,1.25vw,36px)] }
.bereich-box {
@apply w-full bg-gray-500/10 rounded-sm xl:rounded-md border-2 border-gray-500/15 pt-6 pb-8 px-4;
}
.bereichs-label{@apply px-4 sm:px-0 mt-6 mb-3}
.collapseBereich{@apply transition-all ease-in-out duration-200 h-[auto]}
.bereich-box {@apply w-full bg-gray-500/10 rounded-sm xl:rounded-md border-2 border-gray-500/15 pt-6 pb-8 px-4;
transform-origin: top center; transform-origin: top center;
/*background: linear-gradient( /*background: linear-gradient(
@@ -28,18 +34,23 @@
); */ ); */
} }
.input-standard {
@apply w-full grid grid-cols-[1fr_25px] items-center relative mt-2;
}
.input-noHelp {
@apply w-full grid grid-cols-[1fr] items-center relative mt-2;
}
.input-checkboxen {
@apply h-[38px] grid grid-cols-[1fr_1fr] gap-2 items-center ring-1 ring-black/15 rounded-sm bg-white;
}
.input-row {
@apply w-full min-h-[38px] grid grid-cols-[1fr_1fr] sm:grid-cols-[1fr_1fr_1fr_1fr] items-center py-[6px] px-[10px] ring-1 ring-black/15 rounded-sm bg-white;
}
.input-standard{@apply w-full grid grid-cols-[1fr_25px] items-center relative mt-2} .help-label {
.input-noHelp{@apply w-full grid grid-cols-[1fr] items-center relative mt-2} @apply bg-black/15 rounded-e-sm ring-1 ring-black/15 self-stretch;
.input-checkboxen{@apply h-[38px] grid grid-cols-[1fr_1fr] gap-2 items-center ring-1 ring-black/15 rounded-sm bg-white} }
.input-row{@apply w-full min-h-[38px] grid grid-cols-[1fr_1fr] sm:grid-cols-[1fr_1fr_1fr_1fr] items-center py-[6px] px-[10px] ring-1 ring-black/15 rounded-sm bg-white }
.help-label{@apply bg-black/15 rounded-e-sm ring-1 ring-black/15 self-stretch}
/* /*

View File

@@ -61,6 +61,7 @@ input[type="email"],
input[type="number"], input[type="number"],
input[type="password"], input[type="password"],
input[type="file"], input[type="file"],
textarea,
select{ select{
@apply p-1 min-h-[38px] ring-1 ring-black/15 rounded-sm} @apply p-1 min-h-[38px] ring-1 ring-black/15 rounded-sm}