Merge pull request #336 from IBCornelsen/main

Update
This commit is contained in:
Jens Cornelsen
2025-02-14 14:18:52 +01:00
committed by GitHub
34 changed files with 583 additions and 297 deletions

View File

@@ -17,7 +17,7 @@
"@trpc/client": "^10.45.2",
"@trpc/server": "^10.45.2",
"astro": "^4.16.17",
"astro-typesafe-api": "^0.2.1",
"astro-typesafe-api": "^0.2.2",
"body-scroll-lock": "^4.0.0-beta.0",
"buffer": "^6.0.3",
"bun": "^1.1.45",
@@ -656,7 +656,7 @@
"astro": ["astro@4.16.18", "", { "dependencies": { "@astrojs/compiler": "^2.10.3", "@astrojs/internal-helpers": "0.4.1", "@astrojs/markdown-remark": "5.3.0", "@astrojs/telemetry": "3.1.0", "@babel/core": "^7.26.0", "@babel/plugin-transform-react-jsx": "^7.25.9", "@babel/types": "^7.26.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.1.3", "@types/babel__core": "^7.20.5", "@types/cookie": "^0.6.0", "acorn": "^8.14.0", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "boxen": "8.0.1", "ci-info": "^4.1.0", "clsx": "^2.1.1", "common-ancestor-path": "^1.0.1", "cookie": "^0.7.2", "cssesc": "^3.0.0", "debug": "^4.3.7", "deterministic-object-hash": "^2.0.2", "devalue": "^5.1.1", "diff": "^5.2.0", "dlv": "^1.1.3", "dset": "^3.1.4", "es-module-lexer": "^1.5.4", "esbuild": "^0.21.5", "estree-walker": "^3.0.3", "fast-glob": "^3.3.2", "flattie": "^1.1.1", "github-slugger": "^2.0.0", "gray-matter": "^4.0.3", "html-escaper": "^3.0.3", "http-cache-semantics": "^4.1.1", "js-yaml": "^4.1.0", "kleur": "^4.1.5", "magic-string": "^0.30.14", "magicast": "^0.3.5", "micromatch": "^4.0.8", "mrmime": "^2.0.0", "neotraverse": "^0.6.18", "ora": "^8.1.1", "p-limit": "^6.1.0", "p-queue": "^8.0.1", "preferred-pm": "^4.0.0", "prompts": "^2.4.2", "rehype": "^13.0.2", "semver": "^7.6.3", "shiki": "^1.23.1", "tinyexec": "^0.3.1", "tsconfck": "^3.1.4", "unist-util-visit": "^5.0.0", "vfile": "^6.0.3", "vite": "^5.4.11", "vitefu": "^1.0.4", "which-pm": "^3.0.0", "xxhash-wasm": "^1.1.0", "yargs-parser": "^21.1.1", "zod": "^3.23.8", "zod-to-json-schema": "^3.23.5", "zod-to-ts": "^1.2.0" }, "optionalDependencies": { "sharp": "^0.33.3" }, "bin": { "astro": "astro.js" } }, "sha512-G7zfwJt9BDHEZwlaLNvjbInIw2hPryyD654314KV/XT34pJU6SfN1S+mWa8RAkALcZNJnJXCJmT3JXLQStD3Lw=="],
"astro-typesafe-api": ["astro-typesafe-api@0.2.1", "", { "dependencies": { "es-codec": "^0.5.0", "globby": "^14.0.2" }, "peerDependencies": { "astro": "^4.16.17", "typescript": "^5.0.0", "zod": "^3.24.1" }, "bin": { "astro-typesafe-api": "src/cli.ts" } }, "sha512-8f0McZj9fWIzT19njJ2z/1zETnbper3ejuba93t72Xvsy6aMTEDXaIGDG3xc9KWUQ9zEcNg+VS52JNWGfYm6CQ=="],
"astro-typesafe-api": ["astro-typesafe-api@0.2.2", "", { "dependencies": { "es-codec": "^0.5.0", "globby": "^14.0.2" }, "peerDependencies": { "astro": "^4.16.17", "typescript": "^5.0.0", "zod": "^3.24.1" }, "bin": { "astro-typesafe-api": "src/cli.ts" } }, "sha512-SEHV2iPyIrdpYdYb0mIN1WmcvC61bvsCQqb/X+R4EOcFjuozJ9fJhSiFGxJMvNoxJ9S3P3GKLyDnxXvFlKq0mw=="],
"async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="],

View File

@@ -31,7 +31,7 @@
"@trpc/client": "^10.45.2",
"@trpc/server": "^10.45.2",
"astro": "^4.16.17",
"astro-typesafe-api": "^0.2.1",
"astro-typesafe-api": "^0.2.2",
"body-scroll-lock": "^4.0.0-beta.0",
"buffer": "^6.0.3",
"bun": "^1.1.45",

View File

@@ -8,8 +8,9 @@ export const createCaller = createCallerFactory({
"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/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": await import("../src/pages/api/bedarfsausweis-wohnen/index.ts"),
"bilder/[uid]": await import("../src/pages/api/bilder/[uid].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"),

View File

@@ -24,7 +24,7 @@
export let ausweisart: Enums.Ausweisart;
</script>
<div class="grid grid-cols-2 gap-x-6 mt-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-6 mt-6">
<div class="box card mb-0 relative">
@@ -65,8 +65,6 @@
<DatenblattButton {ausweis} {aufnahme} {objekt} bilder={images} {ausweisart} />
</div>
</div>
</div>

View File

@@ -1,15 +1,11 @@
<script lang="ts">
import AusweisWeiter from "#modules/VerbrauchsausweisWohnen/AusweisWeiter.svelte";
import Hilfe from "#components/Ausweis/Hilfe.svelte";
import {
AufnahmeClient,
BedarfsausweisWohnenClient,
BenutzerClient,
ObjektClient,
UploadedGebaeudeBild,
VerbrauchsausweisGewerbeClient,
VerbrauchsausweisWohnenClient,
} from "./types.js";
import { ausweisSpeichern } from "#client/lib/ausweisSpeichern.js";
import { validateAccessTokenClient } from "#client/lib/validateAccessToken.js";
import { AufnahmeClient, BedarfsausweisWohnenClient, BenutzerClient, ObjektClient, UploadedGebaeudeBild, VerbrauchsausweisGewerbeClient, VerbrauchsausweisWohnenClient } from "#components/Ausweis/types.js";
import Overlay from "#components/Overlay.svelte";
import EmbeddedAuthFlowModule from "#modules/EmbeddedAuthFlowModule.svelte";
import { Enums } from "@ibcornelsen/database/client";
export let ausweis: VerbrauchsausweisWohnenClient | VerbrauchsausweisGewerbeClient | BedarfsausweisWohnenClient;
@@ -19,7 +15,46 @@
export let aufnahme: AufnahmeClient;
export let ausweisart: Enums.Ausweisart
export let spaeterWeitermachen;
async function ausweisAbschicken() {
if (!await validateAccessTokenClient()) {
loginOverlayHidden = false;
return
}
loginOverlayHidden = true
const result = await ausweisSpeichern(ausweis, objekt, aufnahme, bilder, ausweisart);
if (result !== null) {
window.history.pushState(
{},
"",
`${location.pathname}?uid=${ausweis.uid}`
);
window.location.href = `/kundendaten?uid=${ausweis.uid}`;
}
}
async function spaeterWeitermachen() {
if (!await validateAccessTokenClient()) {
loginOverlayHidden = false;
return
}
loginOverlayHidden = true
const result = await ausweisSpeichern(ausweis, objekt, aufnahme, bilder, ausweisart);
if (result !== null) {
window.history.pushState(
{},
"",
`${location.pathname}?uid=${ausweis.uid}`
);
}
}
let loginOverlayHidden = true;
</script>
<div
@@ -33,14 +68,13 @@
>Später Weitermachen
</button>
<div class="">
<AusweisWeiter
bind:ausweis
bind:bilder
bind:user
bind:objekt
bind:aufnahme
{ausweisart}
></AusweisWeiter>
<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>

View File

@@ -8,17 +8,17 @@
export let objekt: ObjektClient;
</script>
<div class="grid grid-cols-2 gap-x-6 mt-6">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-x-6 mt-6">
<div class="box card mb-0">
<div class="md:box md:card mb-0">
<p class="font-bold mb-2">
<div class="font-bold mb-2">
<span class="text-red-500">WICHTIG:</span>
Bild Upload - Wärmedämmung (2 bis 4 Bilder)
</p>
</div>
<p>
<div>
Seit Mai 2021 wird das neue Gebäudeenergiegesetz (GEG) in
Deutschland angewendet. Daher werden von nun an Bilder vom Gebäude
zur Einschätzung der Modernisierungsempfehlungen benötigt. Hierfür
@@ -26,23 +26,17 @@
Gebäudehülle und der Anlagentechnik (Wärmeerzeuger etc.) zur
Verfügung gestellt werden. Diese Bilder erscheinen nicht auf Ihrem
Energieausweis.
</p>
</div>
<br />
<ol>
<li>1.Bild : Detailbild Dach bzw. des Dachinnenraums*</li>
<li>
2.Bild : <em
>(weiteres Detailbild Dach bzw. des Dachinnenraums)</em
>
</li>
<li>2.Bild :<em>(weiteres Detailbild Dach bzw. des Dachinnenraums)</em></li>
<li>3.Bild : Detailbild der Außenwand*</li>
<li>
4.Bild : <em>(weiteres Detailbild der Außenwand)</em>
</li>
<li>4.Bild : <em>(weiteres Detailbild der Außenwand)</em></li>
</ol>
<p>* erforderliches Bild</p>
<div>* erforderliches Bild</div>
<br />
<p>
<div>
Idealerweise sollte Dämmung oder nicht vorhandene Dämmung gut zu
erkennen sein. Sollte aufgrund der Verkleidung bzw. Verschalung, der
Dämmzustand nicht erkennbar sein, reicht ein Bild vom ausgebauten
@@ -50,15 +44,15 @@
beim Detailbild der Außenwand aufgrund des Wandaufbaus die Dämmung
nicht erkennbar sein, reicht ein normales Bild der Außenwand
und/oder vom Dach-Wandanschluss.
</p>
</div>
</div>
<div class="box card mb-0">
<p>
<div class="md:box md:card mb-0 mt-6 md:mt-0">
<div>
Diese Bilder erscheinen <span class="text-red-500">nicht</span> auf Ihrem
Energieausweis!<br />
<strong>Bitte laden Sie hier mind. 2 Bilder hoch:</strong>
</p>
</div>
<ImageGrid
max={4}
min={2}

View File

@@ -8,16 +8,16 @@
export let objekt: ObjektClient;
</script>
<div class="grid grid-cols-2 gap-x-6 mt-6">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-x-6 mt-6">
<div class="box card mb-0">
<div class="md:box md:card mb-0">
<p class="font-bold mb-2">
<div class="font-bold mb-2">
<span class="text-red-500">WICHTIG:</span>
Bild Upload - Fenster/Dachfenster/Türen (1 bis 4 Bilder)
</p>
</div>
<p>
<div>
Seit Mai 2021 wird das neue Gebäudeenergiegesetz (GEG) in
Deutschland angewendet. Daher werden von nun an Bilder vom Gebäude
zur Einschätzung der Modernisierungsempfehlungen benötigt. Hierfür
@@ -25,36 +25,30 @@
Gebäudehülle und der Anlagentechnik (Wärmeerzeuger etc.) zur
Verfügung gestellt werden. Diese Bilder erscheinen nicht auf Ihrem
Energieausweis.
</p>
<br />
</div>
<br />
<ol>
<li>1. Bild : Exemplarisches Bild eines Fensters*</li>
<li>
2.Bild : <em>(Im Baualter abweichendes Fenster)</em>
</li>
<li>
3.Bild : <em>(Im Baualter abweichendes Fenster)</em>
</li>
<li>
4.Bild : <em>(wenn möglich, Bild der Haustür)</em>
</li>
<li>2.Bild : <em>(Im Baualter abweichendes Fenster)</em></li>
<li>3.Bild : <em>(Im Baualter abweichendes Fenster)</em></li>
<li>4.Bild : <em>(wenn möglich, Bild der Haustür)</em></li>
</ol>
<p>* erforderliches Bild</p>
<div>* erforderliches Bild</div>
<br />
<p>
<div>
Wenn eine Fensterart bzw. Fensterqualität verbaut wurde, reicht ein
exemplarisches Bild, sonst pro Art ein Bild. Wenn möglich eine
Großaufnahme des Fensters bzw. des Fensterfalzes. Idealerweise
sollte der Datumsaufdruck am Verglasungsrahmen zu erkennen sein.
</p>
</div>
</div>
<div class="box card mb-0">
<p>
<div class="md:box md:card mb-0 mt-6 md:mt-0">
<div>
Diese Bilder erscheinen <span class="text-red-500">nicht</span> auf Ihrem
Energieausweis!<br />
<strong>Bitte laden Sie hier mind. 1 Bild hoch:</strong>
</p>
</div>
<ImageGrid
max={4}
min={1}

View File

@@ -8,17 +8,16 @@
export let gebaeude: ObjektClient;
</script>
<div class="grid grid-cols-2 gap-x-6 mt-6">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-x-6 mt-6">
<div id="getWidth" class="box card mb-0">
<div class="md:box md:card mb-0">
<p class="font-bold mb-2">
<div class="font-bold mb-2">
<span class="text-red-500">WICHTIG:</span>
Bild Upload - Heizungsanlage bzw. des Wärmeerzeugers (1 bis 4 Bilder)
</p>
</div>
<p>
<div>
Seit Mai 2021 wird das neue Gebäudeenergiegesetz (GEG) in
Deutschland angewendet. Daher werden von nun an Bilder vom Gebäude
zur Einschätzung der Modernisierungsempfehlungen benötigt. Hierfür
@@ -26,31 +25,31 @@
Gebäudehülle und der Anlagentechnik (Wärmeerzeuger etc.) zur
Verfügung gestellt werden. Diese Bilder erscheinen nicht auf Ihrem
Energieausweis.
</p>
</div>
<br />
<ol class="">
<ol>
<li>1.Bild : Heizungsraum mit Heizkessel bzw. Heizungsanlage*</li>
<li>2.Bild : <em>(wenn möglich, Warmwasserleitungen bzw. die Heizungsrohre)</em></li>
<li>3.Bild : <em>(wenn möglich, Detailbild des Wärmeerzeugers bzw. Heizkessel)</em></li>
<li>4.Bild : <em>(wenn möglich, Bild des Typenschildes der Heizung)</em></li>
</ol>
<br>
<p>* erforderliches Bild</p>
<div>* erforderliches Bild</div>
<br />
<p>
<div>
Idealerweise sollte der Heizungsraum mit Heizkessel bzw.
Heizungsanlage fotografiert werden. Die Warmwasserleitungen bzw. die
Heizungsrohre sollten gut sichtbar sein und vorhandene bzw. nicht
vorhandene Dämmung sollte erkennbar sein.
</p>
</div>
</div>
<div class="box card mb-0">
<p>
<div class="md:box md:card mb-0 mt-6 md:mt-0">
<div>
Diese Bilder erscheinen <span class="text-red-500">nicht</span> auf
Ihrem Energieausweis!<br />
<strong>Bitte laden Sie hier mind. 1 Bild hoch:</strong>
</p>
</div>
<ImageGrid
name={"heizung_image"}

View File

@@ -1,5 +1,8 @@
<script lang="ts">
export let progress: number = 0;
export let step1: string;
export let step2: string;
export let step3: string;
</script>
@@ -13,19 +16,18 @@
</div>
</div>
<div class="phase justify-self-start">
<div class="point">1</div>
<div class="{step1} point">1</div>
<div class="">Gebäudedaten</div>
</div>
<div class="phase">
<div class="point">2</div>
<div class="{step2} point">2</div>
<div class="">Kundendaten</div>
</div>
<div class="phase justify-self-end">
<div class="point">3</div>
<div class="{step3} point">3</div>
<div class="">Kaufbestätigung</div>
</div>
@@ -35,7 +37,8 @@
<style lang="postcss">
.phase{@apply grid grid-cols-1 items-center justify-items-center z-10;
.point{@apply rounded-full w-8 h-8 text-white font-bold bg-secondary text-center pt-1 ring-white ring-4;}
.point{@apply rounded-full w-8 h-8 text-white font-bold bg-gray-300 text-center pt-1 ring-white ring-4;}
.step{@apply bg-secondary}
}
</style>

View File

@@ -27,7 +27,7 @@
class="bereich-box grid
grid-cols-1 gap-x-4 gap-y-2
sm:grid-cols-2 sm:gap-x-6 sm:gap-y-8
sm:grid-cols-1 sm:gap-x-6 sm:gap-y-1
md:grid-cols-2 md:gap-x-6 md:gap-y-8
xl:grid-cols-2 xl:gap-x-8 xl:gap-y-8

View File

@@ -25,9 +25,9 @@
<div
id="sanierungszustand"
class="bereich-box grid
grid-cols-1 gap-x-4 gap-y-2
grid-cols-1 gap-x-4 gap-y-1
sm:grid-cols-2 sm:gap-x-6 sm:gap-y-8
sm:grid-cols-1 sm:gap-x-6 sm:gap-y-1
md:grid-cols-2 md:gap-x-6 md:gap-y-8
xl:grid-cols-2 xl:gap-x-8 xl:gap-y-8
@@ -122,104 +122,5 @@
<HeizungImage bind:images bind:ausweis bind:gebaeude={objekt} />
<!--
<div class="GRB3">
<div class="form-group col-md-12">
<HelpLabel title="Wärmedämmung (bitte zutreffendes ankreuzen) *">
Wir benötigen diese Angaben um den allgemeinen Modernisierungsstand
einschätzen zu können. Bitte setzen Sie den Haken wenn zutreffend.
Das seit Mai 2021 gültige GEG erfordert eine genauere Prüfung anhand
von Fotos. Bitte laden Sie ein oder mehrere Fotos der Außenwand und
des Dachbereiches hoch.
</HelpLabel>
<hr />
</div>
<div class="grid grid-cols-4">
<div class="form-group col-md-3 mw1">
<label class="checkbox-inline"
><input
type="checkbox"
class="checkbox"
name="aussenwand_gedaemmt"
bind:checked={aufnahme.aussenwand_gedaemmt}
value="AWD"
/>Außenwand gedämmt</label
>
</div>
<div class="form-group col-md-3 mw1">
<label class="checkbox-inline"
><input
type="checkbox"
class="checkbox"
name="keller_wand_gedaemmt"
bind:checked={aufnahme.keller_wand_gedaemmt}
value="KWD"
/>Kelleraußenwand gedämmt</label
>
</div>
<div class="form-group col-md-3 mw1">
<label class="checkbox-inline"
><input
type="checkbox"
class="checkbox"
name="keller_decke_gedaemmt"
bind:checked={aufnahme.keller_decke_gedaemmt}
value="KDD"
/>Kellerdecke gedämmt</label
>
</div>
<div class="form-group col-md-3 mw1">
<label class="checkbox-inline"
><input
type="checkbox"
class="checkbox"
name="dachgeschoss_gedaemmt"
bind:checked={aufnahme.dachgeschoss_gedaemmt}
value="DGD"
/>Dachgeschoss gedämmt</label
>
</div>
<div class="form-group col-md-3 mw1">
<label class="checkbox-inline"
><input
type="checkbox"
class="checkbox"
name="oberste_geschossdecke_gedaemmt"
bind:checked={aufnahme.oberste_geschossdecke_gedaemmt}
value="OGDDW"
/>Oberste Geschossdecke gedämmt</label
>
</div>
<div class="form-group col-md-6 mw1">
<label class="checkbox-inline"
><input
type="checkbox"
class="checkbox"
name="oberste_geschossdecke_min_12cm_gedaemmt"
bind:checked={aufnahme.oberste_geschossdecke_min_12cm_gedaemmt}
value="OGDD"
/>Oberste Geschossdecke min. 12cm gedämmt</label
>
</div>
</div>
</div>
<hr />
<DaemmungImage bind:images bind:ausweis bind:gebaeude />
<hr />
<Label
>H - Hier können Sie ein Gebäudebild hochladen und sich Ihren Energieausweis
als PDF anschauen</Label
>
<AusweisPreviewContainer bind:images bind:ausweis bind:gebaeude />
-->
<style lang="postcss">
</style>

View File

@@ -1,6 +1,5 @@
<script lang="ts">
import DaemmungImage from "./DaemmungImage.svelte";
//import Label from "../Label.svelte";
import {
@@ -26,8 +25,8 @@
id="sanierungszustand"
class="bereich-box grid
grid-cols-1 gap-x-4 gap-y-2
sm:grid-cols-2 sm:gap-x-6 sm:gap-y-8
sm:grid-cols-1 sm:gap-x-6 sm:gap-y-1
md:grid-cols-2 md:gap-x-6 md:gap-y-8
xl:grid-cols-2 xl:gap-x-8 xl:gap-y-8

View File

@@ -113,13 +113,13 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
"
>
<!-- primäre Heizquellen -->
<div class="grid grid-cols-2 gap-x-4 order-2 md:order-2 xl:order-2">
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-4 order-1 md:order-1 xl:order-1">
<div class="input-standard">
<Inputlabel title="Heizquellen"></Inputlabel>
<div class="input-checkboxen">
<div
class="grid grid-cols-[40px_max-content] items-center justify-items-start"
class="grid grid-cols-[25px_max-content] items-center justify-items-start"
>
<input
id="primaere_heizquelle"
@@ -142,10 +142,10 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
</div>
<div class="input-standard">
<Inputlabel title="Heizquellen"></Inputlabel>
<div class="input-checkboxen">
<div
class="grid grid-cols-[40px_max-content] items-center justify-items-start"
class="grid grid-cols-[25px_max-content] items-center justify-items-start"
>
{#if !ausweis.zusaetzliche_heizquelle}
<input

View File

@@ -26,7 +26,7 @@ $: {
<div class="input-row items-center gap-2">
<div class="grid grid-cols-[max-content_40px] gap-x-2 items-center justify-items-start">
<div class="grid grid-cols-[25px_max-content] items-center justify-items-start">
<input
id="warmwasser_enthalten"
type="checkbox"
@@ -38,7 +38,7 @@ $: {
<label for="warmwasser_enthalten" class="cursor-pointer">Ja</label>
</div>
<div class="grid grid-cols-[40px_max-content] items-center justify-items-start">
<div class="grid grid-cols-[25px_max-content] items-center justify-items-start">
<input
id="warmwasser_anteil_bekannt"
type="checkbox"

View File

@@ -1,12 +1,12 @@
<script lang="ts">
import { ripple } from "svelte-ripple-action";
import type { RippleOptions } from "svelte-ripple-action/dist/constants";
import type { RippleOptions } from "svelte-ripple-action/dist/constants.js";
import { Home, Reader, EnvelopeClosed, Cube, Bell, Gear, LockClosed, HamburgerMenu } from "radix-svelte-icons"
import NotificationProvider from "#components/NotificationProvider/NotificationProvider.svelte";
import DashboardNotification from "./DashboardNotification.svelte";
import { notifications } from "#components/NotificationProvider/shared";
import { notifications } from "#components/NotificationProvider/shared.js";
import ThemeController from "#components/ThemeController.svelte";
import { BenutzerClient } from "#components/Ausweis/types";
import { BenutzerClient } from "#components/Ausweis/types.js";
import Cross1 from "radix-svelte-icons/src/lib/icons/Cross1.svelte";
export let lightTheme: boolean;

View File

@@ -16,7 +16,7 @@
}
try {
const result = await api.postleitzahlen.GET.fetch({ plz: zip });
const result = await api.postleitzahlen.GET.fetch({ plz: zip, limit: 10 });
if (result.length > 0) {
zipCodes = result;
@@ -45,7 +45,7 @@
</script>
<div class="" use:clickOutside={() => {
<div use:clickOutside={() => {
hideZipDropdown = true;
}}>
@@ -65,13 +65,19 @@
maxlength="5"
/>
<div data-test="plz-container" class="absolute top-[calc(100%+4px)] left-0 w-full bg-white py-2 shadow-md rounded-lg z-50" hidden={hideZipDropdown}>
<div data-test="plz-container" class="absolute top-[calc(100%+4px)] flex flex-col left-0 bg-white py-1 shadow-md rounded-lg z-10" class:hidden={hideZipDropdown}>
{#each zipCodes as zipCode}
<div class="hover:bg-gray-100 cursor-pointer px-2 py-0.5 text-nowrap" tabindex="-1" on:click={() => {
<button class="hover:bg-gray-100 cursor-pointer px-2 py-1 text-nowrap" tabindex="-1" on:click={() => {
zip = zipCode.plz;
city = zipCode.stadt;
hideZipDropdown = true;
}}>{zipCode.plz}, {zipCode.stadt}</div>
}}>{zipCode.plz}, {zipCode.stadt}</button>
{/each}
</div>
</div>
</div>
<style>
button:not(:last-of-type) {
@apply border-b border-b-gray-200;
}
</style>

View File

@@ -8,6 +8,7 @@
export let src: string;
export let alt: string;
export let empfehlung: string;
export let cta: string;
</script>
@@ -35,7 +36,7 @@
<div class="sumCent buttoncols">
<a
href="{href_buy}"
class="buttoncol">jetzt&nbsp;online erstellen</a
class="buttoncol">{cta}</a
>
<a

View File

@@ -5,15 +5,15 @@
let collapse: HTMLDivElement;
</script>
<div class="bereichs-label">
<div>
<div class="bereichs-label w-full">
<div
class="grid grid-cols-[max-content_1fr_40px] items-center justify-items-start gap-2"
class="grid grid-cols-[max-content_1fr_max-content] items-center justify-items-start gap-2"
>
<div class="formular-abschnitt1">{bereich}</div>
<div class="formular-abschnitt2">{title}</div>
<div
class="justify-self-center self-center cursor-pointer text-[2rem] ring-1 ring-secondary px-4 rounded-sm select-none"
class="justify-self-end self-center cursor-pointer text-[1.25rem] ring-1 ring-secondary px-2 rounded-sm select-none"
on:click={() => {
open = !open
}}
@@ -26,7 +26,7 @@
</div>
</div>
</div>
</div>
<div class="collapseBereich" bind:this={collapse} class:max-h-0={!open} class:overflow-hidden={!open} class:max-h-[1500px]={open} class:overflow-visible={open}>
<div class="collapseBereich" bind:this={collapse} class:max-h-0={!open} class:overflow-hidden={!open} class:max-h-[1800px]={open} class:overflow-visible={open}>
<slot></slot>
</div>

View File

@@ -12,7 +12,7 @@
collapse.style.overflow = "hidden";
} else {
//y.classList.add('hidden');
collapse.style.maxHeight = "1500px";
collapse.style.maxHeight = "1800px";
collapse.style.overflow = "visible";
}
@@ -23,13 +23,13 @@
<div class="bereichs-label" bind:this={label}>
<div>
<div
class="grid grid-cols-[max-content_1fr_40px] items-center justify-items-start gap-2"
class="grid grid-cols-[max-content_1fr_max-content] items-center justify-items-start gap-2"
>
<div class="formular-abschnitt1">{bereich}</div>
<div class="formular-abschnitt2"><slot></slot></div>
<div
class="justify-self-center self-center mr-[2px] cursor-pointer text-[2rem] ring-1 ring-secondary px-4 rounded-sm select-none"
class="justify-self-center self-center mr-[2px] cursor-pointer text-[2rem] ring-1 ring-secondary px-2 rounded-sm select-none"
on:click={toggleBereich}
>
{#if open}

View File

@@ -4,7 +4,7 @@
</script>
<div class="absolute border-x-[1px] border-t-[1px] border-black/25 px-3 top-[-1.275rem] left-[0rem] text-[0.8rem] rounded-t-sm font-sans">{title}</div>
<div class="absolute border-x-[1px] border-t-[1px] border-black/25 px-2 top-[-1.275rem] left-[0rem] text-[0.8rem] rounded-t-sm font-sans">{title}</div>

View File

@@ -210,6 +210,7 @@ threeBOX = ((ausnahme === true) && (gebaeudetyp === "Mischgebäude") && (twoBo
alt="Wohnhaus Verbrauchsausweis"
variant="einfach"
empfehlung="nein"
cta="jetzt&nbsp;online erstellen"
services={[
["3&nbsp;Jahresverbräuche der Heizung benötigt.", true],
["Zulässig bei Vermietung oder Verkauf.", true],
@@ -235,6 +236,7 @@ threeBOX = ((ausnahme === true) && (gebaeudetyp === "Mischgebäude") && (twoBo
alt="Wohnhaus Bedarfsausweis"
variant="fundiert"
empfehlung="ja"
cta="jetzt&nbsp;online erstellen"
services={[
["Erfassung der Gebäudegeometrie.", true],
["Für Vermietung, Verkauf und Finanzierung.", true],
@@ -259,7 +261,8 @@ threeBOX = ((ausnahme === true) && (gebaeudetyp === "Mischgebäude") && (twoBo
src="/images/immowelt/gewerbegebaeude_immowelt.svg"
alt="Gewerbe Verbrauchsausweis"
variant="einfach"
empfehlung="nein"
empfehlung="nein"
cta="jetzt&nbsp;online erstellen"
services={[
["3&nbsp;Jahresverbräuche von Heizung Gebäudestrom&nbsp;nötig.", true],
@@ -287,6 +290,7 @@ threeBOX = ((ausnahme === true) && (gebaeudetyp === "Mischgebäude") && (twoBo
alt="Gewerbe Bedarfsausweis"
variant="fundiert"
empfehlung="ja"
cta="Angebot anfragen"
services={[
["Mehrzonenmodell nach DIN 18599.", true],
@@ -313,6 +317,7 @@ threeBOX = ((ausnahme === true) && (gebaeudetyp === "Mischgebäude") && (twoBo
alt="Gewerbe Bedarfsausweis"
variant="Bauvorlage"
empfehlung="ja"
cta="Angebot anfragen"
services={[
["Nachweis fürs Bauamt bei Neubau oder Modernisierung.", true],
@@ -341,6 +346,7 @@ threeBOX = ((ausnahme === true) && (gebaeudetyp === "Mischgebäude") && (twoBo
alt="Gewerbe Bedarfsausweis"
variant="Bauvorlage"
empfehlung="ja"
cta="Angebot anfragen"
services={[
["Nachweis fürs Bauamt bei Neubau oder Modernisierung.", true],

View File

@@ -85,7 +85,7 @@ window.addEventListener("scroll", (event) => {
<!--<SidebarLeft />-->
<article class="box rounded-tl-none p-2 lg:p-12">
<article class="p-0 lg:p-2">
<slot />
</article>

View File

@@ -6,6 +6,8 @@ import "svelte-ripple-action/ripple.css"
import DashboardSidebar from "../components/Dashboard/DashboardSidebar.svelte"
import { validateAccessTokenServer } from "#server/lib/validateAccessToken";
import { createCaller } from "src/astro-typesafe-api-caller";
import { API_ACCESS_TOKEN_COOKIE_NAME } from "#lib/constants";
import { BenutzerClient } from "#components/Ausweis/types";
const valid = validateAccessTokenServer(Astro)
@@ -16,7 +18,11 @@ if (!valid) {
const caller = createCaller(Astro)
const benutzer = await caller.v1.benutzer.self()
const benutzer = (await caller.user.self.GET.fetch(undefined, {
headers: {
Authorization: `Bearer ${Astro.cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)?.value}`
}
})) || {} as BenutzerClient;
export interface Props {
title: string;
@@ -101,7 +107,7 @@ let lightTheme = Astro.cookies.get("theme")?.value === "light";
</head>
<body class="min-h-screen grid md:grid-cols-[300px_1fr]">
<DashboardSidebar lightTheme={lightTheme} benutzer={benutzer} client:load></DashboardSidebar>
<DashboardSidebar lightTheme={lightTheme} {benutzer} client:load></DashboardSidebar>
<main class="p-4 md:p-8 overflow-auto h-screen bg-base-100 pt-20 md:!pt-24">
<slot />
</main>

View File

@@ -4,7 +4,7 @@ import { getEmpfehlungen } from "#lib/XML/getEmpfehlungen.js";
import { Enums } from "@ibcornelsen/database/server";
import * as fs from "fs"
import moment from "moment";
import { PDFDocument, PDFFont, PDFName, PDFNumber, PDFPage, StandardFonts, TextAlignment } from "pdf-lib";
import { PDFDocument, PDFFont, PDFPage, RotationTypes, StandardFonts, TextAlignment } from "pdf-lib";
/* -------------------------------- Pdf Tools ------------------------------- */
@@ -82,7 +82,6 @@ export async function pdfVerbrauchsausweisWohnen(ausweis: VerbrauchsausweisWohne
/* -------------------------------- Seite 2 -------------------------------- */
const co2Emissionen = fillFormField("co2emissionen", berechnungen?.co2EmissionenGesamt.toString(), 8, TextAlignment.Center)
const addEnergieverbrauchSkalaPfeile = async (page: PDFPage) => {
const pfeilNachUnten = await pdf.embedPng(fs.readFileSync(new URL("../../../public/images/pfeil-nach-unten.png", import.meta.url), "base64"))
@@ -172,11 +171,7 @@ export async function pdfVerbrauchsausweisWohnen(ausweis: VerbrauchsausweisWohne
}
}
addEnergieverbrauchSkalaPfeile(pages[1])
addEnergieverbrauchSkalaPfeile(pages[2])
const primaerenergiebedarfIst = fillFormField("primaerenergiebedarf_ist", berechnungen?.primaerEnergieVerbrauchGesamt.toString())
addEnergieverbrauchSkalaPfeile(pages[2])
/* -------------------------------- Seite 3 -------------------------------- */
@@ -369,6 +364,22 @@ export async function pdfVerbrauchsausweisWohnen(ausweis: VerbrauchsausweisWohne
}
function addAnsichtsausweisLabel(page: PDFPage, font: PDFFont) {
page.drawText("Ansichtsausweis", {
x: page.getWidth() / 2 - font.heightAtSize(112) * 2.2, y: page.getHeight() - font.heightAtSize(112) / 2,
size: 112,
font,
rotate: {
type: RotationTypes.Degrees,
angle: -60
},
opacity: 0.3
})
}
for (const page of pages) {
addAnsichtsausweisLabel(page, font)
}
// pdf.getForm().flatten()

View File

@@ -3,6 +3,9 @@
export let user: BenutzerClient;
export let objekte: ObjektClient[];
console.log(objekte);
</script>
<h1 class="text-4xl font-medium my-8">Willkommen zurück, {user.vorname}!</h1>
@@ -12,7 +15,7 @@
<h1 class="text-4xl font-medium my-8">Gebäude</h1>
<div class="grid grid-cols-1 gap-4 lg:grid-cols-2">
{#each objekte as objekt}
<!-- {#each objekte as objekt}
<div class="card lg:card-side bg-base-200 card-bordered border-base-300">
<figure class="lg:w-1/2">
<img
@@ -25,5 +28,5 @@
<h4 class="text-lg font-semibold">{objekt.adresse}, {objekt.plz} {objekt.ort}</h4>
</div>
</div>
{/each}
{/each} -->
</div>

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import PerformanceScore from "#components/Ausweis/PerformanceScore.svelte";
import ProgressBar from "#components/Ausweis/Progressbar.svelte";
import Progressbar from "#components/Ausweis/Progressbar.svelte";
import Bereich from "#components/labels/Bereich.svelte";
import Ansprechpartner from "#components/Ausweis/Ansprechpartner.svelte";
import Rechnungsadresse from "#components/Ausweis/Rechnungsadresse.svelte";
@@ -30,6 +30,7 @@
export let ausweis: VerbrauchsausweisWohnenClient;
export let aufnahme: AufnahmeClient;
export let objekt: ObjektClient;
export let ausweisart: Enums.Ausweisart;
let rechnung: Partial<RechnungClient> = {
email: user.email,
@@ -89,6 +90,13 @@
0
);
const zurueck = {
[Enums.Ausweisart.VerbrauchsausweisWohnen]: `/energieausweis-erstellen/verbrauchsausweis-wohngebaeude?uid=${ausweis.uid}`,
[Enums.Ausweisart.VerbrauchsausweisGewerbe]: `/energieausweis-erstellen/verbrauchsausweis-gewerbe?uid=${ausweis.uid}`,
[Enums.Ausweisart.BedarfsausweisWohnen]: `/energieausweis-erstellen/bedarfsausweis-wohnen?uid=${ausweis.uid}`,
[Enums.Ausweisart.BedarfsausweisGewerbe]: `/energieausweis-erstellen/bedarfsausweis-gewerbe?uid=${ausweis.uid}`,
}[ausweisart]
async function speichern(e: SubmitEvent) {
e.preventDefault();
@@ -151,12 +159,14 @@
2xl:grid-cols-2 2xl:gap-x-8
"
>
<div
id="performance-box"
class="w-full box relative px-4 order-2 2xl:order-1 self-stretch grid grid-cols-1"
>
<PerformanceScore bind:ausweis bind:aufnahme bind:objekt />
</div>
<div id="performance-box" class="w-full box relative px-4 order-1 self-stretch grid grid-cols-1">
<PerformanceScore
bind:ausweis
bind:aufnahme={aufnahme}
bind:objekt={objekt}
{ausweisart}
/>
</div>
<div
id="progress-box"
@@ -166,7 +176,7 @@
<h2 class="text-primary text-xl">
Verbrauchsausweis Wohnen {PRICES.VerbrauchsausweisWohnen[0]}
</h2>
<ProgressBar progress={50} />
<Progressbar progress={0} step1={''} step2={'step'} step3={''}/>
</div>
</div>
@@ -254,7 +264,7 @@
<div
class="w-full grid grid-cols-[min-content_1fr_min-content_min-content] grid-rows-[min_content_1fr] gap-x-2 self-start justify-self-end mt-8"
>
<button class="button justify-self-start">Zurück</button>
<a class="button justify-self-start" href={zurueck}>Zurück</a>
<div></div>
@@ -262,8 +272,10 @@
<button class="button" data-cy="bestellen" on:click={bestellen}>kostenpflichtig bestellen</button>
</div>
</div>
</div>
<!--
<div class="bereich-box pr-12 mt-6">
<Pruefung
bereich="A"
@@ -390,9 +402,10 @@
</div>
{/each}
</div>
<ButtonZurueckSpeichernKaufabschluss bind:ausweis bind:aufnahme bind:objekt bind:bilder bind:user /> -->
<!-- <ButtonZurueckSpeichernKaufabschluss bind:ausweis bind:aufnahme bind:objekt bind:bilder bind:user /> -->
</div>
<style lang="postcss">
h3 {

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import { loginClient } from "#lib/login";
import { loginClient } from "#lib/login.js";
import CrossCircled from "radix-svelte-icons/src/lib/icons/CrossCircled.svelte";
import { fade } from "svelte/transition";
@@ -61,7 +61,7 @@
<span class="font-semibold">Das hat leider nicht geklappt, haben sie ihr Passwort und ihre Email Adresse richtig eingegeben?</span>
</div>
{/if}
<button class="btn btn-primary" type="submit">Einloggen</button>
<button class="button" type="submit">Einloggen</button>
<div class="flex-row justify-between" style="margin-top: 10px">
<a class="link link-hover" href="/auth/signup{redirect ? `?redirect=${redirect}` : ""}">Registrieren</a>
<a class="link link-hover" href="/auth/passwort-vergessen{redirect ? `?redirect=${redirect}` : ""}"

View File

@@ -1,46 +0,0 @@
<script lang="ts">
import { ausweisSpeichern } from "#client/lib/ausweisSpeichern.js";
import { validateAccessTokenClient } from "#client/lib/validateAccessToken.js";
import { AufnahmeClient, BedarfsausweisWohnenClient, BenutzerClient, ObjektClient, UploadedGebaeudeBild, VerbrauchsausweisGewerbeClient, VerbrauchsausweisWohnenClient } from "#components/Ausweis/types.js";
import Overlay from "#components/Overlay.svelte";
import EmbeddedAuthFlowModule from "#modules/EmbeddedAuthFlowModule.svelte";
import { Enums } from "@ibcornelsen/database/client";
export let objekt: ObjektClient;
export let bilder: UploadedGebaeudeBild[];
export let ausweis: VerbrauchsausweisWohnenClient | VerbrauchsausweisGewerbeClient | BedarfsausweisWohnenClient;
export let user: BenutzerClient;
export let aufnahme: AufnahmeClient;
export let ausweisart: Enums.Ausweisart
async function ausweisAbschicken() {
if (!await validateAccessTokenClient()) {
loginOverlayHidden = false;
return
}
loginOverlayHidden = true
const result = await ausweisSpeichern(ausweis, objekt, aufnahme, bilder, ausweisart);
if (result !== null) {
window.history.pushState(
{},
"",
`${location.pathname}?uid=${ausweis.uid}`
);
window.location.href = `/kundendaten?uid=${ausweis.uid}`;
}
}
let loginOverlayHidden = true;
</script>
<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>

View File

@@ -193,7 +193,7 @@ lg:grid-cols-2 lg:gap-x-6
<h1 class="text-secondary text-3xl m-0">Energiesausweis erstellen</h1>
<h2 class="text-primary text-xl">{ausweisart} {PRICES.VerbrauchsausweisWohnen[0]}</h2>
<Progressbar progress={0} />
<Progressbar progress={0} step1={'step'} step2={''} step3={''}/>
</div>
</div>

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 } 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 Bedarfsausweis für Wohngebäude nach dem Schema der EnEV von 2016. Als Input wird ein bestehendes Gebäude benötigt. Falls keine UID einer bestehenden Gebäudeaufnahme mitgegeben wird, wird automatisch eine erstellt.",
tags: ["Bedarfsausweis Wohnen"],
},
input: z.object({
ausweis: BedarfsausweisWohnenSchema.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.bedarfsausweisWohnen.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.bedarfsausweisWohnen.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

@@ -59,10 +59,10 @@ export const GET = defineApiRoute({
}
}
},
output: ZodOverlap<OptionalNullable<ObjektClient>>(ObjektSchema.omit({
output: ObjektSchema.omit({
benutzer_id: true,
id: true
})),
}),
middleware: authorizationMiddleware,
async fetch(input, ctx, user) {
const { uid } = ctx.params;

View File

@@ -88,6 +88,6 @@ if (!ausweis || !user) {
---
<AusweisLayout title="Kundendaten Aufnehmen - IBCornelsen">
<KundendatenModule {user} {ausweis} {objekt} {aufnahme} selectedPaymentType={Enums.Bezahlmethoden.paypal} client:load></KundendatenModule>
<KundendatenModule {user} {ausweis} {objekt} {aufnahme} {ausweisart} selectedPaymentType={Enums.Bezahlmethoden.paypal} client:load></KundendatenModule>
</AusweisLayout>

View File

@@ -62,13 +62,13 @@ input[type="number"],
input[type="password"],
input[type="file"],
select{
@apply min-h-[38px] ring-1 ring-black/15 rounded-sm}
@apply p-1 min-h-[38px] ring-1 ring-black/15 rounded-sm}
input[type="file"]{@apply pt-[4px]}
input[type="checkbox"],input[type="radio"]{@apply inline-block accent-secondary h-[13px]}
input:disabled, input:read-only, select:disabled {
input:disabled, input:read-only {
@apply bg-gray-200 border-gray-500/15;
}