1 Commits

Author SHA1 Message Date
Moritz Utcke
dc0509cac2 Implementiert Nutzer Verifizierung
Fügt einen Mechanismus zur Nutzerverifizierung per E-Mail ein.

Nach der Registrierung wird eine E-Mail mit einem zeitbasierten Verifizierungscode versandt. Der Nutzer muss diesen Code eingeben, um sein Konto zu aktivieren.

Die Methode zur Erstellung des Codes ist zeitbasiert und ändert sich alle 15 Minuten.
2025-07-30 09:39:30 -05:00
91 changed files with 1932 additions and 1497 deletions

View File

@@ -0,0 +1,72 @@
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
outputs:
reason: ${{ steps.check.outputs.reason }}
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
COUNT=$(git rev-list --count origin/staging..origin/main)
if [ "$COUNT" -gt 0 ]; then
echo "reason=ok" >> $GITHUB_OUTPUT
echo "❌ Staging is behind main and requires manual merging."
exit 1
elif [ "$COUNT" -eq 0 ]; then
echo "reason=identical" >> $GITHUB_OUTPUT
echo "✅ Staging and main are identical. Nothing to do."
exit 42
fi
- name: Create PR from staging to main
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() && needs.merge.outputs.reason != 'identical'
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 }}

View File

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

View File

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

View File

@@ -30,9 +30,6 @@ run-database: stop-database
docker volume create $(DB_VOLUME) docker volume create $(DB_VOLUME)
docker build -t $(DB_CONTAINER_NAME) . docker build -t $(DB_CONTAINER_NAME) .
docker run -d --name $(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 \ --restart=always \
-e POSTGRES_USER=$(DB_USER) \ -e POSTGRES_USER=$(DB_USER) \
-e POSTGRES_PASSWORD=$(DB_PASSWORD) \ -e POSTGRES_PASSWORD=$(DB_PASSWORD) \
@@ -62,11 +59,9 @@ all:
bun run dev 2>&1 | tee ~/logs/`date '+%d-%m-%Y_%H:%M:%S'`.log bun run dev 2>&1 | tee ~/logs/`date '+%d-%m-%Y_%H:%M:%S'`.log
update-dwd-klimafaktoren-cron: update-dwd-klimafaktoren-cron:
pm2 start bun --name "update-dwd-klimafaktoren-cron" --cron "0 12 28 * *" -- src/cronjobs/update-dwd-klimafaktoren.ts pm2 start bun --name "update-dwd-klimafaktoren-cron" --no-autorestart --cron "0 12 28 * *" -- src/cronjobs/update-dwd-klimafaktoren.ts
prod: prod-no-backup backup-database-cronjob prod: install-dependencies prisma-studio backup-database-cronjob update-dwd-klimafaktoren-cron
prod-no-backup: install-dependencies prisma-studio update-dwd-klimafaktoren-cron
bun run build bun run build
mkdir -p ~/logs mkdir -p ~/logs
mkdir -p ~/persistent/online-energieausweis mkdir -p ~/persistent/online-energieausweis

View File

@@ -2,7 +2,6 @@
FILE_NAME=data-dump_`date +%Y-%m-%d"_"%H_%M_%S`.sql.br 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 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. # Das wird benötigt für AWS Ionos Kompatibilität.
export AWS_REQUEST_CHECKSUM_CALCULATION=when_required export AWS_REQUEST_CHECKSUM_CALCULATION=when_required
@@ -12,15 +11,19 @@ export AWS_RESPONSE_CHECKSUM_VALIDATION=when_required
# IMPORTANT: Dieser Befehl benötigt das `ionos` Profil, sonst wird er nicht funktionieren. # IMPORTANT: Dieser Befehl benötigt das `ionos` Profil, sonst wird er nicht funktionieren.
# Das Profil kann mit `aws configure --profile ionos` erstellt werden. # 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 # 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-3.ionoscloud.com --storage-class STANDARD
echo "Uploaded $FILE_NAME" 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
<<<<<<< HEAD
aws s3 cp $FILE_NAME_COMPLETE s3://ibc-db-backup/ --profile ionos --endpoint-url https://s3-eu-central-3.ionoscloud.com --storage-class STANDARD
=======
aws s3 cp $FILE_NAME_COMPLETE s3://ibc-db-backup/ --profile ionos --endpoint-url https://s3.eu-central-3.ionoscloud.com --storage-class STANDARD aws s3 cp $FILE_NAME_COMPLETE s3://ibc-db-backup/ --profile ionos --endpoint-url https://s3.eu-central-3.ionoscloud.com --storage-class STANDARD
>>>>>>> dev
echo "Uploaded $FILE_NAME_COMPLETE" echo "Uploaded $FILE_NAME_COMPLETE"

View File

@@ -15,7 +15,6 @@
"@pdfme/common": "^5.2.16", "@pdfme/common": "^5.2.16",
"@pdfme/generator": "^5.2.16", "@pdfme/generator": "^5.2.16",
"@pdfme/ui": "^5.2.16", "@pdfme/ui": "^5.2.16",
"@svelte-plugins/datepicker": "^1.0.11",
"@trpc/client": "^10.45.2", "@trpc/client": "^10.45.2",
"@trpc/server": "^10.45.2", "@trpc/server": "^10.45.2",
"astro": "^4.16.17", "astro": "^4.16.17",
@@ -27,8 +26,6 @@
"express": "^4.21.2", "express": "^4.21.2",
"flag-icons": "^6.15.0", "flag-icons": "^6.15.0",
"fontkit": "^2.0.4", "fontkit": "^2.0.4",
"handlebars": "^4.7.8",
"heic2any": "^0.0.4",
"highlight.run": "^9.14.0", "highlight.run": "^9.14.0",
"is-base64": "^1.1.0", "is-base64": "^1.1.0",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
@@ -37,11 +34,11 @@
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
"mime": "^4.0.6", "mime": "^4.0.6",
"moment": "^2.30.1", "moment": "^2.30.1",
"moment-timezone": "^0.6.0", "moment-timezone": "^0.5.46",
"nodemailer": "^6.10.0", "nodemailer": "^6.10.0",
"pdf-lib": "^1.17.1", "pdf-lib": "^1.17.1",
"postcss-nested": "^7.0.2", "postcss-nested": "^7.0.2",
"puppeteer": "^24.15.0", "puppeteer": "^24.7.2",
"radix-svelte-icons": "^1.0.0", "radix-svelte-icons": "^1.0.0",
"sass": "^1.83.4", "sass": "^1.83.4",
"sharp": "^0.33.5", "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=="], "@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=="], "@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=="], "@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": ["@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=="], "@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=="], "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=="], "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=="], "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=="], "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=="], "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-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], "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=="], "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=="], "hexoid": ["hexoid@2.0.0", "", {}, "sha512-qlspKUK7IlSQv2o+5I7yhUd7TxlOG2Vr5LTa3ve2XSNVKAL/n/u/7KLvKmFNimomDIKvZFXWHv0T12mv7rT8Aw=="],
"highlight.run": ["highlight.run@9.14.0", "", {}, "sha512-ZR+ZLHlVU8lXqsuto0ZEMAOuvptaTBBf1jradnKDIn9OfAXupcYFbkASDlbsZtyBh2SYJSK50xwrucXujhksRg=="], "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": ["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=="], "mrmime": ["mrmime@2.0.0", "", {}, "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw=="],
@@ -1986,8 +1977,6 @@
"negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], "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=="], "neotraverse": ["neotraverse@0.6.18", "", {}, "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA=="],
"netmask": ["netmask@2.0.2", "", {}, "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg=="], "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=="], "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=="], "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": ["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=="], "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=="], "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=="], "uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="],
"undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], "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=="], "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": ["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=="], "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=="], "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=="], "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=="], "@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.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
"@puppeteer/browsers/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], "@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=="], "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=="], "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=="], "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=="], "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-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=="], "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,9 +1,7 @@
version: '3' version: '3'
name: database
services: services:
database: database:
container_name: database
image: postgres:17.5
build: ./ build: ./
restart: always restart: always
env_file: env_file:

View File

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

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

@@ -2,7 +2,6 @@
enum BenutzerRolle { enum BenutzerRolle {
USER USER
ADMIN ADMIN
RESELLER
} }
model Benutzer { model Benutzer {
@@ -21,7 +20,6 @@ model Benutzer {
rolle BenutzerRolle @default(USER) rolle BenutzerRolle @default(USER)
firma String? firma String?
lex_office_id String? lex_office_id String?
partner_code String?
verified Boolean @default(false) verified Boolean @default(false)
@@ -52,8 +50,6 @@ model Benutzer {
events Event[] events Event[]
@@map("benutzer") @@map("benutzer")
Provisionen Provisionen[]
bilder Bild[]
} }

View File

@@ -15,8 +15,6 @@ model Bild {
created_at DateTime @default(now()) created_at DateTime @default(now())
updated_at DateTime @updatedAt @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_id String?
aufnahme Aufnahme? @relation(fields: [aufnahme_id], references: [id], onDelete: NoAction, onUpdate: NoAction) aufnahme Aufnahme? @relation(fields: [aufnahme_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
} }

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

@@ -3,43 +3,17 @@
# === Configuration === # === Configuration ===
BUCKET_NAME="ibc-db-backup" BUCKET_NAME="ibc-db-backup"
ENDPOINT_URL="https://s3.eu-central-3.ionoscloud.com" ENDPOINT_URL="https://s3.eu-central-3.ionoscloud.com"
LOCAL_DOWNLOAD_DIR="./" # Where to save the file LOCAL_DOWNLOAD_DIR="./"
DATABASE_NAME=database
# === Check if a custom file is given as a command line argument === # === Use filename from argument if provided ===
if [ $# -eq 1 ]; then if [ -n "$1" ]; then
CUSTOM_FILE="$1" LATEST_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 else
echo "🔍 No custom file provided, searching for latest .sql.br file in S3" echo "📡 No filename provided, fetching latest..."
# === Get latest file from IONOS S3 bucket === # === Get latest file from IONOS S3 bucket ===
LATEST_FILE=$(aws s3api list-objects-v2 \ LATEST_FILE=$(aws --profile ionos s3api list-objects-v2 \
--bucket "$BUCKET_NAME" \ --bucket "$BUCKET_NAME" \
--prefix "data-dump" \ --prefix "full-dump" \
--endpoint-url "$ENDPOINT_URL" \ --endpoint-url "$ENDPOINT_URL" \
--query 'Contents | sort_by(@, &LastModified) | [-1].Key' \ --query 'Contents | sort_by(@, &LastModified) | [-1].Key' \
--output text) --output text)
@@ -49,30 +23,25 @@ else
echo "❌ No matching .sql.br file found." echo "❌ No matching .sql.br file found."
exit 1 exit 1
fi 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"
fi fi
FILENAME=$(basename "$LATEST_FILE")
SQL_FILE="${FILENAME%.br}" # Remove .br suffix
echo "📥 Downloading $LATEST_FILE"
aws --profile ionos s3 cp "s3://$BUCKET_NAME/$LATEST_FILE" "$LOCAL_DOWNLOAD_DIR" \
--endpoint-url "$ENDPOINT_URL"
# === Decompress with Brotli ===
echo "🗜️ Decompressing $FILENAME -> $SQL_FILE"
brotli -d "$FILENAME"
# === Import into Postgres inside Docker === # === Import into Postgres inside Docker ===
echo "🐘 Importing into PostgreSQL ($DATABASE_NAME:main)" echo "🐘 Importing into PostgreSQL (database:main)"
docker exec -i "$DATABASE_NAME" env PGPASSWORD="hHMP8cd^N3SnzGRR" \ docker exec -i "database" env PGPASSWORD="hHMP8cd^N3SnzGRR" \
psql -v ON_ERROR_STOP=0 -U main -d main < "$SQL_FILE" psql -U "main" -d "main" < "$SQL_FILE"
echo "✅ Import complete." echo "✅ Import complete."
# === Optional: Clean up # === Optional: Clean up
# If custom file was provided, do not delete it rm "$FILENAME" "$SQL_FILE"
if [ -z "$CUSTOM_FILE" ]; then
echo "🧹 Cleaning up downloaded files..."
rm "$FILENAME" "$SQL_FILE"
fi

View File

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

View File

@@ -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,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

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

View File

@@ -214,8 +214,8 @@ grid-cols-1 gap-x-2 gap-y-4
/> />
<div class="text-center xs:text-left justify-self-stretch"> <div class="text-center xs:text-left justify-self-stretch">
<b>Selbsteingabe online</b><br>inkl. ausführlicher&nbsp;telefonischer <b>Verbrauchsausweis online</b><br>inkl. ausführlicher&nbsp;telefonischer
Beratung! Beratung
</div> </div>
<div class="text-center xs:text-right"> <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"> <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>
<div class="text-center xs:text-right"> <div class="text-center xs:text-right">

View File

@@ -11,22 +11,6 @@
export let objekt: ObjektClient; export let objekt: ObjektClient;
export let ausweisart: Enums.Ausweisart; 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> </script>
<div <div
@@ -113,11 +97,10 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
data-test="flaeche" data-test="flaeche"
maxlength="4" maxlength="4"
type="number" type="number"
step="1"
required required
autocomplete="off" autocomplete="off"
on:keypress={onlyAllowIntegerInput} data-rule-minlength="2"
on:paste={onlyAllowPastingIntegers} data-msg-minlength="min. 2 Zeichen"
bind:value={aufnahme.flaeche} bind:value={aufnahme.flaeche}
/> />
@@ -141,10 +124,7 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
data-test="nutzflaeche" data-test="nutzflaeche"
maxlength="4" maxlength="4"
type="number" type="number"
step="1"
required required
on:keypress={onlyAllowIntegerInput}
on:paste={onlyAllowPastingIntegers}
bind:value={aufnahme.nutzflaeche} bind:value={aufnahme.nutzflaeche}
/> />

View File

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

View File

@@ -16,7 +16,7 @@ const brennstoffe: [
["Flüssiggas", "kg", 13.0, 1.1, 0.27], ["Flüssiggas", "kg", 13.0, 1.1, 0.27],
["Braunkohle", "kg", 5.5, 1.2, 0.43], ["Braunkohle", "kg", 5.5, 1.2, 0.43],
["Holzhackschnitzel", "SRm", 650.0, 0.2, 0.02], ["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], ["Fernwärme KWK FB", "kWh", 1.0, 0.7, 0.3],
["Nahwä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], ["Heizöl EL", "kWh", 1.0, 1.1, 0.31],
@@ -42,9 +42,6 @@ const brennstoffe: [
["Fernwärme HKW FB", "kWh", 1.0, 1.3, 0.4], ["Fernwärme HKW FB", "kWh", 1.0, 1.3, 0.4],
["Fernwärme HKW EB", "kWh", 1.0, 0.1, 0.06], ["Fernwärme HKW EB", "kWh", 1.0, 0.1, 0.06],
["Fernwärme Hamburg", "kWh", 1.0, 0.33, 0.064], ["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], ["Erdgas", "kWh", 1.0, 1.1, 0.24],
["Heizöl", "kWh", 1.0, 1.1, 0.31], ["Heizöl", "kWh", 1.0, 1.1, 0.31],
["Heizöl", "l", 10.0, 1.1, 0.31], ["Heizöl", "l", 10.0, 1.1, 0.31],

View File

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

View File

@@ -2,7 +2,6 @@
export let hidden: boolean = true; export let hidden: boolean = true;
export let closeable: boolean = true; export let closeable: boolean = true;
import { disableBodyScroll, enableBodyScroll } from 'body-scroll-lock'; import { disableBodyScroll, enableBodyScroll } from 'body-scroll-lock';
import { Cross1 } from 'radix-svelte-icons';
$: if (globalThis.window) { $: if (globalThis.window) {
if (hidden) { if (hidden) {
@@ -13,11 +12,13 @@
} }
</script> </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} {#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; hidden = true;
}}><Cross1 size={20}></Cross1></button> }}>Schließen</button>
{/if} {/if}
<slot></slot> <slot></slot>
</div> </div>

View File

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

View File

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

View File

@@ -1,9 +1,7 @@
<script lang="ts"> <script lang="ts">
import HelpLabel from "#components/labels/HelpLabel.svelte"; import HelpLabel from "#components/labels/HelpLabel.svelte";
import type { Enums } from "#lib/client/prisma.js"; import type { Enums } from "#lib/client/prisma.js";
import Cookies from "js-cookie";
import { tryCatch } from "#lib/tryCatch.js"; import { tryCatch } from "#lib/tryCatch.js";
import heic2any from "heic2any";
export let max: number = 2; export let max: number = 2;
export let min: number = 1; export let min: number = 1;
@@ -19,7 +17,6 @@
} from "./Ausweis/types.js"; } from "./Ausweis/types.js";
import { api } from "astro-typesafe-api/client"; import { api } from "astro-typesafe-api/client";
import { addNotification } from "./Notifications/shared.js"; import { addNotification } from "./Notifications/shared.js";
import { API_ACCESS_TOKEN_COOKIE_NAME } from "#lib/constants.js";
export let images: BildClient[] = []; export let images: BildClient[] = [];
export let ausweis: export let ausweis:
@@ -39,10 +36,8 @@
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
const file = files[i]; const file = files[i];
console.log(file);
if (file.type !== "image/jpeg" && file.type !== "image/png") {
if (file.type !== "image/jpeg" && file.type !== "image/png" && file.type !== "image/webp" && file.type !== "image/heif" && file.type !== "image/heic") {
continue; continue;
} }
@@ -52,7 +47,7 @@
const reader = new FileReader(); const reader = new FileReader();
reader.onload = async () => { reader.onload = () => {
if (reader.readyState != reader.DONE) { if (reader.readyState != reader.DONE) {
return; return;
} }
@@ -62,18 +57,6 @@
} }
let blob = new Blob([reader.result as ArrayBuffer]); 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 url = URL.createObjectURL(blob);
let image = new Image(); let image = new Image();
image.onload = async () => { image.onload = async () => {
@@ -102,12 +85,6 @@
data: dataURL, data: dataURL,
kategorie, kategorie,
name: file.name name: file.name
}, {
headers: {
"Authorization": `Bearer ${Cookies.get(
API_ACCESS_TOKEN_COOKIE_NAME
)}`
}
})) }))
if (error) { if (error) {
@@ -151,7 +128,6 @@
<div class="input-standard"> <div class="input-standard">
<input <input
type="file" type="file"
accept="image/*"
class="file-input file-input-ghost h-[38px]" class="file-input file-input-ghost h-[38px]"
bind:this={fileUpload} bind:this={fileUpload}
{name} {name}
@@ -168,7 +144,6 @@
<div class="input-standard"> <div class="input-standard">
<input <input
type="file" type="file"
accept="image/*"
class="file-input file-input-ghost h-[38px]" class="file-input file-input-ghost h-[38px]"
bind:this={fileUpload} bind:this={fileUpload}
{name} {name}

View File

@@ -11,7 +11,7 @@ export async function auditEndEnergie(ausweis: VerbrauchsausweisWohnenClient | V
if (aufnahme){ if (aufnahme){
if (aufnahme.flaeche && ausweis.verbrauch_1 && ausweis.verbrauch_2 && ausweis.verbrauch_3) { if (aufnahme.flaeche && ausweis.verbrauch_1 && ausweis.verbrauch_2 && ausweis.verbrauch_3) {
try { 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. // Alle Klimfaktoren konnten abgefragt werden.
const eevva = await endEnergieVerbrauchVerbrauchsausweis_2016(ausweis, aufnahme, objekt); const eevva = await endEnergieVerbrauchVerbrauchsausweis_2016(ausweis, aufnahme, objekt);
if (eevva){ if (eevva){

View File

@@ -39,38 +39,63 @@ const isNET = pathname.includes("immonet");
</div> </div>
<div> <div>
<!-- Navigation als Liste (nur ab sm sichtbar) --> <ul class="navlist">
<ul class="navlist hidden xl:flex"> <li>
<li><a href={`/${partner}/energieausweis-erstellen/verbrauchsausweis-wohngebaeude/`}><button class={tab === 0 ? "glow" : ""}>Verbrauchsausweis</button></a></li> <a
<li><a href={`/${partner}/energieausweis-erstellen/verbrauchsausweis-gewerbe/`}><button class={tab === 1 ? "glow" : ""}>Verbrauchsausweis Gewerbe</button></a></li> href={`/${partner}/energieausweis-erstellen/verbrauchsausweis-wohngebaeude/`}
<li><a href={`/${partner}/energieausweis-erstellen/bedarfsausweis-wohngebaeude/`}><button class={tab === 2 ? "glow" : ""}>Bedarfsausweis</button></a></li> ><button class={tab === 0 ? "glow" : ""}
<li><a href={`/${partner}/angebot-anfragen/bedarfsausweis-gewerbe-anfragen/`}><button class={tab === 3 ? "glow" : ""}>Bedarfsausweis Gewerbe</button></a></li> >Verbrauchsausweis</button
<li><a href={`/${partner}/angebot-anfragen/geg-nachweis-wohnen-anfragen/`}><button class={tab === 4 ? "glow" : ""}>GEG Nachweis Wohngebäude</button></a></li> ></a>
<li><a href={`/${partner}/angebot-anfragen/geg-nachweis-gewerbe-anfragen/`}><button class={tab === 5 ? "glow" : ""}>GEG Nachweis Gewerbe</button></a></li> </li>
<li><a href={`/${partner}/welcher-ausweis/${partner}`}><button class={tab === 6 ? "glow" : ""}>Welcher Ausweis</button></a></li> <li>
</ul> <a
href={`/${partner}/energieausweis-erstellen/verbrauchsausweis-gewerbe/`}
<!-- Navigation als Dropdown (nur bis sm sichtbar) --> ><button class={tab === 1 ? "glow" : ""}
<select >Verbrauchsausweis Gewerbe</button
class="xl:hidden border rounded p-2 w-[calc(100%-2rem)]" ></a>
onchange="if (this.value) window.location.href=this.value" </li>
> <li>
<option value="">Auswahl treffen…</option> <a
<option value={`/${partner}/energieausweis-erstellen/verbrauchsausweis-wohngebaeude/`} selected={tab === 0}>Verbrauchsausweis</option> href={`/${partner}/energieausweis-erstellen/bedarfsausweis-wohngebaeude/`}
<option value={`/${partner}/energieausweis-erstellen/verbrauchsausweis-gewerbe/`} selected={tab === 1}>Verbrauchsausweis Gewerbe</option> ><button class={tab === 2 ? "glow" : ""}
<option value={`/${partner}/energieausweis-erstellen/bedarfsausweis-wohngebaeude/`} selected={tab === 2}>Bedarfsausweis</option> >Bedarfsausweis</button
<option value={`/${partner}/angebot-anfragen/bedarfsausweis-gewerbe-anfragen/`} selected={tab === 3}>Bedarfsausweis Gewerbe</option> ></a>
<option value={`/${partner}/angebot-anfragen/geg-nachweis-wohnen-anfragen/`} selected={tab === 4}>GEG Nachweis Wohngebäude</option> </li>
<option value={`/${partner}/angebot-anfragen/geg-nachweis-gewerbe-anfragen/`} selected={tab === 5}>GEG Nachweis Gewerbe</option> <li>
<option value={`/${partner}/welcher-ausweis/${partner}`} selected={tab === 6}>Welcher Ausweis</option> <a
</select> 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>
</div> </div>
</header> </header>
<div <div
id="titel" 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'); style={`background-image: url('/images/partner/${partner}/hero-energieausweis.jpg');
background-repeat:no-repeat; background-position:right;`} background-repeat:no-repeat; background-position:right;`}
> >
@@ -161,7 +186,7 @@ background-repeat:no-repeat; background-position:right;`}
font-family: "immo Sans"; font-family: "immo Sans";
font-weight:400; 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 { .header-button {

View File

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

View File

@@ -34,7 +34,6 @@
ab {price} ab {price}
</p> </p>
</div> </div>
</div> </div>
<hr class="col-span-2 w-full md:w-[50%] md:m-auto bg-[#ffcc00] h-[2px]" /> <hr class="col-span-2 w-full md:w-[50%] md:m-auto bg-[#ffcc00] h-[2px]" />
@@ -50,11 +49,13 @@
{/each} {/each}
</div> </div>
<hr class="col-span-2 w-full md:w-[50%] md:m-auto bg-[#ffcc00] h-[2px]" /> <hr class="col-span-2 w-full md:w-[50%] md:m-auto bg-[#ffcc00] h-[2px]" />
<div class="sumCent buttoncols" <div
class:md:grid-cols-3={href_buy3} class="sumCent buttoncols"
class:md:grid-cols-2={!href_buy3}> class:md:grid-cols-3={href_buy3}
class:md:grid-cols-2={!href_buy3}
>
<a <a
href={href_buy1} href={href_buy1}
class="buttoncol" class="buttoncol"

View File

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

View File

@@ -47,10 +47,10 @@ export const BedarfsausweisWohnenSchema = z.object({
haustuer_art: z.number().nullish(), haustuer_art: z.number().nullish(),
dach_bauart: z.string().nullish(), dach_bauart: z.string().nullish(),
decke_bauart: z.string().nullish(), decke_bauart: z.string().nullish(),
dach_daemmung: z.number().nullish(), dach_daemmung: z.string().nullish(),
decke_daemmung: z.number().nullish(), decke_daemmung: z.string().nullish(),
aussenwand_daemmung: z.number().nullish(), aussenwand_daemmung: z.string().nullish(),
boden_daemmung: z.number().nullish(), boden_daemmung: z.string().nullish(),
aussenwand_bauart: z.string().nullish(), aussenwand_bauart: z.string().nullish(),
boden_bauart: z.string().nullish(), boden_bauart: z.string().nullish(),
warmwasser_verteilung: z.string().nullish(), warmwasser_verteilung: z.string().nullish(),

View File

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

View File

@@ -7,6 +7,5 @@ export const BildSchema = z.object({
name: z.string(), name: z.string(),
created_at: z.date(), created_at: z.date(),
updated_at: z.date(), updated_at: z.date(),
benutzer_id: z.string().nullish(),
aufnahme_id: z.string().nullish(), aufnahme_id: z.string().nullish(),
}) })

View File

@@ -12,7 +12,6 @@ export * from "./gegnachweiswohnen"
export * from "./klimafaktoren" export * from "./klimafaktoren"
export * from "./objekt" export * from "./objekt"
export * from "./postleitzahlen" export * from "./postleitzahlen"
export * from "./provisionen"
export * from "./rechnung" export * from "./rechnung"
export * from "./refreshtokens" export * from "./refreshtokens"
export * from "./tickets" export * from "./tickets"

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

@@ -150,7 +150,7 @@ height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<Footer /> <Footer />
<NotificationWrapper client:load /> <NotificationWrapper client:load />
<TicketButton client:load userEmail={user?.email ?? ""}></TicketButton> <TicketButton client:load></TicketButton>
</body> </body>
</html> </html>

View File

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

View File

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

View File

@@ -7,13 +7,17 @@ export const getKlimafaktoren = memoize(async (date: Date, plz: string) => {
return null; return null;
} }
const response = await api.klimafaktoren.GET.fetch({ try {
plz, const response = await api.klimafaktoren.GET.fetch({
genauigkeit: "years", plz,
// @ts-ignore Der Adapter nimmt z.coerce.date() genauigkeit: "years",
startdatum: moment(date).utc(true).toString(), // @ts-ignore Der Adapter nimmt z.coerce.date()
// @ts-ignore Der Adapter nimmt z.coerce.date() startdatum: moment(date).utc(true).toString(),
enddatum: moment(date).add(2, "years").utc(true).toString(), // @ts-ignore Der Adapter nimmt z.coerce.date()
}); enddatum: moment(date).add(2, "years").utc(true).toString(),
return response; });
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> { export function memoize<T>(func: (...args: any[]) => T): MemoizedFunction<T> {
const cache = new Map<string, ReturnType<T>>(); const cache = new Map<string, T>();
return (...args: Parameters<T>): Promise<ReturnType<T>> | ReturnType<T> => { return (...args: any[]): T => {
const key = JSON.stringify(args); const key = JSON.stringify(args);
if (cache.has(key)) { if (cache.has(key)) {
return cache.get(key)!; return cache.get(key)!;
} }
const result = func(...args); const result = func(...args);
if (result instanceof Promise) { cache.set(key, result);
return result.then(resolved => {
return resolved; return result;
});
} else {
cache.set(key, result);
return result;
}
}; };
} }

View File

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

View File

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

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 { import { AufnahmeClient, BedarfsausweisWohnenClient, BenutzerClient, BildClient, getAusweisartFromId, ObjektClient, RechnungClient, VerbrauchsausweisGewerbeClient, VerbrauchsausweisWohnenClient } from "#components/Ausweis/types.js";
AufnahmeClient,
BedarfsausweisWohnenClient,
BenutzerClient,
BildClient,
getAusweisartFromId,
ObjektClient,
RechnungClient,
VerbrauchsausweisGewerbeClient,
VerbrauchsausweisWohnenClient,
} from "#components/Ausweis/types.js";
import { pdfDatenblattVerbrauchsausweisGewerbe } from "#lib/pdf/pdfDatenblattVerbrauchsausweisGewerbe.js"; import { pdfDatenblattVerbrauchsausweisGewerbe } from "#lib/pdf/pdfDatenblattVerbrauchsausweisGewerbe.js";
import { pdfDatenblattVerbrauchsausweisWohnen } from "#lib/pdf/pdfDatenblattVerbrauchsausweisWohnen.js"; import { pdfDatenblattVerbrauchsausweisWohnen } from "#lib/pdf/pdfDatenblattVerbrauchsausweisWohnen.js";
import { pdfVerbrauchsausweisGewerbe } from "#lib/pdf/pdfVerbrauchsausweisGewerbe.js"; import { pdfVerbrauchsausweisGewerbe } from "#lib/pdf/pdfVerbrauchsausweisGewerbe.js";
import { pdfVerbrauchsausweisWohnen } from "#lib/pdf/pdfVerbrauchsausweisWohnen.js"; import { pdfVerbrauchsausweisWohnen } from "#lib/pdf/pdfVerbrauchsausweisWohnen.js";
import { pdfAushangVerbrauchsausweisGewerbe } from "#lib/pdf/pdfAushangVerbrauchsausweisGewerbe.js"; import { pdfAushangVerbrauchsausweisGewerbe } from "#lib/pdf/pdfAushangVerbrauchsausweisGewerbe.js";
import { import { Enums, prisma, Rechnung } from "#lib/server/prisma.js";
BedarfsausweisWohnen,
Enums,
prisma,
Rechnung,
VerbrauchsausweisGewerbe,
VerbrauchsausweisWohnen,
} 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. * Gibt den richtigen Prisma Adapter für die Ausweisart basierend auf der UID zurück, oder null bei einer falschen UID.
@@ -31,17 +15,17 @@ export function getPrismaAusweisAdapter(id: string) {
const ausweisart = getAusweisartFromId(id); const ausweisart = getAusweisartFromId(id);
if (ausweisart === Enums.Ausweisart.VerbrauchsausweisWohnen) { if (ausweisart === Enums.Ausweisart.VerbrauchsausweisWohnen) {
return prisma.verbrauchsausweisWohnen; return prisma.verbrauchsausweisWohnen
} else if (ausweisart === Enums.Ausweisart.VerbrauchsausweisGewerbe) { } else if (ausweisart === Enums.Ausweisart.VerbrauchsausweisGewerbe) {
return prisma.verbrauchsausweisGewerbe; return prisma.verbrauchsausweisGewerbe
} else if (ausweisart === Enums.Ausweisart.BedarfsausweisWohnen) { } else if (ausweisart === Enums.Ausweisart.BedarfsausweisWohnen) {
return prisma.bedarfsausweisWohnen; return prisma.bedarfsausweisWohnen
} else if (ausweisart === Enums.Ausweisart.GEGNachweisWohnen) { } else if (ausweisart === Enums.Ausweisart.GEGNachweisWohnen) {
return prisma.gEGNachweisWohnen; return prisma.gEGNachweisWohnen
} else if (ausweisart === Enums.Ausweisart.GEGNachweisGewerbe) { } else if (ausweisart === Enums.Ausweisart.GEGNachweisGewerbe) {
return prisma.gEGNachweisGewerbe; return prisma.gEGNachweisGewerbe
} else if (ausweisart === Enums.Ausweisart.BedarfsausweisGewerbe) { } else if (ausweisart === Enums.Ausweisart.BedarfsausweisGewerbe) {
return prisma.bedarfsausweisGewerbe; return prisma.bedarfsausweisGewerbe
} }
} }
@@ -49,166 +33,50 @@ export function getPrismaAusweisAdapter(id: string) {
* Gibt den richtigen Ansichtsausweis basierend auf der Ausweisart zurück. * Gibt den richtigen Ansichtsausweis basierend auf der Ausweisart zurück.
* @param ausweis * @param ausweis
*/ */
export async function getAnsichtsausweis( export async function getAnsichtsausweis(ausweis: VerbrauchsausweisWohnenClient | VerbrauchsausweisGewerbeClient | BedarfsausweisWohnenClient, aufnahme: AufnahmeClient, objekt: ObjektClient, bilder: BildClient[], user: BenutzerClient, vorschau: boolean = true, ausweisart = getAusweisartFromId(ausweis.id)) {
ausweis:
| VerbrauchsausweisWohnenClient
| VerbrauchsausweisGewerbeClient
| BedarfsausweisWohnenClient,
aufnahme: AufnahmeClient,
objekt: ObjektClient,
bilder: BildClient[],
user: BenutzerClient,
vorschau: boolean = true,
ausweisart = getAusweisartFromId(ausweis.id)
) {
if (!ausweisart) { if (!ausweisart) {
return null; return null
} }
if (ausweisart === Enums.Ausweisart.VerbrauchsausweisWohnen) { if (ausweisart === Enums.Ausweisart.VerbrauchsausweisWohnen) {
return await pdfVerbrauchsausweisWohnen( return await pdfVerbrauchsausweisWohnen(ausweis as VerbrauchsausweisWohnenClient, aufnahme, objekt, bilder, user, vorschau)
ausweis as VerbrauchsausweisWohnenClient,
aufnahme,
objekt,
bilder,
user,
vorschau
);
} else if (ausweisart === Enums.Ausweisart.VerbrauchsausweisGewerbe) { } else if (ausweisart === Enums.Ausweisart.VerbrauchsausweisGewerbe) {
return await pdfVerbrauchsausweisGewerbe( return await pdfVerbrauchsausweisGewerbe(ausweis as VerbrauchsausweisGewerbeClient, aufnahme, objekt, bilder, user, vorschau)
ausweis as VerbrauchsausweisGewerbeClient,
aufnahme,
objekt,
bilder,
user,
vorschau
);
} }
return null; return null
} }
/** /**
* Gibt das richtige Datenblatt basierend auf der Ausweisart zurück. * Gibt das richtige Datenblatt basierend auf der Ausweisart zurück.
* @param ausweis * @param ausweis
*/ */
export async function getDatenblatt( export async function getDatenblatt(ausweis: VerbrauchsausweisWohnenClient | VerbrauchsausweisGewerbeClient | BedarfsausweisWohnenClient, aufnahme: AufnahmeClient, objekt: ObjektClient, bilder: BildClient[], user: BenutzerClient, rechnung: Rechnung, ausweisart = getAusweisartFromId(ausweis.id)) {
ausweis:
| VerbrauchsausweisWohnenClient
| VerbrauchsausweisGewerbeClient
| BedarfsausweisWohnenClient,
aufnahme: AufnahmeClient,
objekt: ObjektClient,
bilder: BildClient[],
user: BenutzerClient,
rechnung: Rechnung,
ausweisart = getAusweisartFromId(ausweis.id)
) {
if (!ausweisart) { if (!ausweisart) {
return null; return null
} }
if (ausweisart === Enums.Ausweisart.VerbrauchsausweisWohnen) { if (ausweisart === Enums.Ausweisart.VerbrauchsausweisWohnen) {
return await pdfDatenblattVerbrauchsausweisWohnen( return await pdfDatenblattVerbrauchsausweisWohnen(ausweis as VerbrauchsausweisWohnenClient, aufnahme, objekt, rechnung, bilder)
ausweis as VerbrauchsausweisWohnenClient,
aufnahme,
objekt,
rechnung,
bilder
);
} else if (ausweisart === Enums.Ausweisart.VerbrauchsausweisGewerbe) { } else if (ausweisart === Enums.Ausweisart.VerbrauchsausweisGewerbe) {
return await pdfDatenblattVerbrauchsausweisGewerbe( return await pdfDatenblattVerbrauchsausweisGewerbe(ausweis as VerbrauchsausweisGewerbeClient, aufnahme, objekt, rechnung, bilder)
ausweis as VerbrauchsausweisGewerbeClient,
aufnahme,
objekt,
rechnung,
bilder
);
} }
return null; return null
} }
/** /**
* Gibt den richtigen Aushang basierend auf der Ausweisart zurück. * Gibt den richtigen Aushang basierend auf der Ausweisart zurück.
* @param ausweis * @param ausweis
*/ */
export async function getAushang( 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)) {
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)) { if (!ausweisart || !rechnung.services.includes(Enums.Service.Aushang)) {
return null; return null
} }
if (ausweisart === Enums.Ausweisart.VerbrauchsausweisGewerbe) { if (ausweisart === Enums.Ausweisart.VerbrauchsausweisGewerbe) {
return await pdfAushangVerbrauchsausweisGewerbe( return await pdfAushangVerbrauchsausweisGewerbe(ausweis as VerbrauchsausweisGewerbeClient, aufnahme, objekt, bilder, user, vorschau)
ausweis as VerbrauchsausweisGewerbeClient,
aufnahme,
objekt,
bilder,
user,
vorschau
);
} }
return null; 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"
>;
} }

View File

@@ -0,0 +1,41 @@
import { transport } from "#lib/mail.js";
import {
Benutzer,
} from "#lib/client/prisma.js";
import { createTimeBasedHash } from "#lib/auth/time-based-hash.js";
export async function sendVerificationCodeMail(
user: Benutzer
) {
const code = createTimeBasedHash(user.email)
await transport.sendMail({
from: `"IBCornelsen" <info@online-energieausweis.org>`,
to: user.email,
subject: `Ihre Registrierung bei IBCornelsen`,
bcc: "info@online-energieausweis.org",
html: `<p>Sehr geehrte*r ${user.vorname} ${user.name},</p>
<p>Um Ihre Registrierung abzuschließen, geben Sie folgenden Bestätigungscode auf der Website ein, um Ihre E-Mail-Adresse zu bestätigen:<br><br>
<b>${code}</b>
</p>
<p>
Mit freundlichen Grüßen,
<br>
Dipl.-Ing. Jens Cornelsen
<br>
<br>
<strong>IB Cornelsen</strong>
<br>
Katendeich 5A
<br>
21035 Hamburg
<br>
www.online-energieausweis.org
<br>
<br>
fon 040 · 209339850
<br>
fax 040 · 209339859
</p>`
});
}

View File

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

View File

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

View File

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

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

View File

@@ -3,6 +3,7 @@
import { CrossCircled } from "radix-svelte-icons"; import { CrossCircled } from "radix-svelte-icons";
import { fade } from "svelte/transition"; import { fade } from "svelte/transition";
import { api } from "astro-typesafe-api/client"; import { api } from "astro-typesafe-api/client";
import NotificationProvider from "#components/NotificationProvider/NotificationProvider.svelte";
import NotificationWrapper from "#components/Notifications/NotificationWrapper.svelte"; import NotificationWrapper from "#components/Notifications/NotificationWrapper.svelte";
let passwort: string; let passwort: string;
@@ -12,6 +13,10 @@
export let redirect: string | null = null; export let redirect: string | null = null;
function handleInput(event) {
email = event.target.value.toLowerCase();
}
async function login(e: SubmitEvent) { async function login(e: SubmitEvent) {
e.preventDefault() e.preventDefault()
if (passwort.length < 8) { if (passwort.length < 8) {
@@ -25,7 +30,7 @@
} }
try { try {
const { id } = await api.user.PUT.fetch({ await api.user.PUT.fetch({
email, email,
passwort, passwort,
vorname, vorname,
@@ -37,7 +42,7 @@
return return
} }
window.location.href = "/auth/login"; window.location.href = `/auth/code?email=${email}`;
} catch (e) { } catch (e) {
errorHidden = false; errorHidden = false;
} }
@@ -82,7 +87,7 @@
name="email" name="email"
class="input input-bordered text-base text-base-content font-medium" class="input input-bordered text-base text-base-content font-medium"
bind:value={email} bind:value={email}
on:keyup={() => (email = email.toLowerCase())} on:input={handleInput}
required required
/> />
</div> </div>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -51,10 +51,7 @@ export const PUT = defineApiRoute({
// Wir erstellen eine Mollie Payment Referenz und eine neue Rechnung in unserer Datenbank, daraufhin geben // Wir erstellen eine Mollie Payment Referenz und eine neue Rechnung in unserer Datenbank, daraufhin geben
// wir eine Checkout URL zurück auf die der Nutzer weitergeleitet werden kann. // wir eine Checkout URL zurück auf die der Nutzer weitergeleitet werden kann.
let { ausweis_id, ausweisart, bezahlmethode, services, partner_code } = input; const { ausweis_id, ausweisart, bezahlmethode, services, partner_code } = input;
// Manchmal startet der partner_code mit Gänsefüßchen, das entfernen wir hier.
partner_code = partner_code?.replace(/"/g, "") ?? null;
const adapter = getPrismaAusweisAdapter(ausweis_id); const adapter = getPrismaAusweisAdapter(ausweis_id);

View File

@@ -50,7 +50,7 @@ export const PUT = defineApiRoute({
const url = new URL("https://api.trello.com/1/cards") const url = new URL("https://api.trello.com/1/cards")
url.searchParams.append("name", input.titel) url.searchParams.append("name", input.titel)
url.searchParams.append("desc", `User: ${input.email}\n\nDescription: ${input.beschreibung}\n\nKategorie: ${category}\n\nTelefon: ${(input.metadata as Record<string, string>).telefon || "Nicht angegeben"}`) url.searchParams.append("desc", `User: ${input.email}\n\nDescription: ${input.beschreibung}\n\nCategory: ${category}`)
url.searchParams.append("pos", "top") url.searchParams.append("pos", "top")
url.searchParams.append("idLabels", url.searchParams.append("idLabels",
(category && (category &&
@@ -58,7 +58,7 @@ export const PUT = defineApiRoute({
"650e909fdc09629a4d6d495d") "650e909fdc09629a4d6d495d")
url.searchParams.append("key", "e057eb39018368ea96e456c753ac41b4") url.searchParams.append("key", "e057eb39018368ea96e456c753ac41b4")
url.searchParams.append("idList", "67d75ca7403fd22c49bc7447") url.searchParams.append("idList", "67d75ca7403fd22c49bc7447")
url.searchParams.append("token", "ATTA6f1774d98472db1897a2373ee7b55ab15f218c2445b6609dfef3071fe5203a90DB15678A") url.searchParams.append("token", "ATTA8b65b3587ab627167038cc32a3460650973eb181cde01dabb208ca1e90ed5467AC06A4F2")
// Wir laden das Ticket zu Trello hoch. // Wir laden das Ticket zu Trello hoch.
const result = await fetch(url, { const result = await fetch(url, {

View File

@@ -65,10 +65,10 @@ export const GET = defineApiRoute({
})), })),
output: z.array(BenutzerSchema), output: z.array(BenutzerSchema),
middleware: authorizationMiddleware, middleware: authorizationMiddleware,
async fetch(input, context, benutzer) { async fetch(input, context, admin) {
if ("id" in input) { if ("id" in input) {
// Nur Admins oder der Benutzer selbst kann einen einzelnen Benutzer lesen //Only Admin can read other users
if (benutzer.rolle != Enums.BenutzerRolle.ADMIN && input.id != benutzer.id) { if (admin.rolle != Enums.BenutzerRolle.ADMIN && input.id != admin.id) {
return; return;
} }
@@ -84,8 +84,8 @@ export const GET = defineApiRoute({
return [user]; return [user];
} else { } else {
// Nur Admins können nach mehreren Benutzern suchen //Only admin can read many users
if (benutzer.rolle != Enums.BenutzerRolle.ADMIN) { if (admin.rolle != Enums.BenutzerRolle.ADMIN ) {
return; return;
} }
@@ -114,12 +114,9 @@ export const PUT = defineApiRoute({
id: IDWithPrefix id: IDWithPrefix
}), }),
async fetch(input) { async fetch(input) {
let { email, passwort, vorname, name } = input;
email = email.toLowerCase();
const existingUser = await prisma.benutzer.findUnique({ const existingUser = await prisma.benutzer.findUnique({
where: { where: {
email email: input.email
} }
}) })
@@ -134,10 +131,10 @@ export const PUT = defineApiRoute({
const user = await prisma.benutzer.create({ const user = await prisma.benutzer.create({
data: { data: {
email, email: input.email,
passwort: hashPassword(passwort), passwort: hashPassword(input.passwort),
vorname, vorname: input.vorname,
name, name: input.name,
id id
} }
}) })

View File

@@ -46,7 +46,7 @@ export const PATCH = defineApiRoute({
data: input data: input
}) })
if (user.rolle !== Enums.BenutzerRolle.ADMIN) { if (user.rolle === Enums.BenutzerRolle.USER) {
await sendAusweisGespeichertMail(user, ctx.params.id as string) await sendAusweisGespeichertMail(user, ctx.params.id as string)
} }
}, },
@@ -173,7 +173,7 @@ export const GET = defineApiRoute({
} }
const ausweis = await prisma.verbrauchsausweisGewerbe.findUnique({ const ausweis = await prisma.verbrauchsausweisGewerbe.findUnique({
where: user.rolle !== Enums.BenutzerRolle.ADMIN ? { where: user.rolle === Enums.BenutzerRolle.USER ? {
id, id,
benutzer_id: user.id benutzer_id: user.id
} : { id }, } : { id },

View File

@@ -47,7 +47,7 @@ export const PATCH = defineApiRoute({
data: input data: input
}) })
if (user.rolle !== Enums.BenutzerRolle.ADMIN) { if (user.rolle === Enums.BenutzerRolle.USER) {
await sendAusweisGespeichertMail(user, ctx.params.id as string) await sendAusweisGespeichertMail(user, ctx.params.id as string)
} }
}, },
@@ -174,7 +174,7 @@ export const GET = defineApiRoute({
} }
const ausweis = await prisma.verbrauchsausweisWohnen.findUnique({ const ausweis = await prisma.verbrauchsausweisWohnen.findUnique({
where: user.rolle !== Enums.BenutzerRolle.ADMIN ? { where: user.rolle === Enums.BenutzerRolle.USER ? {
id, id,
benutzer_id: user.id benutzer_id: user.id
} : { id }, } : { id },

View File

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

24
src/pages/auth/code.astro Normal file
View File

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

View File

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

View File

@@ -1,146 +0,0 @@
---
import abrechnungTemplateHTML from "../../../templates/pdf/abrechnung.handlebars?raw";
import puppeteer from "puppeteer";
import Handlebars from "handlebars";
import moment from "moment";
import { getCurrentUser } from "#lib/server/user";
import { prisma } from "#lib/server/prisma";
import { extrahiereAusweisAusFeldMitMehrerenAusweisen } from "#lib/server/ausweis";
import { getProvision } from "#lib/provision";
moment.locale("de");
moment.tz.setDefault("Europe/Berlin");
const datum = moment(Astro.url.searchParams.get("d")).set("date", 1).set("hour", 0).set("minute", 0).set("second", 0);
const benutzer = await getCurrentUser(Astro);
// Wir dürfen die Abrechnung erst ab Juni starten lassen.
if (datum.isBefore(moment().set("year", 2025).set("month", 4).endOf("month"))) {
return Astro.redirect("/404")
}
if (!benutzer) {
return Astro.redirect("/404");
}
let bestellungen = await prisma.rechnung.findMany({
where: {
partner_code: benutzer.partner_code,
OR: [
{
verbrauchsausweis_gewerbe: {
ausgestellt: true,
},
},
{
bedarfsausweis_wohnen: {
ausgestellt: true,
},
},
{
verbrauchsausweis_wohnen: {
ausgestellt: true,
},
},
],
AND: [
{
erstellt_am: {
gte: datum.startOf("month").toDate(),
},
},
{
erstellt_am: {
lte: datum.endOf("month").toDate(),
},
},
],
},
orderBy: {
erstellt_am: "desc",
},
include: {
bedarfsausweis_wohnen: {
include: {
aufnahme: {
include: {
objekt: true,
},
},
},
},
verbrauchsausweis_gewerbe: {
include: {
aufnahme: {
include: {
objekt: true,
},
},
},
},
verbrauchsausweis_wohnen: {
include: {
aufnahme: {
include: {
objekt: true,
},
},
},
},
},
});
const provisionen = await prisma.provisionen.findMany({
where: {
benutzer_id: benutzer.id
}
})
const ausweisBestellungen = bestellungen.map(bestellung => extrahiereAusweisAusFeldMitMehrerenAusweisen(bestellung));
const browser = await puppeteer.launch({
headless: true,
args: ["--no-sandbox", "--disable-setuid-sandbox"],
});
const page = await browser.newPage();
// Wir splitten die Daten in Blöcke auf, der erste Block hat 11 Einträge, die folgenden Blöcke haben 15 Einträge.
const blocks = [];
const firstBlock = ausweisBestellungen.slice(0, 16);
const remainingBlocks = ausweisBestellungen.slice(16);
blocks.push(firstBlock);
for (let i = 0; i < remainingBlocks.length; i += 20) {
blocks.push(remainingBlocks.slice(i, i + 20));
}
Handlebars.registerHelper("get-provision-prozent", function (ausweisart, ausweistyp) {
const { provision_prozent } = getProvision(ausweisart, ausweistyp, provisionen);
return provision_prozent || 0;
});
Handlebars.registerHelper("get-provision-betrag", function (ausweisart, ausweistyp) {
const { provision_betrag } = getProvision(ausweisart, ausweistyp, provisionen);
return provision_betrag ? provision_betrag.toFixed(2) : "0.00";
});
const gesamt = ausweisBestellungen.reduce((acc, bestellung) => {
const { provision_betrag } = getProvision(bestellung.ausweis.ausweisart, bestellung.ausweis.ausweistyp, provisionen);
return acc + (provision_betrag || 0);
}, 0).toFixed(2);
const template = Handlebars.compile(abrechnungTemplateHTML);
const html = template({ monat: datum.format("MMMM YYYY"), bestellungen: blocks, heute: moment().format("DD.MM.YYYY"), plz: benutzer.plz, ort: benutzer.ort, adresse: benutzer.adresse, firma: benutzer.firma, email: benutzer.email, gesamt });
await page.goto(`data:text/html;charset=UTF-8,${encodeURIComponent(html)}`, {
waitUntil: "networkidle0",
});
const pdf = await page.pdf({ format: "A4" });
await browser.close();
return new Response(pdf, {
headers: {
"Content-Type": "application/pdf",
"Content-Disposition": `attachment; filename="Abrechnung_${datum.format("YYYY_MM")}.pdf"`,
},
});
---

View File

@@ -61,5 +61,5 @@ Astro.cookies.set(API_ACCESS_TOKEN_COOKIE_NAME, accessToken, {
expires: moment().add(30, "minutes").toDate() expires: moment().add(30, "minutes").toDate()
}) })
return Astro.redirect("/dashboard"); return Astro.redirect("/dashboard")
--- ---

View File

@@ -28,7 +28,7 @@ if (!user) {
const aufnahme = await prisma.aufnahme.findUnique({ const aufnahme = await prisma.aufnahme.findUnique({
where: user.rolle !== Enums.BenutzerRolle.ADMIN ? { where: user.rolle === Enums.BenutzerRolle.USER ? {
benutzer: { benutzer: {
id: user.id id: user.id
}, },

View File

@@ -15,7 +15,7 @@ if (!user) {
const totalPageCount = await prisma.aufnahme.count({ const totalPageCount = await prisma.aufnahme.count({
where: where:
user.rolle !== Enums.BenutzerRolle.ADMIN user.rolle === Enums.BenutzerRolle.USER
? { ? {
benutzer: { benutzer: {
id: user.id, id: user.id,
@@ -27,7 +27,7 @@ const totalPageCount = await prisma.aufnahme.count({
let ausweis; let ausweis;
// Wir fragen den neuesten Ausweis ab // Wir fragen den neuesten Ausweis ab
// Falls der Nutzer ein Admin ist dann kommt der ganz neueste ansonsten der neueste des eingeloggten Benutzers. // Falls der Nutzer ein Admin ist dann kommt der ganz neueste ansonsten der neueste des eingeloggten Benutzers.
if (user.rolle !== Enums.BenutzerRolle.ADMIN) { if (user.rolle === Enums.BenutzerRolle.USER) {
const adapter = getPrismaAusweisAdapter(id); const adapter = getPrismaAusweisAdapter(id);
ausweis = await adapter?.findUnique({ ausweis = await adapter?.findUnique({
where: { where: {

View File

@@ -1,6 +1,8 @@
--- ---
import { Enums, prisma } from "#lib/server/prisma"; import { Enums, prisma } from "#lib/server/prisma";
import UserLayout from "#layouts/DashboardLayout.astro";
import { getCurrentUser } from "#lib/server/user"; import { getCurrentUser } from "#lib/server/user";
import moment from "moment";
const page = Number(Astro.url.searchParams.get("p")); const page = Number(Astro.url.searchParams.get("p"));
@@ -12,7 +14,7 @@ if (!user) {
const totalPageCount = await prisma.aufnahme.count({ const totalPageCount = await prisma.aufnahme.count({
where: where:
(user.rolle !== Enums.BenutzerRolle.ADMIN) user.rolle === Enums.BenutzerRolle.USER
? { ? {
benutzer: { benutzer: {
id: user.id, id: user.id,
@@ -21,17 +23,14 @@ const totalPageCount = await prisma.aufnahme.count({
: {}, : {},
}); });
if (page < 1 || page > totalPageCount) {
if ((page < 1 || page > totalPageCount) && totalPageCount > 0) {
return Astro.redirect("/dashboard/objekte?p=1"); return Astro.redirect("/dashboard/objekte?p=1");
} else if (totalPageCount === 0) {
return Astro.redirect("/dashboard/objekte/leer");
} }
let result: { id: string; updated_at: Date }[] = []; let result: { id: string; updated_at: Date }[] = [];
// Wir fragen den neuesten Ausweis ab // Wir fragen den neuesten Ausweis ab
// Falls der Nutzer ein Admin ist dann kommt der ganz neueste ansonsten der neueste des eingeloggten Benutzers. // Falls der Nutzer ein Admin ist dann kommt der ganz neueste ansonsten der neueste des eingeloggten Benutzers.
if (user.rolle !== Enums.BenutzerRolle.ADMIN) { if (user.rolle === Enums.BenutzerRolle.USER) {
result = result =
await prisma.$queryRaw`SELECT id, updated_at FROM "VerbrauchsausweisWohnen" WHERE benutzer_id = ${user.id} UNION ALL await prisma.$queryRaw`SELECT id, updated_at FROM "VerbrauchsausweisWohnen" WHERE benutzer_id = ${user.id} UNION ALL
SELECT id, updated_at FROM "VerbrauchsausweisGewerbe" WHERE benutzer_id = ${user.id} UNION ALL SELECT id, updated_at FROM "VerbrauchsausweisGewerbe" WHERE benutzer_id = ${user.id} UNION ALL
@@ -41,6 +40,15 @@ if (user.rolle !== Enums.BenutzerRolle.ADMIN) {
SELECT id, updated_at FROM "GEGNachweisGewerbe" WHERE benutzer_id = ${user.id} SELECT id, updated_at FROM "GEGNachweisGewerbe" WHERE benutzer_id = ${user.id}
ORDER BY updated_at DESC LIMIT 1 OFFSET ${page - 1}`; ORDER BY updated_at DESC LIMIT 1 OFFSET ${page - 1}`;
} else { } else {
const date = moment().subtract(2, "hours").toDate()
// SELECT id, updated_at FROM "VerbrauchsausweisWohnen" WHERE created_at >= ${date} AND bestellt = ${true} UNION ALL
// SELECT id, updated_at FROM "VerbrauchsausweisGewerbe" WHERE created_at >= ${date} AND bestellt = ${true} UNION ALL
// SELECT id, updated_at FROM "BedarfsausweisWohnen" WHERE created_at >= ${date} AND bestellt = ${true} UNION ALL
// SELECT id, updated_at FROM "BedarfsausweisGewerbe" WHERE created_at >= ${date} AND bestellt = ${true} UNION ALL
// SELECT id, updated_at FROM "GEGNachweisWohnen" WHERE created_at >= ${date} AND bestellt = ${true} UNION ALL
// SELECT id, updated_at FROM "GEGNachweisGewerbe" WHERE created_at >= ${date} AND bestellt = ${true}
result = result =
await prisma.$queryRaw`SELECT id, updated_at FROM "VerbrauchsausweisWohnen" WHERE ausgestellt = ${false} AND bestellt = ${true} UNION ALL await prisma.$queryRaw`SELECT id, updated_at FROM "VerbrauchsausweisWohnen" WHERE ausgestellt = ${false} AND bestellt = ${true} UNION ALL
SELECT id, updated_at FROM "VerbrauchsausweisGewerbe" WHERE ausgestellt = ${false} AND bestellt = ${true} UNION ALL SELECT id, updated_at FROM "VerbrauchsausweisGewerbe" WHERE ausgestellt = ${false} AND bestellt = ${true} UNION ALL
@@ -52,6 +60,10 @@ if (user.rolle !== Enums.BenutzerRolle.ADMIN) {
} }
if (result.length > 0) { if (result.length > 0) {
return Astro.redirect(`/dashboard/objekte/${result[0].id}?p=${page}`); return Astro.redirect(`/dashboard/objekte/${result[0].id}?p=${page}`)
} }
--- ---
<UserLayout title="Objekte" {user} besteller={null}>
<p>Keine Ausweise konnten gefunden werden.</p>
</UserLayout>

View File

@@ -1,15 +0,0 @@
---
import DashboardLayout from "#layouts/DashboardLayout.astro";
import { getCurrentUser } from "#lib/server/user";
const user = await getCurrentUser(Astro);
if (!user) {
return Astro.redirect("/auth/login");
}
---
<DashboardLayout title="Objekte" user={user} besteller={null}>
<p>Sie haben bisher keine Ausweise erstellt. Klicken sie <a href="/energieausweis-erstellen/verbrauchsausweis-wohngebaeude/">hier</a>, um einen neuen Ausweis zu erstellen.</p>
</DashboardLayout>

View File

@@ -0,0 +1,31 @@
---
import KaufabschlussModule from "#modules/KaufabschlussModule.svelte";
import AusweisLayout from "#layouts/AusweisLayoutPruefung.astro";
import { Enums } from "#lib/client/prisma";
import { createCaller } from "src/astro-typesafe-api-caller";
// Man sollte nur auf diese Seite kommen, wenn ein Ausweis bereits vorliegt und in der Datenbank abgespeichert wurde.
const uid = Astro.url.searchParams.get("uid");
if (!uid) {
return Astro.redirect("/404");
}
const caller = createCaller(Astro);
const ausweis = await caller.v1.verbrauchsausweisWohnen.get({
uid
})
const user = await caller.v1.benutzer.self();
if (!ausweis || !user) {
return Astro.redirect("/404");
}
---
<AusweisLayout title="Kundendaten Aufnehmen - IBCornelsen">
<KaufabschlussModule user={user} ausweis={ausweis} aktiveBezahlmethode={Enums.Bezahlmethoden.paypal} client:load></KaufabschlussModule>
</AusweisLayout>

View File

@@ -1,87 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Abrechnung</title>
<link href="https://unpkg.com/tailwindcss@2.0.1/dist/tailwind.min.css" rel="stylesheet">
</head>
<body>
{{#each bestellungen}}
<header class="p-12 flex flex-row items-center justify-between" style="border-bottom: 12px #f37e3c solid;">
<img src="https://online-energieausweis.org/images/header/logo-big.png" alt="" class="h-24">
<div class="flex flex-col">
<p>fon 040 · 209339850</p>
<p>fax 040 · 209339859</p>
<p class="font-semibold">online-energieausweis.org</p>
</div>
</header>
<article class="py-8 px-16">
{{#if @first}}
<div class="flex flex-col gap-4">
<div class="flex flex-row">
<p class="text-sm">IB Cornelsen · Katendeich 5A · 21035 Hamburg</p>
</div>
<div class="flex flex-col">
<p>{{ @root.firma }}</p>
<p>{{ @root.adresse }}</p>
<p>{{ @root.plz }} {{ @root.ort }}</p>
</div>
</div>
{{/if}}
<div class="flex flex-col gap-4 mt-12">
{{#if @first}}
<div class="flex flex-row justify-between items-center">
<div class="flex flex-col">
<p class="font-semibold">Erzielte Conversions {{ @root.monat }}</p>
<p>Erstellt am {{ @root.heute }}</p>
</div>
<p class="font-semibold">Gesamt {{ @root.gesamt }} €</p>
</div>
{{/if}}
<table class="table border-collapse border border-black">
<thead>
<tr class="h-16">
<th class="text-center text-sm border-black border" style="background-color: #f37e3c;">ID - Datum</th>
<th class="text-center text-sm border-black border" style="background-color: #f37e3c;">Produkt</th>
<th class="text-center text-sm border-black border" style="background-color: #f37e3c;">Produktpreis</th>
<th class="text-center text-sm border-black border" style="background-color: #f37e3c;">Provision in %</th>
<th class="text-center text-sm border-black border" style="background-color: #f37e3c;">Betrag Netto</th>
</tr>
</thead>
<tbody>
{{#each this}}
<tr>
{{#with ausweis}}
<td class="border-black border p-1">
<p class="text-sm">{{ id }}</p>
<p class="text-sm">{{ createdAt }}</p>
</td>
<td class=" border-black border p-1 text-sm text-center">
{{ ausweisart }}
</td>
{{/with}}
<td class=" border-black border p-1 font-semibold text-sm text-center">
{{ betrag }}
</td>
{{#with ausweis}}
<td class=" border-black border p-1 text-sm text-center">
{{get-provision-prozent ausweisart ausweistyp}} %
</td>
<td class=" border-black border p-1 text-sm text-center">
{{get-provision-betrag ausweisart ausweistyp}}
</td>
{{/with}}
</tr>
{{/each}}
</tbody>
</table>
</div>
</article>
<footer class="px-16 py-6 flex flex-row justify-between items-center fixed bottom-0 left-0 w-full" style="border-top: 12px #f37e3c solid;">
<p class="font-semibold">Copyright © 2018 · IB Cornelsen</p>
<p class="font-semibold">info@online-energieausweis.org</p>
</footer>
{{/each}}
</body>
</html>

View File

@@ -39,16 +39,40 @@ fi
if [[ "$SKIP_BACKUP" == false ]]; then if [[ "$SKIP_BACKUP" == false ]]; then
echo "📦 Backup wird erstellt..." echo "📦 Backup wird erstellt..."
docker exec -t "$CONTAINER_NAME" pg_dumpall -c -U "$DB_USER" | brotli --quality=1 > "$FILE_NAME" docker exec -t "$CONTAINER_NAME" pg_dumpall -c -U "$DB_USER" | brotli > "$FILE_NAME"
echo "✅ Backup abgeschlossen: $FILE_NAME" echo "✅ Backup abgeschlossen: $FILE_NAME"
fi fi
echo "🧨 Alle Daten aus allen Tabellen werden gelöscht..." echo "🧨 Alle Daten aus allen Tabellen werden gelöscht..."
# Generate and run TRUNCATE statements for all tables in the public schema # Generate and run TRUNCATE statements for all tables in the public schema
docker exec -i "$CONTAINER_NAME" psql -U "$DB_USER" "postgres" <<'EOSQL' docker exec -i "$CONTAINER_NAME" psql -U "$DB_USER" "$DB_NAME" <<'EOSQL'
DROP DATABASE IF EXISTS main; DO $$
CREATE DATABASE main WITH OWNER main ENCODING 'UTF8'; DECLARE
r RECORD;
sql TEXT := '';
BEGIN
-- Truncate all tables
FOR r IN
SELECT tablename
FROM pg_tables
WHERE schemaname = 'public'
LOOP
sql := sql || FORMAT('DROP TABLE public.%I CASCADE;', r.tablename);
END LOOP;
-- Drop all sequences
FOR r IN
SELECT sequence_name
FROM information_schema.sequences
WHERE sequence_schema = 'public'
LOOP
sql := sql || FORMAT('DROP SEQUENCE IF EXISTS public.%I CASCADE;', r.sequence_name);
END LOOP;
EXECUTE sql;
END
$$;
EOSQL EOSQL
echo "✅ Alle Tabellen gelöscht und Schema zurückgesetzt." echo "✅ Alle Tabellen gelöscht und Schema zurückgesetzt."