Merge pull request #367 from IBCornelsen/main

get main
This commit is contained in:
Jens Cornelsen
2025-02-21 21:26:18 +01:00
committed by GitHub
87 changed files with 5003 additions and 1674 deletions

View File

@@ -61,6 +61,7 @@
"@types/jsonwebtoken": "^9.0.7",
"@types/mime-types": "^2.1.4",
"@types/nodemailer": "^6.4.17",
"@types/papaparse": "^5.3.15",
"@types/siema": "^1.4.11",
"@types/uuid": "^9.0.8",
"@typescript-eslint/eslint-plugin": "^5.62.0",
@@ -72,6 +73,7 @@
"cypress-vite": "^1.6.0",
"eslint": "~8.15.0",
"eslint-config-prettier": "8.1.0",
"papaparse": "^5.5.2",
"postcss": "^8.5.1",
"postcss-import": "^16.1.0",
"postcss-nesting": "^13.0.1",
@@ -556,6 +558,8 @@
"@types/nodemailer": ["@types/nodemailer@6.4.17", "", { "dependencies": { "@types/node": "*" } }, "sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww=="],
"@types/papaparse": ["@types/papaparse@5.3.15", "", { "dependencies": { "@types/node": "*" } }, "sha512-JHe6vF6x/8Z85nCX4yFdDslN11d+1pr12E526X8WAfhadOeaOTx5AuIkvDKIBopfvlzpzkdMx4YyvSKCM9oqtw=="],
"@types/pug": ["@types/pug@2.0.10", "", {}, "sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA=="],
"@types/qs": ["@types/qs@6.9.18", "", {}, "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA=="],
@@ -1658,6 +1662,8 @@
"pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="],
"papaparse": ["papaparse@5.5.2", "", {}, "sha512-PZXg8UuAc4PcVwLosEEDYjPyfWnTEhOrUfdv+3Bx+NuAb+5NhDmXzg5fHWmdCh1mP5p7JAZfFr3IMQfcntNAdA=="],
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
"parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="],

View File

@@ -75,6 +75,7 @@
"@types/jsonwebtoken": "^9.0.7",
"@types/mime-types": "^2.1.4",
"@types/nodemailer": "^6.4.17",
"@types/papaparse": "^5.3.15",
"@types/siema": "^1.4.11",
"@types/uuid": "^9.0.8",
"@typescript-eslint/eslint-plugin": "^5.62.0",
@@ -86,6 +87,7 @@
"cypress-vite": "^1.6.0",
"eslint": "~8.15.0",
"eslint-config-prettier": "8.1.0",
"papaparse": "^5.5.2",
"postcss": "^8.5.1",
"postcss-import": "^16.1.0",
"postcss-nesting": "^13.0.1",

View File

@@ -4,21 +4,26 @@ export const createCaller = createCallerFactory({
"bild": await import("../src/pages/api/bild.ts"),
"klimafaktoren": await import("../src/pages/api/klimafaktoren.ts"),
"postleitzahlen": await import("../src/pages/api/postleitzahlen.ts"),
"unterlage": await import("../src/pages/api/unterlage.ts"),
"admin/ausstellen": await import("../src/pages/api/admin/ausstellen.ts"),
"admin/bestellbestaetigung": await import("../src/pages/api/admin/bestellbestaetigung.ts"),
"admin/erinnern": await import("../src/pages/api/admin/erinnern.ts"),
"admin/nicht-ausstellen": await import("../src/pages/api/admin/nicht-ausstellen.ts"),
"admin/post-ausstellen": await import("../src/pages/api/admin/post-ausstellen.ts"),
"admin/registriernummer": await import("../src/pages/api/admin/registriernummer.ts"),
"admin/stornieren": await import("../src/pages/api/admin/stornieren.ts"),
"aufnahme": await import("../src/pages/api/aufnahme/index.ts"),
"ausweise": await import("../src/pages/api/ausweise/index.ts"),
"bedarfsausweis-wohnen/[uid]": await import("../src/pages/api/bedarfsausweis-wohnen/[uid].ts"),
"bedarfsausweis-wohnen": await import("../src/pages/api/bedarfsausweis-wohnen/index.ts"),
"auth/access-token": await import("../src/pages/api/auth/access-token.ts"),
"auth/forgot-password": await import("../src/pages/api/auth/forgot-password.ts"),
"auth/refresh-token": await import("../src/pages/api/auth/refresh-token.ts"),
"bedarfsausweis-wohnen/[uid]": await import("../src/pages/api/bedarfsausweis-wohnen/[uid].ts"),
"bedarfsausweis-wohnen": await import("../src/pages/api/bedarfsausweis-wohnen/index.ts"),
"bilder/[uid]": await import("../src/pages/api/bilder/[uid].ts"),
"geg-nachweis-wohnen/[uid]": await import("../src/pages/api/geg-nachweis-wohnen/[uid].ts"),
"geg-nachweis-wohnen": await import("../src/pages/api/geg-nachweis-wohnen/index.ts"),
"geg-nachweis-gewerbe/[uid]": await import("../src/pages/api/geg-nachweis-gewerbe/[uid].ts"),
"geg-nachweis-gewerbe": await import("../src/pages/api/geg-nachweis-gewerbe/index.ts"),
"objekt": await import("../src/pages/api/objekt/index.ts"),
"rechnung/anfordern": await import("../src/pages/api/rechnung/anfordern.ts"),
"rechnung": await import("../src/pages/api/rechnung/index.ts"),
@@ -32,6 +37,6 @@ export const createCaller = createCallerFactory({
"webhooks/mollie": await import("../src/pages/api/webhooks/mollie.ts"),
"aufnahme/[uid]/bilder": await import("../src/pages/api/aufnahme/[uid]/bilder.ts"),
"aufnahme/[uid]": await import("../src/pages/api/aufnahme/[uid]/index.ts"),
"aufnahme/[uid]/unterlagen": await import("../src/pages/api/aufnahme/[uid]/unterlagen.ts"),
"objekt/[uid]": await import("../src/pages/api/objekt/[uid]/index.ts"),
"objekt/[uid]/unterlagen": await import("../src/pages/api/objekt/[uid]/unterlagen.ts"),
})

45
src/client/lib/helpers.ts Normal file
View File

@@ -0,0 +1,45 @@
import { writable, Writable } from "svelte/store";
import { ZodEffects, ZodNullable, ZodOptional, ZodType } from "zod";
export function localStorageSync<T>(initial: T, name: string, modifier: (stored: any) => T = (stored) => JSON.parse(stored), reverseModifier: (value: T) => string = (value) => JSON.stringify(value)): Writable<T> {
const stored = localStorage.getItem(name) as T
let value = initial;
if (stored) {
value = modifier(stored)
}
const writableStore = writable(value)
writableStore.subscribe((value) => {
const reversed = reverseModifier(value);
localStorage.setItem(name, reversed)
})
return writableStore
}
export function isZodInstanceOf<T extends ZodType<any>>(
schema: ZodType<any>,
targetType: new (...args: any) => T
): schema is T {
if (schema instanceof targetType) {
return true;
}
if (schema instanceof ZodOptional || schema instanceof ZodNullable) {
return isZodInstanceOf(schema._def.innerType, targetType);
}else if (schema instanceof ZodEffects) {
return getZodBaseType(schema._def.schema)
}
return false;
}
export function getZodBaseType(schema: ZodType<any>): ZodType<any> {
if (schema instanceof ZodOptional || schema instanceof ZodNullable) {
return getZodBaseType(schema._def.innerType);
} else if (schema instanceof ZodEffects) {
return getZodBaseType(schema._def.schema)
}
return schema;
}

View File

@@ -0,0 +1,138 @@
import { api } from "astro-typesafe-api/client"
import { exclude } from "#lib/exclude.js";
import Cookies from "js-cookie";
import { API_ACCESS_TOKEN_COOKIE_NAME } from "#lib/constants.js";
import { AufnahmeClient, BedarfsausweisWohnenClient, BildClient, ObjektClient, UnterlageClient, UploadedGebaeudeBild, VerbrauchsausweisGewerbeClient, VerbrauchsausweisWohnenClient, } from "#components/Ausweis/types.js";
import { Enums } from "@ibcornelsen/database/client";
export async function nachweisSpeichern(
nachweis: VerbrauchsausweisWohnenClient | VerbrauchsausweisGewerbeClient | BedarfsausweisWohnenClient,
objekt: ObjektClient,
aufnahme: AufnahmeClient,
bilder: BildClient[],
unterlagen: UnterlageClient[],
ausweisart: Enums.Ausweisart
) {
if (objekt.uid) {
await api.objekt._uid.PATCH.fetch({
...exclude(objekt, ["uid"])
}, {
params: {
uid: objekt.uid
},
headers: {
"Authorization": `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`
}
})
} else {
const { uid } = await api.objekt.PUT.fetch({
...exclude(objekt, ["uid"])
}, {
headers: {
"Authorization": `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`
}
})
objekt.uid = uid;
}
if (aufnahme.uid) {
await api.aufnahme._uid.PATCH.fetch({
...exclude(aufnahme, ["uid"]),
baujahr_gebaeude: aufnahme.baujahr_gebaeude || [],
baujahr_klima: aufnahme.baujahr_klima || [],
baujahr_heizung: aufnahme.baujahr_heizung || [],
}, {
params: {
uid: aufnahme.uid
},
headers: {
"Authorization": `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`
}
})
} else {
const { uid } = await api.aufnahme.PUT.fetch({
aufnahme,
uid_objekt: objekt.uid
}, {
headers: {
"Authorization": `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`
}
})
aufnahme.uid = uid
}
let patchRoute: any;
let putRoute: any;
if (ausweisart == Enums.Ausweisart.GEGNachweisWohnen) {
patchRoute = api["geg-nachweis-wohnen"]._uid.PATCH
putRoute = api["geg-nachweis-wohnen"].PUT
} else if (ausweisart === Enums.Ausweisart.GEGNachweisGewerbe) {
patchRoute = api["geg-nachweis-gewerbe"]._uid.PATCH
putRoute = api["geg-nachweis-gewerbe"].PUT
}
if (nachweis.uid) {
await patchRoute.fetch({
...exclude(nachweis, ["uid"])
}, {
params: {
uid: nachweis.uid
},
headers: {
"Authorization": `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`
}
})
} else {
const { uid } = await putRoute.fetch({
nachweis,
uid_aufnahme: aufnahme.uid
}, {
headers: {
"Authorization": `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`
}
})
nachweis.uid = uid;
}
await api.aufnahme._uid.bilder.PUT.fetch(bilder.map(bild => bild.uid), {
params: {
uid: aufnahme.uid
},
headers: {
"Authorization": `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`
}
})
return {
uid_nachweis: nachweis.uid,
uid_aufnahme: aufnahme.uid,
uid_objekt: objekt.uid
}
// await client.v1.tickets.erstellen.mutate({
// titel: "Ausweis konnte nicht gespeichert werden",
// beschreibung: e.stack,
// email: user.email ?? "",
// metadata: JSON.stringify({
// ausweis,
// }),
// });
// 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

@@ -48,7 +48,7 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
required
data-cy="ausstellgrund"
>
<option disabled selected value={false}>Bitte auswählen</option>
<option disabled selected value={null}>Bitte auswählen</option>
{#each Object.entries(Enums.Ausstellgrund) as [name, ausstellgrund]}
<option value={ausstellgrund}>{name}</option>
{/each}
@@ -72,7 +72,7 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
required
bind:value={aufnahme.gebaeudetyp}
>
<option disabled selected>Bitte auswählen</option>
<option disabled selected value={null}>Bitte auswählen</option>
{#if ausweisart==Enums.Ausweisart.VerbrauchsausweisWohnen || ausweisart === Enums.Ausweisart.GEGNachweisWohnen}
<option value="Einfamilienhaus">Einfamilienhaus</option>
@@ -224,7 +224,7 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
required
bind:value={aufnahme.saniert}
>
<option disabled selected>Bitte auswählen</option>
<option disabled selected value={null}>Bitte auswählen</option>
<option value={true}>saniert</option>
<option value={false}>unsaniert</option>
</select>

View File

@@ -1,16 +1,18 @@
<script lang="ts">
import { ausweisSpeichern } from "#client/lib/ausweisSpeichern.js";
import { validateAccessTokenClient } from "#client/lib/validateAccessToken.js";
import { AufnahmeClient, BedarfsausweisWohnenClient, BenutzerClient, ObjektClient, UploadedGebaeudeBild, VerbrauchsausweisGewerbeClient, VerbrauchsausweisWohnenClient } from "#components/Ausweis/types.js";
import { AufnahmeClient, BedarfsausweisWohnenClient, BenutzerClient, GEGNachweisWohnenClient, ObjektClient, UnterlageClient, UploadedGebaeudeBild, VerbrauchsausweisGewerbeClient, VerbrauchsausweisWohnenClient } from "#components/Ausweis/types.js";
import Overlay from "#components/Overlay.svelte";
import EmbeddedAuthFlowModule from "#modules/EmbeddedAuthFlowModule.svelte";
import { AusweisTyp, Enums } from "@ibcornelsen/database/client";
import { openWindowWithPost } from "#lib/helpers/window.js";
import { PRICES } from "#lib/constants.js";
import { nachweisSpeichern } from "#client/lib/nachweisSpeichern.js";
export let ausweis: VerbrauchsausweisWohnenClient | VerbrauchsausweisGewerbeClient | BedarfsausweisWohnenClient;
export let ausweis: VerbrauchsausweisWohnenClient | VerbrauchsausweisGewerbeClient | BedarfsausweisWohnenClient | GEGNachweisWohnenClient;
export let bilder: UploadedGebaeudeBild[];
export let unterlagen: UnterlageClient[];
export let user: BenutzerClient;
export let objekt: ObjektClient;
export let aufnahme: AufnahmeClient;
@@ -26,6 +28,7 @@
objekt,
aufnahme,
bilder,
unterlagen,
ausweisart,
ausweistyp
}, "")
@@ -42,7 +45,12 @@
loginOverlayHidden = true
const result = await ausweisSpeichern(ausweis, objekt, aufnahme, bilder, ausweisart);
let result: Awaited<ReturnType<typeof ausweisSpeichern>> | null = null;
if (ausweisart === Enums.Ausweisart.GEGNachweisWohnen || ausweisart === Enums.Ausweisart.GEGNachweisGewerbe || ausweisart === Enums.Ausweisart.GEGNachweisBedarfsausweis) {
result = await nachweisSpeichern(ausweis, objekt, aufnahme, bilder, unterlagen, ausweisart)
} else {
result = await ausweisSpeichern(ausweis, objekt, aufnahme, bilder, ausweisart)
}
if (result !== null) {
window.history.pushState(

View File

@@ -121,7 +121,7 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
bind:value={aufnahme.dachgeschoss}
required
>
<option disabled selected value={false}>Bitte auswählen</option>
<option disabled selected value={null}>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>

View File

@@ -29,7 +29,7 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
bind:value={aufnahme.gebaeudeteil}
required
>
<option disabled selected value={false}>Bitte auswählen</option>
<option disabled selected value={null}>Bitte auswählen</option>
<option value="Gesamtgebäude">Gesamtgebäude</option>
<option value="Wohnen">Wohnen</option>
</select>
@@ -79,7 +79,7 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
required
bind:value={aufnahme.lueftung}
>
<option disabled selected value={false}>Bitte auswählen</option>
<option disabled selected value={null}>Bitte auswählen</option>
<option value="Fensterlueftung">Fensterlüftung</option>
<option value="Schachtlueftung">Schachtlüftung</option>
<option value="LueftungsanlageOhneWaermerueckgewinnung"
@@ -112,7 +112,7 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
required
bind:value={aufnahme.kuehlung}
>
<option disabled selected value={false}>Bitte auswählen</option>
<option disabled selected value={null}>Bitte auswählen</option>
<option value="1">vorhanden</option>
<option value="0">nicht vorhanden</option>
</select>

View File

@@ -86,7 +86,6 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
<input
name="rechnung_ort"
readonly
type="text"
required
value={rechnung.ort}

View File

@@ -223,7 +223,7 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
bind:value={aufnahme.brennstoff_1}
required
>
<option disabled selected value={false}>Bitte auswählen</option>
<option disabled selected value={null}>Bitte auswählen</option>
{#each Object.keys(fuelMap) as fuel}
<option value={fuel}>{fuel}</option>
{/each}
@@ -256,7 +256,7 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
disabled={!aufnahme.brennstoff_1}
required
>
<option disabled selected value={false}>Bitte auswählen</option>
<option disabled selected >Bitte auswählen</option>
{#each fuelMap.hasOwnProperty(aufnahme.brennstoff_1) ? fuelMap[aufnahme.brennstoff_1] : [] as unit}
<option value={unit}>{unit}</option>
{/each}
@@ -405,7 +405,7 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
bind:value={aufnahme.brennstoff_2}
required
>
<option disabled selected value={false}
<option disabled selected
>Bitte auswählen</option
>
{#each Object.keys(fuelMap) as fuel}
@@ -440,7 +440,7 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
disabled={!aufnahme.brennstoff_2}
required
>
<option disabled selected value={false}
<option disabled selected
>Bitte auswählen</option
>
{#each fuelMap.hasOwnProperty(aufnahme.brennstoff_2) ? fuelMap[aufnahme.brennstoff_2] : [] as unit}

View File

@@ -236,7 +236,7 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
bind:value={aufnahme.brennstoff_1}
required
>
<option disabled selected value={false}>Bitte auswählen</option>
<option disabled selected >Bitte auswählen</option>
{#each Object.keys(fuelMap) as fuel}
<option value={fuel}>{fuel}</option>
{/each}
@@ -276,7 +276,7 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
disabled={!aufnahme.brennstoff_1}
required
>
<option disabled selected value={false}>Bitte auswählen</option>
<option disabled selected >Bitte auswählen</option>
{#each fuelMap.hasOwnProperty(aufnahme.brennstoff_1) ? fuelMap[aufnahme.brennstoff_1] : [] as unit}
<option value={unit}>{unit}</option>
{/each}
@@ -431,7 +431,7 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
required
data-cy="brennstoff_2"
>
<option disabled selected value={false}
<option disabled selected
>Bitte auswählen</option
>
{#each Object.keys(fuelMap) as fuel}
@@ -474,7 +474,7 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
disabled={!aufnahme.brennstoff_2}
required
>
<option disabled selected value={false}
<option disabled selected
>Bitte auswählen</option
>
{#each fuelMap.hasOwnProperty(aufnahme.brennstoff_2) ? fuelMap[aufnahme.brennstoff_2] : [] as unit}

View File

@@ -17,7 +17,7 @@ import { z, ZodSchema } from "zod";
export type OmitKeys<T, K extends keyof T> = Omit<T, K>;
export type UploadedGebaeudeBild = OmitKeys<Bild, "id" | "objekt_id"> & {
export type UploadedGebaeudeBild = OmitKeys<Bild, "id" | "aufnahme_id"> & {
data: string
}
@@ -148,6 +148,8 @@ export function getAusweisartFromUUID(uid: string): Enums.Ausweisart | null {
return Enums.Ausweisart.BedarfsausweisGewerbe
} else if (uid.startsWith("gnw")) {
return Enums.Ausweisart.GEGNachweisWohnen
} else if (uid.startsWith("gng")) {
return Enums.Ausweisart.GEGNachweisGewerbe
}
return null;
@@ -163,9 +165,10 @@ export type ObjektKomplettClient = ObjektClient & {
export type AufnahmeKomplettClient = AufnahmeClient & {
bilder: BildClient[],
unterlagen: UnterlageClient[],
bedarfsausweis_wohnen?: BedarfsausweisWohnenClient,
verbrauchsausweis_wohnen?: VerbrauchsausweisWohnenClient,
verbrauchsausweis_gewerbe?: VerbrauchsausweisGewerbeClient
bedarfsausweise_wohnen: BedarfsausweisWohnenClient[],
verbrauchsausweise_wohnen: VerbrauchsausweisWohnenClient[],
verbrauchsausweise_gewerbe: VerbrauchsausweisGewerbeClient[],
geg_nachweise_wohnen: GEGNachweisWohnenClient[]
}
export type GEGNachweisWohnenClient = Omit<GEGNachweisWohnen, "id" | "aufnahme_id" | "benutzer_id"> & {

View File

@@ -9,7 +9,7 @@
VerbrauchsausweisWohnenClient,
} from "./Ausweis/types.js";
import AusweisPruefenTooltip from "./AusweisPruefenTooltip.svelte";
import { addNotification } from "./NotificationProvider/shared.js";
import { addNotification } from "#components/Notifications/shared.js";
import { CheckCircled, CrossCircled, Image } from "radix-svelte-icons";
import ChevronDown from "radix-svelte-icons/src/lib/icons/ChevronDown.svelte";
import { Event } from "@ibcornelsen/database/client";
@@ -31,16 +31,16 @@
async function ausweisAusstellen(uid: string) {
try {
await api.admin.ausstellen.GET.fetch({
uid
}, {
headers: {
"Authorization": `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`
}
})
uid_ausweis: uid
}, {
headers: {
"Authorization": `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`
}
})
} catch(e) {
addNotification({
title: "Das hat nicht geklappt.",
description: e.cause.statusText,
message: "Das hat nicht geklappt.",
subtext: e as string,
timeout: 3000,
type: "error",
})
@@ -49,7 +49,7 @@
const ausweisArt = getAusweisartFromUUID(ausweis.uid) // TODO: Das ist ein Platzhalter, hier muss die Ausweisart aus dem Ausweisobjekt kommen
const ausweisArt = getAusweisartFromUUID(ausweis.uid)
let verbrauchWWGesamt_1 = "";
let verbrauchWWGesamt_2 = "";
@@ -124,12 +124,12 @@
let Abgeschlossen: any;
if (aufnahme.erledigt) {
if (ausweis.ausgestellt) {
Ausweisbild = "/images/dashboard/ausweishaken.jpg";
DatenBlattBild = "/images/dashboard/datenblatthaken.jpg";
StatusIcon = "/images/dashboard/erledigt.svg";
Abgeschlossen = 0;
} else if (aufnahme.bestellt) {
} else if (ausweis.bestellt) {
Ausweisbild = "/images/dashboard/ausweis.jpg";
DatenBlattBild = "/images/dashboard/datenblatt.jpg";
StatusIcon = "/images/dashboard/bestellt.svg";
@@ -147,7 +147,7 @@
symbolPruefung = "/images/dashboard/kreiskreuz.png";
}
if (aufnahme.zurueckgestellt) {
if (ausweis.zurueckgestellt) {
zurueckGestellt =
"<img src='/images/dashboard/zurueckGestellt.svg' alt='Status' width=\"25\" height=\"25\"></img>";
} else {
@@ -456,16 +456,33 @@
// }
async function stornieren(ausweis: VerbrauchsausweisWohnenClient) {
addNotification({
title: "Ausweis wurde storniert",
type: "success",
dismissable: true,
timeout: 3000,
})
try {
const response = await api.admin.stornieren.PUT.fetch({
uid_ausweis: ausweis.uid
}, {
headers: {
"Authorization": `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`
}
})
ausweis.aufnahme.storniert = true;
addNotification({
message: "Ausweis wurde storniert",
type: "success",
dismissable: true,
timeout: 3000,
})
ausweis = ausweis;
ausweis.storniert = true;
ausweis = ausweis;
} catch(e) {
addNotification({
message: "Ausweis konnte nicht storniert werden.",
subtext: e as string,
type: "error",
dismissable: true,
timeout: 3000,
})
}
}
let bilderModal: HTMLDialogElement;
@@ -473,13 +490,23 @@
async function registriernummerAnfordern(uid: string) {
const result = await api.admin.registriernummer.GET.fetch({
uid
}, {
headers: {
"Authorization": `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`
}
})
try {
const result = await api.admin.registriernummer.GET.fetch({
uid
}, {
headers: {
"Authorization": `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`
}
})
} catch(e) {
addNotification({
message: "Registriernummer anfordern fehlgeschlagen.",
subtext: e as string,
type: "error",
dismissable: true,
timeout: 3000,
})
}
}
</script>
@@ -909,4 +936,4 @@
</div>
</div>
<NotificationWrapper></NotificationWrapper>
<NotificationWrapper />

View File

@@ -0,0 +1,68 @@
<script lang="ts">
import { getZodBaseType } from "#client/lib/helpers.js";
import { filterAusweise } from "#lib/filters.js";
import { ZodTypeAny } from "astro:schema";
import { Cross1 } from "radix-svelte-icons";
import z, { ZodBoolean, ZodNativeEnum, ZodNumber } from "zod"
export let filters: { name: keyof z.infer<typeof filterAusweise>, type: ZodTypeAny, value: any }[] = []
</script>
{#each filters as filter, i}
{@const type = getZodBaseType(filter.type)}
<div class="flex flex-row bg-white gap-4 px-2 py-2 rounded-lg">
{#if i === 0}
<span class="badge">where</span>
{:else}
<span class="badge">and</span>
{/if}
<select on:change={function(e) {
delete filters[filter.name]
filter.name = e.target.value;
filter.type = filterAusweise._def.shape()[filter.name]
filters = filters.filter(Boolean);
}}>
<option value={filter.name} selected>{filter.name}</option>
{#each Object.keys(filterAusweise._def.shape()) as n}
{#if !filters.find(filter => filter.name === n)}
<option value={n}>{n}</option>
{/if}
{/each}
</select>
<span class="badge">equals</span>
{#if type instanceof ZodNumber}
<input type="number" bind:value={filter.value}>
{:else if type instanceof ZodBoolean}
<select bind:value={filter.value}>
<option value={true}>true</option>
<option value={false}>false</option>
</select>
{:else if type instanceof ZodNativeEnum}
<select bind:value={filter.value}>
{#each Object.entries(type._def.values) as [key, value]}
<option {value}>{key}</option>
{/each}
</select>
{:else}
<input type="text" bind:value={filter.value}>
{/if}
<Cross1 size={24} class="cursor-pointer"></Cross1>
</div>
{/each}
<button on:click={() => {
const entry = Object.entries(filterAusweise._def.shape())[0]
filters.push({
name: entry[0],
type: entry[1],
value: null
})
filters = filters
}}>Filter Hinzufügen</button>
<style>
.badge {
@apply rounded-lg px-2 py-1 bg-gray-500 text-white;
}
</style>

View File

@@ -13,11 +13,11 @@
import { api } from "astro-typesafe-api/client";
import Cookies from "js-cookie";
import { API_ACCESS_TOKEN_COOKIE_NAME } from "#lib/constants.js";
import { Enums } from "@ibcornelsen/database/client";
import { Enums, Objekt } from "@ibcornelsen/database/client";
export let ausweis: VerbrauchsausweisWohnenClient;
export let aufnahme: AufnahmeKomplettClient;
export let objekt: ObjektKomplettClient;
export let objekt: Objekt;
export let progress: number;
const ausweisart = getAusweisartFromUUID(ausweis.uid);
@@ -188,14 +188,14 @@
</div>
</div>
{/await}
<div class="card-actions justify-end mt-8">
<div class="flex flex-row justify-end gap-4 mt-4">
<a
class="btn btn-primary"
class="button text-sm"
href="/energieausweis-erstellen/verbrauchsausweis-wohngebaeude?uid={ausweis.uid}"
>Bearbeiten</a
>
<a
class="btn btn-ghost"
class="p-2 rounded-lg hover:bg-gray-200"
title="PDF Herunterladen"
target="_blank"
href="/pdf/ansichtsausweis?uid={ausweis.uid}"

View File

@@ -1,3 +1,5 @@
<script lang="ts"></script>
<div class="card lg:card-side bg-base-200 card-bordered border-base-300">
<figure class="lg:w-1/2 min-h-72 skeleton"></figure>
<div class="card-body lg:w-1/2">

View File

@@ -5,23 +5,40 @@
import { File, OpenInNewWindow } from "radix-svelte-icons";
export let objekt: ObjektKomplettClient;
const objektBild = objekt.aufnahmen[0].bilder.find(bild => bild.kategorie === Enums.BilderKategorie.Gebaeude);
</script>
<div class="border rounded-lg border-base-300 bg-white">
{#if objektBild}
<img src="/bilder/{objektBild.uid}.webp" class="w-full max-h-72 object-cover rounded-t-lg" alt="Gebäude">
<div class="border rounded-lg border-base-300 bg-white break-inside-avoid-column mb-4">
{#if objekt.aufnahmen.length > 0}
{@const bild = objekt.aufnahmen[0].bilder.find(bild => bild.kategorie === Enums.BilderKategorie.Gebaeude)}
{#if bild}
<img src="/bilder/{bild.uid}.webp" class="w-full max-h-72 object-cover rounded-t-lg" alt="Gebäude">
{/if}
{/if}
<div class="p-4">
<div class="flex flex-row justify-between">
<h3 class="text-lg font-medium">{objekt.adresse}</h3>
<div class="flex flex-row justify-between items-center">
<h3 class="text-lg font-medium">{objekt.adresse}, {objekt.plz} {objekt.ort}</h3>
<span class="text-sm opacity-70 font-medium">{moment(objekt.erstellungsdatum).format("DD.MM.YYYY")}</span>
</div>
<div class="flex flex-row justify-end">
<a href="/dashboard/objekt/{objekt.uid}" class="rounded-lg p-2.5 hover:bg-gray-200" target="_blank"><OpenInNewWindow size={20}></OpenInNewWindow></a>
<div class="flex flex-col gap-2 mt-4">
{#each objekt.aufnahmen as aufnahme}
<div class="border rounded-lg px-4 py-2">
<div class="flex flex-row justify-between items-center">
<span>Sanierungsstand vom {moment(aufnahme.erstellungsdatum).format("DD.MM.YYYY")}</span>
<a href="/dashboard/aufnahme/{aufnahme.uid}" class="rounded-lg p-2 hover:bg-gray-100 transition-all"><OpenInNewWindow size={20}></OpenInNewWindow></a>
</div>
<div class="flex flex-row gap-2">
{#if aufnahme.verbrauchsausweise_wohnen}
<a href="/dashboard/aufnahme/{aufnahme.uid}" class="rounded-lg p-2 hover:bg-gray-100 transition-all"><File size={20}></File></a>
{/if}
{#if aufnahme.verbrauchsausweis_gewerbe}
<a href="/dashboard/aufnahme/{aufnahme.uid}" class="rounded-lg p-2 hover:bg-gray-100 transition-all"><File size={20}></File></a>
{/if}
</div>
</div>
{/each}
</div>
</div>
</div>

View File

@@ -46,16 +46,12 @@
/></a
>
<div class="menu flex flex-col gap-2 mt-0 md:mt-12 px-0">
<div class="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
</a>
<a use:ripple={rippleOptions} class="button-tab" href="/dashboard/ausweise">
<Reader width={22} height={22} />
Ausweise
Objekte
</a>
<button use:ripple={rippleOptions} class="button-tab">
<!-- <button use:ripple={rippleOptions} class="button-tab">
<EnvelopeClosed width={22} height={22} />
Kontakt
</button>
@@ -75,7 +71,7 @@
</button>
</li>
</ul>
</details></li>
</details></li> -->
{#if benutzer.rolle === "ADMIN"}
<li><details class="[&_.caret]:open:rotate-180">
<summary class="button-tab w-full outline-0 hover:outline-0 cursor-pointer">

View File

@@ -4,13 +4,14 @@
import HelpLabel from "#components/labels/HelpLabel.svelte";
export let kategorie: string = "";
export let files: (UnterlageClient & { data: string | ArrayBuffer })[] = [];
export let files: UnterlageClient[] = [];
export let max: number = Infinity;
export let min: number = 1;
export let name: string = "";
export let ausweis: VerbrauchsausweisWohnenClient | VerbrauchsausweisGewerbeClient | BedarfsausweisWohnenClient | GEGNachweisWohnenClient;
export let objekt: ObjektClient;
import mime from "mime-types";
import { api } from "astro-typesafe-api/client";
function getAllFiles(this: HTMLInputElement) {
@@ -30,7 +31,7 @@
const reader = new FileReader();
reader.onload = () => {
reader.onload = async () => {
if (reader.readyState != reader.DONE) {
return;
}
@@ -41,7 +42,14 @@
const mimeType = mime.types[file.name.split(".").pop() as string]
files.push({ data: reader.result, kategorie, name: file.name, mime: mimeType });
const { uid } = await api.unterlage.PUT.fetch({
data: reader.result as string,
kategorie,
mime: mimeType,
name: file.name
})
files.push({ uid, kategorie, name: file.name, mime: mimeType });
files = files;
@@ -50,7 +58,7 @@
}
}
reader.readAsArrayBuffer(file);
reader.readAsDataURL(file);
}
}

View File

@@ -0,0 +1,287 @@
<script lang="ts">
import HelpLabel from "#components/labels/HelpLabel.svelte";
import Inputlabel from "#components/labels/InputLabel.svelte";
//import Label from "#components/Label.svelte";
import { auditHeizungGebaeudeBaujahr } from "../Verbrauchsausweis/audits/HeizungGebaeudeBaujahr.js";
import { addNotification, deleteNotification } from "#components/Notifications/shared.js";
import TagInput from "../TagInput.svelte";
import { Enums } from "@ibcornelsen/database/client";
import {
AufnahmeClient,
ObjektClient,
GEGNachweisWohnenClient,
} from "../Ausweis/types.js";
export let objekt: ObjektClient;
export let ausweis:
GEGNachweisWohnenClient;
export let aufnahme: AufnahmeClient;
export let ausweisart: Enums.Ausweisart;
</script>
<div id="ausweisart" class="bereich-box grid
grid-cols-1 gap-x-4 gap-y-8
sm:grid-cols-2 sm:gap-x-6 sm:gap-y-8
xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
"
>
<!-- Anlass für Energieausweis -->
<div class="input-standard order-1 md:order-1 xl:order-1">
<Inputlabel title="Anlass *"></Inputlabel>
<select
id="ausstellgrund"
class="rounded-e-none"
name="ausstellgrund"
placeholder="Anlass"
bind:value={ausweis.ausstellgrund}
required
data-cy="ausstellgrund"
>
<option disabled selected value={null}>Bitte auswählen</option>
<option value={Enums.Ausstellgrund.Neubau}>Neubau</option>
<option value={Enums.Ausstellgrund.Modernisierung}>Modernisierung</option>
</select>
<div class="help-label">
<HelpLabel>Bitte wählen Sie aus wofür Sie den Energieausweis benötigen.
<strong>Vermietung, Verkauf oder sonstiges (z.B. zur Vorlage bei der Bank) ist als Anlass für den Verbrauchsausweis zulässig.</strong>
Neubau oder Modernisierung ist Sie hier nicht zulässig.</HelpLabel>
</div>
</div>
<!-- Gebäudetyp -->
<div class="input-standard order-2 md:order-3 xl:order-2">
<Inputlabel title="Gebäudetyp *"></Inputlabel>
<select
name="gebaeudetyp"
data-test="gebaeudetyp"
required
bind:value={aufnahme.gebaeudetyp}
>
<option disabled selected value={null}>Bitte auswählen</option>
{#if ausweisart === Enums.Ausweisart.GEGNachweisWohnen}
<option value="Einfamilienhaus">Einfamilienhaus</option>
<option value="Freistehendes Einfamilienhaus">Freistehendes Einfamilienhaus</option>
<option value="Freistehendes Zweifamilienhaus">Freistehendes Zweifamilienhaus</option>
<option value="Doppelhaushälfte">Doppelhaushälfte</option>
<option value="Reihenendhaus">Reihenendhaus</option>
<option value="Reihenmittelhaus">Reihenmittelhaus</option>
<option value="Mehrfamilienhaus">Mehrfamilienhaus</option>
<option value="Wohn- und Geschäftshaus">Wohn- und Geschäftshaus</option>
<option value="Atrium-Bungalow">Atrium-Bungalow</option>
<option value="Winkelbungalow">Winkelbungalow</option>
{:else if ausweisart==Enums.Ausweisart.VerbrauchsausweisGewerbe}
<option value="Verwaltungsgebäude (allgemein)">Verwaltungsgebäude (allgemein)</option>
<option value="Parlaments- und Gerichtsgebäude">Parlaments- und Gerichtsgebäude</option>
<option value="Ministerien u. Ämter u. Behörden">Ministerien u. Ämter u. Behörden</option>
<option value="Polizeidienstgebäude">Polizeidienstgebäude</option>
<option value="Gebäude für öffentliche Bereitschaftsdienste">Gebäude für öffentliche Bereitschaftsdienste</option>
<option value="Feuerwehrdienstgebäude">Feuerwehrdienstgebäude</option>
<option value="Bürogebäude">Bürogebäude</option>
<option value="Bürogebäude - überwiegend Großraumbüros">Bürogebäude - überwiegend Großraumbüros</option>
<option value="Bankgebäude">Bankgebäude</option>
<option value="Hochschule und Forschung (allgemein)">Hochschule und Forschung (allgemein)</option>
<option value="Gebäude für Lehre">Gebäude für Lehre</option>
<option value="Institute für Lehre und Forschung">Institute für Lehre und Forschung</option>
<option value="Gebäude für Forschung ohne Lehre">Gebäude für Forschung ohne Lehre</option>
<option value="Laborgebäude">Laborgebäude</option>
<option value="Gesundheitswesen (allgemein)">Gesundheitswesen (allgemein)</option>
<option value="Krankenhäuser (ohne Forschung und Lehre)">Krankenhäuser (ohne Forschung und Lehre)</option>
<option value="Krankenhäuser (ohne Forschung und Lehre) &amp; teilstationäre Versorgung">Krankenhäuser (ohne Forschung und Lehre) &amp; teilstationäre Versorgung</option>
<option value="Medizinische Einrichtungen für nicht stationäre Versorgung">Medizinische Einrichtungen für nicht stationäre Versorgung</option>
<option value="Gebäude für Reha">Gebäude für Reha</option>
<option value="Bildungseinrichtungen (allgemein)">Bildungseinrichtungen (allgemein)</option>
<option value="Schulen">Schulen</option>
<option value="Kinderbetreuungseinrichtungen">Kinderbetreuungseinrichtungen</option>
<option value="Kultureinrichtungen (allgemein)">Kultureinrichtungen (allgemein)</option>
<option value="Bibliotheken/Archive">Bibliotheken/Archive</option>
<option value="Ausstellungsgebäude">Ausstellungsgebäude</option>
<option value="Veranstaltungsgebäude">Veranstaltungsgebäude</option>
<option value="Gemeinschafts-/Gemeindehäuser">Gemeinschafts-/Gemeindehäuser</option>
<option value="Opern/Theater">Opern/Theater</option>
<option value="Sporteinrichtungen (allgemein)">Sporteinrichtungen (allgemein)</option>
<option value="Sporthallen">Sporthallen</option>
<option value="Fitnessstudios">Fitnessstudios</option>
<option value="Schwimmhallen">Schwimmhallen</option>
<option value="Gebäude für Sportaußenanlagen">Gebäude für Sportaußenanlagen</option>
<option value="Verpflegungseinrichtungen (allgemein)">Verpflegungseinrichtungen (allgemein)</option>
<option value="Beherbergungsstätten (allgemein)">Beherbergungsstätten (allgemein)</option>
<option value="Hotels/Pensionen">Hotels/Pensionen</option>
<option value="Jugendherbergen u. Ferienhäuser">Jugendherbergen u. Ferienhäuser</option>
<option value="Gaststätten">Gaststätten</option>
<option value="Mensen u. Kantinen">Mensen u. Kantinen</option>
<option value="Gewerbliche und industrielle Gebäude (allgemein)">Gewerbliche und industrielle Gebäude (allgemein)</option>
<option value="Gewerbliche und industrielle Gebäude - schwere Arbeit">Gewerbliche und industrielle Gebäude - schwere Arbeit</option>
<option value="Gewerbliche und industrielle Gebäude - Mischung aus leichter u. schwerer Arbeit">Gewerbliche und industrielle Gebäude - Mischung aus leichter u. schwerer Arbeit</option>
<option value="Gewerbliche und industrielle Gebäude - leichte Arbeit">Gewerbliche und industrielle Gebäude - leichte Arbeit</option>
<option value="Gebäude für Lagerung">Gebäude für Lagerung</option>
<option value="Verkaufsstätten (allgemein)">Verkaufsstätten (allgemein)</option>
<option value="Kaufhäuser">Kaufhäuser</option>
<option value="Kaufhauszentren/Einkaufszentren">Kaufhauszentren/Einkaufszentren</option>
<option value="Märkte">Märkte</option>
<option value="Märkte mit sehr hohem Anteil von Kühlung für Lebensmittel">Märkte mit sehr hohem Anteil von Kühlung für Lebensmittel</option>
<option value="Läden">Läden</option>
<option value="Läden mit sehr hohem Anteil von Kühlung für Lebensmittel">Läden mit sehr hohem Anteil von Kühlung für Lebensmittel</option>
<option value="Fernmeldetechnik">Fernmeldetechnik</option>
{/if}
</select>
<div class="help-label">
<HelpLabel><strong>Der Energieausweis bezieht sich immer auf das gesamte Gebäude</strong> oder den gesamten Wohnteil eines Mischgebäudes.
Für einzelne Wohnungen kann kein Energieausweis ausgestellt werden.</HelpLabel>
</div>
</div>
{#if ausweisart=="VerbrauchsausweisWohnen"}
<!-- Anzahl der Wohnungen -->
<div class="input-standard order-3 md:order-5 xl:order-3">
<Inputlabel title="Anzahl Wohnungen *"></Inputlabel>
<input
id="einheiten"
class="rounded-e-none"
name="einheiten"
type="number"
required
autocomplete="off"
data-msg="Pflichtfeld"
maxlength="3"
bind:value={aufnahme.einheiten}
/>
<div class="help-label">
<HelpLabel>
Bitte geben Sie hier die Anzahl der Wohnungen ein, die sich im
Gebäude befinden.
</HelpLabel>
</div>
</div>
{:else if ausweisart=="VerbrauchsausweisGewerbe"}
<!-- Baujahr Klimaanlage -->
<div class="input-standard order-3 md:order-5 xl:order-3">
<Inputlabel title="Baujahr Klimaanlage *"></Inputlabel>
<TagInput
name="baujahr_klimaanlage"
type="number"
onlyUnique={true}
minlength={4}
maxlength={4}
onFocusIn={() => {
addNotification({
message: "Info",
subtext:
"Wussten sie, dass sie weitere Jahre angeben können? Z.B. das Baujahr der Erneuerung wesentlicher Komponenten ihrer Heizung. Drücken sie dafür einfach <kbd>Enter</kbd> oder <kbd>Space</kbd> nach jedem Jahr.",
dismissable: true,
uid: "KLIMAANLAGE_BAUJAHR",
timeout: 0,
type: "info",
});
}}
onFocusOut={() => {
deleteNotification("KLIMAANLAGE_BAUJAHR");
}}
className={auditHeizungGebaeudeBaujahr(aufnahme)
? "linked"
: ""}
bind:tags={aufnahme.baujahr_klima}
/>
<div class="help-label">
<HelpLabel>
Bitte geben Sie hier das Baujahr der Heizungsanlage ein. Sollten
unterschiedliche Baujahre vorliegen, können Sie mehrere Jahre eingeben.
</HelpLabel>
</div>
</div>
{/if}
<!-- Baujahr -->
<div class="input-standard order-5 md:order-4 xl:order-5">
<Inputlabel title="Baujahr *"></Inputlabel>
<TagInput
name="baujahr_gebaeude"
type="number"
minlength={4}
maxlength={4}
onlyUnique={true}
onFocusIn={() => {
addNotification({
message: "Info",
subtext:
"Wussten sie, dass sie mehrere Jahre angeben können in denen z.B. Renovierungen an ihrem Gebäude durchgeführt wurden. Drücken sie dafür einfach <kbd>Enter</kbd> oder <kbd>Space</kbd> nach jedem Jahr.",
dismissable: true,
uid: "GEBAEUDE_BAUJAHR",
timeout: 0,
type: "info",
});
}}
onFocusOut={() => {
deleteNotification("GEBAEUDE_BAUJAHR");
}}
className={auditHeizungGebaeudeBaujahr(aufnahme)
? "linked"
: ""}
bind:tags={aufnahme.baujahr_gebaeude}
/>
<div class="help-label">
<HelpLabel>
Bitte geben Sie hier das Baujahr des Gebäudes ein. Sollte eine grundlegende Sanierung stattgefunden haben, dann geben Sie das Sanierungsjahr ebenfalls an.
</HelpLabel>
</div>
</div>
<!-- Baujahr Heizung -->
<div class="input-standard order-6 md:order-6 xl:order-6">
<Inputlabel title="Baujahr Heizung *"></Inputlabel>
<TagInput
name="baujahr_heizung"
type="number"
onlyUnique={true}
minlength={4}
maxlength={4}
onFocusIn={() => {
addNotification({
message: "Info",
subtext:
"Wussten sie, dass sie weitere Jahre angeben können? Z.B. das Baujahr der Erneuerung wesentlicher Komponenten ihrer Heizung. Drücken sie dafür einfach <kbd>Enter</kbd> oder <kbd>Space</kbd> nach jedem Jahr.",
dismissable: true,
uid: "HEIZUNG_BAUJAHR",
timeout: 0,
type: "info",
});
}}
onFocusOut={() => {
deleteNotification("HEIZUNG_BAUJAHR");
}}
className={auditHeizungGebaeudeBaujahr(aufnahme)
? "linked"
: ""}
bind:tags={aufnahme.baujahr_heizung}
/>
<div class="help-label">
<HelpLabel>
Bitte geben Sie hier das Baujahr der Heizungsanlage ein. Sollten
unterschiedliche Baujahre vorliegen, können Sie mehrere Jahre eingeben.
</HelpLabel>
</div>
</div>
</div>

View File

@@ -0,0 +1,33 @@
<script lang="ts">
import { ChevronLeft, ChevronRight } from "radix-svelte-icons";
export let pages: number;
export let current: number
export let next: string;
export let prev: string;
</script>
<div class='flex items-center justify-center gap-2 my-4'>
<a href={prev}
class:disabled={current === 1 ? true : false}
aria-label="left arrow icon"
aria-describedby="prev"
class="flex p-2 hover:bg-gray-200 rounded-lg">
<ChevronLeft></ChevronLeft>
</a>
<p>{current} of {pages}</p>
<a href={next}
class:disabled={current === pages ? true : false}
aria-label="right arrow icon"
aria-describedby="next"
class="flex p-2 hover:bg-gray-200 rounded-lg">
<ChevronRight></ChevronRight>
</a>
</div>
<style>
.disabled {
@apply pointer-events-none cursor-default text-gray-500;
}
</style>

View File

@@ -67,7 +67,7 @@
<div data-test="plz-container" class="absolute top-[calc(100%+4px)] flex flex-col left-0 bg-white py-1 shadow-md rounded-lg z-10" class:hidden={hideZipDropdown}>
{#each zipCodes as zipCode}
<button class="hover:bg-gray-100 cursor-pointer px-2 py-1 text-nowrap" tabindex="-1" on:click={() => {
<button class="hover:bg-gray-100 cursor-pointer px-2 py-1 text-nowrap" type="button" tabindex="-1" on:click={() => {
zip = zipCode.plz;
city = zipCode.stadt;
hideZipDropdown = true;

View File

@@ -43,7 +43,7 @@
<div>
<h4>Kategorie *</h4>
<select class="select select-bordered" bind:value={category}>
<option value="" disabled selected>Bitte Auswählen</option>
<option value={null} 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>

View File

@@ -2,6 +2,7 @@
import { PRICES } from "#lib/constants";
export let bullets;
export let title;
import { Enums } from "@ibcornelsen/database/client";
bullets = [
["Prüfung durch Dipl.&nbsp;Ing.<br>Registrierung beim DiBt<br>rechtssicher nach&nbsp;GEG",true, true, true],
@@ -43,9 +44,9 @@
<div class="zeile grid grid-cols-subgrid col-span-4 py-4 border-b-[0px] justify-items-center items-center sm:text-[1.25rem]">
<div class="price justify-self-start pl-2">Preis inkl. MwSt.</div>
<div class="price"><b>{PRICES.BedarfsausweisGewerbe[0]}&nbsp;</b></div>
<div class="price"><b>{PRICES.BedarfsausweisGewerbe[1]}&nbsp;</b></div>
<div class="price"><b>{PRICES.BedarfsausweisGewerbe[2]}&nbsp;</b></div>
<div class="price"><b>{PRICES.BedarfsausweisGewerbe[Enums.AusweisTyp.Standard]}&nbsp;</b></div>
<div class="price"><b>{PRICES.BedarfsausweisGewerbe[Enums.AusweisTyp.Beratung]}&nbsp;</b></div>
<div class="price"><b>{PRICES.BedarfsausweisGewerbe[Enums.AusweisTyp.Offline]}&nbsp;</b></div>
</div>
<div class="zeile grid grid-cols-subgrid col-span-4 py-4 border-b-[0px] justify-items-center items-center">

View File

@@ -2,6 +2,7 @@
import { PRICES } from "#lib/constants";
export let bullets;
export let title;
import { Enums } from "@ibcornelsen/database/client";
bullets = [
["Prüfung durch Dipl.&nbsp;Ing.<br>Registrierung beim DiBt<br>rechtssicher nach&nbsp;GEG",true, true, true],
@@ -43,9 +44,9 @@
<div class="zeile grid grid-cols-subgrid col-span-4 py-4 border-b-[0px] justify-items-center items-center sm:text-[1.25rem]">
<div class="price justify-self-start pl-2">Preis inkl. MwSt.</div>
<div class="price"><b>{PRICES.BedarfsausweisWohnen[0]}&nbsp;</b></div>
<div class="price"><b>{PRICES.BedarfsausweisWohnen[1]}&nbsp;</b></div>
<div class="price"><b>{PRICES.BedarfsausweisWohnen[2]}&nbsp;</b></div>
<div class="price"><b>{PRICES.BedarfsausweisWohnen[Enums.AusweisTyp.Standard]}&nbsp;</b></div>
<div class="price"><b>{PRICES.BedarfsausweisWohnen[Enums.AusweisTyp.Beratung]}&nbsp;</b></div>
<div class="price"><b>{PRICES.BedarfsausweisWohnen[Enums.AusweisTyp.Offline]}&nbsp;</b></div>
</div>
<div class="zeile grid grid-cols-subgrid col-span-4 py-4 border-b-[0px] justify-items-center items-center">

View File

@@ -1,5 +1,6 @@
<script>
import { PRICES } from "#lib/constants";
import { Enums } from "@ibcornelsen/database/client";
export let bullets;
export let title;
@@ -43,9 +44,9 @@
<div class="zeile grid grid-cols-subgrid col-span-4 py-4 border-b-[0px] justify-items-center items-center sm:text-[1.25rem]">
<div class="price justify-self-start pl-2">Preis inkl. MwSt.</div>
<div class="price"><b>{PRICES.VerbrauchsausweisGewerbe[0]}&nbsp;</b></div>
<div class="price"><b>{PRICES.VerbrauchsausweisGewerbe[1]}&nbsp;</b></div>
<div class="price"><b>{PRICES.VerbrauchsausweisGewerbe[2]}&nbsp;</b></div>
<div class="price"><b>{PRICES.VerbrauchsausweisGewerbe[Enums.AusweisTyp.Standard]}&nbsp;</b></div>
<div class="price"><b>{PRICES.VerbrauchsausweisGewerbe[Enums.AusweisTyp.Beratung]}&nbsp;</b></div>
<div class="price"><b>{PRICES.VerbrauchsausweisGewerbe[Enums.AusweisTyp.Offline]}&nbsp;</b></div>
</div>
<div class="zeile grid grid-cols-subgrid col-span-4 py-4 border-b-[0px] justify-items-center items-center">

View File

@@ -1,5 +1,6 @@
<script>
import { PRICES } from "#lib/constants";
<script lang="ts">
import { PRICES } from "#lib/constants.js";
import { Enums } from "@ibcornelsen/database/client";
export let bullets;
export let title;
@@ -43,9 +44,9 @@ bullets = [
<div class="zeile grid grid-cols-subgrid col-span-4 py-4 border-b-[0px] justify-items-center items-center sm:text-[1.25rem]">
<div class="price justify-self-start pl-2">Preis inkl. MwSt.</div>
<div class="price"><b>{PRICES.VerbrauchsausweisWohnen[0]}&nbsp;</b></div>
<div class="price"><b>{PRICES.VerbrauchsausweisWohnen[1]}&nbsp;</b></div>
<div class="price"><b>{PRICES.VerbrauchsausweisWohnen[2]}&nbsp;</b></div>
<div class="price"><b>{PRICES.VerbrauchsausweisWohnen[Enums.AusweisTyp.Standard]}&nbsp;</b></div>
<div class="price"><b>{PRICES.VerbrauchsausweisWohnen[Enums.AusweisTyp.Beratung]}&nbsp;</b></div>
<div class="price"><b>{PRICES.VerbrauchsausweisWohnen[Enums.AusweisTyp.Offline]}&nbsp;</b></div>
</div>
<div class="zeile grid grid-cols-subgrid col-span-4 py-4 border-b-[0px] justify-items-center items-center">

View File

@@ -1,5 +1,6 @@
<script>
import { PRICES } from "#lib/constants";
import { Enums } from "@ibcornelsen/database/client";
</script>
<div class="mt-6 mb-6 m-auto w-[99%] relative">
@@ -22,7 +23,7 @@
<div class="orange">
<div class="price">
ab <b> {PRICES.VerbrauchsausweisWohnen[0]} </b>€ inkl. 19% MwSt.
ab <b> {PRICES.VerbrauchsausweisWohnen[Enums.AusweisTyp.Standard]} </b>€ inkl. 19% MwSt.
</div>
</div>
@@ -53,7 +54,7 @@
<div class="orange">
<div class="price">
<b>ab {PRICES.BedarfsausweisWohnen[0]} €</b> inkl. 19% MwSt.
<b>ab {PRICES.BedarfsausweisWohnen[Enums.AusweisTyp.Standard]} €</b> inkl. 19% MwSt.
</div>
</div>

View File

@@ -1,5 +1,6 @@
<script>
import { PRICES } from "#lib/constants";
import { Enums } from "@ibcornelsen/database/client";
</script>
<div class="mt-6 mb-6 m-auto w-[99%] relative">
@@ -21,7 +22,7 @@
<div class="orange">
<div class="price">
ab <b> {PRICES.VerbrauchsausweisGewerbe[0]} </b>€ inkl. 19% MwSt.
ab <b> {PRICES.VerbrauchsausweisGewerbe[Enums.AusweisTyp.Standard]} </b>€ inkl. 19% MwSt.
</div>
</div>
@@ -52,7 +53,7 @@
<div class="orange">
<div class="price">
<b>ab {PRICES.BedarfsausweisGewerbe[0]} €</b> inkl. 19% MwSt.
<b>ab {PRICES.BedarfsausweisGewerbe[Enums.AusweisTyp.Standard]} €</b> inkl. 19% MwSt.
</div>
</div>

View File

@@ -4,6 +4,7 @@ import CardPriceiInfo from "#components/design/sidebars/cards/cardPriceiInfo.sve
import CardProduktSidebar from "#components/design/sidebars/cards/CardProduktSidebar.svelte";
import { PRICES } from "#lib/constants";
import { Enums } from "@ibcornelsen/database/client";
---
@@ -11,8 +12,8 @@ import { PRICES } from "#lib/constants";
<NavigationCard client:load/>
<CardProduktSidebar art="Verbrauchsausweis Gewerbe" price={PRICES.VerbrauchsausweisGewerbe[0]}></CardProduktSidebar>
<CardProduktSidebar art="Verbrauchsausweis Gewerbe" price={PRICES.VerbrauchsausweisGewerbe[Enums.AusweisTyp.Standard]}></CardProduktSidebar>
<CardPriceiInfo />
<CardProduktSidebar art="Bedarfsausweis Wohnen" price={PRICES.BedarfsausweisWohnen[0]}></CardProduktSidebar>
<CardProduktSidebar art="Bedarfsausweis Wohnen" price={PRICES.BedarfsausweisWohnen[Enums.AusweisTyp.Standard]}></CardProduktSidebar>
</div>

View File

@@ -4,6 +4,7 @@ import CardPriceiInfo from "#components/design/sidebars/cards/cardPriceiInfo.sve
import CardProduktSidebar from "#components/design/sidebars/cards/CardProduktSidebar.svelte";
import { PRICES } from "#lib/constants";
import { Enums } from "@ibcornelsen/database/client";
---
<div class="hidden 2xl:block">
@@ -11,9 +12,9 @@ import { PRICES } from "#lib/constants";
<CardContact />
<CardProduktSidebar art="Verbrauchsausweis Wohnen" price={PRICES.VerbrauchsausweisWohnen[0]}></CardProduktSidebar>
<CardProduktSidebar art="Verbrauchsausweis Wohnen" price={PRICES.VerbrauchsausweisWohnen[Enums.AusweisTyp.Standard]}></CardProduktSidebar>
<CardPriceiInfo />
<CardProduktSidebar art="Bedarfsausweis Gewerbe" price={PRICES.BedarfsausweisGewerbe[0]}></CardProduktSidebar>
<CardProduktSidebar art="Bedarfsausweis Gewerbe" price={PRICES.BedarfsausweisGewerbe[Enums.AusweisTyp.Standard]}></CardProduktSidebar>
</div>

View File

@@ -1,5 +1,6 @@
<script>
import { PRICES } from "#lib/constants";
<script lang="ts">
import { PRICES } from "#lib/constants.js";
import { Enums } from "@ibcornelsen/database/client";
</script>
<div id ="cardBAGpromo"
@@ -10,7 +11,7 @@ class=" box card">
<h2>Bedarfssausweis Gewerbe</h2>
<hr class="mb-4">
<img class="w-[70%] justify-self-center !min-w-[100px] mb-[1rem]" src="/images/right-sidebar/UMBE_gewerbegebaeude.svg" alt="Gewerbe Bedarfsausweis"/>
<p class="promo tracking-tighter text-[2rem] text-gray-700 pl-6">ab<span class="promo pl-[0.2rem]">{PRICES.BedarfsausweisGewerbe[0]}</span></p>
<p class="promo tracking-tighter text-[2rem] text-gray-700 pl-6">ab<span class="promo pl-[0.2rem]">{PRICES.BedarfsausweisGewerbe[Enums.AusweisTyp.Standard]}</span></p>
<a href="./energieausweis-erstellen/bedarfsausweis-gewerbe/" id="link-BA-promo"
class=" w-[90%] justify-self-center text-center text-white font-[700] bg-secondary rounded-md px-3 py-1 mt-2 no-underline text-[1rem]
hover:bg-primary

View File

@@ -1,5 +1,6 @@
<script>
import { PRICES } from "#lib/constants";
<script lang="ts">
import { PRICES } from "#lib/constants.js";
import { Enums } from "@ibcornelsen/database/client";
</script>
<div id ="cardBApromo"
@@ -10,7 +11,7 @@ class=" box card">
<h2>Bedarfssausweis Wohngebäude</h2>
<hr class="mb-4">
<img class="w-[70%] justify-self-center !min-w-[100px] mb-[1rem]" src="/images/right-sidebar/UMBE_wohngebaeude.svg" alt="Wohnhaus Verbrauchsausweis"/>
<p class="promo tracking-tighter text-[2rem] text-gray-700 pl-6">ab<span class="promo pl-[0.2rem]">{PRICES.BedarfsausweisWohnen[0]}</span></p>
<p class="promo tracking-tighter text-[2rem] text-gray-700 pl-6">ab<span class="promo pl-[0.2rem]">{PRICES.BedarfsausweisWohnen[Enums.AusweisTyp.Standard]}</span></p>
<a href="./energieausweis-erstellen/bedarfsausweis-wohngebaeude/" id="link-BA-promo"
class=" w-[90%] justify-self-center text-center text-white font-[700] bg-secondary rounded-md px-3 py-1 mt-2 no-underline text-[1rem]
hover:bg-primary

View File

@@ -1,5 +1,6 @@
<script>
import { PRICES } from "#lib/constants";
import { Enums } from "@ibcornelsen/database/client";
</script>
<div id ="cardPriceinfo" class="box card hidden lg:block">
@@ -8,23 +9,23 @@
<div>Verbrauchsausweis Wohngebäude</div>
<div>ab&nbsp;<span class="price">{PRICES.VerbrauchsausweisWohnen[0]}€</span>&nbsp;inkl.&nbsp;MwSt.</div>
<div>ab&nbsp;<span class="price">{PRICES.VerbrauchsausweisWohnen[Enums.AusweisTyp.Standard]}€</span>&nbsp;inkl.&nbsp;MwSt.</div>
<hr class="trenner">
<div>Bedarfsausweis Wohngebäude</div>
<div>ab&nbsp;<span class="price">{PRICES.BedarfsausweisWohnen[0]}€</span>&nbsp;inkl.&nbsp;MwSt.</div>
<div>ab&nbsp;<span class="price">{PRICES.BedarfsausweisWohnen[Enums.AusweisTyp.Standard]}€</span>&nbsp;inkl.&nbsp;MwSt.</div>
<hr class="trenner">
<div>Verbrauchsausweis Gewerbe</div>
<div>ab&nbsp;<span class="price">{PRICES.VerbrauchsausweisGewerbe[0]}€</span>&nbsp;inkl.&nbsp;MwSt.</div>
<div>ab&nbsp;<span class="price">{PRICES.VerbrauchsausweisGewerbe[Enums.AusweisTyp.Standard]}€</span>&nbsp;inkl.&nbsp;MwSt.</div>
<hr class="trenner">
<div>Bedarfsausweis Gewerbe</div>
<div>ab&nbsp;<span class="price">{PRICES.BedarfsausweisGewerbe[0]}€</span>&nbsp;inkl.&nbsp;MwSt.</div>
<div>ab&nbsp;<span class="price">{PRICES.BedarfsausweisGewerbe[Enums.AusweisTyp.Standard]}€</span>&nbsp;inkl.&nbsp;MwSt.</div>
<hr class="trenner">
<div>GEG-Nachweis Wohngebäude</div>
<div>ab&nbsp;<span class="price">{PRICES.GEGNachweisWohnen[0]}€</span>&nbsp;inkl.&nbsp;MwSt.</div>
<div>ab&nbsp;<span class="price">{PRICES.GEGNachweisWohnen[Enums.AusweisTyp.Standard]}€</span>&nbsp;inkl.&nbsp;MwSt.</div>
<hr class="trenner">
<div>GEG-Nachweis Gewerbe</div>

View File

@@ -1,5 +1,6 @@
<script lang="ts">
import { PRICES } from "#lib/constants";
import { PRICES } from "#lib/constants.js";
import { Enums } from "@ibcornelsen/database/client";
</script>
<div id ="cardVAGpromo"
@@ -10,7 +11,7 @@
<h2>Verbrauchsausweis Gewerbe</h2>
<hr class="mb-4">
<img class="w-[70%] justify-self-center !min-w-[100px]" src="/images/right-sidebar/UMBE_gewerbegebaeude.svg" alt="Gewerbe Verbrauchsausweis"/>
<p class="promo tracking-tighter text-[2rem] text-gray-700 pl-6">ab<span class="promo pl-2">{PRICES.VerbrauchsausweisGewerbe[0]}</span></p>
<p class="promo tracking-tighter text-[2rem] text-gray-700 pl-6">ab<span class="promo pl-2">{PRICES.VerbrauchsausweisGewerbe[Enums.AusweisTyp.Standard]}</span></p>
<a href="./energieausweis-erstellen/verbrauchsausweis-gewerbe/" id="link-VA-promo"
class=" w-[90%] justify-self-center text-center text-white font-[700] bg-gradient-to-br from-secondary to-secondary-grad rounded-md px-3 py-1 mt-2 no-underline text-[1rem]
hover:bg-primary

View File

@@ -1,5 +1,6 @@
<script>
import { PRICES } from "#lib/constants";
<script lang="ts">
import { PRICES } from "#lib/constants.js";
import { Enums } from "@ibcornelsen/database/client";
</script>
@@ -14,7 +15,7 @@
alt="Wohnhaus Verbrauchsausweis"
/>
<p class="promo tracking-tighter text-[2rem] text-gray-700 pl-6">
ab<span class="promo pl-2">{PRICES.VerbrauchsausweisWohnen[0]}</span
ab<span class="promo pl-2">{PRICES.VerbrauchsausweisWohnen[Enums.AusweisTyp.Standard]}</span
>
</p>
<a

View File

@@ -2,6 +2,7 @@
import { fade } from "svelte/transition";
import WidgetCardTemplate from "#components/widgets/WidgetCardTemplate_IBC.svelte";
import { PRICES } from "#lib/constants.js";
import { Enums } from "@ibcornelsen/database/client";
let gebaeudetyp: string = "bitte auswählen";
let anlass: string = "bitte auswählen";
@@ -200,7 +201,7 @@ threeBOX = ((ausnahme === true) && (gebaeudetyp === "Mischgebäude") && (twoBo
>
<WidgetCardTemplate
name="Verbrauchsausweis Wohngebäude"
price={PRICES.VerbrauchsausweisWohnen[0]}
price={PRICES.VerbrauchsausweisWohnen[Enums.AusweisTyp.Standard]}
src="/images/widget/wohngebaeude.svg"
alt="Wohnhaus Verbrauchsausweis"
variant="einfach"
@@ -226,7 +227,7 @@ threeBOX = ((ausnahme === true) && (gebaeudetyp === "Mischgebäude") && (twoBo
>
<WidgetCardTemplate
name="Bedarfsausweis Wohngebäude"
price={PRICES.BedarfsausweisWohnen[0]}
price={PRICES.BedarfsausweisWohnen[Enums.AusweisTyp.Standard]}
src="/images/widget/wohngebaeude.svg"
alt="Wohnhaus Bedarfsausweis"
variant="fundiert"
@@ -252,7 +253,7 @@ threeBOX = ((ausnahme === true) && (gebaeudetyp === "Mischgebäude") && (twoBo
>
<WidgetCardTemplate
name="Verbrauchsausweis Gewerbegebäude"
price={PRICES.VerbrauchsausweisGewerbe[0]}
price={PRICES.VerbrauchsausweisGewerbe[Enums.AusweisTyp.Standard]}
src="/images/widget/gewerbegebaeude.svg"
alt="Gewerbe Verbrauchsausweis"
variant="einfach"
@@ -280,7 +281,7 @@ threeBOX = ((ausnahme === true) && (gebaeudetyp === "Mischgebäude") && (twoBo
>
<WidgetCardTemplate
name="Bedarfsausweis Gewerbegebäude"
price={PRICES.BedarfsausweisGewerbe[0]}
price={PRICES.BedarfsausweisGewerbe[Enums.AusweisTyp.Standard]}
src="/images/widget/gewerbegebaeude.svg"
alt="Gewerbe Bedarfsausweis"
variant="fundiert"
@@ -307,7 +308,7 @@ threeBOX = ((ausnahme === true) && (gebaeudetyp === "Mischgebäude") && (twoBo
>
<WidgetCardTemplate
name="GEG-Nachweis Wohngebäude"
price={PRICES.GEGNachweisWohnen[0]}
price={PRICES.GEGNachweisWohnen[Enums.AusweisTyp.Standard]}
src="/images/widget/wohngebaeude.svg"
alt="Gewerbe Bedarfsausweis"
variant="Bauvorlage"
@@ -336,7 +337,7 @@ threeBOX = ((ausnahme === true) && (gebaeudetyp === "Mischgebäude") && (twoBo
>
<WidgetCardTemplate
name="GEG-Nachweis Gewerbegebäude"
price={PRICES.GEGNachweisGewerbe[0]}
price={PRICES.GEGNachweisGewerbe[Enums.AusweisTyp.Standard]}
src="/images/widget/gewerbegebaeude.svg"
alt="Gewerbe Bedarfsausweis"
variant="Bauvorlage"

View File

@@ -2,6 +2,7 @@
import { fade } from "svelte/transition";
import WidgetCardTemplate from "#components/widgets/immowelt/WidgetCardTemplate_immowelt.svelte";
import { PRICES } from "#lib/constants.js";
import { Enums } from "@ibcornelsen/database/client";
let gebaeudetyp: string = "bitte auswählen";
let anlass: string = "bitte auswählen";
@@ -205,7 +206,7 @@ threeBOX = ((ausnahme === true) && (gebaeudetyp === "Mischgebäude") && (twoBo
>
<WidgetCardTemplate
name="Verbrauchsausweis Wohngebäude"
price={PRICES.VerbrauchsausweisWohnen[0]}
price={PRICES.VerbrauchsausweisWohnen[Enums.AusweisTyp.Standard]}
src="/images/immowelt/wohngebaeude_immowelt.svg"
alt="Wohnhaus Verbrauchsausweis"
variant="einfach"
@@ -231,7 +232,7 @@ threeBOX = ((ausnahme === true) && (gebaeudetyp === "Mischgebäude") && (twoBo
>
<WidgetCardTemplate
name="Bedarfsausweis Wohngebäude"
price={PRICES.BedarfsausweisWohnen[0]}
price={PRICES.BedarfsausweisWohnen[Enums.AusweisTyp.Standard]}
src="/images/immowelt/wohngebaeude_immowelt.svg"
alt="Wohnhaus Bedarfsausweis"
variant="fundiert"
@@ -257,7 +258,7 @@ threeBOX = ((ausnahme === true) && (gebaeudetyp === "Mischgebäude") && (twoBo
>
<WidgetCardTemplate
name="Verbrauchsausweis Gewerbegebäude"
price={PRICES.VerbrauchsausweisGewerbe[0]}
price={PRICES.VerbrauchsausweisGewerbe[Enums.AusweisTyp.Standard]}
src="/images/immowelt/gewerbegebaeude_immowelt.svg"
alt="Gewerbe Verbrauchsausweis"
variant="einfach"
@@ -285,7 +286,7 @@ threeBOX = ((ausnahme === true) && (gebaeudetyp === "Mischgebäude") && (twoBo
>
<WidgetCardTemplate
name="Bedarfsausweis Gewerbegebäude"
price={PRICES.BedarfsausweisGewerbe[0]}
price={PRICES.BedarfsausweisGewerbe[Enums.AusweisTyp.Standard]}
src="/images/immowelt/gewerbegebaeude_immowelt.svg"
alt="Gewerbe Bedarfsausweis"
variant="fundiert"
@@ -312,7 +313,7 @@ threeBOX = ((ausnahme === true) && (gebaeudetyp === "Mischgebäude") && (twoBo
>
<WidgetCardTemplate
name="GEG-Nachweis Wohngebäude"
price={PRICES.GEGNachweisWohnen[0]}
price={PRICES.GEGNachweisWohnen[Enums.AusweisTyp.Standard]}
src="/images/immowelt/wohngebaeude_immowelt.svg"
alt="Gewerbe Bedarfsausweis"
variant="Bauvorlage"
@@ -341,7 +342,7 @@ threeBOX = ((ausnahme === true) && (gebaeudetyp === "Mischgebäude") && (twoBo
>
<WidgetCardTemplate
name="GEG-Nachweis Gewerbegebäude"
price={PRICES.GEGNachweisGewerbe[0]}
price={PRICES.GEGNachweisGewerbe[Enums.AusweisTyp.Standard]}
src="/images/immowelt/gewerbegebaeude_immowelt.svg"
alt="Gewerbe Bedarfsausweis"
variant="Bauvorlage"

View File

@@ -18,7 +18,9 @@ export enum VALID_UUID_PREFIXES {
"inv" = "Rechnung",
"tkt" = "Ticket",
"pln" = "Gebäude Plan",
"gnw" = "GEG Nachweis Wohnen"
"gnw" = "GEG Nachweis Wohnen",
"gng" = "GEG Nachweis Gewerbe",
"gge" = "GEG Einpreisung",
}
/**
@@ -26,6 +28,7 @@ export enum VALID_UUID_PREFIXES {
*/
export const PRICES: Record<Enums.Ausweisart, Record<Enums.AusweisTyp, number>> = {
// per E-Mail , inkl.Beratung, offline
BedarfsausweisWohnen: {
[Enums.AusweisTyp.Standard]: 95,
[Enums.AusweisTyp.Beratung]: 125,
@@ -51,6 +54,7 @@ export const PRICES: Record<Enums.Ausweisart, Record<Enums.AusweisTyp, number>>
[Enums.AusweisTyp.Beratung]: 700,
[Enums.AusweisTyp.Offline]: 1000
}
};
export const SERVICES: Record<

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

@@ -0,0 +1,11 @@
import { UUidWithPrefix } from "#components/Ausweis/types.js";
import { Enums } from "@ibcornelsen/database/client";
import { z } from "zod";
export const filterAusweise = z.object({
uid: UUidWithPrefix.optional(),
ausgestellt: z.boolean().optional(),
ausstellgrund: z.nativeEnum(Enums.Ausstellgrund).optional(),
bestellt: z.boolean().optional(),
zurueckgestellt: z.boolean().optional()
})

View File

@@ -510,23 +510,23 @@ export async function pdfVerbrauchsausweisGewerbe(ausweis: VerbrauchsausweisGewe
// }
// }
if (aufnahme.kuehlung) {
/**
* Kühlungszuschlag - Pauschale Erhöhung um 6kWh/m²
* Primärenergiefaktor Strom
* @link https://www.bundesanzeiger.de/pub/publication/MRYM4nI84Sdlr0EIvvW?2
*/
addVerbrauch(
moment(ausweis.startdatum).format("MM.YYYY"),
moment(ausweis.startdatum).add(3, "years").format("MM.YYYY"),
"Kühlungszuschlag",
berechnungen?.primaerfaktorww.toString(),
Math.round(berechnungen?.kuehlungsZuschlag || 0).toString(),
"0",
"0",
""
);
}
// if (aufnahme.kuehlung) {
// /**
// * Kühlungszuschlag - Pauschale Erhöhung um 6kWh/m²
// * Primärenergiefaktor Strom
// * @link https://www.bundesanzeiger.de/pub/publication/MRYM4nI84Sdlr0EIvvW?2
// */
// addVerbrauch(
// moment(ausweis.startdatum).format("MM.YYYY"),
// moment(ausweis.startdatum).add(3, "years").format("MM.YYYY"),
// "Kühlungszuschlag",
// berechnungen?.primaerfaktorww.toString(),
// Math.round(berechnungen?.kuehlungsZuschlag || 0).toString(),
// "0",
// "0",
// ""
// );
// }
/* -------------------------------- Seite 4 -------------------------------- */

View File

@@ -0,0 +1,47 @@
import { transport } from "#lib/mail.js";
import {
Benutzer,
GEGNachweisWohnen,
} from "@ibcornelsen/database/client";
export async function sendGEGBestellungsMail(
nachweis: GEGNachweisWohnen,
user: Benutzer,
) {
await transport.sendMail({
from: `"IBCornelsen" <info@online-energieausweis.org>`,
to: user.email,
subject: `Bestellbestätigung vom IBCornelsen (ID: ${nachweis.uid})`,
cc: {
address: user.email || "",
name: user.name || "",
},
bcc: "info@online-energieausweis.org",
html: `<p>Sehr geehrte/r ${user.vorname} ${user.name},
<br>
<br>
vielen Dank für ihre Bestellung. Wir werden Ihnen Originalausweis und Rechnung nach Prüfung zuschicken. Die Ausstellung erfolgt nach aktuellem GEG und Ihr Ausweis wird beim DIBT registriert.<br><br>
Den Rechnungsbetrag haben Sie bereits bezahlt. Vielen Dank.</p>
<p>
Mit freundlichen Grüßen,
<br>
Dipl.-Ing. Jens Cornelsen
<br>
<br>
<strong>IB Cornelsen</strong>
<br>
Katendeich 5A
<br>
21035 Hamburg
<br>
www.online-energieausweis.org
<br>
<br>
fon 040 · 209339850
<br>
fax 040 · 209339859
</p>`,
});
}

View File

@@ -8,16 +8,16 @@ export async function getObjektKomplettClient(uid: string): Promise<ObjektKomple
uid
},
include: {
bilder: true,
aufnahmen: {
include: {
bedarfsausweis_wohnen: true,
verbrauchsausweis_gewerbe: true,
verbrauchsausweis_wohnen: true,
bilder: true,
unterlagen: true,
bedarfsausweise_wohnen: true,
verbrauchsausweise_gewerbe: true,
verbrauchsausweise_wohnen: true,
events: true
}
},
unterlagen: true
}
})
@@ -26,27 +26,29 @@ export async function getObjektKomplettClient(uid: string): Promise<ObjektKomple
}
return {
...omit(objekt, ["benutzer_id", "id", "aufnahmen", "bilder"]),
...omit(objekt, ["benutzer_id", "id"]),
aufnahmen: objekt.aufnahmen.map(aufnahme => ({
...omit(aufnahme, ["id", "objekt_id", "benutzer_id", "bedarfsausweis_wohnen", "verbrauchsausweis_gewerbe", "verbrauchsausweis_wohnen"]),
bedarfsausweis_wohnen: (aufnahme.bedarfsausweis_wohnen && {
...omit(aufnahme.bedarfsausweis_wohnen, ["id", "aufnahme_id", "benutzer_id"]),
...omit(aufnahme, ["id", "objekt_id", "benutzer_id", "bedarfsausweise_wohnen", "verbrauchsausweise_gewerbe", "verbrauchsausweise_wohnen"]),
bedarfsausweise_wohnen: aufnahme.bedarfsausweise_wohnen.map(ausweis => ({
...omit(ausweis, ["id", "aufnahme_id", "benutzer_id"]),
uid_aufnahme: aufnahme.uid,
uid_objekt: objekt.uid
}) || undefined,
verbrauchsausweis_wohnen: (aufnahme.verbrauchsausweis_wohnen && {
...omit(aufnahme.verbrauchsausweis_wohnen, ["id", "aufnahme_id", "benutzer_id"]),
})),
verbrauchsausweise_wohnen:
aufnahme.verbrauchsausweise_wohnen.map(ausweis => ({
...omit(ausweis, ["id", "aufnahme_id", "benutzer_id"]),
uid_aufnahme: aufnahme.uid,
uid_objekt: objekt.uid
}) || undefined,
verbrauchsausweis_gewerbe: (aufnahme.verbrauchsausweis_gewerbe && {
...omit(aufnahme.verbrauchsausweis_gewerbe, ["id", "aufnahme_id", "benutzer_id"]),
})),
verbrauchsausweise_gewerbe:
aufnahme.verbrauchsausweise_gewerbe.map(ausweis => ({
...omit(ausweis, ["id", "aufnahme_id", "benutzer_id"]),
uid_aufnahme: aufnahme.uid,
uid_objekt: objekt.uid
}) || undefined,
})),
uid_objekt: objekt.uid,
})),
bilder: objekt.bilder.map(bild => omit(bild, ["id", "objekt_id"])),
unterlagen: objekt.unterlagen.map(unterlage => omit(unterlage, ["id", "objekt_id"]))
bilder: aufnahme.bilder.map(bild => omit(bild, ["id", "aufnahme_id"])),
unterlagen: aufnahme.unterlagen.map(unterlage => omit(unterlage, ["id", "aufnahme_id"]))
}))
}
}

View File

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

View File

@@ -0,0 +1,44 @@
<script lang="ts">
import { AufnahmeKomplettClient, BenutzerClient } from "#components/Ausweis/types.js";
import Carousel from "#components/Carousel.svelte";
import DashboardAusweis from "#components/Dashboard/DashboardAusweis.svelte";
import { Objekt } from "@ibcornelsen/database/client";
import { ChevronLeft, ChevronRight, Plus } from "radix-svelte-icons";
export let user: BenutzerClient;
export let aufnahme: AufnahmeKomplettClient;
export let objekt: Objekt;
</script>
<h1 class="text-4xl font-medium mb-8">{objekt.adresse}, {objekt.plz} {objekt.ort}</h1>
<div class="bg-white rounded-lg">
{#if aufnahme.bilder.length > 0}
<Carousel perPage={1}>
{#each aufnahme.bilder as bild, i (i)}
<img src="/bilder/{bild.uid}.webp" alt={bild.kategorie} class="max-h-[60vh] h-full w-full object-contain">
{/each}
<span slot="left-control" class="p-2.5 bg-opacity-50 bg-white block rounded-full"><ChevronLeft size={24}></ChevronLeft></span>
<span slot="right-control" class="p-2.5 bg-opacity-50 bg-white block rounded-full"><ChevronRight size={24}></ChevronRight></span>
</Carousel>
{/if}
</div>
<div class="flex flex-row gap-4">
<button class="button flex flex-row rounded-lg gap-2"><Plus size={20}></Plus> Verbrauchsausweis Wohnen Erstellen</button>
<button class="button flex flex-row rounded-lg gap-2"><Plus size={20}></Plus> Verbrauchsausweis Gewerbe Erstellen</button>
<button class="button flex flex-row rounded-lg gap-2"><Plus size={20}></Plus> Bedarfsausweis Erstellen</button>
</div>
<div class="my-8 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
{#each aufnahme.verbrauchsausweise_wohnen as ausweis}
<DashboardAusweis {ausweis} {aufnahme} {objekt} progress={0}></DashboardAusweis>
{/each}
{#each aufnahme.bedarfsausweise_wohnen as ausweis}
<DashboardAusweis {ausweis} {aufnahme} {objekt} progress={0}></DashboardAusweis>
{/each}
{#each aufnahme.verbrauchsausweise_gewerbe as ausweis}
<DashboardAusweis {ausweis} {aufnahme} {objekt} progress={0}></DashboardAusweis>
{/each}
</div>

View File

@@ -1,114 +1,126 @@
<script lang="ts">
import {
AufnahmeClient,
ObjektClient,
UploadedGebaeudeBild,
VerbrauchsausweisWohnenClient,
} from "#components/Ausweis/types.js";
import AusweisPruefenBox from "#components/AusweisPruefenBox.svelte";
import NotificationProvider from "#components/NotificationProvider/NotificationProvider.svelte";
import { endEnergieVerbrauchVerbrauchsausweis_2016 } from "#lib/Berechnungen/VerbrauchsausweisWohnen/VerbrauchsausweisWohnen_2016.js";
import AusweisPruefenNotification from "#components/AusweisPruefenNotification.svelte";
import DashboardAusweisSkeleton from "#components/Dashboard/DashboardAusweisSkeleton.svelte"
import { Event } from "@ibcornelsen/database/client";
import Pagination from "#components/Pagination.svelte";
import { api } from "astro-typesafe-api/client";
import Cookies from "js-cookie"
import { API_ACCESS_TOKEN_COOKIE_NAME } from "#lib/constants.js"
import AusweisePruefenFilter from "#components/Dashboard/AusweisePruefenFilter.svelte";
import { z, ZodTypeAny } from "zod";
import { filterAusweise } from "#lib/filters.js";
export let ausweise: {
ausweis: VerbrauchsausweisWohnenClient,
aufnahme: AufnahmeClient,
objekt: ObjektClient,
bilder: UploadedGebaeudeBild[],
events: Event[]
}[];
export let page: number;
export let totalPages: number;
let filters: { name: keyof z.infer<typeof filterAusweise>, type: ZodTypeAny, value: any }[] = []
</script>
<div class="gap-4 flex flex-col">
{#each ausweise as { ausweis, objekt, aufnahme, bilder, events }}
{#await endEnergieVerbrauchVerbrauchsausweis_2016(ausweis, aufnahme, objekt)}
<div class="rounded-lg border w-full h-20 p-2.5 gap-4 flex flex-row items-center">
<div class="w-1/12 h-full flex flex-col gap-2">
<div class="skeleton w-8 h-8 rounded-full"></div>
</div>
<div class="w-1/12 h-full flex flex-col gap-2">
<div class="skeleton w-full h-4"></div>
<div class="skeleton w-full h-4"></div>
</div>
<div class="w-1/12 h-full flex flex-col gap-2">
<div class="skeleton w-full h-4"></div>
<div class="skeleton w-full h-4"></div>
</div>
<div class="w-1/12 h-full flex flex-col gap-2">
<div class="skeleton w-full h-4"></div>
<div class="skeleton w-full h-4"></div>
</div>
<div class="w-1/12 h-full flex flex-col gap-2">
<div class="skeleton w-full h-4"></div>
<div class="skeleton w-full h-4"></div>
</div>
<div class="w-1/12 h-full flex flex-col gap-2">
<div class="skeleton w-full h-4"></div>
</div>
<div class="w-1/12 h-full flex flex-col gap-2">
<div class="skeleton w-full h-4"></div>
<div class="skeleton w-full h-4"></div>
</div>
<div class="w-1/12 h-full flex flex-col gap-2">
<div class="skeleton w-full h-4"></div>
</div>
<div class="w-1/12 h-full flex flex-col gap-2">
<div class="skeleton w-full h-4"></div>
<div class="skeleton w-full h-4"></div>
</div>
<div class="w-1/12 h-full flex flex-col gap-2">
<div class="skeleton w-full h-4"></div>
</div>
<div class="w-1/12 h-full flex flex-col gap-2">
<div class="skeleton w-full h-4"></div>
</div>
<div class="w-1/12 h-full flex flex-col gap-2">
<div class="skeleton w-full h-4"></div>
<div class="skeleton w-full h-4"></div>
</div>
<div class="w-1/12 h-full flex flex-col gap-2">
<div class="skeleton w-full h-4"></div>
<div class="skeleton w-full h-4"></div>
</div>
<div class="w-1/12 h-full flex flex-col gap-2">
<div class="skeleton w-full h-4"></div>
<div class="skeleton w-full h-4"></div>
</div>
<div class="w-1/12 h-full flex flex-col gap-2">
<div class="skeleton w-full h-4"></div>
<div class="skeleton w-full h-4"></div>
</div>
<div class="w-1/12 h-full flex flex-col gap-2">
<div class="skeleton w-full h-4"></div>
<div class="skeleton w-full h-4"></div>
</div>
<div class="skeleton w-4 h-4"></div>
<div class="skeleton w-4 h-4"></div>
<div class="skeleton w-4 h-4"></div>
<div class="skeleton w-4 h-4"></div>
<div class="skeleton w-4 h-4"></div>
<div class="skeleton w-4 h-4"></div>
<div class="skeleton w-4 h-4"></div>
<div class="skeleton w-4 h-4"></div>
<div class="skeleton w-4 h-4"></div>
<div class="skeleton w-4 h-4"></div>
</div>
{:then calculations}
<AusweisPruefenBox {ausweis} {aufnahme} {objekt} {bilder} {events} {calculations}></AusweisPruefenBox>
{/await}
{/each}
<div class="flex flex-col mb-4">
<AusweisePruefenFilter bind:filters={filters}></AusweisePruefenFilter>
</div>
<div class="flex items-center justify-center mt-12">
<div class="join">
<button class="join-item btn btn-ghost shadow-none">1</button>
<button class="join-item btn btn-ghost shadow-none">2</button>
<button class="join-item btn btn-ghost shadow-none">3</button>
<button class="join-item btn btn-ghost shadow-none">4</button>
</div>
<div class="gap-4 flex flex-col">
{#await api.ausweise.GET.fetch({
limit: 10,
skip: (page - 1) * 10,
filters: filters.reduce((acc, filter) => {
acc[filter.name] = filter.value
return acc
}, {})
}, {
headers: {
Authorization: `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`
}
})}
<DashboardAusweisSkeleton></DashboardAusweisSkeleton>
{:then ausweise}
{#each ausweise as { ausweis, objekt, aufnahme, bilder, events }}
{#await endEnergieVerbrauchVerbrauchsausweis_2016(ausweis, aufnahme, objekt)}
<div class="rounded-lg border w-full h-20 p-2.5 gap-4 flex flex-row items-center">
<div class="w-1/12 h-full flex flex-col gap-2">
<div class="skeleton w-8 h-8 rounded-full"></div>
</div>
<div class="w-1/12 h-full flex flex-col gap-2">
<div class="skeleton w-full h-4"></div>
<div class="skeleton w-full h-4"></div>
</div>
<div class="w-1/12 h-full flex flex-col gap-2">
<div class="skeleton w-full h-4"></div>
<div class="skeleton w-full h-4"></div>
</div>
<div class="w-1/12 h-full flex flex-col gap-2">
<div class="skeleton w-full h-4"></div>
<div class="skeleton w-full h-4"></div>
</div>
<div class="w-1/12 h-full flex flex-col gap-2">
<div class="skeleton w-full h-4"></div>
<div class="skeleton w-full h-4"></div>
</div>
<div class="w-1/12 h-full flex flex-col gap-2">
<div class="skeleton w-full h-4"></div>
</div>
<div class="w-1/12 h-full flex flex-col gap-2">
<div class="skeleton w-full h-4"></div>
<div class="skeleton w-full h-4"></div>
</div>
<div class="w-1/12 h-full flex flex-col gap-2">
<div class="skeleton w-full h-4"></div>
</div>
<div class="w-1/12 h-full flex flex-col gap-2">
<div class="skeleton w-full h-4"></div>
<div class="skeleton w-full h-4"></div>
</div>
<div class="w-1/12 h-full flex flex-col gap-2">
<div class="skeleton w-full h-4"></div>
</div>
<div class="w-1/12 h-full flex flex-col gap-2">
<div class="skeleton w-full h-4"></div>
</div>
<div class="w-1/12 h-full flex flex-col gap-2">
<div class="skeleton w-full h-4"></div>
<div class="skeleton w-full h-4"></div>
</div>
<div class="w-1/12 h-full flex flex-col gap-2">
<div class="skeleton w-full h-4"></div>
<div class="skeleton w-full h-4"></div>
</div>
<div class="w-1/12 h-full flex flex-col gap-2">
<div class="skeleton w-full h-4"></div>
<div class="skeleton w-full h-4"></div>
</div>
<div class="w-1/12 h-full flex flex-col gap-2">
<div class="skeleton w-full h-4"></div>
<div class="skeleton w-full h-4"></div>
</div>
<div class="w-1/12 h-full flex flex-col gap-2">
<div class="skeleton w-full h-4"></div>
<div class="skeleton w-full h-4"></div>
</div>
<div class="skeleton w-4 h-4"></div>
<div class="skeleton w-4 h-4"></div>
<div class="skeleton w-4 h-4"></div>
<div class="skeleton w-4 h-4"></div>
<div class="skeleton w-4 h-4"></div>
<div class="skeleton w-4 h-4"></div>
<div class="skeleton w-4 h-4"></div>
<div class="skeleton w-4 h-4"></div>
<div class="skeleton w-4 h-4"></div>
<div class="skeleton w-4 h-4"></div>
</div>
{:then calculations}
<AusweisPruefenBox {ausweis} {aufnahme} {objekt} {bilder} {events} {calculations}></AusweisPruefenBox>
{/await}
{/each}
{/await}
</div>
<Pagination pages={totalPages} current={page} prev="/dashboard/admin/ausweise-pruefen/{page - 1}" next="/dashboard/admin/ausweise-pruefen/{page + 1}"></Pagination>
<div class="fixed bottom-8 right-8 flex flex-col gap-4">
<NotificationProvider component={AusweisPruefenNotification}></NotificationProvider>
</div>

View File

@@ -161,7 +161,7 @@
<style>
:global(.tab-list) {
@apply menu flex flex-col gap-2 px-0 bg-base-200 rounded-lg border border-base-300;
@apply flex flex-col gap-2 px-0 bg-base-200 rounded-lg border;
}
@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");
@@ -171,15 +171,15 @@
}
:global(.tab.selected) {
@apply bg-base-300;
@apply bg-gray-200;
}
:global(.tab) {
@apply btn btn-primary btn-ghost rounded-none px-8 justify-start outline-0 gap-4 items-center text-base font-normal text-base-content no-animation;
@apply rounded-none px-8 justify-start outline-0 gap-4 items-center text-base font-normal text-base-content;
}
:global(.tab:hover) {
@apply bg-base-300 outline-0;
@apply bg-gray-200 outline-0;
}
:global(.tab:focus) {

View File

@@ -13,7 +13,7 @@
</p>
<h1 class="text-4xl font-medium my-8">Gebäude</h1>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
<div class="columns columns-1 md:columns-2 lg:columns-3 gap-4">
{#each objekte as objekt}
<DashboardObjekt {objekt}></DashboardObjekt>
{/each}

View File

@@ -1,33 +0,0 @@
<script lang="ts">
import { BenutzerClient, ObjektKomplettClient } from "#components/Ausweis/types.js";
import Carousel from "#components/Carousel.svelte";
import DashboardAusweis from "#components/Dashboard/DashboardAusweis.svelte";
import { ChevronLeft, ChevronRight } from "radix-svelte-icons";
export let user: BenutzerClient;
export let objekt: ObjektKomplettClient;
</script>
<h1 class="text-4xl font-medium mb-8">{objekt.adresse}</h1>
<div class="bg-white rounded-lg">
<Carousel perPage={1}>
{#each objekt.bilder as bild, i (i)}
<img src="/bilder/{bild.uid}.webp" alt={bild.kategorie} class="max-h-[60vh] h-full w-full object-contain">
{/each}
<span slot="left-control" class="p-2.5 bg-opacity-50 bg-white block rounded-full"><ChevronLeft size={24}></ChevronLeft></span>
<span slot="right-control" class="p-2.5 bg-opacity-50 bg-white block rounded-full"><ChevronRight size={24}></ChevronRight></span>
</Carousel>
</div>
<div class="my-8 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
{#each objekt.aufnahmen as aufnahme}
{@const ausweis = aufnahme.verbrauchsausweis_wohnen ?? aufnahme.verbrauchsausweis_gewerbe ?? aufnahme.bedarfsausweis_wohnen}
{#if !ausweis}
<p>Diese Aufnahme hat noch keinen Ausweis.</p>
{:else}
<DashboardAusweis {ausweis} {aufnahme} {objekt} progress={0}></DashboardAusweis>
{/if}
{/each}
</div>

View File

@@ -56,7 +56,7 @@
},
];
export let selectedPaymentType: Bezahlmethoden =
export let aktiveBezahlmethode: Bezahlmethoden =
Enums.Bezahlmethoden.paypal;
async function createPayment(e: SubmitEvent) {
@@ -67,13 +67,13 @@
...rechnung,
ausweisart: Enums.Ausweisart.VerbrauchsausweisWohnen,
ausweis_uid: ausweis.uid,
bezahlmethode: selectedPaymentType,
bezahlmethode: aktiveBezahlmethode,
services: services
.filter((service) => service.selected)
.map((service) => service.id),
});
if (selectedPaymentType === Enums.Bezahlmethoden.rechnung) {
if (aktiveBezahlmethode === Enums.Bezahlmethoden.rechnung) {
window.location.href = `/payment/success?r=${response.uid}&a=${ausweis.uid}`
} else {
window.location.href = response.checkout_url as string;
@@ -315,32 +315,32 @@
class="rounded-lg border p-4 border-base-300 bg-base-100 flex flex-row gap-4 justify-between"
>
<PaymentOption
paymentType={Enums.Bezahlmethoden.paypal}
bind:selectedPaymentType
bezahlmethode={Enums.Bezahlmethoden.paypal}
bind:aktiveBezahlmethode
name={"PayPal"}
icon={"/images/paypal.png"}
></PaymentOption>
<PaymentOption
paymentType={Enums.Bezahlmethoden.sofort}
bind:selectedPaymentType
bezahlmethode={Enums.Bezahlmethoden.sofort}
bind:aktiveBezahlmethode
name={"Sofort"}
icon={"/images/sofort.png"}
></PaymentOption>
<PaymentOption
paymentType={Enums.Bezahlmethoden.giropay}
bind:selectedPaymentType
bezahlmethode={Enums.Bezahlmethoden.giropay}
bind:aktiveBezahlmethode
name={"Giropay"}
icon={"/images/giropay.png"}
></PaymentOption>
<PaymentOption
paymentType={Enums.Bezahlmethoden.creditcard}
bind:selectedPaymentType
bezahlmethode={Enums.Bezahlmethoden.creditcard}
bind:aktiveBezahlmethode
name={"Kreditkarte"}
icon={"/images/mastercard.png"}
></PaymentOption>
<PaymentOption
paymentType={Enums.Bezahlmethoden.rechnung}
bind:selectedPaymentType
bezahlmethode={Enums.Bezahlmethoden.rechnung}
bind:aktiveBezahlmethode
name={"Rechnung"}
icon={"/images/rechnung.png"}
></PaymentOption>

View File

@@ -17,9 +17,9 @@
AufnahmeClient,
BenutzerClient,
BildClient,
getAusweisartFromUUID,
ObjektClient,
RechnungClient,
UnterlageClient,
VerbrauchsausweisWohnenClient,
} from "#components/Ausweis/types.js";
import { validateAccessTokenClient } from "src/client/lib/validateAccessToken.js";
@@ -30,12 +30,15 @@
import { ausweisSpeichern } from "#client/lib/ausweisSpeichern.js";
import { addNotification } from "#components/Notifications/shared.js";
import NotificationWrapper from "#components/Notifications/NotificationWrapper.svelte";
import { nachweisSpeichern } from "#client/lib/nachweisSpeichern.js";
import { EnterFullScreen } from "radix-svelte-icons";
export let user: Partial<BenutzerClient>;
export let ausweis: VerbrauchsausweisWohnenClient;
export let aufnahme: AufnahmeClient;
export let objekt: ObjektClient;
export let bilder: BildClient[];
export let unterlagen: UnterlageClient[];
export let ausweisart: Enums.Ausweisart;
export let aktiveBezahlmethode: Bezahlmethoden = Enums.Bezahlmethoden.paypal;
export let ausweistyp: Enums.AusweisTyp = Enums.AusweisTyp.Standard;
@@ -137,7 +140,74 @@
}
async function anfordern() {
// TODO Angebot anfordern
if (!form.checkValidity()) {
addNotification({
dismissable: true,
message: "Fehlende Daten.",
subtext: "Nicht alle notwendigen Felder sind ausgefüllt, bitte füllen sie diese aus bevor sie es erneut versuchen.."
})
form.reportValidity();
return;
}
if (!await validateAccessTokenClient()) {
loginAction = bestellen
rechnung = rechnung
loginOverlayHidden = false;
return
}
loginOverlayHidden = true
let result: Awaited<ReturnType<typeof ausweisSpeichern>> | null = null;
if (ausweisart === Enums.Ausweisart.GEGNachweisWohnen) {
result = await nachweisSpeichern(ausweis, objekt, aufnahme, bilder, unterlagen, ausweisart)
} else {
result = await ausweisSpeichern(ausweis, objekt, aufnahme, bilder, ausweisart)
}
if (result === null) {
addNotification({
dismissable: true,
message: "Ups... Das hat nicht geklappt.",
subtext: "Der Nachweis konnte nicht gespeichert werden, bitte versuchen sie es erneut oder kontaktieren sie unseren Support."
})
}
try {
const { uid } = await api.rechnung.anfordern.PUT.fetch(
{
email: rechnung.email,
empfaenger: rechnung.empfaenger,
strasse: rechnung.strasse,
plz: rechnung.plz,
ort: rechnung.ort,
versand_empfaenger: rechnung.versand_empfaenger,
versand_strasse: rechnung.versand_strasse,
versand_plz: rechnung.versand_plz,
versand_ort: rechnung.versand_ort,
telefon: rechnung.telefon,
nachweis_uid: ausweis.uid,
},
{
headers: {
Authorization: `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`,
},
}
);
// Alle alten Ausweisdateien im localStorage löschen.
localStorage.clear();
window.location.href = `/einpreisung/success?e=${uid}&a=${ausweis.uid}`;
} catch (e) {
addNotification({
dismissable: true,
message: "Ups... Das hat nicht geklappt.",
subtext: "Beim erstellen des Nachweises ist etwas schiefgelaufen, bitte versuchen sie es erneut oder kontaktieren sie unseren Support."
})
}
}
async function bestellen() {
@@ -161,7 +231,12 @@
loginOverlayHidden = true
const result = await ausweisSpeichern(ausweis, objekt, aufnahme, bilder, ausweisart)
let result: Awaited<ReturnType<typeof ausweisSpeichern>> | null = null;
if (ausweisart === Enums.Ausweisart.GEGNachweisWohnen || ausweisart === Enums.Ausweisart.GEGNachweisGewerbe || ausweisart === Enums.Ausweisart.GEGNachweisBedarfsausweis) {
result = await nachweisSpeichern(ausweis, objekt, aufnahme, bilder, unterlagen, ausweisart)
} else {
result = await ausweisSpeichern(ausweis, objekt, aufnahme, bilder, ausweisart)
}
if (result === null) {
addNotification({
@@ -207,7 +282,13 @@
} else {
window.location.href = checkout_url as string;
}
} catch (e) {}
} catch (e) {
addNotification({
dismissable: true,
message: "Ups... Das hat nicht geklappt.",
subtext: "Beim erstellen des Ausweises ist etwas schiefgelaufen, bitte versuchen sie es erneut oder kontaktieren sie unseren Support."
})
}
}
/**
@@ -363,8 +444,14 @@ grid-cols-5 justify-around justify-items-center items-center"
<div class="ProduktKostenTabelle">
<div class="zeile betrag">
<div>Netto-Preis Energieausweis</div>
<div>:</div>
{#if ausweistyp === Enums.AusweisTyp.Standard}
<span>Netto-Preis Energieausweis</span>
{:else if ausweistyp === Enums.AusweisTyp.Beratung}
<span>Energieausweis inkl. Beratung</span>
{:else if ausweistyp === Enums.AusweisTyp.Offline}
<span>Energieausweis Offline</span>
{/if}
<span>:</span>
<div class="text-right">
<b>{(price * 0.81).toFixed(2) + " €"}</b>
</div>

View File

@@ -21,6 +21,23 @@
import GebaeudeDaten from "#components/Ausweis/GebaeudeDaten.svelte";
import { Enums } from "@ibcornelsen/database/client";
import moment from "moment";
import {
AuditType,
hidden,
} from "#components/Verbrauchsausweis/audits/hidden.js";
import { auditBedarfsausweisBenoetigt } from "#components/Verbrauchsausweis/audits/BedarfsausweisBenoetigt.js";
import { auditEndEnergie } from "#components/Verbrauchsausweis/audits/EndEnergie.js";
import { auditHeizungGebaeudeBaujahr } from "#components/Verbrauchsausweis/audits/HeizungGebaeudeBaujahr.js";
import { auditHeizungJuengerDreiJahre } from "#components/Verbrauchsausweis/audits/HeizungJuengerDreiJahre.js";
import { auditKlimaFaktoren } from "#components/Verbrauchsausweis/audits/KlimaFaktoren.js";
import { auditLeerStand } from "#components/Verbrauchsausweis/audits/LeerStand.js";
import { auditPlzNichtErkannt } from "#components/Verbrauchsausweis/audits/PlzNichtErkannt.js";
import { auditVerbrauchAbweichung } from "#components/Verbrauchsausweis/audits/VerbrauchAbweichung.js";
import { auditWarmWasser } from "#components/Verbrauchsausweis/audits/WarmWasser.js";
import { auditWohnFlaeche } from "#components/Verbrauchsausweis/audits/WohnFlaeche.js";
import { auditWohnflaecheGroesserGesamtflaeche } from "#components/Verbrauchsausweis/audits/WohnflaecheGroesserGesamtflaeche.js";
import { auditZeitraumAktuell } from "#components/Verbrauchsausweis/audits/ZeitraumAktuell.js";
import { notifications, RawNotificationWrapper, RawNotification } from "@ibcornelsen/ui";
export let ausweis: VerbrauchsausweisGewerbeClient;
export let user: BenutzerClient;
@@ -254,3 +271,267 @@
</form>
<RawNotificationWrapper class="fixed left-8 bottom-8 max-w-[400px] flex flex-col gap-4 z-50">
{#each Object.entries($notifications) as [uid, notification] (uid)}
<RawNotification notification={{ ...notification, uid }}>
{@html notification.subtext}
</RawNotification>
{/each}
{#if auditBedarfsausweisBenoetigt(ausweis, aufnahme)}
<RawNotification
notification={{
message: "Bedarfsausweis benötigt!",
timeout: 0,
uid: "BEDARFSAUSWEIS",
dismissable: false,
type: "info",
}}
>
Sie benötigen einen Bedarfsausweis. <a href="/energieausweis-erstellen/bedarfsausweis-erstellen"
>Bitte führen Sie hier Ihre Eingabe für den Bedarfsausweis fort</a
>.
</RawNotification>
{/if}
{#if typeof aufnahme.einheiten === "number" && aufnahme.einheiten < 1}
<RawNotification
notification={{
message: "Plausibilitätsprüfung",
timeout: 0,
uid: "ANZAHL_EINHEITEN",
dismissable: false,
type: "warning",
selector: "input[name='einheiten']"
}}
>
Die Anzahl der Wohneinheiten muss mindestens 1 betragen.
</RawNotification>
{/if}
{#await auditPlzNichtErkannt(objekt) then result}
{#if result}
<RawNotification
notification={{
message: "Plausibilitätsprüfung",
timeout: 0,
uid: "PLZ_NICHT_ERKANNT",
dismissable: false,
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(aufnahme)}
<RawNotification
notification={{
message: "Plausibilitätsprüfung",
timeout: 0,
uid: "HEIZUNG_GEBAEUDE_BAUJAHR",
dismissable: true,
onUserDismiss: () => {
hidden.add(AuditType.HEIZUNG_GEBAEUDE_BAUJAHR);
aufnahme = aufnahme;
},
type: "warning",
}}
>
Sie haben angegeben, dass ihre Heizung vor ihrem Gebäude konstruiert
wurde. Sind sie sich sicher, dass das stimmt?
</RawNotification>
{/if}
{#if auditHeizungJuengerDreiJahre(aufnahme)}
<RawNotification
notification={{
message: "Plausibilitätsprüfung",
timeout: 0,
uid: "HEIZUNG_JUENGER_DREI_JAHRE",
dismissable: true,
onUserDismiss: () => {
hidden.add(AuditType.HEIZUNG_JUENGER_DREI_JAHRE);
aufnahme = aufnahme;
},
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.
</RawNotification>
{/if}
{#if auditZeitraumAktuell(ausweis, objekt)}
<RawNotification
notification={{
message: "Plausibilitätsprüfung",
timeout: 0,
uid: "ZEITRAUM_AKTUELL",
dismissable: true,
onUserDismiss: () => {
hidden.add(AuditType.ZEITRAUM_AKTUELL);
objekt = objekt;
ausweis = ausweis
},
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.
</RawNotification>
{/if}
{#await auditKlimaFaktoren(ausweis, objekt) then result}
{#if result}
<RawNotification
notification={{
message: "Plausibilitätsprüfung",
timeout: 0,
uid: "KLIMA_FAKTOREN",
dismissable: true,
onUserDismiss: () => {
hidden.add(AuditType.KLIMA_FAKTOREN);
objekt = objekt;
ausweis = ausweis;
},
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.
</RawNotification>
{/if}
{/await}
{#if auditWohnFlaeche(aufnahme)}
<RawNotification
notification={{
message: "Plausibilitätsprüfung",
timeout: 0,
uid: "WOHN_FLAECHE",
dismissable: true,
onUserDismiss: () => {
hidden.add(AuditType.WOHN_FLAECHE);
aufnahme = aufnahme;
},
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.
</RawNotification>
{/if}
{#if auditWarmWasser(ausweis)}
<RawNotification
notification={{
message: "Plausibilitätsprüfung",
timeout: 0,
uid: "WARM_WASSER",
dismissable: true,
onUserDismiss: () => {
hidden.add(AuditType.WARM_WASSER);
ausweis = ausweis;
},
type: "warning",
}}
>
Bitte überprüfen Sie nochmal die Höhe des Warmwasseranteils. Dieser
scheint nicht ganz im Rahmen zu liegen.
</RawNotification>
{/if}
{#if auditLeerStand(aufnahme)}
<RawNotification
notification={{
message: "Plausibilitätsprüfung",
timeout: 0,
uid: "LEER_STAND",
dismissable: false,
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.
</RawNotification>
{/if}
{@const abweichung = auditVerbrauchAbweichung(ausweis, aufnahme)}
{#if abweichung.length > 0}
<RawNotification
notification={{
message: "Plausibilitätsprüfung",
timeout: 0,
uid: "VERBRAUCH_ABWEICHUNG",
dismissable: true,
onUserDismiss: () => {
hidden.add(AuditType.VERBRAUCH_ABWEICHUNG);
objekt = objekt;
ausweis = ausweis;
},
type: "warning",
}}
>
Die Abweichung der Verbräuche zwischen Zeitraum {abweichung[0]} und {abweichung[1]} beträgt mehr als 30% und sie haben keinen Leerstand angegeben.
Sind sie sich sicher, dass das stimmt?
</RawNotification>
{/if}
{#await auditEndEnergie(ausweis, objekt, aufnahme) then result}
{#if result}
<RawNotification
notification={{
message: "Plausibilitätsprüfung",
timeout: 0,
uid: "END_ENERGIE",
dismissable: true,
onUserDismiss: () => {
hidden.add(AuditType.END_ENERGIE);
objekt = objekt;
},
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(aufnahme)}
<RawNotification
notification={{
message: "Plausibilitätsprüfung",
timeout: 0,
uid: "WOHNFLAECHE_GROESSER_GESAMTFLAECHE",
dismissable: false,
type: "error",
selector: "input[name='nutzflaeche']"
}}
>
Die Wohnfläche darf nicht größer als die Nutzfläche sein.
</RawNotification>
{/if}
</RawNotificationWrapper>

View File

@@ -0,0 +1,204 @@
<script lang="ts">
import Ausweisart from "#components/Ausweis/Ausweisart.svelte";
import GebaeudeDaten from "#components/Ausweis/GebaeudeDaten.svelte";
import {
ObjektClient,
AufnahmeClient,
BenutzerClient,
UploadedGebaeudeBild,
UnterlageClient,
GEGNachweisWohnenClient,
} from "#components/Ausweis/types.js";
import Bereich from "#components/labels/Bereich.svelte";
import { Enums } from "@ibcornelsen/database/client";
import InputLabel from "#components/labels/InputLabel.svelte";
import HelpLabel from "#components/labels/HelpLabel.svelte";
import Progressbar from "#components/Ausweis/Progressbar.svelte";
import FileGrid from "#components/FileGrid.svelte";
import ButtonWeiterHilfe from "#components/Ausweis/ButtonWeiterHilfe.svelte";
export let nachweis: GEGNachweisWohnenClient;
export let objekt: ObjektClient;
export let aufnahme: AufnahmeClient;
export let user: BenutzerClient = {} as BenutzerClient;
export let bilder: UploadedGebaeudeBild[] = [];
export let plaene: UnterlageClient[] =
[];
export let unterlagen: UnterlageClient[] = [];
if (Object.keys(nachweis).length === 0) {
const localStorageAusweis = localStorage.getItem("ausweis");
if (localStorageAusweis) {
nachweis = JSON.parse(localStorageAusweis)
}
}
if (Object.keys(aufnahme).length === 0) {
const localStorageAufnahme = localStorage.getItem("aufnahme");
if (localStorageAufnahme) {
aufnahme = JSON.parse(localStorageAufnahme)
}
}
if (Object.keys(objekt).length === 0) {
const localStorageObjekt = localStorage.getItem("objekt");
if (localStorageObjekt) {
objekt = JSON.parse(localStorageObjekt)
}
}
if (Object.keys(bilder).length === 0) {
const localStorageBilder = localStorage.getItem("bilder");
if (localStorageBilder) {
bilder = JSON.parse(localStorageBilder)
}
}
if (Object.keys(unterlagen).length === 0) {
const localStorageUnterlagen = localStorage.getItem("unterlagen");
if (localStorageUnterlagen) {
unterlagen = JSON.parse(localStorageUnterlagen)
}
}
$: {
localStorage.setItem("ausweis", JSON.stringify(nachweis))
localStorage.setItem("aufnahme", JSON.stringify(aufnahme))
localStorage.setItem("objekt", JSON.stringify(objekt))
localStorage.setItem("bilder", JSON.stringify(bilder))
localStorage.setItem("unterlagen", JSON.stringify(unterlagen))
}
const ausweisart = Enums.Ausweisart.GEGNachweisGewerbe;
</script>
<div
id="skala"
class="bg-white grid grid-cols-1 p-4 lg:grid-cols-2 lg:gap-x-6 no-scroll"
>
<div
id="progress-box"
class="w-full box relative px-4 py-3 text-center order-2 self-stretch"
>
<h1 class="text-secondary text-3xl m-0">GEG Nachweis Anfragen</h1>
<h2 class="text-primary text-xl">Verbrauchsausweis Wohnen</h2>
<Progressbar
active={0}
steps={["Gebäudedaten", "Kundendaten", "Bestätigung"]}
/>
</div>
</div>
<form
id="formInput-1"
name="ausweis"
data-test="ausweis"
>
<div id="formular-box" class="formular-boxen ring-0">
<!-- A Prüfung der Ausweisart -->
<Bereich bereich="A" title="Prüfung der Ausweisart">
<Ausweisart bind:objekt bind:aufnahme bind:ausweis={nachweis} {ausweisart} />
</Bereich>
<!-- B Eingabe der Gebäudeadresse - Angaben zu Wohnfläche, Keller und Dachgeschoss -->
<Bereich
bereich="B"
title="Eingabe der Gebäudeadresse - Angaben zu Wohnfläche, Keller und Dachgeschoss"
><GebaeudeDaten bind:aufnahme bind:objekt {ausweisart} /></Bereich
>
<Bereich bereich="C" title="Beschreibung des Bauvorhabens">
<div class="bereich-box">
<div class="input-standard order-3 md:order-5 xl:order-3">
<InputLabel
title="Angaben zur Heizung, Lüftung, Energieerezugung, Qualität und Aufbau der Gebäudehülle usw."
></InputLabel>
<textarea class="rounded-e-none" rows="10" bind:value={nachweis.beschreibung}></textarea>
<div class="help-label">
<HelpLabel>
Geben Sie kurz an: Heizung, Lüftung, regenerative
Energie (Solar, PV) und die Gebäudehülle (Qualität,
Aufbau).
</HelpLabel>
</div>
</div>
</div>
</Bereich>
<Bereich bereich="D" title="Gebäudepläne & Unterlagen">
<div
class="bereich-box grid grid-cols-1 lg:grid-cols-2 gap-x-6 mt-6"
>
<div class="md:box md:card mb-0">
<div class="font-bold mb-2">Pläne</div>
<div>
Hier können sie Grundrisspläne, Ansichtspläne und
Schnitte hochladen. Die Dateien können entweder im PDF
Format oder als Bild hochgeladen werden.
</div>
</div>
<div class="md:box md:card mb-0 mt-6 md:mt-0">
<div>
<strong
>Bitte laden Sie hier mind. 1 Dokument hoch:</strong
>
</div>
<FileGrid
max={Infinity}
min={1}
name={"plaene"}
bind:files={plaene}
bind:ausweis={nachweis}
bind:objekt
></FileGrid>
</div>
</div>
<div
class="bereich-box grid grid-cols-1 lg:grid-cols-2 gap-x-6 mt-6"
>
<div class="md:box md:card mb-0">
<div class="font-bold mb-2">Unterlagen</div>
<div>
Hier können sie weitere Unterlagen wie z.B.
Baugenehmigungen, U-Wert Berechnungen, Anlagentechnik
oder ihren alten Energieausweis hochladen. Die Dateien
können entweder im PDF Format oder als Bild hochgeladen
werden.
</div>
</div>
<div class="md:box md:card mb-0 mt-6 md:mt-0">
<FileGrid
max={Infinity}
min={0}
name={"unterlagen"}
bind:files={unterlagen}
bind:ausweis={nachweis}
bind:objekt
></FileGrid>
</div>
</div>
</Bereich>
</div>
<ButtonWeiterHilfe
bind:ausweis={nachweis}
bind:bilder
bind:unterlagen
bind:user
bind:objekt
bind:aufnahme
ausweisart={Enums.Ausweisart.GEGNachweisGewerbe}
>
</ButtonWeiterHilfe>
</form>

View File

@@ -2,7 +2,6 @@
import Ausweisart from "#components/Ausweis/Ausweisart.svelte";
import GebaeudeDaten from "#components/Ausweis/GebaeudeDaten.svelte";
import {
VerbrauchsausweisWohnenClient,
ObjektClient,
AufnahmeClient,
BenutzerClient,
@@ -16,167 +15,18 @@
import HelpLabel from "#components/labels/HelpLabel.svelte";
import Progressbar from "#components/Ausweis/Progressbar.svelte";
import FileGrid from "#components/FileGrid.svelte";
import Hilfe from "#components/Ausweis/Hilfe.svelte";
import Overlay from "#components/Overlay.svelte";
import EmbeddedAuthFlowModule from "#modules/EmbeddedAuthFlowModule.svelte";
import { validateAccessTokenClient } from "#client/lib/validateAccessToken.js";
import { api } from "astro-typesafe-api/client";
import { API_ACCESS_TOKEN_COOKIE_NAME } from "#lib/constants.js";
import { exclude } from "#lib/exclude.js";
import Cookies from "js-cookie";
import ButtonWeiterHilfe from "#components/Ausweis/ButtonWeiterHilfe.svelte";
import GEGAusweisart from "#components/GEGNachweis/GEGAusweisart.svelte";
export async function nachweisSpeichern(
nachweis: GEGNachweisWohnenClient,
objekt: ObjektClient,
aufnahme: AufnahmeClient,
unterlagen: (UnterlageClient & { data?: string })[]
) {
if (objekt.uid) {
await api.objekt._uid.PATCH.fetch({
...exclude(objekt, ["uid"])
}, {
params: {
uid: objekt.uid
},
headers: {
"Authorization": `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`
}
})
} else {
const { uid } = await api.objekt.PUT.fetch({
...exclude(objekt, ["uid"])
}, {
headers: {
"Authorization": `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`
}
})
objekt.uid = uid;
}
if (aufnahme.uid) {
await api.aufnahme._uid.PATCH.fetch({
...exclude({...aufnahme, baujahr_klima: []}, ["uid"])
}, {
params: {
uid: aufnahme.uid
},
headers: {
"Authorization": `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`
}
})
} else {
const { uid } = await api.aufnahme.PUT.fetch({
aufnahme: {...aufnahme, baujahr_klima: []},
uid_objekt: objekt.uid
}, {
headers: {
"Authorization": `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`
}
})
aufnahme.uid = uid
}
if (nachweis.uid) {
await api["geg-nachweis-wohnen"]._uid.PATCH.fetch({
...exclude(nachweis, ["uid"])
}, {
params: {
uid: nachweis.uid
},
headers: {
"Authorization": `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`
}
})
} else {
const { uid } = await api["geg-nachweis-wohnen"].PUT.fetch({
nachweis: nachweis,
uid_aufnahme: aufnahme.uid
}, {
headers: {
"Authorization": `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`
}
})
nachweis.uid = uid;
}
for (const unterlage of unterlagen) {
if (unterlage.uid || !unterlage.data) {
continue;
}
const response = await api.objekt._uid.unterlagen.PUT.fetch({
data: unterlage.data,
kategorie: unterlage.kategorie,
mime: unterlage.mime,
name: unterlage.name
}, {
params: {
uid: objekt.uid
},
headers: {
"Authorization": `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`
}
})
unterlage.uid = response.uid
}
return {
uid_ausweis: nachweis.uid,
uid_aufnahme: aufnahme.uid,
uid_objekt: objekt.uid
}
}
async function nachweisAbschicken() {
if (!(await validateAccessTokenClient())) {
loginOverlayHidden = false;
return;
}
const result = await nachweisSpeichern(nachweis, objekt, aufnahme, unterlagen)
if (result !== null) {
window.history.pushState(
{},
"",
`${location.pathname}?uid=${result.uid_ausweis}`
);
window.location.href = `/kundendaten?uid=${result.uid_ausweis}`;
}
loginOverlayHidden = true;
}
async function spaeterWeitermachen() {
if (!(await validateAccessTokenClient())) {
loginOverlayHidden = false;
return;
}
loginOverlayHidden = true;
}
let loginOverlayHidden = true;
export let nachweis: GEGNachweisWohnenClient;
export let objekt: ObjektClient;
export let aufnahme: AufnahmeClient;
export let user: BenutzerClient = {} as BenutzerClient;
export let bilder: UploadedGebaeudeBild[] = [];
export let plaene: (UnterlageClient & { data: string })[] =
export let plaene: UnterlageClient[] =
[];
export let unterlagen: (UnterlageClient & {
data: string;
})[] = [];
export let unterlagen: UnterlageClient[] = [];
if (Object.keys(nachweis).length === 0) {
const localStorageAusweis = localStorage.getItem("ausweis");
@@ -199,17 +49,27 @@
}
}
if (Object.keys(bilder).length === 0) {
const localStorageBilder = localStorage.getItem("bilder");
if (localStorageBilder) {
bilder = JSON.parse(localStorageBilder)
}
}
if (Object.keys(unterlagen).length === 0) {
const localStorageUnterlagen = localStorage.getItem("unterlagen");
if (localStorageUnterlagen) {
unterlagen = JSON.parse(localStorageUnterlagen)
}
}
$: {
if (nachweis.uid && objekt.uid && aufnahme.uid) {
localStorage.setItem(nachweis.uid, JSON.stringify(nachweis))
localStorage.setItem(objekt.uid, JSON.stringify(objekt))
localStorage.setItem(aufnahme.uid, JSON.stringify(aufnahme))
} else {
localStorage.setItem("ausweis", JSON.stringify(nachweis))
localStorage.setItem("aufnahme", JSON.stringify(aufnahme))
localStorage.setItem("objekt", JSON.stringify(objekt))
}
localStorage.setItem("ausweis", JSON.stringify(nachweis))
localStorage.setItem("aufnahme", JSON.stringify(aufnahme))
localStorage.setItem("objekt", JSON.stringify(objekt))
localStorage.setItem("bilder", JSON.stringify(bilder))
localStorage.setItem("unterlagen", JSON.stringify(unterlagen))
}
const ausweisart = Enums.Ausweisart.GEGNachweisWohnen;
@@ -224,7 +84,7 @@
class="w-full box relative px-4 py-3 text-center order-2 self-stretch"
>
<h1 class="text-secondary text-3xl m-0">GEG Nachweis Anfragen</h1>
<h2 class="text-primary text-xl">Verbrauchsausweis Wohnen</h2>
<h2 class="text-primary text-xl">GEG-Nachweis Wohngebäude</h2>
<Progressbar
active={0}
steps={["Gebäudedaten", "Kundendaten", "Bestätigung"]}
@@ -234,7 +94,6 @@
<form
id="formInput-1"
on:submit={nachweisAbschicken}
name="ausweis"
data-test="ausweis"
>
@@ -242,7 +101,7 @@
<!-- A Prüfung der Ausweisart -->
<Bereich bereich="A" title="Prüfung der Ausweisart">
<Ausweisart bind:objekt bind:aufnahme bind:ausweis={nachweis} {ausweisart} />
<GEGAusweisart bind:objekt bind:aufnahme bind:ausweis={nachweis} {ausweisart} />
</Bereich>
<!-- B Eingabe der Gebäudeadresse - Angaben zu Wohnfläche, Keller und Dachgeschoss -->
@@ -332,31 +191,15 @@
</Bereich>
</div>
<div
class="grid grid-cols-[1fr_min-content_min-content_min-content] grid-rows-[min_content_1fr] gap-x-2 self-start justify-self-end mt-8"
<ButtonWeiterHilfe
bind:ausweis={nachweis}
bind:bilder
bind:unterlagen
bind:user
bind:objekt
bind:aufnahme
ausweisart={Enums.Ausweisart.GEGNachweisWohnen}
>
<div></div>
</ButtonWeiterHilfe>
<Hilfe />
<button class="button" type="button" on:click={spaeterWeitermachen}
>Später Weitermachen
</button>
<div>
<Overlay bind:hidden={loginOverlayHidden}>
<div class="bg-white w-full max-w-screen-sm py-8">
<EmbeddedAuthFlowModule onLogin={nachweisAbschicken}
></EmbeddedAuthFlowModule>
</div>
</Overlay>
<button
on:click={nachweisAbschicken}
type="button"
class="button"
data-cy="weiter">Weiter</button
>
</div>
</div>
</form>

View File

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

View File

@@ -1,6 +1,6 @@
---
import AusweisLayout from "#layouts/AusweisLayoutDaten.astro";
import { AufnahmeClient, GEGNachweisWohnenClient, ObjektClient, UploadedGebaeudeBild } from "#components/Ausweis/types";
import { AufnahmeClient, GEGNachweisWohnenClient, ObjektClient, UnterlageClient, UploadedGebaeudeBild } from "#components/Ausweis/types";
import { createCaller } from "../../astro-typesafe-api-caller.js";
import { API_ACCESS_TOKEN_COOKIE_NAME } from "#lib/constants.js";
import { validateAccessTokenServer } from "#server/lib/validateAccessToken.js";
@@ -11,6 +11,7 @@ let nachweis: GEGNachweisWohnenClient = {} as GEGNachweisWohnenClient;
let aufnahme: AufnahmeClient = {} as AufnahmeClient;
let objekt: ObjektClient = {} as ObjektClient;
let bilder: UploadedGebaeudeBild[] = []
let unterlagen: UnterlageClient[] = []
const valid = validateAccessTokenServer(Astro);
@@ -60,6 +61,15 @@ if (uid) {
}
})
unterlagen = await caller.aufnahme._uid.unterlagen.GET.fetch(null, {
headers: {
authorization: `Bearer ${Astro.cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)?.value}`
},
params: {
uid: nachweis.uid_aufnahme
}
})
if (!nachweis) {
// Der Ausweis scheint nicht zu existieren.
// Wir leiten auf die generische Ausweisseite ohne UID weiter.
@@ -76,5 +86,5 @@ if (uid) {
---
<AusweisLayout title="GEG Nachweis Wohnen anfragen">
<GEGNachweisWohnenModule client:only {nachweis} {objekt} {aufnahme} {bilder} />
<GEGNachweisWohnenModule client:only {nachweis} {objekt} {aufnahme} {bilder} {unterlagen} />
</AusweisLayout>

View File

@@ -13,17 +13,17 @@ import { getAnsichtsausweis, getDatenblatt } from "#lib/server/ausweis.js";
export const GET = defineApiRoute({
input: z.object({
uid: z.string(),
uid_ausweis: z.string(),
}),
output: z.void(),
middleware: adminMiddleware,
async fetch({ uid }, context, user) {
async fetch({ uid_ausweis }, context, user) {
const ausweisart = getAusweisartFromUUID(uid);
if (ausweisart === "VerbrauchsausweisWohnen") {
const ausweis = await prisma.verbrauchsausweisWohnen.findUnique({
where: {
uid,
uid: uid_ausweis,
},
include: {
aufnahme: {
@@ -48,7 +48,9 @@ export const GET = defineApiRoute({
const rechnung = await prisma.rechnung.findFirst({
where: {
aufnahme_id: ausweis.aufnahme.id,
verbrauchsausweis_wohnen: {
uid: uid_ausweis
},
},
orderBy: {
erstellt_am: "desc",

View File

@@ -0,0 +1,103 @@
import { UUidWithPrefix } from "#components/Ausweis/types.js";
import { adminMiddleware } from "#lib/middleware/authorization.js";
import { mollieClient } from "#lib/mollie.js";
import { getPrismaAusweisAdapter } from "#lib/server/ausweis.js";
import { Prisma, prisma } from "@ibcornelsen/database/server";
import { APIError, defineApiRoute } from "astro-typesafe-api/server";
import { z } from "zod";
export const PUT = defineApiRoute({
input: z.object({
uid_ausweis: UUidWithPrefix
}),
middleware: adminMiddleware,
async fetch(input, context, transfer) {
const adapter = getPrismaAusweisAdapter(input.uid_ausweis) as Prisma.VerbrauchsausweisWohnenDelegate;
if (!adapter) {
throw new APIError({
code: "BAD_REQUEST",
message: "Ungültige 'uid_ausweis'"
})
}
const ausweis = await adapter.findUnique({
where: {
uid: input.uid_ausweis
}
})
if (!ausweis) {
throw new APIError({
code: "NOT_FOUND",
message: "Ausweis konnte nicht gefunden werden."
})
}
const response = await adapter.findUnique({
where: {
uid: input.uid_ausweis
},
select: {
rechnung: true
}
})
if (!response || !response.rechnung) {
await adapter.update({
where: {
uid: input.uid_ausweis
},
data: {
storniert: true
}
})
throw new APIError({
code: "NOT_FOUND",
message: "Rechnung konnte nicht gefunden werden aber Ausweis wurde storniert."
})
}
const rechnung = response.rechnung;
await adapter.update({
where: {
uid: input.uid_ausweis
},
data: {
storniert: true,
rechnung: {
update: {
status: "canceled",
storniert_am: new Date()
}
}
},
select: {
rechnung: {
select: {
betrag: true,
bezahlmethode: true,
status: true
}
}
}
})
if (rechnung.betrag > 0 && rechnung.bezahlmethode !== "rechnung" && rechnung.status === "paid" && rechnung.transaktions_referenz) {
const refund = await mollieClient.paymentRefunds.create({
description: "Rückerstattung IBCornelsen",
paymentId: rechnung.transaktions_referenz,
amount: {
currency: "EUR",
value: rechnung.betrag.toFixed(2)
},
metadata: {
rechnung_uid: rechnung.uid
},
testmode: true
})
}
},
})

View File

@@ -0,0 +1,87 @@
import { authorizationMiddleware } from "#lib/middleware/authorization.js";
import { prisma, UnterlageSchema } from "@ibcornelsen/database/server";
import { APIError, defineApiRoute } from "astro-typesafe-api/server";
import { z } from "zod";
import { UUidWithPrefix } from "#components/Ausweis/types.js";
export const PUT = defineApiRoute({
input: z.array(UUidWithPrefix),
output: z.void(),
middleware: authorizationMiddleware,
async fetch(input, ctx, user) {
const aufnahme = await prisma.aufnahme.findUnique({
where: {
uid: ctx.params.uid
}
})
if (!aufnahme) {
throw new APIError({
code: "NOT_FOUND",
message: "Aufnahme existiert nicht oder gehört einem anderen Benutzer."
})
}
prisma.$transaction(async tx => {
for (const uid of input) {
const img = await tx.unterlage.update({
where: {
uid,
aufnahme_id: null
},
data: {
aufnahme_id: aufnahme.id
},
select: {
uid: true
}
})
if (!img) {
throw new APIError({
code: "NOT_FOUND",
message: "Unterlage existiert nicht oder gehört bereits zu einer anderen Aufnahme."
})
}
}
})
},
})
export const GET = defineApiRoute({
middleware: authorizationMiddleware,
output: z.array(UnterlageSchema.pick({
kategorie: true,
uid: true
})),
async fetch(input, ctx, user) {
const { uid } = ctx.params;
const aufnahme = await prisma.aufnahme.findUnique({
where: {
uid,
benutzer_id: user.id
},
select: {
benutzer_id: true,
unterlagen: {
select: {
kategorie: true,
uid: true,
mime: true,
name: true
}
}
}
})
if (!aufnahme) {
throw new APIError({
code: "NOT_FOUND",
message: "Aufnahme existiert nicht oder gehört einem anderen Benutzer."
})
}
return aufnahme.unterlagen
},
})

View File

@@ -0,0 +1,46 @@
import { AufnahmeClient, ObjektClient, UploadedGebaeudeBild, UUidWithPrefix, VerbrauchsausweisWohnenClient } from "#components/Ausweis/types.js";
import { filterAusweise } from "#lib/filters.js";
import { omit } from "#lib/helpers.js";
import { authorizationHeaders, authorizationMiddleware } from "#lib/middleware/authorization.js";
import { Enums, prisma, VerbrauchsausweisWohnenSchema } from "@ibcornelsen/database/server";
import { defineApiRoute } from "astro-typesafe-api/server";
import { z } from "zod";
export const GET = defineApiRoute({
middleware: authorizationMiddleware,
headers: authorizationHeaders,
input: z.object({
filters: filterAusweise.optional(),
limit: z.number().optional(),
skip: z.number().optional()
}),
async fetch(input, context, user) {
const ausweise = await prisma.verbrauchsausweisWohnen.findMany({
where: {
...input.filters,
benutzer: {
uid: user.uid
},
},
include: {
aufnahme: {
include: {
objekt: true,
bilder: true,
events: true
}
}
},
skip: input.skip || 0,
take: Math.min(input.limit || 50, 50)
})
return ausweise.map(ausweis => ({
ausweis: omit(ausweis, ["aufnahme"]) as VerbrauchsausweisWohnenClient,
aufnahme: omit(omit(ausweis.aufnahme, ["events"]), ["objekt"]) as AufnahmeClient,
objekt: omit(ausweis.aufnahme.objekt, ["bilder"]) as ObjektClient,
bilder: ausweis.aufnahme.bilder as unknown as UploadedGebaeudeBild[],
events: ausweis.aufnahme.events
}))
},
})

View File

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

View File

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

View File

@@ -10,6 +10,7 @@ export const PATCH = defineApiRoute({
uid: true,
id: true,
benutzer_id: true,
geg_einpreisung_id: true,
aufnahme_id: true,
}),
output: z.void(),

View File

@@ -16,7 +16,8 @@ export const PUT = defineApiRoute({
id: true,
benutzer_id: true,
uid: true,
aufnahme_id: true
aufnahme_id: true,
geg_einpreisung_id: true
}),
uid_aufnahme: UUidWithPrefix
}),
@@ -80,8 +81,8 @@ export const PUT = defineApiRoute({
export const GET = defineApiRoute({
meta: {
description: "Gibt ein spezifisches Gebäude des Benutzers zurück.",
tags: ["Gebäude"],
description: "Gibt eine spezifische GEG Nachweis Anfrage des Benutzers zurück.",
tags: ["GEG Nachweis"],
headers: {
Authorization: {
description: "Ein gültiger Authentifizierungstoken",
@@ -95,11 +96,12 @@ export const GET = defineApiRoute({
},
},
},
headers: authorizationHeaders,
middleware: authorizationMiddleware,
async fetch(input, context, user) {
const { uid } = context.params;
const ausweis = await prisma.verbrauchsausweisWohnen.findUnique({
const nachweis = await prisma.gEGNachweisGewerbe.findUnique({
where: {
uid,
},
@@ -126,16 +128,16 @@ export const GET = defineApiRoute({
});
if (
!ausweis ||
(ausweis.benutzer_id !== null && ausweis.benutzer_id !== user.id)
!nachweis ||
(nachweis.benutzer_id !== null && nachweis.benutzer_id !== user.id)
) {
// Falls wir den Ausweis nicht finden können, werfen wir einen Fehler
throw new APIError({
code: "NOT_FOUND",
message: "Ausweis konnte nicht gefunden werden.",
message: "GEG Nachweis konnte nicht gefunden werden.",
});
}
return ausweis;
return nachweis;
},
});

View File

@@ -1,9 +1,67 @@
import { defineApiRoute } from "astro-typesafe-api/server";
// TODO
import { getAusweisartFromUUID, UUidWithPrefix } from "#components/Ausweis/types.js";
import { omit } from "#lib/helpers.js";
import { authorizationHeaders, authorizationMiddleware } from "#lib/middleware/authorization.js";
import { Enums, GEGEinpreisungSchema, prisma } from "@ibcornelsen/database/server";
import { APIError, defineApiRoute } from "astro-typesafe-api/server";
import { z } from "zod";
export const PUT = defineApiRoute({
async fetch(input, context, transfer) {
input: GEGEinpreisungSchema.omit({
benutzer_id: true,
id: true,
uid: true,
status: true,
}).merge(z.object({
nachweis_uid: UUidWithPrefix
})),
headers: authorizationHeaders,
middleware: authorizationMiddleware,
async fetch(input, context, user) {
const ausweisart = getAusweisartFromUUID(input.nachweis_uid);
let einpreisung;
if (ausweisart === Enums.Ausweisart.GEGNachweisWohnen) {
const nachweis = await prisma.gEGNachweisWohnen.findUnique({
where: {
uid: input.nachweis_uid
}
})
if (!nachweis || nachweis.benutzer_id !== user.id) {
throw new APIError({
code: "BAD_REQUEST",
message: "Ausweis existiert nicht oder gehört einem anderen Benutzer."
})
}
einpreisung = await prisma.gEGEinpreisung.create({
data: {
...omit(input, ["nachweis_uid"]),
status: Enums.Einpreisungsstatus.open,
benutzer: {
connect: {
id: user.id
}
},
geg_nachweis_wohnen: {
connect: {
uid: input.nachweis_uid
}
}
}
})
} else {
throw new APIError({
"code": "BAD_REQUEST",
"message": `Ausweisart wird nicht unterstützt: ${ausweisart}`
})
}
return {
uid: einpreisung.uid
}
},
})

View File

@@ -86,6 +86,7 @@ export const PUT = defineApiRoute({
});
}
// TODO
// Wir erstellen eine neue Rechnung in unserer Datenbank.
const rechnung = await prisma.rechnung.create({
data: {
@@ -93,7 +94,11 @@ export const PUT = defineApiRoute({
betrag,
bezahlmethode: bezahlmethode,
status: Enums.Rechnungsstatus.open,
aufnahme_id: ausweis.aufnahme_id,
verbrauchsausweis_wohnen: {
connect: {
uid: ausweis_uid
}
},
services,
ausweistyp
},

View File

@@ -16,7 +16,6 @@ export const PUT = defineApiRoute({
output: z.object({
uid: z.string({ description: "Die UID der Unterlage." })
}),
middleware: authorizationMiddleware,
async fetch({ data, name, kategorie, mime }, ctx, user) {
if (mime !== "application/pdf" && mime !== "image/png" && mime !== "image/jpeg") {
throw new APIError({
@@ -25,37 +24,20 @@ export const PUT = defineApiRoute({
})
}
let aufnahme = await prisma.aufnahme.findUnique({
where: {
uid: ctx.params.uid,
benutzer_id: user.id
},
});
if (!aufnahme) {
throw new APIError({
code: "NOT_FOUND",
message: "Objekt nicht gefunden oder gehört einem anderen Benutzer.",
});
}
const buffer = Buffer.from(data, "base64");
const unterlage = await prisma.unterlage.create({
data: {
kategorie: kategorie,
aufnahme: {
connect: {
id: aufnahme.id,
},
},
mime,
name
},
select: {
uid: true,
},
});
const filePath = fileURLToPath(new URL(`../../../../../persistent/unterlagen/${unterlage.uid}`, import.meta.url));
const filePath = fileURLToPath(new URL(`../../../persistent/unterlagen/${unterlage.uid}`, import.meta.url));
try {
writeFileSync(filePath, buffer)
@@ -68,7 +50,7 @@ export const PUT = defineApiRoute({
})
// Und geben einen Fehler zurück
throw new APIError({
code: "INTERNAL_SERVER_ERROR",
code: "UNPROCESSABLE_CONTENT",
message: "Unterlage konnte nicht gespeichert werden.",
});
}

View File

@@ -9,6 +9,9 @@ import { createCaller } from "src/astro-typesafe-api-caller";
const caller = createCaller(Astro)
const params = Astro.params;
const page = Number(params.page)
let user: BenutzerClient;
try {
@@ -34,14 +37,21 @@ const ausweise = await prisma.verbrauchsausweisWohnen.findMany({
include: {
aufnahme: {
include: {
objekt: {
include: {
bilder: true,
}
},
objekt: true,
bilder: true,
events: true
}
}
},
skip: (page - 1) * 10,
take: 10
})
const totalPages = await prisma.verbrauchsausweisWohnen.count({
where: {
benutzer: {
uid: user.uid
}
}
})
@@ -55,5 +65,5 @@ const reformedAusweise = ausweise.map(ausweis => ({
---
<UserLayout title="Ausweise Prüfen" {user}>
<DashboardAusweisePruefenModule ausweise={reformedAusweise} client:load></DashboardAusweisePruefenModule>
<DashboardAusweisePruefenModule totalPages={Math.ceil(totalPages / 10)} page={page} ausweise={reformedAusweise} client:load></DashboardAusweisePruefenModule>
</UserLayout>

View File

@@ -0,0 +1,3 @@
---
return Astro.redirect("/dashboard/admin/ausweise-pruefen/1")
---

View File

@@ -2,11 +2,11 @@
import { createCaller } from "../../../astro-typesafe-api-caller.js";
import { validateAccessTokenServer } from "#server/lib/validateAccessToken";
import DashboardModule from "#modules/Dashboard/DashboardModule.svelte";
import { API_ACCESS_TOKEN_COOKIE_NAME } from "#lib/constants";
import { API_ACCESS_TOKEN_COOKIE_NAME } from "#lib/constants.js";
import Layout from "#layouts/Layout.astro";
import { prisma } from "@ibcornelsen/database/server";
import DashboardObjektModule from "#modules/Dashboard/DashboardObjektModule.svelte";
import UserLayout from "#layouts/DashboardLayout.astro";
import DashboardAufnahmeModule from "#modules/Dashboard/DashboardAufnahmeModule.svelte";
const { uid } = Astro.params;
@@ -28,7 +28,7 @@ if (!user) {
return Astro.redirect("/auth/login")
}
const objekt = await prisma.objekt.findUnique({
const aufnahme = await prisma.aufnahme.findUnique({
where: {
benutzer: {
uid: user.uid
@@ -36,19 +36,24 @@ const objekt = await prisma.objekt.findUnique({
uid
},
include: {
objekt: true,
bilder: true,
unterlagen: true,
aufnahmen: {
include: {
bedarfsausweis_wohnen: true,
verbrauchsausweis_gewerbe: true,
verbrauchsausweis_wohnen: true
}
}
bedarfsausweise_wohnen: true,
verbrauchsausweise_gewerbe: true,
verbrauchsausweise_wohnen: true,
bedarfsausweise_gewerbe: true,
geg_nachweise_gewerbe: true,
geg_nachweise_wohnen: true,
events: true
}
})
if (!aufnahme) {
return Astro.redirect("/dashboard")
}
---
<UserLayout title="Dashboard" {user}>
<DashboardObjektModule {user} {objekt} client:only/>
<DashboardAufnahmeModule {user} {aufnahme} objekt={aufnahme.objekt} client:only/>
</UserLayout>

View File

@@ -1,20 +1,17 @@
---
import UserLayout from "#layouts/DashboardLayout.astro";
import { createCaller } from "#lib/caller";
import { getCurrentUser } from "#lib/server/user";
import DashboardEinstellungenModule from "#modules/Dashboard/DashboardEinstellungenModule.svelte";
import { validateAccessTokenServer } from "#server/lib/validateAccessToken";
const valid = await validateAccessTokenServer(Astro);
const user = getCurrentUser(Astro)
if (!valid) {
if (!user) {
return Astro.redirect("/auth/login", 302);
}
const caller = createCaller(Astro);
const benutzer = await caller.v1.benutzer.self();
---
<UserLayout title="Einstellungen">
<DashboardEinstellungenModule benutzer={benutzer} client:load />
<UserLayout title="Einstellungen" {user}>
<DashboardEinstellungenModule benutzer={user} client:load />
</UserLayout>

View File

@@ -36,9 +36,9 @@ const objekte = await prisma.objekt.findMany({
include: {
bilder: true,
unterlagen: true,
bedarfsausweis_wohnen: true,
verbrauchsausweis_gewerbe: true,
verbrauchsausweis_wohnen: true
bedarfsausweise_wohnen: true,
verbrauchsausweise_gewerbe: true,
verbrauchsausweise_wohnen: true
}
}
}

View File

@@ -0,0 +1,30 @@
---
import Layout from "#layouts/Layout.astro";
import { getCurrentUser } from "#lib/server/user";
import { prisma } from "@ibcornelsen/database/server";
const uidEinpreisung = Astro.url.searchParams.get("e");
const uidAusweis = Astro.url.searchParams.get("a");
const user = await getCurrentUser(Astro)
if (!uidEinpreisung || !uidAusweis || !user) {
return Astro.redirect("/")
}
const einpreisung = await prisma.gEGEinpreisung.findUnique({
where: {
uid: uidEinpreisung,
benutzer: {
uid: user.uid
}
}
})
if (!einpreisung) {
return Astro.redirect("/")
}
---
<Layout title="GEG Anforderung erfolgreich">
<h1>GEG Anforderung erfolgreich</h1>
</Layout>

View File

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

View File

2000
src/testing/ausweise.csv Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,15 +1,11 @@
import moment from "moment";
import ausweise from "./ausweise.json" assert { type: "json" };
import users from "./users.json" assert { type: "json" };
import newUserIdMap from "./new-user-id-map.json" assert { type: "json" };
import processed from "./processed.json" assert { type: "json" };
import { Benutzer, Enums, prisma } from "@ibcornelsen/database/server";
import * as fs from "fs";
import { fileURLToPath } from "url";
import { hashPassword } from "#lib/password.js";
const datasets = ausweise[2].data;
const datasetUsers = users[2].data;
import Papa from "papaparse"
const saveUserMap = () => {
fs.writeFileSync(
@@ -18,285 +14,419 @@ const saveUserMap = () => {
);
};
if (!datasets || !datasetUsers) {
throw new Error("Invalid dataset");
const path = fileURLToPath(new URL("./ausweise.csv", import.meta.url));
if (!fs.existsSync(path)) {
throw new Error(`${path} existiert nicht.`)
}
let i = 0;
for (const user of datasetUsers) {
if (user.id in newUserIdMap) {
continue;
}
const file = fs.createReadStream(path, "utf8");
Papa.parse(file, {
header: true,
step: async function(results) {
const dataset = results.data as any;
try {
const benutzer = await prisma.benutzer.create({
data: {
email: user.email,
passwort: user.password,
adresse: user.adresse,
anrede: user.anrede,
name: user.name,
vorname: user.vorname,
ort: user.ort,
plz: user.plz,
rolle:
parseInt(user.level) == 4
? Enums.BenutzerRolle.ADMIN
: Enums.BenutzerRolle.USER,
firma: user.firma,
lex_office_id: user.lex_office_id,
telefon: user.phone,
},
select: {
uid: true,
},
});
if (dataset.id in processed) {
return;
}
newUserIdMap[user.id] = benutzer.uid;
} catch (e) {
saveUserMap();
throw e;
}
const user_id = dataset.user_id;
// Alle 50 werden gespeichert.
if (i % 50 == 0) {
saveUserMap();
}
let user: Benutzer;
if (!newUserIdMap[user_id]) {
console.log(`Missing user ${user_id}`);
i++;
}
const saveProcessed = () => {
fs.writeFileSync(
fileURLToPath(new URL("./processed.json", import.meta.url)),
JSON.stringify(processed)
);
};
for (const dataset of datasets) {
if (dataset.id in processed) {
continue;
}
let user: Benutzer;
if (!newUserIdMap[dataset.user_id]) {
user = await prisma.benutzer.create({
data: {
email: dataset.email,
passwort: hashPassword(Math.random().toString(36).slice(-8)),
adresse: dataset.strasse,
ort: dataset.ort,
plz: dataset.plz,
name: dataset.name,
vorname: dataset.vorname,
rolle: Enums.BenutzerRolle.USER,
telefon: dataset.telefonnummer,
},
});
newUserIdMap[dataset.user_id] = user.uid;
saveUserMap();
} else {
user = await prisma.benutzer.findUnique({
where: {
uid: newUserIdMap[dataset.user_id],
},
});
}
if (!user) {
saveProcessed();
throw new Error("Benutzer ist nicht vorhanden.");
}
await prisma.$transaction(async (tx) => {
const objekt = await prisma.objekt.create({
data: {
adresse: dataset.strasse,
erstellungsdatum: moment(dataset.erstellungsdatum).toDate(),
ort: dataset.objekt_ort,
plz: dataset.objekt_plz,
benutzer: {
connect: {
uid: user.uid,
},
},
},
});
const aufnahme = await prisma.aufnahme.create({
data: {
alternative_heizung: dataset.alheizung == "1",
alternative_kuehlung: dataset.alkuehlung == "1",
alternative_lueftung: dataset.allueftung == "1",
alternative_warmwasser: dataset.alwarmwasser == "1",
aussenwand_gedaemmt: dataset.aussenwand_gedaemmt == "1",
aussenwand_min_12cm_gedaemmt:
dataset.aussenwand_min_12cm_gedaemmt == "1",
ausstellungsdatum: moment(dataset.erstellungsdatum).toDate(),
baujahr_gebaeude: parseInt(dataset.baujahr_gebaeude) ? [parseInt(dataset.baujahr_gebaeude)] : [],
baujahr_heizung: parseInt(dataset.baujahr_anlage) ? [parseInt(dataset.baujahr_anlage)] : [],
baujahr_klima: parseInt(dataset.baujahr_klimaanlage) ? [parseInt(dataset.baujahr_klimaanlage)] : [],
benutzer: {
connect: {
uid: user.uid,
},
},
objekt: {
connect: {
id: objekt.id,
},
},
brennstoff_1: dataset.energietraeger_1,
brennstoff_2: dataset.energietraeger_2,
dachgeschoss:
dataset.dachgeschoss == "Beheizt"
? Enums.Heizungsstatus.BEHEIZT
: dataset.dachgeschoss == "Nicht Vorhanden"
? Enums.Heizungsstatus.NICHT_VORHANDEN
: Enums.Heizungsstatus.UNBEHEIZT,
brennwert_kessel: dataset.brennwert_kessel == "1",
dachgeschoss_gedaemmt: dataset.dachgeschoss_gedaemmt == "1",
dachgeschoss_min_12cm_gedaemmt:
dataset.dachgeschoss_min_12cm_gedaemmt == "1",
doppel_verglasung: dataset.doppel_verglasung == "1",
dreifach_verglasung: dataset.dreifach_verglasung == "1",
durchlauf_erhitzer: dataset.durchlauf_erhitzer == "1",
einfach_verglasung: dataset.einfach_verglasung == "1",
einzelofen: dataset.einzelofen == "1",
einheiten: parseInt(dataset.anzahl_einheiten),
fenster_dicht: dataset.fenster_dicht == "1",
fenster_teilweise_undicht:
dataset.fenster_teilweise_undicht == "1",
flaeche: parseFloat(dataset.wohnflaeche),
nutzflaeche: parseFloat(dataset.nutzflaeche),
gebaeudeteil: dataset.objekt_gebaeudeteil,
gebaeudetyp: dataset.objekt_typ,
heizungsrohre_gedaemmt: dataset.heizungsrohre_gedaemmt == "1",
isolier_verglasung: dataset.isolier_verglasung == "1",
keller:
dataset.keller_beheizt == "Beheizt"
? Enums.Heizungsstatus.BEHEIZT
: dataset.dachgeschoss == "Nicht Vorhanden"
? Enums.Heizungsstatus.NICHT_VORHANDEN
: Enums.Heizungsstatus.UNBEHEIZT,
keller_decke_gedaemmt: dataset.keller_decke_gedaemmt == "1",
keller_wand_gedaemmt: dataset.keller_wand_gedaemmt == "1",
kuehlung: dataset.wird_gekuehlt,
erstellungsdatum: moment(dataset.erstellungsdatum).toDate(),
rolllaeden_kaesten_gedaemmt:
dataset.rolllaeden_kaesten_gedaemmt == "1",
leerstand: parseInt(dataset.leerstand),
lueftung:
dataset.lueftungskonzept == "Fensterlüftung"
? Enums.Lueftungskonzept.Fensterlueftung
: dataset.lueftungskonzept ==
"Lüftungsanlage mit Wärmerückgewinnung"
? Enums.Lueftungskonzept
.LueftungsanlageMitWaermerueckgewinnung
: dataset.lueftungskonzept ==
"Lüftungsanlage ohne Wärmerückgewinnung"
? Enums.Lueftungskonzept
.LueftungsanlageOhneWaermerueckgewinnung
: Enums.Lueftungskonzept.Schachtlueftung,
niedertemperatur_kessel: dataset.niedertemperatur_kessel == "1",
oberste_geschossdecke_gedaemmt:
dataset.oberste_geschossdecke_gedaemmt == "1",
oberste_geschossdecke_min_12cm_gedaemmt:
dataset.oberste_geschossdecke_min_12cm_gedaemmt == "1",
photovoltaik: dataset.photovoltaik == "1",
raum_temperatur_regler: dataset.raum_temperatur_regler == "1",
saniert: dataset.objekt_saniert == "1",
solarsystem_warmwasser: dataset.solarsystem_warmwasser == "1",
standard_kessel: dataset.standard_kessel == "1",
tueren_dicht: dataset.tueren_dicht == "1",
tueren_undicht: dataset.tueren_undicht == "1",
waermepumpe: dataset.waermepumpe == "1",
warmwasser_rohre_gedaemmt:
dataset.warmwasser_rohre_gedaemmt == "1",
zentralheizung: dataset.zentralheizung == "1",
zirkulation: dataset.zirkulation == "1",
boxpruefung: dataset.boxpruefung == "1",
},
});
if (dataset.ausweisart === "VA") {
const ausweis = await prisma.verbrauchsausweisWohnen.create({
user = await prisma.benutzer.create({
data: {
alternative_heizung: dataset.alheizung == "1",
alternative_kuehlung: dataset.alkuehlung == "1",
alternative_lueftung: dataset.allueftung == "1",
alternative_warmwasser: dataset.alwarmwasser == "1",
anteil_warmwasser_1: parseFloat(
dataset.anteil_warmwasser_1
),
anteil_warmwasser_2: parseFloat(
dataset.anteil_warmwasser_2
),
aufnahme: {
connect: {
id: aufnahme.id,
},
},
ausgestellt: dataset.erledigt == "2",
ausstellgrund:
dataset.ausstellgrund == "Vermietung"
? Enums.Ausstellgrund.Vermietung
: dataset.ausstellgrund == "Verkauf"
? Enums.Ausstellgrund.Verkauf
: dataset.ausstellgrund == "Modernisierung"
? Enums.Ausstellgrund.Modernisierung
: dataset.ausstellgrund == "Neubau"
? Enums.Ausstellgrund.Neubau
: Enums.Ausstellgrund.Sonstiges,
bestellt: dataset.erledigt == "0",
einheit_1: dataset.energietraeger_einheit_heizquelle_1,
einheit_2: dataset.energietraeger_einheit_heizquelle_2,
storniert: dataset.erledigt == "4",
email: dataset.email || "nutzer@nutzeremail.com",
passwort: hashPassword(Math.random().toString(36).slice(-8)),
adresse: dataset.strasse,
ort: dataset.ort,
plz: dataset.plz,
name: dataset.name,
vorname: dataset.vorname,
rolle: Enums.BenutzerRolle.USER,
telefon: dataset.telefonnummer,
},
});
newUserIdMap[user_id] = user.uid;
saveUserMap();
} else {
user = await prisma.benutzer.findUnique({
where: {
uid: newUserIdMap[user_id],
},
});
}
if (!user) {
saveProcessed();
throw new Error("Benutzer ist nicht vorhanden.");
}
await prisma.$transaction(async (tx) => {
const objekt = await tx.objekt.create({
data: {
adresse: dataset.strasse,
erstellungsdatum: moment(dataset.erstellungsdatum).toDate(),
ort: dataset.objekt_ort,
plz: dataset.objekt_plz,
benutzer: {
connect: {
uid: user.uid,
},
},
kontrolldatei_angefragt: dataset.kontrolldatei == "1",
registriernummer: dataset.regnummer,
startdatum: moment(
`${dataset.energieverbrauch_zeitraum_jahr}-${dataset.energieverbrauch_zeitraum_monat.padStart(2, "0")}-01`
).toDate(),
verbrauch_1: parseFloat(
dataset.energieverbrauch_1_heizquelle_1
),
verbrauch_2: parseFloat(
dataset.energieverbrauch_2_heizquelle_1
),
verbrauch_3: parseFloat(
dataset.energieverbrauch_3_heizquelle_1
),
verbrauch_4: parseFloat(
dataset.energieverbrauch_1_heizquelle_2
),
verbrauch_5: parseFloat(
dataset.energieverbrauch_2_heizquelle_2
),
verbrauch_6: parseFloat(
dataset.energieverbrauch_3_heizquelle_2
),
warmwasser_anteil_bekannt:
dataset.warmwasser_anteil_bekannt == "1",
warmwasser_enthalten: dataset.warmwasser_enthalten == "1",
zurueckgestellt: dataset.zurueckGestellt == "1",
zusaetzliche_heizquelle:
dataset.zusaetzliche_heizquelle == "1",
},
});
}
processed[dataset.id] = true;
const aufnahme = await tx.aufnahme.create({
data: {
alternative_heizung: dataset.alheizung == "1",
alternative_kuehlung: dataset.alkuehlung == "1",
alternative_lueftung: dataset.allueftung == "1",
alternative_warmwasser: dataset.alwarmwasser == "1",
aussenwand_gedaemmt: dataset.aussenwand_gedaemmt == "1",
aussenwand_min_12cm_gedaemmt:
dataset.aussenwand_min_12cm_gedaemmt == "1",
ausstellungsdatum: moment(dataset.erstellungsdatum).toDate(),
baujahr_gebaeude: parseInt(dataset.baujahr_gebaeude) ? [parseInt(dataset.baujahr_gebaeude)] : [],
baujahr_heizung: parseInt(dataset.baujahr_anlage) ? [parseInt(dataset.baujahr_anlage)] : [],
baujahr_klima: parseInt(dataset.baujahr_klimaanlage) ? [parseInt(dataset.baujahr_klimaanlage)] : [],
benutzer: {
connect: {
uid: user.uid,
},
},
objekt: {
connect: {
id: objekt.id,
},
},
brennstoff_1: dataset.energietraeger_1,
brennstoff_2: dataset.energietraeger_2,
dachgeschoss:
dataset.dachgeschoss == "Beheizt"
? Enums.Heizungsstatus.BEHEIZT
: dataset.dachgeschoss == "Nicht Vorhanden"
? Enums.Heizungsstatus.NICHT_VORHANDEN
: Enums.Heizungsstatus.UNBEHEIZT,
brennwert_kessel: dataset.brennwert_kessel == "1",
dachgeschoss_gedaemmt: dataset.dachgeschoss_gedaemmt == "1",
dachgeschoss_min_12cm_gedaemmt:
dataset.dachgeschoss_min_12cm_gedaemmt == "1",
doppel_verglasung: dataset.doppel_verglasung == "1",
dreifach_verglasung: dataset.dreifach_verglasung == "1",
durchlauf_erhitzer: dataset.durchlauf_erhitzer == "1",
einfach_verglasung: dataset.einfach_verglasung == "1",
einzelofen: dataset.einzelofen == "1",
einheiten: parseInt(dataset.anzahl_einheiten),
fenster_dicht: dataset.fenster_dicht == "1",
fenster_teilweise_undicht:
dataset.fenster_teilweise_undicht == "1",
flaeche: parseFloat(dataset.wohnflaeche),
nutzflaeche: parseFloat(dataset.nutzflaeche),
gebaeudeteil: dataset.objekt_gebaeudeteil,
gebaeudetyp: dataset.objekt_typ,
heizungsrohre_gedaemmt: dataset.heizungsrohre_gedaemmt == "1",
isolier_verglasung: dataset.isolier_verglasung == "1",
keller:
dataset.keller_beheizt == "Beheizt"
? Enums.Heizungsstatus.BEHEIZT
: dataset.dachgeschoss == "Nicht Vorhanden"
? Enums.Heizungsstatus.NICHT_VORHANDEN
: Enums.Heizungsstatus.UNBEHEIZT,
keller_decke_gedaemmt: dataset.keller_decke_gedaemmt == "1",
keller_wand_gedaemmt: dataset.keller_wand_gedaemmt == "1",
kuehlung: dataset.wird_gekuehlt,
erstellungsdatum: moment(dataset.erstellungsdatum).toDate(),
rolllaeden_kaesten_gedaemmt:
dataset.rolllaeden_kaesten_gedaemmt == "1",
leerstand: parseInt(dataset.leerstand),
lueftung:
dataset.lueftungskonzept == "Fensterlüftung"
? Enums.Lueftungskonzept.Fensterlueftung
: dataset.lueftungskonzept ==
"Lüftungsanlage mit Wärmerückgewinnung"
? Enums.Lueftungskonzept
.LueftungsanlageMitWaermerueckgewinnung
: dataset.lueftungskonzept ==
"Lüftungsanlage ohne Wärmerückgewinnung"
? Enums.Lueftungskonzept
.LueftungsanlageOhneWaermerueckgewinnung
: Enums.Lueftungskonzept.Schachtlueftung,
niedertemperatur_kessel: dataset.niedertemperatur_kessel == "1",
oberste_geschossdecke_gedaemmt:
dataset.oberste_geschossdecke_gedaemmt == "1",
oberste_geschossdecke_min_12cm_gedaemmt:
dataset.oberste_geschossdecke_min_12cm_gedaemmt == "1",
photovoltaik: dataset.photovoltaik == "1",
raum_temperatur_regler: dataset.raum_temperatur_regler == "1",
saniert: dataset.objekt_saniert == "1",
solarsystem_warmwasser: dataset.solarsystem_warmwasser == "1",
standard_kessel: dataset.standard_kessel == "1",
tueren_dicht: dataset.tueren_dicht == "1",
tueren_undicht: dataset.tueren_undicht == "1",
waermepumpe: dataset.waermepumpe == "1",
warmwasser_rohre_gedaemmt:
dataset.warmwasser_rohre_gedaemmt == "1",
zentralheizung: dataset.zentralheizung == "1",
zirkulation: dataset.zirkulation == "1",
boxpruefung: dataset.boxpruefung == "1",
},
});
if (i % 25 == 0) {
if (dataset.ausweisart === "VA") {
const ausweis = await tx.verbrauchsausweisWohnen.create({
data: {
alternative_heizung: dataset.alheizung == "1",
alternative_kuehlung: dataset.alkuehlung == "1",
alternative_lueftung: dataset.allueftung == "1",
alternative_warmwasser: dataset.alwarmwasser == "1",
anteil_warmwasser_1: parseFloat(
dataset.anteil_warmwasser_1
),
anteil_warmwasser_2: parseFloat(
dataset.anteil_warmwasser_2
),
aufnahme: {
connect: {
id: aufnahme.id,
},
},
ausgestellt: dataset.erledigt == "2",
ausstellgrund:
dataset.ausstellgrund == "Vermietung"
? Enums.Ausstellgrund.Vermietung
: dataset.ausstellgrund == "Verkauf"
? Enums.Ausstellgrund.Verkauf
: dataset.ausstellgrund == "Modernisierung"
? Enums.Ausstellgrund.Modernisierung
: dataset.ausstellgrund == "Neubau"
? Enums.Ausstellgrund.Neubau
: Enums.Ausstellgrund.Sonstiges,
bestellt: dataset.erledigt == "0",
einheit_1: dataset.energietraeger_einheit_heizquelle_1,
einheit_2: dataset.energietraeger_einheit_heizquelle_2,
storniert: dataset.erledigt == "4",
benutzer: {
connect: {
uid: user.uid,
},
},
kontrolldatei_angefragt: dataset.kontrolldatei == "1",
registriernummer: dataset.regnummer,
startdatum: moment(
`${dataset.energieverbrauch_zeitraum_jahr}-${dataset.energieverbrauch_zeitraum_monat.padStart(2, "0")}-01`
).toDate(),
verbrauch_1: parseFloat(
dataset.energieverbrauch_1_heizquelle_1
),
verbrauch_2: parseFloat(
dataset.energieverbrauch_2_heizquelle_1
),
verbrauch_3: parseFloat(
dataset.energieverbrauch_3_heizquelle_1
),
verbrauch_4: parseFloat(
dataset.energieverbrauch_1_heizquelle_2
),
verbrauch_5: parseFloat(
dataset.energieverbrauch_2_heizquelle_2
),
verbrauch_6: parseFloat(
dataset.energieverbrauch_3_heizquelle_2
),
warmwasser_anteil_bekannt:
dataset.warmwasser_anteil_bekannt == "1",
warmwasser_enthalten: dataset.warmwasser_enthalten == "1",
zurueckgestellt: dataset.zurueckGestellt == "1",
zusaetzliche_heizquelle:
dataset.zusaetzliche_heizquelle == "1",
},
});
} else if (dataset.ausweisart === "VANW") {
const ausweis = await tx.verbrauchsausweisGewerbe.create({
data: {
alternative_heizung: dataset.alheizung == "1",
alternative_kuehlung: dataset.alkuehlung == "1",
alternative_lueftung: dataset.allueftung == "1",
alternative_warmwasser: dataset.alwarmwasser == "1",
anteil_warmwasser_1: parseFloat(
dataset.anteil_warmwasser_1
),
anteil_warmwasser_2: parseFloat(
dataset.anteil_warmwasser_2
),
aufnahme: {
connect: {
id: aufnahme.id,
},
},
ausgestellt: dataset.erledigt == "2",
ausstellgrund:
dataset.ausstellgrund == "Vermietung"
? Enums.Ausstellgrund.Vermietung
: dataset.ausstellgrund == "Verkauf"
? Enums.Ausstellgrund.Verkauf
: dataset.ausstellgrund == "Modernisierung"
? Enums.Ausstellgrund.Modernisierung
: dataset.ausstellgrund == "Neubau"
? Enums.Ausstellgrund.Neubau
: Enums.Ausstellgrund.Sonstiges,
bestellt: dataset.erledigt == "0",
einheit_1: dataset.energietraeger_einheit_heizquelle_1,
einheit_2: dataset.energietraeger_einheit_heizquelle_2,
storniert: dataset.erledigt == "4",
benutzer: {
connect: {
uid: user.uid,
},
},
kontrolldatei_angefragt: dataset.kontrolldatei == "1",
registriernummer: dataset.regnummer,
startdatum: moment(
`${dataset.energieverbrauch_zeitraum_jahr}-${dataset.energieverbrauch_zeitraum_monat.padStart(2, "0")}-01`
).toDate(),
verbrauch_1: parseFloat(
dataset.energieverbrauch_1_heizquelle_1
),
verbrauch_2: parseFloat(
dataset.energieverbrauch_2_heizquelle_1
),
verbrauch_3: parseFloat(
dataset.energieverbrauch_3_heizquelle_1
),
verbrauch_4: parseFloat(
dataset.energieverbrauch_1_heizquelle_2
),
verbrauch_5: parseFloat(
dataset.energieverbrauch_2_heizquelle_2
),
verbrauch_6: parseFloat(
dataset.energieverbrauch_3_heizquelle_2
),
anteil_kuehlung_1: parseFloat(dataset.anteil_kuehlung_1),
anteil_kuehlung_2: parseFloat(dataset.anteil_kuehlung_2),
brennstoff_1: dataset.energietraeger_1,
brennstoff_2: dataset.energietraeger_2,
strom_1: parseFloat(dataset.vanw_strom_1),
strom_2: parseFloat(dataset.vanw_strom_2),
strom_3: parseFloat(dataset.vanw_strom_3),
kuehlung_enthalten: dataset.wird_gekuehlt == "1" ? true : false,
stromverbrauch_enthaelt_beleuchtung: dataset.nwbeleuchtung == "1",
stromverbrauch_enthaelt_heizung: dataset.nwheizung == "1",
stromverbrauch_enthaelt_kuehlung: dataset.nwkuehlung == "1",
stromverbrauch_enthaelt_lueftung: dataset.nwlueftung == "1",
stromverbrauch_enthaelt_sonstige: dataset.nwsonstiges,
stromverbrauch_enthaelt_warmwasser: dataset.nwwarmwasser == "1",
warmwasser_enthalten: dataset.warmwasser_enthalten == "1",
zurueckgestellt: dataset.zurueckGestellt == "1",
zusaetzliche_heizquelle:
dataset.zusaetzliche_heizquelle == "1"
},
});
} else if (dataset.ausweisart === "BA") {
const ausweis = await tx.bedarfsausweisWohnen.create({
data: {
alternative_heizung: dataset.alheizung == "1",
alternative_kuehlung: dataset.alkuehlung == "1",
alternative_lueftung: dataset.allueftung == "1",
alternative_warmwasser: dataset.alwarmwasser == "1",
aufnahme: {
connect: {
id: aufnahme.id,
},
},
ausgestellt: dataset.erledigt == "2",
ausstellgrund:
dataset.ausstellgrund == "Vermietung"
? Enums.Ausstellgrund.Vermietung
: dataset.ausstellgrund == "Verkauf"
? Enums.Ausstellgrund.Verkauf
: dataset.ausstellgrund == "Modernisierung"
? Enums.Ausstellgrund.Modernisierung
: dataset.ausstellgrund == "Neubau"
? Enums.Ausstellgrund.Neubau
: Enums.Ausstellgrund.Sonstiges,
bestellt: dataset.erledigt == "0",
storniert: dataset.erledigt == "4",
benutzer: {
connect: {
uid: user.uid,
},
},
kontrolldatei_angefragt: dataset.kontrolldatei == "1",
registriernummer: dataset.regnummer,
zurueckgestellt: dataset.zurueckGestellt == "1",
anteil_zusatzheizung: parseFloat(dataset.anteil_zusatzheizung),
anzahl_gauben: parseInt(dataset.anzahl_gauben),
anzahl_vollgeschosse: parseInt(dataset.anzahl_vollgeschosse),
aussenwand_bauart: dataset.aussenwand_bauart,
aussenwand_daemmung: parseFloat(dataset.aussenwand_daemmung),
aussenwand_flaeche: parseFloat(dataset.aussenwand_flaeche),
aussenwand_u_wert: parseFloat(dataset.aussenwand_u_wert),
aussenwandflaeche_unbeheizt: parseFloat(dataset.aussenwandflaeche_unbeheizt),
boden_bauart: dataset.boden_bauart,
boden_daemmung: parseFloat(dataset.boden_daemmung),
breite_gauben: parseFloat(dataset.breite_gauben),
dach_bauart: dataset.dach_bauart,
dach_daemmung: parseFloat(dataset.dach_daemmung),
dach_u_wert: parseFloat(dataset.dach_u_wert),
// TODO Das machen wir zwar so aber das ist doch scheiße....
dachfenster_art: parseFloat(dataset.dachfenster_art),
dachfenster_flaeche: parseFloat(dataset.dachfenster_flaeche),
dachflaeche: parseFloat(dataset.dachflaeche),
decke_bauart: dataset.decke_bauart,
decke_daemmung: parseFloat(dataset.decke_daemmung),
decke_u_wert: parseFloat(dataset.decke_u_wert),
dicht: dataset.dicht == "1",
fenster_art_1: parseFloat(dataset.fenster_art_1),
fenster_art_2: parseFloat(dataset.fenster_art_2),
deckenflaeche: parseFloat(dataset.deckenflaeche),
fenster_flaeche_1: parseFloat(dataset.fenster_flaeche_1),
fenster_flaeche_2: parseFloat(dataset.fenster_flaeche_2),
fensterflaeche_nw_no: parseFloat(dataset.fensterflaeche_nw_no),
fensterflaeche_so_sw: parseFloat(dataset.fensterflaeche_so_sw),
fussboden_flaeche: parseFloat(dataset.fussboden_flaeche),
fussboden_u_wert: parseFloat(dataset.fussboden_u_wert),
geschosshoehe: parseFloat(dataset.geschosshoehe),
haustuer_art: parseFloat(dataset.haustuer_art),
haustuer_flaeche: parseFloat(dataset.haustuer_flaeche),
heizung_speicherung: dataset.heizung_speicherung,
heizung_verteilung: dataset.heizung_verteilung,
heizung_zentral: dataset.heizung_zentral == "1",
masse_a: parseFloat(dataset.masse_a),
masse_b: parseFloat(dataset.masse_b),
masse_c: parseFloat(dataset.masse_c),
masse_d: parseFloat(dataset.masse_d),
masse_e: parseFloat(dataset.masse_e),
masse_f: parseFloat(dataset.masse_f),
kollektor_flaeche: parseFloat(dataset.kollektor_flaeche),
volumen: parseFloat(dataset.volumen),
waerme_erzeugung_heizung: dataset.waerme_erzeugung_heizung,
warmwasser_erzeugung: dataset.warmwasser_erzeugung,
warmwasser_speicherung: dataset.warmwasser_speicherung,
warmwasser_verteilung: dataset.warmwasser_verteilung
},
});
}
processed[dataset.id as string] = true;
saveProcessed()
}
});
}
}, {
maxWait: 5000,
timeout: 10000
});
},
complete(results, file) {
saveProcessed()
},
});
saveProcessed()
function saveProcessed() {
fs.writeFileSync(
fileURLToPath(new URL("./processed.json", import.meta.url)),
JSON.stringify(processed)
);
};

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,62 @@
import userFile from "./users.json" assert { type: "json" };
import newUserIdMap from "./new-user-id-map.json" assert { type: "json" };
import { Enums, prisma } from "@ibcornelsen/database/server";
import * as fs from "fs";
import { fileURLToPath } from "url";
const users = userFile[2].data;
const saveUserMap = () => {
fs.writeFileSync(
fileURLToPath(new URL("./new-user-id-map.json", import.meta.url)),
JSON.stringify(newUserIdMap)
);
};
if (!users) {
throw new Error("Invalid dataset");
}
let i = 0;
for (const user of users) {
if (user.id in newUserIdMap) {
continue;
}
try {
const benutzer = await prisma.benutzer.create({
data: {
email: user.email,
passwort: user.password,
adresse: user.adresse,
anrede: user.anrede,
name: user.name,
vorname: user.vorname,
ort: user.ort,
plz: user.plz,
rolle:
parseInt(user.level) == 4
? Enums.BenutzerRolle.ADMIN
: Enums.BenutzerRolle.USER,
firma: user.firma,
lex_office_id: user.lex_office_id,
telefon: user.phone,
},
select: {
uid: true,
},
});
newUserIdMap[user.id] = benutzer.uid;
} catch (e) {
saveUserMap();
throw e;
}
// Alle 50 werden gespeichert.
if (i % 50 == 0) {
saveUserMap();
}
i++;
}

File diff suppressed because one or more lines are too long