UMBE updaten #4

Merged
UMBENOMENA merged 8 commits from main into UMBE 2024-03-14 15:06:40 +00:00
23 changed files with 683 additions and 124 deletions

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

@@ -5,9 +5,7 @@ import {
UploadedGebaeudeBild,
VerbrauchsausweisWohnenClient,
} from "#components/Ausweis/types";
import { dialogs } from "svelte-dialogs";
import { validateAccessTokenClient } from "./validateAccessToken";
import LoginDialog from "#components/LoginDialog.svelte";
import { exclude } from "#lib/exclude";
import { client } from "src/trpc";
import { bilderHochladen } from "./bilderHochladen";
@@ -20,48 +18,28 @@ export async function verbrauchsausweisWohnenSpeichern(
images: (UploadedGebaeudeBild & { base64?: string })[],
user: BenutzerClient
) {
// 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", "events"]
);
const ausweisGeneratedFieldsEntfernt = exclude(ausweis, [
"rechnungen",
"erstellungsdatum",
]);
await client.v1.verbrauchsausweisWohnen[2016].speichern.mutate({
...ausweisGeneratedFieldsEntfernt,
...ausweis,
gebaeude_aufnahme_allgemein: {
...gebaeudeAufnahmeGeneratedFieldsEntfernt,
...exclude(
gebaeude_aufnahme_allgemein,
["erstellungsdatum", "events", "ausstellungsdatum", "rechnungen"]
),
gebaeude_stammdaten: {
...gebaeudeBilderEntfernt,
...exclude(gebaeude, [
"gebaeude_bilder",
]),
},
},
});
console.log(ausweisGeneratedFieldsEntfernt);
images = await bilderHochladen(images, gebaeude);
return true;
return { uid: ausweis.uid, gebaeude_uid: gebaeude.uid, gebaeude_aufnahme_uid: gebaeude_aufnahme_allgemein.uid };
} catch (e) {
// TODO: Ticket mit Fehldermeldung abschicken.
}
@@ -78,13 +56,10 @@ export async function verbrauchsausweisWohnenSpeichern(
},
},
});
ausweis.uid = response.uid;
gebaeude.uid = response.gebaeude_uid;
gebaeude_aufnahme_allgemein.uid = response.gebaeude_aufnahme_uid;
images = await bilderHochladen(images, gebaeude);
return true;
return response;
} catch (e: any) {
await client.v1.tickets.erstellen.mutate({
titel: "Ausweis konnte nicht gespeichert werden",
@@ -107,5 +82,5 @@ export async function verbrauchsausweisWohnenSpeichern(
timeout: 6000,
type: "error",
});
return false;
return null;
}

View File

@@ -13,10 +13,8 @@
VerbrauchsausweisWohnenClient,
} from "./types";
export let ausweis:
| VerbrauchsausweisWohnenClient;
export let ausweis: VerbrauchsausweisWohnenClient;
export let gebaeude: GebaeudeClient;
export let images: UploadedGebaeudeBild[] = [];
</script>

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)}%)" />
</div>
<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,17 @@
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 = [
// 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 +29,20 @@
"Dezember",
];
const startDate = moment().subtract(4, "years").subtract(6, "months");
const endDate = moment().subtract(3, "years");
for (let m = moment(startDate); m.isBefore(endDate); m.add(1, "month")) {
availableDates.push({
year: m.year(),
month: m.month(),
});
}
export let gebaeude: GebaeudeClient;
export let gebaeude_aufnahme_allgemein: GebaeudeAufnahmeClient;
export let ausweis: VerbrauchsausweisWohnenClient | VerbrauchsausweisGewerbe;
export let ausweis: VerbrauchsausweisWohnenClient;
const fuelMap: Record<string, string[]> = {};
for (const fuel of fuelList) {
@@ -35,12 +50,10 @@
fuelMap[fuel[0]] = fuelMap[fuel[0]] || [];
fuelMap[fuel[0]].push(fuel[1]);
}
console.log(ausweis.startdatum);
let month = ausweis.startdatum?.getMonth() || 1;
let year = ausweis.startdatum?.getFullYear() || 2018;
let month = ausweis.startdatum?.getMonth() || null;
let year = ausweis.startdatum?.getFullYear() || null;
$: {
if (typeof month === "number" && typeof year === "number") {
@@ -173,9 +186,18 @@
required
>
<option>auswählen</option>
{#each availableMonths as m, i}
<option value={i}>{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
@@ -185,8 +207,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>

View File

@@ -4,7 +4,7 @@ import { inferProcedureInput, inferProcedureOutput } from "@trpc/server";
export type UploadedGebaeudeBild = inferProcedureOutput<
AppRouter["v1"]["verbrauchsausweisWohnen"]["get"]
>["gebaeude_aufnahme_allgemein"]["gebaeude_stammdaten"]["gebaeude_bilder"][0];
>["gebaeude_aufnahme_allgemein"]["gebaeude_stammdaten"]["gebaeude_bilder"][0] & { base64?: string };
/**

View File

@@ -9,6 +9,9 @@
export let ausweis: VerbrauchsausweisWohnenClient | VerbrauchsausweisGewerbe | BedarfsausweisWohnen;
export let gebaeude: GebaeudeClient;
export let kategorie: Enums.BilderKategorie
console.log(images);
</script>
<div class="flex flex-col gap-4">
@@ -18,7 +21,7 @@
{#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"
/>

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

@@ -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();

View File

@@ -8,6 +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 TicketPopup from "../components/Tickets/TicketButton.svelte"
export interface Props {
title: string;
@@ -104,6 +105,7 @@ const schema = JSON.stringify({
</main>
<Footer />
<NotificationWrapper client:load />
<TicketPopup client:load />
</body>
</html>

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 || 1) * 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;
@@ -285,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),
@@ -331,6 +337,9 @@ export async function endEnergieVerbrauchVerbrauchsausweis_2016(
primaerEnergieVerbrauchKuehlungsZuschlag
),
primaerfaktorww,
primaerfaktorww_1,
co2Emissionen_1: co2Emissionen_1,
co2Emissionen_2: co2Emissionen_2,

View File

@@ -0,0 +1,135 @@
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 = {
rechnungen: null,
gebaeude_aufnahme_allgemein: {
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;
}

View File

@@ -38,8 +38,13 @@
async function spaeterWeitermachen() {
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;
}
}
@@ -74,11 +79,14 @@
if (e && e.preventDefault) e.preventDefault();
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}`;
}
}
@@ -102,7 +110,7 @@
<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">
@@ -384,7 +392,7 @@
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"
@@ -423,7 +431,7 @@
'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>

View File

@@ -19,9 +19,17 @@
let container: HTMLDivElement;
let designer: Designer;
let page: number = 0;
onMount(() => {
designer = new Designer({ domContainer: container, template, plugins });
console.log(designer);
designer.onChangePage((p) => {
page = p
});
});
function loadBasePDF() {
@@ -44,7 +52,7 @@
function addNewField() {
template = designer.getTemplate();
template.schemas[0]["new-field"] = {
template.schemas[page]["new-field"] = {
type: "text",
position: { x: 0, y: 0 },
width: 10,

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.gebaeude_aufnahme_allgemein.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,6 +42,8 @@
import { PRICES } from "#lib/constants";
import { BenutzerClient, VerbrauchsausweisWohnenClient } from "#components/Ausweis/types";
import { validateAccessTokenClient } from "src/client/lib/validateAccessToken";
import { client } from "src/trpc";
let prices: number[] = []
@@ -56,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">

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,12 @@
---
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 { createCaller } from "#lib/caller";
import { VerbrauchsausweisWohnenClient } from "#components/Ausweis/types";
import { Template } from "@pdfme/common";
const base64 = Astro.url.searchParams.get("base64");
let ausweis: Partial<VerbrauchsausweisWohnenClient> | null = null;
@@ -27,16 +28,32 @@ if (base64) {
})
}
const template = VerbrauchsausweisWohnen2016Template as Template;
template.schemas.push({})
template.schemas.push({})
template.schemas[2]["energie_1"] = {
position: {
x: 15,
y: 140
},
height: 5,
width: 14,
type: "text",
fontSize: 8,
verticalAlign: "middle"
}
const pdf = await generate({
template: VerbrauchsausweisWohnen2016Template,
template,
plugins: { text, image, variable },
inputs: [convertAusweisData(ausweis)],
inputs: [{...convertAusweisData(ausweis), energie_1: "Hallo"}],
options: {
author: "IB Cornelsen",
creationDate: new Date(),
creator: "IB Cornelsen",
language: "de",
title: "Verbrauchsausweis Wohnen 2016",
title: "Verbrauchsausweis Wohnen GEG 2024",
},
});

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)
})
})