1 Commits

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

View File

@@ -0,0 +1,76 @@
name: Auto Merge Staging into Main
on:
schedule:
- cron: '0 2 * * *' # 2:00 UTC = 4:00 Europäische Zeit
workflow_dispatch:
jobs:
merge:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set Git user
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Fetch all branches
run: |
git fetch origin main
git fetch origin staging
- name: Check if main has commits not in staging
id: check
run: |
git fetch origin
if [ $(git rev-list --count origin/staging..origin/main) -gt 0 ]; then
echo "❌ Staging is behind main and requires manual merging."
exit 1
fi
- name: Create PR from staging to main
id: create_pr
run: |
PR_URL=$(gh pr create --base main --head staging --title "Auto-merge staging into main" --body "This PR was created automatically by GitHub Actions. It merges the latest \`staging\` into \`main\`.")
echo "PR_URL=$PR_URL" >> $GITHUB_OUTPUT
PR_NUMBER=$(echo $PR_URL | awk -F'/' '{print $NF}')
echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_OUTPUT
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Enable auto-merge on PR
if: steps.create_pr.outputs.PR_NUMBER != ''
run: |
gh pr merge ${{ steps.create_pr.outputs.PR_NUMBER }} --merge --auto
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
notify_failure:
needs: merge
if: failure()
runs-on: ubuntu-latest
steps:
- name: Send Discord notification on failure
run: |
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

@@ -28,4 +28,4 @@ jobs:
git clean -f -d
git pull origin dev
git status
make prod-no-backup
make prod

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,12 +13,11 @@ jobs:
steps:
- name: Prevent dev merges
run: |
if [[ "${{ github.head_ref }}" == "dev" && "${{ github.base_ref }}" == "main" ]]; then
echo "ERROR: Merging 'dev' into 'main' is forbidden!"
if [[ "${{ github.head_ref }}" == "dev" ]]; then
echo "ERROR: Merging 'dev' into '${{ github.base_ref }}' is forbidden!"
exit 1
fi
- name: Allow only staging into main
if: github.base_ref == 'main'
run: |

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,10 +30,6 @@ run-database: stop-database
docker volume create $(DB_VOLUME)
docker build -t $(DB_CONTAINER_NAME) .
docker run -d --name $(DB_CONTAINER_NAME) \
--log-driver=json-file \
--log-opt max-size=50m \
--log-opt max-file=3 \
--restart=always \
-e POSTGRES_USER=$(DB_USER) \
-e POSTGRES_PASSWORD=$(DB_PASSWORD) \
-p $(DB_PORT):5432 \
@@ -64,9 +60,7 @@ all:
update-dwd-klimafaktoren-cron:
pm2 start bun --name "update-dwd-klimafaktoren-cron" --cron "0 12 28 * *" -- src/cronjobs/update-dwd-klimafaktoren.ts
prod: prod-no-backup backup-database-cronjob
prod-no-backup: install-dependencies prisma-studio update-dwd-klimafaktoren-cron
prod: install-dependencies prisma-studio backup-database-cronjob update-dwd-klimafaktoren-cron
bun run build
mkdir -p ~/logs
mkdir -p ~/persistent/online-energieausweis
@@ -77,4 +71,4 @@ prod-no-backup: install-dependencies prisma-studio update-dwd-klimafaktoren-cron
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

@@ -2,7 +2,6 @@
FILE_NAME=data-dump_`date +%Y-%m-%d"_"%H_%M_%S`.sql.br
FILE_NAME_COMPLETE=full-dump_`date +%Y-%m-%d"_"%H_%M_%S`.sql.br
DATABASE_NAME=database
# Das wird benötigt für AWS Ionos Kompatibilität.
export AWS_REQUEST_CHECKSUM_CALCULATION=when_required
@@ -12,15 +11,15 @@ export AWS_RESPONSE_CHECKSUM_VALIDATION=when_required
# IMPORTANT: Dieser Befehl benötigt das `ionos` Profil, sonst wird er nicht funktionieren.
# Das Profil kann mit `aws configure --profile ionos` erstellt werden.
# Den Key dafür findet man auf https://dcd.ionos.com/latest/?lang=en#/key-management
docker exec -t $DATABASE_NAME pg_dump --data-only -U main main | brotli --quality=3 > $FILE_NAME
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 $DATABASE_NAME pg_dumpall -c -U main | brotli --quality=3 > $FILE_NAME_COMPLETE
docker exec -t online-energieausweis-database-1 pg_dumpall -c -U main | brotli --best > $FILE_NAME_COMPLETE
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-1.ionoscloud.com --storage-class STANDARD
echo "Uploaded $FILE_NAME_COMPLETE"

View File

@@ -15,7 +15,6 @@
"@pdfme/common": "^5.2.16",
"@pdfme/generator": "^5.2.16",
"@pdfme/ui": "^5.2.16",
"@svelte-plugins/datepicker": "^1.0.11",
"@trpc/client": "^10.45.2",
"@trpc/server": "^10.45.2",
"astro": "^4.16.17",
@@ -27,8 +26,6 @@
"express": "^4.21.2",
"flag-icons": "^6.15.0",
"fontkit": "^2.0.4",
"handlebars": "^4.7.8",
"heic2any": "^0.0.4",
"highlight.run": "^9.14.0",
"is-base64": "^1.1.0",
"js-cookie": "^3.0.5",
@@ -37,11 +34,11 @@
"jwt-decode": "^4.0.0",
"mime": "^4.0.6",
"moment": "^2.30.1",
"moment-timezone": "^0.6.0",
"moment-timezone": "^0.5.46",
"nodemailer": "^6.10.0",
"pdf-lib": "^1.17.1",
"postcss-nested": "^7.0.2",
"puppeteer": "^24.15.0",
"puppeteer": "^24.7.2",
"radix-svelte-icons": "^1.0.0",
"sass": "^1.83.4",
"sharp": "^0.33.5",
@@ -526,7 +523,7 @@
"@proload/core": ["@proload/core@0.3.3", "", { "dependencies": { "deepmerge": "^4.2.2", "escalade": "^3.1.1" } }, "sha512-7dAFWsIK84C90AMl24+N/ProHKm4iw0akcnoKjRvbfHifJZBLhaDsDus1QJmhG12lXj4e/uB/8mB/0aduCW+NQ=="],
"@puppeteer/browsers": ["@puppeteer/browsers@2.10.6", "", { "dependencies": { "debug": "^4.4.1", "extract-zip": "^2.0.1", "progress": "^2.0.3", "proxy-agent": "^6.5.0", "semver": "^7.7.2", "tar-fs": "^3.1.0", "yargs": "^17.7.2" }, "bin": { "browsers": "lib/cjs/main-cli.js" } }, "sha512-pHUn6ZRt39bP3698HFQlu2ZHCkS/lPcpv7fVQcGBSzNNygw171UXAKrCUhy+TEMw4lEttOKDgNpb04hwUAJeiQ=="],
"@puppeteer/browsers": ["@puppeteer/browsers@2.10.2", "", { "dependencies": { "debug": "^4.4.0", "extract-zip": "^2.0.1", "progress": "^2.0.3", "proxy-agent": "^6.5.0", "semver": "^7.7.1", "tar-fs": "^3.0.8", "yargs": "^17.7.2" }, "bin": { "browsers": "lib/cjs/main-cli.js" } }, "sha512-i4Ez+s9oRWQbNjtI/3+jxr7OH508mjAKvza0ekPJem0ZtmsYHP3B5dq62+IaBHKaGCOuqJxXzvFLUhJvQ6jtsQ=="],
"@rc-component/async-validator": ["@rc-component/async-validator@5.0.4", "", { "dependencies": { "@babel/runtime": "^7.24.4" } }, "sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg=="],
@@ -714,8 +711,6 @@
"@smithy/util-waiter": ["@smithy/util-waiter@4.0.2", "", { "dependencies": { "@smithy/abort-controller": "^4.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-piUTHyp2Axx3p/kc2CIJkYSv0BAaheBQmbACZgQSSfWUumWNW+R1lL+H9PDBxKJkvOeEX+hKYEFiwO8xagL8AQ=="],
"@svelte-plugins/datepicker": ["@svelte-plugins/datepicker@1.0.11", "", {}, "sha512-Tqc07QLyRkCpc3Glg6oRLTUApLtCrOh52d6vJ7L32QI17HrwvcDDjaH3LF3X1SBm3CWdMrnqfJp3xjUZmB4wzw=="],
"@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@2.5.3", "", { "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^1.0.4", "debug": "^4.3.4", "deepmerge": "^4.3.1", "kleur": "^4.1.5", "magic-string": "^0.30.3", "svelte-hmr": "^0.15.3", "vitefu": "^0.2.4" }, "peerDependencies": { "svelte": "^3.54.0 || ^4.0.0 || ^5.0.0-next.0", "vite": "^4.0.0" } }, "sha512-erhNtXxE5/6xGZz/M9eXsmI7Pxa6MS7jyTy06zN3Ck++ldrppOnOlJwHHTsMC7DHDQdgUp4NAc4cDNQ9eGdB/w=="],
"@sveltejs/vite-plugin-svelte-inspector": ["@sveltejs/vite-plugin-svelte-inspector@1.0.4", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^2.2.0", "svelte": "^3.54.0 || ^4.0.0", "vite": "^4.0.0" } }, "sha512-zjiuZ3yydBtwpF3bj0kQNV0YXe+iKE545QGZVTaylW3eAzFr+pJ/cwK8lZEaRp4JtaJXhD5DyWAV4AxLh6DgaQ=="],
@@ -1062,7 +1057,7 @@
"chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="],
"chromium-bidi": ["chromium-bidi@7.2.0", "", { "dependencies": { "mitt": "^3.0.1", "zod": "^3.24.1" }, "peerDependencies": { "devtools-protocol": "*" } }, "sha512-gREyhyBstermK+0RbcJLbFhcQctg92AGgDe/h/taMJEOLRdtSswBAO9KmvltFSQWgM2LrwWu5SIuEUbdm3JsyQ=="],
"chromium-bidi": ["chromium-bidi@4.1.1", "", { "dependencies": { "mitt": "^3.0.1", "zod": "^3.24.1" }, "peerDependencies": { "devtools-protocol": "*" } }, "sha512-biR7t4vF3YluE6RlMSk9IWk+b9U+WWyzHp+N2pL9vRTk+UXHYRTVp7jTK58ZNzMLBgoLMHY4QyJMbeuw3eKxqg=="],
"ci-info": ["ci-info@4.1.0", "", {}, "sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A=="],
@@ -1246,7 +1241,7 @@
"devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="],
"devtools-protocol": ["devtools-protocol@0.0.1464554", "", {}, "sha512-CAoP3lYfwAGQTaAXYvA6JZR0fjGUb7qec1qf4mToyoH2TZgUFeIqYcjh6f9jNuhHfuZiEdH+PONHYrLhRQX6aw=="],
"devtools-protocol": ["devtools-protocol@0.0.1425554", "", {}, "sha512-uRfxR6Nlzdzt0ihVIkV+sLztKgs7rgquY/Mhcv1YNCWDh5IZgl5mnn2aeEnW5stYTE0wwiF4RYVz8eMEpV1SEw=="],
"dezalgo": ["dezalgo@1.0.4", "", { "dependencies": { "asap": "^2.0.0", "wrappy": "1" } }, "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig=="],
@@ -1520,8 +1515,6 @@
"h3": ["h3@1.14.0", "", { "dependencies": { "cookie-es": "^1.2.2", "crossws": "^0.3.2", "defu": "^6.1.4", "destr": "^2.0.3", "iron-webcrypto": "^1.2.1", "ohash": "^1.1.4", "radix3": "^1.1.2", "ufo": "^1.5.4", "uncrypto": "^0.1.3", "unenv": "^1.10.0" } }, "sha512-ao22eiONdgelqcnknw0iD645qW0s9NnrJHr5OBz4WOMdBdycfSas1EQf1wXRsm+PcB2Yoj43pjBPwqIpJQTeWg=="],
"handlebars": ["handlebars@4.7.8", "", { "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.2", "source-map": "^0.6.1", "wordwrap": "^1.0.0" }, "optionalDependencies": { "uglify-js": "^3.1.4" }, "bin": { "handlebars": "bin/handlebars" } }, "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ=="],
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
@@ -1556,8 +1549,6 @@
"hastscript": ["hastscript@9.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-jzaLBGavEDKHrc5EfFImKN7nZKKBdSLIdGvCwDZ9TfzbF2ffXiov8CKE445L2Z1Ek2t/m4SKQ2j6Ipv7NyUolw=="],
"heic2any": ["heic2any@0.0.4", "", {}, "sha512-3lLnZiDELfabVH87htnRolZ2iehX9zwpRyGNz22GKXIu0fznlblf0/ftppXKNqS26dqFSeqfIBhAmAj/uSp0cA=="],
"hexoid": ["hexoid@2.0.0", "", {}, "sha512-qlspKUK7IlSQv2o+5I7yhUd7TxlOG2Vr5LTa3ve2XSNVKAL/n/u/7KLvKmFNimomDIKvZFXWHv0T12mv7rT8Aw=="],
"highlight.run": ["highlight.run@9.14.0", "", {}, "sha512-ZR+ZLHlVU8lXqsuto0ZEMAOuvptaTBBf1jradnKDIn9OfAXupcYFbkASDlbsZtyBh2SYJSK50xwrucXujhksRg=="],
@@ -1968,7 +1959,7 @@
"moment": ["moment@2.30.1", "", {}, "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how=="],
"moment-timezone": ["moment-timezone@0.6.0", "", { "dependencies": { "moment": "^2.29.4" } }, "sha512-ldA5lRNm3iJCWZcBCab4pnNL3HSZYXVb/3TYr75/1WCTWYuTqYUb5f/S384pncYjJ88lbO8Z4uPDvmoluHJc8Q=="],
"moment-timezone": ["moment-timezone@0.5.47", "", { "dependencies": { "moment": "^2.29.4" } }, "sha512-UbNt/JAWS0m/NJOebR0QMRHBk0hu03r5dx9GK8Cs0AS3I81yDcOc9k+DytPItgVvBP7J6Mf6U2n3BPAacAV9oA=="],
"mrmime": ["mrmime@2.0.0", "", {}, "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw=="],
@@ -1986,8 +1977,6 @@
"negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
"neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="],
"neotraverse": ["neotraverse@0.6.18", "", {}, "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA=="],
"netmask": ["netmask@2.0.2", "", {}, "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg=="],
@@ -2198,9 +2187,9 @@
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
"puppeteer": ["puppeteer@24.15.0", "", { "dependencies": { "@puppeteer/browsers": "2.10.6", "chromium-bidi": "7.2.0", "cosmiconfig": "^9.0.0", "devtools-protocol": "0.0.1464554", "puppeteer-core": "24.15.0", "typed-query-selector": "^2.12.0" }, "bin": { "puppeteer": "lib/cjs/puppeteer/node/cli.js" } }, "sha512-HPSOTw+DFsU/5s2TUUWEum9WjFbyjmvFDuGHtj2X4YUz2AzOzvKMkT3+A3FR+E+ZefiX/h3kyLyXzWJWx/eMLQ=="],
"puppeteer": ["puppeteer@24.7.2", "", { "dependencies": { "@puppeteer/browsers": "2.10.2", "chromium-bidi": "4.1.1", "cosmiconfig": "^9.0.0", "devtools-protocol": "0.0.1425554", "puppeteer-core": "24.7.2", "typed-query-selector": "^2.12.0" }, "bin": { "puppeteer": "lib/cjs/puppeteer/node/cli.js" } }, "sha512-ifYqoY6wGs0yZeFuFPn8BE9FhuveXkarF+eO18I2e/axdoCh4Qh1AE+qXdJBhdaeoPt6eRNTY4Dih29Jbq8wow=="],
"puppeteer-core": ["puppeteer-core@24.15.0", "", { "dependencies": { "@puppeteer/browsers": "2.10.6", "chromium-bidi": "7.2.0", "debug": "^4.4.1", "devtools-protocol": "0.0.1464554", "typed-query-selector": "^2.12.0", "ws": "^8.18.3" } }, "sha512-2iy0iBeWbNyhgiCGd/wvGrDSo73emNFjSxYOcyAqYiagkYt5q4cPfVXaVDKBsukgc2fIIfLAalBZlaxldxdDYg=="],
"puppeteer-core": ["puppeteer-core@24.7.2", "", { "dependencies": { "@puppeteer/browsers": "2.10.2", "chromium-bidi": "4.1.1", "debug": "^4.4.0", "devtools-protocol": "0.0.1425554", "typed-query-selector": "^2.12.0", "ws": "^8.18.1" } }, "sha512-P9pZyTmJqKODFCnkZgemCpoFA4LbAa8+NumHVQKyP5X9IgdNS1ZnAnIh1sMAwhF8/xEUGf7jt+qmNLlKieFw1Q=="],
"qs": ["qs@6.13.0", "", { "dependencies": { "side-channel": "^1.0.6" } }, "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg=="],
@@ -2574,7 +2563,7 @@
"tar": ["tar@6.2.1", "", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="],
"tar-fs": ["tar-fs@3.1.0", "", { "dependencies": { "pump": "^3.0.0", "tar-stream": "^3.1.5" }, "optionalDependencies": { "bare-fs": "^4.0.1", "bare-path": "^3.0.0" } }, "sha512-5Mty5y/sOF1YWj1J6GiBodjlDc05CUR8PKXrsnFAiSG0xA+GHeWLovaZPYUDXkH/1iKRf2+M5+OrRgzC7O9b7w=="],
"tar-fs": ["tar-fs@3.0.8", "", { "dependencies": { "pump": "^3.0.0", "tar-stream": "^3.1.5" }, "optionalDependencies": { "bare-fs": "^4.0.1", "bare-path": "^3.0.0" } }, "sha512-ZoROL70jptorGAlgAYiLoBLItEKw/fUxg9BSYK/dF/GAGYFJOJJJMvjPAKDJraCXFwadD456FCuvLWgfhMsPwg=="],
"tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="],
@@ -2674,8 +2663,6 @@
"ufo": ["ufo@1.5.4", "", {}, "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ=="],
"uglify-js": ["uglify-js@3.19.3", "", { "bin": { "uglifyjs": "bin/uglifyjs" } }, "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ=="],
"uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="],
"undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
@@ -2774,15 +2761,13 @@
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
"wordwrap": ["wordwrap@1.0.0", "", {}, "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q=="],
"wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
"wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
"ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
"ws": ["ws@8.18.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w=="],
"xml-crypto": ["xml-crypto@6.0.0", "", { "dependencies": { "@xmldom/is-dom-node": "^1.0.1", "@xmldom/xmldom": "^0.8.10", "xpath": "^0.0.33" } }, "sha512-L3RgnkaDrHaYcCnoENv4Idzt1ZRj5U1z1BDH98QdDTQfssScx8adgxhd9qwyYo+E3fXbQZjEQH7aiXHLVgxGvw=="],
@@ -2882,9 +2867,7 @@
"@prisma/schema-files-loader/fs-extra": ["fs-extra@11.1.1", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ=="],
"@puppeteer/browsers/debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
"@puppeteer/browsers/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"@puppeteer/browsers/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
"@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
@@ -2982,8 +2965,6 @@
"gray-matter/js-yaml": ["js-yaml@3.14.1", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g=="],
"handlebars/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
"hasha/type-fest": ["type-fest@0.8.1", "", {}, "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA=="],
"ignore-walk/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="],
@@ -3082,8 +3063,6 @@
"proxy-agent/proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
"puppeteer-core/debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
"rc-align/rc-util": ["rc-util@4.21.1", "", { "dependencies": { "add-dom-event-listener": "^1.1.0", "prop-types": "^15.5.10", "react-is": "^16.12.0", "react-lifecycles-compat": "^3.0.4", "shallowequal": "^1.1.0" } }, "sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg=="],
"rc-animate/rc-util": ["rc-util@4.21.1", "", { "dependencies": { "add-dom-event-listener": "^1.1.0", "prop-types": "^15.5.10", "react-is": "^16.12.0", "react-lifecycles-compat": "^3.0.4", "shallowequal": "^1.1.0" } }, "sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg=="],

View File

@@ -1,11 +1,7 @@
version: '3'
services:
database:
container_name: database
image: postgres:17.5
build: ./
restart: always
env_file:
- .env
ports:

View File

@@ -29,7 +29,6 @@
"@pdfme/common": "^5.2.16",
"@pdfme/generator": "^5.2.16",
"@pdfme/ui": "^5.2.16",
"@svelte-plugins/datepicker": "^1.0.11",
"@trpc/client": "^10.45.2",
"@trpc/server": "^10.45.2",
"astro": "^4.16.17",
@@ -41,8 +40,6 @@
"express": "^4.21.2",
"flag-icons": "^6.15.0",
"fontkit": "^2.0.4",
"handlebars": "^4.7.8",
"heic2any": "^0.0.4",
"highlight.run": "^9.14.0",
"is-base64": "^1.1.0",
"js-cookie": "^3.0.5",
@@ -51,11 +48,11 @@
"jwt-decode": "^4.0.0",
"mime": "^4.0.6",
"moment": "^2.30.1",
"moment-timezone": "^0.6.0",
"moment-timezone": "^0.5.46",
"nodemailer": "^6.10.0",
"pdf-lib": "^1.17.1",
"postcss-nested": "^7.0.2",
"puppeteer": "^24.15.0",
"puppeteer": "^24.7.2",
"radix-svelte-icons": "^1.0.0",
"sass": "^1.83.4",
"sharp": "^0.33.5",

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

@@ -1,49 +0,0 @@
/*
Warnings:
- The `fenster_art_1` column on the `BedarfsausweisWohnen` table would be dropped and recreated. This will lead to data loss if there is data in the column.
- The `fenster_art_2` column on the `BedarfsausweisWohnen` table would be dropped and recreated. This will lead to data loss if there is data in the column.
- The `dachfenster_art` column on the `BedarfsausweisWohnen` table would be dropped and recreated. This will lead to data loss if there is data in the column.
- The `haustuer_art` column on the `BedarfsausweisWohnen` table would be dropped and recreated. This will lead to data loss if there is data in the column.
- The `dach_daemmung` column on the `BedarfsausweisWohnen` table would be dropped and recreated. This will lead to data loss if there is data in the column.
- The `decke_daemmung` column on the `BedarfsausweisWohnen` table would be dropped and recreated. This will lead to data loss if there is data in the column.
- The `aussenwand_daemmung` column on the `BedarfsausweisWohnen` table would be dropped and recreated. This will lead to data loss if there is data in the column.
- The `boden_daemmung` column on the `BedarfsausweisWohnen` table would be dropped and recreated. This will lead to data loss if there is data in the column.
*/
-- AlterEnum
ALTER TYPE "BenutzerRolle" ADD VALUE 'RESELLER';
-- AlterTable
ALTER TABLE "BedarfsausweisWohnen" DROP COLUMN "fenster_art_1",
ADD COLUMN "fenster_art_1" DOUBLE PRECISION,
DROP COLUMN "fenster_art_2",
ADD COLUMN "fenster_art_2" DOUBLE PRECISION,
DROP COLUMN "dachfenster_art",
ADD COLUMN "dachfenster_art" DOUBLE PRECISION,
DROP COLUMN "haustuer_art",
ADD COLUMN "haustuer_art" DOUBLE PRECISION,
DROP COLUMN "dach_daemmung",
ADD COLUMN "dach_daemmung" DOUBLE PRECISION,
DROP COLUMN "decke_daemmung",
ADD COLUMN "decke_daemmung" DOUBLE PRECISION,
DROP COLUMN "aussenwand_daemmung",
ADD COLUMN "aussenwand_daemmung" DOUBLE PRECISION,
DROP COLUMN "boden_daemmung",
ADD COLUMN "boden_daemmung" DOUBLE PRECISION;
-- CreateTable
CREATE TABLE "Provisionen" (
"id" TEXT NOT NULL,
"ausweisart" TEXT NOT NULL,
"provision_prozent" DOUBLE PRECISION NOT NULL,
"provision_betrag" DOUBLE PRECISION NOT NULL,
"benutzer_id" VARCHAR(11),
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Provisionen_pkey" PRIMARY KEY ("id")
);
-- AddForeignKey
ALTER TABLE "Provisionen" ADD CONSTRAINT "Provisionen_benutzer_id_fkey" FOREIGN KEY ("benutzer_id") REFERENCES "benutzer"("id") ON DELETE SET NULL ON UPDATE CASCADE;

View File

@@ -1,15 +0,0 @@
/*
Warnings:
- The primary key for the `Provisionen` table will be changed. If it partially fails, the table could be left without primary key constraint.
- The `id` column on the `Provisionen` table would be dropped and recreated. This will lead to data loss if there is data in the column.
- Changed the type of `ausweisart` on the `Provisionen` table. No cast exists, the column would be dropped and recreated, which cannot be done if there is data, since the column is required.
*/
-- AlterTable
ALTER TABLE "Provisionen" DROP CONSTRAINT "Provisionen_pkey",
DROP COLUMN "id",
ADD COLUMN "id" SERIAL NOT NULL,
DROP COLUMN "ausweisart",
ADD COLUMN "ausweisart" "Ausweisart" NOT NULL,
ADD CONSTRAINT "Provisionen_pkey" PRIMARY KEY ("id");

View File

@@ -1,2 +0,0 @@
-- AlterTable
ALTER TABLE "benutzer" ADD COLUMN "partner_code" TEXT;

View File

@@ -1,12 +0,0 @@
/*
Warnings:
- Added the required column `ausweistyp` to the `Provisionen` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "Aufnahme" ALTER COLUMN "flaeche" SET DATA TYPE DOUBLE PRECISION,
ALTER COLUMN "nutzflaeche" SET DATA TYPE DOUBLE PRECISION;
-- AlterTable
ALTER TABLE "Provisionen" ADD COLUMN "ausweistyp" "AusweisTyp" NOT NULL;

View File

@@ -1,5 +0,0 @@
-- AlterTable
ALTER TABLE "Bild" ADD COLUMN "benutzer_id" TEXT;
-- AddForeignKey
ALTER TABLE "Bild" ADD CONSTRAINT "Bild_benutzer_id_fkey" FOREIGN KEY ("benutzer_id") REFERENCES "benutzer"("id") ON DELETE NO ACTION ON UPDATE NO ACTION;

View File

@@ -1,10 +0,0 @@
/*
Warnings:
- You are about to alter the column `flaeche` on the `Aufnahme` table. The data in that column could be lost. The data in that column will be cast from `DoublePrecision` to `Integer`.
- You are about to alter the column `nutzflaeche` on the `Aufnahme` table. The data in that column could be lost. The data in that column will be cast from `DoublePrecision` to `Integer`.
*/
-- AlterTable
ALTER TABLE "Aufnahme" ALTER COLUMN "flaeche" SET DATA TYPE INTEGER,
ALTER COLUMN "nutzflaeche" SET DATA TYPE INTEGER;

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

@@ -2,7 +2,6 @@
enum BenutzerRolle {
USER
ADMIN
RESELLER
}
model Benutzer {
@@ -21,7 +20,6 @@ model Benutzer {
rolle BenutzerRolle @default(USER)
firma String?
lex_office_id String?
partner_code String?
verified Boolean @default(false)
@@ -51,9 +49,10 @@ model Benutzer {
BearbeiteteTickets Tickets[] @relation("BearbeiteteTickets")
events Event[]
@@map("benutzer")
Provisionen Provisionen[]
bilder Bild[]
conversations Participant[]
messages Message[]
@@map("benutzer")
}

View File

@@ -15,8 +15,6 @@ model Bild {
created_at DateTime @default(now())
updated_at DateTime @updatedAt @default(now())
benutzer_id String?
benutzer Benutzer? @relation(fields: [benutzer_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
aufnahme_id String?
aufnahme Aufnahme? @relation(fields: [aufnahme_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
}

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

@@ -1,11 +0,0 @@
model Provisionen {
id Int @id @default(autoincrement())
ausweisart Ausweisart
ausweistyp AusweisTyp
provision_prozent Float
provision_betrag Float
benutzer_id String? @db.VarChar(11)
benutzer Benutzer? @relation(fields: [benutzer_id], references: [id])
created_at DateTime @default(now())
updated_at DateTime @updatedAt
}

View File

@@ -2,77 +2,40 @@
# === Configuration ===
BUCKET_NAME="ibc-db-backup"
ENDPOINT_URL="https://s3.eu-central-3.ionoscloud.com"
ENDPOINT_URL="https://s3-eu-central-1.ionoscloud.com"
LOCAL_DOWNLOAD_DIR="./" # Where to save the file
DATABASE_NAME=database
# === Check if a custom file is given as a command line argument ===
if [ $# -eq 1 ]; then
CUSTOM_FILE="$1"
echo "🔍 Using custom file: $CUSTOM_FILE"
# Check if file exists locally
if [ ! -f "$CUSTOM_FILE" ]; then
# Check if the file exists on the remote
if ! aws s3api head-object --bucket "$BUCKET_NAME" --key "$CUSTOM_FILE" --endpoint-url "$ENDPOINT_URL" > /dev/null 2>&1; then
echo "❌ Custom file does not exist in S3 bucket or locally."
exit 1
else
echo "📥 Downloading $CUSTOM_FILE from S3"
aws s3 cp "s3://$BUCKET_NAME/$CUSTOM_FILE" "$LOCAL_DOWNLOAD_DIR" \
--endpoint-url "$ENDPOINT_URL"
fi
fi
LATEST_FILE="$CUSTOM_FILE"
FILENAME=$(basename "$LATEST_FILE")
if [[ "$FILENAME" == *.br ]]; then
echo "🗜️ Detected compressed file: $FILENAME"
# Remove the .br suffix for the SQL file
SQL_FILE="${FILENAME%.br}" # Remove .br suffix
brotli -d "$FILENAME"
else
SQL_FILE=$FILENAME
fi
else
echo "🔍 No custom file provided, searching for latest .sql.br file in S3"
# === 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)
# === 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
echo "🔍 Latest file found: $LATEST_FILE"
FILENAME=$(basename "$LATEST_FILE")
SQL_FILE="${FILENAME%.br}" # Remove .br suffix
echo "📥 Downloading $LATEST_FILE"
aws s3 cp "s3://$BUCKET_NAME/$LATEST_FILE" "$LOCAL_DOWNLOAD_DIR" \
--endpoint-url "$ENDPOINT_URL"
brotli -d "$FILENAME"
echo "🗜️ Decompressed to $SQL_FILE"
# === 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 s3 cp "s3://$BUCKET_NAME/$LATEST_FILE" "$LOCAL_DOWNLOAD_DIR" \
--endpoint-url "$ENDPOINT_URL"
# === Decompress with Brotli ===
echo "🗜️ Decompressing $FILENAME -> $SQL_FILE"
brotli -d "$FILENAME"
# === Import into Postgres inside Docker ===
echo "🐘 Importing into PostgreSQL ($DATABASE_NAME:main)"
docker exec -i "$DATABASE_NAME" env PGPASSWORD="hHMP8cd^N3SnzGRR" \
psql -v ON_ERROR_STOP=0 -U main -d main < "$SQL_FILE"
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."
# === Optional: Clean up
# If custom file was provided, do not delete it
if [ -z "$CUSTOM_FILE" ]; then
echo "🧹 Cleaning up downloaded files..."
rm "$FILENAME" "$SQL_FILE"
fi
rm "$FILENAME" "$SQL_FILE"

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,12 +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"),
"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"),
"aufnahme": await import("../src/pages/api/aufnahme/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"),
"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"),
@@ -18,13 +12,21 @@ 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"),
"bilder/[id]": await import("../src/pages/api/bilder/[id].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"),
"objekt": await import("../src/pages/api/objekt/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"),
"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"),
"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-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"),
@@ -35,9 +37,6 @@ export const createCaller = createCallerFactory({
"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"),
"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"),
"webhooks/mollie": await import("../src/pages/api/webhooks/mollie.ts"),
"aufnahme/[id]/bilder": await import("../src/pages/api/aufnahme/[id]/bilder.ts"),
"aufnahme/[id]": await import("../src/pages/api/aufnahme/[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

@@ -0,0 +1,49 @@
import { dialogs } from "../../../svelte-dialogs.config";
import { loginClient } from "#lib/login";
import { addNotification } from "#components/Notifications/shared";
export async function spawnLoginPrompt() {
const result = await dialogs.prompt(
[
{
label: "Email",
type: "email",
required: true,
placeholder: "Email",
name: "email"
},
{
label: "Passwort",
type: "password",
name: "passwort",
required: true,
placeholder: "********",
},
],
{
title: "Login",
submitButtonText: "Einloggen",
cancelButtonText: "Abbrechen",
}
);
if (!result) return false;
const [email, passwort] = result;
const loginResult = await loginClient(email, passwort);
if (loginResult === null) {
addNotification({
type: "error",
message: "Einloggen fehlgeschlagen",
dismissable: true,
subtext: "Bitte überprüfen Sie ihre Eingaben und versuchen es erneut.",
timeout: 5000,
})
return false
}
return true
}

View File

@@ -0,0 +1,67 @@
import { dialogs } from "../../../svelte-dialogs.config.js";
import { addNotification } from "#components/Notifications/shared.js";
import { api } from "astro-typesafe-api/client";
export async function spawnSignupPrompt() {
const result = await dialogs.prompt(
[
{
label: "Vorname",
type: "text",
required: true,
placeholder: "Vorname",
name: "vorname"
},
{
label: "Name",
type: "text",
required: true,
placeholder: "Name",
name: "name"
},
{
label: "Email",
type: "email",
required: true,
placeholder: "Email",
name: "email"
},
{
label: "Passwort",
type: "password",
name: "passwort",
required: true,
placeholder: "********",
},
],
{
title: "Registrieren",
submitButtonText: "Registrieren",
cancelButtonText: "Abbrechen",
}
);
if (!result) return false;
const [vorname, name, email, passwort] = result;
try {
const response = await api.user.PUT.fetch({
email,
passwort,
vorname,
name,
});
return true;
} catch(e) {
addNotification({
type: "error",
message: "Registrieren fehlgeschlagen",
dismissable: true,
subtext: "Ein Fehler ist aufgetreten, vielleicht wird die angegebene Email bereits verwendet.",
timeout: 5000,
})
return false;
}
}

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,133 +0,0 @@
<script lang="ts">
import { Aufnahme, BedarfsausweisWohnen, Enums, Objekt, Provisionen, Rechnung, VerbrauchsausweisGewerbe, VerbrauchsausweisWohnen } from "#lib/server/prisma.js";
import moment from "moment-timezone"
import { DatePicker } from "@svelte-plugins/datepicker"
import { getProvision } from "#lib/provision.js";
export let bestellungen: (Rechnung & {
ausweis: (VerbrauchsausweisWohnen | BedarfsausweisWohnen | VerbrauchsausweisGewerbe) & { aufnahme: Aufnahme & { objekt: Objekt }}
})[];
export let provisionen: Provisionen[];
export let email: string;
export let startdatum: Date;
export let enddatum: Date;
moment.locale("de");
moment.tz.setDefault("Europe/Berlin");
const bestellungenNachMonat: Record<string, (typeof bestellungen)> = {};
for (const bestellung of bestellungen) {
const monat = moment(bestellung.created_at).format("Y-MM");
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.
function getMonthlyPeriods(from: Date, to: Date): moment.Moment[] {
const start = moment(from).startOf('month');
const end = moment(to).endOf('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(startdatum, enddatum)
let isOpen = false;
$: formatiertesStartDatum = moment(startdatum).format("DD.MM.YYYY");
$: formatiertesEndDatum = moment(enddatum).format("DD.MM.YYYY");
function toggleDatePicker() {
isOpen = !isOpen;
}
const onChange = ({ startDate, endDate }: { startDate: number, endDate: number }) => {
window.location.href = `/dashboard/abrechnung?start=${moment(startDate).format("YYYY-MM-DD")}&end=${moment(endDate).format("YYYY-MM-DD")}`;
};
</script>
<div class="fixed top-0 left-0 right-0 bg-white p-4 shadow z-10">
<div class="flex justify-between items-center">
<DatePicker bind:isOpen bind:startDate={startdatum} bind:endDate={enddatum} enableFutureDates={false} isRange={true} onDateChange={onChange} isMultipane={true}>
<input type="text" class="w-min" readonly value={`${formatiertesStartDatum} - ${formatiertesEndDatum}`} on:click={toggleDatePicker} />
</DatePicker>
<p>Abrechnungsübersicht für <strong>{email}</strong></p>
</div>
</div>
<main class="my-24 flex flex-col justify-center max-w-6xl mx-auto px-4">
{#if !bestellungen || bestellungen.length === 0}
<p class="text-center text-gray-500">Keine Bestellungen gefunden.</p>
{/if}
{#each periods as dt}
{@const jahrMonat = dt.format("Y-MM")}
{#if jahrMonat in bestellungenNachMonat && bestellungenNachMonat[jahrMonat].length > 0}
<!-- Echo dropdown foreach month. -->
{@const provisionMonat = bestellungenNachMonat[jahrMonat].reduce((acc, bestellung) => {
const { provision_prozent, provision_betrag } = getProvision(bestellung.ausweis.ausweisart, bestellung.ausweis.ausweistyp, provisionen);
return acc + provision_betrag;
}, 0)}
<details class="group" open>
<summary class="flex justify-between items-center cursor-pointer p-4 bg-gray-100 hover:bg-gray-200">
<span class="font-semibold">{moment(dt).format("MMMM YYYY")}</span>
<div class="flex flex-row gap-4 items-center">
<a href={`/dashboard/abrechnung/monatlich.pdf?d=${moment(dt).format("YYYY-MM")}`} target="_blank" rel="noreferrer noopener">PDF generieren</a>
<span class="text-gray-500">{provisionMonat.toFixed(2)}</span>
<svg class="w-4 h-4 transition-transform duration-300 group-open:rotate-180" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</div>
</summary>
<div class="p-4">
<table class="w-full mb-4 border-collapse border border-gray-300">
<thead>
<tr class="bg-primary text-white w-full">
<td class="text-center p-2 font-bold">ID</td>
<td class="text-center p-2 font-bold">Datum</td>
<td class="text-center p-2 font-bold">Ausweis</td>
<td class="text-center p-2 font-bold">Provision in %</td>
<td class="text-center p-2 font-bold">Betrag Netto</td>
</tr>
</thead>
<tbody class="text-sm">
{#each bestellungenNachMonat[jahrMonat] as bestellung}
{@const provisionBestellung = getProvision(bestellung.ausweis.ausweisart, bestellung.ausweis.ausweistyp, provisionen)}
<tr class="border-b border-gray-300 hover:bg-gray-100">
<td class="text-center py-2 px-4 w-24" style="font-family: monospace;">{bestellung.ausweis.id}</td>
<td class="text-center py-2 font-bold w-32">{moment(bestellung.created_at).format("DD.MM.YYYY HH:mm")}</td>
<td class="text-center py-2 w-32">{bestellung.ausweis.ausweisart} {bestellung.ausweis.ausweistyp}</td>
<td class="text-center py-2 w-32">{provisionBestellung?.provision_prozent || 0} %</td>
<td class="text-right py-2 w-24" style="font-family: monospace;">{provisionBestellung?.provision_betrag.toFixed(2)}</td>
</tr>
{/each}
</tbody>
</table>
</div>
</details>
{:else if !bestellungenNachMonat[jahrMonat] || bestellungenNachMonat[jahrMonat].length === 0}
<details class="group">
<summary class="flex justify-between items-center cursor-pointer p-4 bg-gray-100 hover:bg-gray-200">
<span class="font-semibold">{moment(dt).format("MMMM YYYY")}</span>
<div class="flex flex-row gap-4 items-center">
<span class="text-gray-500">Keine Bestellungen gefunden</span>
<svg class="w-4 h-4 transition-transform duration-300 group-open:rotate-180" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</div>
</summary>
<div class="p-4 border-t border-gray-200">
<p class="text-gray-500">Für diesen Monat liegen uns keine Bestellungen über ihren Resellercode vor.</p>
</div>
</details>
{/if}
{/each}
</main>

View File

@@ -214,8 +214,8 @@ grid-cols-1 gap-x-2 gap-y-4
/>
<div class="text-center xs:text-left justify-self-stretch">
<b>Selbsteingabe online</b><br>inkl. ausführlicher&nbsp;telefonischer
Beratung!
<b>Verbrauchsausweis online</b><br>inkl. ausführlicher&nbsp;telefonischer
Beratung
</div>
<div class="text-center xs:text-right">
@@ -234,7 +234,7 @@ grid-cols-1 gap-x-2 gap-y-4
/>
<div class="text-center xs:text-left justify-self-stretch">
<b>Wir übernehmen die Eingabe</b><br>Sie übermitteln die nötigen Unterlagen per Upload oder E-Mail.
<b>Verbrauchsausweis offline</b><br>Sie schicken uns 3&nbsp;Verbrauchsabrechnungen&nbsp;zu)
</div>
<div class="text-center xs:text-right">

View File

@@ -11,22 +11,6 @@
export let objekt: ObjektClient;
export let ausweisart: Enums.Ausweisart;
function onlyAllowIntegerInput(event: KeyboardEvent) {
const charCode = event.which || event.keyCode;
// Allow only numbers (0-9) and control keys (e.g., backspace)
if (charCode < 48 || charCode > 57) {
event.preventDefault();
}
}
function onlyAllowPastingIntegers(event: ClipboardEvent) {
const clipboardData = event.clipboardData || (window as any).clipboardData;
const pastedData = clipboardData.getData("Text");
if (!/^\d+$/.test(pastedData)) {
event.preventDefault();
}
}
</script>
<div
@@ -113,11 +97,10 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
data-test="flaeche"
maxlength="4"
type="number"
step="1"
required
autocomplete="off"
on:keypress={onlyAllowIntegerInput}
on:paste={onlyAllowPastingIntegers}
data-rule-minlength="2"
data-msg-minlength="min. 2 Zeichen"
bind:value={aufnahme.flaeche}
/>
@@ -141,10 +124,7 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
data-test="nutzflaeche"
maxlength="4"
type="number"
step="1"
required
on:keypress={onlyAllowIntegerInput}
on:paste={onlyAllowPastingIntegers}
bind:value={aufnahme.nutzflaeche}
/>

View File

@@ -60,8 +60,6 @@
return centeredValue;
}
let translation_1 = 0;
let translation_2 = 0;
$: {
@@ -76,7 +74,6 @@
if (!result) {
return;
}
translation_1 = Math.max(
0,
Math.min(

View File

@@ -16,7 +16,7 @@ const brennstoffe: [
["Flüssiggas", "kg", 13.0, 1.1, 0.27],
["Braunkohle", "kg", 5.5, 1.2, 0.43],
["Holzhackschnitzel", "SRm", 650.0, 0.2, 0.02],
["Strommix", "kWh", 1.0, 1.8, 0.56],
["Strommix", "kWh", 1.0, 2.4, 0.56],
["Fernwärme KWK FB", "kWh", 1.0, 0.7, 0.3],
["Nahwärme KWK FB", "kWh", 1.0, 0.7, 0.3],
["Heizöl EL", "kWh", 1.0, 1.1, 0.31],
@@ -41,10 +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],
["Fernwärme Erfurt", "kWh", 1.0, 0.3, 0],
["Fernwärme Neumünster", "kWh", 1.0, 0.28, 0.0133],
["Fernwärme Pforzheim", "kWh", 1.0, 0.25, 0],
["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

@@ -5,7 +5,7 @@
CaretDown,
MagnifyingGlass,
} from "radix-svelte-icons";
import { Benutzer, Enums } from "#lib/client/prisma.js";
import { Benutzer } from "#lib/server/prisma.js";
export let lightTheme: boolean;
export let benutzer: Benutzer;
@@ -38,12 +38,6 @@
{benutzer.name}
</div>
<div class="text-base-content text-sm flex">{benutzer.email}</div>
{#if benutzer.rolle === Enums.BenutzerRolle.RESELLER}
<div class="text-xs text-gray-500 flex">Reseller</div>
{/if}
{#if benutzer.rolle === Enums.BenutzerRolle.ADMIN}
<div class="text-xs text-gray-500 flex">Admin</div>
{/if}
<a href="/auth/logout" class="text-xs">Logout</a>
</div>
</div>
@@ -76,9 +70,6 @@
</div>
</div>
{/if}
{#if benutzer.rolle === Enums.BenutzerRolle.RESELLER}
<a href="/dashboard/abrechnung" class="button ">Monatliche Abrechnung</a>
{/if}
</div>
<hr class="border-gray-600" />

View File

@@ -2,7 +2,6 @@
export let hidden: boolean = true;
export let closeable: boolean = true;
import { disableBodyScroll, enableBodyScroll } from 'body-scroll-lock';
import { Cross1 } from 'radix-svelte-icons';
$: if (globalThis.window) {
if (hidden) {
@@ -13,11 +12,13 @@
}
</script>
<div class="fixed top-0 left-0 w-[100vw] h-[100vh] flex items-center justify-center bg-[rgba(0,0,0,0.8)] z-50" class:hidden={hidden}>
<div class="fixed top-0 left-0 w-[100vw] h-[100vh] flex items-center justify-center bg-[rgba(0,0,0,0.8)] z-50" class:hidden={hidden} on:click|self={() => {
hidden = closeable ? true : hidden;
}}>
{#if closeable}
<button class="absolute top-4 right-4 text-white bg-gray-50 bg-opacity-25 px-4 py-2 rounded-lg" type="button" on:click={() => {
<button class="absolute top-4 left-4 text-white" on:click={() => {
hidden = true;
}}><Cross1 size={20}></Cross1></button>
}}>Schließen</button>
{/if}
<slot></slot>
</div>

View File

@@ -2,10 +2,9 @@
import { dialogs } from "svelte-dialogs";
import TicketPopup from "./TicketPopup.svelte";
import { addNotification } from "@ibcornelsen/ui";
export let userEmail: string = "";
async function showTicketPopup() {
const success = await dialogs.modal(TicketPopup, { email: userEmail });
const success = await dialogs.modal(TicketPopup);
console.log(success);

View File

@@ -1,7 +1,6 @@
<script lang="ts">
import { api } from "astro-typesafe-api/client";
import { getClose } from "svelte-dialogs";
export let email: string = "";
const close = getClose();
@@ -13,7 +12,7 @@
email: email,
metadata: {
category: category,
telefon: telefon,
phone: phone,
},
titel: title,
})
@@ -28,8 +27,8 @@
let category = "";
let title = "";
let description = "";
//let email = "";
let telefon = "";
let email = "";
let phone = "";
</script>
<form class="max-w-lg" on:submit={createTicket}>
@@ -90,9 +89,9 @@
<input
class="input input-bordered"
type="tel"
placeholder="Ihre Telefonnummer"
name="telefon"
bind:value={telefon}
placeholder="Ihre Telefonnumer"
name="phone"
bind:value={phone}
required
/>
</div>

View File

@@ -1,9 +1,7 @@
<script lang="ts">
import HelpLabel from "#components/labels/HelpLabel.svelte";
import type { Enums } from "#lib/client/prisma.js";
import Cookies from "js-cookie";
import { tryCatch } from "#lib/tryCatch.js";
import heic2any from "heic2any";
export let max: number = 2;
export let min: number = 1;
@@ -19,7 +17,6 @@
} from "./Ausweis/types.js";
import { api } from "astro-typesafe-api/client";
import { addNotification } from "./Notifications/shared.js";
import { API_ACCESS_TOKEN_COOKIE_NAME } from "#lib/constants.js";
export let images: BildClient[] = [];
export let ausweis:
@@ -39,10 +36,8 @@
for (let i = 0; i < files.length; i++) {
const file = files[i];
console.log(file);
if (file.type !== "image/jpeg" && file.type !== "image/png" && file.type !== "image/webp" && file.type !== "image/heif" && file.type !== "image/heic") {
if (file.type !== "image/jpeg" && file.type !== "image/png") {
continue;
}
@@ -52,7 +47,7 @@
const reader = new FileReader();
reader.onload = async () => {
reader.onload = () => {
if (reader.readyState != reader.DONE) {
return;
}
@@ -62,18 +57,6 @@
}
let blob = new Blob([reader.result as ArrayBuffer]);
if (file.type === "image/heif" || file.type === "image/heic") {
// Heic files are not supported by canvas, so we convert them to jpeg first
// using an external library.
// This is a workaround until all browsers support heic natively.
// For more information see: https://caniuse.com/?search=heic
// and https://developer.apple.com/documentation/imageio/reading_heif_and_heic_images_on_ios
// and https://stackoverflow.com/questions/65887402/how-to-convert-heic-to-jpeg-in-javascript
// and https://github.com/mbitsnbites/heic2any
blob = await heic2any({ blob, toType: "image/jpeg", quality: 0.8 });
}
let url = URL.createObjectURL(blob);
let image = new Image();
image.onload = async () => {
@@ -102,12 +85,6 @@
data: dataURL,
kategorie,
name: file.name
}, {
headers: {
"Authorization": `Bearer ${Cookies.get(
API_ACCESS_TOKEN_COOKIE_NAME
)}`
}
}))
if (error) {
@@ -151,7 +128,6 @@
<div class="input-standard">
<input
type="file"
accept="image/*"
class="file-input file-input-ghost h-[38px]"
bind:this={fileUpload}
{name}
@@ -168,7 +144,6 @@
<div class="input-standard">
<input
type="file"
accept="image/*"
class="file-input file-input-ghost h-[38px]"
bind:this={fileUpload}
{name}

View File

@@ -11,7 +11,7 @@ export async function auditEndEnergie(ausweis: VerbrauchsausweisWohnenClient | V
if (aufnahme){
if (aufnahme.flaeche && ausweis.verbrauch_1 && ausweis.verbrauch_2 && ausweis.verbrauch_3) {
try {
const response = await getKlimafaktoren(ausweis.startdatum as Date, objekt.plz as string);
const response = await getKlimafaktoren(ausweis.startdatum, objekt.plz);
// Alle Klimfaktoren konnten abgefragt werden.
const eevva = await endEnergieVerbrauchVerbrauchsausweis_2016(ausweis, aufnahme, objekt);
if (eevva){

View File

@@ -39,38 +39,63 @@ const isNET = pathname.includes("immonet");
</div>
<div>
<!-- Navigation als Liste (nur ab sm sichtbar) -->
<ul class="navlist hidden xl:flex">
<li><a href={`/${partner}/energieausweis-erstellen/verbrauchsausweis-wohngebaeude/`}><button class={tab === 0 ? "glow" : ""}>Verbrauchsausweis</button></a></li>
<li><a href={`/${partner}/energieausweis-erstellen/verbrauchsausweis-gewerbe/`}><button class={tab === 1 ? "glow" : ""}>Verbrauchsausweis Gewerbe</button></a></li>
<li><a href={`/${partner}/energieausweis-erstellen/bedarfsausweis-wohngebaeude/`}><button class={tab === 2 ? "glow" : ""}>Bedarfsausweis</button></a></li>
<li><a href={`/${partner}/angebot-anfragen/bedarfsausweis-gewerbe-anfragen/`}><button class={tab === 3 ? "glow" : ""}>Bedarfsausweis Gewerbe</button></a></li>
<li><a href={`/${partner}/angebot-anfragen/geg-nachweis-wohnen-anfragen/`}><button class={tab === 4 ? "glow" : ""}>GEG Nachweis Wohngebäude</button></a></li>
<li><a href={`/${partner}/angebot-anfragen/geg-nachweis-gewerbe-anfragen/`}><button class={tab === 5 ? "glow" : ""}>GEG Nachweis Gewerbe</button></a></li>
<li><a href={`/${partner}/welcher-ausweis/${partner}`}><button class={tab === 6 ? "glow" : ""}>Welcher Ausweis</button></a></li>
</ul>
<!-- Navigation als Dropdown (nur bis sm sichtbar) -->
<select
class="xl:hidden border rounded p-2 w-[calc(100%-2rem)]"
onchange="if (this.value) window.location.href=this.value"
>
<option value="">Auswahl treffen…</option>
<option value={`/${partner}/energieausweis-erstellen/verbrauchsausweis-wohngebaeude/`} selected={tab === 0}>Verbrauchsausweis</option>
<option value={`/${partner}/energieausweis-erstellen/verbrauchsausweis-gewerbe/`} selected={tab === 1}>Verbrauchsausweis Gewerbe</option>
<option value={`/${partner}/energieausweis-erstellen/bedarfsausweis-wohngebaeude/`} selected={tab === 2}>Bedarfsausweis</option>
<option value={`/${partner}/angebot-anfragen/bedarfsausweis-gewerbe-anfragen/`} selected={tab === 3}>Bedarfsausweis Gewerbe</option>
<option value={`/${partner}/angebot-anfragen/geg-nachweis-wohnen-anfragen/`} selected={tab === 4}>GEG Nachweis Wohngebäude</option>
<option value={`/${partner}/angebot-anfragen/geg-nachweis-gewerbe-anfragen/`} selected={tab === 5}>GEG Nachweis Gewerbe</option>
<option value={`/${partner}/welcher-ausweis/${partner}`} selected={tab === 6}>Welcher Ausweis</option>
</select>
<ul class="navlist">
<li>
<a
href={`/${partner}/energieausweis-erstellen/verbrauchsausweis-wohngebaeude/`}
><button class={tab === 0 ? "glow" : ""}
>Verbrauchsausweis</button
></a>
</li>
<li>
<a
href={`/${partner}/energieausweis-erstellen/verbrauchsausweis-gewerbe/`}
><button class={tab === 1 ? "glow" : ""}
>Verbrauchsausweis Gewerbe</button
></a>
</li>
<li>
<a
href={`/${partner}/energieausweis-erstellen/bedarfsausweis-wohngebaeude/`}
><button class={tab === 2 ? "glow" : ""}
>Bedarfsausweis</button
></a>
</li>
<li>
<a
href={`/${partner}/angebot-anfragen/bedarfsausweis-gewerbe-anfragen/`}
><button class={tab === 3 ? "glow" : ""}
>Bedarfsausweis Gewerbe</button
></a>
</li>
<li>
<a
href={`/${partner}/angebot-anfragen/geg-nachweis-wohnen-anfragen/`}
><button class={tab === 4 ? "glow" : ""}
>GEG Nachweis Wohngebäude</button
></a>
</li>
<li>
<a
href={`/${partner}/angebot-anfragen/geg-nachweis-gewerbe-anfragen/`}
><button class={tab === 5 ? "glow" : ""}
>GEG Nachweis Gewerbe</button
></a>
</li>
<li>
<a href={`/${partner}/welcher-ausweis/${partner}`}
><button class={tab === 6 ? "glow" : ""}
>Welcher Ausweis</button
></a>
</li>
</ul>
</div>
</div>
</header>
<div
id="titel"
class="block w-full lg:h-[270px] bg-cover !px-8 md:px-32 !py-8 md:py-16"
class="block w-full 2xl:h-[270px] lg:h-[148px] bg-cover px-24 py-20"
style={`background-image: url('/images/partner/${partner}/hero-energieausweis.jpg');
background-repeat:no-repeat; background-position:right;`}
>
@@ -161,7 +186,7 @@ background-repeat:no-repeat; background-position:right;`}
font-family: "immo Sans";
font-weight:400;
div{@apply w-fit bg-white/75 py-6 px-4 md:px-16 rounded-lg ring-2 ring-black/15 text-[1.45rem];box-shadow:8px 8px 16px rgba(0,0,0,0.5);}
div{@apply w-fit bg-white/75 py-6 px-16 rounded-lg ring-2 ring-black/15 text-[1.45rem];box-shadow:8px 8px 16px rgba(0,0,0,0.5);}
}
.header-button {

View File

@@ -177,10 +177,7 @@ $: standardXL =
<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]}
price={PRICES.VerbrauchsausweisWohnen[Enums.AusweisTyp.Standard] + (standardXL ? 10 : 0)}
src={'https://online-energieausweis.org/images/partner/'+partner+'/wohngebaeude.svg'}
alt="Wohnhaus Verbrauchsausweis"
variant="einfach"
@@ -193,12 +190,9 @@ $: standardXL =
["Ungenau durch individuelles Heizverhalten.", 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'}`}
></WidgetCardTemplate>
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}
@@ -206,9 +200,7 @@ $: standardXL =
<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]}
price={PRICES.BedarfsausweisWohnen[Enums.AusweisTyp.Standard] + (standardXL ? 25 : 0)}
src={'https://online-energieausweis.org/images/partner/'+partner+'/wohngebaeude.svg'}
alt="Wohnhaus Bedarfsausweis"
variant="fundiert"
@@ -221,11 +213,8 @@ $: standardXL =
["Kann als Grundlage für den ISFP dienen.", 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}
@@ -234,9 +223,7 @@ $: standardXL =
<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]}
price={PRICES.VerbrauchsausweisGewerbe[Enums.AusweisTyp.Standard] + (standardXL ? 15 : 0)}
src={'https://online-energieausweis.org/images/partner/'+partner+'/gewerbegebaeude.svg'}
alt="Gewerbe Verbrauchsausweis"
variant="einfach"
@@ -250,10 +237,8 @@ $: standardXL =
["Wird nicht immer bei den Banken akzeptiert.", 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}
@@ -262,9 +247,7 @@ $: standardXL =
<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]}
price={PRICES.BedarfsausweisGewerbe[Enums.AusweisTyp.Standard]}
src={'https://online-energieausweis.org/images/partner/'+partner+'/gewerbegebaeude.svg'}
alt="Gewerbe Bedarfsausweis"
variant="fundiert"
@@ -278,10 +261,8 @@ $: standardXL =
["Objektiveres, besser vergleichbares Ergebnis.", 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}
@@ -290,9 +271,7 @@ $: standardXL =
<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]}
price={PRICES.GEGNachweisWohnen[Enums.AusweisTyp.Standard]}
src={'https://online-energieausweis.org/images/partner/'+partner+'/wohngebaeude.svg'}
alt="GEG-Nachweis-Wohnen"
variant="Bauvorlage"
@@ -306,10 +285,8 @@ $: standardXL =
["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'}`}
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>
@@ -319,9 +296,7 @@ $: standardXL =
<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]}
price={PRICES.GEGNachweisGewerbe[Enums.AusweisTyp.Standard]}
src={'https://online-energieausweis.org/images/partner/'+partner+'/gewerbegebaeude.svg'}
alt="GEG-Nachweis-Gewerbe"
variant="Bauvorlage"
@@ -335,10 +310,8 @@ $: standardXL =
["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'}`}
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}

View File

@@ -1,148 +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

@@ -19,7 +19,6 @@ export type Lueftungskonzept = (typeof Lueftungskonzept)[keyof typeof Lueftungsk
export const BenutzerRolle = {
USER: "USER",
ADMIN: "ADMIN",
RESELLER: "RESELLER",
} as const;
export type BenutzerRolle = (typeof BenutzerRolle)[keyof typeof BenutzerRolle];

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,19 +38,19 @@ 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.number().nullish(),
decke_daemmung: z.number().nullish(),
aussenwand_daemmung: z.number().nullish(),
boden_daemmung: z.number().nullish(),
dach_daemmung: z.string().nullish(),
decke_daemmung: z.string().nullish(),
aussenwand_daemmung: z.string().nullish(),
boden_daemmung: z.string().nullish(),
aussenwand_bauart: z.string().nullish(),
boden_bauart: z.string().nullish(),
warmwasser_verteilung: z.string().nullish(),

View File

@@ -17,7 +17,6 @@ export const BenutzerSchema = z.object({
rolle: z.nativeEnum(BenutzerRolle),
firma: z.string().nullish(),
lex_office_id: z.string().nullish(),
partner_code: z.string().nullish(),
verified: z.boolean(),
created_at: z.date(),
updated_at: z.date(),

View File

@@ -7,6 +7,5 @@ export const BildSchema = z.object({
name: z.string(),
created_at: z.date(),
updated_at: z.date(),
benutzer_id: z.string().nullish(),
aufnahme_id: 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,18 +1,22 @@
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 "./provisionen"
export * from "./rechnung"
export * from "./refreshtokens"
export * from "./tickets"

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

@@ -1,13 +0,0 @@
import * as z from "zod"
import { Ausweisart, AusweisTyp } from "@prisma/client"
export const ProvisionenSchema = z.object({
id: z.number().int(),
ausweisart: z.nativeEnum(Ausweisart),
ausweistyp: z.nativeEnum(AusweisTyp),
provision_prozent: z.number(),
provision_betrag: z.number(),
benutzer_id: z.string().nullish(),
created_at: z.date(),
updated_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} />
@@ -150,7 +137,7 @@ height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<Footer />
<NotificationWrapper client:load />
<TicketButton client:load userEmail={user?.email ?? ""}></TicketButton>
<TicketButton client:load></TicketButton>
</body>
</html>

View File

@@ -10,8 +10,9 @@ export async function endEnergieVerbrauchVerbrauchsausweisGewerbe_2016_Client(
) {
const startdatum = moment(ausweis.startdatum);
let klimafaktoren = await getKlimafaktoren(
startdatum.toDate(),
objekt.plz as string,
startdatum.toDate(),
startdatum.add(2, "years").toDate()
);
if (!klimafaktoren || klimafaktoren.length === 0) {

View File

@@ -13,14 +13,12 @@ export async function endEnergieVerbrauchVerbrauchsausweis_2016_Client(
objekt: ObjektClient,
) {
const startdatum = moment(ausweis.startdatum);
let klimafaktoren = await getKlimafaktoren(
objekt.plz as string,
startdatum.toDate(),
objekt.plz as string
startdatum.add(2, "years").toDate()
);
console.log("Klimafaktoren", klimafaktoren);
if (!klimafaktoren || klimafaktoren.length === 0) {
klimafaktoren = [
{

View File

@@ -5,15 +5,19 @@ import { api } from "astro-typesafe-api/client"
export const getKlimafaktoren = memoize(async (date: Date, plz: string) => {
if (!plz || !date) {
return null;
}
}
const response = await api.klimafaktoren.GET.fetch({
plz,
genauigkeit: "years",
// @ts-ignore Der Adapter nimmt z.coerce.date()
startdatum: moment(date).utc(true).toString(),
// @ts-ignore Der Adapter nimmt z.coerce.date()
enddatum: moment(date).add(2, "years").utc(true).toString(),
});
return response;
try {
const response = await api.klimafaktoren.GET.fetch({
plz,
genauigkeit: "years",
// @ts-ignore Der Adapter nimmt z.coerce.date()
startdatum: moment(date).utc(true).toString(),
// @ts-ignore Der Adapter nimmt z.coerce.date()
enddatum: moment(date).add(2, "years").utc(true).toString(),
});
return response;
} catch (e) {
return null;
}
});

View File

@@ -1,21 +1,18 @@
type MemoizedFunction<T extends (...args: any[]) => Promise<any> | any> = (...args: Parameters<T>) => Promise<ReturnType<T>> | ReturnType<T>;
type MemoizedFunction<T> = (...args: any[]) => T;
export function memoize<T extends (...args: any[]) => Promise<any> | any>(func: T): MemoizedFunction<T> {
const cache = new Map<string, ReturnType<T>>();
return (...args: Parameters<T>): Promise<ReturnType<T>> | ReturnType<T> => {
export function memoize<T>(func: (...args: any[]) => T): MemoizedFunction<T> {
const cache = new Map<string, T>();
return (...args: any[]): T => {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key)!;
}
const result = func(...args);
if (result instanceof Promise) {
return result.then(resolved => {
return resolved;
});
} else {
cache.set(key, result);
return result;
}
cache.set(key, result);
return result;
};
}

View File

@@ -92,14 +92,11 @@ export async function authorizationMiddleware(input: any, ctx: TypesafeAPIContex
}
export async function maybeAuthorizationMiddleware(input: any, ctx: TypesafeAPIContextWithRequest<any>) {
let user = null;
try {
user = await authorizationMiddleware(input, ctx)
return authorizationMiddleware(input, ctx)
} catch(e) {
console.log(e);
return null;
}
return user;
}
export const authorizationHeaders = {

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,14 +0,0 @@
import { Enums, Provisionen } from "./client/prisma.js";
export function getProvision(ausweisart: Enums.Ausweisart, ausweistyp: Enums.AusweisTyp, provisionen: Provisionen[]): { provision_prozent: number, provision_betrag: number } {
const provision = provisionen.find(p => p.ausweisart === ausweisart && p.ausweistyp === ausweistyp);
return {
provision_prozent: provision?.provision_prozent || 0,
provision_betrag: provision?.provision_betrag || 0
};
}
export function getProductName(ausweisart: Enums.Ausweisart, ausweistyp: Enums.AusweisTyp): string {
return `${Enums.Ausweisart[ausweisart]} ${Enums.AusweisTyp[ausweistyp]}`;
}

View File

@@ -1,27 +1,11 @@
import {
AufnahmeClient,
BedarfsausweisWohnenClient,
BenutzerClient,
BildClient,
getAusweisartFromId,
ObjektClient,
RechnungClient,
VerbrauchsausweisGewerbeClient,
VerbrauchsausweisWohnenClient,
} from "#components/Ausweis/types.js";
import { AufnahmeClient, BedarfsausweisWohnenClient, BenutzerClient, BildClient, getAusweisartFromId, ObjektClient, RechnungClient, VerbrauchsausweisGewerbeClient, VerbrauchsausweisWohnenClient } from "#components/Ausweis/types.js";
import { pdfDatenblattVerbrauchsausweisGewerbe } from "#lib/pdf/pdfDatenblattVerbrauchsausweisGewerbe.js";
import { pdfDatenblattVerbrauchsausweisWohnen } from "#lib/pdf/pdfDatenblattVerbrauchsausweisWohnen.js";
import { pdfVerbrauchsausweisGewerbe } from "#lib/pdf/pdfVerbrauchsausweisGewerbe.js";
import { pdfVerbrauchsausweisWohnen } from "#lib/pdf/pdfVerbrauchsausweisWohnen.js";
import { pdfAushangVerbrauchsausweisGewerbe } from "#lib/pdf/pdfAushangVerbrauchsausweisGewerbe.js";
import {
BedarfsausweisWohnen,
Enums,
prisma,
Rechnung,
VerbrauchsausweisGewerbe,
VerbrauchsausweisWohnen,
} from "#lib/server/prisma.js";
import { Enums, prisma, Rechnung } from "#lib/server/prisma.js";
/**
* Gibt den richtigen Prisma Adapter für die Ausweisart basierend auf der UID zurück, oder null bei einer falschen UID.
@@ -31,184 +15,68 @@ export function getPrismaAusweisAdapter(id: string) {
const ausweisart = getAusweisartFromId(id);
if (ausweisart === Enums.Ausweisart.VerbrauchsausweisWohnen) {
return prisma.verbrauchsausweisWohnen;
return prisma.verbrauchsausweisWohnen
} else if (ausweisart === Enums.Ausweisart.VerbrauchsausweisGewerbe) {
return prisma.verbrauchsausweisGewerbe;
return prisma.verbrauchsausweisGewerbe
} else if (ausweisart === Enums.Ausweisart.BedarfsausweisWohnen) {
return prisma.bedarfsausweisWohnen;
return prisma.bedarfsausweisWohnen
} else if (ausweisart === Enums.Ausweisart.GEGNachweisWohnen) {
return prisma.gEGNachweisWohnen;
return prisma.gEGNachweisWohnen
} else if (ausweisart === Enums.Ausweisart.GEGNachweisGewerbe) {
return prisma.gEGNachweisGewerbe;
return prisma.gEGNachweisGewerbe
} else if (ausweisart === Enums.Ausweisart.BedarfsausweisGewerbe) {
return prisma.bedarfsausweisGewerbe;
return prisma.bedarfsausweisGewerbe
}
}
/**
* Gibt den richtigen Ansichtsausweis basierend auf der Ausweisart zurück.
* @param ausweis
* @param ausweis
*/
export async function getAnsichtsausweis(
ausweis:
| VerbrauchsausweisWohnenClient
| VerbrauchsausweisGewerbeClient
| BedarfsausweisWohnenClient,
aufnahme: AufnahmeClient,
objekt: ObjektClient,
bilder: BildClient[],
user: BenutzerClient,
vorschau: boolean = true,
ausweisart = getAusweisartFromId(ausweis.id)
) {
export async function getAnsichtsausweis(ausweis: VerbrauchsausweisWohnenClient | VerbrauchsausweisGewerbeClient | BedarfsausweisWohnenClient, aufnahme: AufnahmeClient, objekt: ObjektClient, bilder: BildClient[], user: BenutzerClient, vorschau: boolean = true, ausweisart = getAusweisartFromId(ausweis.id)) {
if (!ausweisart) {
return null;
return null
}
if (ausweisart === Enums.Ausweisart.VerbrauchsausweisWohnen) {
return await pdfVerbrauchsausweisWohnen(
ausweis as VerbrauchsausweisWohnenClient,
aufnahme,
objekt,
bilder,
user,
vorschau
);
return await pdfVerbrauchsausweisWohnen(ausweis as VerbrauchsausweisWohnenClient, aufnahme, objekt, bilder, user, vorschau)
} else if (ausweisart === Enums.Ausweisart.VerbrauchsausweisGewerbe) {
return await pdfVerbrauchsausweisGewerbe(
ausweis as VerbrauchsausweisGewerbeClient,
aufnahme,
objekt,
bilder,
user,
vorschau
);
return await pdfVerbrauchsausweisGewerbe(ausweis as VerbrauchsausweisGewerbeClient, aufnahme, objekt, bilder, user, vorschau)
}
return null;
return null
}
/**
* Gibt das richtige Datenblatt basierend auf der Ausweisart zurück.
* @param ausweis
* @param ausweis
*/
export async function getDatenblatt(
ausweis:
| VerbrauchsausweisWohnenClient
| VerbrauchsausweisGewerbeClient
| BedarfsausweisWohnenClient,
aufnahme: AufnahmeClient,
objekt: ObjektClient,
bilder: BildClient[],
user: BenutzerClient,
rechnung: Rechnung,
ausweisart = getAusweisartFromId(ausweis.id)
) {
export async function getDatenblatt(ausweis: VerbrauchsausweisWohnenClient | VerbrauchsausweisGewerbeClient | BedarfsausweisWohnenClient, aufnahme: AufnahmeClient, objekt: ObjektClient, bilder: BildClient[], user: BenutzerClient, rechnung: Rechnung, ausweisart = getAusweisartFromId(ausweis.id)) {
if (!ausweisart) {
return null;
return null
}
if (ausweisart === Enums.Ausweisart.VerbrauchsausweisWohnen) {
return await pdfDatenblattVerbrauchsausweisWohnen(
ausweis as VerbrauchsausweisWohnenClient,
aufnahme,
objekt,
rechnung,
bilder
);
return await pdfDatenblattVerbrauchsausweisWohnen(ausweis as VerbrauchsausweisWohnenClient, aufnahme, objekt, rechnung, bilder)
} else if (ausweisart === Enums.Ausweisart.VerbrauchsausweisGewerbe) {
return await pdfDatenblattVerbrauchsausweisGewerbe(
ausweis as VerbrauchsausweisGewerbeClient,
aufnahme,
objekt,
rechnung,
bilder
);
return await pdfDatenblattVerbrauchsausweisGewerbe(ausweis as VerbrauchsausweisGewerbeClient, aufnahme, objekt, rechnung, bilder)
}
return null;
return null
}
/**
* Gibt den richtigen Aushang basierend auf der Ausweisart zurück.
* @param ausweis
* @param ausweis
*/
export async function getAushang(
ausweis:
| VerbrauchsausweisWohnenClient
| VerbrauchsausweisGewerbeClient
| BedarfsausweisWohnenClient,
aufnahme: AufnahmeClient,
objekt: ObjektClient,
bilder: BildClient[],
user: BenutzerClient,
vorschau: boolean = true,
rechnung: Rechnung,
ausweisart = getAusweisartFromId(ausweis.id)
) {
export async function getAushang(ausweis: VerbrauchsausweisWohnenClient | VerbrauchsausweisGewerbeClient | BedarfsausweisWohnenClient, aufnahme: AufnahmeClient, objekt: ObjektClient, bilder: BildClient[], user: BenutzerClient, vorschau: boolean = true, rechnung: Rechnung, ausweisart = getAusweisartFromId(ausweis.id)) {
if (!ausweisart || !rechnung.services.includes(Enums.Service.Aushang)) {
return null;
return null
}
if (ausweisart === Enums.Ausweisart.VerbrauchsausweisGewerbe) {
return await pdfAushangVerbrauchsausweisGewerbe(
ausweis as VerbrauchsausweisGewerbeClient,
aufnahme,
objekt,
bilder,
user,
vorschau
);
return await pdfAushangVerbrauchsausweisGewerbe(ausweis as VerbrauchsausweisGewerbeClient, aufnahme, objekt, bilder, user, vorschau)
}
return null;
}
/**
* Extrahiert die Ausweisfelder aus einem Objekt, das mehrere Ausweisarten enthält, und fasst sie in einem gemeinsamen Feld `ausweis` zusammen.
* @param row Ein Objekt, das die Ausweisfelder enthält.
* @returns Ein neues Objekt, das die Ausweisfelder extrahiert und in einem gemeinsamen Feld `ausweis` zusammenfasst.
*/
export function extrahiereAusweisAusFeldMitMehrerenAusweisen<T>(
row: T & {
bedarfsausweis_wohnen?: BedarfsausweisWohnen;
verbrauchsausweis_wohnen?: VerbrauchsausweisWohnen;
verbrauchsausweis_gewerbe?: VerbrauchsausweisGewerbe;
}
) {
const {
bedarfsausweis_wohnen,
verbrauchsausweis_wohnen,
verbrauchsausweis_gewerbe,
...rest
} = row;
return {
...rest,
ausweis: {
...(bedarfsausweis_wohnen ??
verbrauchsausweis_wohnen ??
verbrauchsausweis_gewerbe),
ausweisart: bedarfsausweis_wohnen
? Enums.Ausweisart.BedarfsausweisWohnen
: verbrauchsausweis_wohnen
? Enums.Ausweisart.VerbrauchsausweisWohnen
: Enums.Ausweisart.VerbrauchsausweisGewerbe,
ausweistyp: bedarfsausweis_wohnen
? bedarfsausweis_wohnen.ausweistyp
: verbrauchsausweis_wohnen
? verbrauchsausweis_wohnen.ausweistyp
: verbrauchsausweis_gewerbe?.ausweistyp || Enums.AusweisTyp.Standard,
},
} as {
ausweis: (
| BedarfsausweisWohnen
| VerbrauchsausweisWohnen
| VerbrauchsausweisGewerbe
) & { ausweisart: Enums.Ausweisart };
} & Omit<
T,
| "bedarfsausweis_wohnen"
| "verbrauchsausweis_wohnen"
| "verbrauchsausweis_gewerbe"
>;
}
return null
}

View File

@@ -12,7 +12,7 @@ export async function sendRegisterMail(
const verificationJwt = encodeToken({
typ: TokenType.Verify,
exp: Date.now() + (15 * 60 * 1000),
id: user.id
uid: user.uid
})
await transport.sendMail({

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

@@ -6,10 +6,11 @@
export let onLogin: (response: Awaited<ReturnType<typeof loginClient>>) => any;
export let email: string = "";
export let password: string = "";
export let route: "login" | "signup" = "login"
const navigate = (target: string) => {
route = target as typeof route;
let route: "login" | "signup" = "login"
const navigate = (target: typeof route) => {
route = target
}
const loginData = {

View File

@@ -72,7 +72,6 @@
name="email"
class="px-2.5 py-1.5 rounded-lg border bg-gray-50"
bind:value={email}
on:keyup={() => (email = email.toLowerCase())}
required
/>
</div>

View File

@@ -0,0 +1,384 @@
<script lang="ts">
import ZipSearch from "../components/PlzSuche.svelte";
import Label from "../components/Label.svelte";
import type {
Bezahlmethoden,
} from "#lib/client/prisma";
import { Enums } from "#lib/client/prisma";
import PaymentOption from "#components/PaymentOption.svelte";
import CheckoutItem from "#components/CheckoutItem.svelte";
import { BenutzerClient, VerbrauchsausweisWohnenClient } from "#components/Ausweis/types.js";
import { PRICES } from "#lib/constants.js";
import { RechnungClient } from "#components/Ausweis/types.js";
import { api } from "astro-typesafe-api/client";
export let user: BenutzerClient;
export let ausweis:
| VerbrauchsausweisWohnenClient;
// TODO: überarbeiten und zu inferProcedureOutput machen
let rechnung: Partial<RechnungClient> = {
email: user.email,
empfaenger: user.vorname + " " + user.name,
strasse: user.adresse,
plz: user.plz,
ort: user.ort,
versand_empfaenger: user.vorname + " " + user.name,
versand_strasse: user.adresse,
versand_plz: user.plz,
versand_ort: user.ort,
telefon: user.telefon,
};
let services = [
{
name: "Qualitätsdruck per Post (zusätzlich zur PDF Version) für 9€ inkl. MwSt.",
id: Enums.Service.Qualitaetsdruck,
price: 9,
selected: false,
},
{
name: "Aushang (für öffentliche Gebäude gesetzlich vorgeschrieben) für 10€ inkl. MwSt.",
id: Enums.Service.Aushang,
price: 10,
selected: false,
},
{
name: "Same Day Service (Bestellung Werktags vor 12:00 Uhr - Ausstellung bis 18:00 Uhr am gleichen Tag) für 29€ inkl. MwSt.",
id: Enums.Service.SameDay,
price: 29,
selected: false,
},
{
name: "Telefonische Energieeffizienzberatung für 75€ inkl. MwSt.",
id: Enums.Service.Telefonberatung,
price: 75,
selected: false,
},
];
export let aktiveBezahlmethode: Bezahlmethoden =
Enums.Bezahlmethoden.paypal;
async function createPayment(e: SubmitEvent) {
e.preventDefault();
// TODO
const response = await api.rechnung.PUT.fetch({
...rechnung,
ausweisart: Enums.Ausweisart.VerbrauchsausweisWohnen,
ausweis_uid: ausweis.uid,
bezahlmethode: aktiveBezahlmethode,
services: services
.filter((service) => service.selected)
.map((service) => service.id),
});
if (aktiveBezahlmethode === Enums.Bezahlmethoden.rechnung) {
window.location.href = `/payment/success?r=${response.uid}&a=${ausweis.uid}`
} else {
window.location.href = response.checkout_url as string;
}
}
const priceTotal = services.reduce((acc, service) => {
if (service.selected) {
return acc + service.price;
}
return acc;
}, 0) + PRICES[Enums.Ausweisart.VerbrauchsausweisWohnen][0];
</script>
<form class="grid grid-cols-[2fr_1fr] gap-4 h-full" on:submit={createPayment}>
<div>
<h3 class="font-semibold">Ansprechpartner</h3>
<div class="rounded-lg border p-4 border-base-300 bg-base-100">
<div class="grid grid-cols-3 gap-4">
<!-- Anrede -->
<div>
<Label>Anrede *</Label>
<div>
<select name="anrede" bind:value={user.anrede}>
<option>bitte auswählen</option>
<option value="Herr">Herr</option>
<option value="Frau">Frau</option>
</select>
</div>
</div>
<!-- Vorname -->
<div>
<Label>Vorname *</Label>
<input
name="vorname"
type="text"
bind:value={user.vorname}
required
/>
</div>
<!-- Nachname -->
<div>
<Label>Nachname *</Label>
<input
name="name"
type="text"
bind:value={user.name}
required
/>
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<!-- Telefon -->
<div>
<Label>Telefon</Label>
<input
name="telefon"
bind:value={user.telefon}
type="text"
/>
</div>
<!-- Email -->
<div>
<Label>E-Mail *</Label>
<input
name="email"
type="email"
bind:value={user.email}
required
/>
</div>
</div>
</div>
<h3 class="mt-8 font-semibold">Rechnungsadresse</h3>
<div class="rounded-lg border p-4 border-base-300 bg-base-100">
<div class="grid grid-cols-2 gap-4">
<div>
<Label>Empfänger *</Label>
<input
name="rechnung_empfaenger"
type="text"
bind:value={rechnung.empfaenger}
required
data-rule-maxlength="100"
data-msg-maxlength="max. 100 Zeichen"
/>
</div>
<!-- Zusatzzeile -->
<div>
<Label>Zusatzzeile</Label>
<input
name="rechnung_zusatzzeile"
bind:value={rechnung.zusatzzeile}
type="text"
data-rule-maxlength="80"
data-msg-maxlength="max. 80 Zeichen"
/>
</div>
</div>
<div class="grid grid-cols-3 gap-4">
<!-- Strasse -->
<div>
<Label>Straße, Hausnummer *</Label>
<input
name="rechnung_strasse"
bind:value={rechnung.strasse}
type="text"
required
data-rule-maxlength="40"
data-msg-maxlength="max. 40 Zeichen"
/>
</div>
<!-- PLZ -->
<ZipSearch
name="rechnung_plz"
bind:zip={rechnung.plz}
bind:city={rechnung.ort}
/>
<!-- Ort -->
<div>
<Label>Ort *</Label>
<input
name="rechnung_ort"
readonly
type="text"
required
value={rechnung.ort}
/>
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<!-- Telefon -->
<div>
<Label>Telefon</Label>
<input
name="rechnung_telefon"
bind:value={rechnung.telefon}
type="text"
/>
</div>
<!-- Email -->
<div>
<Label>E-Mail</Label>
<input
name="rechnung_email"
bind:value={rechnung.email}
type="email"
/>
</div>
</div>
</div>
<h3 class="mt-8 font-semibold">Versandadresse</h3>
<div class="rounded-lg border p-4 border-base-300 bg-base-100">
<div class="flex flex-row gap-2 items-center">
<input
class="w-[15px] h-[15px]"
type="checkbox"
name="abweichende_versand_adresse"
bind:checked={rechnung.abweichende_versand_adresse}
/>
<Label>Abweichende Versandadresse</Label>
</div>
<div class="grid grid-cols-2 gap-4">
<!-- Empfänger -->
<div>
<Label>Empfänger *</Label>
<input
name="versand_empfaenger"
type="text"
readonly={!rechnung.abweichende_versand_adresse}
bind:value={rechnung.versand_empfaenger}
required
data-rule-maxlength="100"
data-msg-maxlength="max. 100 Zeichen"
/>
</div>
<!-- Zusatzzeile -->
<div>
<Label>Zusatzzeile</Label>
<input
name="versand_zusatzzeile"
type="text"
readonly={!rechnung.abweichende_versand_adresse}
bind:value={rechnung.versand_zusatzzeile}
data-rule-maxlength="80"
data-msg-maxlength="max. 80 Zeichen"
/>
</div>
</div>
<div class="grid grid-cols-3 gap-4">
<!-- Strasse -->
<div>
<Label>Straße, Hausnummer *</Label>
<input
name="versand_strasse"
type="text"
readonly={!rechnung.abweichende_versand_adresse}
bind:value={rechnung.versand_strasse}
required
data-rule-maxlength="40"
data-msg-maxlength="max. 40 Zeichen"
/>
</div>
<!-- PLZ -->
<ZipSearch
name="versand_plz"
readonly={!rechnung.abweichende_versand_adresse}
bind:zip={rechnung.versand_plz}
bind:city={rechnung.versand_ort}
/>
<!-- Ort -->
<div>
<Label>Ort *</Label>
<input
name="versand_ort"
type="text"
readonly
required
bind:value={rechnung.versand_ort}
/>
</div>
</div>
</div>
<h3 class="mt-8 font-semibold">Bezahlmethode</h3>
<div
class="rounded-lg border p-4 border-base-300 bg-base-100 flex flex-row gap-4 justify-between"
>
<PaymentOption
bezahlmethode={Enums.Bezahlmethoden.paypal}
bind:aktiveBezahlmethode
name={"PayPal"}
icon={"/images/paypal.png"}
></PaymentOption>
<PaymentOption
bezahlmethode={Enums.Bezahlmethoden.sofort}
bind:aktiveBezahlmethode
name={"Sofort"}
icon={"/images/sofort.png"}
></PaymentOption>
<PaymentOption
bezahlmethode={Enums.Bezahlmethoden.giropay}
bind:aktiveBezahlmethode
name={"Giropay"}
icon={"/images/giropay.png"}
></PaymentOption>
<PaymentOption
bezahlmethode={Enums.Bezahlmethoden.creditcard}
bind:aktiveBezahlmethode
name={"Kreditkarte"}
icon={"/images/mastercard.png"}
></PaymentOption>
<PaymentOption
bezahlmethode={Enums.Bezahlmethoden.rechnung}
bind:aktiveBezahlmethode
name={"Rechnung"}
icon={"/images/rechnung.png"}
></PaymentOption>
</div>
</div>
<div>
<h3 class="font-semibold">Zusammenfassung</h3>
<div class="rounded-lg border p-4 border-base-300 bg-base-100">
<CheckoutItem
image={"https://www.gih.de/wp-content/uploads/2015/11/EnergieausweisW-E.jpg"}
name={"Energieausweis"}
description={"Verbrauchsausweis Wohnen"}
price={45}
quantity={1}
removable={false}
maxQuantity={1}
/>
<div class="mt-auto">
<hr />
<div class="flex flex-row items-center justify-between">
<span class="opacity-75 text-sm">Netto</span>
<span class="font-semibold text-sm">{Math.round(priceTotal * 0.81 * 100) / 100}</span>
</div>
<div class="flex flex-row items-center justify-between">
<span class="opacity-75 text-sm">19% MwSt</span>
<span class="font-semibold text-sm">{Math.round(priceTotal * 0.19 * 100) / 100}}€</span>
</div>
<hr />
<div class="flex flex-row items-center justify-between">
<span class="opacity-75 text-sm">Gesamt</span>
<span class="font-semibold text-sm">{Math.round(priceTotal)}</span>
</div>
<p class="mt-8">Mit dem Klick auf "Bestellung Bestätigen" akzeptieren sie unsere <a href="/agb">AGB</a> und <a href="/impressum">Datenschutzbestimmungen</a>. Sie werden zu ihrem ausgewählten Bezahlprovider weitergeleitet, nach Bezahlung werden sie automatisch zu unserem Portal zurückgeleitet.</p>
<button class="btn btn-secondary w-full mt-4"
>Bestellung Bestätigen</button
>
</div>
</div>
</div>
</form>

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>
@@ -1156,7 +1054,7 @@ grid-cols-3 sm:grid-cols-5 justify-around justify-items-center items-center"
{/if}
<!-- Für alle -->
<div class="pruefpunkt">
<input type="checkbox" required/>
<input type="checkbox"/>
<div class="text-left">
Ich habe die AGB und DSGVO im <a href="/impressum#agb" target="_blank" rel="noopener noreferrer">Impressum</a> gelesen und akzeptiert.
</div>
@@ -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}
@@ -1215,7 +1111,7 @@ sm:grid-cols-[min-content_min-content_min-content] sm:justify-self-end sm:mt-8"
<Overlay bind:hidden={loginOverlayHidden}>
<div class="bg-white w-full max-w-screen-sm py-8">
<EmbeddedAuthFlowModule onLogin={loginAction} email={email} route="signup"></EmbeddedAuthFlowModule>
<EmbeddedAuthFlowModule onLogin={loginAction} email={email}></EmbeddedAuthFlowModule>
</div>
</Overlay>

View File

@@ -3,6 +3,7 @@
import { CrossCircled } from "radix-svelte-icons";
import { fade } from "svelte/transition";
import { api } from "astro-typesafe-api/client";
import NotificationProvider from "#components/NotificationProvider/NotificationProvider.svelte";
import NotificationWrapper from "#components/Notifications/NotificationWrapper.svelte";
let passwort: string;
@@ -25,7 +26,7 @@
}
try {
const { id } = await api.user.PUT.fetch({
const { uid } = await api.user.PUT.fetch({
email,
passwort,
vorname,
@@ -82,7 +83,6 @@
name="email"
class="input input-bordered text-base text-base-content font-medium"
bind:value={email}
on:keyup={() => (email = email.toLowerCase())}
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,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

@@ -64,7 +64,7 @@ export const GET = defineApiRoute({
const { id } = ctx.params;
const aufnahme = await prisma.aufnahme.findUnique({
where: user.rolle !== Enums.BenutzerRolle.ADMIN ? {
where: user.rolle === Enums.BenutzerRolle.USER ? {
id,
benutzer_id: user.id
} : { id },

View File

@@ -63,7 +63,7 @@ export const GET = defineApiRoute({
const { id } = context.params;
const aufnahme = await prisma.aufnahme.findUnique({
where: user.rolle !== Enums.BenutzerRolle.ADMIN ? {
where: user.rolle === Enums.BenutzerRolle.USER ? {
id,
benutzer_id: user.id
} : { id },

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

@@ -49,7 +49,7 @@ export const PATCH = defineApiRoute({
data: input
})
if (user.rolle !== Enums.BenutzerRolle.ADMIN) {
if (user.rolle === Enums.BenutzerRolle.USER) {
await sendAusweisGespeichertMail(user, ctx.params.id as string)
}
},

View File

@@ -66,7 +66,7 @@ export const PUT = defineApiRoute({
}
}
});
if (user.rolle !== Enums.BenutzerRolle.ADMIN) {
if (user.rolle === Enums.BenutzerRolle.USER) {
await sendAusweisGespeichertMail(user, id)
}
return nachweis.id

View File

@@ -48,7 +48,7 @@ export const PATCH = defineApiRoute({
},
data: input
})
if (user.rolle !== Enums.BenutzerRolle.ADMIN) {
if (user.rolle === Enums.BenutzerRolle.USER) {
await sendAusweisGespeichertMail(user, ctx.params.id as string)
}
},

View File

@@ -73,7 +73,7 @@ export const PUT = defineApiRoute({
},
},
});
if (user.rolle !== Enums.BenutzerRolle.ADMIN) {
if (user.rolle === Enums.BenutzerRolle.USER) {
await sendAusweisGespeichertMail(user, id)
}
return id;

View File

@@ -23,8 +23,7 @@ export const PUT = defineApiRoute({
output: z.object({
id: z.string({ description: "Die id des Bildes." }),
}),
middleware: maybeAuthorizationMiddleware,
async fetch(input, context, user) {
async fetch(input) {
const data = input.data;
if (!isBase64(data, { mimeRequired: true })) {
@@ -44,7 +43,6 @@ export const PUT = defineApiRoute({
id,
kategorie: input.kategorie,
name: input.name,
benutzer_id: user ? user.id : null,
},
});
@@ -93,11 +91,11 @@ export const DELETE = defineApiRoute({
await prisma.bild.delete({
where: {
id: input.id,
OR: [{
benutzer_id: user.id
}, {
benutzer_id: null
}]
aufnahme: {
benutzer: {
id: user.id,
},
},
},
});
} else {
@@ -109,8 +107,6 @@ export const DELETE = defineApiRoute({
});
}
} catch (e) {
console.log(e);
throw new APIError({
code: "INTERNAL_SERVER_ERROR",
message: "Bild konnte nicht gelöscht werden.",

View File

@@ -48,7 +48,7 @@ export const PATCH = defineApiRoute({
},
data: input
})
if (user.rolle !== Enums.BenutzerRolle.ADMIN) {
if (user.rolle === Enums.BenutzerRolle.USER) {
await sendAusweisGespeichertMail(user, ctx.params.id as string)
}
},

View File

@@ -83,7 +83,7 @@ export const PUT = defineApiRoute({
},
},
});
if (user.rolle !== Enums.BenutzerRolle.ADMIN) {
if (user.rolle === Enums.BenutzerRolle.USER) {
await sendAusweisGespeichertMail(user, id)
}
return {

View File

@@ -48,7 +48,7 @@ export const PATCH = defineApiRoute({
},
data: input
})
if (user.rolle !== Enums.BenutzerRolle.ADMIN) {
if (user.rolle === Enums.BenutzerRolle.USER) {
await sendAusweisGespeichertMail(user, ctx.params.id as string)
}
},

Some files were not shown because too many files have changed in this diff Show More