83 Commits

Author SHA1 Message Date
Jens Cornelsen
f56c0f83c2 Merge pull request #576 from IBCornelsen/staging
Fernwärme Pforzheim hinzugefuegt
2025-10-14 17:37:07 +02:00
Jens Cornelsen
35a849913e Fernwärme Pforzheim hinzugefuegt 2025-10-14 17:34:59 +02:00
Jens Cornelsen
e01b9b5445 Merge pull request #575 from IBCornelsen/staging
Update main
2025-10-01 16:24:41 +02:00
Jens Cornelsen
e3484718a0 Merge pull request #574 from IBCornelsen/dev
Update staging
2025-10-01 16:21:18 +02:00
Jens Cornelsen
762c0aeb97 Primaerenergiefasktor Strom angepasst 2025-10-01 16:11:44 +02:00
Moritz Utcke
c010bbaff0 Fläche darf nur noch Integer sein 2025-09-25 11:41:28 -04:00
Moritz Utcke
6e3f749b07 Merge branch 'main' into dev 2025-09-25 10:55:30 -04:00
Moritz Utcke
e4ef1230dd Registrierung Email Lowercase
Bei der Registrierung wird die Email nun immer zu Kleinbuchstaben umgewandelt
2025-09-25 10:54:56 -04:00
Moritz Utcke
a7bdd02652 HEIC und HEIF zu JPEG Konvertierung
Damit wird nun auch das bescheuerte Bildformat von Apple unterstützt
2025-09-25 10:43:49 -04:00
Moritz Utcke
aa60e63d1b Merge branch 'staging' 2025-09-25 10:20:38 -04:00
Moritz Utcke
6b5ae04e36 Merge branch 'dev' into staging 2025-09-25 10:20:31 -04:00
Moritz Utcke
634bef971d Float zu Int und Bildupload 2025-09-25 10:20:18 -04:00
Moritz Utcke
2b76953e68 Merge branch 'staging' [skip ci] 2025-09-25 08:09:22 -04:00
Moritz Utcke
192396933f Merge branch 'dev' into staging 2025-09-25 08:04:54 -04:00
Moritz Utcke
82f8cfc0ff Int zu float umgestellt 2025-09-24 14:26:08 -04:00
Moritz Utcke
c2e96aad74 Merge pull request #573 from IBCornelsen/staging
Resller kann sich nicht einloggen
2025-09-17 10:27:52 -04:00
Moritz Utcke
2d7ea57f57 Merge branch 'dev' into staging 2025-09-17 10:25:28 -04:00
Moritz Utcke
9e6e893c28 Reseller kann sich nicht einloggen 2025-09-17 10:25:15 -04:00
Moritz Utcke
f71c4b9ef9 Merge pull request #572 from IBCornelsen/staging
Klimafaktoren Bug Gefixt
2025-09-14 15:04:19 -03:00
Moritz Utcke
ce8d4bde18 Merge branch 'staging' into dev 2025-09-14 14:03:42 -04:00
Moritz Utcke
4e54511fe0 Klimafaktoren Bug Gefixt
In dem Client Skript was sich die Klimfaktoren abholen sollte war noch eine alte Definition der getKlimafaktoren Funktion genutzt worden.
2025-09-14 14:03:16 -04:00
Moritz Utcke
a1a286b67f Merge pull request #571 from IBCornelsen/staging
Abrechnung Fix
2025-09-12 13:12:35 -03:00
Moritz Utcke
fe4b4fb53a Merge branch 'dev' into staging 2025-09-12 12:11:25 -04:00
Moritz Utcke
b4f84d7ecd Abrechnung Fix 2025-09-12 12:11:05 -04:00
Jens Cornelsen
9157df7a1c Merge pull request #570 from IBCornelsen/staging
Update main
2025-09-12 17:58:20 +02:00
Jens Cornelsen
ef7086a08a Merge pull request #569 from IBCornelsen/dev
Partner Code aufgeräumt
2025-09-12 17:55:34 +02:00
Moritz Utcke
15d57f0a2c Partner Code aufgeräumt 2025-09-12 12:27:57 -03:00
Jens Cornelsen
82993ce74d Merge pull request #568 from IBCornelsen/staging
Update main
2025-09-12 17:14:50 +02:00
Jens Cornelsen
76fa34bf64 Merge pull request #567 from IBCornelsen/dev
Update staging
2025-09-12 17:13:25 +02:00
Jens Cornelsen
d31c8c2fb2 Fernwärme Neumünster 2025-09-08 14:07:41 +02:00
Jens Cornelsen
4f5b94cbd7 Fernwärme Neumünster 2025-09-08 14:05:27 +02:00
Jens Cornelsen
db4325ad2c Fernwärme Erfurt hinzugefügt 2025-09-07 14:00:25 +02:00
Jens Cornelsen
1d2f0a7ce8 Fernwärme Erfurt hinzugefügt 2025-09-07 13:46:11 +02:00
Moritz Utcke
3fc7196652 Docker Datenbank Log Limit 2025-08-30 18:27:00 -04:00
Carl Mahnke
a7bd398f39 Support Popup: Email vorbelegen wenn eingeloggt 2025-08-19 17:07:14 +02:00
Moritz Utcke
cd4e021332 Email von Nutzern klein machen 2025-08-06 10:53:15 -04:00
Moritz Utcke
9c897bd1e3 Merge branch 'main' into dev 2025-08-05 19:49:58 -04:00
Moritz Utcke
addb647618 Layout 2025-08-05 12:13:18 -04:00
Moritz Utcke
8d07486bed Logs entfernt 2025-08-05 11:57:30 -04:00
Moritz Utcke
208ade8219 Fix 2025-08-05 11:48:19 -04:00
Moritz Utcke
2cc44d2c09 Gesamtpreis 2025-08-05 11:36:31 -04:00
Moritz Utcke
366e813f29 Adresse 2025-08-05 11:16:14 -04:00
Moritz Utcke
5683596e09 Zeit Fix 2025-08-05 11:06:27 -04:00
Moritz Utcke
811d574576 Filter 2025-08-05 10:56:55 -04:00
Moritz Utcke
4df30004de Ausweistyp 2025-08-05 10:35:15 -04:00
Moritz Utcke
55a0248de4 Netto Fix 2025-08-05 09:53:45 -04:00
Moritz Utcke
b3c84e1cb6 Sortierung 2025-08-05 09:45:03 -04:00
Moritz Utcke
7b7a1f77a0 Zeitzonen 2025-08-05 09:41:00 -04:00
Moritz Utcke
24da37aa87 Merge pull request #565 from IBCornelsen/dev
Abrechnung PDF
2025-08-05 09:26:47 -04:00
Moritz Utcke
2fdd71f6d6 Merge remote-tracking branch 'origin/dev' into dev 2025-08-05 09:25:43 -04:00
Moritz Utcke
648ecc5901 PDF Abrechnung 2025-08-05 09:25:37 -04:00
Jens Cornelsen
f0e2bd8096 Merge pull request #564 from IBCornelsen/staging
Update main
2025-08-05 14:41:02 +02:00
Jens Cornelsen
10591444a8 Merge pull request #563 from IBCornelsen/dev
Update staging
2025-08-05 14:38:24 +02:00
Jens Cornelsen
6c706573b0 Merge branch 'staging' into dev 2025-08-05 14:33:53 +02:00
Jens Cornelsen
0106d380fc Merge pull request #562 from IBCornelsen/main
Update staging
2025-08-05 14:24:43 +02:00
Moritz Utcke
a05c32167e Benutzer Rolle Fix 2025-08-04 20:16:04 -04:00
Moritz Utcke
cbfa49b7bc Fix? 2025-08-04 19:33:17 -04:00
Moritz Utcke
f56684a6e8 Abrechnung Generierung 2025-08-04 17:23:04 -04:00
Moritz Utcke
2bee3f55c6 Ticket Telefon Hotfix 2025-08-04 11:09:36 -04:00
Moritz Utcke
eb04f5b0b5 Trello ID Hotfix 2025-08-04 11:04:41 -04:00
UMBENOMENA
0866a6e4a1 Merge pull request #561 from IBCornelsen/staging
Landingpage Menu Titel
2025-08-04 00:08:31 +02:00
Robert Jagtiani
0dc4289232 Titel PY- 2025-08-04 00:06:48 +02:00
Robert Jagtiani
6d8d8cb39e landingpage partner header menu und Titel fix 2025-08-04 00:06:24 +02:00
UMBENOMENA
3096f05f67 Merge pull request #560 from IBCornelsen/UMBE
Titel PY-
2025-08-03 22:47:44 +02:00
Robert Jagtiani
641fdf6213 Titel PY- 2025-08-03 22:47:02 +02:00
UMBENOMENA
ea695e3234 Merge pull request #559 from IBCornelsen/UMBE
Landing Page Menu Titel Fix
2025-08-03 22:36:30 +02:00
Robert Jagtiani
ac502169a9 landingpage partner header menu und Titel fix 2025-08-03 22:34:40 +02:00
Moritz Utcke
f6d9b565d7 Merge pull request #558 from IBCornelsen/staging
staging nach main
2025-08-02 13:05:25 -05:00
Moritz Utcke
288072ce1a Merge branch 'main' into staging 2025-08-02 14:03:56 -04:00
Moritz Utcke
e9c7f91a1d Merge pull request #556 from IBCornelsen/dev
Dev nach staging
2025-08-02 12:57:57 -05:00
Moritz Utcke
f29aaa2170 Prevent Wrong PR Fix 2025-08-02 13:57:34 -04:00
Moritz Utcke
249caafddb Monatliche Abrechnung auskommentiert 2025-08-02 13:52:42 -04:00
Jens Cornelsen
38333a117b . 2025-08-02 19:39:07 +02:00
Jens Cornelsen
c5042066ff Textanpassung Hilfe Box 2025-08-02 19:23:33 +02:00
Moritz Utcke
f028ac2d4e Fügt eine Auswahlmöglichkeit für eine Datenbank hinzu 2025-08-01 08:37:49 -05:00
Moritz Utcke
6372ade5c1 Update trello board id 2025-07-31 18:11:25 -05:00
Moritz Utcke
8378909517 Update Makefile 2025-07-30 13:12:19 -05:00
Moritz Utcke
67a44f8bdb Disable Auto Merge 2025-07-30 12:52:27 -05:00
Moritz Utcke
10b1aec389 Abrechnungsübersicht 2025-07-30 12:26:28 -05:00
Moritz Utcke
f9dc9ebd48 Behebt folgende Fehler aus Trello
Kaufabschluss bei Neukunden: Reg Box soll sofort erscheinen (ohne Umweg über Login Box)

Login bzw Reg Box verschwindet plöltzlich. Das passiert wahrscheinlich wenn man aus versehen neben die Box klickt. Das kann den Kunden verwirren
2025-07-30 09:51:57 -05:00
Jens Cornelsen
8bfa14352c Merge pull request #554 from IBCornelsen/staging
Fernwärme Hamburg hinzugefügt
2025-07-09 21:09:17 +02:00
Jens Cornelsen
ec7fccac35 Merge remote-tracking branch 'origin/staging' into staging 2025-07-08 17:50:45 +02:00
Jens Cornelsen
397a2ced4e Fernwärme Hamburg hinzugefügt 2025-07-08 17:16:27 +02:00
82 changed files with 1228 additions and 1309 deletions

View File

@@ -1,72 +0,0 @@
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 make prod-no-backup

View File

@@ -13,18 +13,15 @@ jobs:
steps: steps:
- name: Prevent dev merges - name: Prevent dev merges
run: | run: |
echo "${{ github.head_ref }}"; if [[ "${{ github.head_ref }}" == "dev" && "${{ github.base_ref }}" == "main" ]]; then
echo "${{ github.base_ref }}"; echo "ERROR: Merging 'dev' into 'main' is forbidden!"
if [[ "${{ github.head_ref }}" == "dev" ]]; then
echo "ERROR: Merging 'dev' into '${{ github.base_ref }}' is forbidden!"
exit 1 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,6 +30,9 @@ 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) \
@@ -61,7 +64,9 @@ all:
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" --cron "0 12 28 * *" -- src/cronjobs/update-dwd-klimafaktoren.ts
prod: install-dependencies prisma-studio backup-database-cronjob update-dwd-klimafaktoren-cron prod: prod-no-backup backup-database-cronjob
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,6 +2,7 @@
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
@@ -11,19 +12,15 @@ 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 online-energieausweis-database-1 pg_dump --data-only -U main main | brotli --best > $FILE_NAME docker exec -t $DATABASE_NAME pg_dump --data-only -U main main | brotli --quality=3 > $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 online-energieausweis-database-1 pg_dumpall -c -U main | brotli --best > $FILE_NAME_COMPLETE docker exec -t $DATABASE_NAME pg_dumpall -c -U main | brotli --quality=3 > $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,6 +15,7 @@
"@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",
@@ -26,6 +27,8 @@
"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",
@@ -34,11 +37,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.5.46", "moment-timezone": "^0.6.0",
"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.7.2", "puppeteer": "^24.15.0",
"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",
@@ -523,7 +526,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.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=="], "@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=="],
"@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=="],
@@ -711,6 +714,8 @@
"@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=="],
@@ -1057,7 +1062,7 @@
"chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="], "chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="],
"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=="], "chromium-bidi": ["chromium-bidi@7.2.0", "", { "dependencies": { "mitt": "^3.0.1", "zod": "^3.24.1" }, "peerDependencies": { "devtools-protocol": "*" } }, "sha512-gREyhyBstermK+0RbcJLbFhcQctg92AGgDe/h/taMJEOLRdtSswBAO9KmvltFSQWgM2LrwWu5SIuEUbdm3JsyQ=="],
"ci-info": ["ci-info@4.1.0", "", {}, "sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A=="], "ci-info": ["ci-info@4.1.0", "", {}, "sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A=="],
@@ -1241,7 +1246,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.1425554", "", {}, "sha512-uRfxR6Nlzdzt0ihVIkV+sLztKgs7rgquY/Mhcv1YNCWDh5IZgl5mnn2aeEnW5stYTE0wwiF4RYVz8eMEpV1SEw=="], "devtools-protocol": ["devtools-protocol@0.0.1464554", "", {}, "sha512-CAoP3lYfwAGQTaAXYvA6JZR0fjGUb7qec1qf4mToyoH2TZgUFeIqYcjh6f9jNuhHfuZiEdH+PONHYrLhRQX6aw=="],
"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=="],
@@ -1515,6 +1520,8 @@
"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=="],
@@ -1549,6 +1556,8 @@
"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=="],
@@ -1959,7 +1968,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.5.47", "", { "dependencies": { "moment": "^2.29.4" } }, "sha512-UbNt/JAWS0m/NJOebR0QMRHBk0hu03r5dx9GK8Cs0AS3I81yDcOc9k+DytPItgVvBP7J6Mf6U2n3BPAacAV9oA=="], "moment-timezone": ["moment-timezone@0.6.0", "", { "dependencies": { "moment": "^2.29.4" } }, "sha512-ldA5lRNm3iJCWZcBCab4pnNL3HSZYXVb/3TYr75/1WCTWYuTqYUb5f/S384pncYjJ88lbO8Z4uPDvmoluHJc8Q=="],
"mrmime": ["mrmime@2.0.0", "", {}, "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw=="], "mrmime": ["mrmime@2.0.0", "", {}, "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw=="],
@@ -1977,6 +1986,8 @@
"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=="],
@@ -2187,9 +2198,9 @@
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
"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": ["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-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=="], "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=="],
"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=="],
@@ -2563,7 +2574,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.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-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-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=="],
@@ -2663,6 +2674,8 @@
"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=="],
@@ -2761,13 +2774,15 @@
"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.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w=="], "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=="],
"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=="],
@@ -2867,7 +2882,9 @@
"@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/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], "@puppeteer/browsers/debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
"@puppeteer/browsers/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"@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=="],
@@ -2965,6 +2982,8 @@
"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=="],
@@ -3063,6 +3082,8 @@
"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,6 +1,9 @@
version: '3' version: '3'
services: services:
database: database:
container_name: database
image: postgres:17.5
build: ./ build: ./
restart: always restart: always
env_file: env_file:

View File

@@ -29,6 +29,7 @@
"@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",
@@ -40,6 +41,8 @@
"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",
@@ -48,11 +51,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.5.46", "moment-timezone": "^0.6.0",
"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.7.2", "puppeteer": "^24.15.0",
"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

@@ -0,0 +1,49 @@
/*
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

@@ -0,0 +1,15 @@
/*
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

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

View File

@@ -0,0 +1,12 @@
/*
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

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

@@ -0,0 +1,10 @@
/*
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,6 +2,7 @@
enum BenutzerRolle { enum BenutzerRolle {
USER USER
ADMIN ADMIN
RESELLER
} }
model Benutzer { model Benutzer {
@@ -20,6 +21,7 @@ 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)
@@ -50,6 +52,8 @@ model Benutzer {
events Event[] events Event[]
@@map("benutzer") @@map("benutzer")
Provisionen Provisionen[]
bilder Bild[]
} }

View File

@@ -15,6 +15,8 @@ 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

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

@@ -4,6 +4,37 @@
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="./" # Where to save the file
DATABASE_NAME=database
# === Check if a custom file is given as a command line argument ===
if [ $# -eq 1 ]; then
CUSTOM_FILE="$1"
echo "🔍 Using custom file: $CUSTOM_FILE"
# Check if file exists locally
if [ ! -f "$CUSTOM_FILE" ]; then
# Check if the file exists on the remote
if ! aws s3api head-object --bucket "$BUCKET_NAME" --key "$CUSTOM_FILE" --endpoint-url "$ENDPOINT_URL" > /dev/null 2>&1; then
echo "❌ Custom file does not exist in S3 bucket or locally."
exit 1
else
echo "📥 Downloading $CUSTOM_FILE from S3"
aws s3 cp "s3://$BUCKET_NAME/$CUSTOM_FILE" "$LOCAL_DOWNLOAD_DIR" \
--endpoint-url "$ENDPOINT_URL"
fi
fi
LATEST_FILE="$CUSTOM_FILE"
FILENAME=$(basename "$LATEST_FILE")
if [[ "$FILENAME" == *.br ]]; then
echo "🗜️ Detected compressed file: $FILENAME"
# Remove the .br suffix for the SQL file
SQL_FILE="${FILENAME%.br}" # Remove .br suffix
brotli -d "$FILENAME"
else
SQL_FILE=$FILENAME
fi
else
echo "🔍 No custom file provided, searching for latest .sql.br file in S3"
# === Get latest file from IONOS S3 bucket === # === Get latest file from IONOS S3 bucket ===
LATEST_FILE=$(aws s3api list-objects-v2 \ LATEST_FILE=$(aws s3api list-objects-v2 \
@@ -18,24 +49,30 @@ if [ "$LATEST_FILE" == "None" ] || [ -z "$LATEST_FILE" ]; then
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") FILENAME=$(basename "$LATEST_FILE")
SQL_FILE="${FILENAME%.br}" # Remove .br suffix SQL_FILE="${FILENAME%.br}" # Remove .br suffix
echo "📥 Downloading $LATEST_FILE" echo "📥 Downloading $LATEST_FILE"
aws s3 cp "s3://$BUCKET_NAME/$LATEST_FILE" "$LOCAL_DOWNLOAD_DIR" \ aws s3 cp "s3://$BUCKET_NAME/$LATEST_FILE" "$LOCAL_DOWNLOAD_DIR" \
--endpoint-url "$ENDPOINT_URL" --endpoint-url "$ENDPOINT_URL"
# === Decompress with Brotli ===
echo "🗜️ Decompressing $FILENAME -> $SQL_FILE"
brotli -d "$FILENAME" brotli -d "$FILENAME"
echo "🗜️ Decompressed to $SQL_FILE"
fi
# === Import into Postgres inside Docker === # === Import into Postgres inside Docker ===
echo "🐘 Importing into PostgreSQL (online-energieausweis-database-1:main)" echo "🐘 Importing into PostgreSQL ($DATABASE_NAME:main)"
docker exec -i "online-energieausweis-database-1" env PGPASSWORD="hHMP8cd^N3SnzGRR" \ docker exec -i "$DATABASE_NAME" env PGPASSWORD="hHMP8cd^N3SnzGRR" \
psql -U "main" -d "main" < "$SQL_FILE" psql -v ON_ERROR_STOP=0 -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
if [ -z "$CUSTOM_FILE" ]; then
echo "🧹 Cleaning up downloaded files..."
rm "$FILENAME" "$SQL_FILE" rm "$FILENAME" "$SQL_FILE"
fi

View File

@@ -5,7 +5,12 @@ 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"),
@@ -13,32 +18,29 @@ 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"),
"ausweise": await import("../src/pages/api/ausweise/index.ts"),
"auth/access-token": await import("../src/pages/api/auth/access-token.ts"),
"auth/passwort-vergessen": await import("../src/pages/api/auth/passwort-vergessen.ts"),
"auth/refresh-token": await import("../src/pages/api/auth/refresh-token.ts"),
"bedarfsausweis-gewerbe/[id]": await import("../src/pages/api/bedarfsausweis-gewerbe/[id].ts"),
"bedarfsausweis-gewerbe": await import("../src/pages/api/bedarfsausweis-gewerbe/index.ts"),
"bedarfsausweis-wohnen/[id]": await import("../src/pages/api/bedarfsausweis-wohnen/[id].ts"),
"bedarfsausweis-wohnen": await import("../src/pages/api/bedarfsausweis-wohnen/index.ts"),
"bilder/[id]": await import("../src/pages/api/bilder/[id].ts"), "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/[id]": await import("../src/pages/api/geg-nachweis-wohnen/[id].ts"),
"geg-nachweis-wohnen": await import("../src/pages/api/geg-nachweis-wohnen/index.ts"), "geg-nachweis-wohnen": await import("../src/pages/api/geg-nachweis-wohnen/index.ts"),
"objekt": await import("../src/pages/api/objekt/index.ts"), "objekt": await import("../src/pages/api/objekt/index.ts"),
"auth/access-token": await import("../src/pages/api/auth/access-token.ts"),
"auth/passwort-vergessen": await import("../src/pages/api/auth/passwort-vergessen.ts"),
"auth/refresh-token": await import("../src/pages/api/auth/refresh-token.ts"),
"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"),
"ticket": await import("../src/pages/api/ticket/index.ts"), "user": await import("../src/pages/api/user/index.ts"),
"user/self": await import("../src/pages/api/user/self.ts"), "user/self": await import("../src/pages/api/user/self.ts"),
"ticket": await import("../src/pages/api/ticket/index.ts"),
"verbrauchsausweis-wohnen/[id]": await import("../src/pages/api/verbrauchsausweis-wohnen/[id].ts"),
"verbrauchsausweis-wohnen": await import("../src/pages/api/verbrauchsausweis-wohnen/index.ts"),
"verbrauchsausweis-gewerbe/[id]": await import("../src/pages/api/verbrauchsausweis-gewerbe/[id].ts"), "verbrauchsausweis-gewerbe/[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

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

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

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

@@ -1,109 +0,0 @@
<script lang="ts">
import { BedarfsausweisWohnen, Enums, Rechnung, VerbrauchsausweisGewerbe, VerbrauchsausweisWohnen } from "#lib/server/prisma.js";
import moment from "moment";
import { format } from "sharp";
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>Verbrauchsausweis online</b><br>inkl. ausführlicher&nbsp;telefonischer <b>Selbsteingabe 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>Verbrauchsausweis offline</b><br>Sie schicken uns 3&nbsp;Verbrauchsabrechnungen&nbsp;zu) <b>Wir übernehmen die Eingabe</b><br>Sie übermitteln die nötigen Unterlagen per Upload oder E-Mail.
</div> </div>
<div class="text-center xs:text-right"> <div class="text-center xs:text-right">

View File

@@ -11,6 +11,22 @@
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
@@ -97,10 +113,11 @@ 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"
data-rule-minlength="2" on:keypress={onlyAllowIntegerInput}
data-msg-minlength="min. 2 Zeichen" on:paste={onlyAllowPastingIntegers}
bind:value={aufnahme.flaeche} bind:value={aufnahme.flaeche}
/> />
@@ -124,7 +141,10 @@ 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,6 +60,8 @@
return centeredValue; return centeredValue;
} }
let translation_1 = 0; let translation_1 = 0;
let translation_2 = 0; let translation_2 = 0;
$: { $: {
@@ -74,6 +76,7 @@
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, 2.4, 0.56], ["Strommix", "kWh", 1.0, 1.8, 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,6 +42,9 @@ 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 } from "#lib/server/prisma.js"; import { Benutzer, Enums } from "#lib/client/prisma.js";
export let lightTheme: boolean; export let lightTheme: boolean;
export let benutzer: Benutzer; export let benutzer: Benutzer;
@@ -38,6 +38,12 @@
{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>
@@ -70,7 +76,9 @@
</div> </div>
</div> </div>
{/if} {/if}
<a href="/dashboard/abrechnung" class="button ">Conversions</a> {#if benutzer.rolle === Enums.BenutzerRolle.RESELLER}
<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,6 +2,7 @@
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) {
@@ -12,13 +13,11 @@
} }
</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} on:click|self={() => { <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}>
hidden = closeable ? true : hidden;
}}>
{#if closeable} {#if closeable}
<button class="absolute top-4 left-4 text-white" on:click={() => { <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={() => {
hidden = true; hidden = true;
}}>Schließen</button> }}><Cross1 size={20}></Cross1></button>
{/if} {/if}
<slot></slot> <slot></slot>
</div> </div>

View File

@@ -2,9 +2,10 @@
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); const success = await dialogs.modal(TicketPopup, { email: userEmail });
console.log(success); console.log(success);

View File

@@ -1,6 +1,7 @@
<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();
@@ -12,7 +13,7 @@
email: email, email: email,
metadata: { metadata: {
category: category, category: category,
phone: phone, telefon: telefon,
}, },
titel: title, titel: title,
}) })
@@ -27,8 +28,8 @@
let category = ""; let category = "";
let title = ""; let title = "";
let description = ""; let description = "";
let email = ""; //let email = "";
let phone = ""; let telefon = "";
</script> </script>
<form class="max-w-lg" on:submit={createTicket}> <form class="max-w-lg" on:submit={createTicket}>
@@ -89,9 +90,9 @@
<input <input
class="input input-bordered" class="input input-bordered"
type="tel" type="tel"
placeholder="Ihre Telefonnumer" placeholder="Ihre Telefonnummer"
name="phone" name="telefon"
bind:value={phone} bind:value={telefon}
required required
/> />
</div> </div>

View File

@@ -1,7 +1,9 @@
<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;
@@ -17,6 +19,7 @@
} 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:
@@ -36,8 +39,10 @@
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;
} }
@@ -47,7 +52,7 @@
const reader = new FileReader(); const reader = new FileReader();
reader.onload = () => { reader.onload = async () => {
if (reader.readyState != reader.DONE) { if (reader.readyState != reader.DONE) {
return; return;
} }
@@ -57,6 +62,18 @@
} }
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 () => {
@@ -85,6 +102,12 @@
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) {
@@ -128,6 +151,7 @@
<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}
@@ -144,6 +168,7 @@
<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, objekt.plz); const response = await getKlimafaktoren(ausweis.startdatum as Date, objekt.plz as string);
// 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,63 +39,38 @@ const isNET = pathname.includes("immonet");
</div> </div>
<div> <div>
<ul class="navlist"> <!-- Navigation als Liste (nur ab sm sichtbar) -->
<li> <ul class="navlist hidden xl:flex">
<a <li><a href={`/${partner}/energieausweis-erstellen/verbrauchsausweis-wohngebaeude/`}><button class={tab === 0 ? "glow" : ""}>Verbrauchsausweis</button></a></li>
href={`/${partner}/energieausweis-erstellen/verbrauchsausweis-wohngebaeude/`} <li><a href={`/${partner}/energieausweis-erstellen/verbrauchsausweis-gewerbe/`}><button class={tab === 1 ? "glow" : ""}>Verbrauchsausweis Gewerbe</button></a></li>
><button class={tab === 0 ? "glow" : ""} <li><a href={`/${partner}/energieausweis-erstellen/bedarfsausweis-wohngebaeude/`}><button class={tab === 2 ? "glow" : ""}>Bedarfsausweis</button></a></li>
>Verbrauchsausweis</button <li><a href={`/${partner}/angebot-anfragen/bedarfsausweis-gewerbe-anfragen/`}><button class={tab === 3 ? "glow" : ""}>Bedarfsausweis Gewerbe</button></a></li>
></a> <li><a href={`/${partner}/angebot-anfragen/geg-nachweis-wohnen-anfragen/`}><button class={tab === 4 ? "glow" : ""}>GEG Nachweis Wohngebäude</button></a></li>
</li> <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>
<a
href={`/${partner}/energieausweis-erstellen/verbrauchsausweis-gewerbe/`}
><button class={tab === 1 ? "glow" : ""}
>Verbrauchsausweis Gewerbe</button
></a>
</li>
<li>
<a
href={`/${partner}/energieausweis-erstellen/bedarfsausweis-wohngebaeude/`}
><button class={tab === 2 ? "glow" : ""}
>Bedarfsausweis</button
></a>
</li>
<li>
<a
href={`/${partner}/angebot-anfragen/bedarfsausweis-gewerbe-anfragen/`}
><button class={tab === 3 ? "glow" : ""}
>Bedarfsausweis Gewerbe</button
></a>
</li>
<li>
<a
href={`/${partner}/angebot-anfragen/geg-nachweis-wohnen-anfragen/`}
><button class={tab === 4 ? "glow" : ""}
>GEG Nachweis Wohngebäude</button
></a>
</li>
<li>
<a
href={`/${partner}/angebot-anfragen/geg-nachweis-gewerbe-anfragen/`}
><button class={tab === 5 ? "glow" : ""}
>GEG Nachweis Gewerbe</button
></a>
</li>
<li>
<a href={`/${partner}/welcher-ausweis/${partner}`}
><button class={tab === 6 ? "glow" : ""}
>Welcher Ausweis</button
></a>
</li>
</ul> </ul>
<!-- Navigation als Dropdown (nur bis sm sichtbar) -->
<select
class="xl:hidden border rounded p-2 w-[calc(100%-2rem)]"
onchange="if (this.value) window.location.href=this.value"
>
<option value="">Auswahl treffen…</option>
<option value={`/${partner}/energieausweis-erstellen/verbrauchsausweis-wohngebaeude/`} selected={tab === 0}>Verbrauchsausweis</option>
<option value={`/${partner}/energieausweis-erstellen/verbrauchsausweis-gewerbe/`} selected={tab === 1}>Verbrauchsausweis Gewerbe</option>
<option value={`/${partner}/energieausweis-erstellen/bedarfsausweis-wohngebaeude/`} selected={tab === 2}>Bedarfsausweis</option>
<option value={`/${partner}/angebot-anfragen/bedarfsausweis-gewerbe-anfragen/`} selected={tab === 3}>Bedarfsausweis Gewerbe</option>
<option value={`/${partner}/angebot-anfragen/geg-nachweis-wohnen-anfragen/`} selected={tab === 4}>GEG Nachweis Wohngebäude</option>
<option value={`/${partner}/angebot-anfragen/geg-nachweis-gewerbe-anfragen/`} selected={tab === 5}>GEG Nachweis Gewerbe</option>
<option value={`/${partner}/welcher-ausweis/${partner}`} selected={tab === 6}>Welcher Ausweis</option>
</select>
</div> </div>
</div> </div>
</header> </header>
<div <div
id="titel" id="titel"
class="block w-full 2xl:h-[270px] lg:h-[148px] bg-cover px-24 py-20" class="block w-full lg:h-[270px] bg-cover !px-8 md:px-32 !py-8 md:py-16"
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;`}
> >
@@ -186,7 +161,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-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-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);}
} }
.header-button { .header-button {

View File

@@ -19,6 +19,7 @@ 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

@@ -38,19 +38,19 @@ export const BedarfsausweisWohnenSchema = z.object({
volumen: z.number().nullish(), volumen: z.number().nullish(),
dicht: z.boolean().nullish(), dicht: z.boolean().nullish(),
fenster_flaeche_1: z.number().nullish(), fenster_flaeche_1: z.number().nullish(),
fenster_art_1: z.string().nullish(), fenster_art_1: z.number().nullish(),
fenster_flaeche_2: z.number().nullish(), fenster_flaeche_2: z.number().nullish(),
fenster_art_2: z.string().nullish(), fenster_art_2: z.number().nullish(),
dachfenster_flaeche: z.number().nullish(), dachfenster_flaeche: z.number().nullish(),
dachfenster_art: z.string().nullish(), dachfenster_art: z.number().nullish(),
haustuer_flaeche: z.number().nullish(), haustuer_flaeche: z.number().nullish(),
haustuer_art: z.string().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.string().nullish(), dach_daemmung: z.number().nullish(),
decke_daemmung: z.string().nullish(), decke_daemmung: z.number().nullish(),
aussenwand_daemmung: z.string().nullish(), aussenwand_daemmung: z.number().nullish(),
boden_daemmung: z.string().nullish(), boden_daemmung: z.number().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,6 +17,7 @@ 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,5 +7,6 @@ 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,6 +12,7 @@ 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

@@ -0,0 +1,13 @@
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></TicketButton> <TicketButton client:load userEmail={user?.email ?? ""}></TicketButton>
</body> </body>
</html> </html>

View File

@@ -10,9 +10,8 @@ export async function endEnergieVerbrauchVerbrauchsausweisGewerbe_2016_Client(
) { ) {
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(),
startdatum.add(2, "years").toDate() objekt.plz as string,
); );
if (!klimafaktoren || klimafaktoren.length === 0) { if (!klimafaktoren || klimafaktoren.length === 0) {

View File

@@ -13,12 +13,14 @@ 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(),
startdatum.add(2, "years").toDate() objekt.plz as string
); );
console.log("Klimafaktoren", klimafaktoren);
if (!klimafaktoren || klimafaktoren.length === 0) { if (!klimafaktoren || klimafaktoren.length === 0) {
klimafaktoren = [ klimafaktoren = [
{ {

View File

@@ -7,7 +7,6 @@ export const getKlimafaktoren = memoize(async (date: Date, plz: string) => {
return null; return null;
} }
try {
const response = await api.klimafaktoren.GET.fetch({ const response = await api.klimafaktoren.GET.fetch({
plz, plz,
genauigkeit: "years", genauigkeit: "years",
@@ -17,7 +16,4 @@ export const getKlimafaktoren = memoize(async (date: Date, plz: string) => {
enddatum: moment(date).add(2, "years").utc(true).toString(), enddatum: moment(date).add(2, "years").utc(true).toString(),
}); });
return response; return response;
} catch (e) {
return null;
}
}); });

View File

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

View File

@@ -92,11 +92,14 @@ 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 {
return authorizationMiddleware(input, ctx) user = await authorizationMiddleware(input, ctx)
} catch(e) { } catch(e) {
return null; console.log(e);
} }
return user;
} }
export const authorizationHeaders = { export const authorizationHeaders = {

14
src/lib/provision.ts Normal file
View File

@@ -0,0 +1,14 @@
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,11 +1,27 @@
import { AufnahmeClient, BedarfsausweisWohnenClient, BenutzerClient, BildClient, getAusweisartFromId, ObjektClient, RechnungClient, VerbrauchsausweisGewerbeClient, VerbrauchsausweisWohnenClient } from "#components/Ausweis/types.js"; import {
AufnahmeClient,
BedarfsausweisWohnenClient,
BenutzerClient,
BildClient,
getAusweisartFromId,
ObjektClient,
RechnungClient,
VerbrauchsausweisGewerbeClient,
VerbrauchsausweisWohnenClient,
} from "#components/Ausweis/types.js";
import { pdfDatenblattVerbrauchsausweisGewerbe } from "#lib/pdf/pdfDatenblattVerbrauchsausweisGewerbe.js"; import { 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 { Enums, prisma, Rechnung } from "#lib/server/prisma.js"; import {
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.
@@ -15,17 +31,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;
} }
} }
@@ -33,50 +49,166 @@ 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(ausweis: VerbrauchsausweisWohnenClient | VerbrauchsausweisGewerbeClient | BedarfsausweisWohnenClient, aufnahme: AufnahmeClient, objekt: ObjektClient, bilder: BildClient[], user: BenutzerClient, vorschau: boolean = true, ausweisart = getAusweisartFromId(ausweis.id)) { export async function getAnsichtsausweis(
ausweis:
| VerbrauchsausweisWohnenClient
| VerbrauchsausweisGewerbeClient
| BedarfsausweisWohnenClient,
aufnahme: AufnahmeClient,
objekt: ObjektClient,
bilder: BildClient[],
user: BenutzerClient,
vorschau: boolean = true,
ausweisart = getAusweisartFromId(ausweis.id)
) {
if (!ausweisart) { if (!ausweisart) {
return null return null;
} }
if (ausweisart === Enums.Ausweisart.VerbrauchsausweisWohnen) { if (ausweisart === Enums.Ausweisart.VerbrauchsausweisWohnen) {
return await pdfVerbrauchsausweisWohnen(ausweis as VerbrauchsausweisWohnenClient, aufnahme, objekt, bilder, user, vorschau) return await pdfVerbrauchsausweisWohnen(
ausweis as VerbrauchsausweisWohnenClient,
aufnahme,
objekt,
bilder,
user,
vorschau
);
} else if (ausweisart === Enums.Ausweisart.VerbrauchsausweisGewerbe) { } else if (ausweisart === Enums.Ausweisart.VerbrauchsausweisGewerbe) {
return await pdfVerbrauchsausweisGewerbe(ausweis as VerbrauchsausweisGewerbeClient, aufnahme, objekt, bilder, user, vorschau) return await pdfVerbrauchsausweisGewerbe(
ausweis as VerbrauchsausweisGewerbeClient,
aufnahme,
objekt,
bilder,
user,
vorschau
);
} }
return null return null;
} }
/** /**
* Gibt das richtige Datenblatt basierend auf der Ausweisart zurück. * Gibt das richtige Datenblatt basierend auf der Ausweisart zurück.
* @param ausweis * @param ausweis
*/ */
export async function getDatenblatt(ausweis: VerbrauchsausweisWohnenClient | VerbrauchsausweisGewerbeClient | BedarfsausweisWohnenClient, aufnahme: AufnahmeClient, objekt: ObjektClient, bilder: BildClient[], user: BenutzerClient, rechnung: Rechnung, ausweisart = getAusweisartFromId(ausweis.id)) { export async function getDatenblatt(
ausweis:
| VerbrauchsausweisWohnenClient
| VerbrauchsausweisGewerbeClient
| BedarfsausweisWohnenClient,
aufnahme: AufnahmeClient,
objekt: ObjektClient,
bilder: BildClient[],
user: BenutzerClient,
rechnung: Rechnung,
ausweisart = getAusweisartFromId(ausweis.id)
) {
if (!ausweisart) { if (!ausweisart) {
return null return null;
} }
if (ausweisart === Enums.Ausweisart.VerbrauchsausweisWohnen) { if (ausweisart === Enums.Ausweisart.VerbrauchsausweisWohnen) {
return await pdfDatenblattVerbrauchsausweisWohnen(ausweis as VerbrauchsausweisWohnenClient, aufnahme, objekt, rechnung, bilder) return await pdfDatenblattVerbrauchsausweisWohnen(
ausweis as VerbrauchsausweisWohnenClient,
aufnahme,
objekt,
rechnung,
bilder
);
} else if (ausweisart === Enums.Ausweisart.VerbrauchsausweisGewerbe) { } else if (ausweisart === Enums.Ausweisart.VerbrauchsausweisGewerbe) {
return await pdfDatenblattVerbrauchsausweisGewerbe(ausweis as VerbrauchsausweisGewerbeClient, aufnahme, objekt, rechnung, bilder) return await pdfDatenblattVerbrauchsausweisGewerbe(
ausweis as VerbrauchsausweisGewerbeClient,
aufnahme,
objekt,
rechnung,
bilder
);
} }
return null return null;
} }
/** /**
* Gibt den richtigen Aushang basierend auf der Ausweisart zurück. * Gibt den richtigen Aushang basierend auf der Ausweisart zurück.
* @param ausweis * @param ausweis
*/ */
export async function getAushang(ausweis: VerbrauchsausweisWohnenClient | VerbrauchsausweisGewerbeClient | BedarfsausweisWohnenClient, aufnahme: AufnahmeClient, objekt: ObjektClient, bilder: BildClient[], user: BenutzerClient, vorschau: boolean = true, rechnung: Rechnung, ausweisart = getAusweisartFromId(ausweis.id)) { export async function getAushang(
ausweis:
| VerbrauchsausweisWohnenClient
| VerbrauchsausweisGewerbeClient
| BedarfsausweisWohnenClient,
aufnahme: AufnahmeClient,
objekt: ObjektClient,
bilder: BildClient[],
user: BenutzerClient,
vorschau: boolean = true,
rechnung: Rechnung,
ausweisart = getAusweisartFromId(ausweis.id)
) {
if (!ausweisart || !rechnung.services.includes(Enums.Service.Aushang)) { 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(ausweis as VerbrauchsausweisGewerbeClient, aufnahme, objekt, bilder, user, vorschau) return await pdfAushangVerbrauchsausweisGewerbe(
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

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

View File

@@ -6,11 +6,10 @@
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"
let route: "login" | "signup" = "login" const navigate = (target: string) => {
route = target as typeof route;
const navigate = (target: typeof route) => {
route = target
} }
const loginData = { const loginData = {

View File

@@ -72,6 +72,7 @@
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

@@ -1,384 +0,0 @@
<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"/> <input type="checkbox" required/>
<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}></EmbeddedAuthFlowModule> <EmbeddedAuthFlowModule onLogin={loginAction} email={email} route="signup"></EmbeddedAuthFlowModule>
</div> </div>
</Overlay> </Overlay>

View File

@@ -3,7 +3,6 @@
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;
@@ -13,10 +12,6 @@
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) {
@@ -30,7 +25,7 @@
} }
try { try {
const { uid } = await api.user.PUT.fetch({ const { id } = await api.user.PUT.fetch({
email, email,
passwort, passwort,
vorname, vorname,
@@ -87,7 +82,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:input={handleInput} on:keyup={() => (email = email.toLowerCase())}
required required
/> />
</div> </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.USER ? { where: user.rolle !== Enums.BenutzerRolle.ADMIN ? {
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.USER ? { where: user.rolle !== Enums.BenutzerRolle.ADMIN ? {
id, id,
benutzer_id: user.id benutzer_id: user.id
} : { id }, } : { id },

View File

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

View File

@@ -23,7 +23,8 @@ 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." }),
}), }),
async fetch(input) { middleware: maybeAuthorizationMiddleware,
async fetch(input, context, user) {
const data = input.data; const data = input.data;
if (!isBase64(data, { mimeRequired: true })) { if (!isBase64(data, { mimeRequired: true })) {
@@ -43,6 +44,7 @@ export const PUT = defineApiRoute({
id, id,
kategorie: input.kategorie, kategorie: input.kategorie,
name: input.name, name: input.name,
benutzer_id: user ? user.id : null,
}, },
}); });
@@ -91,11 +93,11 @@ export const DELETE = defineApiRoute({
await prisma.bild.delete({ await prisma.bild.delete({
where: { where: {
id: input.id, id: input.id,
aufnahme: { OR: [{
benutzer: { benutzer_id: user.id
id: user.id, }, {
}, benutzer_id: null
}, }]
}, },
}); });
} else { } else {
@@ -107,6 +109,8 @@ 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.USER) { if (user.rolle !== Enums.BenutzerRolle.ADMIN) {
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.USER) { if (user.rolle !== Enums.BenutzerRolle.ADMIN) {
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.USER) { if (user.rolle !== Enums.BenutzerRolle.ADMIN) {
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.USER) { if (user.rolle !== Enums.BenutzerRolle.ADMIN) {
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.USER ? { where: user.rolle !== Enums.BenutzerRolle.ADMIN ? {
id, id,
benutzer_id: user.id benutzer_id: user.id
} : { id }, } : { id },

View File

@@ -51,7 +51,10 @@ 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.
const { ausweis_id, ausweisart, bezahlmethode, services, partner_code } = input; let { 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\nCategory: ${category}`) 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("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", "ATTA8b65b3587ab627167038cc32a3460650973eb181cde01dabb208ca1e90ed5467AC06A4F2") url.searchParams.append("token", "ATTA6f1774d98472db1897a2373ee7b55ab15f218c2445b6609dfef3071fe5203a90DB15678A")
// 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, admin) { async fetch(input, context, benutzer) {
if ("id" in input) { if ("id" in input) {
//Only Admin can read other users // Nur Admins oder der Benutzer selbst kann einen einzelnen Benutzer lesen
if (admin.rolle != Enums.BenutzerRolle.ADMIN && input.id != admin.id) { if (benutzer.rolle != Enums.BenutzerRolle.ADMIN && input.id != benutzer.id) {
return; return;
} }
@@ -84,8 +84,8 @@ export const GET = defineApiRoute({
return [user]; return [user];
} else { } else {
//Only admin can read many users // Nur Admins können nach mehreren Benutzern suchen
if (admin.rolle != Enums.BenutzerRolle.ADMIN ) { if (benutzer.rolle != Enums.BenutzerRolle.ADMIN) {
return; return;
} }
@@ -114,9 +114,12 @@ 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: input.email email
} }
}) })
@@ -131,10 +134,10 @@ export const PUT = defineApiRoute({
const user = await prisma.benutzer.create({ const user = await prisma.benutzer.create({
data: { data: {
email: input.email, email,
passwort: hashPassword(input.passwort), passwort: hashPassword(passwort),
vorname: input.vorname, vorname,
name: input.name, name,
id id
} }
}) })

View File

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

View File

@@ -1,26 +1,31 @@
--- ---
import AbrechungTable from "#components/Abrechnung/AbrechungTable.svelte"; import AbrechnungTable from "#components/Abrechnung/AbrechnungTable.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"; import moment from "moment-timezone";
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.toDate(); let startdatum = start.isValid() ? start.startOf("day").toDate() : moment().startOf("month").toDate();
let enddatum = end.toDate(); let enddatum = end.isValid() ? end.endOf("day").toDate() : moment().endOf("month").toDate();
const benutzer = await getCurrentUser(Astro) // Wir dürfen die Abrechnung erst ab Juni starten lassen.
if (startdatum < moment().set("year", 2025).set("month", 5).set("date", 1).toDate()) {
if (!benutzer) { startdatum = moment().set("year", 2025).set("month", 5).set("date", 1).set("hour", 0).set("minute", 0).set("second", 0).toDate();
return Astro.redirect("/404") enddatum = moment().set("year", 2025).set("month", 5).set("date", 1).set("hour", 0).set("minute", 0).set("second", 0).endOf("month").toDate();
} }
const provisionen={ const benutzer = await getCurrentUser(Astro);
[Enums.Ausweisart.VerbrauchsausweisWohnen]: 10,
[Enums.Ausweisart.BedarfsausweisWohnen]: 10, if (!benutzer) {
[Enums.Ausweisart.VerbrauchsausweisGewerbe]: 10, return Astro.redirect("/404");
} }
// $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]);
@@ -29,306 +34,206 @@ 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: "immowelt", partner_code: benutzer.partner_code,
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,
}
}],
AND: [{
created_at: {
gte: startdatum
}, },
}, {
created_at: {
lte: enddatum
}, },
}] ],
AND: [
{
erstellt_am: {
gte: startdatum,
},
},
{
erstellt_am: {
lte: enddatum,
},
},
],
}, },
orderBy: { orderBy: {
created_at: "desc" erstellt_am: "desc",
}, },
include: { include: {
bedarfsausweis_wohnen: true, bedarfsausweis_wohnen: {
verbrauchsausweis_gewerbe: true, include: {
verbrauchsausweis_wohnen: true aufnahme: {
} 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: "immowelt", partner_code: benutzer.partner_code,
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: {
created_at: "desc" erstellt_am: "desc",
}, },
include: { include: {
bedarfsausweis_wohnen: true, bedarfsausweis_wohnen: {
verbrauchsausweis_gewerbe: true, include: {
verbrauchsausweis_wohnen: true aufnahme: {
} 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?
const partnerCodeErstesMal = (await prisma.rechnung.findFirst({ if (!startdatum) {
startdatum = (
await prisma.rechnung.findFirst({
select: { select: {
created_at: true erstellt_am: true,
}, },
where: { where: {
partner_code: "immowelt", partner_code: benutzer.partner_code,
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,
} },
}], },
created_at: { ],
gte: moment().set("year", 2020).set("dayOfYear", 1).toDate() erstellt_am: {
} gte: moment().set("year", 2020).set("dayOfYear", 1).toDate(),
},
}, },
orderBy: { orderBy: {
created_at: "asc" erstellt_am: "asc",
},
})
)?.erstellt_am || moment().startOf("month").toDate();
} }
}))?.created_at
const provisionen = await prisma.provisionen.findMany({
where: {
benutzer_id: benutzer.id
}
})
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) {
if (bestellung.verbrauchsausweis_wohnen) { const { provision_betrag, provision_prozent } = getProvision(bestellung.ausweis.ausweisart, bestellung.ausweis.ausweistyp, provisionen);
ausweisarten.push(Enums.Ausweisart.VerbrauchsausweisWohnen) provision += provision_betrag;
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
/>
<!doctype html> <div class="fixed bottom-0 left-0 right-0 bg-white p-4 shadow">
<html lang="de"> <div class="flex justify-between items-center">
<head>
<meta charset="utf-8">
<title>Reporting | online-energieausweis.org</title>
<meta charset="utf-8">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<script type="text/javascript" src="https://cdn.jsdelivr.net/jquery/latest/jquery.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/momentjs/latest/moment.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.min.js"></script>
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.css" />
<link rel="stylesheet" href="./main.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
</head>
<style>
body {
font-family: arial;
}
#inputwrap {
position: fixed;
top: 0;
left: 10%;
width: 70%;
padding-top: 10px;
padding-bottom: 10px;
background: #fff;
}
#cal {
margin-right: 10px !important;
}
#demo {
width: 76%;
display: inline-block;
}
table tr,
td {
border: 0.1em solid #000;
padding: 0;
margin: 0;
}
#QTT {
border-collapse: collapse;
width: 70%;
margin-top: 8em;
margin-left: 10%;
table-layout: auto;
}
#QTT thead td {
background: #ff7d26;
font-weight: bold;
}
#QTT tr:nth-child(even) {
background-color: #f2f2f2;
}
#QTT td {
padding: 0.4em 0.4em 0.4em 0.4em;
text-align: right;
}
#logo1 {
display: inline-block;
margin-right: 1em;
margin-top: -3px;
}
#logo2 {
width: 18%;
margin-bottom: 0.5em;
float: right;
padding-top: -1px;
margin-right: -5px;
}
</style>
<div id='inputwrap' class='form-group' >
<div style='display:flex; justify-content: space-between; align-items:center;'>
<img id='logo1' src='https://widget.ib-cornelsen.de/OEA_WIDGETS/img/IBC-logo.png' alt='IBCornelsen' />
<h5 style='margin-top: 10px;'><b>Erziehlte Conversions von {benutzer.email}</b></h5>
</div>
<input type='text' id='demo' class='form-control' name='demo' value='' placeholder='Bitte Zeitraum auswählen' />
</div>
<AbrechungTable bestellungen={bestellungen} {provisionen} {partnerCodeErstesMal}></AbrechungTable>
<div id="total" class="footer">
<div class="inner">
<div> <div>
<p id="betrag_gesamt">Abrechnungsbetrag gesamt: <b>{provision} €</b></p> <p>
Abrechnungsbetrag gesamt: <b>{provision.toFixed(2)} €</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> <a
target="_blank"
rel="noreferrer noopener"
class="bg-secondary text-white px-4 py-2 rounded-lg hover:bg-secondary-focus"
href=`/dashboard/abrechnung/monatlich.pdf?d=${moment().subtract(1, "month").format("YYYY-MM")}`
>PDF für letzten Monat generieren.</a
>
</div> </div>
</div> </div>
</BlankLayout>
<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

@@ -0,0 +1,146 @@
---
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.USER ? { where: user.rolle !== Enums.BenutzerRolle.ADMIN ? {
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.USER user.rolle !== Enums.BenutzerRolle.ADMIN
? { ? {
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.USER) { if (user.rolle !== Enums.BenutzerRolle.ADMIN) {
const adapter = getPrismaAusweisAdapter(id); const adapter = getPrismaAusweisAdapter(id);
ausweis = await adapter?.findUnique({ ausweis = await adapter?.findUnique({
where: { where: {

View File

@@ -1,8 +1,6 @@
--- ---
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"));
@@ -14,7 +12,7 @@ if (!user) {
const totalPageCount = await prisma.aufnahme.count({ const totalPageCount = await prisma.aufnahme.count({
where: where:
user.rolle === Enums.BenutzerRolle.USER (user.rolle !== Enums.BenutzerRolle.ADMIN)
? { ? {
benutzer: { benutzer: {
id: user.id, id: user.id,
@@ -23,14 +21,17 @@ 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.USER) { if (user.rolle !== Enums.BenutzerRolle.ADMIN) {
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
@@ -40,15 +41,6 @@ if (user.rolle === Enums.BenutzerRolle.USER) {
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
@@ -60,10 +52,6 @@ if (user.rolle === Enums.BenutzerRolle.USER) {
} }
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

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

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

@@ -0,0 +1,87 @@
<!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

@@ -3,7 +3,7 @@
set -e set -e
# Config # Config
CONTAINER_NAME="online-energieausweis-database-1" CONTAINER_NAME="database"
DB_USER="main" DB_USER="main"
DB_NAME="main" DB_NAME="main"
TIMESTAMP=$(date +"%Y-%m-%d_%H-%M-%S") TIMESTAMP=$(date +"%Y-%m-%d_%H-%M-%S")
@@ -39,40 +39,16 @@ 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 > "$FILE_NAME" docker exec -t "$CONTAINER_NAME" pg_dumpall -c -U "$DB_USER" | brotli --quality=1 > "$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" "$DB_NAME" <<'EOSQL' docker exec -i "$CONTAINER_NAME" psql -U "$DB_USER" "postgres" <<'EOSQL'
DO $$ DROP DATABASE IF EXISTS main;
DECLARE CREATE DATABASE main WITH OWNER main ENCODING 'UTF8';
r RECORD;
sql TEXT := '';
BEGIN
-- Truncate all tables
FOR r IN
SELECT tablename
FROM pg_tables
WHERE schemaname = 'public'
LOOP
sql := sql || FORMAT('TRUNCATE 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."