1 Commits

Author SHA1 Message Date
Moritz Utcke
c0c22453f1 Datenbank 2025-04-29 12:20:21 -03:00
76 changed files with 1287 additions and 1945 deletions

View File

@@ -8,8 +8,6 @@ on:
jobs:
merge:
runs-on: ubuntu-latest
outputs:
reason: ${{ steps.check.outputs.reason }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
@@ -30,15 +28,9 @@ jobs:
id: check
run: |
git fetch origin
COUNT=$(git rev-list --count origin/staging..origin/main)
if [ "$COUNT" -gt 0 ]; then
echo "reason=ok" >> $GITHUB_OUTPUT
if [ $(git rev-list --count origin/staging..origin/main) -gt 0 ]; then
echo "❌ Staging is behind main and requires manual merging."
exit 1
elif [ "$COUNT" -eq 0 ]; then
echo "reason=identical" >> $GITHUB_OUTPUT
echo "✅ Staging and main are identical. Nothing to do."
exit 42
fi
- name: Create PR from staging to main
@@ -61,7 +53,7 @@ jobs:
notify_failure:
needs: merge
if: failure() && needs.merge.outputs.reason != 'identical'
if: failure()
runs-on: ubuntu-latest
steps:
- name: Send Discord notification on failure
@@ -69,4 +61,16 @@ jobs:
curl -H "Content-Type: application/json" \
-X POST \
-d "{\"content\": \"🚨 Auto-Merge fehlgeschlagen! Bitte manuell prüfen: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}\"}" \
${{ secrets.DISCORD_WEBHOOK_URL }}
notify_success:
needs: merge
if: success()
runs-on: ubuntu-latest
steps:
- name: Send Discord notification on success
run: |
curl -H "Content-Type: application/json" \
-X POST \
-d "{\"content\": \"✅ Auto-Merge ausgeführt! Ergebnis jetzt auf [GitHub](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) und [online-energieausweis.org](https://online-energieausweis.org) einsehen.\"}" \
${{ secrets.DISCORD_WEBHOOK_URL }}

View File

@@ -1,18 +0,0 @@
name: Enforce PR Source
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
branches:
- main
jobs:
check-pr-source:
runs-on: ubuntu-latest
steps:
- name: Enforce only staging → main
run: |
if [[ "${{ github.head_ref }}" != "staging" ]]; then
echo "ERROR: Only 'staging' branch may create PRs into 'main'!"
exit 1
fi

View File

@@ -13,8 +13,6 @@ jobs:
steps:
- name: Prevent dev merges
run: |
echo "${{ github.head_ref }}";
echo "${{ github.base_ref }}";
if [[ "${{ github.head_ref }}" == "dev" ]]; then
echo "ERROR: Merging 'dev' into '${{ github.base_ref }}' is forbidden!"
exit 1
@@ -23,8 +21,6 @@ jobs:
- name: Allow only staging into main
if: github.base_ref == 'main'
run: |
echo "${{ github.head_ref }}";
echo "${{ github.base_ref }}";
if [[ "${{ github.head_ref }}" != "staging" ]]; then
echo "ERROR: Only 'staging' branch is allowed to merge into 'main'. Current: '${{ github.head_ref }}'"
exit 1

View File

@@ -10,7 +10,7 @@ PERSISTENT_DIR := $(HOME)/persistent/$(APP_NAME)
BACKUP_FILENAME := $(HOME)/backups/$(shell date +"%Y-%m-%d_%H-%M-%S").sql.gz
online-energieausweis:
NODE_ENV="development" bun run dev --host
bun run dev --host
dev: database online-energieausweis
@@ -30,7 +30,6 @@ run-database: stop-database
docker volume create $(DB_VOLUME)
docker build -t $(DB_CONTAINER_NAME) .
docker run -d --name $(DB_CONTAINER_NAME) \
--restart=always \
-e POSTGRES_USER=$(DB_USER) \
-e POSTGRES_PASSWORD=$(DB_PASSWORD) \
-p $(DB_PORT):5432 \
@@ -59,7 +58,7 @@ all:
bun run dev 2>&1 | tee ~/logs/`date '+%d-%m-%Y_%H:%M:%S'`.log
update-dwd-klimafaktoren-cron:
pm2 start bun --name "update-dwd-klimafaktoren-cron" --no-autorestart --cron "0 12 28 * *" -- src/cronjobs/update-dwd-klimafaktoren.ts
pm2 start bun --name "update-dwd-klimafaktoren-cron" --cron "0 12 28 * *" -- src/cronjobs/update-dwd-klimafaktoren.ts
prod: install-dependencies prisma-studio backup-database-cronjob update-dwd-klimafaktoren-cron
bun run build
@@ -72,4 +71,4 @@ prod: install-dependencies prisma-studio backup-database-cronjob update-dwd-klim
backup-database-cronjob:
- pm2 delete daily-db-backup
pm2 start bash --name "daily-db-backup" --no-autorestart --cron "0 0 * * *" -- backup-database.bash
pm2 start bash --name "daily-db-backup" --cron "0 0 * * *" -- backup-database.bash

View File

@@ -13,17 +13,13 @@ export AWS_RESPONSE_CHECKSUM_VALIDATION=when_required
# Den Key dafür findet man auf https://dcd.ionos.com/latest/?lang=en#/key-management
docker exec -t online-energieausweis-database-1 pg_dump --data-only -U main main | brotli --best > $FILE_NAME
aws s3 cp $FILE_NAME s3://ibc-db-backup/ --profile ionos --endpoint-url https://s3.eu-central-3.ionoscloud.com --storage-class STANDARD
aws s3 cp $FILE_NAME s3://ibc-db-backup/ --profile ionos --endpoint-url https://s3-eu-central-1.ionoscloud.com --storage-class STANDARD
echo "Uploaded $FILE_NAME"
docker exec -t online-energieausweis-database-1 pg_dumpall -c -U main | brotli --best > $FILE_NAME_COMPLETE
<<<<<<< HEAD
aws s3 cp $FILE_NAME_COMPLETE s3://ibc-db-backup/ --profile ionos --endpoint-url https://s3-eu-central-3.ionoscloud.com --storage-class STANDARD
=======
aws s3 cp $FILE_NAME_COMPLETE s3://ibc-db-backup/ --profile ionos --endpoint-url https://s3.eu-central-3.ionoscloud.com --storage-class STANDARD
>>>>>>> dev
aws s3 cp $FILE_NAME_COMPLETE s3://ibc-db-backup/ --profile ionos --endpoint-url https://s3-eu-central-1.ionoscloud.com --storage-class STANDARD
echo "Uploaded $FILE_NAME_COMPLETE"

View File

@@ -1,9 +1,7 @@
version: '3'
name: database
services:
database:
build: ./
restart: always
env_file:
- .env
ports:

View File

@@ -0,0 +1,10 @@
-- CreateTable
CREATE TABLE "Log" (
"id" SERIAL NOT NULL,
"level" TEXT NOT NULL,
"message" TEXT NOT NULL,
"meta" JSONB NOT NULL,
"timestamp" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Log_pkey" PRIMARY KEY ("id")
);

View File

@@ -0,0 +1,64 @@
-- CreateTable
CREATE TABLE "Attachment" (
"id" TEXT NOT NULL,
"name" TEXT,
"kategorie" TEXT,
"mime" TEXT NOT NULL,
CONSTRAINT "Attachment_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Message" (
"id" TEXT NOT NULL,
"content" TEXT NOT NULL,
"sender_id" TEXT NOT NULL,
"reply_to_id" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Message_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "_AttachmentToMessage" (
"A" TEXT NOT NULL,
"B" TEXT NOT NULL,
CONSTRAINT "_AttachmentToMessage_AB_pkey" PRIMARY KEY ("A","B")
);
-- CreateTable
CREATE TABLE "_recipients" (
"A" VARCHAR(11) NOT NULL,
"B" TEXT NOT NULL,
CONSTRAINT "_recipients_AB_pkey" PRIMARY KEY ("A","B")
);
-- CreateIndex
CREATE UNIQUE INDEX "Attachment_id_key" ON "Attachment"("id");
-- CreateIndex
CREATE INDEX "_AttachmentToMessage_B_index" ON "_AttachmentToMessage"("B");
-- CreateIndex
CREATE INDEX "_recipients_B_index" ON "_recipients"("B");
-- AddForeignKey
ALTER TABLE "Message" ADD CONSTRAINT "Message_sender_id_fkey" FOREIGN KEY ("sender_id") REFERENCES "benutzer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Message" ADD CONSTRAINT "Message_reply_to_id_fkey" FOREIGN KEY ("reply_to_id") REFERENCES "Message"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_AttachmentToMessage" ADD CONSTRAINT "_AttachmentToMessage_A_fkey" FOREIGN KEY ("A") REFERENCES "Attachment"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_AttachmentToMessage" ADD CONSTRAINT "_AttachmentToMessage_B_fkey" FOREIGN KEY ("B") REFERENCES "Message"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_recipients" ADD CONSTRAINT "_recipients_A_fkey" FOREIGN KEY ("A") REFERENCES "benutzer"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_recipients" ADD CONSTRAINT "_recipients_B_fkey" FOREIGN KEY ("B") REFERENCES "Message"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,56 @@
/*
Warnings:
- You are about to drop the column `created_at` on the `Message` table. All the data in the column will be lost.
- You are about to drop the column `updated_at` on the `Message` table. All the data in the column will be lost.
- You are about to drop the `_recipients` table. If the table is not empty, all the data it contains will be lost.
- Added the required column `conversation_id` to the `Message` table without a default value. This is not possible if the table is not empty.
*/
-- DropForeignKey
ALTER TABLE "_recipients" DROP CONSTRAINT "_recipients_A_fkey";
-- DropForeignKey
ALTER TABLE "_recipients" DROP CONSTRAINT "_recipients_B_fkey";
-- AlterTable
ALTER TABLE "Message" DROP COLUMN "created_at",
DROP COLUMN "updated_at",
ADD COLUMN "conversation_id" TEXT NOT NULL,
ADD COLUMN "sentAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
-- DropTable
DROP TABLE "_recipients";
-- CreateTable
CREATE TABLE "Conversation" (
"id" TEXT NOT NULL,
"name" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL
);
-- CreateTable
CREATE TABLE "Participant" (
"id" TEXT NOT NULL,
"benutzer_id" TEXT NOT NULL,
"conversation_id" TEXT NOT NULL,
"joined_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Participant_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "Conversation_id_key" ON "Conversation"("id");
-- CreateIndex
CREATE UNIQUE INDEX "Participant_benutzer_id_conversation_id_key" ON "Participant"("benutzer_id", "conversation_id");
-- AddForeignKey
ALTER TABLE "Message" ADD CONSTRAINT "Message_conversation_id_fkey" FOREIGN KEY ("conversation_id") REFERENCES "Conversation"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Participant" ADD CONSTRAINT "Participant_benutzer_id_fkey" FOREIGN KEY ("benutzer_id") REFERENCES "benutzer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Participant" ADD CONSTRAINT "Participant_conversation_id_fkey" FOREIGN KEY ("conversation_id") REFERENCES "Conversation"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -0,0 +1,9 @@
model Attachment {
id String @id @unique @default(uuid())
name String?
kategorie String?
mime String
attached_to_messages Message[]
}

View File

@@ -41,19 +41,19 @@ model BedarfsausweisWohnen {
volumen Float?
dicht Boolean?
fenster_flaeche_1 Float?
fenster_art_1 Float?
fenster_art_1 String?
fenster_flaeche_2 Float?
fenster_art_2 Float?
fenster_art_2 String?
dachfenster_flaeche Float?
dachfenster_art Float?
dachfenster_art String?
haustuer_flaeche Float?
haustuer_art Float?
haustuer_art String?
dach_bauart String? @db.VarChar
decke_bauart String? @db.VarChar
dach_daemmung Float?
decke_daemmung Float?
aussenwand_daemmung Float?
boden_daemmung Float?
dach_daemmung String?
decke_daemmung String?
aussenwand_daemmung String?
boden_daemmung String?
aussenwand_bauart String? @db.VarChar
boden_bauart String? @db.VarChar
warmwasser_verteilung String? @db.VarChar

View File

@@ -49,7 +49,10 @@ model Benutzer {
BearbeiteteTickets Tickets[] @relation("BearbeiteteTickets")
events Event[]
@@map("benutzer")
conversations Participant[]
messages Message[]
@@map("benutzer")
}

View File

@@ -0,0 +1,9 @@
model Conversation {
id String @unique @default(cuid())
name String?
participants Participant[]
messages Message[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

7
prisma/schema/Log.prisma Normal file
View File

@@ -0,0 +1,7 @@
model Log {
id Int @id @default(autoincrement())
level String
message String
meta Json
timestamp DateTime @default(now())
}

View File

@@ -0,0 +1,17 @@
model Message {
id String @id @default(cuid())
attachments Attachment[]
reply_to_id String? // Nullable because not all messages are replies
reply_to Message? @relation("MessageReplies", fields: [reply_to_id], references: [id])
replies Message[] @relation("MessageReplies")
content String
sender_id String
conversation_id String
sentAt DateTime @default(now())
sender Benutzer @relation(fields: [sender_id], references: [id])
conversation Conversation @relation(fields: [conversation_id], references: [id])
}

View File

@@ -0,0 +1,11 @@
model Participant {
id String @id @default(cuid())
benutzer_id String
conversation_id String
joined_at DateTime @default(now())
benutzer Benutzer @relation(fields: [benutzer_id], references: [id])
conversation Conversation @relation(fields: [conversation_id], references: [id])
@@unique([benutzer_id, conversation_id])
}

View File

@@ -2,34 +2,28 @@
# === Configuration ===
BUCKET_NAME="ibc-db-backup"
ENDPOINT_URL="https://s3.eu-central-3.ionoscloud.com"
LOCAL_DOWNLOAD_DIR="./"
ENDPOINT_URL="https://s3-eu-central-1.ionoscloud.com"
LOCAL_DOWNLOAD_DIR="./" # Where to save the file
# === Use filename from argument if provided ===
if [ -n "$1" ]; then
LATEST_FILE="$1"
else
echo "📡 No filename provided, fetching latest..."
# === Get latest file from IONOS S3 bucket ===
LATEST_FILE=$(aws --profile ionos s3api list-objects-v2 \
--bucket "$BUCKET_NAME" \
--prefix "full-dump" \
--endpoint-url "$ENDPOINT_URL" \
--query 'Contents | sort_by(@, &LastModified) | [-1].Key' \
--output text)
# === Get latest file from IONOS S3 bucket ===
LATEST_FILE=$(aws s3api list-objects-v2 \
--bucket "$BUCKET_NAME" \
--prefix "data-dump" \
--endpoint-url "$ENDPOINT_URL" \
--query 'Contents | sort_by(@, &LastModified) | [-1].Key' \
--output text)
# === Check if file was found ===
if [ "$LATEST_FILE" == "None" ] || [ -z "$LATEST_FILE" ]; then
echo "❌ No matching .sql.br file found."
exit 1
fi
# === Check if file was found ===
if [ "$LATEST_FILE" == "None" ] || [ -z "$LATEST_FILE" ]; then
echo "❌ No matching .sql.br file found."
exit 1
fi
FILENAME=$(basename "$LATEST_FILE")
SQL_FILE="${FILENAME%.br}" # Remove .br suffix
echo "📥 Downloading $LATEST_FILE"
aws --profile ionos s3 cp "s3://$BUCKET_NAME/$LATEST_FILE" "$LOCAL_DOWNLOAD_DIR" \
aws s3 cp "s3://$BUCKET_NAME/$LATEST_FILE" "$LOCAL_DOWNLOAD_DIR" \
--endpoint-url "$ENDPOINT_URL"
# === Decompress with Brotli ===
@@ -37,8 +31,8 @@ echo "🗜️ Decompressing $FILENAME -> $SQL_FILE"
brotli -d "$FILENAME"
# === Import into Postgres inside Docker ===
echo "🐘 Importing into PostgreSQL (database:main)"
docker exec -i "database" env PGPASSWORD="hHMP8cd^N3SnzGRR" \
echo "🐘 Importing into PostgreSQL (online-energieausweis-database-1:main)"
docker exec -i "online-energieausweis-database-1" env PGPASSWORD="hHMP8cd^N3SnzGRR" \
psql -U "main" -d "main" < "$SQL_FILE"
echo "✅ Import complete."

View File

@@ -2,6 +2,7 @@ import express from 'express';
import { H, Handlers } from '@highlight-run/node'
// @ts-ignore
import { handler as ssrHandler } from './dist/server/entry.mjs';
import { server } from "./src/ws/server";
const highlightConfig = {
projectID: '1jdkoe52',

View File

@@ -5,7 +5,6 @@ export const createCaller = createCallerFactory({
"klimafaktoren": await import("../src/pages/api/klimafaktoren.ts"),
"postleitzahlen": await import("../src/pages/api/postleitzahlen.ts"),
"unterlage": await import("../src/pages/api/unterlage.ts"),
"aufnahme": await import("../src/pages/api/aufnahme/index.ts"),
"admin/ausstellen": await import("../src/pages/api/admin/ausstellen.ts"),
"admin/bedarfsausweis-ausstellen": await import("../src/pages/api/admin/bedarfsausweis-ausstellen.ts"),
"admin/bestellbestaetigung": await import("../src/pages/api/admin/bestellbestaetigung.ts"),
@@ -13,35 +12,34 @@ export const createCaller = createCallerFactory({
"admin/nicht-ausstellen": await import("../src/pages/api/admin/nicht-ausstellen.ts"),
"admin/registriernummer": await import("../src/pages/api/admin/registriernummer.ts"),
"admin/stornieren": await import("../src/pages/api/admin/stornieren.ts"),
"ausweise": await import("../src/pages/api/ausweise/index.ts"),
"aufnahme": await import("../src/pages/api/aufnahme/index.ts"),
"auth/access-token": await import("../src/pages/api/auth/access-token.ts"),
"auth/passwort-vergessen": await import("../src/pages/api/auth/passwort-vergessen.ts"),
"auth/refresh-token": await import("../src/pages/api/auth/refresh-token.ts"),
"auth/verification-code": await import("../src/pages/api/auth/verification-code.ts"),
"aufnahme": await import("../src/pages/api/aufnahme/index.ts"),
"ausweise": await import("../src/pages/api/ausweise/index.ts"),
"bedarfsausweis-gewerbe/[id]": await import("../src/pages/api/bedarfsausweis-gewerbe/[id].ts"),
"bedarfsausweis-gewerbe": await import("../src/pages/api/bedarfsausweis-gewerbe/index.ts"),
"ausweise": await import("../src/pages/api/ausweise/index.ts"),
"bedarfsausweis-wohnen/[id]": await import("../src/pages/api/bedarfsausweis-wohnen/[id].ts"),
"bedarfsausweis-wohnen": await import("../src/pages/api/bedarfsausweis-wohnen/index.ts"),
"bilder/[id]": await import("../src/pages/api/bilder/[id].ts"),
"geg-nachweis-gewerbe/[id]": await import("../src/pages/api/geg-nachweis-gewerbe/[id].ts"),
"geg-nachweis-gewerbe": await import("../src/pages/api/geg-nachweis-gewerbe/index.ts"),
"geg-nachweis-wohnen/[id]": await import("../src/pages/api/geg-nachweis-wohnen/[id].ts"),
"geg-nachweis-wohnen": await import("../src/pages/api/geg-nachweis-wohnen/index.ts"),
"geg-nachweis-gewerbe/[id]": await import("../src/pages/api/geg-nachweis-gewerbe/[id].ts"),
"geg-nachweis-gewerbe": await import("../src/pages/api/geg-nachweis-gewerbe/index.ts"),
"objekt": await import("../src/pages/api/objekt/index.ts"),
"rechnung/[id]": await import("../src/pages/api/rechnung/[id].ts"),
"rechnung/anfordern": await import("../src/pages/api/rechnung/anfordern.ts"),
"rechnung": await import("../src/pages/api/rechnung/index.ts"),
"ticket": await import("../src/pages/api/ticket/index.ts"),
"user": await import("../src/pages/api/user/index.ts"),
"user/self": await import("../src/pages/api/user/self.ts"),
"verbrauchsausweis-gewerbe/[id]": await import("../src/pages/api/verbrauchsausweis-gewerbe/[id].ts"),
"verbrauchsausweis-gewerbe": await import("../src/pages/api/verbrauchsausweis-gewerbe/index.ts"),
"ticket": await import("../src/pages/api/ticket/index.ts"),
"verbrauchsausweis-wohnen/[id]": await import("../src/pages/api/verbrauchsausweis-wohnen/[id].ts"),
"verbrauchsausweis-wohnen": await import("../src/pages/api/verbrauchsausweis-wohnen/index.ts"),
"verbrauchsausweis-gewerbe/[id]": await import("../src/pages/api/verbrauchsausweis-gewerbe/[id].ts"),
"verbrauchsausweis-gewerbe": await import("../src/pages/api/verbrauchsausweis-gewerbe/index.ts"),
"webhooks/mollie": await import("../src/pages/api/webhooks/mollie.ts"),
"objekt/[id]": await import("../src/pages/api/objekt/[id]/index.ts"),
"aufnahme/[id]/bilder": await import("../src/pages/api/aufnahme/[id]/bilder.ts"),
"aufnahme/[id]": await import("../src/pages/api/aufnahme/[id]/index.ts"),
"aufnahme/[id]/unterlagen": await import("../src/pages/api/aufnahme/[id]/unterlagen.ts"),
"objekt/[id]": await import("../src/pages/api/objekt/[id]/index.ts"),
})

View File

@@ -1,15 +0,0 @@
import { API_ACCESS_TOKEN_COOKIE_NAME } from "#lib/constants.js";
import { Benutzer } from "#lib/client/prisma.js";
import { api } from "astro-typesafe-api/client";
import Cookies from "js-cookie";
export async function benutzerLesen(benutzerId: string): Promise<Benutzer> {
const benutzer = await api.user.GET.fetch({ id: benutzerId }
, {
headers: {
Authorization: `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`
}
});
return benutzer[0];
}

View File

@@ -1,5 +1,5 @@
import { API_ACCESS_TOKEN_COOKIE_NAME } from "#lib/constants.js";
import { Aufnahme, Benutzer, Objekt } from "#lib/client/prisma.js";
import { Aufnahme, Objekt } from "#lib/client/prisma.js";
import { api } from "astro-typesafe-api/client";
import Cookies from "js-cookie";
@@ -161,32 +161,4 @@ export async function objektSpeichern(objekt: Objekt & { id?: string }): Promise
return id;
}
}
export async function benutzerSpeichern(benutzer: Partial<Benutzer>): Promise<string> {
const completeBenutzer: Benutzer = {
id: benutzer.id,
name: benutzer.name ?? null,
email: benutzer.email,
passwort: "",
adresse: benutzer.adresse ?? null,
anrede: benutzer.anrede ?? null,
firma: benutzer.firma ?? null,
vorname: benutzer.vorname ?? null,
ort: benutzer.ort ?? null,
plz: benutzer.plz ?? null,
profilbild: benutzer.profilbild ?? null,
telefon: benutzer.telefon ?? null,
updated_at: new Date(),
verified: benutzer.verified ?? false,
};
await api.user.POST.fetch(completeBenutzer
, {
headers: {
Authorization: `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`
}
});
return benutzer.id;
}

View File

@@ -1,108 +0,0 @@
<script lang="ts">
import { BedarfsausweisWohnen, Enums, Rechnung, VerbrauchsausweisGewerbe, VerbrauchsausweisWohnen } from "#lib/server/prisma.js";
import moment from "moment";
export let bestellungen: (Rechnung & {
verbrauchsausweis_wohnen: VerbrauchsausweisWohnen | null,
verbrauchsausweis_gewerbe: VerbrauchsausweisGewerbe | null,
bedarfsausweis_wohnen: BedarfsausweisWohnen | null,
})[];
export let provisionen: Record<Enums.Ausweisart, number>;
export let partnerCodeErstesMal: Date;
const bestellungenNachMonat: Record<string, (typeof bestellungen)> = {};
for (const bestellung of bestellungen) {
const monat = moment(bestellung.created_at).format("Y-m");
if (monat in bestellungenNachMonat) {
bestellungenNachMonat[monat].push(bestellung)
} else {
bestellungenNachMonat[monat] = [bestellung]
}
}
// Wir brauchen alle Monate zwischen dem ersten Mal, dass der partner_code benutzt wurde bis zum heutigen Zeitpunkt.
const months: Record<string, string> = {
"01": "Januar", "02": "Februar", "03": "März", "04": "April",
"05": "Mai", "06": "Juni", "07": "Juli", "08": "August",
"09": "September", "10": "Oktober", "11": "November", "12": "Dezember"
};
function getMonthlyPeriods(minTime?: Date): moment.Moment[] {
const min = minTime ? moment(minTime) : moment();
const start = min.clone().startOf('month');
const end = moment().add(1, 'month').startOf('month');
const monthsArray: moment.Moment[] = [];
const current = start.clone();
while (current.isBefore(end)) {
monthsArray.push(current.clone());
current.add(1, 'month');
}
return monthsArray.reverse(); // Most recent month first
}
const periods = getMonthlyPeriods(partnerCodeErstesMal)
</script>
{#each periods as dt}
{@const jahrMonat = dt.format("Y-m")}
{#if jahrMonat in bestellungenNachMonat && bestellungenNachMonat[jahrMonat].length > 0}
<!-- Echo dropdown foreach month. -->
{@const provisionMonat = bestellungenNachMonat[jahrMonat].reduce((acc, bestellung) => {
if (bestellung.verbrauchsausweis_wohnen) {
return acc + provisionen[Enums.Ausweisart.VerbrauchsausweisWohnen];
}
if (bestellung.bedarfsausweis_wohnen) {
return acc + provisionen[Enums.Ausweisart.BedarfsausweisWohnen];
}
if (bestellung.verbrauchsausweis_gewerbe) {
return acc + provisionen[Enums.Ausweisart.VerbrauchsausweisGewerbe];
}
return acc;
}) * 1.19}
<div onclick="$(this).nextUntil('.dropdown_month').filter('table').toggle(); $('#betrag_gesamt').html('Abrechnungsbetrag $month_name: <b>$provision_month €</b>')" class='dropdown_month'>
<p>$month_name $year_name - Klicke, um Tabelle anzuzeigen</p>
<a target='_blank' rel='noreferrer noopener' href='/user/abrechnung/pdf.php?month={dt.format("m")}&year={dt.format("Y")}'>PDF Ansehen</a>
</div>
<table id='QTT' style='margin-top: 0 !important; display:none;'>
<thead>
<tr>
<td style='text-align:center;'>ID</td>
<td style='text-align:center;'>DATUM</td>
<td style='width:11em;text-align:center;'>GEBÄUDEADRESSE </td>
<td style='width:11em;text-align:center;'>PLZ </td>
<td style='width:11em;text-align:center;'>ORT </td>
<td style='text-align:center;'>AUSWEIS</td>
<td style='width:5em;text-align:center;'>BETRAG NETTO</td>
</tr>
</thead>
<tbody>
{#each bestellungenNachMonat[jahrMonat] as bestellung}
{@const provisionBestellung = bestellung.verbrauchsausweis_wohnen ? provisionen[Enums.Ausweisart.VerbrauchsausweisWohnen] : bestellung.verbrauchsausweis_gewerbe ? provisionen[Enums.Ausweisart.VerbrauchsausweisGewerbe] : provisionen[Enums.Ausweisart.BedarfsausweisWohnen]}
<tr>
<td style='width:1em;text-align:center;'>{bestellung.id}</td>
<td style='width:9em;text-align:center;font-weight:bold;'>{moment(bestellung.created_at).format("Y/m/d")}</td>
<td style='width:8em;text-align:left;'>{bestellung["objekt_strasse"]}</td>
<td style='width:5em;text-align:center;'>{bestellung["objekt_plz"]}</td>
<td style='width:6em;text-align:left;'>{bestellung["objekt_ort"]}</td>
<td style='width:3em;text-align:center;'>{bestellung['ausweisart']}</td>
<td style='width:8em;text-align:right;'>{provisionBestellung}</td>
</tr>
{/each}
</table>
{/if}
{/each}
<!-- foreach ($period as $dt) {
$year_month = $dt->format("Y-m");
$month_name = $months[$dt->format("m")];
if ((new DateTime(date("m/d/Y", strtotime($EEtimestamp))))->format("d") - (new DateTime(date("m/d/Y", strtotime($SStimestamp))))->format("d") == 1) {
$Pall = $dt->format("d/m/Y") . ' bis ' . (new DateTime($today))->format("d/m/Y");
} -->
<!-- } -->

View File

@@ -41,7 +41,6 @@ const brennstoffe: [
["Fernwärme KWK EB", "kWh", 1.0, 0.0, 0.04],
["Fernwärme HKW FB", "kWh", 1.0, 1.3, 0.4],
["Fernwärme HKW EB", "kWh", 1.0, 0.1, 0.06],
["Fernwärme Hamburg", "kWh", 1.0, 0.33, 0.064],
["Erdgas", "kWh", 1.0, 1.1, 0.24],
["Heizöl", "kWh", 1.0, 1.1, 0.31],
["Heizöl", "l", 10.0, 1.1, 0.31],

View File

@@ -257,12 +257,6 @@
let bedarfsausweisFileInput: HTMLInputElement;
let bedarfsausweisAdditionalInput: HTMLInputElement;
let dropdownOpen = false;
function toggleDropdown() {
dropdownOpen = !dropdownOpen;
}
</script>
<div class="relative bg-base-200 border border-base-300 rounded-lg p-4 mx-2">
@@ -279,41 +273,30 @@
{/if}
<div class="card-body">
<div
class="mb-2 dropdown dropdown-bottom absolute top-4 right-4 bg-base-100"
class="flex justify-end mb-2 dropdown dropdown-bottom absolute top-4 right-4"
>
<button class="rounded-full p-2.5 hover:bg-base-100" on:click={toggleDropdown}>
<button class="rounded-full p-2.5 hover:bg-base-100">
<DotsVertical size={15} />
</button>
</div>
{#if dropdownOpen}
<ul
tabindex="-1"
class="z-[1] menu p-2 shadow bg-base-100 rounded-box w-64 gap-2 border"
class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-64 gap-2"
>
<!-- <div>
<button on:click={ausweisStornieren} class="flex items-center gap-2" disabled
><CrossCircled size={15} />Stornieren</button
<li>
<button on:click={ausweisStornieren}
><CrossCircled size={15} />Ausweis Stornieren</button
>
</div>
<div>
<button class="flex items-center gap-2" disabled
><Pencil2 size={15} /> Als Vorlage</button>
</div>
<div>
<button on:click={() => hilfeModal.showModal()} class="flex items-center gap-2" disabled
</li>
<li>
<button><Pencil2 size={15} /> Als Vorlage benutzen</button>
</li>
<li>
<button on:click={() => hilfeModal.showModal()}
><QuestionMarkCircled size={15} /> Hilfe Erhalten</button
>
</div> -->
{#if ausweis.bestellt && rechnung}
<div>
<a href="/dashboard/rechnung/aendern?rechnungid={rechnung.id}"><button> Adresse ändern</button
></a>
</div>
{/if}
</li>
</ul>
{/if}
</div>
<div class="flex flex-row flex-wrap items-center gap-2">
{#if ausweis.ausgestellt}
<span class="bg-green-600 px-2 py-0.5 text-sm font-semibold rounded-lg text-white">Ausgestellt</span>
@@ -344,20 +327,18 @@
</div>
<div class="badge badge-accent font-semibold text-black text-m">
{#if ausweis.ausweistyp === Enums.AusweisTyp.Beratung || ausweis.ausweistyp === Enums.AusweisTyp.BeratungXL}
mit Beratung
mit Beratung
{:else if ausweis.ausweistyp === Enums.AusweisTyp.Offline || ausweis.ausweistyp === Enums.AusweisTyp.OfflineXL}
Offline
Offline
{/if}
{#if (rechnung?.services ?? []).length > 0}
{#if rechnung}
<span class="text-sm italic">({rechnung.services})</span>
{/if}
{/if}
</div>
<div class="mb-4 flex flex-row items-center gap-4">
<div class="w-full border rounded-lg my-2">
<div class="bg-green-600 h-4 rounded-lg" class:bg-red-600={progress == 33} class:bg-primary={progress == 66} style="width: {progress}%;"></div>
</div>
<span class="text-sm font-semibold text-base-content"
>{progress}%</span
>
</div>
{#await calculations then calculations}
<div class="flex flex-col mb-4">
@@ -535,17 +516,17 @@
{#if ausweisart === Enums.Ausweisart.VerbrauchsausweisWohnen}
<a
class="button text-sm"
href="/energieausweis-erstellen/verbrauchsausweis-wohngebaeude?ausweis_id={ausweis.id}&typ={AusstellungsTyp.Speichern}"
href="/energieausweis-erstellen/verbrauchsausweis-wohngebaeude?ausweis_id={ausweis.id}"
>Formular</a>
{:else if ausweisart === Enums.Ausweisart.VerbrauchsausweisGewerbe}
<a
class="button text-sm"
href="/energieausweis-erstellen/verbrauchsausweis-gewerbe?ausweis_id={ausweis.id}&typ={AusstellungsTyp.Speichern}"
href="/energieausweis-erstellen/verbrauchsausweis-gewerbe?ausweis_id={ausweis.id}"
>Formular</a>
{:else if ausweisart === Enums.Ausweisart.BedarfsausweisWohnen}
<a
class="button text-sm"
href="/energieausweis-erstellen/bedarfsausweis-wohngebaeude?ausweis_id={ausweis.id}&typ={AusstellungsTyp.Speichern}"
href="/energieausweis-erstellen/bedarfsausweis-wohngebaeude?ausweis_id={ausweis.id}"
>Formular</a>
{/if}
{/if}
@@ -702,7 +683,7 @@
{aufnahme.aussenwand_min_12cm_gedaemmt ? "Außenwand min. 12cm gedämmt" : ""}
</div>
<div class="text-xs space-y-1 p-2">
<span class="font-semibold">Hiermit bestätige ich {benutzer.vorname} {benutzer.name} als Besteller:</span><br>
<span class="font-semibold">Hiermit bestätige ich {benutzer.vorname} {benutzer.name} als Besteller folgende Angaben:</span><br>
{#if ausweis.pruefpunkt_heizungsalter}
<div>Das Heizungsalter ist jünger als 3 Jahre. Es betrifft einen Heizungstausch ohne energetische Verbesserung.</div>
{/if}

View File

@@ -70,7 +70,6 @@
</div>
</div>
{/if}
<a href="/dashboard/abrechnung" class="button ">Conversions</a>
</div>
<hr class="border-gray-600" />

View File

@@ -1,68 +1,72 @@
<script lang="ts">
import { fade } from "svelte/transition";
import WidgetCardTemplate from "#components/widgets/immowelt/WidgetCardTemplate.svelte";
import { PRICES } from "#lib/constants.js";
import { Enums } from "#lib/client/prisma.js";
import { Enums } from "#lib/client/prisma";
let ausnahme: boolean = false;
let oneBOX: boolean = false;
let threeBOX: boolean = false;
let threeBOX: boolean = false;
let gebaeudetyp: string = "bitte auswählen";
let anlass: string = "bitte auswählen";
let einheiten: string = "bitte auswählen";
let sanierungsstatus: string = "bitte auswählen";
let baujahr: string = "bitte auswählen";
let heizungsAlter: string = "bitte auswählen";
let leerStand: string = "bitte auswählen";
let gebaeudetyp: string = "bitte auswählen";
let anlass: string = "bitte auswählen";
let einheiten: string = "bitte auswählen";
let sanierungsstatus: string = "bitte auswählen";
let baujahr: string = "bitte auswählen";
let heizungsAlter: string = "bitte auswählen";
let leerStand: string = "bitte auswählen";
const partner:string = "immowelt";
const partner: string = "immowelt";
const twoBoxReason = ["Vermietung/Verkauf", "Aushangpflicht", "Sonstiges"];
const gewerbeHouse = ["Gewerbegebäude", "Mischgebäude"];
const twoBoxReason = ["Vermietung/Verkauf", "Aushangpflicht", "Sonstiges"];
const gewerbeHouse = ["Gewerbegebäude", "Mischgebäude"];
$: ausnahme =
leerStand === "mehr als 30" ||
heizungsAlter === "< 3" ||
(baujahr === "vor 1978" && einheiten === "bis 4 Wohneinheiten" && sanierungsstatus === "unsaniert");
$: ausnahme =
leerStand === "mehr als 30" ||
heizungsAlter === "< 3" ||
(baujahr === "vor 1978" &&
einheiten === "bis 4 Wohneinheiten" &&
sanierungsstatus === "unsaniert");
$: isTwoBoxReason = twoBoxReason.includes(anlass);
$: isTwoBoxReason = twoBoxReason.includes(anlass);
$: isGewerbe = gewerbeHouse.includes(gebaeudetyp);
$: isGewerbe = gewerbeHouse.includes(gebaeudetyp);
$: oneBOX =
(ausnahme && !isGewerbe) ||
(!isTwoBoxReason && gebaeudetyp !== "Mischgebäude") ||
(gebaeudetyp === "Gewerbegebäude" && leerStand === "mehr als 30");
$: oneBOX =
(ausnahme && !isGewerbe) ||
(!isTwoBoxReason && gebaeudetyp !== "Mischgebäude") ||
(gebaeudetyp === "Gewerbegebäude" && leerStand === "mehr als 30");
$: threeBOX =
(ausnahme && gebaeudetyp === "Mischgebäude" && isTwoBoxReason && leerStand !== "mehr als 30");
$: threeBOX =
ausnahme &&
gebaeudetyp === "Mischgebäude" &&
isTwoBoxReason &&
leerStand !== "mehr als 30";
$: standardXL =
(einheiten === "mehr als 4 Wohneinheiten")
$: standardXL = einheiten === "mehr als 4 Wohneinheiten";
</script>
<div id="IBC_app">
<input id="recode" type="hidden" value="widgetvorlage" />
<div id="OEA_input">
<div
id="firstrow"
class="firstrow"
class:sm:grid-cols-3={isTwoBoxReason}
class:sm:grid-cols-2={!isTwoBoxReason}
>
<div id="firstrow" class="firstrow"
class:sm:grid-cols-3={isTwoBoxReason}
class:sm:grid-cols-2={!isTwoBoxReason}>
<div class="auswahl">
<div class="titel">Anlass</div>
<select id="anlass" class="selectfeld" bind:value={anlass}>
>
<select
id="anlass"
class="selectfeld"
bind:value={anlass}>
>
<option selected disabled>bitte auswählen</option>
<option value="Vermietung/Verkauf"
>Vermietung/Verkauf</option
>
<option value="Vermietung/Verkauf">Vermietung/Verkauf</option>
<option value="Modernisierung">Modernisierung</option>
<option value="Neubau">Neubau</option>
<option value="Erweiterung">Erweiterung</option>
@@ -73,8 +77,10 @@
<div class="auswahl">
<div class="titel">Gebäudetyp</div>
<select class="selectfeld" bind:value={gebaeudetyp}>
>
<select
class="selectfeld"
bind:value={gebaeudetyp}>
>
<option selected disabled>bitte auswählen</option>
<option value="Einfamilienhaus">Einfamilienhaus</option>
<option value="Zweifamilienhaus">Zweifamilienhaus</option>
@@ -85,94 +91,94 @@
</div>
{#if isTwoBoxReason}
<div class="auswahl">
<div class="titel">Sanierungsstand</div>
<select class="selectfeld" bind:value={sanierungsstatus}>
>
<option selected disabled>bitte auswählen</option>
<option value="saniert">saniert</option>
<option value="unsaniert">unsaniert</option>
</select>
</div>
<div class="auswahl">
<div class="titel">Sanierungsstand</div>
<select
class="selectfeld"
bind:value={sanierungsstatus}>
>
<option selected disabled>bitte auswählen</option>
<option value="saniert">saniert</option>
<option value="unsaniert">unsaniert</option>
</select>
</div>
{/if}
</div>
{#if isTwoBoxReason}
<div id="secondrow" class="secondrow">
<div class="auswahl">
<div class="titel">Baujahr</div>
<select
id="baujahr"
class="selectfeld"
bind:value={baujahr}
>
<option selected disabled>bitte auswählen</option>
<option value="vor 1978">vor 1978</option>
<option value="nach 1977">nach 1977</option>
</select>
</div>
<div id="secondrow" class="secondrow">
<div class="auswahl">
<div class="titel">Baujahr</div>
<select
id="baujahr"
class="selectfeld"
bind:value={baujahr}
>
<option selected disabled>bitte auswählen</option>
<option value="vor 1978">vor 1978</option>
<option value="nach 1977">nach 1977</option>
</select>
</div>
<div class="auswahl">
<div class="titel">Heizungsalter</div>
<select class="selectfeld" bind:value={heizungsAlter}>
<select
class="selectfeld"
bind:value={heizungsAlter}
>
<option selected disabled>bitte auswählen</option>
<option value="< 3">jünger als 3 Jahre</option>
<option value=">= 3">3 Jahre oder älter</option>
</select>
</div>
<div class="auswahl">
<div class="titel">Wohneinheiten</div>
<select class="selectfeld" bind:value={einheiten}>
<option selected disabled>bitte auswählen</option>
<option value="bis 4 Wohneinheiten"
>bis 4 Wohneinheiten</option
>
<option value="mehr als 4 Wohneinheiten"
>mehr als 4 Wohneinheiten</option
>
</select>
</div>
<div class="OEA_item4">
<div class="titel">Leerstand</div>
<select class="selectfeld ausnahmen" bind:value={leerStand}>
<option selected disabled>bitte auswählen</option>
<option value="bis 30">bis 30%</option>
<option value="mehr als 30">mehr als 30%</option>
</select>
</div>
<div class="auswahl">
<div class="titel">Wohneinheiten</div>
<select
class="selectfeld"
bind:value={einheiten}
>
<option selected disabled>bitte auswählen</option>
<option value="bis 4 Wohneinheiten"
>bis 4 Wohneinheiten</option
>
<option value="mehr als 4 Wohneinheiten"
>mehr als 4 Wohneinheiten</option
>
</select>
</div>
<div class="OEA_item4">
<div class="titel">Leerstand</div>
<select
class="selectfeld ausnahmen"
bind:value={leerStand}
>
<option selected disabled>bitte auswählen</option>
<option value="bis 30">bis 30%</option>
<option value="mehr als 30">mehr als 30%</option>
</select>
</div>
</div>
{/if}
<div
id="thirdrow"
class="thirdrow"
class:grid-cols-1={oneBOX}
class:md:grid-cols-6={threeBOX}
class:md:grid-cols-4={!oneBOX && !threeBOX}
>
{#if isTwoBoxReason && gebaeudetyp != "Gewerbegebäude" && ausnahme === false}
<div id="thirdrow" class="thirdrow"
class:grid-cols-1={oneBOX}
class:md:grid-cols-6={threeBOX}
class:md:grid-cols-4={!oneBOX && !threeBOX}
>
{#if isTwoBoxReason && (gebaeudetyp != "Gewerbegebäude") && (ausnahme === false)}
<WidgetCardTemplate
name="Verbrauchsausweis Wohngebäude"
price={PRICES.VerbrauchsausweisWohnen[
standardXL
? Enums.AusweisTyp.standardXL
: Enums.AusweisTyp.Standard
]}
price1={PRICES.VerbrauchsausweisWohnen[
standardXL
? Enums.AusweisTyp.BeratungXL
: Enums.AusweisTyp.Beratung
]}
price2={PRICES.VerbrauchsausweisWohnen[
standardXL
? Enums.AusweisTyp.OfflineXL
: Enums.AusweisTyp.Offline
]}
src={"https://online-energieausweis.org/images/partner/" +
partner +
"/wohngebaeude.svg"}
price={PRICES.VerbrauchsausweisWohnen[Enums.AusweisTyp.Standard] + (standardXL ? 10 : 0)}
src={'https://online-energieausweis.org/images/partner/'+partner+'/wohngebaeude.svg'}
alt="Wohnhaus Verbrauchsausweis"
variant="einfach"
empfehlung="nein"
@@ -180,40 +186,22 @@
services={[
["3&nbsp;Jahresverbräuche der Heizung benötigt.", true],
["Zulässig bei Vermietung oder Verkauf.", true],
[
"Unzulässig bei unsanierten Gebäuden vor 1978.",
false,
],
["Unzulässig bei unsanierten Gebäuden vor 1978.", false],
["Ungenau durch individuelles Heizverhalten.", false],
["Wird nicht immer bei den Banken akzeptiert.", false],
["Wird nicht immer bei den Banken akzeptiert.", false]
]}
href_buy1={`https://online-energieausweis.org/${partner}/energieausweis-erstellen/verbrauchsausweis-wohngebaeude/${standardXL ? "?ausweistyp=standardXL" : ""}`}
href_buy2={`https://online-energieausweis.org/${partner}/energieausweis-erstellen/verbrauchsausweis-wohngebaeude/${standardXL ? "?ausweistyp=BeratungXL" : "?ausweistyp=Beratung"}`}
href_buy3={`https://online-energieausweis.org/${partner}/energieausweis-erstellen/verbrauchsausweis-wohngebaeude/${standardXL ? "?ausweistyp=OfflineXL" : "?ausweistyp=Offline"}`}
href_buy={"https://online-energieausweis.org/"+partner+"/energieausweis-erstellen/verbrauchsausweis-wohngebaeude/"}{standardXL ? '?ausweistyp=standardXL' : ''}
href_overview={"https://online-energieausweis.org/"+partner+"/energieausweis-erstellen/verbrauchsausweis-wohngebaeude/produkt-uebersicht/"}{standardXL ? '?ausweistyp=standardXL' : ''}
></WidgetCardTemplate>
{/if}
{#if isTwoBoxReason && gebaeudetyp != "Gewerbegebäude"}
{#if isTwoBoxReason && (gebaeudetyp != "Gewerbegebäude")}
<WidgetCardTemplate
name="Bedarfsausweis Wohngebäude"
price={PRICES.BedarfsausweisWohnen[
standardXL
? Enums.AusweisTyp.standardXL
: Enums.AusweisTyp.Standard
]}
price1={PRICES.BedarfsausweisWohnen[
standardXL
? Enums.AusweisTyp.BeratungXL
: Enums.AusweisTyp.Beratung
]}
price2={PRICES.BedarfsausweisWohnen[
standardXL
? Enums.AusweisTyp.OfflineXL
: Enums.AusweisTyp.Offline
]}
src={"https://online-energieausweis.org/images/partner/" +
partner +
"/wohngebaeude.svg"}
price={PRICES.BedarfsausweisWohnen[Enums.AusweisTyp.Standard] + (standardXL ? 25 : 0)}
src={'https://online-energieausweis.org/images/partner/'+partner+'/wohngebaeude.svg'}
alt="Wohnhaus Bedarfsausweis"
variant="fundiert"
empfehlung="ja"
@@ -223,283 +211,181 @@
["Für Vermietung, Verkauf und Finanzierung.", true],
["Zulässig auch für unsanierte Objekte.", true],
["Kann als Grundlage für den ISFP dienen.", true],
[
"Objektivere Berechnungsmethode nach DIN 18599.",
true,
],
["Objektivere Berechnungsmethode nach DIN 18599.", true],
]}
href_buy1={`https://online-energieausweis.org/${partner}/energieausweis-erstellen/bedarfsausweis-wohngebaeude/${standardXL ? "?ausweistyp=standardXL" : ""}`}
href_buy2={`https://online-energieausweis.org/${partner}/energieausweis-erstellen/bedarfsausweis-wohngebaeude/${standardXL ? "?ausweistyp=BeratungXL" : "?ausweistyp=Beratung"}`}
href_buy3={`https://online-energieausweis.org/${partner}/energieausweis-erstellen/bedarfsausweis-wohngebaeude/${standardXL ? "?ausweistyp=OfflineXL" : "?ausweistyp=Offline"}`}
href_buy={"https://online-energieausweis.org/"+partner+"/energieausweis-erstellen/bedarfsausweis-wohngebaeude/"}{standardXL ? '?ausweistyp=standardXL' : ''}
href_overview={"https://online-energieausweis.org/"+partner+"/energieausweis-erstellen/bedarfsausweis-wohngebaeude/produkt-uebersicht"}{standardXL ? '?ausweistyp=standardXL' : ''}
></WidgetCardTemplate>
{/if}
{#if isTwoBoxReason && isGewerbe && leerStand != "mehr als 30"}
{#if isTwoBoxReason && isGewerbe && (leerStand != "mehr als 30")}
<WidgetCardTemplate
name="Verbrauchsausweis Gewerbegebäude"
price={PRICES.VerbrauchsausweisGewerbe[
standardXL
? Enums.AusweisTyp.standardXL
: Enums.AusweisTyp.Standard
]}
price1={PRICES.VerbrauchsausweisGewerbe[
standardXL
? Enums.AusweisTyp.BeratungXL
: Enums.AusweisTyp.Beratung
]}
price2={PRICES.VerbrauchsausweisGewerbe[
standardXL
? Enums.AusweisTyp.OfflineXL
: Enums.AusweisTyp.Offline
]}
src={"https://online-energieausweis.org/images/partner/" +
partner +
"/gewerbegebaeude.svg"}
price={PRICES.VerbrauchsausweisGewerbe[Enums.AusweisTyp.Standard] + (standardXL ? 15 : 0)}
src={'https://online-energieausweis.org/images/partner/'+partner+'/gewerbegebaeude.svg'}
alt="Gewerbe Verbrauchsausweis"
variant="einfach"
empfehlung="nein"
cta="jetzt&nbsp;online erstellen"
cta="jetzt&nbsp;online erstellen"
services={[
[
"3&nbsp;Jahresverbräuche von Heizung Gebäudestrom&nbsp;nötig.",
true,
],
["3&nbsp;Jahresverbräuche von Heizung Gebäudestrom&nbsp;nötig.", true],
["Zulässig bei Vermietung oder Verkauf.", true],
[
"Für bauliche und energetische Maßnahmen ungeeignet.",
false,
],
["Für bauliche und energetische Maßnahmen ungeeignet.", false],
["Wird nicht immer bei den Banken akzeptiert.", false],
["Ungenau durch individuelles Heizverhalten", false],
["Ungenau durch individuelles Heizverhalten", false],
]}
href_buy1={`https://online-energieausweis.org/${partner}/energieausweis-erstellen/verbrauchsausweis-gewerbe/${standardXL ? "?ausweistyp=standardXL" : ""}`}
href_buy2={`https://online-energieausweis.org/${partner}/energieausweis-erstellen/verbrauchsausweis-gewerbe/${standardXL ? "?ausweistyp=BeratungXL" : "?ausweistyp=Beratung"}`}
href_buy3={`https://online-energieausweis.org/${partner}/energieausweis-erstellen/verbrauchsausweis-gewerbe/${standardXL ? "?ausweistyp=OfflineXL" : "?ausweistyp=Offline"}`}
href_buy={"https://online-energieausweis.org/"+partner+"/energieausweis-erstellen/verbrauchsausweis-gewerbe/"}{standardXL ? '?ausweistyp=standardXL' : ''}
href_overview={"https://online-energieausweis.org/"+partner+"/energieausweis-erstellen/verbrauchsausweis-gewerbe/produkt-uebersicht/"}{standardXL ? '?ausweistyp=standardXL' : ''}
></WidgetCardTemplate>
{/if}
{#if isTwoBoxReason && isGewerbe}
<WidgetCardTemplate
name="Bedarfsausweis Gewerbegebäude"
price={PRICES.BedarfsausweisGewerbe[
standardXL
? Enums.AusweisTyp.standardXL
: Enums.AusweisTyp.Standard
]}
price1={PRICES.BedarfsausweisGewerbe[
standardXL
? Enums.AusweisTyp.BeratungXL
: Enums.AusweisTyp.Beratung
]}
price2={PRICES.BedarfsausweisGewerbe[
standardXL
? Enums.AusweisTyp.OfflineXL
: Enums.AusweisTyp.Offline
]}
src={"https://online-energieausweis.org/images/partner/" +
partner +
"/gewerbegebaeude.svg"}
price={PRICES.BedarfsausweisGewerbe[Enums.AusweisTyp.Standard]}
src={'https://online-energieausweis.org/images/partner/'+partner+'/gewerbegebaeude.svg'}
alt="Gewerbe Bedarfsausweis"
variant="fundiert"
empfehlung="ja"
cta="Angebot anfragen"
services={[
["Mehrzonenmodell nach DIN 18599.", true],
["Zulässig bei Vermietung oder Verkauf.", true],
["Grundlage für Sanierung-Varianten.", true],
["Objektiveres, besser vergleichbares Ergebnis.", true],
[
"Zulässig bei Leerstand oder fehlenden Verbräuchen",
true,
],
["Zulässig bei Leerstand oder fehlenden Verbräuchen", true],
]}
href_buy1={`https://online-energieausweis.org/${partner}/angebot-anfragen/bedarfsausweis-gewerbe-anfragen/${standardXL ? "?ausweistyp=standardXL" : ""}`}
href_buy2={`https://online-energieausweis.org/${partner}/angebot-anfragen/bedarfsausweis-gewerbe-anfragen/${standardXL ? "?ausweistyp=BeratungXL" : "?ausweistyp=Beratung"}`}
href_buy={"https://online-energieausweis.org/"+partner+"/angebot-anfragen/bedarfsausweis-gewerbe-anfragen/"}
href_overview={"https://online-energieausweis.org/"+partner+"/angebot-anfragen/bedarfsausweis-gewerbe-anfragen/produkt-uebersicht/"}
></WidgetCardTemplate>
{/if}
{#if anlass != "bitte auswählen" && !isTwoBoxReason && gebaeudetyp != "Gewerbegebäude"}
<WidgetCardTemplate
name="GEG-Nachweis Wohngebäude"
price={PRICES.GEGNachweisWohnen[
standardXL
? Enums.AusweisTyp.standardXL
: Enums.AusweisTyp.Standard
]}
price1={PRICES.GEGNachweisWohnen[
standardXL
? Enums.AusweisTyp.BeratungXL
: Enums.AusweisTyp.Beratung
]}
price2={PRICES.GEGNachweisWohnen[
standardXL
? Enums.AusweisTyp.OfflineXL
: Enums.AusweisTyp.Offline
]}
src={"https://online-energieausweis.org/images/partner/" +
partner +
"/wohngebaeude.svg"}
alt="GEG-Nachweis-Wohnen"
variant="Bauvorlage"
empfehlung="ja"
cta="Angebot anfragen"
services={[
[
"Nachweis fürs Bauamt bei Neubau oder Modernisierung.",
true,
],
[
"Beinhaltet die Effizienz der Bauteile und Anlagentechnik.",
true,
],
[
"Erstellung des Energieausweises nach Abschluss inklusive.",
true,
],
[
"Berechnung und Bilanzierung nach aktueller DIN 18599.",
true,
],
[
"Zonierung und Erstellung eines 3D Gebäudemodells.",
true,
],
]}
href_buy1={`https://online-energieausweis.org/${partner}/angebot-anfragen/geg-nachweis-wohnen-anfragen/${standardXL ? "?ausweistyp=standardXL" : ""}`}
href_buy2={`https://online-energieausweis.org/${partner}/angebot-anfragen/geg-nachweis-wohnen-anfragen/${standardXL ? "?ausweistyp=BeratungXL" : "?ausweistyp=Beratung"}`}
></WidgetCardTemplate>
{#if (anlass != "bitte auswählen") && !isTwoBoxReason && (gebaeudetyp != "Gewerbegebäude")}
<WidgetCardTemplate
name="GEG-Nachweis Wohngebäude"
price={PRICES.GEGNachweisWohnen[Enums.AusweisTyp.Standard]}
src={'https://online-energieausweis.org/images/partner/'+partner+'/wohngebaeude.svg'}
alt="GEG-Nachweis-Wohnen"
variant="Bauvorlage"
empfehlung="ja"
cta="Angebot anfragen"
services={[
["Nachweis fürs Bauamt bei Neubau oder Modernisierung.", true],
["Beinhaltet die Effizienz der Bauteile und Anlagentechnik.", true],
["Erstellung des Energieausweises nach Abschluss inklusive.", true],
["Berechnung und Bilanzierung nach aktueller DIN 18599.", true],
["Zonierung und Erstellung eines 3D Gebäudemodells.", true],
]}
href_buy={"https://online-energieausweis.org/"+partner+"/angebot-anfragen/geg-nachweis-wohnen-anfragen/"}
href_overview={"https://online-energieausweis.org/"+partner+"/angebot-anfragen/geg-nachweis-wohnen-anfragen/produkt-uebersicht/"}
></WidgetCardTemplate>
{/if}
{#if anlass != "bitte auswählen" && !isTwoBoxReason && isGewerbe}
<WidgetCardTemplate
name="GEG-Nachweis Gewerbegebäude"
price={PRICES.GEGNachweisGewerbe[
standardXL
? Enums.AusweisTyp.standardXL
: Enums.AusweisTyp.Standard
]}
price1={PRICES.GEGNachweisGewerbe[
standardXL
? Enums.AusweisTyp.BeratungXL
: Enums.AusweisTyp.Beratung
]}
price2={PRICES.GEGNachweisGewerbe[
standardXL
? Enums.AusweisTyp.OfflineXL
: Enums.AusweisTyp.Offline
]}
src={"https://online-energieausweis.org/images/partner/" +
partner +
"/gewerbegebaeude.svg"}
alt="GEG-Nachweis-Gewerbe"
variant="Bauvorlage"
empfehlung="ja"
cta="Angebot anfragen"
services={[
[
"Nachweis fürs Bauamt bei Neubau oder Modernisierung.",
true,
],
[
"Beinhaltet die Effizienz der Bauteile und Anlagentechnik.",
true,
],
[
"Erstellung des Energieausweises nach Abschluss inklusive.",
true,
],
[
"Berechnung und Bilanzierung nach aktueller DIN 18599.",
true,
],
[
"Mehrzonenmodell inkl. Erstellung eines 3D Gebäudemodells.",
true,
],
]}
href_buy1={`https://online-energieausweis.org/${partner}/angebot-anfragen/geg-nachweis-gewerbe-anfragen/${standardXL ? "?ausweistyp=standardXL" : ""}`}
href_buy2={`https://online-energieausweis.org/${partner}/angebot-anfragen/geg-nachweis-gewerbe-anfragen/${standardXL ? "?ausweistyp=BeratungXL" : "?ausweistyp=Beratung"}`}
></WidgetCardTemplate>
{#if (anlass != "bitte auswählen") && !isTwoBoxReason && isGewerbe}
<WidgetCardTemplate
name="GEG-Nachweis Gewerbegebäude"
price={PRICES.GEGNachweisGewerbe[Enums.AusweisTyp.Standard]}
src={'https://online-energieausweis.org/images/partner/'+partner+'/gewerbegebaeude.svg'}
alt="GEG-Nachweis-Gewerbe"
variant="Bauvorlage"
empfehlung="ja"
cta="Angebot anfragen"
services={[
["Nachweis fürs Bauamt bei Neubau oder Modernisierung.", true],
["Beinhaltet die Effizienz der Bauteile und Anlagentechnik.", true],
["Erstellung des Energieausweises nach Abschluss inklusive.", true],
["Berechnung und Bilanzierung nach aktueller DIN 18599.", true],
["Mehrzonenmodell inkl. Erstellung eines 3D Gebäudemodells.", true],
]}
href_buy={"https://online-energieausweis.org/"+partner+"/angebot-anfragen/geg-nachweis-gewerbe-anfragen/"}
href_overview={"https://online-energieausweis.org/"+partner+"/angebot-anfragen/geg-nachweis-gewerbe-anfragen/produkt-uebersicht/"}
></WidgetCardTemplate>
{/if}
</div>
</div>
</div>
<style lang="postcss">
#IBC_app {
@font-face {
font-family: "immo Sans";
src: url("/fonts/Immo-Sans/immoSans-Regular.eot");
src:
url("/fonts/Immo-Sans/immoSans-Regular.eot?#iefix")
format("embedded-opentype"),
url("/fonts/Immo-Sans/immoSans-Regular.woff2") format("woff2"),
url("/fonts/Immo-Sans/immoSans-Regular.woff") format("woff");
font-style: normal;
font-weight: 400;
}
@font-face {
font-family: "immo Sans Bold";
src: url("/fonts/Immo-Sans/immoSans-Bold.eot");
src:
url("/fonts/Immo-Sans/immoSans-Bold.eot?#iefix")
format("embedded-opentype"),
url("../../Fonts/Immo-Sans/immoSans-Bold.woff2") format("woff2"),
url("../../Fonts/Immo-Sans/immoSans-Bold.woff") format("woff");
font-style: normal;
font-weight: 700;
}
#IBC_app {
@font-face {
font-family: "Antique Olive Compact bold";
font-weight: 700;
font-style: normal;
font-display: swap;
src: url("/fonts/Antique Olive Std Compact.woff2") format("woff2");
}
@font-face {
font-family: "immo Sans";
src: url('/fonts/Immo-Sans/immoSans-Regular.eot');
src: url('/fonts/Immo-Sans/immoSans-Regular.eot?#iefix') format('embedded-opentype'),
url('/fonts/Immo-Sans/immoSans-Regular.woff2') format('woff2'),
url('/fonts/Immo-Sans/immoSans-Regular.woff') format('woff');
font-style: normal;
font-weight: 400;
}
@apply min-w-[320px] max-w-[1920px] p-[4px]
@font-face {
font-family: "immo Sans Bold";
src: url('/fonts/Immo-Sans/immoSans-Bold.eot');
src: url('/fonts/Immo-Sans/immoSans-Bold.eot?#iefix') format('embedded-opentype'), url('../../Fonts/Immo-Sans/immoSans-Bold.woff2') format('woff2'), url('../../Fonts/Immo-Sans/immoSans-Bold.woff') format('woff');
font-style: normal;
font-weight: 700;
}
@font-face {
font-family: 'Antique Olive Compact bold';
font-weight: 700;
font-style: normal;
font-display:swap;
src: url("/fonts/Antique Olive Std Compact.woff2") format('woff2');
}
@apply min-w-[320px] max-w-[1920px] p-[4px]
sm:p-[10px];
font-family: "immo Sans";
select option {
font-family: "immo Sans", sans-serif;
}
font-family: "immo Sans";
select option{
font-family: "immo Sans",sans-serif;}
.firstrow {
@apply grid grid-cols-1 gap-x-4 gap-y-2
.firstrow{@apply grid grid-cols-1 gap-x-4 gap-y-2
sm:grid-cols-2 sm:gap-x-4 sm:gap-y-2;
.titel {
@apply text-black font-bold bg-[#ffcc00] px-2 py-1 rounded-[0.25rem];
}
.titel{@apply text-black font-bold bg-[#ffcc00] px-2 py-1 rounded-[0.25rem];}
.selectfeld {
@apply w-full px-2 py-1 min-h-[38px] ring-1 ring-black/15;
}
}
.selectfeld{@apply w-full px-2 py-1 min-h-[38px] ring-1 ring-black/15}
.secondrow {
@apply grid grid-cols-2 gap-x-4 gap-y-2 mt-4
}
.secondrow{@apply grid grid-cols-2 gap-x-4 gap-y-2 mt-4
sm:grid-cols-4 sm:gap-x-4 sm:gap-y-2;
.titel {
@apply text-black font-bold bg-[#cccccc] px-2 py-1 rounded-[0.25rem];
}
.titel{@apply text-black font-bold bg-[#cccccc] px-2 py-1 rounded-[0.25rem];}
.selectfeld{@apply w-full px-2 py-1 min-h-[38px] ring-1 ring-black/15}
.selectfeld {
@apply w-full px-2 py-1 min-h-[38px] ring-1 ring-black/15;
}
}
}
#OEA_input {
@apply grid;
}
#OEA_input{@apply grid}
.thirdrow {
@apply grid grid-cols-1 gap-x-4 gap-y-2 col-start-1
.thirdrow{@apply grid grid-cols-1 gap-x-4 gap-y-2 col-start-1
md:grid-cols-4 md:gap-x-4 md:gap-y-2;
}
}
}
}
</style>

View File

@@ -1,149 +1,115 @@
<script lang="ts">
import { fade } from "svelte/transition";
export let price: number;
export let price1: number;
export let price2: number;
export let name: string;
export let variant: string;
export let services: [string, boolean][];
export let href_buy1: string;
export let href_buy2: string;
export let href_buy3: string = "";
export let href_buy: string;
export let href_overview: string;
export let src: string;
export let alt: string;
export let empfehlung: string;
export let cta: string;
</script>
<div class="produktbox" transition:fade={{ duration: 0 }}>
{#if empfehlung === "ja"}
<div class="empfehlung" aria-label="Empfohlenes Produkt">
Empfehlung
</div>
{/if}
<h2 class="titel sm:mb-2">{name}</h2>
<div
class="produktbox"
transition:fade={{ duration: 0 }}
>
<div class="sumCent">
<div class="variante">{variant}</div>
<img class="image" {src} {alt} />
<div class="">
<p class="price">
ab {price}
</p>
</div>
</div>
{#if empfehlung === "ja"}
<div class="empfehlung" aria-label="Empfohlenes Produkt">Empfehlung</div>
{/if}
<h2 class="titel sm:mb-2">{name}</h2>
<hr class="col-span-2 w-full md:w-[50%] md:m-auto bg-[#ffcc00] h-[2px]" />
<div class="sumRows forServices">
{#each services as [service, check]}
<div class="services">
<span>{@html service}</span>
<span class={check ? "check" : "check-no"}
>{check ? "✔" : "✘"}</span
>
<div class="sumCent">
<div class="variante">{variant}</div>
<img
class="image"
{src}
{alt}
/>
<div class="">
<p class="price">
ab {price}
</p>
</div>
</div>
{/each}
</div>
<hr class="col-span-2 w-full md:w-[50%] md:m-auto bg-[#ffcc00] h-[2px]" />
<div class="sumCent buttoncols">
<a
href={href_buy}
class="buttoncol"
aria-label="Jetzt {name} kaufen"
target="_blank"
>{cta}
</a>
<a
href={href_overview}
class="buttoncol"
aria-label="{name} Produkt-Übersicht"
target="_blank"
>Produkt-Übersicht</a
>
</div>
<div
class="sumCent buttoncols"
class:md:grid-cols-3={href_buy3}
class:md:grid-cols-2={!href_buy3}
>
<a
href={href_buy1}
class="buttoncol"
aria-label="Jetzt {name} kaufen"
target="_blank"
>mach selbst (<span class="inside-price">{price}</span>&nbsp;€)
</a>
<a
href={href_buy2}
class="buttoncol"
aria-label="{name} Produkt-Übersicht"
target="_blank"
>wir helfen (<span class="inside-price">{price1}</span>&nbsp;€)
</a>
{#if href_buy3}
<a
href={href_buy3}
class="buttoncol"
aria-label="{name} Produkt-Übersicht"
target="_blank"
>wir machen (<span class="inside-price">{price2}</span>&nbsp;€)
</a>
{/if}
</div>
</div>
<div class="sumRows forServices">
{#each services as [service, check]}
<div class="services">
<span>{@html service}</span>
<span class={check ? "check" : "check-no"}>{check ? "✔" : "✘"}</span>
</div>
{/each}
</div>
</div>
<style lang="postcss">
.produktbox {
@apply grid grid-cols-subgrid col-span-2 grid-rows-subgrid row-span-3 md:row-span-12 bg-black/5 rounded-lg
.produktbox{@apply grid grid-cols-subgrid col-span-2 grid-rows-subgrid row-span-3 md:row-span-12 bg-black/5 rounded-lg
px-2 py-2 mt-5;
box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.25);
box-shadow:2px 2px 8px rgba(0,0,0,0.25);
.sumCent {
@apply justify-self-center col-span-2;
}
.sumRows {
@apply hidden sm:grid grid-rows-subgrid row-span-5 items-center;
}
.forServices {
@apply grid-rows-subgrid row-span-5 items-center col-span-2 justify-center px-6;
}
.sumCent{@apply justify-self-center col-span-2}
.sumRows{@apply hidden sm:grid grid-rows-subgrid row-span-5 items-center}
.forServices{@apply grid-rows-subgrid row-span-5 items-center col-span-2 justify-center px-6}
.image {
@apply w-[75%] mx-auto
md:w-[75%] md:pl-0;
}
.image{@apply w-[75%] mx-auto
md:w-[75%] md:pl-0}
.buttoncols {
@apply grid grid-cols-1 gap-x-4 w-full my-4
md:grid-cols-3 md:w-[auto];
}
.buttoncols{@apply grid grid-cols-1 gap-x-4 w-full mb-4
md:grid-cols-2 md:w-[auto]}
.buttoncol {
@apply mt-2 md:mt-0 text-center text-black bg-[#ffcc00] rounded-md px-3 py-1 no-underline
hover:bg-[#222222] hover:text-white;
}
.inside-price {
@apply font-bold;
}
.buttoncol{@apply mt-2 md:mt-0 text-center text-black bg-[#ffcc00] rounded-md px-3 py-1 no-underline
hover:bg-[#222222] hover:text-white}
.price {
@apply tracking-tighter text-[2rem] text-[#222222] pl-12 m-0 -mt-7 text-nowrap text-left;
font-family: "Antique Olive Compact bold";
}
.price{@apply tracking-tighter text-[2rem] text-[#222222] pl-12 m-0 -mt-7 text-nowrap text-left;
font-family: "Antique Olive Compact bold";}
.titel {
@apply col-span-2 text-center [font-size:_clamp(20px,2.5vw,28px)];
}
.empfehlung {
@apply -mt-4 absolute justify-self-end rounded-md bg-red-700 text-white w-fit h-fit px-2 py-1 rotate-1 text-[0.65rem] ring-4 ring-white mr-6;
}
.variante {
@apply w-fit italic col-span-2 -mt-2 -mb-4 text-[1rem] text-[#222222] justify-self-start ring-2 ring-[#ffcc00] rounded-md pl-[4px] pr-[6px] py-[0px];
}
.services {
@apply hidden text-start py-1 md:grid grid-rows-subgrid row-span-1 items-center md:grid-cols-[1fr_50px];
}
.services:not(:last-child) {
@apply border-b-[1px] border-gray-200;
}
.check {
@apply justify-self-end self-center font-bold text-green-700;
}
.check-no {
@apply justify-self-end self-center font-bold text-red-700;
}
.titel {@apply col-span-2 text-center [font-size:_clamp(20px,2.5vw,28px)]}
.empfehlung{@apply -mt-4 absolute justify-self-end rounded-md bg-red-700 text-white w-fit h-fit px-2 py-1 rotate-1 text-[0.65rem] ring-4 ring-white mr-6}
.variante {
@apply w-fit italic col-span-2 -mt-2 -mb-4 text-[1rem] text-[#222222] justify-self-start ring-2 ring-[#ffcc00] rounded-md pl-[4px] pr-[6px] py-[0px];
}
.services {
@apply hidden text-start py-1 md:grid grid-rows-subgrid row-span-1 items-center md:grid-cols-[1fr_50px]
}
.services:not(:last-child) {
@apply border-b-[1px] border-gray-200;
}
.check {
@apply justify-self-end self-center font-bold text-green-700;
}
.check-no {
@apply justify-self-end self-center font-bold text-red-700;
}
}
</style>

View File

@@ -0,0 +1,8 @@
import * as z from "zod"
export const AttachmentSchema = z.object({
id: z.string(),
name: z.string().nullish(),
kategorie: z.string().nullish(),
mime: z.string(),
})

View File

@@ -38,13 +38,13 @@ export const BedarfsausweisWohnenSchema = z.object({
volumen: z.number().nullish(),
dicht: z.boolean().nullish(),
fenster_flaeche_1: z.number().nullish(),
fenster_art_1: z.number().nullish(),
fenster_art_1: z.string().nullish(),
fenster_flaeche_2: z.number().nullish(),
fenster_art_2: z.number().nullish(),
fenster_art_2: z.string().nullish(),
dachfenster_flaeche: z.number().nullish(),
dachfenster_art: z.number().nullish(),
dachfenster_art: z.string().nullish(),
haustuer_flaeche: z.number().nullish(),
haustuer_art: z.number().nullish(),
haustuer_art: z.string().nullish(),
dach_bauart: z.string().nullish(),
decke_bauart: z.string().nullish(),
dach_daemmung: z.string().nullish(),

View File

@@ -0,0 +1,8 @@
import * as z from "zod"
export const ConversationSchema = z.object({
id: z.string(),
name: z.string().nullish(),
createdAt: z.date(),
updatedAt: z.date(),
})

View File

@@ -1,16 +1,21 @@
export * from "./anteilshaber"
export * from "./apirequests"
export * from "./attachment"
export * from "./aufnahme"
export * from "./bedarfsausweisgewerbe"
export * from "./bedarfsausweiswohnen"
export * from "./benutzer"
export * from "./bild"
export * from "./conversation"
export * from "./event"
export * from "./gegeinpreisung"
export * from "./gegnachweisgewerbe"
export * from "./gegnachweiswohnen"
export * from "./klimafaktoren"
export * from "./log"
export * from "./message"
export * from "./objekt"
export * from "./participant"
export * from "./postleitzahlen"
export * from "./rechnung"
export * from "./refreshtokens"

15
src/generated/zod/log.ts Normal file
View File

@@ -0,0 +1,15 @@
import * as z from "zod"
// Helper schema for JSON fields
type Literal = boolean | number | string
type Json = Literal | { [key: string]: Json } | Json[]
const literalSchema = z.union([z.string(), z.number(), z.boolean()])
const jsonSchema: z.ZodSchema<Json> = z.lazy(() => z.union([literalSchema, z.array(jsonSchema), z.record(jsonSchema)]))
export const LogSchema = z.object({
id: z.number().int(),
level: z.string(),
message: z.string(),
meta: jsonSchema,
timestamp: z.date(),
})

View File

@@ -0,0 +1,10 @@
import * as z from "zod"
export const MessageSchema = z.object({
id: z.string(),
reply_to_id: z.string().nullish(),
content: z.string(),
sender_id: z.string(),
conversation_id: z.string(),
sentAt: z.date(),
})

View File

@@ -0,0 +1,8 @@
import * as z from "zod"
export const ParticipantSchema = z.object({
id: z.string(),
benutzer_id: z.string(),
conversation_id: z.string(),
joined_at: z.date(),
})

View File

@@ -102,13 +102,6 @@ const { title } = Astro.props;
<html lang="de">
<head>
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-59QKHH8');</script>
<!-- End Google Tag Manager -->
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.jpg" />
@@ -154,11 +147,6 @@ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
</head>
<body>
<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-59QKHH8"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->
<Header {user} />
<main

View File

@@ -0,0 +1,157 @@
---
import "../style/global.css";
import "../style/formular.css";
import "../../svelte-dialogs.config";
import Header from "#components/design/header/AusweisHeaderImmowelt.astro";
import Footer from "#components/design/footer/Footer.astro";
import { NotificationWrapper } from "@ibcornelsen/ui";
export interface Props {
title: string;
}
const { title } = Astro.props;
---
<script>
window.addEventListener("scroll", (event) => {
let scroll = window.scrollY;
console.log(scroll);
if (scroll >= 400) {
document
.getElementById("skala")
?.classList.add(
"2xl:fixed",
"2xl:py-4",
"2xl:top-0",
"2xl:z-20"
);
document.getElementById("skala")?.classList.remove("w-full");
document.getElementById("skala").style.borderBottom =
"3px solid #e6e6e6";
document.getElementById("performance-box").style.maxWidth =
"688.5px";
document.getElementById("progress-box").style.maxWidth = "688.5px";
document
.getElementById("formInput-1")
?.classList.add("2xl:mt-[370px]");
} else {
document
.getElementById("skala")
?.classList.remove(
"2xl:fixed",
"2xl:py-4",
"2xl:top-0",
"2xl:z-20"
);
document.getElementById("skala")?.classList.add("w-full");
document.getElementById("skala").style.borderBottom = "none";
document
.getElementById("formInput-1")
?.classList.remove("2xl:mt-[370px]");
}
});
</script>
<html lang="de">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.jpg" />
<meta
name="description"
content="✅ Jetzt Ihren Energieausweis online erstellen. Erhalten Sie Ihren online Energieausweis rechtssicher und nach aktueller GEG (vormals EnEV) vom Diplom Ingenieur geprüft."
/>
<link rel="canonical" href="https://online-energieausweis.org/" />
<meta property="og:locale" content="de_DE" />
<meta property="og:type" content="website" />
<meta
property="og:title"
content="Energieausweis online erstellen - Online Energieausweis"
/>
<meta
property="og:description"
content="✅ Jetzt Ihren Energieausweis online erstellen. Erhalten Sie Ihren online Energieausweis rechtssicher und nach aktueller GEG (vormals EnEV) vom Diplom Ingenieur geprüft."
/>
<meta property="og:url" content="https://online-energieausweis.org/" />
<meta
property="og:site_name"
content="Energieausweis online erstellen"
/>
<meta name="twitter:card" content="summary_large_image" />
<meta
name="twitter:description"
content="✅ Jetzt Ihren Energieausweis online erstellen. Erhalten Sie Ihren online Energieausweis rechtssicher und nach aktueller GEG (vormals EnEV) vom Diplom Ingenieur geprüft."
/>
<meta
name="twitter:title"
content="Energieausweis online erstellen - Online Energieausweis"
/>
<meta
name="twitter:image"
content="https://online-energieausweis.org/images/energieausweis-online-erstellen.jpg"
/>
<title>
{title || "Energieausweis online erstellen - Online Energieausweis"}
</title>
</head>
<body>
<Header />
<main
class="w-full p-0 grid
xs:grid-cols-[minmax(1fr)] xs:gap-1 xs:p-0
sm:grid-cols-[minmax(1fr)] sm:gap-1 sm:p-0
md:grid-cols-[minmax(1fr)] md:gap-2 md:p-0
lg:grid-cols-[minmax(1fr)] lg:gap-3 lg:p-4
xl:grid-cols-[minmax(1fr)] xl:gap-4 xl:p-6
2xl:grid-cols-[minmax(1fr)] 2xl:gap-5 2xl:p-6"
>
<article class="box rounded-tl-none p-2 lg:p-12">
<slot />
</article>
</main>
<Footer />
<NotificationWrapper client:load />
</body>
</html>
<style is:global lang="postcss">
body {
min-height: 100vh;
width:100%;
}
article {
p, h1, h2, h3, h4, h5, h6 {
@apply text-base-content;
}
}
.headline {
@apply text-lg;
}
.radio-inline {
@apply flex flex-row gap-2;
}
.checkbox-inline {
@apply flex flex-row gap-2;
}
</style>

View File

@@ -0,0 +1,156 @@
---
import "../style/global.css";
import "../style/formular.css";
import "../../svelte-dialogs.config"
import Header from "#components/design/header/AusweisHeaderImmowelt2.astro";
import Footer from "#components/design/footer/Footer.astro";
import { NotificationWrapper } from "@ibcornelsen/ui";
export interface Props {
title: string;
}
const { title } = Astro.props;
---
<script>
window.addEventListener("scroll", (event) => {
let scroll = window.scrollY;
console.log(scroll);
if(scroll>=400){
document.getElementById('skala')?.classList.add('2xl:fixed','2xl:py-4','2xl:top-0','2xl:z-20');
document.getElementById('skala')?.classList.remove('w-full');
document.getElementById('skala').style.borderBottom = "3px solid #e6e6e6";
document.getElementById('performance-box').style.maxWidth = "688.5px";
document.getElementById('progress-box').style.maxWidth = "688.5px";
document.getElementById('formInput-1')?.classList.add('2xl:mt-[370px]');
}else{
document.getElementById('skala')?.classList.remove('2xl:fixed','2xl:py-4','2xl:top-0','2xl:z-20');
document.getElementById('skala')?.classList.add('w-full');
document.getElementById('skala').style.borderBottom = "none";
document.getElementById('formInput-1')?.classList.remove('2xl:mt-[370px]');
}
});
</script>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.jpg" />
<meta
name="description"
content="✅ Jetzt Ihren Energieausweis online erstellen. Erhalten Sie Ihren online Energieausweis rechtssicher und nach aktueller GEG (vormals EnEV) vom Diplom Ingenieur geprüft."
/>
<link rel="canonical" href="https://online-energieausweis.org/" />
<meta property="og:locale" content="de_DE" />
<meta property="og:type" content="website" />
<meta
property="og:title"
content="Energieausweis online erstellen - Online Energieausweis"
/>
<meta
property="og:description"
content="✅ Jetzt Ihren Energieausweis online erstellen. Erhalten Sie Ihren online Energieausweis rechtssicher und nach aktueller GEG (vormals EnEV) vom Diplom Ingenieur geprüft."
/>
<meta property="og:url" content="https://online-energieausweis.org/" />
<meta property="og:site_name" content="Energieausweis online erstellen" />
<meta name="twitter:card" content="summary_large_image" />
<meta
name="twitter:description"
content="✅ Jetzt Ihren Energieausweis online erstellen. Erhalten Sie Ihren online Energieausweis rechtssicher und nach aktueller GEG (vormals EnEV) vom Diplom Ingenieur geprüft."
/>
<meta
name="twitter:title"
content="Energieausweis online erstellen - Online Energieausweis"
/>
<meta
name="twitter:image"
content="https://online-energieausweis.org/images/energieausweis-online-erstellen.jpg"
/>
<title>
{title || 'Energieausweis online erstellen - Online Energieausweis'}
</title>
</head>
<body>
<Header />
<main
class="w-full p-0 grid
xs:grid-cols-[minmax(1fr)] xs:gap-1 xs:p-0
sm:grid-cols-[minmax(1fr)] sm:gap-1 sm:p-0
md:grid-cols-[minmax(1fr)] md:gap-2 md:p-0
lg:grid-cols-[minmax(1fr)] lg:gap-3 lg:p-4
xl:grid-cols-[minmax(1fr)] xl:gap-4 xl:p-6
2xl:grid-cols-[minmax(1fr)] 2xl:gap-5 2xl:p-6
">
<article class="box rounded-tl-none p-2 lg:p-12">
<slot />
</article>
</main>
<Footer />
<NotificationWrapper client:load />
</body>
</html>
<style is:global lang="postcss">
body {
min-height: 100vh;
width:100%;
}
article {
p, h1, h2, h3, h4, h5, h6 {
@apply text-base-content;
}
}
.headline {
@apply text-lg;
}
.radio-inline {
@apply flex flex-row gap-2;
}
.checkbox-inline {
@apply flex flex-row gap-2;
}
</style>

View File

@@ -85,14 +85,6 @@ window.addEventListener("scroll", () => {
<!DOCTYPE html>
<html lang="de">
<head>
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-59QKHH8');</script>
<!-- End Google Tag Manager -->
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.jpg" />
@@ -135,11 +127,7 @@ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
</head>
<body>
<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-59QKHH8"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->
<AusweisHeaderPartner {tab}/>
<main class="w-full p-0 grid grid-cols-1 rounded-none">
@@ -148,6 +136,7 @@ height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<article class="p-0 lg:px-20 lg:py-12">
<slot />
<div style="height: 450px;"></div>
</article>
</main>

View File

@@ -37,14 +37,6 @@ const { title } = Astro.props;
<!DOCTYPE html>
<html lang="de">
<head>
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-59QKHH8');</script>
<!-- End Google Tag Manager -->
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.jpg" />
@@ -87,10 +79,6 @@ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
</head>
<body>
<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-59QKHH8"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->
<Header {user} />

View File

@@ -98,15 +98,6 @@ window.addEventListener("scroll", (event) => {
<!DOCTYPE html>
<html lang="de">
<head>
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-59QKHH8');</script>
<!-- End Google Tag Manager -->
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.jpg" />
@@ -120,10 +111,6 @@ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
</head>
<body>
<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-59QKHH8"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->
<Header {user} />

View File

@@ -1,29 +0,0 @@
import { uptime } from "os"
import crypto from "crypto";
/**
* Generiert einen zeitbasierten Hash der sich alle 15 Minuten ändert und an die Uptime des servers gekoppelt ist.
* @param email - Die E-Mail-Adresse des Benutzers, die als Teil des Hashes verwendet wird.
* @param time - Die Zeit in Millisekunden, die seit dem Start des Servers vergangen ist (Standard ist die Uptime des Servers).
* @param length - Die Länge des zurückgegebenen Hashes (Standard ist 6 Zeichen).
* @returns Ein zeitbasierter Hash, der sich alle 15 Minuten ändert und auf der E-Mail-Adresse basiert.
*/
export function createTimeBasedHash(email: string, time: number = uptime(), length: number = 6): string {
const now = Date.now();
const elapsed = now - time;
const window = Math.floor(elapsed / (15 * 60 * 1000)); // 15 minute windows
const data = `${email}:${window}`;
// Use a cryptographic hash (you can also use HMAC with a secret if you want)
const hash = crypto.createHash('sha256').update(data).digest();
// Convert part of the hash to an integer
const int = hash.readUInt32BE(0); // take first 4 bytes
// Modulo to get 6 digits
const pin = (int % 1000000).toString().padStart(6, '0');
return pin;
}

View File

@@ -199,28 +199,19 @@ export async function pdfVerbrauchsausweisGewerbe(ausweis: VerbrauchsausweisGewe
if (bild) {
const file = await getS3File("ibc-images", `${bild.id}.jpg`);
if (file) {
let image: PDFImage;
image = await pdf.embedJpg(file);
const originalWidth = image.width;
const originalHeight = image.height;
// Calculate the scaling factor to fit within the maximum dimensions while maintaining proportions
const scaleFactor = Math.min(111 / originalWidth, 138 / originalHeight);
const scaledWidth = originalWidth * scaleFactor;
const scaledHeight = originalHeight * scaleFactor;
image = await pdf.embedJpg(file)
pages[0].drawImage(image, {
x: 460.5,
y: (height - 289 - scaledHeight) + 138, // Adjust y to align the image properly
width: scaledWidth,
height: scaledHeight
});
y: height - 289,
width: 111,
height: 138
})
}
}
// Checkmark Verbrauchsausweis.
pages[0].drawText("x", {
x: 41,
@@ -369,25 +360,25 @@ export async function pdfVerbrauchsausweisGewerbe(ausweis: VerbrauchsausweisGewe
if (endenergieverbrauchTranslationPercentage > 0.5) {
page.drawText("Endenergieverbrauch Wärme", {
x: endenergieverbrauchTranslationX - margin - font.widthOfTextAtSize("Endenergieverbrauch Wärme", 10) - (pfeilWidth / 2),
x: endenergieverbrauchTranslationX - margin - font.widthOfTextAtSize("Endenergieverbrauch Wärme", 10),
y: height - 191,
size: 10
})
page.drawText(endEnergieVerbrauchGesamtText, {
x: endenergieverbrauchTranslationX - margin - bold.widthOfTextAtSize(endEnergieVerbrauchGesamtText, 10) - (pfeilWidth / 2),
x: endenergieverbrauchTranslationX - margin - bold.widthOfTextAtSize(endEnergieVerbrauchGesamtText, 10),
y: height - 205,
size: 10,
font: bold
})
} else {
page.drawText("Endenergieverbrauch Wärme", {
x: endenergieverbrauchTranslationX + pfeilWidth + margin + (pfeilWidth / 2),
x: endenergieverbrauchTranslationX + pfeilWidth + margin,
y: height - 191,
size: 10
})
page.drawText(endEnergieVerbrauchGesamtText, {
x: endenergieverbrauchTranslationX + pfeilWidth + margin + (pfeilWidth / 2),
x: endenergieverbrauchTranslationX + pfeilWidth + margin,
y: height - 205,
size: 10,
font: bold
@@ -398,13 +389,13 @@ export async function pdfVerbrauchsausweisGewerbe(ausweis: VerbrauchsausweisGewe
if (vergleichsWertWaermeTranslationPercentage > 0.5) {
page.drawText("Vergleichswert Wärme", {
x: vergleichsWertWaermeTranslationX - margin - font.widthOfTextAtSize("Vergleichswert Wärme", 10) - (pfeilWidth / 2),
x: vergleichsWertWaermeTranslationX - margin - font.widthOfTextAtSize("Vergleichswert Wärme", 10),
y: height - 275,
size: 10
})
page.drawText(vergleichswertWaermeText, {
x: vergleichsWertWaermeTranslationX - margin - bold.widthOfTextAtSize(vergleichswertWaermeText, 10) - (pfeilWidth / 2),
x: vergleichsWertWaermeTranslationX - margin - bold.widthOfTextAtSize(vergleichswertWaermeText, 10),
y: height - 289,
size: 10,
font: bold
@@ -431,7 +422,7 @@ export async function pdfVerbrauchsausweisGewerbe(ausweis: VerbrauchsausweisGewe
})
page.drawImage(pfeilNachOben, {
x: vergleichsWertStromTranslationX,
x: vergleichsWertStromTranslationX,
y: height - 437,
width: pfeilWidth,
height: 30
@@ -441,25 +432,25 @@ export async function pdfVerbrauchsausweisGewerbe(ausweis: VerbrauchsausweisGewe
if (stromVerbrauchTranslationPercentage > 0.5) {
page.drawText("Endenergieverbrauch Strom", {
x: stromVerbrauchTranslationX - margin - font.widthOfTextAtSize("Endenergieverbrauch Strom", 10) - (pfeilWidth / 2),
x: stromVerbrauchTranslationX - margin - font.widthOfTextAtSize("Endenergieverbrauch Strom", 10),
y: height - 335,
size: 10
})
page.drawText(stromVerbrauchGesamtText, {
x: stromVerbrauchTranslationX - margin - bold.widthOfTextAtSize(stromVerbrauchGesamtText, 10) - (pfeilWidth / 2),
x: stromVerbrauchTranslationX - margin - bold.widthOfTextAtSize(stromVerbrauchGesamtText, 10),
y: height - 349,
size: 10,
font: bold
})
} else {
page.drawText("Endenergieverbrauch Strom", {
x: stromVerbrauchTranslationX + pfeilWidth + margin + (pfeilWidth / 2),
x: stromVerbrauchTranslationX + pfeilWidth + margin,
y: height - 335,
size: 10
})
page.drawText(stromVerbrauchGesamtText, {
x: stromVerbrauchTranslationX + pfeilWidth + margin + (pfeilWidth / 2),
x: stromVerbrauchTranslationX + pfeilWidth + margin,
y: height - 349,
size: 10,
font: bold
@@ -484,12 +475,12 @@ export async function pdfVerbrauchsausweisGewerbe(ausweis: VerbrauchsausweisGewe
})
} else {
page.drawText("Vergleichswert Strom", {
x: vergleichsWertStromTranslationX + pfeilWidth + margin + (pfeilWidth / 2),
x: vergleichsWertStromTranslationX + pfeilWidth + margin,
y: height - 420,
size: 10
})
page.drawText(vergleichswertStromText, {
x: vergleichsWertStromTranslationX + pfeilWidth + margin + (pfeilWidth / 2),
x: vergleichsWertStromTranslationX + pfeilWidth + margin,
y: height - 434,
size: 10,
font: bold

View File

@@ -59,23 +59,13 @@ export async function pdfVerbrauchsausweisWohnen(ausweis: VerbrauchsausweisWohne
if (file) {
let image: PDFImage;
image = await pdf.embedJpg(file);
const originalWidth = image.width;
const originalHeight = image.height;
// Calculate the scaling factor to fit within the maximum dimensions while maintaining proportions
const scaleFactor = Math.min(111 / originalWidth, 138 / originalHeight);
const scaledWidth = originalWidth * scaleFactor;
const scaledHeight = originalHeight * scaleFactor;
image = await pdf.embedJpg(file)
pages[0].drawImage(image, {
x: 460.5,
y: (height - 289 - scaledHeight) + 138, // Adjust y to align the image properly
width: scaledWidth,
height: scaledHeight
});
y: height - 289,
width: 111,
height: 138
})
}
}
@@ -343,7 +333,7 @@ export async function pdfVerbrauchsausweisWohnen(ausweis: VerbrauchsausweisWohne
}
page.drawImage(pfeilNachUnten, {
x: endenergieverbrauchTranslationX - (pfeilWidth / 2),
x: endenergieverbrauchTranslationX,
y: height - 212,
width: pfeilWidth,
height: 30
@@ -380,7 +370,7 @@ export async function pdfVerbrauchsausweisWohnen(ausweis: VerbrauchsausweisWohne
}
page.drawImage(pfeilNachOben, {
x: primaerenergieverbrauchTranslationX - (pfeilWidth / 2),
x: primaerenergieverbrauchTranslationX,
y: height - 297,
width: pfeilWidth,
height: 30
@@ -505,7 +495,7 @@ export async function pdfVerbrauchsausweisWohnen(ausweis: VerbrauchsausweisWohne
const addVerbrauch = addVerbrauchGenerator();
if (ausweis.warmwasser_enthalten !== true) {
if (!ausweis.warmwasser_enthalten) {
// Mit Warmwasserzuschlag
addVerbrauch(
moment(ausweis.startdatum).format("MM.YYYY"),
@@ -555,7 +545,7 @@ export async function pdfVerbrauchsausweisWohnen(ausweis: VerbrauchsausweisWohne
"Warmwasserzuschlag",
berechnungen?.primaerfaktorww.toString(),
Math.round(berechnungen?.energieVerbrauchWarmwasser_1 || 0).toString(),
Math.round(berechnungen?.energieVerbrauchWarmwasser_1 || 0).toString(),
Math.round(berechnungen?.energieVerbrauchWarmwasser_1 || 0).toString(),
"0",
"0"
);

View File

@@ -1,41 +0,0 @@
import { transport } from "#lib/mail.js";
import {
Benutzer,
} from "#lib/client/prisma.js";
import { createTimeBasedHash } from "#lib/auth/time-based-hash.js";
export async function sendVerificationCodeMail(
user: Benutzer
) {
const code = createTimeBasedHash(user.email)
await transport.sendMail({
from: `"IBCornelsen" <info@online-energieausweis.org>`,
to: user.email,
subject: `Ihre Registrierung bei IBCornelsen`,
bcc: "info@online-energieausweis.org",
html: `<p>Sehr geehrte*r ${user.vorname} ${user.name},</p>
<p>Um Ihre Registrierung abzuschließen, geben Sie folgenden Bestätigungscode auf der Website ein, um Ihre E-Mail-Adresse zu bestätigen:<br><br>
<b>${code}</b>
</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

@@ -5,7 +5,6 @@ import {
} from "#lib/client/prisma.js";
import { encodeToken } from "#lib/auth/token.js";
import { TokenType } from "#lib/auth/types.js";
import { createTimeBasedHash } from "#lib/auth/time-based-hash.js";
export async function sendRegisterMail(
user: Benutzer
@@ -13,11 +12,9 @@ export async function sendRegisterMail(
const verificationJwt = encodeToken({
typ: TokenType.Verify,
exp: Date.now() + (15 * 60 * 1000),
id: user.id
uid: user.uid
})
const code = createTimeBasedHash(user.email)
await transport.sendMail({
from: `"IBCornelsen" <info@online-energieausweis.org>`,
to: user.email,
@@ -25,11 +22,10 @@ export async function sendRegisterMail(
bcc: "info@online-energieausweis.org",
html: `<p>Sehr geehrte*r ${user.vorname} ${user.name},</p>
<p>vielen Dank für Ihre Registrierung bei IBCornelsen. Ihr Benutzerkonto wurde erfolgreich erstellt.<br><br>
Um Ihre Registrierung abzuschließen, klicken Sie bitte auf den folgenden Link, um Ihre E-Mail-Adresse zu bestätigen:<br><br>
<a href="${BASE_URI}/auth/verify?t=${verificationJwt}">E-Mail-Adresse bestätigen</a><br><br>
Oder geben Sie folgenden Bestätigungscode auf der Website ein:<br>
<b>${code}</b>
</p>
<a href="${BASE_URI}/auth/verify?t=${verificationJwt}">E-Mail-Adresse bestätigen</a><br></p>
<p>
Mit freundlichen Grüßen,
<br>

View File

@@ -1,27 +1,9 @@
import { API_ACCESS_TOKEN_COOKIE_NAME } from "#lib/constants.js";
import { checkAuthorizationHeader, checkAuthorizationHeaderNoThrow } from "#lib/middleware/authorization.js";
import { AstroGlobal } from "astro";
import { Enums } from "#lib/client/prisma.js";
import { prisma } from "#lib/server/prisma.js";
export function getCurrentUser(Astro: AstroGlobal) {
const accessToken = Astro.cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)?.value;
return checkAuthorizationHeaderNoThrow(`Bearer ${accessToken}`)
}
export async function getOtherUser(Astro: AstroGlobal, userId : string) {
const accessToken = Astro.cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)?.value;
let currentUser = await checkAuthorizationHeaderNoThrow(`Bearer ${accessToken}`)
if (currentUser?.rolle == Enums.BenutzerRolle.ADMIN) {
const user = await prisma.benutzer.findUnique({
where: {
id: userId
}
})
return user;
}
return null;
}

View File

@@ -57,8 +57,7 @@
"bedarfsausweis-wohnen.ausweis"
);
if (localStorageAusweis) {
ausweis = JSON.parse(localStorageAusweis)
ausweis.ausweistyp = ausweistyp;
ausweis = JSON.parse(localStorageAusweis);
}
const localStorageAufnahme = localStorage.getItem(
@@ -148,7 +147,6 @@
{ausweisart}
{partner_code}
showWeiter={false}
{ausweistyp}
{form}
{skala}
></ButtonWeiterHilfe>
@@ -284,7 +282,6 @@
{ausweisart}
{partner_code}
showWeiter={true}
{ausweistyp}
{form}
{skala}
></ButtonWeiterHilfe>

View File

@@ -0,0 +1,18 @@
<script lang="ts">
const socket = new WebSocket("http://localhost:8080");
socket.on("open", () => {
console.log("Connected to WebSocket");
socket.send("Hello")
})
socket.on("message", (data) => {
console.log(data);
})
</script>
<div>
</div>

View File

@@ -32,12 +32,8 @@
import { getMaximumDevitationInPercent } from "#client/lib/helpers.js";
import { endEnergieVerbrauchVerbrauchsausweis_2016_Client } from "#lib/Berechnungen/VerbrauchsausweisWohnen/VerbrauchsausweisWohnen_2016_Client.js";
import { endEnergieVerbrauchVerbrauchsausweisGewerbe_2016_Client } from "#lib/Berechnungen/VerbrauchsausweisGewerbe/VerbrauchsausweisGewerbe_2016_Client.js";
import { benutzerSpeichern } from "#client/lib/speichern.js";
import { benutzerLesen } from "#client/lib/lesen.js";
import { exclude } from "#lib/exclude.js";
export let user: Partial<BenutzerClient>;
export let impersonatedUser: Partial<BenutzerClient> | null = null;
export let ausweis: VerbrauchsausweisWohnenClient | VerbrauchsausweisGewerbe | BedarfsausweisWohnen;
export let aufnahme: AufnahmeClient;
export let objekt: ObjektClient;
@@ -46,8 +42,9 @@
export let rechnung: RechnungClient | null = null;
export let ausweisart: Enums.Ausweisart;
export let aktiveBezahlmethode: Bezahlmethoden = Enums.Bezahlmethoden.paypal;
export let partner_code: string;
export let nurRechnungsadresseUpdate: Boolean | null = false;
let email: string, vorname: string, name: string, empfaenger: string, strasse: string, plz: string, ort: string, zusatzzeile: string, telefon: string;
@@ -62,22 +59,9 @@
ort = rechnung?.ort || localStorage.getItem("kundendaten.ort") || user.ort || "";
zusatzzeile = rechnung?.zusatzzeile || localStorage.getItem("kundendaten.zusatzzeile") || ""
telefon = rechnung?.telefon || localStorage.getItem("kundendaten.telefon") || user.telefon || "";
} else if (impersonatedUser) {
vorname = impersonatedUser.vorname || "";
name = impersonatedUser.name || "";
telefon = impersonatedUser.telefon || "";
email = impersonatedUser.email || "";
if (rechnung){
empfaenger = rechnung?.empfaenger || "";
strasse = rechnung?.strasse || "";
plz = rechnung?.plz || "";
ort = rechnung?.ort || "";
zusatzzeile = rechnung?.zusatzzeile || "";
email = rechnung?.email || "";
}
}
let abweichende_versand_adresse = rechnung?.abweichende_versand_adresse ?? JSON.parse(localStorage.getItem("kundendaten.abweichende_versand_adresse") || "false")
let abweichende_versand_adresse = JSON.parse(localStorage.getItem("kundendaten.abweichende_versand_adresse") || "false")
let versand_email: string | undefined,
versand_zusatzzeile: string | undefined,
@@ -85,13 +69,16 @@
versand_strasse: string | undefined,
versand_plz: string | undefined,
versand_ort: string | undefined;
versand_email = "";//Todo Datenbankfeld fehlt noch
versand_zusatzzeile = rechnung?.versand_zusatzzeile ?? zusatzzeile;
versand_empfaenger = rechnung?.versand_empfaenger ?? empfaenger;
versand_strasse = rechnung?.versand_strasse ?? strasse;
versand_plz = rechnung?.versand_plz ?? plz;
versand_ort = rechnung?.versand_ort ?? ort;
$: {
if (!abweichende_versand_adresse) {
versand_email = email
versand_zusatzzeile = zusatzzeile
versand_empfaenger = empfaenger
versand_strasse = strasse
versand_plz = plz
versand_ort = ort
}
}
$: {
// Wir speichern jede Änderung an den Kundendaten im localStorage ab.
@@ -212,12 +199,6 @@
}
try {
const merged_versand_empfaenger = versand_empfaenger || empfaenger;
const merged_versand_strasse = versand_strasse || strasse;
const merged_versand_plz = versand_plz || plz;
const merged_versand_ort = versand_ort || ort;
const merged_versand_zusatzzeile = versand_zusatzzeile || zusatzzeile;
const { id } = await api.rechnung.anfordern.PUT.fetch(
{
email: email,
@@ -225,11 +206,10 @@
strasse: strasse,
plz: plz,
ort: ort,
versand_empfaenger: merged_versand_empfaenger,
versand_strasse: merged_versand_strasse,
versand_plz: merged_versand_plz,
versand_ort: merged_versand_ort,
versand_zusatzzeile: merged_versand_zusatzzeile,
versand_empfaenger: versand_empfaenger,
versand_strasse: versand_strasse,
versand_plz: versand_plz,
versand_ort: versand_ort,
telefon: telefon,
nachweis_id: result.nachweis_id
},
@@ -253,7 +233,7 @@
}
}
async function speichern(authuser = null) {
async function speichern() {
loginAction = speichern;
if (!await validateAccessTokenClient()) {
loginOverlayHidden = false;
@@ -269,62 +249,6 @@
} else {
result = await ausweisSpeichern(ausweis, objekt, aufnahme, bilder, unterlagen, ausweisart)
}
if (authuser) {
user = await benutzerLesen(authuser.id);
}
let resultUser: Awaited<ReturnType<typeof benutzerSpeichern>> | Awaited<ReturnType<typeof benutzerSpeichern>> | null = null;
const { passwort, ...baseUser } = impersonatedUser ?? user;
const benutzerObjekt = {
...baseUser,
name,
vorname,
telefon
};
resultUser = await benutzerSpeichern(benutzerObjekt);
let id: string, checkout_url: string | undefined;
if (rechnung) {
const merged_versand_empfaenger = versand_empfaenger || empfaenger;
const merged_versand_strasse = versand_strasse || strasse;
const merged_versand_plz = versand_plz || plz;
const merged_versand_ort = versand_ort || ort;
const merged_versand_zusatzzeile = versand_zusatzzeile || zusatzzeile;
const result = await api.rechnung._id.PATCH.fetch({
bezahlmethode: aktiveBezahlmethode,
abweichende_versand_adresse: abweichende_versand_adresse,
empfaenger,
strasse,
plz,
ort,
telefon,
email,
zusatzzeile,
versand_empfaenger: merged_versand_empfaenger,
versand_strasse: merged_versand_strasse,
versand_plz: merged_versand_plz,
versand_ort: merged_versand_ort,
versand_zusatzzeile: merged_versand_zusatzzeile
}, {
params: {
id: rechnung.id
},
headers: {
Authorization: `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`,
},
});
id = result.id;
checkout_url = result.checkout_url;
}
} catch(e) {
addNotification({
dismissable: true,
@@ -345,7 +269,7 @@
}
}
async function bestellen(authuser = null) {
async function bestellen() {
if (!form.checkValidity()) {
addNotification({
dismissable: true,
@@ -388,22 +312,6 @@
}
}
if (authuser) {
user = await benutzerLesen(authuser.id);
}
let resultUser: Awaited<ReturnType<typeof benutzerSpeichern>> | Awaited<ReturnType<typeof benutzerSpeichern>> | null = null;
const { passwort, ...baseUser } = impersonatedUser ?? user;
const benutzerObjekt = {
...baseUser,
name,
vorname,
telefon
};
resultUser = await benutzerSpeichern(benutzerObjekt);
if (rechnung && rechnung.status === "paid") {
window.location.href = "/dashboard"
return;
@@ -412,12 +320,6 @@
try {
let id: string, checkout_url: string | undefined;
const merged_versand_empfaenger = versand_empfaenger || empfaenger;
const merged_versand_strasse = versand_strasse || strasse;
const merged_versand_plz = versand_plz || plz;
const merged_versand_ort = versand_ort || ort;
const merged_versand_zusatzzeile = versand_zusatzzeile || zusatzzeile;
if (rechnung) {
const result = await api.rechnung._id.PATCH.fetch({
bezahlmethode: aktiveBezahlmethode,
@@ -426,13 +328,12 @@
strasse: strasse,
plz: plz,
ort: ort,
versand_empfaenger: versand_empfaenger,
versand_strasse: versand_strasse,
versand_plz: versand_plz,
versand_ort: versand_ort,
telefon: telefon,
zusatzzeile: zusatzzeile,
versand_empfaenger: merged_versand_empfaenger,
versand_strasse: merged_versand_strasse,
versand_plz: merged_versand_plz,
versand_ort: merged_versand_ort,
versand_zusatzzeile: merged_versand_zusatzzeile
versand_zusatzzeile: versand_zusatzzeile
}, {
params: {
id: rechnung.id
@@ -457,16 +358,13 @@
strasse: strasse,
plz: plz,
ort: ort,
zusatzzeile: zusatzzeile,
versand_empfaenger: merged_versand_empfaenger,
versand_strasse: merged_versand_strasse,
versand_plz: merged_versand_plz,
versand_ort: merged_versand_ort,
versand_zusatzzeile: merged_versand_zusatzzeile,
versand_empfaenger: versand_empfaenger,
versand_strasse: versand_strasse,
versand_plz: versand_plz,
versand_ort: versand_ort,
telefon: telefon,
ausweis_id: ausweis.id,
partner_code,
abweichende_versand_adresse: abweichende_versand_adresse
partner_code
},
{
headers: {
@@ -506,7 +404,6 @@
let form: HTMLFormElement;
</script>
{#if !nurRechnungsadresseUpdate}
<div
id="skala"
class="bg-white grid grid-cols-1 gap-x-8 gap-y-4 px-0 sm:p-4
@@ -537,7 +434,7 @@
<Progressbar active={1} {ausweisart} ausweistyp={ausweis.ausweistyp} anliegen={"Energieausweis erstellen"} />
{/if}
</div>
{/if}
<form id="formInput-2" bind:this={form}>
<div id="formular-box" class="formular-boxen ring-0">
@@ -805,6 +702,7 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
<input
name="versand_ort"
type="text"
readonly
required
bind:value={versand_ort}
/>
@@ -841,7 +739,7 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
</div>
<!-- E-mail -->
<!-- Versand Email wird derzeit nicht verwendet
<div class="input-standard order-12 md:order-12 xl:order-12">
<InputLabel title="E-mail *"></InputLabel>
@@ -858,7 +756,7 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
</HelpLabel>
</div>
</div>
-->
<!-- Telefon
<div class="input-standard order-[13] md:order-[13] xl:order-[13]">
@@ -882,7 +780,7 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
</Bereich
>
{#if !gegAnfrage && !nurRechnungsadresseUpdate}
{#if !gegAnfrage}
<Bereich bereich="3" title="Bezahlmethode">
<div
id="bezahlung"
@@ -993,7 +891,7 @@ grid-cols-3 sm:grid-cols-5 justify-around justify-items-center items-center"
{/if}
<!-- Falls wir es mit einem Ausweis zu tun haben und der Nutzer keine Hilfe bestellt hat dann zeigen wir eine Box mit sachen an die vielleicht unklar sind. -->
{#if (ausweisart === Enums.Ausweisart.VerbrauchsausweisWohnen || ausweisart === Enums.Ausweisart.VerbrauchsausweisGewerbe || ausweisart === Enums.Ausweisart.BedarfsausweisWohnen) && (ausweis.ausweistyp === Enums.AusweisTyp.Standard || ausweis.ausweistyp === Enums.AusweisTyp.standardXL) && (!nurRechnungsadresseUpdate)}
{#if (ausweisart === Enums.Ausweisart.VerbrauchsausweisWohnen || ausweisart === Enums.Ausweisart.VerbrauchsausweisGewerbe || ausweisart === Enums.Ausweisart.BedarfsausweisWohnen) && (ausweis.ausweistyp === Enums.AusweisTyp.Standard || ausweis.ausweistyp === Enums.AusweisTyp.standardXL)}
<div class="grid grid-cols-1 sm:grid-cols-1 gap-x-6 my-6">
<div class="pruefpunkte bereich-box bg-white">
<h3>Bitte untenstehende Punkte bestätigen. Bitte gehen Sie gegebenenfalls zurück zum Formular und überprüfen bzw. korrigieren Ihre Eingaben.</h3>
@@ -1174,33 +1072,31 @@ sm:grid-cols-[min-content_min-content_min-content] sm:justify-self-end sm:mt-8"
<button class="order-2 button" type="button" on:click={() => speichern()}>Speichern</button>
<button class="order-2 button" type="button" on:click={speichern}>Speichern</button>
{#if !nurRechnungsadresseUpdate}
{#if rechnung && rechnung.status === "paid"}
<!-- Von einer GEG Anfrage sollte man sowieso nicht noch mal auf die Kundendaten Seite gelangen, also brauchen wir das hier nicht. -->
{#if rechnung && rechnung.status === "paid"}
<!-- Von einer GEG Anfrage sollte man sowieso nicht noch mal auf die Kundendaten Seite gelangen, also brauchen wir das hier nicht. -->
<button
class="order-1 sm:order-2 button cursor-pointer"
data-cy="bestellen"
type="button"
on:click={bestellen}>Absenden</button
>
{:else}
{#if gegAnfrage}
<button
class="order-1 sm:order-2 button cursor-pointer"
type="button"
data-cy="bestellen"
on:click={anfordern}>Angebot anfordern</button
>
{:else}
<button
class="order-1 sm:order-2 button cursor-pointer"
data-cy="bestellen"
type="button"
on:click={() => bestellen()}>Absenden</button
on:click={bestellen}>Kostenpflichtig bestellen</button
>
{:else}
{#if gegAnfrage}
<button
class="order-1 sm:order-2 button cursor-pointer"
type="button"
data-cy="bestellen"
on:click={anfordern}>Angebot anfordern</button
>
{:else}
<button
class="order-1 sm:order-2 button cursor-pointer"
data-cy="bestellen"
type="button"
on:click={() => bestellen()}>Kostenpflichtig bestellen</button
>
{/if}
{/if}
{/if}

View File

@@ -13,10 +13,6 @@
export let redirect: string | null = null;
function handleInput(event) {
email = event.target.value.toLowerCase();
}
async function login(e: SubmitEvent) {
e.preventDefault()
if (passwort.length < 8) {
@@ -30,7 +26,7 @@
}
try {
await api.user.PUT.fetch({
const { uid } = await api.user.PUT.fetch({
email,
passwort,
vorname,
@@ -42,7 +38,7 @@
return
}
window.location.href = `/auth/code?email=${email}`;
window.location.href = "/auth/login";
} catch (e) {
errorHidden = false;
}
@@ -87,7 +83,6 @@
name="email"
class="input input-bordered text-base text-base-content font-medium"
bind:value={email}
on:input={handleInput}
required
/>
</div>

View File

@@ -63,7 +63,6 @@
const localStorageAusweis = localStorage.getItem("verbrauchsausweis-gewerbe.ausweis");
if (localStorageAusweis) {
ausweis = JSON.parse(localStorageAusweis)
ausweis.ausweistyp = ausweistyp;
}
const localStorageAufnahme = localStorage.getItem("verbrauchsausweis-gewerbe.aufnahme");
@@ -129,7 +128,6 @@
bind:blockLocalStorageSync
ausweisart={Enums.Ausweisart.VerbrauchsausweisGewerbe}
showWeiter={false}
{ausweistyp}
{form}
{partner_code}
{skala}
@@ -247,7 +245,6 @@
bind:blockLocalStorageSync
ausweisart={Enums.Ausweisart.VerbrauchsausweisGewerbe}
showWeiter={true}
{ausweistyp}
{form}
{partner_code}
{skala}

View File

@@ -1,89 +0,0 @@
<script lang="ts">
import { addNotification } from "#components/Notifications/shared.js";
import { CrossCircled } from "radix-svelte-icons";
import { fade } from "svelte/transition";
import { api } from "astro-typesafe-api/client";
import NotificationWrapper from "#components/Notifications/NotificationWrapper.svelte";
export let redirect: string | null = null;
export let email: string;
function verify(e: SubmitEvent) {
e.preventDefault();
const code = numbers.join("");
if (code.length !== 6) {
addNotification({
message: "Bitte geben Sie einen gültigen Verifizierungscode ein.",
dismissable: true,
timeout: 3000,
type: "error"
});
return;
}
api.auth["verification-code"].POST.fetch({ code, email }).then(() => {
if (redirect) {
window.location.href = redirect;
} else {
window.location.href = "/";
}
}).catch(() => {
errorHidden = false;
});
}
// TODO
function codeErneutAnfordern() {
api.auth["verification-code"].GET.fetch(null, {
headers: {
"Authorization": "Bearer"
}
})
}
let numbers = new Array(6).fill("");
let errorHidden = true;
</script>
<div class="mx-auto w-1/3 bg-base-200 p-8 border border-base-300 rounded-lg">
<h1 class="text-3xl mb-4">Verifizierung</h1>
<p>Wir haben ihnen einen Verifizierungscode per Email geschickt, bitte geben sie diesen ein um ihre Registrierung fertigzustellen.</p>
<form on:submit={verify}>
<div class="flex flex-row gap-4 w-full justify-center my-12">
{#each { length: 6 } as _, i}
<input
type="text"
class="input input-bordered text-4xl text-base-content font-medium w-12 text-center"
bind:value={numbers[i]}
maxlength="1"
on:input={function(e) {
if (i !== 5) {
e.target.nextSibling.focus()
}
}}
required
/>
{/each}
</div>
<div class="flex justify-between">
<button type="submit" class="button"
>Abschicken</button
>
<!-- <button type="button" on:click={codeErneutAnfordern} class="button"
>Code erneut anfordern</button
> -->
</div>
{#if !errorHidden}
<div class="flex flex-row gap-4 mt-8" in:fade out:fade={{delay: 400}}>
<CrossCircled size={24} />
<span class="font-semibold"> Da ist wohl etwas schiefgelaufen. Der eingegebene Verifizierungscode ist ungültig.</span>
</div>
{/if}
</form>
<NotificationWrapper></NotificationWrapper>
</div>

View File

@@ -1,5 +1,7 @@
---
import AusweisLayout from "#layouts/AusweisLayoutDatenPartner.astro";
import VerbrauchsausweisWohnenModule from "#modules/VerbrauchsausweisWohnen/VerbrauchsausweisWohnenModule.svelte";
import { AufnahmeClient, BildClient, ObjektClient, UploadedGebaeudeBild, VerbrauchsausweisWohnenClient } from "#components/Ausweis/types";
import { Aufnahme, BedarfsausweisGewerbe, Bild, Enums, Objekt, Unterlage, VerbrauchsausweisWohnen } from "#lib/server/prisma";
import { getAufnahme, getBedarfsausweisGewerbe, getBilder, getObjekt, getUnterlagen, getVerbrauchsausweisWohnen } from "#lib/server/db";
import { getCurrentUser } from "#lib/server/user";
@@ -7,7 +9,7 @@ import BedarfsausweisGewerbeModule from "#modules/angebot-anfragen/Bedarfsauswei
const id = Astro.url.searchParams.get("id");
const aufnahme_id = Astro.url.searchParams.get("aufnahme")
let nachweistyp = Astro.url.searchParams.get("ausweistyp") as Enums.AusweisTyp || Enums.AusweisTyp.Standard;
let nachweistyp = Astro.url.searchParams.get("nachweistyp") as Enums.AusweisTyp || Enums.AusweisTyp.Standard;
let nachweis: BedarfsausweisGewerbe = {} as BedarfsausweisGewerbe;
let aufnahme: Aufnahme = {} as Aufnahme;

View File

@@ -7,7 +7,7 @@ import GEGNachweisGewerbeModule from "#modules/angebot-anfragen/GEGNachweisGewer
const id = Astro.url.searchParams.get("id");
const aufnahme_id = Astro.url.searchParams.get("aufnahme")
let nachweistyp = Astro.url.searchParams.get("ausweistyp") as Enums.AusweisTyp || Enums.AusweisTyp.Standard;
let nachweistyp = Astro.url.searchParams.get("nachweistyp") as Enums.AusweisTyp || Enums.AusweisTyp.Standard;
let nachweis: GEGNachweisGewerbe = {} as GEGNachweisGewerbe;
let aufnahme: Aufnahme = {} as Aufnahme;

View File

@@ -17,6 +17,7 @@ import { APIError, defineApiRoute } from "astro-typesafe-api/server";
import { z } from "astro:content";
import { transport } from "#lib/mail.js";
import {Attachment} from "nodemailer/lib/mailer/index.js";
import { BASE_URI } from "#lib/constants.js";
import { getAnsichtsausweis, getDatenblatt, getAushang } from "#lib/server/ausweis.js";
import { PutObjectCommand } from "@aws-sdk/client-s3";
import { s3Client } from "#lib/s3.js";
@@ -33,6 +34,7 @@ import {
getVerbrauchsausweisWohnenKomplett,
} from "#lib/server/db.js";
import { PDFDocument } from "pdf-lib";
import * as fs from 'fs';
export const GET = defineApiRoute({
input: z.object({
@@ -327,14 +329,14 @@ export const GET = defineApiRoute({
<p>im Anhang finden Sie Ihren geprüften Energieusweis inkl. Rechnung als PDF-Datei. ${
post ? "Zusätzlich haben wir Ihren Ausweis per Post verschickt" : ""
} <b>Bitte beachten Sie unsere neue Bankverbindung.</b> Bitte geben Sie als Verwendungszweck die Rechnungsnummer an (siehe unten). Vielen Dank.</p>
} Nachfolgend finden Sie unsere Bankverbindung. Bitte geben Sie als Verwendungszweck die Rechnungsnummer an (siehe unten). Vielen Dank.</p>
<br>
<table>
<tr><td>Kreditinstitut</td><td>:</td><td>\t Volksbank eG</td>
<tr><td>Kreditinstitut</td><td>:</td><td>\t Commerzbank AG</td>
<tr><td>Empfänger</td><td>:</td><td>\t IB Cornelsen</td>
<tr><td>IBAN</td><td>:<td>\t DE13 2519 3331 7209 0731 00</td>
<tr><td>BIC</td><td>:</td><td>\t GENODEF1PAT</td>
<tr><td>IBAN</td><td>:<td>\t DE81 2004 0000 0348 6008 00</td>
<tr><td>BIC</td><td>:</td><td>\t COBADEFFXXX</td>
<tr><td>Betrag</td><td>:</td><td>\t <b>${rechnung.betrag}€</b></td>
<tr><td>Verwendungszweck</td><td>:</td><td>\t <b>${voucherNumber}</b></td>
</table>

View File

@@ -197,7 +197,7 @@ export const POST = defineApiRoute({
let filename: string;
if (type === "Ausweis") {
filename = `ID_${ausweis.id}_Energieausweis.pdf`
filename = `ID_${ausweis.id}_Ausweis.pdf`
} else {
filename = `ID_${ausweis.id}_${name}`;
}
@@ -212,7 +212,7 @@ export const POST = defineApiRoute({
const command = new PutObjectCommand({
Bucket: "ibc-pdfs",
Key: filename,
Key: name,
Body: buffer,
ACL: "private",
});
@@ -336,14 +336,14 @@ export const POST = defineApiRoute({
<p>im Anhang finden Sie Ihren geprüften Energieusweis inkl. Rechnung als PDF-Datei. ${
post ? "Zusätzlich haben wir Ihren Ausweis per Post verschickt" : ""
} <b>Bitte beachten Sie unsere neue Bankverbindung.</b> Bitte geben Sie als Verwendungszweck die Rechnungsnummer an (siehe unten). Vielen Dank.</p>
} Nachfolgend finden Sie unsere Bankverbindung. Bitte geben Sie als Verwendungszweck die Rechnungsnummer an (siehe unten). Vielen Dank.</p>
<br>
<table>
<tr><td>Kreditinstitut</td><td>:</td><td>\t Volksbank eG</td>
<tr><td>Kreditinstitut</td><td>:</td><td>\t Commerzbank AG</td>
<tr><td>Empfänger</td><td>:</td><td>\t IB Cornelsen</td>
<tr><td>IBAN</td><td>:<td>\t DE13 2519 3331 7209 0731 00</td>
<tr><td>BIC</td><td>:</td><td>\t GENODEF1PAT</td>
<tr><td>IBAN</td><td>:<td>\t DE81 2004 0000 0348 6008 00</td>
<tr><td>BIC</td><td>:</td><td>\t COBADEFFXXX</td>
<tr><td>Betrag</td><td>:</td><td>\t <b>${rechnung.betrag}€</b></td>
<tr><td>Verwendungszweck</td><td>:</td><td>\t <b>${voucherNumber}</b></td>
</table>

View File

@@ -62,7 +62,6 @@ export const GET = defineApiRoute({
exp: refreshTokenExpiry.valueOf(),
});
console.log("Creating refresh token for user ID:", user.id);
const { id } = await prisma.refreshTokens.create({
data: {
token: refreshToken,

View File

@@ -1,70 +0,0 @@
import { z } from "zod";
import { prisma } from "#lib/server/prisma.js";
import { APIError, defineApiRoute } from "astro-typesafe-api/server";
import { sendVerificationCodeMail } from "#lib/server/mail/code.js";
import { authorizationMiddleware } from "#lib/middleware/authorization.js";
import { createTimeBasedHash } from "#lib/auth/time-based-hash.js";
export const GET = defineApiRoute({
meta: {
description:
"Fragt einen neuen Verifizierungscode per Mail an.",
tags: ["Benutzer"],
summary: "Verifizierungscode anfragen.",
},
middleware: authorizationMiddleware,
output: z.void(),
async fetch(input, ctx) {
// Falls der Nutzer nicht existiert, wird eine Fehlermeldung zurückgegeben.
const user = await prisma.benutzer.findUnique({
where: {
email: input.email.toLowerCase(),
},
});
if (!user) {
throw new APIError({
code: "BAD_REQUEST",
message: "Benutzer konnte nicht gefunden werden.",
});
}
await sendVerificationCodeMail(user);
},
});
export const POST = defineApiRoute({
meta: {
description:
"Versucht den Nutzer mithilfe des abgeschickten Codes zu verifizieren.",
tags: ["Benutzer"],
summary: "Verifizieren.",
},
input: z.object({
code: z.string(),
email: z.string().email().toLowerCase(),
}),
output: z.void(),
async fetch({ code, email }, ctx) {
const generatedCode = createTimeBasedHash(email);
console.log(generatedCode, code);
if (code !== generatedCode) {
throw new APIError({
code: "BAD_REQUEST",
message: "Der eingegebene Verifizierungscode ist ungültig.",
});
}
await prisma.benutzer.update({
where: {
email: email.toLowerCase(),
},
data: {
verified: true,
},
});
},
});

View File

@@ -16,14 +16,12 @@ export const PATCH = defineApiRoute({
strasse: true,
telefon: true,
empfaenger: true,
zusatzzeile: true,
versand_empfaenger: true,
versand_ort: true,
versand_plz: true,
versand_strasse: true,
versand_zusatzzeile: true,
abweichende_versand_adresse: true,
email: true
abweichende_versand_adresse: true
}),
output: z.object({
checkout_url: z.string().optional(),
@@ -33,39 +31,22 @@ export const PATCH = defineApiRoute({
headers: authorizationHeaders,
async fetch(input, context, user) {
// Wir holen uns die Rechnung
let rechnung;
if (user.rolle !== Enums.BenutzerRolle.ADMIN) {
rechnung = await prisma.rechnung.findUnique({
where: {
id: context.params.id,
benutzer: {
id: user.id
}
},
include: {
bedarfsausweis_gewerbe: true,
bedarfsausweis_wohnen: true,
geg_nachweis_gewerbe: true,
geg_nachweis_wohnen: true,
verbrauchsausweis_gewerbe: true,
verbrauchsausweis_wohnen: true
const rechnung = await prisma.rechnung.findUnique({
where: {
id: context.params.id,
benutzer: {
id: user.id
}
})
} else {
rechnung = await prisma.rechnung.findUnique({
where: {
id: context.params.id
},
include: {
bedarfsausweis_gewerbe: true,
bedarfsausweis_wohnen: true,
geg_nachweis_gewerbe: true,
geg_nachweis_wohnen: true,
verbrauchsausweis_gewerbe: true,
verbrauchsausweis_wohnen: true
}
})
}
},
include: {
bedarfsausweis_gewerbe: true,
bedarfsausweis_wohnen: true,
geg_nachweis_gewerbe: true,
geg_nachweis_wohnen: true,
verbrauchsausweis_gewerbe: true,
verbrauchsausweis_wohnen: true
}
})
if (!rechnung) {
throw new APIError({
@@ -85,14 +66,12 @@ export const PATCH = defineApiRoute({
strasse: input.strasse,
telefon: input.telefon,
empfaenger: input.empfaenger,
zusatzzeile: input.zusatzzeile,
versand_empfaenger: input.versand_empfaenger,
versand_ort: input.versand_ort,
versand_plz: input.versand_plz,
versand_strasse: input.versand_strasse,
versand_zusatzzeile: input.versand_zusatzzeile,
abweichende_versand_adresse: input.abweichende_versand_adresse,
email: input.email
}
})

View File

@@ -5,17 +5,18 @@ import { adminMiddleware, authorizationMiddleware } from "#lib/middleware/author
import { hashPassword } from "#lib/password.js";
import { createLexOfficeCustomer } from "#lib/server/lexoffice.js";
import { sendRegisterMail } from "#lib/server/mail/registrierung.js";
import { Benutzer, prisma } from "#lib/server/prisma.js";
import { prisma } from "#lib/server/prisma.js";
import { APIError, defineApiRoute } from "astro-typesafe-api/server";
import { BenutzerSchema } from "src/generated/zod/benutzer.js";
import { z } from "zod";
import { Enums } from "#lib/client/prisma.js";
export const POST = defineApiRoute({
input: BenutzerSchema.omit({
id: true,
lex_office_id: true,
rolle: true,
created_at: true
created_at: true,
updated_at: true
}),
middleware: authorizationMiddleware,
async fetch(input, context, user) {
@@ -23,36 +24,24 @@ export const POST = defineApiRoute({
// TODO: Email wurde geändert, neue Bestätigunsmail schicken.
}
const updateData: any = {};
updateData.id = user.id;
if (input.adresse) updateData.adresse = input.adresse;
if (input.anrede) updateData.anrede = input.anrede;
if (input.email) updateData.email = input.email;
if (input.firma) updateData.firma = input.firma;
if (input.name) updateData.name = input.name;
if (input.vorname) updateData.vorname = input.vorname;
if (input.ort) updateData.ort = input.ort;
if (input.passwort.length != 0) updateData.passwort = hashPassword(input.passwort);
if (input.plz) updateData.plz = input.plz;
if (input.profilbild) updateData.profilbild = input.profilbild;
if (input.telefon) updateData.telefon = input.telefon;
if (input.verified) updateData.telefon = input.verified;
//Only Admin can update other users
if (user.rolle == Enums.BenutzerRolle.ADMIN && input.id != user.id) {
updateData.id = input.id;
} else if(user.rolle != Enums.BenutzerRolle.ADMIN && input.id != user.id){
return;
}
await prisma.benutzer.update({
where: {
id: updateData.id
id: user.id
},
data: updateData
});
data: {
adresse: input.adresse,
anrede: input.anrede,
email: input.email,
firma: input.firma,
name: input.name,
vorname: input.vorname,
ort: input.ort,
passwort: hashPassword(input.passwort),
plz: input.plz,
profilbild: input.profilbild,
telefon: input.telefon,
}
})
},
})
@@ -64,14 +53,9 @@ export const GET = defineApiRoute({
email: z.string()
})),
output: z.array(BenutzerSchema),
middleware: authorizationMiddleware,
middleware: adminMiddleware,
async fetch(input, context, admin) {
if ("id" in input) {
//Only Admin can read other users
if (admin.rolle != Enums.BenutzerRolle.ADMIN && input.id != admin.id) {
return;
}
const user = await prisma.benutzer.findUnique({
where: {
id: input.id
@@ -84,11 +68,6 @@ export const GET = defineApiRoute({
return [user];
} else {
//Only admin can read many users
if (admin.rolle != Enums.BenutzerRolle.ADMIN ) {
return;
}
const users = await prisma.benutzer.findMany({
where: {
email: {

View File

@@ -1,24 +0,0 @@
---
import CodeModule from "../../modules/auth/CodeModule.svelte";
import MinimalLayout from "#layouts/MinimalLayout.astro";
import { validateAccessTokenServer } from "#server/lib/validateAccessToken";
const valid = await validateAccessTokenServer(Astro)
if (valid) {
return Astro.redirect("/dashboard")
}
const redirect = Astro.url.searchParams.get("redirect");
const email = Astro.url.searchParams.get("email");
if (!email) {
return Astro.redirect("/");
}
---
<MinimalLayout title="Verifizierung - IBCornelsen">
<CodeModule client:load {redirect} {email}></CodeModule>
</MinimalLayout>

8
src/pages/chat.astro Normal file
View File

@@ -0,0 +1,8 @@
---
import Layout from "#layouts/Layout.astro";
import ChatModule from "#modules/ChatModule.svelte";
---
<Layout title="Websockets">
<ChatModule client:only></ChatModule>
</Layout>

View File

@@ -1,334 +0,0 @@
---
import AbrechungTable from "#components/Abrechnung/AbrechungTable.svelte";
import { Enums, prisma } from "#lib/server/prisma";
import { getCurrentUser } from "#lib/server/user";
import moment from "moment";
const start = moment(Astro.url.searchParams.get("start"))
const end = moment(Astro.url.searchParams.get("end"))
let startdatum = start.toDate();
let enddatum = end.toDate();
const benutzer = await getCurrentUser(Astro)
if (!benutzer) {
return Astro.redirect("/404")
}
const provisionen={
[Enums.Ausweisart.VerbrauchsausweisWohnen]: 10,
[Enums.Ausweisart.BedarfsausweisWohnen]: 10,
[Enums.Ausweisart.VerbrauchsausweisGewerbe]: 10,
}
// $kommission = db()->one("SELECT abr_va, abr_ba, abr_vanw FROM users WHERE resellercode = :resellercode", ["resellercode" => $resellercode]);
// Select every entry from database where user was involved.
let bestellungen;
if (start.isValid() && end.isValid()) {
bestellungen = await prisma.rechnung.findMany({
where: {
partner_code: "immowelt",
OR: [{
verbrauchsausweis_gewerbe: {
ausgestellt: true
}
},
{
bedarfsausweis_wohnen: {
ausgestellt: true
}
},
{
verbrauchsausweis_wohnen: {
ausgestellt: true
}
}],
AND: [{
created_at: {
gte: startdatum
},
}, {
created_at: {
lte: enddatum
},
}]
},
orderBy: {
created_at: "desc"
},
include: {
bedarfsausweis_wohnen: true,
verbrauchsausweis_gewerbe: true,
verbrauchsausweis_wohnen: true
}
});
} else {
bestellungen = await prisma.rechnung.findMany({
where: {
partner_code: "immowelt",
OR: [{
verbrauchsausweis_gewerbe: {
ausgestellt: true
}
},
{
bedarfsausweis_wohnen: {
ausgestellt: true
}
},
{
verbrauchsausweis_wohnen: {
ausgestellt: true
}
}]
},
orderBy: {
created_at: "desc"
},
include: {
bedarfsausweis_wohnen: true,
verbrauchsausweis_gewerbe: true,
verbrauchsausweis_wohnen: true
}
});
}
// Wann wurde der partner_code zum ersten mal benutzt?
const partnerCodeErstesMal = (await prisma.rechnung.findFirst({
select: {
created_at: true
},
where: {
partner_code: "immowelt",
OR: [{
verbrauchsausweis_gewerbe: {
ausgestellt: true
}
},
{
bedarfsausweis_wohnen: {
ausgestellt: true
}
},
{
verbrauchsausweis_wohnen: {
ausgestellt: true
}
}],
created_at: {
gte: moment().set("year", 2020).set("dayOfYear", 1).toDate()
}
},
orderBy: {
created_at: "asc"
}
}))?.created_at
let provision = 0;
const ausweisarten: string[] = [];
for (const bestellung of bestellungen) {
if (bestellung.verbrauchsausweis_wohnen) {
ausweisarten.push(Enums.Ausweisart.VerbrauchsausweisWohnen)
provision += provisionen[Enums.Ausweisart.VerbrauchsausweisWohnen]
}
if (bestellung.bedarfsausweis_wohnen) {
ausweisarten.push(Enums.Ausweisart.BedarfsausweisWohnen)
provision += provisionen[Enums.Ausweisart.BedarfsausweisWohnen]
}
if (bestellung.verbrauchsausweis_gewerbe) {
ausweisarten.push(Enums.Ausweisart.VerbrauchsausweisGewerbe)
provision += provisionen[Enums.Ausweisart.VerbrauchsausweisGewerbe]
}
}
---
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>Reporting | online-energieausweis.org</title>
<meta charset="utf-8">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<script type="text/javascript" src="https://cdn.jsdelivr.net/jquery/latest/jquery.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/momentjs/latest/moment.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.min.js"></script>
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.css" />
<link rel="stylesheet" href="./main.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
</head>
<style>
body {
font-family: arial;
}
#inputwrap {
position: fixed;
top: 0;
left: 10%;
width: 70%;
padding-top: 10px;
padding-bottom: 10px;
background: #fff;
}
#cal {
margin-right: 10px !important;
}
#demo {
width: 76%;
display: inline-block;
}
table tr,
td {
border: 0.1em solid #000;
padding: 0;
margin: 0;
}
#QTT {
border-collapse: collapse;
width: 70%;
margin-top: 8em;
margin-left: 10%;
table-layout: auto;
}
#QTT thead td {
background: #ff7d26;
font-weight: bold;
}
#QTT tr:nth-child(even) {
background-color: #f2f2f2;
}
#QTT td {
padding: 0.4em 0.4em 0.4em 0.4em;
text-align: right;
}
#logo1 {
display: inline-block;
margin-right: 1em;
margin-top: -3px;
}
#logo2 {
width: 18%;
margin-bottom: 0.5em;
float: right;
padding-top: -1px;
margin-right: -5px;
}
</style>
<div id='inputwrap' class='form-group' >
<div style='display:flex; justify-content: space-between; align-items:center;'>
<img id='logo1' src='https://widget.ib-cornelsen.de/OEA_WIDGETS/img/IBC-logo.png' alt='IBCornelsen' />
<h5 style='margin-top: 10px;'><b>Erziehlte Conversions von {benutzer.email}</b></h5>
</div>
<input type='text' id='demo' class='form-control' name='demo' value='' placeholder='Bitte Zeitraum auswählen' />
</div>
<AbrechungTable bestellungen={bestellungen} {provisionen} {partnerCodeErstesMal}></AbrechungTable>
<div id="total" class="footer">
<div class="inner">
<div>
<p id="betrag_gesamt">Abrechnungsbetrag gesamt: <b>{provision} €</b></p>
</div>
<a target='_blank' rel='noreferrer noopener' href=`/user/abrechnung/pdf.php?month=${moment().subtract(1, "month").get("month")}&year=${moment().subtract(1, "month").get("year")}`>PDF für letzten Monat generieren.</a>
</div>
</div>
<script type="text/javascript">
$('#demo').daterangepicker({
"showDropdowns": true,
"minYear": 2019,
ranges: {
'Heute': [moment(), moment()],
'Gestern': [moment().subtract(1, 'days'), moment().subtract(1, 'days')],
'letzte 7 Tage': [moment().subtract(6, 'days'), moment()],
'letzte 30 Tage': [moment().subtract(29, 'days'), moment()],
'dieser Monat': [moment().startOf('month'), moment().endOf('month')],
'letzter Monat': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')]
},
"locale": {
"format": "DD/MM/YYYY",
"separator": " - ",
"applyLabel": "Übernehmen",
"cancelLabel": "Abrechen",
"fromLabel": "Von",
"toLabel": "Bis",
"customRangeLabel": "Benutzerdefiniert",
"weekLabel": "W",
"daysOfWeek": [
"So",
"Mo",
"Di",
"Mi",
"Do",
"Fr",
"Sa"
],
"monthNames": [
"Januar",
"Februar",
"März",
"April",
"Mai",
"Juni",
"Juli",
"August",
"September",
"Oktober",
"November",
"Dezember"
],
"firstDay": 1
},
"autoUpdateInput": false,
"alwaysShowCalendars": true,
"startDate": "<?php echo $day_start_display; ?>",
"endDate": "<?php echo $day_end_display; ?>",
"minDate": "01/10/2019"
}, function(start, end, label) {
var Pstart = start.format('MM/DD/YYYY');
var Pend = end.format('MM/DD/YYYY');
$("#start").val(Pstart);
$("#end").val(Pend);
$('#FO').submit();
});
// $("#demo").val(`Conversions im Zeitraum: <?php echo $Pall; ?>`);
</script>
<body>
<form id='FO' method='GET' action='index.php'>
<input type="hidden" id="start" name="start" value='' />
<input type="hidden" id="end" name="end" value='' />
</form>
<script>
</script>
</body>
</html>

View File

@@ -50,9 +50,9 @@ if (user.rolle === Enums.BenutzerRolle.USER) {
// SELECT id, updated_at FROM "GEGNachweisGewerbe" WHERE created_at >= ${date} AND bestellt = ${true}
result =
await prisma.$queryRaw`SELECT id, updated_at FROM "VerbrauchsausweisWohnen" WHERE ausgestellt = ${false} AND bestellt = ${true} UNION ALL
SELECT id, updated_at FROM "VerbrauchsausweisGewerbe" WHERE ausgestellt = ${false} AND bestellt = ${true} UNION ALL
SELECT id, updated_at FROM "BedarfsausweisWohnen" WHERE ausgestellt = ${false} AND bestellt = ${true} UNION ALL
await prisma.$queryRaw`SELECT id, updated_at FROM "VerbrauchsausweisWohnen" UNION ALL
SELECT id, updated_at FROM "VerbrauchsausweisGewerbe" UNION ALL
SELECT id, updated_at FROM "BedarfsausweisWohnen" UNION ALL
SELECT id, updated_at FROM "BedarfsausweisGewerbe" UNION ALL
SELECT id, updated_at FROM "GEGNachweisWohnen" UNION ALL
SELECT id, updated_at FROM "GEGNachweisGewerbe"

View File

@@ -1,108 +0,0 @@
---
import { encodeToken } from "#lib/auth/token";
import { TokenType } from "#lib/auth/types";
import { API_ACCESS_TOKEN_COOKIE_NAME, API_REFRESH_TOKEN_COOKIE_NAME } from "#lib/constants";
import { Enums, prisma } from "#lib/server/prisma";
import moment from "moment";
import { createCaller } from "src/astro-typesafe-api-caller";
import KundendatenModule from "#modules/KundendatenModule.svelte";
import AusweisLayout from "#layouts/AusweisLayoutPruefung.astro";
import { getCurrentUser, getOtherUser } from "#lib/server/user";
import { getAusweisartFromId } from "#components/Ausweis/types";
import { getAufnahme, getBedarfsausweisWohnen, getBilder, getObjekt, getRechnung, getUnterlagen, getVerbrauchsausweisGewerbe, getVerbrauchsausweisWohnen } from "#lib/server/db";
import { BenutzerClient } from "#components/Ausweis/types.js";
function getExistingAusweis(rechnung) {
if (rechnung.verbrauchsausweis_wohnen) {
return rechnung.verbrauchsausweis_wohnen;
}
if (rechnung.verbrauchsausweis_gewerbe) {
return rechnung.verbrauchsausweis_gewerbe;
}
if (rechnung.bedarfsausweis_wohnen) {
return rechnung.bedarfsausweis_wohnen ;
}
if (rechnung.bedarfsausweis_gewerbe) {
return rechnung.bedarfsausweis_gewerbe;
}
if (rechnung.geg_nachweis_gewerbe) {
return rechnung.geg_nachweis_gewerbe;
}
if (rechnung.geg_nachweis_wohnen) {
return rechnung.geg_nachweis_wohnen;
}
return null;
}
const user = await getCurrentUser(Astro);
if (!user) {
return Astro.redirect("/auth/login")
}
const rechnungid = Astro.url.searchParams.get("rechnungid")
if (!rechnungid) {
return Astro.redirect("/404")
}
let rechnung;
//Only Admin can read foreign invoices
if (user.rolle !== Enums.BenutzerRolle.ADMIN) {
rechnung = await prisma.rechnung.findUnique({
where: {
id: rechnungid,
benutzer_id: user.id
},
include: {
verbrauchsausweis_wohnen: true,
verbrauchsausweis_gewerbe: true,
bedarfsausweis_wohnen: true,
bedarfsausweis_gewerbe: true,
geg_nachweis_gewerbe: true,
geg_nachweis_wohnen: true
}
})
} else {
rechnung = await prisma.rechnung.findUnique({
where: {
id: rechnungid,
},
include: {
verbrauchsausweis_wohnen: true,
verbrauchsausweis_gewerbe: true,
bedarfsausweis_wohnen: true,
bedarfsausweis_gewerbe: true,
geg_nachweis_gewerbe: true,
geg_nachweis_wohnen: true
}
})
}
if (!rechnung) {
return Astro.redirect("/404")
}
const ausweis = getExistingAusweis(rechnung);
const ausweisart = getAusweisartFromId(ausweis.id)
let aufnahme, objekt, bilder, unterlagen, partner_code;
aufnahme = await getAufnahme(ausweis.aufnahme_id)
objekt = await getObjekt(aufnahme?.objekt_id)
bilder = await getBilder(ausweis.aufnahme_id)
unterlagen = await getUnterlagen(ausweis.aufnahme_id)
let impersonatedUser: Partial<BenutzerClient> | null = null;
if (user){
if (user.id !== ausweis.benutzer_id && ausweis.benutzer_id !== undefined){
impersonatedUser = await getOtherUser(Astro, ausweis.benutzer_id) || {}
}
}
---
<AusweisLayout title="Kundendaten Aufnehmen - IBCornelsen">
<KundendatenModule {user} {impersonatedUser} {ausweis} {objekt} {aufnahme} {bilder} {rechnung} {ausweisart} {unterlagen} {partner_code} aktiveBezahlmethode={Enums.Bezahlmethoden.paypal} nurRechnungsadresseUpdate={true} client:only ></KundendatenModule>
</AusweisLayout>

View File

@@ -3,11 +3,10 @@ import { getAusweisartFromId } from "#components/Ausweis/types";
import AusweisLayoutPruefung from "#layouts/AusweisLayoutPruefung.astro";
import { getPrismaAusweisAdapter } from "#lib/server/ausweis";
import { Enums } from "#lib/server/prisma";
import { getCurrentUser, getOtherUser } from "#lib/server/user";
import { getCurrentUser } from "#lib/server/user";
import KundendatenModule from "#modules/KundendatenModule.svelte";
import { PaymentStatus } from "@mollie/api-client";
import { AusweisTyp } from "src/generated/enums";
import { BenutzerClient } from "#components/Ausweis/types.js";
const uid = Astro.url.searchParams.get("uid")
@@ -46,16 +45,8 @@ if (!ausweis) {
if (ausweis.rechnung.status === PaymentStatus.paid) {
return Astro.redirect("/dashboard")
}
let impersonatedUser: Partial<BenutzerClient> | null = null;
if (user){
if (user.id !== ausweis.benutzer_id && ausweis.benutzer_id !== undefined){
impersonatedUser = await getOtherUser(Astro, ausweis.benutzer_id) || {}
}
}
---
<AusweisLayoutPruefung title="Energieausweis Bezahlung">
<KundendatenModule {user} {impersonatedUser} {ausweis} objekt={ausweis.aufnahme.objekt} rechnung={ausweis.rechnung} aufnahme={ausweis.aufnahme} bilder={ausweis.aufnahme.bilder} {ausweisart} ausweistyp={AusweisTyp.Standard} aktiveBezahlmethode={Enums.Bezahlmethoden.paypal} client:only></KundendatenModule>
<KundendatenModule {user} {ausweis} objekt={ausweis.aufnahme.objekt} rechnung={ausweis.rechnung} aufnahme={ausweis.aufnahme} bilder={ausweis.aufnahme.bilder} {ausweisart} ausweistyp={AusweisTyp.Standard} aktiveBezahlmethode={Enums.Bezahlmethoden.paypal} client:only></KundendatenModule>
</AusweisLayoutPruefung>

View File

@@ -1,19 +0,0 @@
---
import { getHeapSnapshot } from "v8";
import * as fs from "fs";
// Create a named heap snapshot
const snapshotStream = getHeapSnapshot();
const fileName = `heap-${Date.now()}.heapsnapshot`;
const fileStream = fs.createWriteStream(fileName);
snapshotStream.pipe(fileStream);
fileStream.on("finish", () => {
console.log(`Heap snapshot saved to ${fileName}`);
});
fileStream.on("error", (err) => {
console.error("Error writing heap snapshot:", err);
});
---

View File

@@ -3,10 +3,9 @@
import KundendatenModule from "#modules/KundendatenModule.svelte";
import AusweisLayout from "#layouts/AusweisLayoutPruefung.astro";
import { Enums } from "#lib/client/prisma";
import { getCurrentUser, getOtherUser } from "#lib/server/user";
import { getCurrentUser } from "#lib/server/user";
import { getAusweisartFromId } from "#components/Ausweis/types";
import { getAufnahme, getBedarfsausweisWohnen, getBilder, getObjekt, getRechnung, getUnterlagen, getVerbrauchsausweisGewerbe, getVerbrauchsausweisWohnen } from "#lib/server/db";
import { BenutzerClient } from "#components/Ausweis/types.js";
// Man sollte nur auf diese Seite kommen, wenn ein Ausweis bereits vorliegt und in der Datenbank abgespeichert wurde.
@@ -71,16 +70,9 @@ if (!params.has("ausweis") || !params.has("aufnahme") || !params.has("objekt") |
}
}
let impersonatedUser: Partial<BenutzerClient> | null = null;
if (user){
if (user.id !== ausweis.benutzer_id && ausweis.benutzer_id !== undefined){
impersonatedUser = await getOtherUser(Astro, ausweis.benutzer_id) || {}
}
}
---
<AusweisLayout title="Kundendaten Aufnehmen - IBCornelsen">
<KundendatenModule {user} {impersonatedUser} {ausweis} {objekt} {aufnahme} {bilder} {rechnung} {ausweisart} {unterlagen} {partner_code} aktiveBezahlmethode={Enums.Bezahlmethoden.paypal} client:only ></KundendatenModule>
<KundendatenModule {user} {ausweis} {objekt} {aufnahme} {bilder} {rechnung} {ausweisart} {unterlagen} {partner_code} aktiveBezahlmethode={Enums.Bezahlmethoden.paypal} client:only ></KundendatenModule>
</AusweisLayout>

View File

@@ -45,23 +45,14 @@ export const GET: APIRoute = async (Astro) => {
user = await getCurrentUser(Astro)
let pdf: Uint8Array<ArrayBufferLike> | null = null;
if (ausweis.ausgestellt) {
if (/[A-Z]{2}[0-9]{8}/.test(ausweis.id)) {
const id = ausweis.id.match(/[A-Z]{2}([0-9]{8})/) as RegExpMatchArray
// Dieser Ausweis wurde mit der alten Version erstellt, das PDF sollte bereits existieren.
pdf = await getS3File("ibc-pdfs", `ID_${id[1]}_Energieausweis.pdf`)
} else {
pdf = await getS3File("ibc-pdfs", `ID_${ausweis.id}_Energieausweis.pdf`)
}
} else {
if (ausweisart === Enums.Ausweisart.VerbrauchsausweisWohnen) {
pdf = await pdfVerbrauchsausweisWohnen(ausweis as VerbrauchsausweisWohnenClient, aufnahme, objekt, bilder, user, !ausweis.ausgestellt);
} else if (ausweisart === Enums.Ausweisart.VerbrauchsausweisGewerbe) {
pdf = await pdfVerbrauchsausweisGewerbe(ausweis as VerbrauchsausweisGewerbeClient, aufnahme, objekt, bilder, user, !ausweis.ausgestellt);
} else if (ausweisart === Enums.Ausweisart.BedarfsausweisWohnen) {
//todo
}
if (/[A-Z]{2}[0-9]{8}/.test(ausweis.id)) {
const id = ausweis.id.match(/[A-Z]{2}([0-9]{8})/) as RegExpMatchArray
// Dieser Ausweis wurde mit der alten Version erstellt, das PDF sollte bereits existieren.
pdf = await getS3File("ibc-pdfs", `ID_${id[1]}_Energieausweis.pdf`)
} else if (ausweisart === Enums.Ausweisart.VerbrauchsausweisWohnen) {
pdf = await pdfVerbrauchsausweisWohnen(ausweis as VerbrauchsausweisWohnenClient, aufnahme, objekt, bilder, user, !ausweis.ausgestellt);
} else if (ausweisart === Enums.Ausweisart.VerbrauchsausweisGewerbe) {
pdf = await pdfVerbrauchsausweisGewerbe(ausweis as VerbrauchsausweisGewerbeClient, aufnahme, objekt, bilder, user, !ausweis.ausgestellt);
}
return new Response(pdf, {

36
src/server/logger.ts Normal file
View File

@@ -0,0 +1,36 @@
import { prisma } from "#lib/server/prisma.js";
import winston from "winston";
import Transport from "winston-transport";
class DatabaseTransport extends Transport {
constructor() {
super();
}
async log(info: any, callback: () => void) {
setImmediate(() => {
this.emit("logged", info);
});
const { level, message, ...meta } = info;
await prisma.log.create({
data: {
level,
message,
meta: JSON.stringify(meta),
},
});
callback();
}
}
export const logger = winston.createLogger({
level: "info",
format: winston.format.json(),
transports: [new DatabaseTransport(), new winston.transports.Console({
handleExceptions: true,
handleRejections: true,
})]
});

30
src/ws/server.ts Normal file
View File

@@ -0,0 +1,30 @@
import { logger } from '#server/logger.js';
import { WebSocketServer } from 'ws';
const server = new WebSocketServer({ port: 8080 });
server.on('connection', (ws, req) => {
logger.info("Client connected to websocket", {
ip: req.socket.remoteAddress
})
ws.on('message', (message) => {
logger.info("Received websocket message", {
message
});
ws.send(`Server received: ${message}`);
});
ws.on('close', () => {
logger.info('Client disconnected', {
ip: req.socket.remoteAddress
});
});
});
logger.info(`Websocket server listening on port ${8080}`)
export { server };

60
src/ws/types.ts Normal file
View File

@@ -0,0 +1,60 @@
import { Conversation, Message } from "#lib/client/prisma.js";
export type WebsocketClientToServerMessage =
| {
type: "messages/get";
payload: {
conversation_id: string;
filters?: {
after?: Date;
before?: Date;
};
};
}
| {
type: "message/send";
payload: {
conversation_id: string;
content: string;
reply_to_id?: string;
};
}
| {
type: "conversations/get";
payload: {
includeMessages?: boolean;
};
}
| {
type: "conversation/create";
payload: {
participant_ids: string[]; // includes sender
name?: string; // optional for group
};
};
/* ---------------------------- Server to client ---------------------------- */
export type WebsocketServerToClientMessage =
| {
type: "messages/list";
payload: {
conversation_id: string;
messages: Message[];
};
}
| {
type: "message/new";
payload: Message;
}
| {
type: "conversations/list";
payload: Conversation[];
}
| {
type: "error";
payload: {
code: string;
message: string;
};
};

View File

@@ -3,28 +3,12 @@
set -e
# Config
CONTAINER_NAME="database"
CONTAINER_NAME="online-energieausweis-database-1"
DB_USER="main"
DB_NAME="main"
TIMESTAMP=$(date +"%Y-%m-%d_%H-%M-%S")
FILE_NAME="backup-$TIMESTAMP.sql.br"
# Wir holen uns den parameter --skip-backup, um zu entscheiden, ob wir ein Backup machen wollen
SKIP_BACKUP=false
while [[ $# -gt 0 ]]; do
case $1 in
--skip-backup)
SKIP_BACKUP=true
shift # Remove the argument from the list
;;
*)
echo "Unbekannter Parameter: $1"
exit 1
;;
esac
done
REQUIRED_CONFIRMATION='Ja, ich möchte alle Daten unwiderruflich löschen.'
echo "⚠️ WARNUNG: Diese Aktion wird alle Tabellen und Einträge in der Datenbank vollständig löschen!"
@@ -37,11 +21,9 @@ if [[ "$USER_CONFIRMATION" != "$REQUIRED_CONFIRMATION" ]]; then
exit 1
fi
if [[ "$SKIP_BACKUP" == false ]]; then
echo "📦 Backup wird erstellt..."
docker exec -t "$CONTAINER_NAME" pg_dumpall -c -U "$DB_USER" | brotli > "$FILE_NAME"
echo "✅ Backup abgeschlossen: $FILE_NAME"
fi
echo "📦 Backup wird erstellt..."
docker exec -t "$CONTAINER_NAME" pg_dumpall -c -U "$DB_USER" | brotli > "$FILE_NAME"
echo "✅ Backup abgeschlossen: $FILE_NAME"
echo "🧨 Alle Daten aus allen Tabellen werden gelöscht..."
@@ -52,22 +34,12 @@ DECLARE
r RECORD;
sql TEXT := '';
BEGIN
-- Truncate all tables
FOR r IN
SELECT tablename
FROM pg_tables
WHERE schemaname = 'public'
LOOP
sql := sql || FORMAT('DROP TABLE public.%I CASCADE;', r.tablename);
END LOOP;
-- Drop all sequences
FOR r IN
SELECT sequence_name
FROM information_schema.sequences
WHERE sequence_schema = 'public'
LOOP
sql := sql || FORMAT('DROP SEQUENCE IF EXISTS public.%I CASCADE;', r.sequence_name);
sql := sql || FORMAT('TRUNCATE TABLE public.%I CASCADE;', r.tablename);
END LOOP;
EXECUTE sql;