35 Commits

Author SHA1 Message Date
Carl Mahnke
d6d141a229 Checkboxen Gedämmt nur setzen wenn initial (undefined) 2024-03-21 14:45:35 +01:00
Moritz Utcke
12d7f11c1b Empfehlungen und Ansichtsausweis 2024-03-20 12:12:35 +07:00
Moritz Utcke
19d83c286e Klimafaktoren respektiert erstellungsdatum 2024-03-19 11:52:44 +07:00
Moritz Utcke
5b6fbf686b Merge branch 'main' of https://github.com/IBCornelsen/online-energieausweis 2024-03-18 18:14:38 +07:00
Moritz Utcke
7bc549dfc4 Merge branch 'main' of https://github.com/IBCornelsen/online-energieausweis 2024-03-18 18:14:32 +07:00
Moritz Utcke
4bfc4c4baf Merge branch 'main' of https://github.com/IBCornelsen/online-energieausweis 2024-03-18 18:12:28 +07:00
Moritz Utcke
3997f288fa Update build skript 2024-03-18 18:12:22 +07:00
Moritz Utcke
a1a0a8e2d9 Merge pull request #5 from IBCornelsen/21-alle-plausibilittsprfungen-mit-infota
21 Alle Plausibilitätsprüfungen mit Infotabs fertigstellen. Dazu gehört auch die Prüfung Verbrauchsausweis zulässig/möglich. Das müssten die selben Prüfalgorithmen wie die unserer Prüfbox sein >> zusammenfassen in einem Skript
2024-03-18 18:01:26 +07:00
Moritz Utcke
0b735bcf4e Merge branch 'main' into 21-alle-plausibilittsprfungen-mit-infota 2024-03-18 18:01:13 +07:00
Carl Mahnke
cc5032e0e1 Ticket Plausibilitätsprüfung 2024-03-18 11:45:05 +01:00
Moritz Utcke
8af5ed39b1 Zu bind umgedingstet 2024-03-18 17:31:36 +07:00
Moritz Utcke
25171210a5 Ansichtsausweis Verbrauch 2024-03-18 10:17:02 +07:00
Moritz Utcke
28a3f9b57c Merge branch 'main' of https://github.com/IBCornelsen/online-energieausweis 2024-03-18 09:46:49 +07:00
Moritz Utcke
63ae424665 Designer getPageCursor implementiert 2024-03-18 09:46:41 +07:00
Moritz Utcke
eb41d87f1c Bilder base64 Admin fix 2024-03-18 09:46:06 +07:00
Moritz Utcke
b45206290f Bilder Rotieren 2024-03-16 11:01:25 +07:00
UMBENOMENA
c3d9913cc7 Merge pull request #3 from IBCornelsen/UMBE
UMBE files für Templates
2024-03-14 16:00:26 +01:00
Robert Jagtiani
918b470639 UMBE files für Templates 2024-03-14 15:55:12 +01:00
Moritz Utcke
df8fa9cce1 Ausweis ohne Nutzer Referenz speichern 2024-03-13 09:52:18 +07:00
Moritz Utcke
39f4435386 Bugfixes und Verbesserungen
Fast alle gelisteten Probleme in https://trello.com/c/GKZotNZV werden hiermit behoben.
2024-03-12 12:20:18 +07:00
Moritz Utcke
80d62ffdc2 Bilder im Formular anzeigen 2024-03-12 11:27:35 +07:00
Moritz Utcke
8c188a54fa Startdatum Auswahl gefixt 2024-03-12 11:22:40 +07:00
Moritz Utcke
e994383317 Automatische Berechnung Test
Automatische Prüfung der Berechnung durch Vergleich mit alten Ergebnissen auf alten Ausweisen.
2024-03-11 10:04:23 +07:00
Moritz Utcke
02e108140a Verbrauchsausweis Wohnen Berechnung Test 2024-03-10 18:19:44 +07:00
Moritz Utcke
538fc7eb01 User Tickets 2024-03-08 14:46:58 +07:00
Moritz Utcke
976afd1cd4 Verbrauchsausweis Wohnen Skripte ausgelagert. 2024-03-07 08:54:54 +07:00
Moritz Utcke
f679f215cc Dev pipeline und build Skript 2024-03-06 08:54:42 +07:00
Moritz Utcke
9964fded85 Merge branch 'main' of https://github.com/IBCornelsen/online-energieausweis 2024-03-04 14:12:41 +07:00
Moritz Utcke
4f479b9c6c Build getestet + MDX Erstmal entfernt 2024-03-04 14:11:47 +07:00
Jens Cornelsen
00e283a01c faktorKeller, ausweisart
Skripe angepasst
2024-03-02 15:00:59 +01:00
Moritz Utcke
26058e3205 Mobile Seite schön gemacht und generelle Verbesserungen 2024-03-01 14:33:13 +07:00
Moritz Utcke
db7cc9af33 Dashboard Startseite 2024-02-29 14:33:38 +07:00
Moritz Utcke
f2be7a36bd Events Fix 2024-02-29 11:24:02 +07:00
Moritz Utcke
022fe20524 Ausweis Prüfen und Email 2024-02-29 11:09:23 +07:00
Moritz Utcke
0341ea4526 Merge pull request #2 from IBCornelsen/VAOnScreenTests
VA On-Screen Checks
2024-02-29 08:39:55 +07:00
83 changed files with 2537 additions and 1115 deletions

View File

@@ -1,20 +0,0 @@
name: Build and Test
on:
push:
branches: [ dev ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Build the application
uses: actions/checkout@v3
- uses: oven-sh/setup-bun@v1
with:
bun-version: "latest"
- run: bun install
- run: bun run build
- run: bun run test:unit
- run: bun run test:e2e

View File

@@ -1,27 +1,26 @@
name: Dev Pipeline
on:
pull_request:
branches: [main]
push:
branches: [ dev ]
branches: [main]
jobs:
build:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy dev changes.
uses: appleboy/ssh-action@master
with:
host: "212.227.155.91"
username: "root"
password: "!2Zc727cI1"
port: 22
script: |
cd ~/apps/online-energieausweis
git reset --hard HEAD
git clean -f -d
git pull origin main
git status
npm install -g bun
bun install
bash build.sh
- uses: actions/checkout@v2
- uses: appleboy/ssh-action@master
with:
host: ${{ secrets.DEV_HOST }}
username: ${{ secrets.DEV_USERNAME }}
password: ${{ secrets.DEV_PASSWORD }}
port: 22
script: |
cd ~/online-energieausweis
git reset --hard HEAD
git clean -f -d
git pull origin main
git status
bash build.sh

View File

@@ -1,27 +0,0 @@
name: Production Deployment
on:
release:
types: [ created ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Deploy to production.
uses: appleboy/ssh-action@master
with:
host: "212.227.155.91"
username: "root"
password: "!2Zc727cI1"
port: 22
script: |
cd ~/apps/online-energieausweis
git reset --hard HEAD
git clean -f -d
git pull origin main
git status
npm install -g bun
bun install
bash build.sh

View File

@@ -1,5 +1,6 @@
{
"i18n-ally.localesPaths": [
"public/locales"
]
],
"typescript.tsdk": "node_modules/typescript/lib"
}

View File

@@ -12,7 +12,7 @@ import mdx from "@astrojs/mdx";
// https://astro.build/config
export default defineConfig({
integrations: [/*astroI18next(),*/svelte(), tailwind(), mdx()],
integrations: [svelte(), tailwind(), mdx()],
outDir: "./dist",
output: "server",
vite: {

View File

@@ -9,50 +9,55 @@ DB_USER="main"
DB_PASSWORD="hHMP8cd^N3SnzGRR"
DB_PORT=5432
git_pull_force() {
git reset --hard HEAD
git clean -f -d
git pull origin main
}
# Wir gehen davon aus, dass die Änderungen bereits von GitHub gepullt wurden
# Dieses Skript ist nur dafür gedacht, von GitHub bei einer Automation
# ausgeführt zu werden. Außerdem würde es nicht wirklich Sinn ergeben, wenn das
# Build Skript sich die Änderungen am build Skript holen würde...
# Wir müssen alle lokalen Pakete verlinken
cd ../database
bun link
cd ../api
bun link
cd ../ui
bun link
cd ../database
bun install
cd ../api
bun install
cd ../ui
bun install
cd ../$APP_NAME
# Zuerst müssen wir neue Änderungen von GitHub pullen.
cd ~/apps/$APP_NAME
git_pull_force;
# Dann bauen wir das Docker Image unserer Application
cd ~/apps/$APP_NAME
# Als erstes linken wir das package mit bun, damit wir z.B. in online-energieausweis darauf zugreifen können.
bun link
# Dann installieren wir noch einmal alle dependencies, das ist besonders wichtig
# falls wir lokal verlinkte Projekte haben, sonst werden die nicht in unser
# docker image übernommen
bun install
# Dann stoppen wir unser altes docker image und bauen es neu.
docker stop $APP_NAME
docker rm $APP_NAME
docker build -t $APP_NAME .
# SECTION: Startup jobs zu crontab hinzufügen.
# Erstmal den cronfile leeren.
crontab -r;
# Alle builds schlagen fehl wenn die Datenbank nicht da ist, also muss der Container zuerst gebaut werden.
(crontab -l ; echo "@reboot sudo ~/database/build.sh &") | crontab -;
(crontab -l ; echo "@reboot sudo ~/apps/online-energieausweis/build.sh &") | crontab -;
(crontab -l ; echo "@reboot sudo ~/apps/layout-tool/build.sh &") | crontab -;
# Wir legen ein persistent directory an
PERSISTENT_DIR="${HOME}/persistent/online-energieausweis";
# Jeder unserer Applikationen hat ein Verzeichnis in dem alle Dateien dauerhaft,
# Versionsunabhängig gespeichert werden. Dieses legen wir hier an, falls es noch
# nicht existiert.
PERSISTENT_DIR="${HOME}/persistent/${APP_NAME}";
mkdir -p $PERSISTENT_DIR;
# Wir legen einen .env file für unsere letsencrypt keys an.
rm -f ~/apps/$APP_NAME/.env;
touch ~/apps/$APP_NAME/.env;
echo "PRIVATE_KEY=$(cat /etc/letsencrypt/live/ibcornelsen.de/privkey.pem | base64 | tr -d '\n')" >> ~/apps/$APP_NAME/.env;
echo "CERTIFICATE=$(cat /etc/letsencrypt/live/ibcornelsen.de/fullchain.pem | base64 | tr -d '\n')" >> ~/apps/$APP_NAME/.env;
# TODO: Wir legen hier die .env Datei an, die die SSL Zertifikate enthält.
# rm -f ~/$APP_NAME/.env;
# touch ~/$APP_NAME/.env;
# echo "PRIVATE_KEY=$(cat /etc/letsencrypt/live/ibcornelsen.de/privkey.pem | base64 | tr -d '\n')" >> ~/$APP_NAME/.env;
# echo "CERTIFICATE=$(cat /etc/letsencrypt/live/ibcornelsen.de/fullchain.pem | base64 | tr -d '\n')" >> ~/$APP_NAME/.env;
# Und starten unsere App wieder.
# Jetzt wo wir alle Vorbereitungen getroffen haben, starten wir das Docker Image und linken es mit der Datenbank.
docker run -d --name $APP_NAME --link $DB_CONTAINER_NAME \
-v "${PERSISTENT_DIR}:/persistent" \
-p "${APP_PORT}:80" \
-e DB_CONNECTION=postgresql://${DB_USER}:${DB_PASSWORD}@${DB_CONTAINER_NAME}:${DB_PORT}/${DB_NAME} \
-e DB_PORT=${DB_PORT} \
--env-file ~/apps/${APP_NAME}/.env \
-v "./node_modules/@ibcornelsen/database:/${APP_NAME}/node_modules/@ibcornelsen/database" \
-v "./node_modules/@ibcornelsen/api:/${APP_NAME}/node_modules/@ibcornelsen/api" \
-v "./node_modules/@ibcornelsen/ui:/${APP_NAME}/node_modules/@ibcornelsen/ui" \
-p "${APP_PORT}:3000" \
$APP_NAME;
# Crontab Updaten
cd ~/$APP_NAME
crontab .crontab

15
install.sh Normal file
View File

@@ -0,0 +1,15 @@
git clone https://github.com/IBCornelsen/online-energieausweis
git clone https://github.com/IBCornelsen/database
git clone https://github.com/IBCornelsen/api
cd ./database
bun link
bun install
cd ../api
bun link
bun install
cd ../online-energieausweis
bun link
bun install

View File

@@ -1,5 +1,5 @@
---
layout: ../layouts/Layout.astro
layout; ../layouts/Layout.astro
title: AGB - online-energieausweis.org
---

View File

@@ -3,6 +3,7 @@ layout: ../layouts/Layout.astro
title: Energieausweis EnEV/GEG
---
# EnEV Zusammenfassung (Archiv - Seit 1. Mai 2021 abgelöst durch GEG)

View File

@@ -1,6 +1,6 @@
---
layout: ../layouts/Layout.astro
title: Welcher Energieausweis?
title: "Welcher Energieausweis?"
---
import { BoxWithHeading } from "@ibcornelsen/ui";

View File

@@ -0,0 +1,19 @@
# Ausweis Erstellung
Wenn ein neuer Nutzer auf unsere Seite kommt und einen Ausweis erstellen möchte muss er sich nicht unbedingt sofort registrieren. Um den Kunden ein reibungsloses Erlebnis zu bieten versuchen wir den Nutzer automatisch anzulegen, allerdings kann es sein, dass der Ausweis nicht weiter bearbeitet wird. In diesem Fall müssen wir den Ausweis nach einer Zeit wieder löschen, damit er nicht für immer in unserer Datenbank bleibt.
```tefcha
Nutzer Kommt auf unsere Seite
if Nutzer ist eingeloggt
Ausweis wird erstellt und Nutzer zugewiesen
else
Ausweis erstellen
-> Schritt 2
if Nutzer registriert sich
Ausweis wird verknüpft
else
Ausweis nach einer Woche gelöscht
usw...
```

View File

@@ -17,7 +17,7 @@
},
"private": true,
"dependencies": {
"@astrojs/mdx": "^0.18.4",
"@astrojs/mdx": "^2.1.1",
"@astrojs/node": "^5.1.4",
"@astrojs/svelte": "^2.2.0",
"@astrojs/tailwind": "^3.1.3",
@@ -51,17 +51,21 @@
"katex": "^0.16.7",
"knex": "^2.4.2",
"moment": "^2.29.4",
"moment-timezone": "^0.5.45",
"pg": "^8.11.0",
"radix-svelte-icons": "^1.0.0",
"remark-frontmatter": "^5.0.0",
"sass": "^1.62.1",
"svelte": "^3.59.1",
"svelte-dialogs": "^1.2.2",
"svelte-katex": "^0.1.2",
"svelte-preprocess": "^5.0.3",
"svelte-ripple-action": "^1.0.5",
"svelte-tabs": "^1.1.0",
"tailwindcss": "^3.3.2",
"trpc-openapi": "^1.2.0",
"uuid": "^9.0.0",
"uuid-validate": "^0.0.3",
"vite-tsconfig-paths": "^4.2.0",
"zod": "^3.22.4"
},

View File

@@ -0,0 +1,65 @@
import { GebaeudeClient, UploadedGebaeudeBild, VerbrauchsausweisWohnenClient } from "#components/Ausweis/types";
import { Enums } from "@ibcornelsen/database/client";
import { addNotification, updateNotification } from "@ibcornelsen/ui";
import { client } from "src/trpc";
export async function bilderHochladen(images: (UploadedGebaeudeBild & { base64?: string })[], gebaeude_uid: string) {
if (images.length == 0) {
return images;
}
// Wenn Bilder hochgeladen werden konvertieren wir sie zu base64, das heißt, dass die base64 Eigenschaft bei diesen Bildern
// existiert. Das müssen wir TypeScript nur wissen lassen, damit es uns in Ruhe lässt.
const imagesToUpload = images.filter(image => !image.uid || image.update) as unknown as { base64: string, kategorie: string, uid?: string, update: boolean }[];
if (imagesToUpload.length == 0) {
return images;
}
// Alle Bilder hochladen
const notification = addNotification({
dismissable: false,
message: "Bilder hochladen.",
subtext: `${imagesToUpload.length} Bilder werden hochgeladen, bitte haben sie Geduld.`,
timeout: 0,
type: "info"
})
for (let i = 0; i < imagesToUpload.length; i++) {
const image = imagesToUpload[i];
try {
if (image.update) {
await client.v1.bilder.update.mutate({
uid: image.uid as string,
base64: image.base64,
kategorie: image.kategorie as Enums.BilderKategorie
})
} else {
const response = await client.v1.bilder.upload.mutate({
base64: image.base64,
kategorie: image.kategorie as Enums.BilderKategorie,
gebaeude_uid
})
image.uid = response.uid
}
updateNotification(notification, {
dismissable: true,
message: "Bild hochgeladen.",
subtext: `${i + 1}/${imagesToUpload.length} Bildern wurden erfolgreich hochgeladen.`,
timeout: 3000
})
} catch (e) {
updateNotification(notification, {
dismissable: true,
message: "Bild konnte nicht hochgeladen werden.",
subtext: `Eines ihrer Bilder konnte nicht hochgeladen werden. Wir haben bereits ein Ticket erstellt und melden uns so schnell wie möglich bei ihnen.`,
timeout: 15000,
type: "error"
})
}
}
return images;
}

View File

@@ -0,0 +1,86 @@
import {
BenutzerClient,
GebaeudeAufnahmeClient,
GebaeudeClient,
UploadedGebaeudeBild,
VerbrauchsausweisWohnenClient,
} from "#components/Ausweis/types";
import { exclude } from "#lib/exclude";
import { client } from "src/trpc";
import { bilderHochladen } from "./bilderHochladen";
import { addNotification } from "@ibcornelsen/ui";
export async function verbrauchsausweisWohnenSpeichern(
ausweis: VerbrauchsausweisWohnenClient,
gebaeude: GebaeudeClient,
gebaeude_aufnahme_allgemein: GebaeudeAufnahmeClient,
images: (UploadedGebaeudeBild & { base64?: string })[],
user: BenutzerClient
) {
if (ausweis.uid) {
// Anscheinend wurde der Ausweis bereits erstellt und hat eine UID.
// Jetzt müssen wir ihn nun nur noch abspeichern.
try {
await client.v1.verbrauchsausweisWohnen[2016].speichern.mutate({
...ausweis,
gebaeude_aufnahme_allgemein: {
...exclude(
gebaeude_aufnahme_allgemein,
["erstellungsdatum", "events", "ausstellungsdatum", "rechnungen"]
),
gebaeude_stammdaten: {
...exclude(gebaeude, [
"gebaeude_bilder",
]),
},
},
});
images = await bilderHochladen(images, gebaeude.uid);
return { uid: ausweis.uid, gebaeude_uid: gebaeude.uid, gebaeude_aufnahme_uid: gebaeude_aufnahme_allgemein.uid };
} catch (e) {
// TODO: Ticket mit Fehldermeldung abschicken.
}
} else {
// Wir speichern den Ausweis ab und leiten auf die "ausweis-gespeichert" Seite weiter.
try {
const response =
await client.v1.verbrauchsausweisWohnen[2016].erstellen.mutate({
...ausweis,
gebaeude_aufnahme_allgemein: {
...gebaeude_aufnahme_allgemein,
gebaeude_stammdaten: {
...gebaeude,
},
},
});
images = await bilderHochladen(images, response.gebaeude_uid);
return response;
} catch (e: any) {
await client.v1.tickets.erstellen.mutate({
titel: "Ausweis konnte nicht gespeichert werden",
beschreibung: e.stack,
email: user.email ?? "",
metadata: JSON.stringify({
ausweis,
}),
});
// TODO: Ticket mit Fehldermeldung abschicken.
}
}
addNotification({
dismissable: false,
message:
"Ausweis konnte nicht gespeichert werden, bitte versuchen sie es erneut.",
subtext:
"Sollte das Problem weiterhin bestehen, kontaktieren sie bitte den Support.",
timeout: 6000,
type: "error",
});
return null;
}

View File

@@ -1,17 +1,10 @@
<script lang="ts">
import type { BedarfsausweisWohnen, VerbrauchsausweisGewerbe } from "@ibcornelsen/database/client";
import { Buffer } from "buffer";
import { GebaeudeClient, VerbrauchsausweisWohnenClient } from "./Ausweis/types";
import { VerbrauchsausweisWohnenClient } from "./Ausweis/types";
export let ausweis: VerbrauchsausweisWohnenClient | VerbrauchsausweisGewerbe | BedarfsausweisWohnen;
export let gebaeude: GebaeudeClient;
export let ausweis: VerbrauchsausweisWohnenClient;
let base64: string = "";
$: {
if (ausweis && gebaeude) {
base64 = Buffer.from(JSON.stringify({...ausweis, gebaeude_stammdaten: gebaeude}), "utf-8").toString("base64");
}
}
$: base64 = Buffer.from(JSON.stringify(ausweis), "utf-8").toString("base64")
</script>
<a class="border-2 rounded-lg w-[30%] bg-white text-center hover:shadow-md no-underline p-6 cursor-pointer" target="_blank" href="/pdf/ansichtsausweis?base64={base64}">

View File

@@ -5,8 +5,6 @@
import ImageGrid from "../ImageGrid.svelte";
import {
Enums,
type BedarfsausweisWohnen,
type VerbrauchsausweisGewerbe,
} from "@ibcornelsen/database/client";
import {
@@ -15,12 +13,8 @@
VerbrauchsausweisWohnenClient,
} from "./types";
export let ausweis:
| VerbrauchsausweisWohnenClient
| VerbrauchsausweisGewerbe
| BedarfsausweisWohnen;
export let ausweis: VerbrauchsausweisWohnenClient;
export let gebaeude: GebaeudeClient;
export let images: UploadedGebaeudeBild[] = [];
</script>
@@ -53,8 +47,8 @@
<hr class="trenner_form_100" />
<div class="flex flex-row gap-4">
<AnsichtsausweisButton {ausweis} {gebaeude} />
<DatenblattButton {ausweis} {gebaeude} />
<AnsichtsausweisButton {ausweis} />
<DatenblattButton {ausweis} />
</div>
</div>
</div>

View File

@@ -139,7 +139,7 @@
required
bind:value={gebaeude_aufnahme_allgemein.saniert}
>
<option disabled>Bitte auswählen</option>
<option disabled selected value={false}>Bitte auswählen</option>
<option value={true}>saniert</option>
<option value={false}>unsaniert</option>
</select>

View File

@@ -5,10 +5,6 @@
import DaemmungImage from "./DaemmungImage.svelte";
import FensterImage from "./FensterImage.svelte";
import Label from "../Label.svelte";
import type {
BedarfsausweisWohnen,
VerbrauchsausweisGewerbe,
} from "@ibcornelsen/database/client";
import {
GebaeudeAufnahmeClient,
GebaeudeClient,
@@ -18,10 +14,7 @@
export let gebaeude: GebaeudeClient;
export let gebaeude_aufnahme_allgemein: GebaeudeAufnahmeClient;
export let ausweis:
| VerbrauchsausweisWohnenClient
| VerbrauchsausweisGewerbe
| BedarfsausweisWohnen;
export let ausweis: VerbrauchsausweisWohnenClient
export let images: UploadedGebaeudeBild[];
</script>
@@ -388,4 +381,4 @@
als PDF anschauen</Label
>
<AusweisPreviewContainer {ausweis} {gebaeude} />
<AusweisPreviewContainer bind:images bind:ausweis bind:gebaeude />

View File

@@ -1,17 +1,31 @@
<script lang="ts">
import { endEnergieVerbrauchVerbrauchsausweis_2016 } from "#lib/Berechnungen/VerbrauchsausweisWohnen/VerbrauchsausweisWohnen_2016";
import ThickArrowDown from "radix-svelte-icons/src/lib/icons/ThickArrowDown.svelte";
import { BedarfsausweisWohnenClient, GebaeudeAufnahmeClient, GebaeudeClient, VerbrauchsausweisGewerbeClient, VerbrauchsausweisWohnenClient } from "./types";
import {
BedarfsausweisWohnenClient,
GebaeudeAufnahmeClient,
GebaeudeClient,
VerbrauchsausweisGewerbeClient,
VerbrauchsausweisWohnenClient,
} from "./types";
import ThickArrowUp from "radix-svelte-icons/src/lib/icons/ThickArrowUp.svelte";
export let ausweis: VerbrauchsausweisWohnenClient;
export let gebaeude_aufnahme_allgemein: GebaeudeAufnahmeClient;
export let gebaeude: GebaeudeClient;
let maxPerformance = 250;
/**
* We use linear interpolation to scale the value between the given boundaries.
*/
function centerValueBetweenBoundaries(value: number, newMinimum: number, newMaximum: number, oldMinimum: number = 0, oldMaximum: number = 100): number {
*/
function centerValueBetweenBoundaries(
value: number,
newMinimum: number,
newMaximum: number,
oldMinimum: number = 0,
oldMaximum: number = 100
): number {
// Calculate the center point of the current range
const center = (oldMinimum + oldMaximum) / 2;
@@ -31,33 +45,62 @@
const scaledValue = shiftedValue * scalingFactor;
// Shift the scaled value back to the center of the new range
const centeredValue = scaledValue + ((newMaximum + newMinimum) / 2);
const centeredValue = scaledValue + (newMaximum + newMinimum) / 2;
return centeredValue;
}
let translation_1 = 0;
let translation_2 = 0;
$: {
(async () => {
const result = (await endEnergieVerbrauchVerbrauchsausweis_2016(ausweis));
const result = await endEnergieVerbrauchVerbrauchsausweis_2016({
...ausweis,
gebaeude_aufnahme_allgemein: {
...gebaeude_aufnahme_allgemein,
gebaeude_stammdaten: gebaeude,
},
});
console.log(result, ausweis);
if (!result) {
return
return;
}
//const primaerEnergieVerbrauch = (await ausweis.primaerEnergieBedarf);
translation_1 = Math.max(0, Math.min(100, result.endEnergieVerbrauchGesamt / maxPerformance * 100))
//translation_2 = Math.max(0, Math.min(100, primaerEnergieVerbrauch / maxPerformance * 100))
})()
translation_1 = Math.max(
0,
Math.min(
100,
(result.endEnergieVerbrauchGesamt / maxPerformance) * 100
)
);
translation_2 = Math.max(0, Math.min(100, result.primaerEnergieVerbrauchGesamt / maxPerformance * 100))
})();
}
</script>
<div class="w-full rounded-lg border-[#ffcc03] border-2 relative p-2">
<img src="/images/SKALA-910.png" alt="Energieeffizienz Skala">
<ThickArrowDown size={28} class="fill-base-content absolute top-1 transition-left duration-1000 ease-in-out"
style="left: {translation_1}%; transform: translateX({centerValueBetweenBoundaries(translation_1, 50, -150, 0, 100)}%)" />
<ThickArrowUp size={28} class="fill-base-content absolute bottom-1 transition-left duration-1000 ease-in-out"
style="left: {translation_2}%; transform: translateX({centerValueBetweenBoundaries(translation_2, 50, -150, 0, 100)}%)" />
<img src="/images/SKALA-910.png" alt="Energieeffizienz Skala" />
<ThickArrowDown
size={28}
class="fill-base-content absolute top-1 transition-left duration-1000 ease-in-out"
style="left: {translation_1}%; transform: translateX({centerValueBetweenBoundaries(
translation_1,
50,
-150,
0,
100
)}%)"
/>
<ThickArrowUp
size={28}
class="fill-base-content absolute bottom-1 transition-left duration-1000 ease-in-out"
style="left: {translation_2}%; transform: translateX({centerValueBetweenBoundaries(
translation_2,
50,
-150,
0,
100
)}%)"
/>
</div>

View File

@@ -4,13 +4,20 @@
import Label from "../Label.svelte";
import fuelList from "./brennstoffListe";
import { auditVerbrauchAbweichung } from "../Verbrauchsausweis/audits/VerbrauchAbweichung";
import type { VerbrauchsausweisGewerbe } from "@ibcornelsen/database/client";
import { GebaeudeAufnahmeClient, GebaeudeClient, VerbrauchsausweisWohnenClient } from "./types";
let availableYears = [
2018, 2019,
];
let availableMonths = [
export let gebaeude: GebaeudeClient;
export let gebaeude_aufnahme_allgemein: GebaeudeAufnahmeClient;
export let ausweis: VerbrauchsausweisWohnenClient;
// Wir dürfen bis zu 4.5 Jahre alte Klimafaktoren benutzen, also nehmen wir alle Monate seitdem und generieren daraus die Auswahl.
// Allerdings müssen wir auch berücksichtigen, dass wir drei folgende Jahre brauchen, also
// kann der Nutzer nur 36 + 18 Monate zurückgehen.
let availableDates: {
year: number;
month: number;
}[] = [];
let monthNames = [
"Januar",
"Februar",
"März",
@@ -25,9 +32,15 @@
"Dezember",
];
export let gebaeude: GebaeudeClient;
export let gebaeude_aufnahme_allgemein: GebaeudeAufnahmeClient;
export let ausweis: VerbrauchsausweisWohnenClient | VerbrauchsausweisGewerbe;
const startDate = moment(ausweis.gebaeude_aufnahme_allgemein.erstellungsdatum || Date.now()).subtract(4, "years").subtract(6, "months");
const endDate = moment(ausweis.gebaeude_aufnahme_allgemein.erstellungsdatum || Date.now()).subtract(3, "years");
for (let m = moment(startDate); m.isBefore(endDate); m.add(1, "month")) {
availableDates.push({
year: m.year(),
month: m.month(),
});
}
const fuelMap: Record<string, string[]> = {};
for (const fuel of fuelList) {
@@ -36,14 +49,15 @@
fuelMap[fuel[0]].push(fuel[1]);
}
let month: string = "01";
let year: string = "2018";
let month = ausweis.startdatum?.getMonth() || null;
let year = ausweis.startdatum?.getFullYear() || null;
$: {
if (month && year) {
ausweis.startdatum = moment(`${month}.01.${year}`).toDate();
console.log(ausweis.startdatum);
if (typeof month === "number" && typeof year === "number") {
// Wir addieren einfach 2 Tage auf das Datum, falls der Nutzer außerhalb Deutschlands und in einer anderen Zeitzone ist.
// NOTE: Das ist eine grauenvolle Lösung aber alle anderen funktionieren irgendwie nicht...
ausweis.startdatum = moment().set("month", month).set("year", year).startOf("month").add(2, "days").toDate();
}
}
@@ -170,9 +184,18 @@
required
>
<option>auswählen</option>
{#each availableMonths as m, i}
<option value={i + 1}>{m}</option>
{/each}
{#if year !== null}
{#each availableDates.filter(date => date.year == year) as date}
<option value={date.month}>{monthNames[date.month]}</option>
{/each}
{:else}
{#each Array.from(availableDates.reduce((a,c) => {
a.add(c.month);
return a;
}, new Set())) as month}
<option value={month}>{monthNames[month]}</option>
{/each}
{/if}
</select>
<select
@@ -182,8 +205,11 @@
required
>
<option>auswählen</option>
{#each availableYears as y}
<option value={y}>{y}</option>
{#each Array.from(availableDates.reduce((a,c) => {
a.add(c.year);
return a;
}, new Set())) as year}
<option value={year}>{year}</option>
{/each}
</select>
</div>
@@ -242,7 +268,7 @@
</div>
<div class="flex flex-col gap-2">
<div class="column">
<span>Verbrauch</span>
<span>Verbrauch *</span>
<input
name="verbrauch_1"
type="number"
@@ -252,7 +278,7 @@
/>
</div>
<div class="column">
<span>Verbrauch</span>
<span>Verbrauch *</span>
<input
name="verbrauch_2"
type="number"
@@ -262,7 +288,7 @@
/>
</div>
<div class="column">
<span>Verbrauch</span>
<span>Verbrauch *</span>
<input
name="verbrauch_3"
type="number"

View File

@@ -2,11 +2,9 @@ import { AppRouter } from "@ibcornelsen/api";
import { Benutzer, GebaeudeBilder } from "@ibcornelsen/database/client";
import { inferProcedureInput, inferProcedureOutput } from "@trpc/server";
export type UploadedGebaeudeBild = Omit<
GebaeudeBilder,
"id" | "gebaeude_stammdaten_id" | "uid"
> &
({ base64: string; uid?: string });
export type UploadedGebaeudeBild = inferProcedureOutput<
AppRouter["v1"]["verbrauchsausweisWohnen"]["get"]
>["gebaeude_aufnahme_allgemein"]["gebaeude_stammdaten"]["gebaeude_bilder"][0] & { base64?: string, update?: boolean };
/**

View File

@@ -6,23 +6,23 @@
} from "./Ausweis/types.js";
import AusweisPruefenTooltip from "./AusweisPruefenTooltip.svelte";
import { addNotification } from "./NotificationProvider/shared";
import { CheckCircled, CrossCircled, Image } from "radix-svelte-icons";
import ChevronDown from "radix-svelte-icons/src/lib/icons/ChevronDown.svelte";
export let ausweis: VerbrauchsausweisWohnenClient;
export let calculations: Awaited<
ReturnType<typeof endEnergieVerbrauchVerbrauchsausweis_2016>
>;
console.log(ausweis);
const gebaeude_aufnahme_allgemein = ausweis.gebaeude_aufnahme_allgemein
const ausweisArt = "VA"; // TODO: Das ist ein Platzhalter, hier muss die Ausweisart aus dem Ausweisobjekt kommen
try {
// TODO: In Zukunft sollen die Bilder von unserer API kommen, das ist allerdings noch nicht ganz fertig.
// images = JSON.parse(ausweis.images)
} catch (e) {}
const images = ausweis.gebaeude_aufnahme_allgemein.gebaeude_stammdaten.gebaeude_bilder;
let verbrauchWWGesamt_1 = "";
let verbrauchWWGesamt_2 = "";
@@ -174,7 +174,7 @@
tooltip3Z1 = "Wohnfläche in m²";
tooltip3Z2 =
gebaeude_aufnahme_allgemein.faktorKeller +
ausweis.faktorKeller +
" x " +
gebaeude_aufnahme_allgemein.flaeche +
" m² Energetische Nutzfläche (Keller " +
@@ -448,328 +448,429 @@
ausweis = ausweis;
}
let bilderModal: HTMLDialogElement;
let infoVisible = false;
</script>
<table class="table table-row border">
<tbody>
<tr>
<td width="30px"
><img
src="{StatusIcon}"
alt="Status"
class="w-8 h-8 max-w-8 max-h-8"
/>{zurueckGestellt}</td
>
<td width="150px"
>
<AusweisPruefenTooltip>
<div slot="tooltip">
<span>{gebaeude_aufnahme_allgemein.adresse} {gebaeude_aufnahme_allgemein.plz} {gebaeude_aufnahme_allgemein.ort}</span>
<br>
<span>{gebaeude_aufnahme_allgemein.gebaeudetyp}, Einheiten: {gebaeude_aufnahme_allgemein.einheiten}</span>
</div>
<span>{ausweisArt} - {ausweis.uid.split("-")[0]}</span>
<span>{moment(gebaeude_aufnahme_allgemein.erstellungsdatum).format("DD.MM.YYYY")}</span>
</AusweisPruefenTooltip></td
>
<td width="35px"
>
<AusweisPruefenTooltip>
<div slot="tooltip">
<span>Baujahr Gebäude / Baujahr Heizung</span>
</div>
<span>{gebaeude_aufnahme_allgemein.baujahr_gebaeude.join(", ")}</span>
<span>{gebaeude_aufnahme_allgemein.baujahr_heizung.join(", ")}</span>
</AusweisPruefenTooltip>
<div class="tooltip" data-tip="">
</div></td
>
<td width="45px"
>
<AusweisPruefenTooltip>
<div slot="tooltip">
<span>Wohnfläche in m²</span>
<br>
<span>{gebaeude_aufnahme_allgemein.faktorKeller} x {gebaeude_aufnahme_allgemein.flaeche}m² Energetische Nutzfläche (Keller {gebaeude_aufnahme_allgemein.keller}) in m²</span>
</div>
<span>{gebaeude_aufnahme_allgemein.flaeche}</span>
<span><strong>{calculations?.energetische_nutzfläche}</strong></span>
</AusweisPruefenTooltip>
</td
>
<td width="90px"
>
<AusweisPruefenTooltip>
<div slot="tooltip">
<span>{tooltip4Z1}</span>
<br>
<span>{tooltip4Z2}</span>
</div>
<span>{table4Z1}</span>
<span>{table4Z2}</span>
</AusweisPruefenTooltip></td
>
<td width="70px"
>
<AusweisPruefenTooltip>
<div slot="tooltip">
<span>{tooltip5Z1}</span>
<br>
<span>{tooltip5Z2}</span>
<br>
<span>{tooltip5Z3}</span>
</div>
<span>{table5Z1}</span>
<span>{table5Z2}</span>
</AusweisPruefenTooltip>
</td
>
<td width="50px"
>
<AusweisPruefenTooltip>
<div slot="tooltip">
<span>{tooltip6Z1}</span>
<br>
<span>{tooltip6Z2}</span>
</div>
<span><strong>{table6Z1}</strong></span>
<span><strong>{table6Z2}</strong></span>
</AusweisPruefenTooltip></td
>
<td width="90px"
>
<AusweisPruefenTooltip>
<div slot="tooltip">
<span>{tooltip7Z1}</span>
<br>
<span>{tooltip7Z2}</span>
</div>
<span>{table7Z1}</span>
<span>{table7Z2}</span>
</AusweisPruefenTooltip></td
>
<td width="60px"
>
<AusweisPruefenTooltip>
<div slot="tooltip">
<span>{tooltip8Z1}</span>
<br>
<span>{tooltip8Z2}</span>
</div>
<span>{table8Z1}</span>
<span>{table8Z2}</span>
</AusweisPruefenTooltip></td
>
<td width="50px"
>
<AusweisPruefenTooltip>
<div slot="tooltip">
<span>{tooltip9Z1}</span>
<br>
<span>{tooltip9Z2}</span>
</div>
<span>{table9Z1}</span>
<span><strong>{table9Z2}</strong></span>
</AusweisPruefenTooltip></td
>
<td width="100px"
>
<AusweisPruefenTooltip>
<div slot="tooltip">
<span>{tooltip10Z1}</span>
<br>
<span>{tooltip10Z2}</span>
</div>
<span>{table10Z1}</span>
<span>{table10Z2}</span>
</AusweisPruefenTooltip></td
>
<td width="110px"
>
<AusweisPruefenTooltip>
<div slot="tooltip">
<span>{tooltip11Z1}</span>
<br>
<span>{tooltip11Z2}</span>
</div>
<span>{table11Z1}</span>
<span>{table11Z2}</span>
</AusweisPruefenTooltip></td
>
<td width="50px"
>
<AusweisPruefenTooltip>
<div slot="tooltip">
<span>{tooltip12Z1}</span>
<br>
<span>{tooltip12Z2}</span>
</div>
<span>{table12Z1}</span>
<span>{table12Z2}</span>
</AusweisPruefenTooltip>
</td
>
<td width="60px"
>
<AusweisPruefenTooltip>
<div slot="tooltip">
<span>{tooltip13Z1}</span>
<br>
<span>{tooltip13Z2}</span>
</div>
<span>{table13Z1}</span>
<span>{table13Z2}</span>
</AusweisPruefenTooltip>
</td
>
<td width="45px"
>
<AusweisPruefenTooltip>
<div slot="tooltip">
<span>{tooltip14Z1}</span>
<br>
<span>{tooltip14Z2}</span>
</div>
<span>{table14Z1}</span>
<span>{table14Z2}</span>
</AusweisPruefenTooltip>
</td
>
<td width="45px"
>
<AusweisPruefenTooltip>
<div slot="tooltip">
<span>{tooltip15Z1}</span>
<br>
<span>{tooltip15Z2}</span>
</div>
<span><strong>{table15Z1}</strong></span>
<span><strong>{table15Z2}</strong></span>
</AusweisPruefenTooltip>
</td
>
<td width="50px"
>
<AusweisPruefenTooltip>
<div slot="tooltip">
<span>{tooltip16Z1}</span>
<br>
<span>{tooltip16Z2}</span>
</div>
<span><strong>{table16Z1}</strong></span>
<span><strong>{table16Z2}</strong></span>
</AusweisPruefenTooltip>
</td
>
<td title="Gebäudebilder anzeigen" width="35px"
><div
class="imagePreview"
data-imagePreview="{imagePreview}"
>
<img
src="/images/dashboard/SymbolBilder.svg"
alt="Gebäudebilder"
width="35"
height="35"
/>
</div></td
>
<td width="30px"
><div class="checkTextPreviewButton">
<img src="{symbolPruefung}" alt="Boxpruefung" />
<div
class="checkTextPreview"
style="display:none; position: absolute; background-color: black; color: white; padding: 10px; border-radius: 5px; max-width: 450px; z-index:9999;"
<div class="border rounded-box">
<table class="table table-row">
<tbody>
<tr>
<td><button on:click={() => infoVisible = !infoVisible}><ChevronDown size={22} class="transition-all {infoVisible ? "" : "rotate-180"}"></ChevronDown></button></td>
<td class="w-6 px-2"
>
{gebaeude_aufnahme_allgemein.prueftext}
{#if gebaeude_aufnahme_allgemein.erledigt}
<div class="tooltip" data-tip="Ausweis wurde ausgestellt">
<div class="rounded-full w-6 h-6 bg-success"></div>
</div>
{:else if gebaeude_aufnahme_allgemein.bestellt}
<div class="tooltip" data-tip="Ausweis wurde bestellt">
<div class="rounded-full w-6 h-6 bg-warning"></div>
</div>
{:else}
<div class="tooltip" data-tip="Ausweis ist in Bearbeitung">
<div class="rounded-full w-6 h-6 bg-error"></div>
</div>
{/if}
</td
>
<td width="150px"
>
<AusweisPruefenTooltip>
<div slot="tooltip">
<span>{gebaeude_aufnahme_allgemein.adresse} {gebaeude_aufnahme_allgemein.plz} {gebaeude_aufnahme_allgemein.ort}</span>
<br>
<span>{gebaeude_aufnahme_allgemein.gebaeudetyp}, Einheiten: {gebaeude_aufnahme_allgemein.einheiten}</span>
</div>
<span>{ausweisArt} - {gebaeude_aufnahme_allgemein.id}</span>
<span>{moment(gebaeude_aufnahme_allgemein.erstellungsdatum).format("DD.MM.YYYY")}</span>
</AusweisPruefenTooltip></td
>
<td width="35px"
>
<AusweisPruefenTooltip>
<div slot="tooltip">
<span>Baujahr Gebäude / Baujahr Heizung</span>
</div>
<span>{gebaeude_aufnahme_allgemein.baujahr_gebaeude.join(", ")}</span>
<span>{gebaeude_aufnahme_allgemein.baujahr_heizung.join(", ")}</span>
</AusweisPruefenTooltip>
<div class="tooltip" data-tip="">
</div></td
>
<td width="45px"
>
<AusweisPruefenTooltip>
<div slot="tooltip">
<span>Wohnfläche in m²</span>
<br>
<span>{ausweis.faktorKeller} x {gebaeude_aufnahme_allgemein.flaeche}m² Energetische Nutzfläche (Keller {gebaeude_aufnahme_allgemein.keller}) in m²</span>
</div>
<span>{gebaeude_aufnahme_allgemein.flaeche}</span>
<span><strong>{calculations?.energetische_nutzfläche}</strong></span>
</AusweisPruefenTooltip>
</td
>
<td width="90px"
>
<AusweisPruefenTooltip>
<div slot="tooltip">
<span>{tooltip4Z1}</span>
<br>
<span>{tooltip4Z2}</span>
</div>
<span>{table4Z1}</span>
<span>{table4Z2}</span>
</AusweisPruefenTooltip></td
>
<td width="70px"
>
<AusweisPruefenTooltip>
<div slot="tooltip">
<span>{tooltip5Z1}</span>
<br>
<span>{tooltip5Z2}</span>
<br>
<span>{tooltip5Z3}</span>
</div>
<span>{table5Z1}</span>
<span>{table5Z2}</span>
</AusweisPruefenTooltip>
</td
>
<td width="50px"
>
<AusweisPruefenTooltip>
<div slot="tooltip">
<span>{tooltip6Z1}</span>
<br>
<span>{tooltip6Z2}</span>
</div>
<span><strong>{table6Z1}</strong></span>
<span><strong>{table6Z2}</strong></span>
</AusweisPruefenTooltip></td
>
<td width="90px"
>
<AusweisPruefenTooltip>
<div slot="tooltip">
<span>{tooltip7Z1}</span>
<br>
<span>{tooltip7Z2}</span>
</div>
<span>{table7Z1}</span>
<span>{table7Z2}</span>
</AusweisPruefenTooltip></td
>
<td width="60px"
>
<AusweisPruefenTooltip>
<div slot="tooltip">
<span>{tooltip8Z1}</span>
<br>
<span>{tooltip8Z2}</span>
</div>
<span>{table8Z1}</span>
<span>{table8Z2}</span>
</AusweisPruefenTooltip></td
>
<td width="50px"
>
<AusweisPruefenTooltip>
<div slot="tooltip">
<span>{tooltip9Z1}</span>
<br>
<span>{tooltip9Z2}</span>
</div>
<span>{table9Z1}</span>
<span><strong>{table9Z2}</strong></span>
</AusweisPruefenTooltip></td
>
<td width="100px"
>
<AusweisPruefenTooltip>
<div slot="tooltip">
<span>{tooltip10Z1}</span>
<br>
<span>{tooltip10Z2}</span>
</div>
<span>{table10Z1}</span>
<span>{table10Z2}</span>
</AusweisPruefenTooltip></td
>
<td width="110px"
>
<AusweisPruefenTooltip>
<div slot="tooltip">
<span>{tooltip11Z1}</span>
<br>
<span>{tooltip11Z2}</span>
</div>
<span>{table11Z1}</span>
<span>{table11Z2}</span>
</AusweisPruefenTooltip></td
>
<td width="50px"
>
<AusweisPruefenTooltip>
<div slot="tooltip">
<span>{tooltip12Z1}</span>
<br>
<span>{tooltip12Z2}</span>
</div>
<span>{table12Z1}</span>
<span>{table12Z2}</span>
</AusweisPruefenTooltip>
</td
>
<td width="60px"
>
<AusweisPruefenTooltip>
<div slot="tooltip">
<span>{tooltip13Z1}</span>
<br>
<span>{tooltip13Z2}</span>
</div>
<span>{table13Z1}</span>
<span>{table13Z2}</span>
</AusweisPruefenTooltip>
</td
>
<td width="45px"
>
<AusweisPruefenTooltip>
<div slot="tooltip">
<span>{tooltip14Z1}</span>
<br>
<span>{tooltip14Z2}</span>
</div>
<span>{table14Z1}</span>
<span>{table14Z2}</span>
</AusweisPruefenTooltip>
</td
>
<td width="45px"
>
<AusweisPruefenTooltip>
<div slot="tooltip">
<span>{tooltip15Z1}</span>
<br>
<span>{tooltip15Z2}</span>
</div>
<span><strong>{table15Z1}</strong></span>
<span><strong>{table15Z2}</strong></span>
</AusweisPruefenTooltip>
</td
>
<td width="50px"
>
<AusweisPruefenTooltip>
<div slot="tooltip">
<span>{tooltip16Z1}</span>
<br>
<span>{tooltip16Z2}</span>
</div>
<span><strong>{table16Z1}</strong></span>
<span><strong>{table16Z2}</strong></span>
</AusweisPruefenTooltip>
</td
>
<td title="Gebäudebilder anzeigen"
><!-- Open the modal using ID.showModal() method -->
<button class="btn btn-square" on:click={() => bilderModal.showModal()}><Image size={22}></Image></button>
<dialog bind:this={bilderModal} class="modal">
<div class="modal-box flex flex-row gap-4 items-center justify-center">
{#if images.length === 0}
<div class="flex flex-col gap-4 items-center justify-center">
<p>Für diesen Ausweis sind noch keine Bilder vorhanden.</p>
<button class="btn btn-primary" tabindex="0">Erinnerung Verschicken</button>
</div>
{:else}
{#each images as image}
<div>
<h2 class="text-lg mb-4 font-bold">{image.kategorie}</h2>
<img src="/bilder/{image.uid}.webp">
</div>
</div></td
>
<td title="Ausweis anzeigen" width="50px"
><a
class="energieausweis-img"
href="/pdf/ansichtsausweis?uid={ausweis.uid}"
target="_blank"
><img
src="/images/dashboard/ausweis.jpg"
alt="Energieausweis"
/></a
></td
>
<td title="Datenblatt anzeigen" width="50px"
><a
class="energieausweis-img"
href="/pdf/datenblatt?uid={ausweis.uid}"
target="_blank"
><img
src="/images/dashboard/datenblatt.jpg"
alt="Datenblatt"
/></a
></td
>
<td
title="Ausweis stornieren und Zahlung wenn erforderlich automatisch zurückbuchen"
class="w-4 p-1"
><button
class="btn btn-xs btn-ghost"
on:click={() => stornieren(ausweis)}>S</button
></td
>
<td title="Ausweis ausstellen" class="w-4 p-1"
><button
class="btn btn-xs btn-ghost"
on:click={() => ausweisAusstellen('{gebaeude_aufnahme_allgemein.uid}')}>A</button
></td
>
<td
title="Ausweis ausstellen und per Post verschicken"
class="w-4 p-1"
><button
class="btn btn-xs btn-ghost"
on:click={() => ausweisAusstellenPost('{gebaeude_aufnahme_allgemein.uid}')}>P</button
></td
>
<td
title="E-Mail an Kunden schicken mit Erläuterungen warum der Ausweis noch nicht ausgestellt werden kann."
class="w-4 p-1"
><button
class="btn btn-xs btn-ghost"
on:click={() => ausweisnichtAusstellen('{gebaeude_aufnahme_allgemein.uid}')}>N</button
></td
>
<td
title="Bestellbestätigung nochmal schicken (Zahlung nicht erfolgreich)"
class="w-4 p-1"
><button
class="btn btn-xs btn-ghost"
on:click={() => bestellBestaetigung('{gebaeude_aufnahme_allgemein.uid}')}>B</button
></td
>
<td
title="E-Mail an Kunden schicken mit Erinnerung die Bestellung abzuschließen."
class="w-4 p-1"
><button
class="btn btn-xs btn-ghost"
on:click={() => erinnern('{gebaeude_aufnahme_allgemein.uid}')}
>E</button
></td
>
<td
title="Zum Formular mit allen Eingabedaten."
class="w-4 p-1"
><a
class="btn btn-xs btn-ghost"
target="_blank"
href="/energieausweis-erstellen/gespeichert?id={gebaeude_aufnahme_allgemein.uid}">F</a
></td
>
{@html gebaeude_aufnahme_allgemein.kontrolldatei
? `<td title="XML-Datei an das DiBT verschicken." class="w-4 p-1"><button class="btn btn-xs btn-ghost" on:click="xmlAbschicken('{gebaeude_aufnahme_allgemein.uid}')">X</button></td>`
: ""}
{@html !gebaeude_aufnahme_allgemein.registriernummer
? `<td title="Registriernummer vom DiBT anfordern." class="w-4 p-1"><button class="btn btn-xs btn-ghost" on:click="registriernummerAnfordern('{gebaeude_aufnahme_allgemein.uid}')">R</button></td>`
: ""}
</tr>
</tbody>
</table>
{/each}
{/if}
</div>
<form method="dialog" class="modal-backdrop">
<button>close</button>
</form>
</dialog></td
>
<td class="w-[30px]"
>
<AusweisPruefenTooltip>
<div slot="tooltip">
<span>{gebaeude_aufnahme_allgemein.prueftext}</span>
</div>
{#if gebaeude_aufnahme_allgemein.boxpruefung}
<CheckCircled size={22}></CheckCircled>
{:else}
<CrossCircled size={22}></CrossCircled>
{/if}
</AusweisPruefenTooltip></td
>
<td title="Ausweis anzeigen" class="w-[50px]"
><a
class="energieausweis-img"
href="/pdf/ansichtsausweis?uid={ausweis.uid}"
target="_blank"
><img
src="/images/dashboard/ausweis.jpg"
alt="Energieausweis"
class="w-full h-8"
/></a
></td
>
<td title="Datenblatt anzeigen" width="50px"
><a
class="energieausweis-img"
href="/pdf/datenblatt?uid={ausweis.uid}"
target="_blank"
><img
src="/images/dashboard/datenblatt.jpg"
alt="Datenblatt"
/></a
></td
>
<td
title="Ausweis stornieren und Zahlung wenn erforderlich automatisch zurückbuchen"
class="w-4 p-1"
><button
class="btn btn-xs btn-ghost"
on:click={() => stornieren(ausweis)}>S</button
></td
>
<td title="Ausweis ausstellen" class="w-4 p-1"
><button
class="btn btn-xs btn-ghost"
on:click={() => ausweisAusstellen('{gebaeude_aufnahme_allgemein.uid}')}>A</button
></td
>
<td
title="Ausweis ausstellen und per Post verschicken"
class="w-4 p-1"
><button
class="btn btn-xs btn-ghost"
on:click={() => ausweisAusstellenPost('{gebaeude_aufnahme_allgemein.uid}')}>P</button
></td
>
<td
title="E-Mail an Kunden schicken mit Erläuterungen warum der Ausweis noch nicht ausgestellt werden kann."
class="w-4 p-1"
><button
class="btn btn-xs btn-ghost"
on:click={() => ausweisnichtAusstellen('{gebaeude_aufnahme_allgemein.uid}')}>N</button
></td
>
<td
title="Bestellbestätigung nochmal schicken (Zahlung nicht erfolgreich)"
class="w-4 p-1"
><button
class="btn btn-xs btn-ghost"
on:click={() => bestellBestaetigung('{gebaeude_aufnahme_allgemein.uid}')}>B</button
></td
>
<td
title="E-Mail an Kunden schicken mit Erinnerung die Bestellung abzuschließen."
class="w-4 p-1"
><button
class="btn btn-xs btn-ghost"
on:click={() => erinnern('{gebaeude_aufnahme_allgemein.uid}')}
>E</button
></td
>
<td
title="Zum Formular mit allen Eingabedaten."
class="w-4 p-1"
><a
class="btn btn-xs btn-ghost"
target="_blank"
href="/energieausweis-erstellen/gespeichert?id={gebaeude_aufnahme_allgemein.uid}">F</a
></td
>
{@html gebaeude_aufnahme_allgemein.kontrolldatei
? `<td title="XML-Datei an das DiBT verschicken." class="w-4 p-1"><button class="btn btn-xs btn-ghost" on:click="xmlAbschicken('{gebaeude_aufnahme_allgemein.uid}')">X</button></td>`
: ""}
{@html !gebaeude_aufnahme_allgemein.registriernummer
? `<td title="Registriernummer vom DiBT anfordern." class="w-4 p-1"><button class="btn btn-xs btn-ghost" on:click="registriernummerAnfordern('{gebaeude_aufnahme_allgemein.uid}')">R</button></td>`
: ""}
</tr>
</tbody>
</table>
<div class:hidden={!infoVisible} class:block={infoVisible} class="py-4 border-t">
<div class="grid grid-cols-[2fr_1fr] prose max-w-full">
<div class="border-r px-8">
<h3 class="mt-0">Wichtige Daten</h3>
<table>
<tbody>
<tr>
<td>Angewendete Berechnungsformel</td>
<td><strong>EnEV 2016</strong></td>
</tr>
<tr>
<td>Berechnungsergebnis</td>
<td>{calculations?.endEnergieVerbrauchGesamt}kWh/m2/A - Energieeffizienzklasse <strong>{calculations?.energieEffizienzKlasse}</strong></td>
</tr>
<tr>
<td>Informationen des Nutzers</td>
<td>{gebaeude_aufnahme_allgemein.boxpruefung}</td>
</tr>
<tr>
<td>UID</td>
<td><strong><pre>{ausweis.uid}</pre></strong></td>
</tr>
</tbody>
</table>
</div>
<div class="px-8">
<h3 class="mt-0">Ereignisse</h3>
<ul class="timeline timeline-snap-icon max-md:timeline-compact timeline-vertical">
<li>
<div class="timeline-middle">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-5 w-5"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" /></svg>
</div>
<div class="timeline-start md:text-end mb-10">
<time class="font-mono italic">{moment(ausweis.erstellungsdatum).format("DD.MM.YYYY - HH:mm")} Uhr</time>
<div class="text-lg font-black">Ausweis erstellt</div>
</div>
<hr/>
</li>
{#each ausweis.gebaeude_aufnahme_allgemein.events as event, i}
<li>
<hr />
<div class="timeline-middle">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-5 w-5"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" /></svg>
</div>
<div class="mb-10" class:timeline-end={i % 2 == 0} class:timeline-start={i % 2 == 1}>
<time class="font-mono italic">{moment(event.date).format("DD.MM.YYYY - HH:mm")} Uhr</time>
<div class="text-lg font-black">{event.title}</div>
{event.description || ""}
</div>
<hr />
</li>
{/each}
{#if ausweis.erledigt}
<li>
<hr />
<div class="timeline-middle">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-5 w-5"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" /></svg>
</div>
<div class="mb-10 timeline-end">
<time class="font-mono italic">{moment(ausweis.ausstellungsdatum).format("DD.MM.YYYY - HH:mm")} Uhr</time>
<div class="text-lg font-black">Ausweis ausgestellt</div>
{ausweis.registriernummer ? `Registriernummer: ${ausweis.registriernummer}` : ""}
</div>
<hr />
</li>
{/if}
</ul>
</div>
</div>
</div>
</div>

View File

@@ -13,8 +13,6 @@
} from "radix-svelte-icons";
import { endEnergieVerbrauchVerbrauchsausweis_2016 } from "#lib/Berechnungen/VerbrauchsausweisWohnen/VerbrauchsausweisWohnen_2016";
import { client } from "src/trpc";
import { verbrauchsausweisWohnenCalculateFormProgress } from "#lib/VerbrauchsausweisWohnen/calculateFormProgress";
import { number } from "zod";
export let ausweis: VerbrauchsausweisWohnenClient;
export let progress: number;
@@ -62,7 +60,7 @@
<div class="card lg:card-side bg-base-200 card-bordered border-base-300">
{#if ausweis.gebaeude_aufnahme_allgemein.storniert}
<div class="absolute top-0 left-0 w-full h-full bg-[rgba(0,0,0,0.7)] z-[5] rounded-lg select-none">
<h1 class="absolute -rotate-[25deg] text-7xl tracking-wide uppercase text-red-500 border-4 border-red-500 rounded-lg top-[50%] translate-y-[-50%] left-[50%] translate-x-[-50%]">Storniert</h1>
<h1 class="absolute -rotate-[25deg] text-5xl md:text-7xl tracking-wide uppercase text-red-500 border-4 border-red-500 rounded-lg top-[50%] translate-y-[-50%] left-[50%] translate-x-[-50%]">Storniert</h1>
</div>
{/if}
<figure class="lg:w-1/2">
@@ -78,7 +76,7 @@
<DotsVertical size={15} />
</button>
<ul
tabindex="0"
tabindex="-1"
class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-64 gap-2"
>
<li>
@@ -97,15 +95,15 @@
</ul>
</div>
<div class="flex flex-row flex-wrap gap-2">
{#if ausweis.ausweisart == "VerbrauchsausweisWohnen"}
{#if ausweis.gebaeude_aufnahme_allgemein.ausweisart == "VerbrauchsausweisWohnen"}
<div class="badge badge-accent font-semibold">
Verbrauchsausweis Wohnen
</div>
{:else if ausweis.ausweisart == "BedarfsausweisWohnen"}
{:else if ausweis.gebaeude_aufnahme_allgemein.ausweisart == "BedarfsausweisWohnen"}
<div class="badge badge-accent font-semibold">
Bedarfsausweis Wohnen
</div>
{:else if ausweis.ausweisart == "VerbrauchsausweisGewerbe"}
{:else if ausweis.gebaeude_aufnahme_allgemein.ausweisart == "VerbrauchsausweisGewerbe"}
<div class="badge badge-accent font-semibold">
Verbrauchsausweis Gewerbe
</div>
@@ -159,6 +157,12 @@
: "N/A"}</span
>
</div>
<div class="flex flex-row justify-between">
<span>ID</span>
<span class="font-bold text-base-content"
>{ausweis.uid.split("-")[0]}</span
>
</div>
</div>
{/await}
<div class="card-actions justify-end mt-8">

View File

@@ -1,12 +1,13 @@
<script lang="ts">
import { ripple } from "svelte-ripple-action";
import type { RippleOptions } from "svelte-ripple-action/dist/constants";
import { Home, Reader, EnvelopeClosed, Cube, Bell, Gear, LockClosed } from "radix-svelte-icons"
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 ThemeController from "#components/ThemeController.svelte";
import { BenutzerClient } from "#components/Ausweis/types";
import Cross1 from "radix-svelte-icons/src/lib/icons/Cross1.svelte";
export let lightTheme: boolean;
export let benutzer: BenutzerClient;
@@ -15,10 +16,29 @@
center: false,
color: lightTheme ? "rgba(233,233,233,0.1)" : "rgba(113, 128, 150, 0.1)",
};
let headerOpen = false;
</script>
<aside class="hidden md:flex bg-base-100 border-r border-r-base-300 flex-col py-8">
<a href="/" class="px-8"
<header class="fixed top-0 left-0 w-full h-16 flex items-center justify-between px-4 border-b z-20">
<button on:click={() => headerOpen = !headerOpen}>
{#if headerOpen}
<Cross1 size={28}></Cross1>
{:else}
<HamburgerMenu size={28}></HamburgerMenu>
{/if}
</button>
<a href="/" class="block md:hidden"
><img
src="/images/header/logo-big.svg"
class="w-24"
alt="IBCornelsen - Logo"
/></a
>
</header>
<aside class:hidden={!headerOpen} class="fixed left-0 top-16 w-full h-[calc(100%-4rem)] flex z-30 md:relative md:h-auto md:w-auto md:top-0 md:flex bg-base-100 border-r border-r-base-300 flex-col py-8">
<a href="/" class="px-8 hidden md:block"
><img
src="/images/header/logo-big.svg"
class="w-36"
@@ -26,7 +46,7 @@
/></a
>
<div class="menu flex flex-col gap-2 mt-12 px-0">
<div class="menu flex flex-col gap-2 mt-0 md:mt-12 px-0">
<a use:ripple={rippleOptions} class="button-tab" href="/dashboard">
<Home width={22} height={22} />
Home

View File

@@ -1,17 +1,10 @@
<script lang="ts">
import type { BedarfsausweisWohnen, VerbrauchsausweisGewerbe } from "@ibcornelsen/database/client";
import { Buffer } from "buffer";
import { GebaeudeClient, VerbrauchsausweisWohnenClient } from "./Ausweis/types";
import { VerbrauchsausweisWohnenClient } from "./Ausweis/types";
export let ausweis: VerbrauchsausweisWohnenClient | VerbrauchsausweisGewerbe | BedarfsausweisWohnen;
export let gebaeude: GebaeudeClient;
export let ausweis: VerbrauchsausweisWohnenClient;
let base64: string = "";
$: {
if (ausweis && gebaeude) {
base64 = Buffer.from(JSON.stringify({...ausweis, gebaeude_stammdaten: gebaeude}), "utf-8").toString("base64");
}
}
$: base64 = Buffer.from(JSON.stringify(ausweis), "utf-8").toString("base64");
</script>
<a class="border-2 rounded-lg w-[30%] bg-white text-center hover:shadow-md no-underline p-6 cursor-pointer" target="_blank" href="/pdf/datenblatt?base64={base64}">

View File

@@ -2,6 +2,7 @@
import UploadImages from "./UploadImages.svelte";
import type { BedarfsausweisWohnen, Enums, VerbrauchsausweisGewerbe } from "@ibcornelsen/database/client";
import { GebaeudeClient, UploadedGebaeudeBild, VerbrauchsausweisWohnenClient } from "./Ausweis/types";
import { RotateCounterClockwise, Trash } from "radix-svelte-icons";
export let images: UploadedGebaeudeBild[] = [];
export let max: number = 4;
@@ -9,6 +10,25 @@
export let ausweis: VerbrauchsausweisWohnenClient | VerbrauchsausweisGewerbe | BedarfsausweisWohnen;
export let gebaeude: GebaeudeClient;
export let kategorie: Enums.BilderKategorie
async function rotateImage(image: UploadedGebaeudeBild): Promise<UploadedGebaeudeBild> {
return new Promise((resolve, reject) => {
let img = new Image();
img.src = image.base64 ? image.base64 : `/bilder/${image.uid}.webp`;
img.onload = () => {
let canvas = document.createElement("canvas");
let ctx = canvas.getContext("2d");
canvas.width = img.height;
canvas.height = img.width;
ctx?.translate(img.height / 2, img.width / 2);
ctx?.rotate((-90 * Math.PI) / 180);
ctx?.drawImage(img, -img.width / 2, -img.height / 2);
image.base64 = canvas.toDataURL("image/webp");
image.update = true;
resolve(image)
};
})
}
</script>
<div class="flex flex-col gap-4">
@@ -18,20 +38,33 @@
{#if image.kategorie == kategorie}
<div class="relative group">
<img
src="/bilder/{image.uid}.webp"
src={image.base64 ? image.base64 : `/bilder/${image.uid}.webp`}
alt={kategorie}
class="h-full rounded-lg border-2 group-hover:contrast-50 object-cover transition-all"
class="h-full max-h-96 w-full rounded-lg border-2 group-hover:contrast-50 object-cover transition-all"
/>
<button
<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="invisible group-hover:visible absolute left-[50%] top-[50%] translate-x-[-50%] translate-y-[-50%] rounded-full w-[30px] h-[30px] p-2 bg-[rgba(0,0,0,0.4)]"
class="rounded-full w-[30px] h-[30px] flex items-center justify-center p-0 bg-[rgba(0,0,0,0.4)]"
on:click={() => {
delete images[i];
images = images.filter((x) => x);
}}
>
R
<Trash size={20} color="#fff"></Trash>
</button>
<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={async () => {
let image = await rotateImage(images[i]);
images[i] = image;
images = images
}}
>
<RotateCounterClockwise size={20} color="#fff"></RotateCounterClockwise>
</button>
</div>
</div>
{/if}
{/each}

View File

@@ -2,7 +2,7 @@
<nav>
<div class="nav-card">
<div class="card-menu-option dropdown dropdown-right dropdown-hover">
<a href="/energieausweis-erstellen"
<a href="/energieausweis-erstellen/verbrauchsausweis-wohnen"
>Energieausweis erstellen</a
>
<div class="dropdown-content">

View File

@@ -0,0 +1,41 @@
<script lang="ts">
import { dialogs } from "svelte-dialogs";
import TicketPopup from "./TicketPopup.svelte";
import { addNotification } from "@ibcornelsen/ui";
async function showTicketPopup() {
const success = await dialogs.modal(TicketPopup);
if (success) {
dialogs.alert({
title: "Ticket erstellt",
text: "Ihr Support Ticket wurde erfolgreich erstellt. Wir werden uns schnellstmöglich um ihre Angelegenheit kümmern. Vielen Dank für ihre Geduld.",
dismissButtonText: "Schließen",
dismissButtonClass: "btn btn-primary",
dialogClass: "modal-box",
headerClass: "bg-base-100 text-center",
titleClass: "text-base-content text-xl font-medium",
dividerClass: "hidden",
footerClass: "bg-base-100 justify-center gap-4 mt-4",
});
} else {
dialogs.alert({
title: "Ticket erstellen fehlgeschlagen",
text: "Leider ist beim erstellen des Tickets ein Fehler aufgetreten. Bitte versuchen sie es später erneut oder kontaktieren sie uns direkt per email unter info@ib-cornelsen.de.",
dismissButtonText: "Schließen",
dismissButtonClass: "btn btn-error",
dialogClass: "modal-box",
headerClass: "bg-base-100 text-center",
titleClass: "text-base-content text-xl font-medium",
dividerClass: "hidden",
footerClass: "bg-base-100 justify-center gap-4 mt-4",
});
}
}
</script>
<button
class="btn btn-primary fixed bottom-0 right-8 rounded-b-none rounded-t-xl w-48 h-12 text-xl hover:h-14 transition-all"
on:click={showTicketPopup}
>Support Ticket</button
>

View File

@@ -0,0 +1,104 @@
<script lang="ts">
import { addNotification } from "#components/Notifications/shared";
import { client } from "src/trpc";
import { getClose } from "svelte-dialogs";
const close = getClose();
async function createTicket(e: SubmitEvent) {
e.preventDefault();
try {
await client.v1.tickets.erstellen.mutate({
beschreibung: description,
email: email,
metadata: {
category: category,
phone: phone,
},
titel: title,
})
// Ticket wurde erfolgreich erstellt
close(true)
} catch (e) {
// Beim erstellen des Tickets ist ein Fehler aufgetreten, das ist ja mal ironisch...
close(false)
}
}
let category = "";
let title = "";
let description = "";
let email = "";
let phone = "";
</script>
<form class="max-w-lg" on:submit={createTicket}>
<h1 class="text-2xl font-semibold mb-6">Ticket erstellen</h1>
<p class="mb-6">
Vielen Dank, dass sie sich die Zeit nehmen ein Support Ticket zu
erstellen. Wir werden uns schnellstmöglich um ihre Angelegenheit
kümmern. Hier können sie alle Details eintragen und uns ihr Problem
schildern.
</p>
<div class="flex flex-col gap-4">
<div>
<h4>Kategorie *</h4>
<select class="select select-bordered" bind:value={category}>
<option value="" disabled selected>Bitte Auswählen</option>
<option value="Verständnisproblem">Verständnisproblem</option>
<option value="Technischer Fehler">Technischer Fehler</option>
<option value="Feature anfordern">Feature anfordern</option>
<option value="Fehlende Funktionalität"
>Fehlende Funktionalität</option
>
</select>
</div>
<div>
<h4>Überschrift *</h4>
<input
class="input input-bordered"
type="text"
placeholder="Überschrift in einem Satz"
name="title"
bind:value={title}
required
/>
</div>
<div>
<h4>Beschreibung *</h4>
<textarea
cols="10"
rows="5"
class="textarea textarea-bordered"
placeholder="Schildern sie hier ihre Erfahrung"
bind:value={description}
required
></textarea>
</div>
<div class="flex flex-row gap-4">
<div class="w-full">
<h4>Email Adresse *</h4>
<input
class="input input-bordered"
type="email"
placeholder="Ihre Email Adresse"
name="email"
bind:value={email}
required
/>
</div>
<div class="w-full">
<h4>Telefonnummer</h4>
<input
class="input input-bordered"
type="tel"
placeholder="Ihre Telefonnumer"
name="phone"
bind:value={phone}
required
/>
</div>
</div>
<button class="btn btn-primary" type="submit">Abschicken</button>
</div>
</form>

View File

@@ -0,0 +1,10 @@
---
const currentYear = new Date().getFullYear();
---
<footer class="max-w-[1920px] w-full">
<div class="flex flex-row justify-between px-4 items-center bg-primary py-2 mt-auto">
<a class="text-white font-medium text-lg" href="/impressum">Impressum und Datenschutz</a>
<a class="text-white font-medium text-lg" href="/">© {currentYear} IB Cornelsen Hamburg.</a>
</div>
</footer>

View File

@@ -0,0 +1,45 @@
<header class="max-w-[1920px] w-full relative">
<a class="hidden md:block w-full h-48 bg-base-200" href="/">
<img
src="/images/header/header-bg.jpg"
class="w-full h-full object-cover"
alt="Hintergrund - Rollen Architektenpapier"
/>
<img
src="/images/header/logo-big.svg"
class="absolute top-4 right-0 w-[464px]"
alt="IBCornelsen - Logo"
/>
<h2
class="text-secondary font-semibold text-2xl absolute top-8 right-4"
>
Energieausweis online erstellen
</h2>
<h2
class="text-primary font-semibold text-xl absolute top-16 right-4"
>
Energieausweise nach aktueller GEG
</h2>
</a>
<div class="px-4 flex flex-row w-full md:justify-end items-center bg-primary">
<a
class="header-button hidden md:block"
href="/energieausweis-erstellen/verbrauchsausweis-erstellen"
>Energieausweis erstellen</a
>
<a class="header-button hidden md:block" href="/kontakt"
>Kontakt</a
>
<a class="header-button hidden md:block" href="/agb">AGB</a>
<a class="hamburger_menu"
><img src="/images/hamburger.png" width="22" alt="hamburger" /></a
>
</div>
</header>
<style>
.header-button {
@apply px-4 py-2 text-primary-content font-medium text-lg tracking-normal hover:bg-secondary h-full;
}
</style>

View File

@@ -0,0 +1,9 @@
---
import Navigation from "../components/UMBE_navcard.astro";
---
<div class="flex flex-col gap-6 bg-slate-200 grow">
<Navigation>
</div>

View File

@@ -0,0 +1,3 @@
<div class="flex flex-col gap-4 bg-slate-200 grow">
</div>

View File

@@ -0,0 +1,100 @@
<script>
</script>
<ul id="main-navigation">
<a class="nav-element-link">
<li class="dropdown dropdown-right dropdown-hover nav-element w-full">Energieausweis erstellen<span></span>
</a>
<ul tabindex="0" class="nav-list dropdown-content z-[1] w-full">
<li class="nav-second"><a class="nav-element-link">Verbrauchsausweis erstellen</a></li>
<li class="nav-second"><a class="nav-element-link">Bedarfsausweis erstellen</a></li>
<li class="nav-second"><a class="nav-element-link">Verbrauchsausweis Gewerbe erstellen</a></li>
<li class="nav-second"><a class="nav-element-link">Bedarfsausweis Gewerbe erstellen</a></li>
</ul>
</li>
<li class="no-dropdown nav-element w-full">
<a class="nav-element-link" href="">Welcher Energieausweis</a>
</li>
<li class="dropdown lg:dropdown-bottom xl:dropdown-right dropdown-hover nav-element w-full">
<a class="nav-element-link" href="">Verbrauchsausweis<span></span></a>
<ul tabindex="0" class="nav-list dropdown-content z-[1] w-full">
<li class="nav-second"><a class="nav-element-link">Item 1</a></li>
<li class="nav-second"><a class="nav-element-link">Item 2</a></li>
</ul>
</li>
<li class="dropdown dropdown-right dropdown-hover nav-element w-full">
<a class="nav-element-link" href="">Bedarfsausweis<span></span></a>
<ul tabindex="0" class="nav-list dropdown-content z-[1] w-full">
<li class="nav-second"><a class="nav-element-link">Item 1</a></li>
<li class="nav-second"><a class="nav-element-link">Item 2</a></li>
</ul>
</li>
<li class="dropdown dropdown-right dropdown-hover nav-element w-full">
<a class="nav-element-link" href="">Energieausweis<span></span></a>
<ul tabindex="0" class="nav-list dropdown-content z-[1] w-full">
<li class="nav-second"><a class="nav-element-link">Item 1</a></li>
<li class="nav-second"><a class="nav-element-link">Item 2</a></li>
</ul>
</li>
<li class="no-dropdown nav-element w-full">
<a class="nav-element-link" href="">EnEV Zusammenfassung - Archiv</a>
</li>
<li class="no-dropdown nav-element w-full">
<a class="nav-element-link" href="">Energieausweis Aussteller</a>
</li>
<li class="no-dropdown nav-element w-full">
<a class="nav-element-link" href="">Kundenbewertungen</a>
</li>
<li class="no-dropdown nav-element w-full">
<a class="nav-element-link" href="">FAQ</a>
</li>
<li class="no-dropdown nav-element w-full">
<a class="nav-element-link" href="">Für Entwickler</a>
</li>
</div>
<style>
#main-navigation, .nav-list{border-top: 1px solid #ccc;}
#main-navigation li{border-bottom: 1px solid #ccc;border-left: 1px solid #ccc;border-right: 1px solid #ccc;}
.nav-element{
@apply p-2 bg-white font-normal text-lg hover:bg-primary hover:text-white
}
.nav-second{
@apply p-2 bg-white font-normal text-lg hover:bg-secondary hover:text-white
}
.nav-element-link{
@apply text-black no-underline hover:no-underline hover:text-white
}
.nav-element-link span{
position:absolute;right:15px;top:0.65rem;
}
.nav-element:hover > .nav-element-link, .nav-second:hover > .nav-element-link{
@apply text-white
}
.nav-element ul{margin: -1px 0;}
</style>

View File

@@ -1,17 +1,22 @@
import { GebaeudeAufnahmeClient, VerbrauchsausweisWohnenClient } from "#components/Ausweis/types";
export function auditBedarfsausweisBenoetigt(ausweis: VerbrauchsausweisWohnenClient, gebaeude: GebaeudeAufnahmeClient): boolean {
if (ausweis.ausstellgrund == "Neubau" || ausweis.ausstellgrund == "Modernisierung") {
return true;
}
if (gebaeude.saniert == true && ( gebaeude.dachgeschoss_gedaemmt == false || gebaeude.oberste_geschossdecke_gedaemmt == false)){
return true;
}
if (gebaeude.baujahr_gebaeude && gebaeude.baujahr_gebaeude.length > 0) {
return (
(gebaeude.baujahr_gebaeude[0] < 1978 &&
(gebaeude.einheiten || 0) <= 4 &&
gebaeude.saniert == false &&
(gebaeude.saniert == false ) &&
(ausweis.ausstellgrund == "Vermietung" ||
ausweis.ausstellgrund == "Sonstiges" ||
ausweis.ausstellgrund == "Verkauf")) ||
ausweis.ausstellgrund == "Neubau" ||
ausweis.ausstellgrund == "Modernisierung"
ausweis.ausstellgrund == "Verkauf"))
);
}

View File

@@ -0,0 +1,27 @@
import { GebaeudeClient, VerbrauchsausweisWohnenClient, GebaeudeAufnahmeClient } from "#components/Ausweis/types";
import { AuditType, hidden } from "./hidden";
import { endEnergieVerbrauchVerbrauchsausweis_2016 } from "#lib/Berechnungen/VerbrauchsausweisWohnen/VerbrauchsausweisWohnen_2016";
import { getKlimafaktoren } from "#lib/Klimafaktoren";
export async function auditEndEnergie(ausweis: VerbrauchsausweisWohnenClient, gebaeude: GebaeudeClient, gebaeude_aufnahme_allgemein: GebaeudeAufnahmeClient): Promise<boolean> {
if (hidden.has(AuditType.END_ENERGIE)) {
return false;
}
//sobald Fläche, Klimafaktoren und alle Verbrauchsjahre eingegeben wurden.
if (gebaeude_aufnahme_allgemein){
if (gebaeude_aufnahme_allgemein.flaeche && ausweis.verbrauch_1 && ausweis.verbrauch_2 && ausweis.verbrauch_3) {
try {
const response = await getKlimafaktoren(ausweis.startdatum, gebaeude.plz);
// Alle Klimfaktoren konnten abgefragt werden.
const eevva = await endEnergieVerbrauchVerbrauchsausweis_2016({...ausweis, gebaeude_aufnahme_allgemein: {...gebaeude_aufnahme_allgemein, gebaeude_stammdaten: gebaeude}});
if (eevva){
if (eevva?.endEnergieVerbrauchGesamt <= 45 || eevva?.endEnergieVerbrauchGesamt >= 500) {
return true;
}
}
} catch (e) {
}
}
}
return false;
}

View File

@@ -1,11 +1,14 @@
import { GebaeudeAufnahmeClient } from "#components/Ausweis/types";
import { AuditType, hidden } from "../audits/hidden";
export function auditHeizungJuengerDreiJahre(gebaeude: GebaeudeAufnahmeClient ): boolean {
export function auditHeizungJuengerDreiJahre(gebaeude: GebaeudeAufnahmeClient): boolean {
if (gebaeude.baujahr_heizung && gebaeude.baujahr_heizung.length > 0) {
return (
(gebaeude.baujahr_heizung.sort()[0] >= (new Date().getFullYear())-3)
);
if (!hidden.has(AuditType.HEIZUNG_JUENGER_DREI_JAHRE)) {
return (
(gebaeude.baujahr_heizung.sort()[0] >= (new Date().getFullYear()) - 3)
);
}
}
return false

View File

@@ -1,8 +1,5 @@
import { GebaeudeClient, VerbrauchsausweisWohnenClient } from "#components/Ausweis/types";
import { AuditType, hidden } from "./hidden";
import { prisma } from "@ibcornelsen/database/server";
import { client } from "src/trpc";
import moment from "moment";
import { getKlimafaktoren } from "#lib/Klimafaktoren";
export async function auditKlimaFaktoren(ausweis: VerbrauchsausweisWohnenClient, gebaeude: GebaeudeClient): Promise<boolean> {

View File

@@ -1,7 +1,8 @@
import { GebaeudeAufnahmeClient } from "#components/Ausweis/types";
import { AuditType, hidden } from "./hidden";
export function auditLeerStand(gebaeude: GebaeudeAufnahmeClient ): boolean {
if (gebaeude.leerstand ) {
export function auditLeerStand(gebaeude: GebaeudeAufnahmeClient): boolean {
if (gebaeude.leerstand && !hidden.has(AuditType.LEER_STAND)) {
return (
(gebaeude.leerstand > 30)
);

View File

@@ -0,0 +1,22 @@
import { GebaeudeAufnahmeClient } from "#components/Ausweis/types";
import { client } from "src/trpc";
import { memoize } from "src/lib/Memoization";
import { AuditType, hidden } from "../audits/hidden";
export const auditPlzNichtErkannt = memoize(async (gebaeude: GebaeudeAufnahmeClient) => {
if (gebaeude.plz) {
if (gebaeude.plz.length == 5) {
try {
const result = await client.v1.postleitzahlen.query({ plz: gebaeude.plz, limit: 1 });
if (result.length > 0) {
return false;
}
} catch (e) {
if (!hidden.has(AuditType.PLZ_NICHT_ERKANNT)){
return true;
}
}
}
}
return false
});

View File

@@ -10,19 +10,19 @@ export function auditVerbrauchAbweichung(ausweis: VerbrauchsausweisWohnenClient,
return [];
}
if (getAbweichung(ausweis.verbrauch_1 || 0, ausweis.verbrauch_2 || 0) > 0.25) {
if (getAbweichung(ausweis.verbrauch_1 || 0, ausweis.verbrauch_2 || 0) > 0.30) {
return [1, 2];
}
if (getAbweichung(ausweis.verbrauch_2 || 0, ausweis.verbrauch_3 || 0) > 0.25) {
if (getAbweichung(ausweis.verbrauch_2 || 0, ausweis.verbrauch_3 || 0) > 0.30) {
return [2, 3];
}
if (getAbweichung(ausweis.verbrauch_4 || 0, ausweis.verbrauch_5 || 0) > 0.25) {
if (getAbweichung(ausweis.verbrauch_4 || 0, ausweis.verbrauch_5 || 0) > 0.30) {
return [4, 5];
}
if (getAbweichung(ausweis.verbrauch_5 || 0, ausweis.verbrauch_6 || 0) > 0.25) {
if (getAbweichung(ausweis.verbrauch_5 || 0, ausweis.verbrauch_6 || 0) > 0.30) {
return [5, 6];
}

View File

@@ -1,11 +1,14 @@
import { VerbrauchsausweisWohnenClient } from "#components/Ausweis/types";
import { AuditType, hidden } from "./hidden";
export function auditWarmWasser(ausweis: VerbrauchsausweisWohnenClient): boolean {
if (ausweis.warmwasser_anteil_bekannt && ausweis.warmwasser_enthalten && ausweis.anteil_warmwasser_1) {
return (
ausweis.anteil_warmwasser_1 <= 6 || ausweis.anteil_warmwasser_1 >= 35
);
if (!hidden.has(AuditType.WARM_WASSER)){
return (
ausweis.anteil_warmwasser_1 <= 6 || ausweis.anteil_warmwasser_1 >= 35
);
}
}
return false

View File

@@ -1,11 +1,13 @@
import { GebaeudeAufnahmeClient } from "#components/Ausweis/types";
import { AuditType, hidden } from "./hidden";
export function auditWohnFlaeche(gebaeude: GebaeudeAufnahmeClient ): boolean {
if (gebaeude.einheiten && gebaeude.flaeche ) {
return (
(gebaeude.flaeche < gebaeude.einheiten * 30)
);
if (!hidden.has(AuditType.WOHN_FLAECHE)){
return (
(gebaeude.flaeche < gebaeude.einheiten * 30)
);
}
}
return false

View File

@@ -0,0 +1,10 @@
import { GebaeudeAufnahmeClient } from "#components/Ausweis/types";
import { AuditType, hidden } from "../audits/hidden";
export function auditWohnflaecheGroesserGesamtflaeche(gebaeude: GebaeudeAufnahmeClient ): boolean {
if (gebaeude.flaeche && gebaeude.nutzflaeche){
return (gebaeude.flaeche > gebaeude.nutzflaeche && !hidden.has(AuditType.WOHNFLAECHE_GROESSER_GESAMTFLAECHE));
}
return false;
}

View File

@@ -8,5 +8,8 @@ export enum AuditType {
KLIMA_FAKTOREN,
WOHN_FLAECHE,
WARM_WASSER,
LEER_STAND
LEER_STAND,
PLZ_NICHT_ERKANNT,
END_ENERGIE,
WOHNFLAECHE_GROESSER_GESAMTFLAECHE
}

View File

@@ -4,7 +4,7 @@ import * as fs from "fs";
const start = moment().set("year", 2019).set("month", 8).set("date", 1);
const end = moment().set("year", 2022).set("month", 10).set("date", 1);
const end = moment().set("year", 2023).set("month", 1).set("date", 1);
let current = start.clone();

File diff suppressed because one or more lines are too long

View File

@@ -8,7 +8,7 @@ import Header from "../components/Header.astro";
import SidebarLeft from "../components/SidebarLeft.astro";
import SidebarRight from "../components/SidebarRight.astro";
import { NotificationWrapper } from "@ibcornelsen/ui";
import HeaderAlternative from "#components/HeaderAlternative.svelte";
import TicketPopup from "../components/Tickets/TicketButton.svelte"
export interface Props {
title: string;
@@ -105,6 +105,7 @@ const schema = JSON.stringify({
</main>
<Footer />
<NotificationWrapper client:load />
<TicketPopup client:load />
</body>
</html>

View File

@@ -0,0 +1,96 @@
---
import i18next from "i18next";
import "../style/UMBE_global.css";
import "../../svelte-dialogs.config"
import Footer from "../components/UMBE_Footer.astro";
import Header from "../components/UMBE_Header.astro";
import SidebarLeft from "../components/UMBE_SidebarLeft.astro";
import SidebarRight from "../components/UMBE_SidebarRight.astro";
import { NotificationWrapper } from "@ibcornelsen/ui";
export interface Props {
title: string;
}
const { title } = Astro.props;
---
<!DOCTYPE html>
<html lang={i18next.language}>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.jpg" />
<meta
name="description"
content="✅ Jetzt Ihren Energieausweis online erstellen. Erhalten Sie Ihren online Energieausweis rechtssicher und nach aktueller GEG (vormals EnEV) vom Diplom Ingenieur geprüft."
/>
<title>
{title || "Energieausweis online erstellen - Online Energieausweis"}
</title>
</head>
<body>
<container class="max-w-[1920px] w-full mx-auto bg-slate-600 grid border-solid border-grey-200 border">
<Header />
<main
class="lg:grid gap-6 md:p-6 lg:grid-cols-[2fr,6fr,2fr] max-w-[1920px] w-full bg-base-100"
>
<SidebarLeft />
<article class="w-full max-w-full bg-base-200 border border-base-300">
<slot />
</article>
<SidebarRight />
</main>
<Footer />
<NotificationWrapper client:load />
</container>
</body>
</html>
<style is:global lang="scss">
:root {
@apply bg-base-100 text-base-content;
}
article {
@apply rounded-lg w-full shadow-md border;
}
.mainContent {
p, h1, h2, h3, h4, h5, h6 {
@apply text-base-content;
}
}
body {
min-height: 100vh;
}
.button {
@apply px-8 py-2 bg-secondary rounded-lg text-white font-medium hover:shadow-lg transition-all hover:underline active:bg-blue-900 text-center cursor-pointer;
color: #fff !important;
}
h3 {
@apply text-xl font-medium mt-6 mb-4;
}
input {
@apply py-1.5 px-4 w-full rounded-lg outline-none text-lg text-slate-700 border bg-gray-50 transition-colors;
}
input:hover,
input:focus {
@apply bg-gray-100;
}
label {
@apply text-base font-semibold;
}
</style>

View File

@@ -103,7 +103,7 @@ let lightTheme = Astro.cookies.get("theme").value === "light";
<body class="min-h-screen grid md:grid-cols-[300px_1fr]">
<DashboardSidebar lightTheme={lightTheme} benutzer={benutzer} client:load></DashboardSidebar>
<main class="p-8 overflow-auto h-screen bg-base-100">
<main class="p-4 md:p-8 overflow-auto h-screen bg-base-100 pt-20 md:!pt-24">
<slot />
</main>
</body>

View File

@@ -1,17 +1,42 @@
import { VerbrauchsausweisWohnenClient } from "#components/Ausweis/types";
import { z } from "zod";
import { verbrauchsausweisWohnenPDFValidator } from "./validators/verbrauchsausweis-wohnen-pdf-validator";
export function convertAusweisData(
inputs: VerbrauchsausweisWohnenClient
inputs: Partial<VerbrauchsausweisWohnenClient>
): Record<string, string> {
return {
"gebaeude_stammdaten.adresse": inputs.gebaeude_aufnahme_allgemein.adresse || "",
"gebaeude_stammdaten.gebaeudetyp":
inputs.gebaeude_aufnahme_allgemein.gebaeudetyp || "",
"gebaeude_stammdaten.baujahr_gebaeude":
inputs.gebaeude_aufnahme_allgemein.baujahr_gebaeude.join(", ") || "",
"gebaeude_stammdaten.baujahr_heizung":
inputs.gebaeude_aufnahme_allgemein.baujahr_heizung.join(", ") || "",
"gebaeude_stammdaten.plz": inputs.gebaeude_aufnahme_allgemein.plz || "",
"gebaeude_stammdaten.ort": inputs.gebaeude_aufnahme_allgemein.ort || "",
};
// Wir wollen alle Werte zu einem Flachen Objekt umwandeln, sodass wir dass später benutzen können.
// Dazu kommen noch einige wichtige Eigenschaften die man im PDF brauchen könnte.
let pdfInputs: z.infer<typeof verbrauchsausweisWohnenPDFValidator> = {
...inputs,
pdf: {
"brennstoff": [inputs.gebaeude_aufnahme_allgemein?.brennstoff_1, inputs.gebaeude_aufnahme_allgemein?.brennstoff_2].filter(x => x).join(", ")
}
}
let result = recursiveFlatten(inputs, "");
// Außerdem müssen wir alle Werte zu strings umwandeln.
for (const key in result) {
result[key] = String(result[key]);
}
return result;
}
function recursiveFlatten(obj: any, parentKey = ""): Record<string, string> {
const result: Record<string, string> = {};
for (const key in obj) {
const value = obj[key];
const newKey = parentKey ? `${parentKey}.${key}` : key;
if (typeof value === "object") {
Object.assign(result, recursiveFlatten(value, newKey));
} else {
result[newKey] = value;
}
}
return result;
}

View File

@@ -1,6 +1,7 @@
import { VerbrauchsausweisWohnenClient } from "#components/Ausweis/types";
import { getKlimafaktoren } from "#lib/Klimafaktoren";
import { getHeizwertfaktor } from "#lib/server/Heizwertfaktor";
import { Enums } from "@ibcornelsen/database/client";
import moment from "moment";
export function energetischeNutzflaecheVerbrauchsausweisWohnen_2016(
@@ -11,14 +12,15 @@ export function energetischeNutzflaecheVerbrauchsausweisWohnen_2016(
}
let faktorKeller = 1.2;
if (ausweis.keller_beheizt && (ausweis.gebaeude_aufnahme_allgemein.einheiten || 1) <= 2) {
// Falls das Gebäude einen Keller besitzt der Beheizt ist erhöhen wir die Nutzfläche um 15%
if (ausweis.gebaeude_aufnahme_allgemein.keller == Enums.Heizungsstatus.BEHEIZT && (ausweis.gebaeude_aufnahme_allgemein.einheiten || 1) <= 2) {
faktorKeller = 1.35;
}
if ((ausweis.gebaeude_aufnahme_allgemein.nutzflaeche || 0) > 0) {
return ausweis.gebaeude_aufnahme_allgemein.nutzflaeche || 0;
return ausweis.gebaeude_aufnahme_allgemein.nutzflaeche || 0;
} else {
return (ausweis.gebaeude_aufnahme_allgemein.flaeche || 1) * faktorKeller;
return (ausweis.gebaeude_aufnahme_allgemein.flaeche || 0) * faktorKeller;
}
}
@@ -129,6 +131,10 @@ export async function endEnergieVerbrauchVerbrauchsausweis_2016(
let energieVerbrauchHeizung_1 = energieVerbrauchGesamt_1;
let energieVerbrauchHeizung_2 = energieVerbrauchGesamt_2;
// TODO: Im aktuellen Skript vom Live System kommt hier etwas anderes raus,
// vielleicht ist da etwas kaputt? Da scheint es so, als wäre
// warmwasser_enthalten immer true...
// NOTE: Das hier müsste die richtige Version sein...
if (ausweis.warmwasser_enthalten) {
energieVerbrauchHeizung_1 -= energieVerbrauchWarmwasser_1;
energieVerbrauchHeizung_2 -= energieVerbrauchWarmwasser_2;
@@ -232,6 +238,29 @@ export async function endEnergieVerbrauchVerbrauchsausweis_2016(
co2EmissionenLeerstandsZuschlag +
co2EmissionenKuehlungsZuschlag;
// Energieeffizienzklasse
let energieEffizienzKlasse = "";
if (endEnergieVerbrauchGesamt < 30) {
energieEffizienzKlasse = 'A+';
}else if (endEnergieVerbrauchGesamt < 50) {
energieEffizienzKlasse = 'A';
}else if (endEnergieVerbrauchGesamt < 75) {
energieEffizienzKlasse = 'B';
}else if (endEnergieVerbrauchGesamt < 100) {
energieEffizienzKlasse = 'C';
}else if (endEnergieVerbrauchGesamt < 130) {
energieEffizienzKlasse = 'D';
}else if (endEnergieVerbrauchGesamt < 160) {
energieEffizienzKlasse = 'E';
}else if (endEnergieVerbrauchGesamt < 200) {
energieEffizienzKlasse = 'F';
}else if (endEnergieVerbrauchGesamt < 250) {
energieEffizienzKlasse = 'G';
}else if (endEnergieVerbrauchGesamt >= 250) {
energieEffizienzKlasse = 'H';
}
return {
brennstoff_1: brennstoff_1,
brennstoff_2: brennstoff_2,
@@ -262,7 +291,7 @@ export async function endEnergieVerbrauchVerbrauchsausweis_2016(
(ausweis.verbrauch_6 || 0) * brennstoff_2.umrechnungsfaktor
),
energetische_nutzfläche: Math.round(energetischeNutzflaeche),
energetischeNutzflaeche: energetischeNutzflaeche,
leerstand: leerstand,
leerstandsZuschlagHeizung: Math.round(leerstandsZuschlagHeizung),
leerstandsZuschlagWarmwasser: Math.round(leerstandsZuschlagWarmwasser),
@@ -308,6 +337,9 @@ export async function endEnergieVerbrauchVerbrauchsausweis_2016(
primaerEnergieVerbrauchKuehlungsZuschlag
),
primaerfaktorww,
primaerfaktorww_1,
co2Emissionen_1: co2Emissionen_1,
co2Emissionen_2: co2Emissionen_2,
@@ -320,5 +352,6 @@ export async function endEnergieVerbrauchVerbrauchsausweis_2016(
primaerEnergieVerbrauchGesamt: Math.round(
primaerEnergieVerbrauchGesamt
),
energieEffizienzKlasse
};
}

View File

@@ -14,7 +14,7 @@ export class AusweisBerechnungen2016 {
}
public getAusweisart() {
switch (this.ausweis.ausweisart.toLowerCase()) {
switch (this.ausweis.gebaeude_aufnahme_allgemein.ausweisart.toLowerCase()) {
case ('va'):
return {'typ' : 'Wohngebaeude', 'art' : 'Verbrauchsausweis Wohngebäude', 'kuerzel' : 'va'};
case ('ba'):
@@ -73,7 +73,7 @@ export class AusweisBerechnungen2016 {
public getGebaeudeTeil() {
if (this.gebaeude.gebaeudeteil == "Gesamtgebäude") {
return "Ganzes Gebäude";
} else if (this.gebaeude.gebaeudeteil == "Wohnen" && this.ausweis.ausweisart == "VANW") {
} else if (this.gebaeude.gebaeudeteil == "Wohnen" && this.ausweis.gebaeude_aufnahme_allgemein.ausweisart == "VANW") {
return "Teil des Wohngebäudes";
} else {
return "Teil des Nichtwohngebäudes";

View File

@@ -1,33 +1,34 @@
import type { GebaeudeStammdaten, VerbrauchsausweisWohnen } from "@ibcornelsen/database/client";
import { VerbrauchsausweisWohnenClient } from "#components/Ausweis/types";
import { Enums } from "@ibcornelsen/database/client";
import moment from "moment";
export function getEmpfehlungen(ausweis: VerbrauchsausweisWohnen, gebaeude: GebaeudeStammdaten): {
export function getEmpfehlungen(ausweis: VerbrauchsausweisWohnenClient): {
title: string,
description: string,
anlagenteil: string
}[] {
let Warmwasserrohre_gedaemmt = gebaeude.warmwasser_rohre_gedaemmt;
let Heizungsrohre_gedaemmt = gebaeude.heizungsrohre_gedaemmt;
let Waermepumpe = gebaeude.waermepumpe;
let Kellerwand_gedaemmt = gebaeude.keller_wand_gedaemmt;
let Keller = ausweis.keller_beheizt;
let Kellerdecke_Kalraeume_gedaemmt = gebaeude.keller_decke_gedaemmt;
let Brennwertkessel = gebaeude.brennwert_kessel;
let baujahr_anlagesanlage = gebaeude.baujahr_heizung[0];
let Zentralheizung = gebaeude.zentralheizung;
let photovoltaik = gebaeude.photovoltaik;
let Brennstoff = ausweis.brennstoff_1;
let Aussenwand_gedaemmt = gebaeude.aussenwand_gedaemmt;
let Dachgeschoss = gebaeude.dachgeschoss;
let Dachgeschoss_gedaemmt = gebaeude.dachgeschoss_gedaemmt;
let Oberste_Geschossdecke_gedaemmt = gebaeude.oberste_geschossdecke_gedaemmt;
let Einfachglas = gebaeude.einfach_verglasung;
let Doppelfenster = gebaeude.doppel_verglasung;
let Fenster_teilw_undicht = gebaeude.fenster_teilweise_undicht;
let Warmwasserrohre_gedaemmt = ausweis.gebaeude_aufnahme_allgemein.warmwasser_rohre_gedaemmt;
let Heizungsrohre_gedaemmt = ausweis.gebaeude_aufnahme_allgemein.heizungsrohre_gedaemmt;
let Waermepumpe = ausweis.gebaeude_aufnahme_allgemein.waermepumpe;
let Kellerwand_gedaemmt = ausweis.gebaeude_aufnahme_allgemein.keller_wand_gedaemmt;
let Keller = ausweis.gebaeude_aufnahme_allgemein.keller;
let Kellerdecke_Kalraeume_gedaemmt = ausweis.gebaeude_aufnahme_allgemein.keller_decke_gedaemmt;
let Brennwertkessel = ausweis.gebaeude_aufnahme_allgemein.brennwert_kessel;
let baujahr_anlagesanlage = ausweis.gebaeude_aufnahme_allgemein.baujahr_heizung[0];
let Zentralheizung = ausweis.gebaeude_aufnahme_allgemein.zentralheizung;
let photovoltaik = ausweis.gebaeude_aufnahme_allgemein.photovoltaik;
let Brennstoff = ausweis.gebaeude_aufnahme_allgemein.brennstoff_1;
let Aussenwand_gedaemmt = ausweis.gebaeude_aufnahme_allgemein.aussenwand_gedaemmt;
let Dachgeschoss = ausweis.gebaeude_aufnahme_allgemein.dachgeschoss;
let Dachgeschoss_gedaemmt = ausweis.gebaeude_aufnahme_allgemein.dachgeschoss_gedaemmt;
let Oberste_Geschossdecke_gedaemmt = ausweis.gebaeude_aufnahme_allgemein.oberste_geschossdecke_gedaemmt;
let Einfachglas = ausweis.gebaeude_aufnahme_allgemein.einfach_verglasung;
let Doppelfenster = ausweis.gebaeude_aufnahme_allgemein.doppel_verglasung;
let Fenster_teilw_undicht = ausweis.gebaeude_aufnahme_allgemein.fenster_teilweise_undicht;
let empfehlungen = [];
if (gebaeude.einfach_verglasung || (Doppelfenster && Fenster_teilw_undicht)) {
if (ausweis.gebaeude_aufnahme_allgemein.einfach_verglasung || (Doppelfenster && Fenster_teilw_undicht)) {
empfehlungen.push({
"title" : "Erneuerung der Fenster",
"description" : "Alte und undichte Fenster mit Wärmeschutzfenstern auswechseln.",
@@ -35,13 +36,13 @@ export function getEmpfehlungen(ausweis: VerbrauchsausweisWohnen, gebaeude: Geba
});
}
if (gebaeude.dachgeschoss == "Unbeheizt" && !Oberste_Geschossdecke_gedaemmt) {
if (ausweis.gebaeude_aufnahme_allgemein.dachgeschoss == Enums.Heizungsstatus.UNBEHEIZT && !Oberste_Geschossdecke_gedaemmt) {
empfehlungen.push({
"title" : "Zusätzliche Dämmung des Fußbodens des kalten Dachraumes",
"description" : "Beim Einbringen sollten mindestens 16cm Dämmstoff verarbeitet werden. Das Einsparpotenzial ist für jeden zusätzlichen cm Dämmung sehr hoch.",
"anlagenteil" : "Dach"
});
} else if (Dachgeschoss == "Beheizt" && !Dachgeschoss_gedaemmt) {
} else if (Dachgeschoss == Enums.Heizungsstatus.BEHEIZT && !Dachgeschoss_gedaemmt) {
empfehlungen.push({
"title" : "Zusätzliche Dämmung des Daches bzw. Dachraumes",
"description" : "Beim Einbringen sollten mindestens 16cm Dämmstoff, wenn möglich, verarbeitet werden. Das Einsparpotenzial ist für jeden zusätzlichen cm Dämmung sehr hoch.",
@@ -67,13 +68,13 @@ export function getEmpfehlungen(ausweis: VerbrauchsausweisWohnen, gebaeude: Geba
});
}
if (!Kellerdecke_Kalraeume_gedaemmt && Keller == "Unbeheizt") {
if (!Kellerdecke_Kalraeume_gedaemmt && Keller == Enums.Heizungsstatus.UNBEHEIZT) {
empfehlungen.push({
"title" : "Nachträgliche Dämmung der Kellerdecke",
"description" : "Je nach Deckenhöhe, den vorhandenen Raum voll ausnutzen. Das Einsparpotenzial für jeden zusätzlichen cm Dämmung sehr hoch.",
"anlagenteil" : "Kellerdecke"
});
} else if (!Kellerwand_gedaemmt && Keller == "Beheizt") {
} else if (!Kellerwand_gedaemmt && Keller == Enums.Heizungsstatus.BEHEIZT) {
empfehlungen.push({
"title" : "Nachträgliche Dämmung der Kellerwände",
"description" : "Man sollte mit Dämmstärken ab 12cm planen. Das Einsparpotenzial für jeden zusätzlichen cm Dämmung sehr hoch.",

View File

@@ -0,0 +1,133 @@
import { VerbrauchsausweisWohnenClient } from "#components/Ausweis/types";
import { faker } from "@faker-js/faker";
import { Enums } from "@ibcornelsen/database/client";
import moment from "moment";
export async function importVerbrauchsausweisWohnenAltesSystem(count: number = 5) {
const response = await fetch("https://online-energieausweis.org/user/ausweis-import.php", {
method: "POST",
body: JSON.stringify({
i: count,
offset: 0,
q: {}
})
})
const data = await response.json();
return data
}
export function verbrauchsausweisWohnenImportTranslate(ausweis: Record<string, any>) {
const ausweisTranslated: VerbrauchsausweisWohnenClient = {
gebaeude_aufnahme_allgemein: {
rechnungen: null,
baujahr_gebaeude: [ausweis.baujahr_gebaeude],
baujahr_heizung: [ausweis.baujahr_anlage],
baujahr_klima: [ausweis.baujahr_klimaanlage],
adresse: ausweis.objekt_strasse,
plz: ausweis.objekt_plz,
ort: ausweis.objekt_ort,
nutzflaeche: ausweis.nutzflaeche,
einheiten: ausweis.anzahl_einheiten,
saniert: ausweis.objekt_saniert,
keller: ausweis.keller_beheizt == "Beheizt" ? Enums.Heizungsstatus.BEHEIZT : ausweis.keller_beheizt == "Unbeheizt" ? Enums.Heizungsstatus.UNBEHEIZT : Enums.Heizungsstatus.NICHT_VORHANDEN,
dachgeschoss: ausweis.dachgeschoss == "Beheizt" ? Enums.Heizungsstatus.BEHEIZT : ausweis.dachgeschoss == "Unbeheizt" ? Enums.Heizungsstatus.UNBEHEIZT : Enums.Heizungsstatus.NICHT_VORHANDEN,
flaeche: ausweis.wohnflaeche,
gebaeudetyp: ausweis.objekt_typ,
gebaeudeteil: ausweis.objekt_gebaeudeteil,
lueftung: ausweis.lueftungskonzept,
kuehlung: ausweis.wird_gekuehlt,
gebaeude_stammdaten: {
adresse: ausweis.objekt_strasse,
plz: ausweis.objekt_plz,
ort: ausweis.objekt_ort,
uid: faker.string.uuid(),
gebaeude_bilder: [],
latitude: null,
longitude: null,
},
brennstoff_1: ausweis.energietraeger_1,
brennstoff_2: ausweis.energietraeger_2,
alternative_heizung: ausweis.alheizung,
alternative_kuehlung: ausweis.alkuehlung,
alternative_lueftung: ausweis.allueftung,
alternative_warmwasser: ausweis.alwarmwasser,
ausweisart: Enums.Ausweisart.VerbrauchsausweisWohnen,
energieeffizienzklasse: "",
aussenwand_gedaemmt: ausweis.aussenwand_gedaemmt,
aussenwand_min_12cm_gedaemmt: ausweis.aussenwand_min_12cm_gedaemmt,
bestellt: ausweis.bestellt,
boxpruefung: ausweis.boxpruefung,
brennwert_kessel: ausweis.brennwert_kessel,
dachgeschoss_gedaemmt: ausweis.dachgeschoss_gedaemmt,
dachgeschoss_min_12cm_gedaemmt: ausweis.dachgeschoss_min_12cm_gedaemmt,
doppel_verglasung: ausweis.doppel_verglasung,
dreifach_verglasung: ausweis.dreifach_verglasung,
durchlauf_erhitzer: ausweis.durchlauf_erhitzer,
einfach_verglasung: ausweis.einfach_verglasung,
einzelofen: ausweis.einzelofen,
erledigt: ausweis.erledigt,
erstellungsdatum: ausweis.erstellungsdatum,
fenster_dicht: ausweis.fenster_dicht,
fenster_teilweise_undicht: ausweis.fenster_teilweise_undicht,
heizungsrohre_gedaemmt: ausweis.heizungsrohre_gedaemmt,
isolier_verglasung: ausweis.isolier_verglasung,
keller_decke_gedaemmt: ausweis.keller_decke_gedaemmt,
keller_wand_gedaemmt: ausweis.keller_wand_gedaemmt,
niedertemperatur_kessel: ausweis.niedertemperatur_kessel,
oberste_geschossdecke_gedaemmt: ausweis.oberste_geschossdecke_gedaemmt,
oberste_geschossdecke_min_12cm_gedaemmt: ausweis.oberste_geschossdecke_min_12cm_gedaemmt,
raum_temperatur_regler: ausweis.raum_temperatur_regler,
rolllaeden_kaesten_gedaemmt: ausweis.rolllaeden_kaesten_gedaemmt,
solarsystem_warmwasser: ausweis.solarsystem_warmwasser,
standard_kessel: ausweis.standard_kessel,
waermepumpe: ausweis.waermepumpe,
warmwasser_rohre_gedaemmt: ausweis.warmwasser_rohre_gedaemmt,
zentralheizung: ausweis.zentralheizung,
zirkulation: ausweis.zirkulation,
photovoltaik: ausweis.photovoltaik,
leerstand: ausweis.leerstand,
prueftext: ausweis["check-texts"],
storniert: false,
tueren_dicht: ausweis.tueren_dicht,
tueren_undicht: ausweis.tueren_undicht,
zurueckgestellt: ausweis.zurueckGestellt,
uid: faker.string.uuid(),
events: []
},
verbrauch_1: parseInt(ausweis.energieverbrauch_1_heizquelle_1),
verbrauch_2: parseInt(ausweis.energieverbrauch_2_heizquelle_1),
verbrauch_3: parseInt(ausweis.energieverbrauch_3_heizquelle_1),
verbrauch_4: parseInt(ausweis.energieverbrauch_1_heizquelle_2),
verbrauch_5: parseInt(ausweis.energieverbrauch_2_heizquelle_2),
verbrauch_6: parseInt(ausweis.energieverbrauch_3_heizquelle_2),
einheit_1: ausweis.energietraeger_einheit_heizquelle_1,
einheit_2: ausweis.energietraeger_einheit_heizquelle_2,
warmwasser_enthalten: ausweis.warmwasser_enthalten,
uid: faker.string.uuid(),
alternative_heizung: ausweis.alheizung,
alternative_kuehlung: ausweis.alkuehlung,
alternative_lueftung: ausweis.allueftung,
alternative_warmwasser: ausweis.alwarmwasser,
anteil_warmwasser_1: ausweis.anteil_warmwasser_1,
anteil_warmwasser_2: ausweis.anteil_warmwasser_2,
ausstellgrund: ausweis.ausstellgrund,
ausstellungsdatum: moment(ausweis.bestelldatum).toDate(),
erledigt: ausweis.erledigt,
erstellungsdatum: moment(ausweis.erstellungsdatum).toDate(),
keller_beheizt: ausweis.keller_beheizt,
registriernummer: ausweis.regnummer,
// Der Monat im alten System ist 1-basiert, in der neuen Datenbank 0-basiert
// Also müssen wir hier 1 abziehen
startdatum: moment(`${ausweis.energieverbrauch_zeitraum_jahr}-${ausweis.energieverbrauch_zeitraum_monat}-01`).toDate(),
warmwasser_anteil_bekannt: ausweis.warmwasser_anteil_bekannt,
wird_gekuehlt: ausweis.wird_gekuehlt,
zusaetzliche_heizquelle: ausweis.zusaetzliche_heizquelle,
}
return ausweisTranslated
}

View File

@@ -0,0 +1,118 @@
import { VerbrauchsausweisWohnenClient } from "#components/Ausweis/types";
import { faker } from "@faker-js/faker";
import { Enums } from "@ibcornelsen/database/client";
export function verbrauchsausweisWohnenFaker(seed: number = 42): VerbrauchsausweisWohnenClient {
faker.seed(seed);
const tuerenDicht = faker.datatype.boolean();
const verbrauch_1 = faker.number.int({ min: 5000, max: 20000 });
const verbrauch_4 = faker.number.int({ min: 5000, max: 15000 });
const ausweis: VerbrauchsausweisWohnenClient = {
rechnungen: null,
gebaeude_aufnahme_allgemein: {
baujahr_gebaeude: [faker.number.int({ min: 1960, max: 2014 })],
baujahr_heizung: [faker.number.int({ min: 1960, max: 2014 })],
baujahr_klima: [faker.number.int({ min: 1985, max: 2014 })],
adresse: faker.location.streetAddress(),
plz: faker.location.zipCode({ format: "#####" }),
ort: faker.location.city(),
nutzflaeche: faker.number.int({ min: 50, max: 300 }),
einheiten: faker.number.int({ min: 1, max: 3 }),
saniert: faker.datatype.boolean(),
keller: faker.helpers.enumValue(Enums.Heizungsstatus),
dachgeschoss: faker.helpers.enumValue(Enums.Heizungsstatus),
flaeche: faker.number.int({ min: 50, max: 300 }),
gebaeudetyp: "Einfamilienhaus",
gebaeudeteil: "Gesamtgebäude",
lueftung: "Fensterlüftung",
kuehlung: "Vorhanden",
gebaeude_stammdaten: {
adresse: faker.location.streetAddress(),
plz: faker.location.zipCode({ format: "#####" }),
ort: faker.location.city(),
uid: faker.string.uuid(),
gebaeude_bilder: [],
latitude: faker.location.latitude(),
longitude: faker.location.longitude(),
},
brennstoff_1: "Erdgas H",
alternative_heizung: faker.datatype.boolean(),
alternative_kuehlung: faker.datatype.boolean(),
alternative_lueftung: faker.datatype.boolean(),
alternative_warmwasser: faker.datatype.boolean(),
aussenwand_gedaemmt: faker.datatype.boolean(),
aussenwand_min_12cm_gedaemmt: faker.datatype.boolean(),
ausweisart: Enums.Ausweisart.VerbrauchsausweisWohnen,
bestellt: faker.datatype.boolean(),
boxpruefung: faker.datatype.boolean(),
brennstoff_2: "Erdgas H",
brennwert_kessel: faker.datatype.boolean(),
dachgeschoss_gedaemmt: faker.datatype.boolean(),
dachgeschoss_min_12cm_gedaemmt: faker.datatype.boolean(),
doppel_verglasung: faker.datatype.boolean(),
dreifach_verglasung: faker.datatype.boolean(),
durchlauf_erhitzer: faker.datatype.boolean(),
einfach_verglasung: faker.datatype.boolean(),
einzelofen: faker.datatype.boolean(),
energieeffizienzklasse: "",
erledigt: faker.datatype.boolean(),
erstellungsdatum: faker.date.past(),
fenster_dicht: faker.datatype.boolean(),
fenster_teilweise_undicht: faker.datatype.boolean(),
heizungsrohre_gedaemmt: faker.datatype.boolean(),
isolier_verglasung: faker.datatype.boolean(),
keller_decke_gedaemmt: faker.datatype.boolean(),
keller_wand_gedaemmt: faker.datatype.boolean(),
leerstand: faker.number.int({ min: 0, max: 20 }),
niedertemperatur_kessel: faker.datatype.boolean(),
oberste_geschossdecke_gedaemmt: faker.datatype.boolean(),
oberste_geschossdecke_min_12cm_gedaemmt: faker.datatype.boolean(),
photovoltaik: faker.datatype.boolean(),
prueftext: faker.lorem.sentence(),
raum_temperatur_regler: faker.datatype.boolean(),
rolllaeden_kaesten_gedaemmt: faker.datatype.boolean(),
solarsystem_warmwasser: faker.datatype.boolean(),
standard_kessel: faker.datatype.boolean(),
storniert: false,
tueren_dicht: tuerenDicht,
tueren_undicht: !tuerenDicht,
waermepumpe: faker.datatype.boolean(),
warmwasser_rohre_gedaemmt: faker.datatype.boolean(),
zentralheizung: faker.datatype.boolean(),
zirkulation: faker.datatype.boolean(),
zurueckgestellt: faker.datatype.boolean(),
uid: faker.string.uuid(),
events: []
},
verbrauch_1: verbrauch_1,
verbrauch_2: verbrauch_1 + faker.number.int({ min: -2000, max: 2000 }),
verbrauch_3: verbrauch_1 + faker.number.int({ min: -2000, max: 2000 }),
einheit_1: "kWh",
warmwasser_enthalten: faker.datatype.boolean(),
uid: faker.string.uuid(),
alternative_heizung: faker.datatype.boolean(),
alternative_kuehlung: faker.datatype.boolean(),
alternative_lueftung: faker.datatype.boolean(),
alternative_warmwasser: faker.datatype.boolean(),
anteil_warmwasser_1: faker.number.int({ min: 0, max: 60 }),
anteil_warmwasser_2: faker.number.int({ min: 0, max: 60 }),
ausstellgrund: faker.helpers.enumValue(Enums.Ausstellgrund),
ausstellungsdatum: faker.date.past(),
einheit_2: "kWh",
erledigt: faker.datatype.boolean(),
erstellungsdatum: faker.date.past(),
keller_beheizt: faker.datatype.boolean(),
registriernummer: faker.string.uuid(),
startdatum: faker.date.past({ years: 3 }),
verbrauch_4: verbrauch_4,
verbrauch_5: verbrauch_4 + faker.number.int({ min: -2000, max: 2000 }),
verbrauch_6: verbrauch_4 + faker.number.int({ min: -2000, max: 2000 }),
warmwasser_anteil_bekannt: faker.datatype.boolean(),
wird_gekuehlt: faker.datatype.boolean(),
zusaetzliche_heizquelle: faker.datatype.boolean(),
}
return ausweis;
}

25
src/lib/helpers/zod.ts Normal file
View File

@@ -0,0 +1,25 @@
import { z } from "zod";
// get zod object keys recursively
export const zodGetKeys = <T extends z.ZodTypeAny>(schema: T): string[] => {
// make sure schema is not null or undefined
if (schema === null || schema === undefined) return [];
// check if schema is nullable or optional
if (schema instanceof z.ZodNullable || schema instanceof z.ZodOptional) return zodGetKeys(schema.unwrap());
// check if schema is an array
if (schema instanceof z.ZodArray) return zodGetKeys(schema.element);
// check if schema is an object
if (schema instanceof z.ZodObject) {
// get key/value pairs from schema
const entries = Object.entries(schema.shape);
// loop through key/value pairs
return entries.flatMap(([key, value]) => {
// get nested keys
const nested = value instanceof z.ZodType ? zodGetKeys(value).map(subKey => `${key}.${subKey}`) : [];
// return nested keys
return nested.length ? nested : key;
});
}
// return empty array
return [];
};

View File

@@ -1,14 +1,12 @@
import {
ZOOM,
Plugin,
Schema,
PropPanel,
DEFAULT_FONT_NAME,
getFallbackFontName,
PropPanelSchema,
PropPanelWidgetProps,
} from "@pdfme/common";
import { image, text } from "@pdfme/schemas";
import { text } from "@pdfme/schemas";
import type { TextSchema } from "@pdfme/schemas/dist/types/src/text/types";
import {
@@ -34,8 +32,10 @@ import {
import {
GebaeudeStammdaten,
Rechnungen,
VerbrauchsausweisWohnen,
} from "@ibcornelsen/database/client";
import { VerbrauchsausweisWohnenClient } from "#components/Ausweis/types";
import { zodGetKeys } from "#lib/helpers/zod";
import { verbrauchsausweisWohnenPDFValidator } from "#lib/validators/verbrauchsausweis-wohnen-pdf-validator";
const UseDynamicFontSize = (props: PropPanelWidgetProps) => {
const { rootElement, changeSchemas, activeSchema, i18n } = props;
@@ -62,51 +62,17 @@ const UseDynamicFontSize = (props: PropPanelWidgetProps) => {
rootElement.appendChild(label);
};
type AusweisIndex = keyof VerbrauchsausweisWohnen
| `gebaeude_stammdaten.${keyof GebaeudeStammdaten}`
| `rechnung.${keyof Rechnungen}`;
type AusweisIndex = keyof Omit<Omit<VerbrauchsausweisWohnenClient, "gebaeude_aufnahme_allgemein">, "rechnungen">
| `gebaeude_aufnahme_allgemein.${keyof VerbrauchsausweisWohnenClient["gebaeude_aufnahme_allgemein"]}`
| `gebaeude_aufnahme_allgemein.gebaeude_stammdaten.${keyof VerbrauchsausweisWohnenClient["gebaeude_aufnahme_allgemein"]["gebaeude_stammdaten"]}`
| `rechnungen.${keyof Rechnungen}`;
const sampleData: Partial<Record<AusweisIndex , string>> = {
"gebaeude_stammdaten.adresse": "Musterstraße 123",
"gebaeude_stammdaten.plz": "12345",
"gebaeude_stammdaten.ort": "Musterstadt",
"gebaeude_stammdaten.baujahr_gebaeude": "1990",
"gebaeude_stammdaten.baujahr_heizung": "2000",
}
const ausweisKeys = zodGetKeys(verbrauchsausweisWohnenPDFValidator);
const variableOptions: {
label: string;
value: AusweisIndex;
}[] = [
{
label: "Gebäude -> Adresse",
value: "gebaeude_stammdaten.adresse",
},
{
label: "Gebäude -> PLZ",
value: "gebaeude_stammdaten.plz",
},
{
label: "Gebaeude -> Ort",
value: "gebaeude_stammdaten.ort",
},
{
label: "Gebäude -> Baujahr Gebäude",
value: "gebaeude_stammdaten.baujahr_gebaeude",
},
{
label: "Gebäude -> Baujahr Heizung",
value: "gebaeude_stammdaten.baujahr_heizung",
},
{
label: "Gebäude -> Fläche",
value: "gebaeude_stammdaten.flaeche"
},
{
label: "Gebäude -> Einheiten",
value: "gebaeude_stammdaten.einheiten"
}
];
}[] = ausweisKeys.map((key: AusweisIndex) => ({ label: key as string, value: key }));
interface VariableSchema extends TextSchema {
variable: AusweisIndex | undefined
@@ -293,18 +259,21 @@ const propPanel: PropPanel<VariableSchema> = {
backgroundColor: "",
opacity: DEFAULT_OPACITY,
variable: undefined
},
}
};
export const variable: Plugin<VariableSchema> = {
ui: (props) => {
if (props.schema.variable) {
props.value = sampleData[props.schema.variable] as string;
ui: function(props) {
// Wir binden die inputs auf dieses Element, damit wir die Werte später auslesen können.
if (props.schema.variable && this) {
props.value = (this as unknown as Record<string, string>)[props.schema.variable] as string;
}
return text.ui(props);
},
pdf: (props) => {
props.value = props.schema.variable as string;
if (props.schema.variable && this) {
props.value = (this as unknown as Record<string, string>)[props.schema.variable] as string;
}
text.pdf(props);
},
propPanel,

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -0,0 +1,8 @@
import { VerbrauchsausweisWohnenValidator } from "@ibcornelsen/api/src/validators";
import { z } from "zod";
export const verbrauchsausweisWohnenPDFValidator = VerbrauchsausweisWohnenValidator.merge(z.object({
pdf: z.object({
brennstoff: z.string()
})
}))

View File

@@ -9,7 +9,7 @@
import ZipSearch from "#components/PlzSuche.svelte";
import moment from "moment";
import BilderZusatzsysteme from "#components/Ausweis/BilderZusatzsysteme.svelte";
import { RawNotificationWrapper, RawNotification, notifications, addNotification, updateNotification } from "@ibcornelsen/ui";
import { RawNotificationWrapper, RawNotification, notifications } from "@ibcornelsen/ui";
import { auditHeizungGebaeudeBaujahr } from "#components/Verbrauchsausweis/audits/HeizungGebaeudeBaujahr";
import { auditHeizungJuengerDreiJahre } from "#components/Verbrauchsausweis/audits/HeizungJuengerDreiJahre";
import { auditZeitraumAktuell } from "#components/Verbrauchsausweis/audits/ZeitraumAktuell";
@@ -17,157 +17,37 @@
import { auditWohnFlaeche } from "#components/Verbrauchsausweis/audits/WohnFlaeche";
import { auditWarmWasser } from "#components/Verbrauchsausweis/audits/WarmWasser";
import { auditLeerStand } from "#components/Verbrauchsausweis/audits/LeerStand";
import { auditPlzNichtErkannt } from "#components/Verbrauchsausweis/audits/PlzNichtErkannt";
import { AuditType, hidden } from "#components/Verbrauchsausweis/audits/hidden";
import { auditBedarfsausweisBenoetigt } from "#components/Verbrauchsausweis/audits/BedarfsausweisBenoetigt";
import { auditVerbrauchAbweichung } from "#components/Verbrauchsausweis/audits/VerbrauchAbweichung";
import { auditEndEnergie } from "#components/Verbrauchsausweis/audits/EndEnergie";
import { auditWohnflaecheGroesserGesamtflaeche } from "#components/Verbrauchsausweis/audits/WohnflaecheGroesserGesamtflaeche";
import { Enums } from "@ibcornelsen/database/client"
import { client } from "src/trpc";
import Overlay from "#components/Overlay.svelte";
import AusweisGespeichertModule from "./AusweisGespeichertModule.svelte";
import { validateAccessTokenClient } from "src/client/lib/validateAccessToken";
import { VerbrauchsausweisWohnenClient, BenutzerClient } from "#components/Ausweis/types";
import { dialogs } from "svelte-dialogs";
import LoginDialog from "#components/LoginDialog.svelte";
import { exclude } from "#lib/exclude";
import { VerbrauchsausweisWohnenClient, BenutzerClient, UploadedGebaeudeBild } from "#components/Ausweis/types";
import { verbrauchsausweisWohnenSpeichern } from "src/client/lib/verbrauchsausweisWohnenSpeichern";
// TODO: Vom Server sollte ein volles Objekt kommen, dass alle Subobjekte enthält, weil es sonst zu Problemen führen kann
// wenn gebaeude_aufnahme_allgemein oder gebaeude_stammdaten nicht existiert...
export let ausweis: VerbrauchsausweisWohnenClient;
export let user: BenutzerClient = {} as BenutzerClient;
let gebaeude_aufnahme_allgemein = ausweis.gebaeude_aufnahme_allgemein || {};
let gebaeude = ausweis.gebaeude_aufnahme_allgemein?.gebaeude_stammdaten || {};
let images = ausweis.gebaeude_aufnahme_allgemein?.gebaeude_stammdaten?.gebaeude_bilder || [];
async function bilderHochladen() {
if (images.length == 0) {
return;
}
const imagesToUpload = images.filter(image => !image.uid);
if (imagesToUpload.length == 0) {
return;
}
// Alle Bilder hochladen
const notification = addNotification({
dismissable: false,
message: "Bilder hochladen.",
subtext: `${imagesToUpload.length} Bilder werden hochgeladen, bitte haben sie Geduld.`,
timeout: 0,
type: "info"
})
for (let i = 0; i < imagesToUpload.length; i++) {
const image = imagesToUpload[i];
try {
const response = await client.v1.bilder.upload.mutate({
base64: image.base64,
kategorie: image.kategorie,
gebaeude_uid: gebaeude.uid
})
image.uid = response.uid
updateNotification(notification, {
dismissable: true,
message: "Bild hochgeladen.",
subtext: `${i + 1}/${imagesToUpload.length} Bildern wurden erfolgreich hochgeladen.`,
timeout: 3000
})
} catch (e) {
updateNotification(notification, {
dismissable: true,
message: "Bild konnte nicht hochgeladen werden.",
subtext: `Eines ihrer Bilder konnte nicht hochgeladen werden. Wir haben bereits ein Ticket erstellt und melden uns so schnell wie möglich bei ihnen.`,
timeout: 15000,
type: "error"
})
}
}
}
async function ausweisSpeichern() {
// Um einen Ausweis zu speichern müssen wir eingeloggt sein, andernfalls wird die API den call ablehnen.
// Wir prüfen also ob wir eingeloggt sind und leiten den Nutzer ggf. auf die Login Seite weiter.
if (!await validateAccessTokenClient()) {
// TOOD: Auf Dialog umstellen.
const loggedIn = await dialogs.modal(LoginDialog);
if (!loggedIn) {
return false;
}
}
if (ausweis.uid) {
// Anscheinend wurde der Ausweis bereits erstellt und hat eine UID.
// Jetzt müssen wir ihn nun nur noch abspeichern.
try {
const gebaeudeBilderEntfernt = exclude(gebaeude, ["gebaeude_bilder"])
const gebaeudeAufnahmeGeneratedFieldsEntfernt = exclude(gebaeude_aufnahme_allgemein, ["erstellungsdatum"])
const ausweisGeneratedFieldsEntfernt = exclude(ausweis, ["ausweisart", "rechnungen"])
await client.v1.verbrauchsausweisWohnen[2016].speichern.mutate({
...ausweisGeneratedFieldsEntfernt,
gebaeude_aufnahme_allgemein: {
...gebaeudeAufnahmeGeneratedFieldsEntfernt,
gebaeude_stammdaten: {
...gebaeudeBilderEntfernt
}
}
})
await bilderHochladen();
return true;
} catch (e) {
// TODO: Ticket mit Fehldermeldung abschicken.
}
} else {
// Wir speichern den Ausweis ab und leiten auf die "ausweis-gespeichert" Seite weiter.
try {
const response = await client.v1.verbrauchsausweisWohnen[2016].erstellen.mutate({
...ausweis,
gebaeude_aufnahme_allgemein: {
...gebaeude_aufnahme_allgemein,
gebaeude_stammdaten: {
...gebaeude
}
}
})
ausweis.uid = response.uid;
gebaeude.uid = response.gebaeude_uid;
gebaeude_aufnahme_allgemein.uid = response.gebaeude_aufnahme_uid
await bilderHochladen();
return true;
} catch (e: any) {
await client.v1.tickets.erstellen.mutate({
titel: "Ausweis konnte nicht gespeichert werden",
beschreibung: e.stack,
email: user.email ?? "",
metadata: JSON.stringify({
ausweis
})
})
// TODO: Ticket mit Fehldermeldung abschicken.
}
}
addNotification({
dismissable: false,
message: "Ausweis konnte nicht gespeichert werden, bitte versuchen sie es erneut.",
subtext: "Sollte das Problem weiterhin bestehen, kontaktieren sie bitte den Support.",
timeout: 6000,
type: "error"
})
return false
}
let images: (UploadedGebaeudeBild & { base64?: string })[] = ausweis.gebaeude_aufnahme_allgemein?.gebaeude_stammdaten?.gebaeude_bilder || [];
async function spaeterWeitermachen() {
const result = await ausweisSpeichern();
const result = await verbrauchsausweisWohnenSpeichern(ausweis, gebaeude, gebaeude_aufnahme_allgemein, images, user);
if (result === true) {
window.history.pushState({}, "", `${location.pathname}?uid=${ausweis.uid}`);
if (result !== null) {
// Falls der Nutzer zurück navigiert, sollte er wieder auf seinen Vorgang kommen.
// Sonst müsste er alles neu eingeben...
ausweis.uid = result.uid
gebaeude.uid = result.gebaeude_uid
gebaeude_aufnahme_allgemein.uid = result.gebaeude_aufnahme_uid
window.history.pushState({}, "", `${location.pathname}?uid=${result.uid}`);
speichernOverlayHidden = false;
}
}
@@ -187,10 +67,10 @@
gebaeude_aufnahme_allgemein.brennstoff_1 = "Erdgas H";
ausweis.einheit_1 = "kWh";
ausweis.anteil_warmwasser_1 = 18;
ausweis.startdatum = moment("12.01.2019").toDate();
gebaeude.plz = "21039";
gebaeude.ort = "Hamburg";
gebaeude.adresse = "Curslacker Deich 170";
ausweis.startdatum = moment("01.01.2019").toDate();
gebaeude_aufnahme_allgemein.plz = "21039";
gebaeude_aufnahme_allgemein.ort = "Hamburg";
gebaeude_aufnahme_allgemein.adresse = "Curslacker Deich 170";
gebaeude_aufnahme_allgemein.gebaeudeteil = "Gesamtgebäude";
gebaeude = gebaeude;
@@ -200,18 +80,31 @@
async function ausweisAbschicken(e: SubmitEvent) {
if (e && e.preventDefault) e.preventDefault();
const result = await ausweisSpeichern();
const result = await verbrauchsausweisWohnenSpeichern(ausweis, gebaeude, gebaeude_aufnahme_allgemein, images, user);
if (result === true) {
if (result !== null) {
// Falls der Nutzer zurück navigiert, sollte er wieder auf seinen Vorgang kommen.
// Sonst müsste er alles neu eingeben...
window.history.pushState({}, "", `${location.pathname}?uid=${ausweis.uid}`);
window.location.href = `/kundendaten?uid=${ausweis.uid}`;
ausweis.uid = result.uid
gebaeude.uid = result.gebaeude_uid
gebaeude_aufnahme_allgemein.uid = result.gebaeude_aufnahme_uid
window.history.pushState({}, "", `${location.pathname}?uid=${result.uid}`);
window.location.href = `/kundendaten?uid=${result.uid}`;
}
}
let waitOverlayHidden = true;
let speichernOverlayHidden = true;
$: {
if (gebaeude_aufnahme_allgemein.saniert
&& gebaeude_aufnahme_allgemein.oberste_geschossdecke_gedaemmt === undefined
&& gebaeude_aufnahme_allgemein.dachgeschoss_gedaemmt === undefined) {
gebaeude_aufnahme_allgemein.oberste_geschossdecke_gedaemmt = true;
gebaeude_aufnahme_allgemein.dachgeschoss_gedaemmt = true;
}
}
</script>
<Overlay bind:hidden={speichernOverlayHidden}>
@@ -221,7 +114,9 @@
</Overlay>
<Overlay bind:hidden={waitOverlayHidden}>
<p class="text-white font-semibold text-4xl">Bitte warten sie, ihr Ausweis wird nun erstellt.</p>
<p class="text-white font-semibold text-4xl">
Bitte warten sie, ihr Ausweis wird nun erstellt.
</p>
</Overlay>
<div class="flex flex-row gap-8 items-center mb-8">
@@ -230,15 +125,19 @@
<Progressbar progress={0} />
</div>
<PerformanceScore bind:ausweis />
<PerformanceScore
bind:ausweis
bind:gebaeude_aufnahme_allgemein
bind:gebaeude
/>
</div>
<form on:submit={ausweisAbschicken} name="ausweis" data-test="ausweis">
<div
class="yellow-box"
>
<div class="yellow-box">
<div class="flex flex-row justify-between">
<button class="button" type="button" on:click={spaeterWeitermachen}>Später Weitermachen</button>
<button class="button" type="button" on:click={spaeterWeitermachen}
>Später Weitermachen</button
>
<div class="flex gap-4">
<Hilfe />
<button
@@ -253,7 +152,11 @@
<Label>A - Prüfung der Ausweisart</Label>
<Ausweisart bind:gebaeude bind:gebaeude_aufnahme_allgemein bind:ausweis />
<Ausweisart
bind:gebaeude
bind:gebaeude_aufnahme_allgemein
bind:ausweis
/>
<hr />
@@ -271,13 +174,14 @@
</HelpLabel>
<div>
<input
name="adresse" data-test="adresse"
name="adresse"
data-test="adresse"
type="text"
autocomplete="off"
required
data-msg-minlength="min. 5 Zeichen"
data-msg-maxlength="max. 40 Zeichen"
bind:value={gebaeude.adresse}
bind:value={gebaeude_aufnahme_allgemein.adresse}
/>
</div>
</div>
@@ -285,9 +189,9 @@
<!-- PLZ -->
<div class="form-group col-md-4 PLZ">
<ZipSearch
bind:zip={gebaeude.plz}
bind:city={gebaeude.ort}
name="plz" data-test="plz"
bind:zip={gebaeude_aufnahme_allgemein.plz}
bind:city={gebaeude_aufnahme_allgemein.ort}
name="plz"
/>
</div>
@@ -297,9 +201,10 @@
</HelpLabel>
<div>
<input
name="ort" data-test="ort"
name="ort"
data-test="ort"
readonly={true}
bind:value={gebaeude.ort}
bind:value={gebaeude_aufnahme_allgemein.ort}
type="text"
/>
</div>
@@ -314,7 +219,8 @@
</HelpLabel>
<div>
<input
name="flaeche" data-test="flaeche"
name="flaeche"
data-test="flaeche"
maxlength="4"
type="number"
required
@@ -331,14 +237,21 @@
<Label>Keller *</Label>
<div>
<select
name="keller" data-test="keller"
name="keller"
data-test="keller"
required
bind:value={gebaeude_aufnahme_allgemein.keller}
>
<option disabled>Bitte auswählen</option>
<option value={Enums.Heizungsstatus.NICHT_VORHANDEN}>nicht vorhanden</option>
<option value={Enums.Heizungsstatus.UNBEHEIZT}>unbeheizt</option>
<option value={Enums.Heizungsstatus.BEHEIZT}>beheizt</option>
<option value={Enums.Heizungsstatus.NICHT_VORHANDEN}
>nicht vorhanden</option
>
<option value={Enums.Heizungsstatus.UNBEHEIZT}
>unbeheizt</option
>
<option value={Enums.Heizungsstatus.BEHEIZT}
>beheizt</option
>
</select>
</div>
</div>
@@ -347,11 +260,22 @@
<div class="form-group col-md-3">
<Label>Dachgeschoss *</Label>
<div>
<select name="dachgeschoss" data-test="dachgeschoss" bind:value={gebaeude_aufnahme_allgemein.dachgeschoss} required>
<select
name="dachgeschoss"
data-test="dachgeschoss"
bind:value={gebaeude_aufnahme_allgemein.dachgeschoss}
required
>
<option disabled>Bitte auswählen</option>
<option value={Enums.Heizungsstatus.NICHT_VORHANDEN}>nicht vorhanden</option>
<option value={Enums.Heizungsstatus.UNBEHEIZT}>unbeheizt</option>
<option value={Enums.Heizungsstatus.BEHEIZT}>beheizt</option>
<option value={Enums.Heizungsstatus.NICHT_VORHANDEN}
>nicht vorhanden</option
>
<option value={Enums.Heizungsstatus.UNBEHEIZT}
>unbeheizt</option
>
<option value={Enums.Heizungsstatus.BEHEIZT}
>beheizt</option
>
</select>
</div>
</div>
@@ -359,14 +283,16 @@
<!-- Gesamtfläche -->
<div class="form-group col-md-3">
<HelpLabel title="Gesamtfläche m²">
Bitte geben Sie hier die beheizte Gesamtfläche in m² ein (wenn bekannt).
Dabei handelt es sich um die Wohnfläche + weiterer Flächen innerhalb des Gebäudes
(z.B. Fläche des beheizten Kellers).
Diese Fläche wird dann im Energieausweis als energetische Nutzfläche (An) ausgewiesen.
Bitte geben Sie hier die beheizte Gesamtfläche in m² ein
(wenn bekannt). Dabei handelt es sich um die Wohnfläche +
weiterer Flächen innerhalb des Gebäudes (z.B. Fläche des
beheizten Kellers). Diese Fläche wird dann im Energieausweis
als energetische Nutzfläche (An) ausgewiesen.
</HelpLabel>
<div>
<input
name="nutzflaeche" data-test="nutzflaeche"
name="nutzflaeche"
data-test="nutzflaeche"
maxlength="4"
type="number"
required
@@ -384,7 +310,11 @@
<Label>C - Eingabe von 3 zusammenhängenden Verbrauchsjahren</Label>
<div class="GRB">
<Verbrauch bind:gebaeude bind:gebaeude_aufnahme_allgemein bind:ausweis />
<Verbrauch
bind:gebaeude
bind:gebaeude_aufnahme_allgemein
bind:ausweis
/>
</div>
<hr />
@@ -399,22 +329,24 @@
<div class="flex flex-col">
<div class="flex flex-row gap-4 items-center">
<input
type="checkbox"
class="checkbox"
name="warmwasser_enthalten" data-test="warmwasser_enthalten"
bind:checked={ausweis.warmwasser_enthalten}
/>
<Label>Warmwasser im Verbrauch enthalten</Label>
type="checkbox"
class="checkbox"
name="warmwasser_enthalten"
data-test="warmwasser_enthalten"
bind:checked={ausweis.warmwasser_enthalten}
/>
<Label>Warmwasser im Verbrauch enthalten</Label>
</div>
<div class="flex flex-row gap-4 items-center">
<input
type="checkbox"
class="checkbox"
name="warmwasser_anteil_bekannt" data-test="warmwasser_anteil_bekannt"
bind:checked={ausweis.warmwasser_anteil_bekannt}
disabled={!ausweis.warmwasser_enthalten}
/>
<Label>Anteil bekannt</Label>
type="checkbox"
class="checkbox"
name="warmwasser_anteil_bekannt"
data-test="warmwasser_anteil_bekannt"
bind:checked={ausweis.warmwasser_anteil_bekannt}
disabled={!ausweis.warmwasser_enthalten}
/>
<Label>Anteil bekannt</Label>
</div>
</div>
@@ -426,11 +358,13 @@
</HelpLabel>
<input
name="anteil_warmwasser_1" data-test="anteil_warmwasser_1"
name="anteil_warmwasser_1"
data-test="anteil_warmwasser_1"
maxlength="2"
type="number"
bind:value={ausweis.anteil_warmwasser_1}
disabled={!ausweis.warmwasser_anteil_bekannt || !ausweis.warmwasser_enthalten}
disabled={!ausweis.warmwasser_anteil_bekannt ||
!ausweis.warmwasser_enthalten}
autocomplete="off"
/>
</div>
@@ -442,13 +376,15 @@
ein Anteil von 18% angenommen.
</HelpLabel>
<input
name="anteil_warmwasser_2" data-test="anteil_warmwasser_2"
name="anteil_warmwasser_2"
data-test="anteil_warmwasser_2"
maxlength="3"
type="number"
autocomplete="off"
bind:value={ausweis.anteil_warmwasser_2}
disabled={!ausweis.zusaetzliche_heizquelle ||
!ausweis.warmwasser_anteil_bekannt || !ausweis.warmwasser_enthalten}
!ausweis.warmwasser_anteil_bekannt ||
!ausweis.warmwasser_enthalten}
/>
</div>
@@ -465,7 +401,8 @@
<label class="checkbox-inline"
><input
type="checkbox"
name="alternative_heizung" data-test="alternative_heizung"
name="alternative_heizung"
data-test="alternative_heizung"
bind:checked={ausweis.alternative_heizung}
value="Heizung"
/>Heizung</label
@@ -473,7 +410,8 @@
<label class="checkbox-inline"
><input
type="checkbox"
name="alternative_warmwasser" data-test="alternative_warmwasser"
name="alternative_warmwasser"
data-test="alternative_warmwasser"
bind:checked={ausweis.alternative_warmwasser}
value="Warmwasser"
/>Warmwasser</label
@@ -481,7 +419,8 @@
<label class="checkbox-inline"
><input
type="checkbox"
name="alternative_lueftung" data-test="alternative_lueftung"
name="alternative_lueftung"
data-test="alternative_lueftung"
bind:checked={ausweis.alternative_lueftung}
value="Lüftung"
/>Lüftung</label
@@ -489,7 +428,8 @@
<label class="checkbox-inline"
><input
type="checkbox"
name="alternative_kuehlung" data-test="alternative_kuehlung"
name="alternative_kuehlung"
data-test="alternative_kuehlung"
bind:checked={ausweis.alternative_kuehlung}
value="Kühlung"
/>Kühlung</label
@@ -512,7 +452,12 @@
Bitte wählen Sie hier den Gebäudetyp aus.
</HelpLabel>
<div>
<select name="gebaeudetyp" data-test="gebaeudetyp" required>
<select
name="gebaeudetyp"
data-test="gebaeudetyp"
bind:value={gebaeude_aufnahme_allgemein.gebaeudetyp}
required
>
<option disabled>Bitte auswählen</option>
<option value="Einfamilienhaus">Einfamilienhaus</option>
<option value="Freistehendes Einfamilienhaus"
@@ -551,7 +496,12 @@
'Gewerbe'.
</HelpLabel>
<div>
<select name="gebaeudeteil" data-test="gebaeudeteil" required>
<select
name="gebaeudeteil"
data-test="gebaeudeteil"
bind:value={gebaeude_aufnahme_allgemein.gebaeudeteil}
required
>
<option disabled>Bitte auswählen</option>
<option value="Gesamtgebäude">Gesamtgebäude</option>
<option value="Wohnen">Wohnen</option>
@@ -567,7 +517,8 @@
</HelpLabel>
<div>
<select
name="lueftung" data-test="lueftung"
name="lueftung"
data-test="lueftung"
required
bind:value={gebaeude_aufnahme_allgemein.lueftung}
>
@@ -592,7 +543,8 @@
</HelpLabel>
<div>
<select
name="kuehlung" data-test="kuehlung"
name="kuehlung"
data-test="kuehlung"
required
bind:value={gebaeude_aufnahme_allgemein.kuehlung}
>
@@ -612,7 +564,8 @@
</HelpLabel>
<div>
<input
name="leerstand" data-test="leerstand"
name="leerstand"
data-test="leerstand"
maxlength="2"
type="number"
bind:value={gebaeude_aufnahme_allgemein.leerstand}
@@ -627,7 +580,7 @@
>F - Bitte prüfen Sie hier die Angaben zum Sanierungszustand des
Gebäudes</Label
>
<BilderZusatzsysteme {images} {gebaeude} {gebaeude_aufnahme_allgemein} {ausweis} />
<BilderZusatzsysteme bind:images bind:gebaeude bind:gebaeude_aufnahme_allgemein bind:ausweis />
<hr />
<div class="flex flex-row justify-between">
<Hilfe />
@@ -636,7 +589,6 @@
</div>
</form>
<RawNotificationWrapper>
{#each Object.entries($notifications) as [uid, notification] (uid)}
<RawNotification notification={{ ...notification, uid }}>
@@ -660,6 +612,28 @@
</RawNotification>
{/if}
{#await auditPlzNichtErkannt(gebaeude_aufnahme_allgemein) then result}
{#if result}
<RawNotification
notification={{
message: "Plausibilitätsprüfung",
timeout: 0,
uid: "PLZ_NICHT_ERKANNT",
dismissable: true,
onUserDismiss: () => {
hidden.add(AuditType.PLZ_NICHT_ERKANNT);
gebaeude = gebaeude;
},
type: "warning",
}}
>
Die Postleitzahl konnte nicht zugeordnet werden. Bitte prüfen
Sie die Eingabe. Sollte die Postleitzahl korrekt eingegeben
sein, werden wir den Ort händisch zuordnen.
</RawNotification>
{/if}
{/await}
{#if auditHeizungGebaeudeBaujahr(gebaeude_aufnahme_allgemein)}
<RawNotification
notification={{
@@ -693,11 +667,12 @@
type: "warning",
}}
>
Ihre Heizungsanlage ist jünger als 3 Jahre. Für den Verbrauchsausweis müssen
Sie mindestens 3 Verbrauchsjahre eingeben die den aktuellen Stand des Gebäudes
abbilden. Ein Verbrauchsausweis ist daher nicht möglich. Bitte klicken Sie
<a href="/bedarfsausweis">hier</a> um zum Eingabeformular für den Bedarfsausweis
zu gelangen.
Ihre Heizungsanlage ist jünger als 3 Jahre. Für den
Verbrauchsausweis müssen Sie mindestens 3 Verbrauchsjahre eingeben
die den aktuellen Stand des Gebäudes abbilden. Ein Verbrauchsausweis
ist daher nicht möglich. Bitte klicken Sie
<a href="/bedarfsausweis">hier</a> um zum Eingabeformular für den Bedarfsausweis
zu gelangen.
</RawNotification>
{/if}
@@ -715,11 +690,11 @@
type: "warning",
}}
>
Die Verbrauchszeiträume sind nicht aktuell genug. Das Ende des letzten
Verbrauchszeitraumes darf nicht mehr als 18 Monate zurückliegen. Ein
Verbrauchsausweis ist mit diesen Zeiträumen daher nicht möglich. Bitte
klicken Sie <a href="/bedarfsausweis">hier</a> um zum Eingabeformular
für den Bedarfsausweis zu gelangen.
Die Verbrauchszeiträume sind nicht aktuell genug. Das Ende des
letzten Verbrauchszeitraumes darf nicht mehr als 18 Monate
zurückliegen. Ein Verbrauchsausweis ist mit diesen Zeiträumen daher
nicht möglich. Bitte klicken Sie <a href="/bedarfsausweis">hier</a> um
zum Eingabeformular für den Bedarfsausweis zu gelangen.
</RawNotification>
{/if}
@@ -738,11 +713,12 @@
type: "warning",
}}
>
Die Verbrauchszeiträume sind zu aktuell und es liegen noch keine
Klimafaktoren dazu vor. Bitte verschieben Sie die Verbrauchszeiträume
1 Jahr nach hinten. Wenn das nicht möglich ist, klicken Sie
<a href="/bedarfsausweis">hier</a> um zum Eingabeformular für den
Bedarfsausweis zu gelangen.
Die Verbrauchszeiträume sind zu aktuell und es liegen noch keine
Klimafaktoren dazu vor. Bitte verschieben Sie die
Verbrauchszeiträume 1 Jahr nach hinten. Wenn das nicht möglich
ist, klicken Sie
<a href="/bedarfsausweis">hier</a> um zum Eingabeformular für den
Bedarfsausweis zu gelangen.
</RawNotification>
{/if}
{/await}
@@ -761,8 +737,9 @@
type: "warning",
}}
>
Die Wohnfläche ist viel zu klein. Bitte überprüfen Sie Ihre Eingabe nochmal
und stellen sicher, daß sich Ihre Angaben auf das gesamte Gebäude beziehen.
Die Wohnfläche ist viel zu klein. Bitte überprüfen Sie Ihre Eingabe
nochmal und stellen sicher, daß sich Ihre Angaben auf das gesamte
Gebäude beziehen.
</RawNotification>
{/if}
@@ -780,8 +757,8 @@
type: "warning",
}}
>
Bitte überprüfen Sie nochmal die Höhe des Warmwasseranteils. Dieser scheint
nicht ganz im Rahmen zu liegen.
Bitte überprüfen Sie nochmal die Höhe des Warmwasseranteils. Dieser
scheint nicht ganz im Rahmen zu liegen.
</RawNotification>
{/if}
@@ -799,9 +776,9 @@
type: "warning",
}}
>
Bei Leerstand größer als 30% darf kein Verbrauchsausweis ausgestellt werden.
Bitte klicken Sie <a href="/bedarfsausweis">hier</a> um zum Eingabeformular
für den Bedarfsausweis zu gelangen.
Bei Leerstand größer als 30% darf kein Verbrauchsausweis ausgestellt
werden. Bitte klicken Sie <a href="/bedarfsausweis">hier</a> um zum Eingabeformular
für den Bedarfsausweis zu gelangen.
</RawNotification>
{/if}
@@ -819,17 +796,59 @@
type: "warning",
}}
>
Die Abweichung der Verbräuche zwischen Zeitraum {auditVerbrauchAbweichung(ausweis,
gebaeude
)[0]} und {auditVerbrauchAbweichung(ausweis, gebaeude)[1]} beträgt mehr als 25%
und sie haben keinen Leerstand angegeben. Sind sie sich sicher, dass
das stimmt?
Die Abweichung der Verbräuche zwischen Zeitraum {auditVerbrauchAbweichung(
ausweis,
gebaeude,
)[0]} und {auditVerbrauchAbweichung(ausweis, gebaeude)[1]} beträgt mehr
als 30% und sie haben keinen Leerstand angegeben. Sind sie sich sicher,
dass das stimmt?
</RawNotification>
{/if}
{#await auditEndEnergie(ausweis, gebaeude, gebaeude_aufnahme_allgemein) then result}
{#if result}
<RawNotification
notification={{
message: "Plausibilitätsprüfung",
timeout: 0,
uid: "END_ENERGIE",
dismissable: true,
onUserDismiss: () => {
hidden.add(AuditType.END_ENERGIE);
gebaeude = gebaeude;
},
type: "warning",
}}
>
Die Endenergie liegt außerhalb des normalen Rahmens. Bitte
nochmal überprüfen. Bei Passivhäusern oder ganz alten
unsanierten Gebäuden ist so eine Abweichung durchaus möglich.
</RawNotification>
{/if}
{/await}
{#if auditWohnflaecheGroesserGesamtflaeche(gebaeude_aufnahme_allgemein)}
<RawNotification
notification={{
message: "Plausibilitätsprüfung",
timeout: 0,
uid: "WOHNFLAECHE_GROESSER_GESAMTFLAECHE",
dismissable: true,
onUserDismiss: () => {
hidden.add(AuditType.WOHNFLAECHE_GROESSER_GESAMTFLAECHE);
gebaeude = gebaeude;
},
type: "warning",
}}
>
Die Wohnfläche darf nicht größer als die Nutzfläche sein.
</RawNotification>
{/if}
</RawNotificationWrapper>
<style>
:global(input[type="number"]), :global(input[type="text"]) {
:global(input[type="number"]),
:global(input[type="text"]) {
@apply input input-bordered py-1.5 px-2 h-auto;
}

View File

@@ -17,6 +17,9 @@
limit: 10
});
console.log(ausweise);
if (!ausweise) return;
ausweisUeberpruefung = ausweise.map(ausweis => verbrauchsausweisWohnenCalculateFormProgress(ausweis));

View File

@@ -61,9 +61,9 @@
}
</script>
<h1 class="text-4xl font-medium my-8">Einstellungen</h1>
<Tabs class="h-full">
<div class="grid grid-cols-[1fr_3fr] h-full gap-8">
<h1 class="text-4xl font-medium mb-8">Einstellungen</h1>
<Tabs class="h-[calc(100%-6rem)]">
<div class="grid grid-cols-1 md:grid-cols-[1fr_3fr] h-full gap-8">
<TabList>
<Tab>
<Person size={22} />
@@ -106,7 +106,7 @@
<TabPanel>
<h2 class="text-2xl font-medium">Profil</h2>
<form class="flex flex-col gap-4 my-4 max-w-2xl" on:submit={profilSpeichern}>
<div class="flex flex-row gap-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="flex flex-col">
<span class="whitespace-nowrap">Vorname</span>
<input type="text" class="input text-base-content font-medium" placeholder="Max" bind:value={benutzer.vorname}>
@@ -124,7 +124,7 @@
<span class="whitespace-nowrap">Telefonnummer</span>
<input type="phone" class="input text-base-content font-medium" placeholder="040 12345678" bind:value={benutzer.telefon}>
</div>
<div class="flex flex-row gap-4">
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="flex flex-col">
<span class="whitespace-nowrap">Adresse</span>
<input type="text" class="input text-base-content font-medium" placeholder="Musterstraße 123" bind:value={benutzer.adresse}>
@@ -138,7 +138,7 @@
<input type="text" class="input text-base-content font-medium" placeholder="Musterhausen" bind:value={benutzer.ort}>
</div>
</div>
<div class="flex flex-row gap-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="flex flex-col">
<span class="whitespace-nowrap">Passwort</span>
<input type="password" class="input text-base-content font-medium" placeholder="*********" bind:value={passwort}>
@@ -152,7 +152,8 @@
</form>
</TabPanel>
<TabPanel>
<h1>Panel Two</h1>
<h2 class="text-2xl font-medium">Ausweise</h2>
<p>In Zukunft werden sie hier spezifische Einstellungen für die Ausweisgenerierung ansehen können. Bitte haben sie Geduld.</p>
</TabPanel>
</div>
</div>

View File

@@ -0,0 +1,29 @@
<script lang="ts">
import { BenutzerClient, GebaeudeClient } from "#components/Ausweis/types";
export let user: BenutzerClient;
export let gebaeudeArray: GebaeudeClient[];
</script>
<h1 class="text-4xl font-medium my-8">Willkommen zurück, {user.vorname}!</h1>
<p class="text-lg">
Hier finden Sie eine Übersicht über all ihre Ausweise und Gebäude.
</p>
<h1 class="text-4xl font-medium my-8">Gebäude</h1>
<div class="grid grid-cols-1 gap-4 lg:grid-cols-2">
{#each gebaeudeArray as gebaeude}
<div class="card lg:card-side bg-base-200 card-bordered border-base-300">
<figure class="lg:w-1/2">
<img
src={(gebaeude.gebaeude_bilder && `/bilder/${gebaeude.gebaeude_bilder[0]?.uid}.webp`) || "/images/placeholder.jpg"}
class="object-cover w-full h-full"
alt="Gebäudebild"
/>
</figure>
<div class="card-body lg:w-1/2 p-4">
<h4 class="text-lg font-semibold">{gebaeude.adresse}, {gebaeude.plz} {gebaeude.ort}</h4>
</div>
</div>
{/each}
</div>

View File

@@ -43,8 +43,9 @@
function addNewField() {
template = designer.getTemplate();
const page = designer.getPageCursor();
template.schemas[0]["new-field"] = {
template.schemas[page]["new-field"] = {
type: "text",
position: { x: 0, y: 0 },
width: 10,
@@ -87,9 +88,18 @@
input.click()
}
function onKeydown(e: KeyboardEvent) {
if (e.ctrlKey && e.key === "s") {
e.preventDefault();
exportTemplate();
}
}
let loadTemplateInput: HTMLInputElement;
</script>
<svelte:window on:keydown={onKeydown}></svelte:window>
<header class="mb-4">
<button class="btn btn-secondary" on:click={() => loadTemplateInput.click()}>Change base PDF</button>
<button class="btn btn-secondary" on:click={addNewField}>Add new Field</button>
@@ -99,4 +109,4 @@
<input type="file" hidden bind:this={loadTemplateInput} on:change={loadBasePDF}>
</header>
<div bind:this={container}></div>
<div bind:this={container} class="h-[80vh]"></div>

View File

@@ -5,13 +5,14 @@
import { Viewer } from "@pdfme/ui";
import { Check } from "radix-svelte-icons";
import { image, text } from "@pdfme/schemas";
import { AusweisData, convertAusweisData } from "#lib/AusweisData";
import { VerbrauchsausweisWohnenClient } from "#components/Ausweis/types";
import { convertAusweisData } from "#lib/AusweisData";
export let ausweise: AusweisData[];
export let ausweise: VerbrauchsausweisWohnenClient[];
let pdfInputs: AusweisData;
let pdfInputs: VerbrauchsausweisWohnenClient;
let template: Template;
let viewer: Viewer
@@ -37,11 +38,13 @@
return
};
const convertedInputs = convertAusweisData(pdfInputs);
viewer = new Viewer({
domContainer: pdfViewerContainer,
template,
inputs: [convertAusweisData(pdfInputs)],
plugins: { text, image, variable}
inputs: [convertedInputs],
plugins: { text, image, variable: { ...variable, pdf: variable.pdf.bind(convertedInputs), ui: variable.ui.bind(convertedInputs) } }
})
};
@@ -51,8 +54,9 @@
input.click();
}
function changeInputs(inputs: AusweisData) {
function changeInputs(inputs: VerbrauchsausweisWohnenClient) {
pdfInputs = inputs;
if (!template) {
alert("Bitte laden Sie zuerst ein Template.")
return
@@ -60,11 +64,13 @@
if (viewer) viewer.destroy();
const convertedInputs = convertAusweisData(pdfInputs);
viewer = new Viewer({
domContainer: pdfViewerContainer,
template,
inputs: [convertAusweisData(pdfInputs)],
plugins: { text, image, variable}
inputs: [convertedInputs],
plugins: { text, image, variable: { ...variable, pdf: variable.pdf.bind(convertedInputs), ui: variable.ui.bind(convertedInputs) } }
})
}
@@ -81,7 +87,7 @@
<div class="flex flex-col gap-4">
{#each ausweise as ausweis}
<div class="rounded-lg border p-2 flex flex-row items-center justify-between">
<h2 class="text-black">{ausweis.gebaeude_stammdaten.adresse}</h2>
<h2 class="text-black">{ausweis.gebaeude_aufnahme_allgemein.gebaeude_stammdaten.adresse}</h2>
<button class="btn btn-square btn-ghost p-1.5" on:click={() => {
changeInputs(ausweis)
}}><Check size={20}/></button>

View File

@@ -2,11 +2,7 @@
import ZipSearch from "../components/PlzSuche.svelte";
import Label from "../components/Label.svelte";
import type {
BedarfsausweisWohnen,
Benutzer,
Bezahlmethoden,
VerbrauchsausweisGewerbe,
VerbrauchsausweisWohnen,
} from "@ibcornelsen/database/client";
import { Enums } from "@ibcornelsen/database/client";
import PaymentOption from "#components/PaymentOption.svelte";
@@ -15,6 +11,7 @@
import type { AppRouter } from "@ibcornelsen/api";
import CheckoutItem from "#components/CheckoutItem.svelte";
import { BenutzerClient, VerbrauchsausweisWohnenClient } from "#components/Ausweis/types";
import { PRICES } from "#lib/constants";
export let user: BenutzerClient;
export let ausweis:
@@ -22,7 +19,18 @@
// TODO: überarbeiten und zu inferProcedureOutput machen
let rechnung: inferProcedureInput<
AppRouter["v1"]["rechnungen"]["erstellen"]
> = {};
> = {
email: user.email,
empfaenger: user.vorname + " " + user.name,
strasse: user.adresse,
plz: user.plz,
ort: user.ort,
versand_empfaenger: user.vorname + " " + user.name,
versand_strasse: user.adresse,
versand_plz: user.plz,
versand_ort: user.ort,
telefon: user.telefon,
};
let services = [
{
@@ -54,15 +62,12 @@
export let selectedPaymentType: Bezahlmethoden =
Enums.Bezahlmethoden.paypal;
let agbAkzeptiert: boolean;
let datenschutzAkzeptiert: boolean;
async function createPayment(e: SubmitEvent) {
e.preventDefault();
const response = await client.v1.rechnungen.erstellen.mutate({
...rechnung,
ausweisart: ausweis.ausweisart,
ausweisart: Enums.Ausweisart.VerbrauchsausweisWohnen,
uid: ausweis.uid,
bezahlmethode: selectedPaymentType,
services: services
@@ -72,9 +77,16 @@
window.location.href = response.checkout_url;
}
const priceTotal = services.reduce((acc, service) => {
if (service.selected) {
return acc + service.price;
}
return acc;
}, 0) + PRICES[Enums.Ausweisart.VerbrauchsausweisWohnen][0];
</script>
<div class="grid grid-cols-[2fr_1fr] gap-4 h-full">
<form class="grid grid-cols-[2fr_1fr] gap-4 h-full" on:submit={createPayment}>
<div>
<h3 class="font-semibold">Ansprechpartner</h3>
<div class="rounded-lg border p-4 border-base-300 bg-base-100">
@@ -348,23 +360,23 @@
<div class="mt-auto">
<hr />
<div class="flex flex-row items-center justify-between">
<span class="opacity-75 text-sm">Brutto</span>
<span class="font-semibold text-sm">45</span>
<span class="opacity-75 text-sm">Netto</span>
<span class="font-semibold text-sm">{priceTotal * 0.81}</span>
</div>
<div class="flex flex-row items-center justify-between">
<span class="opacity-75 text-sm">Netto</span>
<span class="font-semibold text-sm">45</span>
<span class="opacity-75 text-sm">19% MwSt</span>
<span class="font-semibold text-sm">{priceTotal * 0.19}</span>
</div>
<hr />
<div class="flex flex-row items-center justify-between">
<span class="opacity-75 text-sm">Gesamt</span>
<span class="font-semibold text-sm">45</span>
<span class="font-semibold text-sm">{priceTotal}</span>
</div>
<p class="mt-8">Mit dem Klick auf "Bestellung Bestätigen" akzeptieren sie unsere <a href="/agb">AGB</a> und <a href="/impressum">Datenschutzbestimmungen</a>. Sie werden zu ihrem ausgewählten Bezahlprovider weitergeleitet, nach Bezahlung werden sie automatisch zu unserem Portal zurückgeleitet.</p>
<button class="btn btn-secondary w-full mt-4" disabled
<button class="btn btn-secondary w-full mt-4"
>Bestellung Bestätigen</button
>
</div>
</div>
</div>
</div>
</form>

View File

@@ -4,6 +4,8 @@
Bezahlmethoden,
} from "@ibcornelsen/database/client";
import { Enums } from "@ibcornelsen/database/client";
import { dialogs } from "svelte-dialogs";
import LoginDialog from "#components/LoginDialog.svelte";
export let user: BenutzerClient;
export let ausweis: VerbrauchsausweisWohnenClient;
@@ -40,8 +42,14 @@
import { PRICES } from "#lib/constants";
import { BenutzerClient, VerbrauchsausweisWohnenClient } from "#components/Ausweis/types";
import { validateAccessTokenClient } from "src/client/lib/validateAccessToken";
import { client } from "src/trpc";
const prices = PRICES[ausweis.ausweisart];
let prices: number[] = []
if (ausweis.gebaeude_aufnahme_allgemein.ausweisart) {
prices = PRICES[ausweis.gebaeude_aufnahme_allgemein.ausweisart]
}
let basePrice: number = prices[0];
@@ -52,12 +60,29 @@
0
);
function speichern(e: SubmitEvent) {
async function speichern(e: SubmitEvent) {
e.preventDefault()
console.log("Speichern");
// Um einen Ausweis zu speichern müssen wir eingeloggt sein, andernfalls wird die API den call ablehnen.
// Wir prüfen also ob wir eingeloggt sind und leiten den Nutzer ggf. auf die Login Seite weiter.
if (!(await validateAccessTokenClient())) {
// TOOD: Auf Dialog umstellen.
const loggedIn = await dialogs.modal(LoginDialog);
if (!loggedIn) {
return false;
}
}
// Falls der Ausweis noch keine benutzer_id hat müssen wir ihn claimen, damit er dem jetzigen Nutzer zugewiesen wird...
await client.v1.verbrauchsausweisWohnen.claim.mutate({
uid: ausweis.uid
})
window.location.href = `/kaufabschluss?uid=${ausweis.uid}`;
}
</script>
<div class="w-full px-8">

16
src/pages/UMBE_test.astro Normal file
View File

@@ -0,0 +1,16 @@
---
import Layout from "#layouts/UMBE_Layout.astro";
---
<Layout title="Energieausweis online erstellen - Online Energieausweis">
<h1>HALLO</h1>
</div>
</Layout>

View File

@@ -1,5 +1,6 @@
import { createCaller } from "#lib/caller";
import { APIRoute } from "astro";
import { validate } from "uuid";
export const get: APIRoute = async ({params, cookies}) => {
const { uid } = params;
@@ -8,6 +9,10 @@ export const get: APIRoute = async ({params, cookies}) => {
return new Response("No uid provided", { status: 400 });
}
if (!validate(uid)) {
return new Response("Invalid uid", { status: 400 });
}
const caller = createCaller({ cookies })
const image = await caller.v1.bilder.getBase64({ uid })

View File

@@ -16,7 +16,7 @@ if (!accessTokenValid) {
const caller = createCaller(Astro);
const ausweise = await caller.v1.verbrauchsausweisWohnen.getMany({
limit: 25
limit: 25,
});
---

View File

@@ -1,16 +1,14 @@
---
import { createCaller } from "#lib/caller";
import UserLayout from "../../../layouts/UserLayout.astro";
import DashboardPDFViewerModule from "../../../modules/Dashboard/DashboardPDFViewerModule.svelte";
import { prisma } from "@ibcornelsen/database/server";
const ausweise = await prisma.verbrauchsausweisWohnen.findMany({
take: 10,
include: {
benutzer: true,
gebaeude_stammdaten: true,
rechnungen: true
}
})
const caller = createCaller(Astro);
const ausweise = await caller.v1.verbrauchsausweisWohnen.getMany({
limit: 10
});
---
<UserLayout title="PDF Viewer">

View File

@@ -1,24 +1,19 @@
---
import UserLayout from "../../../layouts/UserLayout.astro";
import { API_UID_COOKIE_NAME } from "../../../lib/constants";
import DashboardAusweiseModule from "#modules/Dashboard/DashboardAusweiseModule.svelte";
import { prisma } from "@ibcornelsen/database/server";
const uid = Astro.cookies.get(API_UID_COOKIE_NAME).value
import { validateAccessTokenServer } from "src/server/lib/validateAccessToken";
import { createCaller } from "#lib/caller";
if (!uid) {
const accessTokenValid = await validateAccessTokenServer(Astro);
if (!accessTokenValid) {
return Astro.redirect("/auth/login")
}
const user = await prisma.benutzer.findUnique({
where: {
uid
}
})
const caller = createCaller(Astro);
if (!user) {
return Astro.redirect("/auth/login")
}
const user = await caller.v1.benutzer.self();
---
<UserLayout title="Dashboard">

View File

@@ -1,13 +1,21 @@
---
import { createCaller } from "#lib/caller";
import UserLayout from "../../layouts/UserLayout.astro";
import { validateAccessTokenServer } from "src/server/lib/validateAccessToken";
import DashboardModule from "#modules/Dashboard/DashboardModule.svelte";
const accessTokenValid = await validateAccessTokenServer(Astro);
if (!accessTokenValid) {
return Astro.redirect("/auth/login")
}
const caller = createCaller(Astro);
const user = await caller.v1.benutzer.self();
const gebaeudeArray = await caller.v1.gebaeude.getMany({ limit: 5 });
---
<UserLayout title="Dashboard">
<DashboardModule user={user} gebaeudeArray={gebaeudeArray} />
</UserLayout>

31
src/pages/email.astro Normal file
View File

@@ -0,0 +1,31 @@
---
import ThemeController from "../components/ThemeController.svelte";
import moment from "moment"
const lightTheme = Astro.cookies.get("theme").value === "light";
---
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>email</title>
</head>
<body class="p-8">
<main class="prose border rounded-box flex flex-col items-center border-base-300">
<div class="bg-base-200 h-auto w-full flex flex-col items-center gap-4 justify-start py-4 border-b border-base-300 rounded-t-box">
<img src="/images/header/logo-big.svg" class="h-12 my-0">
</div>
<div class="bg-base-100 h-auto w-full flex flex-col items-center gap-4 justify-start py-16 px-8">
<h2 class="my-0">Erinnerung vom IBCornelsen</h2>
<p class="text-center">Bitte denken sie daran, die restlichen Bilder für ihr Gebäude hochzuladen. Die aktuelle Gesetzgebung erfordert dies, bevor ihr Ausweis ausgestellt werden kann.</p>
<a class="btn btn-primary text-primary-content no-underline">Bilder Hochladen</a>
</div>
<div class="bg-base-200 h-auto w-full flex flex-col items-center gap-4 justify-start py-4 border-t border-base-300 rounded-b-box">
<a class="text-base-content font-medium text-sm" href="https://online-energieausweis.org">© {moment().format("YYYY")} IB Cornelsen Hamburg</a>
</div>
</main>
<ThemeController lightTheme={lightTheme}></ThemeController>
</body>
</html>

View File

@@ -1,24 +1,27 @@
---
import AusweisLayout from "#layouts/AusweisLayout.astro";
import VerbrauchsausweisWohnenModule from "#modules/Ausweise/VerbrauchsausweisWohnenModule.svelte";
import { VerbrauchsausweisWohnenClient } from "#components/Ausweis/types";
import { createCaller } from "#lib/caller";
const uid = Astro.url.searchParams.get("uid");
let ausweis: VerbrauchsausweisWohnenClient = {} as VerbrauchsausweisWohnenClient;
let ausweis: VerbrauchsausweisWohnenClient = {
gebaeude_aufnahme_allgemein: { gebaeude_stammdaten: {} },
} as VerbrauchsausweisWohnenClient;
const caller = createCaller(Astro);
if (uid) {
ausweis = await caller.v1.verbrauchsausweisWohnen.get({
uid: uid
})
uid: uid,
});
if (!ausweis) {
// Der Ausweis scheint nicht zu existieren.
// Wir leiten auf die generische Ausweisseite ohne UID weiter.
return Astro.redirect("/energieausweis-erstellen/verbrauchsausweis-wohnen");
return Astro.redirect(
"/energieausweis-erstellen/verbrauchsausweis-wohnen"
);
}
}
---

View File

@@ -21,7 +21,7 @@ const ausweis = await caller.v1.verbrauchsausweisWohnen.get({
const user = await caller.v1.benutzer.self();
if (!ausweis || !user) {
if (!ausweis) {
return Astro.redirect("/404");
}
---

View File

@@ -1,11 +1,16 @@
---
import { generate } from "@pdfme/generator";
import VerbrauchsausweisWohnen2016Template from "../../data/templates/verbrauchsausweis-wohnen-2016.json";
import VerbrauchsausweisWohnen2016Template from "../../lib/pdf/templates/GEG24_Verbrauchsausweis.json";
import { convertAusweisData } from "#lib/AusweisData";
import { variable } from "#lib/pdf/plugins/variables";
import { text, image } from "@pdfme/schemas"
import { text, image } from "@pdfme/schemas";
import { createCaller } from "#lib/caller";
import { VerbrauchsausweisWohnenClient } from "#components/Ausweis/types";
import { Schema, Template } from "@pdfme/common";
import { Moment } from "moment";
import { endEnergieVerbrauchVerbrauchsausweis_2016 } from "#lib/Berechnungen/VerbrauchsausweisWohnen/VerbrauchsausweisWohnen_2016";
import moment from "moment";
import { getEmpfehlungen } from "#lib/XML/getEmpfehlungen";
const base64 = Astro.url.searchParams.get("base64");
let ausweis: VerbrauchsausweisWohnenClient | null = null;
@@ -23,24 +28,208 @@ if (base64) {
const caller = createCaller(Astro);
ausweis = await caller.v1.verbrauchsausweisWohnen.get({
uid: uidAusweis
})
uid: uidAusweis,
});
}
if (!ausweis) {
return Astro.redirect("/404");
const template = VerbrauchsausweisWohnen2016Template as Template;
const berechnungen = await endEnergieVerbrauchVerbrauchsausweis_2016(ausweis);
let inputs: Record<string, string> = {};
// Verbrauchszeiträume
const addVerbrauchszeitraum = (() => {
let row = 0;
let columnWidth = [16, 16, 80, 12, 18, 18, 18, 12];
const getPosition = (column: number) => {
return {
position: {
x:
15 +
columnWidth.reduce(
(a, c, i) => (i >= column ? a : a + c),
0
),
y: 141 + row * 5,
},
height: 5,
width: 14,
type: "text",
fontSize: 8,
verticalAlign: "middle",
};
};
return (
schema: Schema,
start: Moment,
end: Moment,
energietraeger: string,
primaerenergiefaktor: number,
energieverbrauch: number,
anteilWarmwasser: number,
klimafaktor: number
) => {
inputs[`verbrauchszeitraum_${row}_start`] = start.format("MM.YYYY");
inputs[`verbrauchszeitraum_${row}_end`] = end.format("MM.YYYY");
inputs[`verbrauchszeitraum_${row}_energietraeger`] = energietraeger;
inputs[`verbrauchszeitraum_${row}_primaerenergiefaktor`] =
primaerenergiefaktor.toString();
inputs[`verbrauchszeitraum_${row}_energieverbrauch`] =
energieverbrauch.toString();
inputs[`verbrauchszeitraum_${row}_anteil_warmwasser`] = Math.round(
anteilWarmwasser * energieverbrauch
).toString();
inputs[`verbrauchszeitraum_${row}_anteil_heizung`] = Math.round(
(1 - anteilWarmwasser) *
energieverbrauch
).toString();
inputs[`verbrauchszeitraum_${row}_klimafaktor`] =
klimafaktor.toString();
schema[`verbrauchszeitraum_${row}_start`] = getPosition(0);
schema[`verbrauchszeitraum_${row}_end`] = getPosition(1);
schema[`verbrauchszeitraum_${row}_energietraeger`] = getPosition(2);
schema[`verbrauchszeitraum_${row}_primaerenergiefaktor`] =
getPosition(3);
schema[`verbrauchszeitraum_${row}_energieverbrauch`] = getPosition(4);
schema[`verbrauchszeitraum_${row}_anteil_warmwasser`] = getPosition(5);
schema[`verbrauchszeitraum_${row}_anteil_heizung`] = getPosition(6);
schema[`verbrauchszeitraum_${row}_klimafaktor`] = getPosition(7);
row++;
};
})();
addVerbrauchszeitraum(
template.schemas[2],
moment(ausweis.startdatum),
moment(ausweis.startdatum).add("1", "year"),
ausweis.gebaeude_aufnahme_allgemein?.brennstoff_1,
berechnungen?.primaerfaktorww,
berechnungen?.verbrauch_1_kwh,
ausweis.anteil_warmwasser_1 / 100,
berechnungen?.klimafaktoren[0].klimafaktor
);
addVerbrauchszeitraum(
template.schemas[2],
moment(ausweis.startdatum).add("1", "year"),
moment(ausweis.startdatum).add("2", "years"),
ausweis.gebaeude_aufnahme_allgemein?.brennstoff_1,
berechnungen?.primaerfaktorww,
berechnungen?.verbrauch_2_kwh,
ausweis.anteil_warmwasser_1 / 100,
berechnungen?.klimafaktoren[1].klimafaktor
);
addVerbrauchszeitraum(
template.schemas[2],
moment(ausweis.startdatum).add("2", "years"),
moment(ausweis.startdatum).add("3", "years"),
ausweis.gebaeude_aufnahme_allgemein?.brennstoff_1,
berechnungen?.primaerfaktorww,
berechnungen?.verbrauch_3_kwh,
ausweis.anteil_warmwasser_1 / 100,
berechnungen?.klimafaktoren[2].klimafaktor
);
if (ausweis.zusaetzliche_heizquelle) {
addVerbrauchszeitraum(
template.schemas[2],
moment(ausweis.startdatum),
moment(ausweis.startdatum).add("1", "year"),
ausweis.gebaeude_aufnahme_allgemein?.brennstoff_2,
berechnungen?.primaerfaktorww_1,
berechnungen?.verbrauch_4_kwh,
ausweis.anteil_warmwasser_2 / 100,
berechnungen?.klimafaktoren[0].klimafaktor
);
addVerbrauchszeitraum(
template.schemas[2],
moment(ausweis.startdatum).add("1", "year"),
moment(ausweis.startdatum).add("2", "years"),
ausweis.gebaeude_aufnahme_allgemein?.brennstoff_2,
berechnungen?.primaerfaktorww_1,
berechnungen?.verbrauch_5_kwh,
ausweis.anteil_warmwasser_2 / 100,
berechnungen?.klimafaktoren[1].klimafaktor
);
addVerbrauchszeitraum(
template.schemas[2],
moment(ausweis.startdatum).add("2", "years"),
moment(ausweis.startdatum).add("3", "years"),
ausweis.gebaeude_aufnahme_allgemein?.brennstoff_2,
berechnungen?.primaerfaktorww_1,
berechnungen?.verbrauch_6_kwh,
ausweis.anteil_warmwasser_2 / 100,
berechnungen?.klimafaktoren[1].klimafaktor
);
}
const convertedInputs = { ...convertAusweisData(ausweis), ...inputs };
const empfehlungen = getEmpfehlungen(ausweis);
const addEmpfehlung = (() => {
let i = 0;
let columnWidth = [9, 25, 81, 13, 10, 17, 31];
const getPosition = (column: number) => {
return {
position: {
x:
13 +
columnWidth.reduce(
(a, c, i) => (i >= column ? a : a + c),
0
),
y: 94 + i * 15,
},
height: 15,
width: columnWidth[column],
type: "text",
fontSize: 8,
verticalAlign: "middle",
};
};
return (empfehlung: ReturnType<typeof getEmpfehlungen>[0]) => {
template.schemas[3][`empfehlung_${i}_nr`] = getPosition(0)
template.schemas[3][`empfehlung_${i}_bauteil`] = getPosition(1)
template.schemas[3][`empfehlung_${i}_beschreibung`] = getPosition(2)
template.schemas[3][`empfehlung_${i}_amortisationszeit`] = getPosition(5)
template.schemas[3][`empfehlung_${i}_kosten`] = getPosition(6)
convertedInputs[`empfehlung_${i}_nr`] = (i + 1).toString();
convertedInputs[`empfehlung_${i}_bauteil`] = empfehlung.anlagenteil;
convertedInputs[`empfehlung_${i}_beschreibung`] = empfehlung.description;
convertedInputs[`empfehlung_${i}_amortisationszeit`] = "";
convertedInputs[`empfehlung_${i}_kosten`] = "";
i++;
}
})()
for (const empfehlung of empfehlungen) {
addEmpfehlung(empfehlung);
}
const pdf = await generate({
template: VerbrauchsausweisWohnen2016Template,
plugins: { text, image, variable },
inputs: [convertAusweisData(ausweis)],
template,
plugins: { text, image, variable: { ...variable, pdf: variable.pdf.bind(convertedInputs), ui: variable.ui.bind(convertedInputs) } },
inputs: [convertedInputs],
options: {
author: "IB Cornelsen",
creationDate: new Date(),
creator: "IB Cornelsen",
language: "de",
title: "Verbrauchsausweis Wohnen 2016",
title: "Verbrauchsausweis Wohnen GEG 2024",
},
});

48
src/style/UMBE_global.css Normal file
View File

@@ -0,0 +1,48 @@
@import url('https://fonts.googleapis.com/css2?family=Abel&display=swap');
@import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,200;0,300;0,400;0,500;0,600;0,700;1,400&display=swap");
* {
font-weight: 400;
box-sizing: border-box;
font-family: "Abel", "Poppins", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
}
article hr {
@apply mb-4 mt-4;
}
article {
@apply px-6 md:px-8 py-6 w-full relative bg-white;
}
article h1 {
@apply text-4xl font-normal my-4;
}
article h2 {
@apply text-2xl font-normal my-4;
}
article h3 {
@apply text-xl font-normal my-2;
}
article a {
@apply text-blue-700 font-medium text-lg;
}
article select {
@apply rounded-lg px-2 py-1.5 outline-none;
}
.checkbox {
@apply h-5 w-4 checkbox-secondary;
}

View File

@@ -0,0 +1,28 @@
import { test, describe, expect } from "bun:test";
import { endEnergieVerbrauchVerbrauchsausweis_2016 } from "#lib/Berechnungen/VerbrauchsausweisWohnen/VerbrauchsausweisWohnen_2016";
import { importVerbrauchsausweisWohnenAltesSystem, verbrauchsausweisWohnenImportTranslate } from "#lib/altes-system/import";
describe('Energieverbrauch', async () => {
const alteAusweise = await importVerbrauchsausweisWohnenAltesSystem();
const ausweis = verbrauchsausweisWohnenImportTranslate(alteAusweise.data[0]);
const berechnungen = await endEnergieVerbrauchVerbrauchsausweis_2016(ausweis);
const berechnungenAlt = alteAusweise.data[0].calculations;
test("Klimafaktoren", async () => {
console.log("PLZ: " + ausweis.gebaeude_aufnahme_allgemein.plz)
expect(berechnungen?.klimafaktoren).toHaveLength(3)
expect(berechnungen?.klimafaktoren.map(x => x.klimafaktor)).toEqual(berechnungenAlt.klimafaktoren)
})
test("Endenergieverbrauch", async () => {
expect(berechnungen?.endEnergieVerbrauchGesamt).toBeCloseTo(berechnungenAlt.endEnergieVerbrauchGesamt, 0)
})
test("Primärenergieverbrauch", async () => {
expect(berechnungen?.primaerEnergieVerbrauchGesamt).toBeCloseTo(berechnungenAlt.primaerEnergieVerbrauchGesamt, 0)
})
})