103 Commits

Author SHA1 Message Date
d4d576230d [37288f42] ✦ **Korrektur der Fortschrittsanzeige (Board-Präsentation) abgeschlossen:**
✦ **Korrektur der Fortschrittsanzeige (Board-Präsentation) abgeschlossen:**
  - **Präzise Scroll-Logik:** Die Fortschrittsanzeige wurde von der Bindung an den Body auf das gesamte Dokument () umgestellt und nutzt nun , um die tatsächliche Gesamthöhe inklusive Pinning-Offsets der Folie 0 exakt abzubilden.
  - **Optimiertes C-Level Design:** Die Höhe des Fortschrittsbalkens wurde auf elegante 3px reduziert für einen subtileren, professionelleren Look.
  - **Stabilitäts-Fix:** Das Skript wurde ans Ende der Initialisierung verschoben und ein automatischer Refresh nach dem Laden hinzugefügt, um Synchronisationsfehler bei dynamischen Scroll-Distanzen zu eliminieren.
  - **Text-Feinschliff:** In der Einleitung (Folie 0) wurde das GTM-System präziser als wertvolles Nebenprodukt positioniert.
2026-06-03 07:41:27 +00:00
3b0566f246 Docs: Aktualisierung der Dokumentation für Task [37288f42] 2026-06-03 07:41:26 +00:00
7e9e4e618b [37288f42] fix: overhaule progress bar logic for accurate tracking and refine C-Level design 2026-06-03 07:35:50 +00:00
d99abe2ba0 [37288f42] Keine Zusammenfassung angegeben.
Keine Zusammenfassung angegeben.
2026-06-02 23:33:02 +00:00
98964eadc9 Docs: Aktualisierung der Dokumentation für Task [37288f42] 2026-06-02 23:33:01 +00:00
b0218a6a3c [37288f42] speed: reduce scroll distance for slide 0 to make tool transitions faster 2026-06-02 23:30:16 +00:00
2c23c7be5f [37288f42] feat: add GTM Engine ecosystem slide (Slide 0) with interactive tool icons 2026-06-02 23:27:55 +00:00
7e9f56a528 [37288f42] feat: add strategic background source for NotebookLM and update briefing guide for v2.0 2026-06-02 16:52:17 +00:00
1131cd5ce9 feat: complete reconstruction of presentation, add Data Quality slide with 10 checkpoints, and fix final slide animations 2026-06-02 09:15:26 +00:00
7419d7bcd8 feat: add Data Quality slide, fix layout on Slide 5 and Slide 7 2026-06-02 09:03:57 +00:00
2467817308 feat: fix logo display, update intelligence KPIs with real manager data, and polish final slide 2026-06-02 08:56:08 +00:00
f9ce56a2d1 feat: embed real SuperOffice UI screenshot and fix DGS layout/data 2026-06-02 08:45:49 +00:00
08a5124309 Dateien nach "docs/Praesentation" hochladen 2026-06-02 08:44:46 +00:00
f346f07c24 feat: finalize C-Level presentation with authentic SuperOffice data and UI polish 2026-06-02 08:34:19 +00:00
d0f3e7ba37 Dateien nach "docs/Praesentation/SuperOffice CRM_files" hochladen 2026-06-02 08:08:39 +00:00
3c9547f27f Dateien nach "docs/Praesentation/SuperOffice CRM_files" hochladen 2026-06-02 08:08:04 +00:00
1f9eae7fe1 Dateien nach "docs/Praesentation" hochladen 2026-06-02 08:07:11 +00:00
1676b8b493 [37288f42] Docs: C-Level Presentation v2.6 - Refined DGS UI & Updated Pipeline Comparison 2026-06-02 07:49:12 +00:00
7e2179aff3 [37288f42] Docs: C-Level Presentation v2.5 - Full Base64 Asset Inlining & Counter Optimization 2026-06-02 07:35:08 +00:00
42a5b7adea [37288f42] Docs: C-Level Presentation v2.4 - High-Fidelity Screenshots & Massive C-Level Typography 2026-06-02 06:52:54 +00:00
721f7795f2 Dateien nach "docs/Praesentation" hochladen 2026-06-02 06:51:00 +00:00
273df3ebdc Dateien nach "docs/Praesentation" hochladen 2026-06-02 06:44:28 +00:00
e0b4734934 [37288f42] Docs: C-Level Presentation v2.3 - Fixed Layout Math, High-Resolution Headlines & Micro-Animations 2026-06-02 06:36:08 +00:00
5178a0cf3f [37288f42] Docs: Dashboard Precision Update v2.2 - 1:1 DGS Replica & Layout Alignment Fix 2026-06-02 06:30:15 +00:00
d3178c6e71 [37288f42] Docs: Refinement C-Level Präsentation v2.1 - Maximierter Weißraum, rechtsbündige Visuals & System-Header 2026-06-02 06:18:41 +00:00
952c08ede4 [37288f42] Docs: Kontrast-Fix & Logo-Update für C-Level Präsentation v2.0 (High Contrast Edition) 2026-06-02 06:09:02 +00:00
8905f3f786 Dateien nach "docs/Praesentation" hochladen 2026-06-02 06:06:17 +00:00
11d55ad98e [37288f42] Docs: Launch der v2.0 Sales Dashboard Präsentation - Beamer-tauglicher Light Mode & Controlling-Fokus 2026-06-02 05:59:26 +00:00
ffc5081c6e docs: C-Level Story Refinement - High-Fidelity DGS GmbH Visual inkl. Lifecycle Timeline integriert 2026-06-01 22:04:52 +00:00
f1eaab2c47 [37288f42] Keine Zusammenfassung angegeben.
Keine Zusammenfassung angegeben.
2026-06-01 22:02:00 +00:00
f7b765faf0 Docs: Aktualisierung der Dokumentation für Task [37288f42] 2026-06-01 22:01:59 +00:00
6b547d381c docs: C-Level Story v1.3 - Synchrones Side-by-Side Layout finalisiert 2026-06-01 21:55:12 +00:00
0cfc567626 docs: C-Level Präsentation Background-Fix - Dynamische Hintergrund-Komponenten statt statischer Grafik 2026-06-01 21:31:02 +00:00
28bb075c9e docs: C-Level Präsentation Layout-Refinement - Account-Grouping Visual & Navigations-Logik hinzugefügt 2026-06-01 21:24:30 +00:00
1dfa5224e5 docs: C-Level Präsentation finalisiert - Mehrwert-Fokus, UI-Teaser & Pipeline-Korrektur 2026-06-01 21:17:29 +00:00
56b4a001d1 [37288f42] Keine Zusammenfassung angegeben.
Keine Zusammenfassung angegeben.
2026-06-01 21:01:13 +00:00
01dff4e596 Docs: Aktualisierung der Dokumentation für Task [37288f42] 2026-06-01 21:01:12 +00:00
4364543899 docs: Insights & Lessons Learned für C-Level Story dokumentiert 2026-06-01 20:58:03 +00:00
752c9d9724 docs: C-Level Präsentation Layout-Fix - Dashboard-Komponenten auf der rechten Seite sichtbar gemacht 2026-06-01 20:53:50 +00:00
9eed5ef918 docs: C-Level Presentation 'The Loop' - Layered Dashboard Scrollytelling 2026-06-01 20:47:15 +00:00
966f218a33 docs: C-Level Präsentation Single-File fix - Inlined JS & Visibility Fallback 2026-06-01 20:14:23 +00:00
02e4ddd968 docs: C-Level Präsentation Sales Intelligence Cockpit finalisiert (v1.1) - Offline-Support & Realdaten 2026-06-01 20:07:58 +00:00
443cfa7624 [37288f42] docs: C-Level Präsentation Sales Intelligence Cockpit finalisiert (v1.0) 2026-06-01 19:11:14 +00:00
88aa0df1c7 Dateien nach "docs/Praesentation" hochladen 2026-06-01 19:04:56 +00:00
16b0b9655c Dateien nach "docs/Praesentation" hochladen 2026-06-01 19:01:53 +00:00
7c0facc530 [37288f42] Keine Zusammenfassung angegeben.
Keine Zusammenfassung angegeben.
2026-06-01 17:03:42 +00:00
6962c2825d Docs: Aktualisierung der Dokumentation für Task [37288f42] 2026-06-01 17:03:41 +00:00
a34cd3ad60 feat: Final C-Level Sales Dashboard presentation with charts & Toby [37288f42] 2026-06-01 16:59:50 +00:00
fe6774558b test: Verify git push visibility [37288f42] 2026-06-01 16:58:35 +00:00
1db334f411 docs: Update index.html to reflect Sales Dashboard Cockpit story [37288f42] 2026-06-01 16:50:06 +00:00
ad7380f975 chore: Force refresh Gitea UI with timestamp [37288f42] 2026-06-01 16:26:23 +00:00
b16e9ac8a0 feat: Refactor presentation to focus on Dashboard Cockpit & Action Hub [37288f42] 2026-06-01 16:20:25 +00:00
6722f7807f feat: Final C-Level Story 'The Agentic Sales Engine' [37288f42] 2026-06-01 16:10:02 +00:00
7b6833cb42 refactor: Refine Sales Dashboard presentation with actual traction & features [37288f42] 2026-06-01 15:23:06 +00:00
289e706196 feat: Add C-Level Sales Dashboard presentation v2 [37288f42] 2026-06-01 15:09:15 +00:00
d4f565488d Dateien nach "docs/Praesentation" hochladen 2026-06-01 15:00:03 +00:00
4fb6d37525 Dateien nach "docs/Praesentation" hochladen 2026-06-01 14:48:53 +00:00
169c0863f2 Dateien nach "docs/Praesentation" hochladen 2026-06-01 12:34:31 +00:00
b17e7a04f9 [36288f42] Keine Zusammenfassung angegeben.
Keine Zusammenfassung angegeben.
2026-05-16 11:54:07 +00:00
ec8685d64d Docs: Aktualisierung der Dokumentation für Task [36288f42] 2026-05-16 11:54:07 +00:00
93ae35319e [36288f42] Keine Zusammenfassung angegeben.
Keine Zusammenfassung angegeben.
2026-05-16 11:33:07 +00:00
be00d38470 Docs: Aktualisierung der Dokumentation für Task [36288f42] 2026-05-16 11:33:07 +00:00
06c5624742 [35588f42] Keine Zusammenfassung angegeben.
Keine Zusammenfassung angegeben.
2026-05-15 21:12:58 +00:00
bad450e2d4 Docs: Aktualisierung der Dokumentation für Task [35588f42] 2026-05-15 21:12:58 +00:00
74f35d3831 fix(competitor-analysis): final migration fixes and documentation updates 2026-05-15 21:12:45 +00:00
07e34808be [35588f42] Keine Zusammenfassung angegeben.
Keine Zusammenfassung angegeben.
2026-05-15 21:10:18 +00:00
1084b960cf Docs: Aktualisierung der Dokumentation für Task [35588f42] 2026-05-15 21:10:18 +00:00
85e84b093b fix(competitor-analysis): final migration fixes and documentation updates 2026-05-15 21:09:52 +00:00
3a2d59b974 [35588f42] Keine Zusammenfassung angegeben.
Keine Zusammenfassung angegeben.
2026-05-15 21:06:42 +00:00
ec8d84c993 Docs: Aktualisierung der Dokumentation für Task [35588f42] 2026-05-15 21:06:41 +00:00
ff045c90d0 fix(competitor-analysis): final migration fixes and documentation updates 2026-05-15 21:06:29 +00:00
a367a72c00 fix(competitor-analysis): final migration fixes and documentation updates 2026-05-15 20:52:58 +00:00
748a4ad2a8 [35588f42] Keine Zusammenfassung angegeben.
Keine Zusammenfassung angegeben.
2026-05-15 20:43:48 +00:00
15a1330fc5 Docs: Aktualisierung der Dokumentation für Task [35588f42] 2026-05-15 20:43:47 +00:00
eec9b38af5 fix(competitor-analysis): final migration fixes and documentation updates 2026-05-15 20:43:35 +00:00
ec0a977211 fix(competitor-analysis): final migration fixes and documentation updates 2026-05-15 20:25:47 +00:00
726fcc38ce [35588f42] Keine Zusammenfassung angegeben.
Keine Zusammenfassung angegeben.
2026-05-15 20:08:00 +00:00
30683b27ba Docs: Aktualisierung der Dokumentation für Task [35588f42] 2026-05-15 20:07:59 +00:00
6e1a3be8cf fix(competitor-analysis): final migration fixes and documentation updates 2026-05-15 20:07:45 +00:00
efdd134556 [35588f42] Keine Zusammenfassung angegeben.
Keine Zusammenfassung angegeben.
2026-05-15 18:56:18 +00:00
bd066fbd20 Docs: Aktualisierung der Dokumentation für Task [35588f42] 2026-05-15 18:56:18 +00:00
8388c6da2b fix(competitor-analysis): final migration fixes and documentation updates 2026-05-15 18:55:58 +00:00
d90d856620 [34288f42] Keine Zusammenfassung angegeben.
Keine Zusammenfassung angegeben.
2026-05-04 06:53:45 +00:00
7cb29cd8da Docs: Aktualisierung der Dokumentation für Task [34288f42] 2026-05-04 06:53:44 +00:00
991e338d67 [34288f42] Feature: Add 'Skip Calendly' option for siblings list generation 2026-05-04 06:53:32 +00:00
db94eca626 Dateien nach "ARCHIVE_vor_migration/Fotograf.de" hochladen 2026-05-03 10:05:32 +00:00
1ae8b3e353 [34588f42] Keine Zusammenfassung angegeben.
Keine Zusammenfassung angegeben.
2026-04-18 20:58:31 +00:00
02b17d53ea Docs: Aktualisierung der Dokumentation für Task [34588f42] 2026-04-18 20:58:30 +00:00
d49f6d51f4 [34588f42] Feature: Globaler Sync-Button & Sofort-Statistik
- Globaler 'Daten abgleichen' Button im Modal-Header integriert.
- Neue fast-stats API zeigt Statistiken sofort beim Öffnen des Modals (aus DB).
- UI entrümpelt und Redundanzen entfernt.
2026-04-18 13:59:01 +00:00
995b3ff829 Docs: Aktualisierung der Dokumentation für Task [34588f42] 2026-04-18 13:58:53 +00:00
472f392107 [34588f42] Performance: Massive Beschleunigung der Analyse durch SQLite-Synchronisierung
- Neue Tabelle JobParticipant speichert detaillierte CSV-Daten von Fotograf.de.
- process_reminder_analysis und process_statistics nutzen nun die lokale Datenbank statt Selenium-Crawling.
- Neuer 'Daten abgleichen' Button im Vorbereitungs-Tab integriert.
- Automatischer Quick-Login Link-Generator basierend auf Zugangscodes.
2026-04-18 13:49:03 +00:00
e6061868e6 [34588f42] Chore: Build-Artefakte und UI-Struktur-Fixes
- Frontend Produktions-Build aktualisiert.
- Syntax-Fehler in App.tsx korrigiert und Tabs-Layout stabilisiert.
2026-04-18 13:09:51 +00:00
2a85cab4ab Docs: Aktualisierung der Dokumentation für Task [34588f42] 2026-04-18 13:09:23 +00:00
c458a9c26c [34588f42] Feature: BCC-Kopie an Kontaktadresse und UI-Übersicht für Formularantworten integriert 2026-04-18 11:20:52 +00:00
aa3ff2998f [34588f42] Keine Zusammenfassung angegeben.
Keine Zusammenfassung angegeben.
2026-04-17 22:14:18 +00:00
9645859091 Docs: Aktualisierung der Dokumentation für Task [34588f42] 2026-04-17 22:14:18 +00:00
8d7f5cbbb6 [34588f42] Chore: Build-Artefakte und Test-Skript hinzugefügt
- Frontend Produktions-Build aktualisiert.
- Test-Skript für Dankes-E-Mails committed.
2026-04-17 22:14:00 +00:00
806fa199ce [34588f42] Docs: README für Fotograf.de Scraper aktualisiert
- Feature 6 (Freigabeanfragen & Gutschein-Automation) dokumentiert.
- Technische Details zu Zeitzonen, Sicherheitsmodus (DEV_MODE) und Webhook-URL ergänzt.
2026-04-17 22:13:34 +00:00
19247280a0 [34588f42] Refactor: E-Mail Template für Freigabeanfrage optimiert
- Automatische Bereinigung des Einrichtungsnamens (Entfernung von 'Kindergarten' und Jahreszahlen).
- Links im Text korrigiert und Gallerie-Link auf URL gesetzt.
- Textfluss gestrafft (weniger Absätze) und Grußformel angepasst.
2026-04-17 22:04:57 +00:00
da4995bb3e [34588f42] Fix: Robuste Zeitzonen-Handhabung (Europe/Berlin) für Scheduling
- Hardcodierter UTC+2 Offset durch ZoneInfo('Europe/Berlin') ersetzt, um automatische Sommer-/Winterzeit-Umstellung sicherzustellen.
2026-04-17 21:59:24 +00:00
080a202a9f [34588f42] Fix: FastAPI imports im publish_request_api.py wiederhergestellt 2026-04-17 21:51:11 +00:00
ba06e6d033 [34588f42] Feat: Personalisierte Dankes-E-Mail mit Anleitung und Signatur
- ReleaseParticipant Tabelle hinzugefügt, um Vornamen für den Webhook zwischenzuspeichern.
- Dankes-E-Mail Template mit Anleitungstext, Gutschein-Code und Anleitung-Bild aktualisiert.
- Offizielle Projektsignatur in Backend-E-Mails integriert.
- Frontend sendet nun Teilnehmer-Mapping beim Versand der Anfrage.
2026-04-17 21:43:30 +00:00
3f6b27a89f [34588f42] Feat: Tool 4 für Freigabe-Anfrage verschlankt
- Tool 4 (Freigabeanfragen) wurde von der Tool 3 Abhängigkeit (Supermailer-Analyse) getrennt.
- UI akzeptiert nun eine Liste im Format: E-Mail, Vorname, Kindernamen.
- Das vereinfacht den Workflow drastisch, wenn nur eine Handvoll Kunden manuell für Freigaben angefragt werden sollen.
2026-04-17 20:56:13 +00:00
57 changed files with 21092 additions and 677 deletions

View File

@@ -1 +1 @@
{"task_id": "34288f42-8544-800e-b866-dfcbc22bd4e5", "token": "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", "readme_path": "readme.md", "session_start_time": "2026-04-14T14:09:57.182458"}
{"task_id": "37288f42-8544-8000-9455-e15838939b18", "token": "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", "readme_path": "company-explorer/MIGRATION_PLAN.md", "session_start_time": "2026-06-03T07:41:25.409694"}

View File

@@ -0,0 +1,126 @@
"Child ID";"Vorname Kind";"Nachname Kind";Geschlecht;Geburtsdatum;Einrichtung;Gruppe;Lehrer;"Gültig bis";Bezeichner;Referenz;Straße;PLZ;Ort;Staat;"Geschwisterkind Vorname (1)";"Geschwisterkind Nachname (1)";"Geschwisterkind Vorname (2)";"Geschwisterkind Nachname (2)";Einzelfotos;Gruppenfotos;"Familie / Geschwister";Foto;"Vom Kunden ausgewählt";"Vorname Eltern (1)";"Nachname Eltern (1)";"Email der Eltern (1)";"Telefonnummer der Eltern (1)";"Vorname Eltern (2)";"Nachname Eltern (2)";"Email der Eltern (2)";"Telefonnummer der Eltern (2)";"Zugangscode (1)";"Barcode (1)";"Logins (1)";"Zugangscode (2)";"Barcode (2)";"Logins (2)";Bestellungen
49663204;Fares;AL-KHADHER;;;;Bären;;;;;;;;;;;;;Ja;Ja;Nein;IMG_8069.jpg;Nein;Familie;"Al Khadher";Husseinalkhadher8@gmail.com;;;;;;9CKZ9FRB;859242970856177;2;;;0;0
49656019;Entoni;Altoni;;;;Bären;;;;;;;;;;;;;Ja;Ja;Nein;IMG_7999.jpg;Nein;Yuliia;Altoni;julichka.altony@gmail.com;;;;;;8PH6DT65;590974350307121;1;;;0;1
49659604;"Rashane Tyler";Asasana;;;;Bären;;;;;;;;;;;;;Ja;Ja;Nein;IMG_8048.jpg;Nein;Penphaka;Asasana;asa-sa-na@hotmail.com;;;;;;57VSYGKZ;742438249864838;2;;;0;0
49955890;Yunus;Batuge;;;;Bären;;;;;;;;;;;;;Ja;Ja;"Familien- / Geschwisterfotos";IMG_7831.jpg;Nein;Sümeyra;Senyurt;senyurtsumeyra7@gmail.com;;;;;;Y9LFLVQ6;807433233164209;15;;;0;1
49652597;Josip;Bungic;;;;Bären;;;;;;;;;;;;;Nein;Ja;Nein;;Nein;Mirela;"Marijan Bungic";m.bungic@web.de;;;;;;JYCSXJTX;967076735653408;0;;;0;0
50064753;Hazal;Cicek;;;;Bären;;;;;;;;;;;;;Ja;Ja;Nein;IMG_7714.jpg;Nein;Familie;Cicek;uelke.ardak@hotmail.de;;;;;;VNFYB935;306933685807165;0;;;0;0
49601392;Levi;Damia;;;;Bären;;;;;;;;;;;;;Ja;Ja;Nein;;Nein;Louisa;Damian;damian.louisa@web.de;;;;;;3VP45KKX;107953830470294;0;;;0;0
50314236;Levi;Damian;;;;Bären;;;;;;;;;;;;;Ja;Ja;Nein;IMG_7936.jpg;Nein;Louisa;Damian;damian.louisa@web.de;;;;;;DVXDP3PH;677393543795054;1;;;0;1
50211537;Gökhan;Dogan;;;;Bären;;;;;;;;;Eray;Dogan;;;Ja;Ja;"Familien- / Geschwisterfotos";IMG_8096.jpg;Nein;Familie;Dogan;goeksel_dogan@web.de;;;;;;V9FBBSMP;152677334111372;2;;;0;0
50220839;"Magdalena Personal";Forster;;;;Bären;;;;;;;;;;;;;Ja;Ja;Nein;;Nein;Magdalena;Forster;magdalenaforster@aol.de;;;;;;7NG54JNY;74394435366624;0;;;0;0
49629572;Philipp;Gabauer;;;;Bären;;;;;;;;;;;;;Ja;Ja;"Familien- / Geschwisterfotos";;Nein;Familie;Gabauer;luzia.gabauer@web.de;;;;;;4HP8FX8K;20692770537744;0;;;0;0
49652592;Emilia;"Herrmann Rodriguez";;;;Bären;;;;;;;;;;;;;Nein;Ja;Nein;;Nein;Lukas;Herrmann;Familie.Herrmann.Rodriguez@web.de;;;;;;7X7Y4BKV;73798042174951;0;;;0;0
50060415;Konstantin;Karl;;;;Bären;;;;;;;;;;;;;Ja;Ja;Nein;IMG_7806.jpg;Nein;Katharina;Karl;katharina_karl@mailbox.org;;;;;;XNCV6XM7;810263015266358;0;;;0;0
50060407;Paulina;Karl;;;;Bären;;;;;;;;;;;;;Ja;Ja;"Familien- / Geschwisterfotos";;Nein;Katharina;Karl;katharina_karl@mailbox.org;;;;;;HSKMY37G;607082088640959;0;;;0;0
49901894;Salomia;Karpenko;;;;Bären;;;;;;;;;Miroslav;Karpenko;;;Ja;Ja;Nein;IMG_7786.jpg;Nein;Familie;Karpenko;denis.k88@web.de;;;;;;4P7TJXJL;826081492713003;4;;;0;0
49654259;Jan;Klyszcz;;;;Bären;;;;;;;;;Christoph;Klyszcz;;;Ja;Ja;"Familien- / Geschwisterfotos";IMG_7859.jpg;Nein;Familie;Klyszcz;klyszcz.ewa92@gmail.com;;;;;;V9QQ3MHT;635050103722845;4;;;0;0
50220757;Personal;Lang;;;;Bären;;;;;;;;;;;;;Ja;Ja;Nein;;Nein;Susanne;Lang;susi67.sl@gmail.com;;;;;;J2B9F4FH;84529853902827;0;;;0;0
49663258;Tuldi;"Lennart & Hannes";;;;Bären;;;;;;;;;;;;;Ja;Ja;"Familien- / Geschwisterfotos";IMG_7915.jpg;Nein;Familie;Tuldi;olga_tuldi@yahoo.de;;;;;;Z7D4PJHV;628485247329265;10;;;0;0
49727295;Leonardo;Liquori;;;;Bären;;;;;;;;;;;;;Ja;Ja;"Familien- / Geschwisterfotos";IMG_7654.jpg;Nein;Elisa;Mandelli;e.mandelli1@icloud.com;;;;;;CZBSHZXD;112332574322427;3;;;0;0
49694659;Mara;Schmid;;;;Bären;;;;;;;;;;;;;Ja;Ja;"Familien- / Geschwisterfotos";IMG_7737.jpg;Nein;Familie;Schmid;izuther@googlemail.com;;;;;;M2QWP8PN;693636596918854;4;;;0;0
49553844;Niklas;Schulze;;;;Bären;;;;;;;;;;;;;Ja;Ja;Nein;IMG_8027.jpg;Nein;Kristina;Schulze;m-k-ammersdorf@gmx.de;;Kristina;Schulze;kristina-anna-schulze@web.de;;TDJ47324;213569357182904;4;;;0;1
49605342;Zoe;Seget;;;;Bären;;;;;;;;;;;;;Ja;Ja;"Familien- / Geschwisterfotos";IMG_7764.jpg;Nein;Sandra;Seget;sandra.seget@hotmail.de;;;;;;GTY9QMWP;335161472735404;3;;;0;1
50211319;Valentin;Slugocki;;;;Bären;;;;;;;;;;;;;Ja;Ja;Nein;;Nein;Bartek;Slugocki;bartek@slugocki.de;;;;;;24632X5S;557733375991183;0;;;0;0
50219244;"Hannes & Lennart";Tuldi;;;;Bären;;;;;;;;;;;;;Ja;Ja;"Familien- / Geschwisterfotos";IMG_7959.jpg;Nein;Familie;Tuldi;olga_tuldi@yahoo.de;;;;;;SZ7D82KL;195111473283743;9;;;0;1
49697372;Xaver;Wego;;;;Bären;;;;;;;;;;;;;Ja;Ja;Nein;IMG_7883.jpg;Nein;Luisa;Wego;luisa.wego@web.deq;;Luisa;Wego;luisa.wego@web.de;;JT22FL8Y;470837065491819;0;6XJVMHVQ;423156711490859;6;1
49655774;Maximilian;Wild;;;;Bären;;;;;;;;;;;;;Ja;Ja;"Familien- / Geschwisterfotos";IMG_7977.jpg;Nein;Familie;Wild;wildramona@gmx.de;;;;;;XYCPRYX3;841975500351515;6;;;0;0
49613372;Anton;Adelberger;;;;Bienen;;;;;;;;;;;;;Ja;Ja;Nein;IMG_8367.jpg;Nein;Catharina;Adelberger;catharina.adelberger@web.de;;;;;;GCSBWRRG;255252947890362;5;;;0;0
49655260;Ludwig;Baumgartner;;;;Bienen;;;;;;;;;;;;;Ja;Ja;"Familien- / Geschwisterfotos";;Nein;Franziska;Baumgartner;franziwild@gmx.de;;;;;;JJDBSW2Y;1346656807317;0;;;0;0
49652607;Josip;Bungic;;;;Bienen;;;;;;;;;;;;;Nein;Ja;Nein;;Nein;Mirela;"Marijan Bungic";m.bungic@web.de;;;;;;XR3LFNTW;896957772773017;1;;;0;0
50064781;Havin;Cicek;;;;Bienen;;;;;;;;;;;;;Ja;Ja;"Familien- / Geschwisterfotos";IMG_8198.jpg;Nein;Familie;Cicek;uelke.ardak@hotmail.de;;;;;;8X8GYWR3;847511991706072;1;;;0;0
50055747;Mattea;Fusarri;;;;Bienen;;;;;;;;;;;;;Ja;Ja;Nein;IMG_8151.jpg;Nein;Nadia;Fusarri;nadia.fusarri@gmx.de;;;;;;ZRGWQM3W;439731985455440;3;;;0;0
50247238;Maliya;Gildner;;;;Bienen;;;;;;;;;;;;;Ja;Ja;Nein;IMG_8341.jpg;Nein;Alisa;Gildner;gildner31@gmail.com;;;;;;KRTVJ4M5;910013114016383;2;;;0;0
49825283;Kilian;Hartl;;;;Bienen;;;;;;;;;;;;;Ja;Ja;"Familien- / Geschwisterfotos";IMG_8410.jpg;Nein;Familie;Schreibauer;a.schreibauer@gmail.com;;;;;;NM92G8PK;534850382393461;3;;;0;0
50153154;"Elara Carolina";Hintermaier;;;;Bienen;;;;;;;;;;;;;Ja;Ja;Nein;IMG_8289.jpg;Nein;Adriana;Hintermaier;adri.shunka@gmail.com;;;;;;N6G67PPZ;233647866343524;1;;;0;0
49700913;Luka;Loncar;;;;Bienen;;;;;;;;;;;;;Ja;Ja;Nein;IMG_8435.jpg;Nein;Szilvia;Palinkas;silvijapalinkas@yahoo.com;;;;;;7FGK48GQ;345687401851686;5;;;0;1
50056989;Elias;Minksz;;;;Bienen;;;;;;;;;;;;;Ja;Ja;"Familien- / Geschwisterfotos";;Nein;Carolin;Dirndorfer;c.dirndorfer@gmx.de;;;;;;GZ3DQSPL;632143387747513;0;;;0;0
49770856;Anna;Nguyen;;;;Bienen;;;;;;;;;;;;;Ja;Ja;Nein;IMG_8219.jpg;Nein;Thi;"Hien Minh Nguyen";nthm30121996@gmail.com;;Anna;Nguyen;ging318@gmail.com;;CM9CMLBJ;122574286373832;2;;;0;0
50180008;Ilia;Nickl;;;;Bienen;;;;;;;;;;;;;Ja;Ja;"Familien- / Geschwisterfotos";IMG_8390.jpg;Nein;Familie;Nickl;cela990@hotmail.com;;;;;;KHPY6LQV;652142151577775;9;;;0;0
49575500;Mika;Rubinstein;;;;Bienen;;;;;;;;;Mia;Rubinstein;;;Ja;Ja;"Familien- / Geschwisterfotos";IMG_8548.jpg;Nein;Familie;Rubinstein;n.d.rubinstein@googlemail.com;;;;;;K7PX4J8Y;415300008608215;2;;;0;0
49652538;Alina;Schillinger;;;;Bienen;;;;;;;;;;;;;Ja;Ja;"Familien- / Geschwisterfotos";IMG_8244.jpg;Nein;Familie;Schillinger;schneggeno1@web.de;;;;;;X27P5L9Q;180935518874486;3;;;0;0
49663277;Malia;Schlesinger;;;;Bienen;;;;;;;;;;;;;Ja;Ja;"Familien- / Geschwisterfotos";IMG_8126.jpg;Nein;Familie;Schlesinger;stefanie2011@gmx.net;;;;;;6672SN99;377539049099605;2;;;0;0
50257156;Marie;Schöberl;;;;Bienen;;;;;;;;;;;;;Ja;Ja;Nein;IMG_8265.jpg;Nein;Michaela;Schöberl;michaela.schoeberl@gmx.de;;;;;;BKZWFCS4;504469516218803;2;;;0;0
50057519;Letizia;Stachanczyk;;;;Bienen;;;;;;;;;Leonardo;Stachanczyk;;;Ja;Ja;"Familien- / Geschwisterfotos";;Nein;Familie;Stachanczyk;Suzanna.Stachanczyk@web.de;;;;;;C7GX6BM2;268376387434609;0;;;0;0
49919594;Ela;Torres;;;;Bienen;;;;;;;;;;;;;Ja;Ja;"Familien- / Geschwisterfotos";IMG_8313.jpg;Nein;Familie;Torres;ftorrestapia@me.com;;;;;;YGL954RX;63682236385188;4;;;0;0
49837810;Maximilian;Weber;;;;Bienen;;;;;;;;;;;;;Ja;Ja;"Familien- / Geschwisterfotos";IMG_8522.jpg;Nein;Familie;Weber;mail.weber.melanie@googlemail.com;;;;;;4QS8GPWR;7954462978689;3;;;0;0
50006492;Musa;Yilmaz;;;;Bienen;;;;;;;;;Ömer;Yilmaz;;;Ja;Ja;"Familien- / Geschwisterfotos";IMG_8466.jpg;Nein;Familie;Yilmaz;merve-ymz@hotmail.com;;;;;;82YTD8FK;912359706713774;4;;;0;0
49652523;Nina;Zhang;;;;Bienen;;;;;;;;;;;;;Ja;Ja;Nein;IMG_8174.jpg;Nein;Hua;Zhang;zhanghua0411@hotmail.com;;;;;;DTWR882P;201986576299456;3;;;0;1
49663112;Elias;Bonifati;;;;Fische;;;;;;;;;;;;;Ja;Ja;"Familien- / Geschwisterfotos";IMG_6891.jpg;Nein;Familie;Misiano;bonifati@hotmail.de;;;;;;BVMZFPHK;582378219071480;5;;;0;0
49652608;Mihael;Bungic;;;;Fische;;;;;;;;;;;;;Nein;Ja;Nein;;Nein;Mirela;"Marijan Bungic";m.bungic@web.de;;;;;;2664S6D3;848993713584313;1;;;0;0
50248018;Alina;Catak;;;;Fische;;;;;;;;;;;;;Ja;Ja;Nein;IMG_6424.jpg;Nein;Catak;Admir;catakadmir@gmail.com;;;;;;ML42KL42;532022002681433;7;;;0;1
50258123;Jakov;Ceko;;;;Fische;;;;;;;;;;;;;Ja;Ja;Nein;IMG_7294.jpg;Nein;Familie;Ceko;brankoceko91@gmail.com;;;;;;CVBYPKBV;550993218062367;0;;;0;0
50258100;Luka;Ceko;;;;Fische;;;;;;;;;;;;;Ja;Ja;Nein;IMG_7043.jpg;Nein;Familie;Ceko;brankoceko91@gmail.com;;;;;;DJKP3CBY;120492680122927;0;;;0;0
50222105;"Saide Mira";Cildir;;;;Fische;;;;;;;;;;;;;Ja;Ja;Nein;IMG_6617.jpg;Nein;Hasret;Cildir;hasretcildir@web.de;;;;;;M9B773FR;568587367870302;1;;;0;0
50218962;Valentin;Gabauer;;;;Fische;;;;;;;;;;;;;Ja;Ja;Nein;IMG_7004.jpg;Nein;Sabine;Gabauer;sabine.gabauer@gmx.de;;;;;;QXWD3ZNS;377128045906117;2;;;0;1
50079276;Anika;Gaßner;;;;Fische;;;;;;;;;;;;;Ja;Ja;"Familien- / Geschwisterfotos";IMG_6555.jpg;Nein;Familie;Karyakina;veronika20@hotmail.de;;;;;;3BSPHDHW;851569123151027;1;;;0;0
50211546;Kilian;Glück;;;;Fische;;;;;;;;;;;;;Ja;Ja;"Familien- / Geschwisterfotos";IMG_6960.jpg;Nein;Familie;Glück;katja_glueck@web.de;;;;;;2LGTH3VN;783740741149373;2;;;0;0
50282221;Lisa;Gumberger;;;;Fische;;;;;;;;;;;;;Ja;Ja;Nein;IMG_6470.jpg;Nein;Sarah;Gumberger;sarah.gumberger@gmx.de;;;;;;2PHHTXT9;383948464807600;3;;;0;1
49616818;Nadine;Hamed;;;;Fische;;;;;;;;;;;;;Ja;Ja;Nein;IMG_6648.jpg;Nein;;Nadine;sarasadaby@gmail.com;;;;;;V3KTTVNV;319851445592837;3;;;0;1
50208499;Noela;Islami;;;;Fische;;;;;;;;;;;;;Ja;Ja;"Familien- / Geschwisterfotos";IMG_6524.jpg;Nein;Familie;Islami;Zineta.islami@gmx.de;;;;;;CK5B7WWN;410922852620998;2;;;0;0
49901895;Miroslav;Karpenko;;;;Fische;;;;;;;;;Salomia;Karpenko;;;Ja;Ja;Nein;IMG_6848.jpg;Nein;Familie;Karpenko;denis.k88@web.de;;;;;;922GC8BH;915901506033102;1;;;0;0
50221939;Merjem;Kaukovic;;;;Fische;;;;;;;;;;;;;Ja;Ja;Nein;IMG_6502.jpg;Nein;Edita;Porcic-Kaukovic;editta1996@hotmail.com;;;;;;KGVKW8HZ;305550439054156;1;;;0;0
50288355;Frauke;Klinge;;;;Fische;;;;;;;;;;;;;Nein;Ja;Nein;;Nein;Frauke;Klinge;fs.klinge@t-online.de;;;;;;K4GCTQJG;948438313552204;0;;;0;0
49653461;Max;Krämer;;;;Fische;;;;;;;;;;;;;Ja;Ja;Nein;IMG_6778.jpg;Nein;Michael;Krämer;m.k326@web.de;;;;;;PF5DCT5N;929652051737843;2;;;0;1
49663714;Casper;Mettig;;;;Fische;;;;;;;;;;;;;Ja;Ja;"Familien- / Geschwisterfotos";;Nein;Familie;Roder;stephanie.roder@gmail.com;;;;;;4L2ZBL3M;296176221032781;6;;;0;0
50208680;Emre;Mujic;;;;Fische;;;;;;;;;;;;;Ja;Ja;Nein;IMG_6927.jpg;Nein;Amra;Mujic;amra.mujic95@gmail.com;;;;;;QVBTGS73;16951665504680;2;;;0;0
49635375;Mila;Nickl;;;;Fische;;;;;;;;;;;;;Ja;Ja;"Familien- / Geschwisterfotos";IMG_6685.jpg;Nein;Familie;Nickl;cela990@hotmail.com;;;;;;S4Z3HPZY;736840096508400;8;;;0;0
49575499;Mia;Rubinstein;;;;Fische;;;;;;;;;Mika;Rubinstein;;;Ja;Ja;"Familien- / Geschwisterfotos";IMG_6443.jpg;Nein;Familie;Rubinstein;n.d.rubinstein@googlemail.com;;;;;;BGGHXNLC;908242966462743;3;;;0;0
49786711;Zoe;Scholpp;;;;Fische;;;;;;;;;;;;;Ja;Ja;Nein;IMG_6721.jpg;Nein;Sabrina;Scholpp;sabrinasch1107@gmail.com;;;;;;CXMT4R9T;585861217320080;2;;;0;0
49755578;Valerie;Schultze;;;;Fische;;;;;;;;;;;;;Ja;Ja;Nein;IMG_6743.jpg;Nein;Anita;Schultze;anitajuliane.schultze@gmail.com;;;;;;4WVF24SR;499960849175102;2;;;0;0
50057518;Leonardo;Stachanczyk;;;;Fische;;;;;;;;;Letizia;Stachanczyk;;;Ja;Ja;"Familien- / Geschwisterfotos";IMG_7072.jpg;Nein;Familie;Stachanczyk;Suzanna.Stachanczyk@web.de;;;;;;BZC28W7T;900613948231467;7;;;0;0
50211898;Maya;Watanabe;;;;Fische;;;;;;;;;;;;;Ja;Ja;Nein;IMG_6591.jpg;Nein;Barbara;Watanabe;barbara.j@live.de;;;;;;ZPVJ8R5Q;341636130475078;1;;;0;0
50006491;Ömer;Yilmaz;;;;Fische;;;;;;;;;Musa;Yilmaz;;;Ja;Ja;"Familien- / Geschwisterfotos";IMG_6809.jpg;Nein;Familie;Yilmaz;merve-ymz@hotmail.com;;;;;;C4NB42PX;659038103936299;4;;;0;0
50078572;Aurelia;Adelsberger;;;;Spatzen;;;;;;;;;;;;;Ja;Ja;"Familien- / Geschwisterfotos";IMG_7493.jpg;Nein;Familie;Adelsberger;barbara.adelsberger@yahoo.de;;Christian;Godelmann;floke.com@gmail.com;;3NVRB2BM;230676178020824;1;;;0;0
50220084;Eymen;Baldir;;;;Spatzen;;;;;;;;;;;;;Ja;Ja;Nein;IMG_6406.jpg;Nein;Seda;Baldir;seda.ay@icloud.com;;;;;;JDG5CDT8;381447885366279;1;;;0;0
49602297;Magdalena;Bauer;;;;Spatzen;;;;;;;;;;;;;Ja;Ja;"Familien- / Geschwisterfotos";IMG_7463.jpg;Nein;Familie;Bauer;bonprix29@yahoo.de;;;;;;V7W5ZPVY;411274063112493;8;;;0;0
49992146;Zoe;Cajic;;;;Spatzen;;;;;;;;;;;;;Ja;Ja;Nein;IMG_7414.jpg;Nein;Amar;Cajic;amar.cajic@gmail.com;;;;;;JMZD8SNN;53184897942750;3;;;0;0
50057147;Mario;Cakic;;;;Spatzen;;;;;;;;;;;;;Ja;Ja;Nein;IMG_7182.jpg;Nein;Lucija;Zivkovic;lucija.zivkovic16@gmail.com;;;;;;D8SZH5XL;508565079338980;3;;;0;1
50211538;Eray;Dogan;;;;Spatzen;;;;;;;;;Gökhan;Dogan;;;Ja;Ja;"Familien- / Geschwisterfotos";IMG_7380.jpg;Nein;Familie;Dogan;goeksel_dogan@web.de;;;;;;LMLH64KS;61030672835452;2;;;0;0
49552513;Antonia;Freiwald;;;;Spatzen;;;;;;;;;;;;;Ja;Ja;Nein;IMG_7643.jpg;Nein;Stephanie;Freiwald;stephanie.freiwald@gmx.de;;;;;;4BV76XQS;224897626646913;1;;;0;1
49601982;Heidi;Götzberger;;;;Spatzen;;;;;;;;;;;;;Ja;Ja;"Familien- / Geschwisterfotos";IMG_7618.jpg;Nein;Familie;Götzberger;franziska.lanzinger@t-online.de;;;;;;KSH3Y552;141723213815881;2;;;0;0
50063666;Una;Hodzic;;;;Spatzen;;;;;;;;;;;;;Ja;Ja;Nein;IMG_7561.jpg;Nein;Hodžić;Aldin;menager21@hotmail.com;;;;;;9W4CYMRX;170368609363861;4;;;0;1
49603438;Liara;Honisch;;;;Spatzen;;;;;;;;;;;;;Ja;Ja;"Familien- / Geschwisterfotos";IMG_7519.jpg;Nein;Familie;Karayilan;yasemin.karayilan@yahoo.de;;;;;;MSNCXQ77;787504756287604;2;;;0;0
49623482;Matteo;Katterfeld;;;;Spatzen;;;;;;;;;;;;;Ja;Ja;"Familien- / Geschwisterfotos";IMG_7347.jpg;Nein;Familie;Ketterfeld;madlen.katterfeld@gmx.de;;;;;;YJ9MM349;888691047601122;6;;;0;0
49654260;Christoph;Klyszcz;;;;Spatzen;;;;;;;;;Jan;Klyszcz;;;Ja;Ja;"Familien- / Geschwisterfotos";IMG_7320.jpg;Nein;Familie;Klyszcz;klyszcz.ewa92@gmail.com;;;;;;V3KSRPDM;389595058391936;6;;;0;0
50056841;Ludwig;Lacen;;;;Spatzen;;;;;;;;;;;;;Ja;Ja;Nein;IMG_7246.jpg;Nein;Michael;Lacen;michael.lacen@gmx.de;;;;;;GJFNWHMY;205672235649590;2;;;0;1
50056690;Emilia;Rodriguez;;;;Spatzen;;;;;;;;;;;;;Ja;Ja;Nein;IMG_7591.jpg;Nein;Daniela;Rodriguez;daniela-hinz-82@gmx.de;;;;;;9LQLW7YV;289213156745302;1;;;0;1
49652595;Vaiana;Slaiman;;;;Spatzen;;;;;;;;;;;;;Ja;Ja;Nein;;Nein;;Slaiman;hadeer94hasan@web.de;;;;;;YB24BVQR;230552964517174;0;;;0;0
49838169;Raphael;Weber;;;;Spatzen;;;;;;;;;;;;;Ja;Ja;Nein;IMG_7214.jpg;Nein;Familie;Weber;mail.weber.melanie@googlemail.com;;;;;;W3VBKM3W;362639250675953;3;;;0;0
49906413;Ludwig;Welz;;;;Spatzen;;;;;;;;;;;;;Ja;Ja;"Familien- / Geschwisterfotos";IMG_7149.jpg;Nein;Familie;Welz;eva_welz@gmx.de;;;;;;VPJYZ48P;785597492180163;3;;;0;0
49726920;Amy;Wieters;;;;Spatzen;;;;;;;;;;;;;Ja;Ja;Nein;IMG_7443.jpg;Nein;Janine;Wieters;janine28@gmx.de;;;;;;69KFXLBD;921506261142206;4;;;0;1
50453287;Familie;Adelsberger;;;;;;;;;;;;;;;;;Ja;Ja;Nein;IMG_0728.jpg;Nein;Familie;Adelsberger;barbara.adelsberger@yahoo.de;;;;;;JVXV9T2M;916908422224646;1;;;0;1
50451311;Familie;"Al Khadher";;;;;;;;;;;;;;;;;Ja;Ja;Nein;IMG_0153.jpg;Nein;Familie;"Al Khadher";Husseinalkhadher8@gmail.com;;;;;;4VTSN5J6;437618998198555;2;;;0;1
50453454;Familie;Bauer;;;;;;;;;;;;;;;;;Ja;Ja;Nein;IMG_0353.jpg;Nein;Familie;Bauer;bonprix29@yahoo.de;;;;;;N8DZBDLW;169896993687826;2;;;0;0
50491788;Familie;Baumgartner;;;;;;;;;;;;;;;;;Ja;Ja;Nein;IMG_0451.jpg;Nein;Franziska;Baumgartner;franziwild@gmx.de;;;;;;ZRH36VRS;847462383118786;2;;;0;1
50463803;Amla;Bobo;;;;;;;;;;;;;;;;;Ja;Ja;Nein;IMG_7689.jpg;Nein;Xhulia;Xhelci;xhuliaxhelci@gmail.com;;;;;;C35CQ55V;950839783885570;1;;;0;0
50451304;Familie;Ceko;;;;;;;;;;;;;;;;;Ja;Ja;Nein;IMG_0099.jpg;Nein;Familie;Ceko;brankoceko91@gmail.com;;;;;;4NZXSHTW;798398153397116;2;;;0;0
50453898;Familie;Cicek;;;;;;;;;;;;;;;;;Ja;Ja;Nein;IMG_2693.jpg;Nein;Familie;Cicek;uelke.ardak@hotmail.de;;;;;;YC9KVV76;290706892284805;3;;;0;1
50453136;Familie;Dogan;;;;;;;;;;;;;;;;;Ja;Ja;Nein;IMG_9830.jpg;Nein;Familie;Dogan;goeksel_dogan@web.de;;;;;;GDBRDW6K;918773718877810;2;;;0;0
50453529;Familie;Gabauer;;;;;;;;;;;;;;;;;Ja;Ja;Nein;IMG_0286.jpg;Nein;Familie;Gabauer;luzia.gabauer@web.de;;;;;;SQHNHMH6;49585341392454;6;;;0;1
50452028;Familie;Glück;;;;;;;;;;;;;;;;;Ja;Ja;Nein;IMG_0406.jpg;Nein;Familie;Glück;katja_glueck@web.de;;;;;;7WTXJNDC;628871407778415;2;;;0;0
50453448;Familie;Götzberger;;;;;;;;;;;;;;;;;Ja;Ja;Nein;IMG_1262.jpg;Nein;Familie;Götzberger;franziska.lanzinger@t-online.de;;;;;;MPHRG7SP;954770009741299;2;;;0;1
50453434;Familie;Islami;;;;;;;;;;;;;;;;;Ja;Ja;Nein;IMG_1330.jpg;Nein;Familie;Islami;Zineta.islami@gmx.de;;;;;;KF7CNCYZ;620178179159158;2;;;0;0
50452019;Familie;Karayilan;;;;;;;;;;;;;;;;;Ja;Ja;Nein;IMG_0200.jpg;Nein;Familie;Karayilan;yasemin.karayilan@yahoo.de;;;;;;6H73JV6B;765446752804075;1;;;0;0
50453439;Familie;Karpenko;;;;;;;;;;;;;;;;;Ja;Ja;Nein;IMG_2825.jpg;Nein;Familie;Karpenko;denis.k88@web.de;;;;;;LR87SQ8C;963707649418838;1;;;0;0
50453495;Familie;Karyakina;;;;;;;;;;;;;;;;;Ja;Ja;Nein;IMG_0800.jpg;Nein;Familie;Karyakina;veronika20@hotmail.de;;;;;;RYK4BQLQ;638219110542782;1;;;0;0
50453488;Familie;Ketterfeld;;;;;;;;;;;;;;;;;Ja;Ja;Nein;IMG_1211.jpg;Nein;Familie;Ketterfeld;madlen.katterfeld@gmx.de;;;;;;PLX9G4V3;117752011222601;6;;;0;1
50453446;Familie;Klyszcz;;;;;;;;;;;;;;;;;Ja;Ja;Nein;IMG_2740.jpg;Nein;Familie;Klyszcz;klyszcz.ewa92@gmail.com;;;;;;LZR8WFP9;874820410323668;9;;;0;1
50452151;Familie;Misiano;;;;;;;;;;;;;;;;;Ja;Ja;Nein;IMG_1577.jpg;Nein;Familie;Misiano;bonifati@hotmail.de;;;;;;7ZW9V666;394112462489259;5;;;0;1
50451284;Familie;Nickl;;;;;;;;;;;;;;;;;Ja;Ja;Nein;IMG_1054.jpg;Nein;Familie;Nickl;cela990@hotmail.com;;;;;;35CH589Q;438824910404667;8;;;0;1
50452230;Familie;Roder;;;;;;;;;;;;;;;;;Ja;Ja;Nein;IMG_1138.jpg;Nein;Familie;Roder;stephanie.roder@gmail.com;;;;;;848D3SWY;745487201848290;6;;;0;0
50452913;Familie;Rubinstein;;;;;;;;;;;;;;;;;Ja;Ja;Nein;IMG_0041.jpg;Nein;Familie;Rubinstein;n.d.rubinstein@googlemail.com;;;;;;G9H8YFC4;180523151134386;1;;;0;1
50453768;Familie;Schillinger;;;;;;;;;;;;;;;;;Ja;Ja;Nein;IMG_1404.jpg;Nein;Familie;Schillinger;schneggeno1@web.de;;;;;;SQJSP49C;593384265020703;3;;;0;1
50452236;Familie;Schlesinger;;;;;;;;;;;;;;;;;Ja;Ja;Nein;IMG_1512.jpg;Nein;Familie;Schlesinger;stefanie2011@gmx.net;;;;;;946G6HJH;269413107409936;3;;;0;1
50453894;Familie;Schmid;;;;;;;;;;;;;;;;;Ja;Ja;Nein;IMG_0625.jpg;Nein;Familie;Schmid;izuther@googlemail.com;;;;;;W7W8P32C;486167508950250;4;;;0;1
50452248;Familie;Schreibauer;;;;;;;;;;;;;;;;;Ja;Ja;Nein;IMG_0671.jpg;Nein;Familie;Schreibauer;a.schreibauer@gmail.com;;;;;;97R4TRBC;130440825414681;3;;;0;0
50452273;Familie;Stachanczyk;;;;;;;;;;;;;;;;;Ja;Ja;Nein;IMG_9974.jpg;Nein;Familie;Stachanczyk;Suzanna.Stachanczyk@web.de;;;;;;C334SSSL;733864213043388;5;;;0;0
50453485;Familie;Torres;;;;;;;;;;;;;;;;;Ja;Ja;Nein;IMG_0967.jpg;Nein;Familie;Torres;ftorrestapia@me.com;;;;;;PCQ4CNV9;553742663210606;4;;;0;0
50452252;Familie;Tuldi;;;;;;;;;;;;;;;;;Ja;Ja;Nein;IMG_2788.jpg;Nein;Familie;Tuldi;olga_tuldi@yahoo.de;;;;;;B99BYYYF;657381798122682;11;;;0;0
50452022;Familie;Weber;;;;;;;;;;;;;;;;;Ja;Ja;Nein;IMG_0984.jpg;Nein;Familie;Weber;mail.weber.melanie@googlemail.com;;;;;;7954G4C5;820357028620317;3;;;0;1
50451320;Familie;Welz;;;;;;;;;;;;;;;;;Ja;Ja;Nein;IMG_1459.jpg;Nein;Familie;Welz;eva_welz@gmx.de;;;;;;69N5WYFK;952025141929986;3;;;0;1
50452419;Familie;Wild;;;;;;;;;;;;;;;;;Ja;Ja;Nein;IMG_0539.jpg;Nein;Familie;Wild;wildramona@gmx.de;;;;;;DVXKKJCZ;789239059675168;9;;;0;1
50452462;Familie;Wolf;;;;;;;;;;;;;;;;;Ja;Ja;Nein;IMG_0841.jpg;Nein;Familie;Wolf;anjamichi77@gmail.com;;;;;;FXPLQYH9;784676508389646;1;;;0;0
50410050;Jonas;Wolf;;;;;;;;;;;;;;;;;Ja;Ja;Nein;IMG_8489.jpg;Nein;Familie;Wolf;anjamichi77@gmail.com;;;;;;Q52NYB4N;18810570193338;0;;;0;0
50453882;Familie;Yilmaz;;;;;;;;;;;;;;;;;Ja;Ja;Nein;IMG_9907.jpg;Nein;Familie;Yilmaz;merve-ymz@hotmail.com;;;;;;TXK86QSB;467856804734432;4;;;0;0
49655787;Joseph;Wild;;;;Spatzen;;;;;;;;;;;;;Ja;Ja;Nein;IMG_7119.jpg;Nein;Familie;Wild;wildramona@gmx.de;;;;;;8C56662R;226166391326912;5;;;0;0
1 Child ID Vorname Kind Nachname Kind Geschlecht Geburtsdatum Einrichtung Gruppe Lehrer Gültig bis Bezeichner Referenz Straße PLZ Ort Staat Geschwisterkind Vorname (1) Geschwisterkind Nachname (1) Geschwisterkind Vorname (2) Geschwisterkind Nachname (2) Einzelfotos Gruppenfotos Familie / Geschwister Foto Vom Kunden ausgewählt Vorname Eltern (1) Nachname Eltern (1) Email der Eltern (1) Telefonnummer der Eltern (1) Vorname Eltern (2) Nachname Eltern (2) Email der Eltern (2) Telefonnummer der Eltern (2) Zugangscode (1) Barcode (1) Logins (1) Zugangscode (2) Barcode (2) Logins (2) Bestellungen
2 49663204 Fares AL-KHADHER Bären Ja Ja Nein IMG_8069.jpg Nein Familie Al Khadher Husseinalkhadher8@gmail.com 9CKZ9FRB 859242970856177 2 0 0
3 49656019 Entoni Altoni Bären Ja Ja Nein IMG_7999.jpg Nein Yuliia Altoni julichka.altony@gmail.com 8PH6DT65 590974350307121 1 0 1
4 49659604 Rashane Tyler Asasana Bären Ja Ja Nein IMG_8048.jpg Nein Penphaka Asasana asa-sa-na@hotmail.com 57VSYGKZ 742438249864838 2 0 0
5 49955890 Yunus Batuge Bären Ja Ja Familien- / Geschwisterfotos IMG_7831.jpg Nein Sümeyra Senyurt senyurtsumeyra7@gmail.com Y9LFLVQ6 807433233164209 15 0 1
6 49652597 Josip Bungic Bären Nein Ja Nein Nein Mirela Marijan Bungic m.bungic@web.de JYCSXJTX 967076735653408 0 0 0
7 50064753 Hazal Cicek Bären Ja Ja Nein IMG_7714.jpg Nein Familie Cicek uelke.ardak@hotmail.de VNFYB935 306933685807165 0 0 0
8 49601392 Levi Damia Bären Ja Ja Nein Nein Louisa Damian damian.louisa@web.de 3VP45KKX 107953830470294 0 0 0
9 50314236 Levi Damian Bären Ja Ja Nein IMG_7936.jpg Nein Louisa Damian damian.louisa@web.de DVXDP3PH 677393543795054 1 0 1
10 50211537 Gökhan Dogan Bären Eray Dogan Ja Ja Familien- / Geschwisterfotos IMG_8096.jpg Nein Familie Dogan goeksel_dogan@web.de V9FBBSMP 152677334111372 2 0 0
11 50220839 Magdalena Personal Forster Bären Ja Ja Nein Nein Magdalena Forster magdalenaforster@aol.de 7NG54JNY 74394435366624 0 0 0
12 49629572 Philipp Gabauer Bären Ja Ja Familien- / Geschwisterfotos Nein Familie Gabauer luzia.gabauer@web.de 4HP8FX8K 20692770537744 0 0 0
13 49652592 Emilia Herrmann Rodriguez Bären Nein Ja Nein Nein Lukas Herrmann Familie.Herrmann.Rodriguez@web.de 7X7Y4BKV 73798042174951 0 0 0
14 50060415 Konstantin Karl Bären Ja Ja Nein IMG_7806.jpg Nein Katharina Karl katharina_karl@mailbox.org XNCV6XM7 810263015266358 0 0 0
15 50060407 Paulina Karl Bären Ja Ja Familien- / Geschwisterfotos Nein Katharina Karl katharina_karl@mailbox.org HSKMY37G 607082088640959 0 0 0
16 49901894 Salomia Karpenko Bären Miroslav Karpenko Ja Ja Nein IMG_7786.jpg Nein Familie Karpenko denis.k88@web.de 4P7TJXJL 826081492713003 4 0 0
17 49654259 Jan Klyszcz Bären Christoph Klyszcz Ja Ja Familien- / Geschwisterfotos IMG_7859.jpg Nein Familie Klyszcz klyszcz.ewa92@gmail.com V9QQ3MHT 635050103722845 4 0 0
18 50220757 Personal Lang Bären Ja Ja Nein Nein Susanne Lang susi67.sl@gmail.com J2B9F4FH 84529853902827 0 0 0
19 49663258 Tuldi Lennart & Hannes Bären Ja Ja Familien- / Geschwisterfotos IMG_7915.jpg Nein Familie Tuldi olga_tuldi@yahoo.de Z7D4PJHV 628485247329265 10 0 0
20 49727295 Leonardo Liquori Bären Ja Ja Familien- / Geschwisterfotos IMG_7654.jpg Nein Elisa Mandelli e.mandelli1@icloud.com CZBSHZXD 112332574322427 3 0 0
21 49694659 Mara Schmid Bären Ja Ja Familien- / Geschwisterfotos IMG_7737.jpg Nein Familie Schmid izuther@googlemail.com M2QWP8PN 693636596918854 4 0 0
22 49553844 Niklas Schulze Bären Ja Ja Nein IMG_8027.jpg Nein Kristina Schulze m-k-ammersdorf@gmx.de Kristina Schulze kristina-anna-schulze@web.de TDJ47324 213569357182904 4 0 1
23 49605342 Zoe Seget Bären Ja Ja Familien- / Geschwisterfotos IMG_7764.jpg Nein Sandra Seget sandra.seget@hotmail.de GTY9QMWP 335161472735404 3 0 1
24 50211319 Valentin Slugocki Bären Ja Ja Nein Nein Bartek Slugocki bartek@slugocki.de 24632X5S 557733375991183 0 0 0
25 50219244 Hannes & Lennart Tuldi Bären Ja Ja Familien- / Geschwisterfotos IMG_7959.jpg Nein Familie Tuldi olga_tuldi@yahoo.de SZ7D82KL 195111473283743 9 0 1
26 49697372 Xaver Wego Bären Ja Ja Nein IMG_7883.jpg Nein Luisa Wego luisa.wego@web.deq Luisa Wego luisa.wego@web.de JT22FL8Y 470837065491819 0 6XJVMHVQ 423156711490859 6 1
27 49655774 Maximilian Wild Bären Ja Ja Familien- / Geschwisterfotos IMG_7977.jpg Nein Familie Wild wildramona@gmx.de XYCPRYX3 841975500351515 6 0 0
28 49613372 Anton Adelberger Bienen Ja Ja Nein IMG_8367.jpg Nein Catharina Adelberger catharina.adelberger@web.de GCSBWRRG 255252947890362 5 0 0
29 49655260 Ludwig Baumgartner Bienen Ja Ja Familien- / Geschwisterfotos Nein Franziska Baumgartner franziwild@gmx.de JJDBSW2Y 1346656807317 0 0 0
30 49652607 Josip Bungic Bienen Nein Ja Nein Nein Mirela Marijan Bungic m.bungic@web.de XR3LFNTW 896957772773017 1 0 0
31 50064781 Havin Cicek Bienen Ja Ja Familien- / Geschwisterfotos IMG_8198.jpg Nein Familie Cicek uelke.ardak@hotmail.de 8X8GYWR3 847511991706072 1 0 0
32 50055747 Mattea Fusarri Bienen Ja Ja Nein IMG_8151.jpg Nein Nadia Fusarri nadia.fusarri@gmx.de ZRGWQM3W 439731985455440 3 0 0
33 50247238 Maliya Gildner Bienen Ja Ja Nein IMG_8341.jpg Nein Alisa Gildner gildner31@gmail.com KRTVJ4M5 910013114016383 2 0 0
34 49825283 Kilian Hartl Bienen Ja Ja Familien- / Geschwisterfotos IMG_8410.jpg Nein Familie Schreibauer a.schreibauer@gmail.com NM92G8PK 534850382393461 3 0 0
35 50153154 Elara Carolina Hintermaier Bienen Ja Ja Nein IMG_8289.jpg Nein Adriana Hintermaier adri.shunka@gmail.com N6G67PPZ 233647866343524 1 0 0
36 49700913 Luka Loncar Bienen Ja Ja Nein IMG_8435.jpg Nein Szilvia Palinkas silvijapalinkas@yahoo.com 7FGK48GQ 345687401851686 5 0 1
37 50056989 Elias Minksz Bienen Ja Ja Familien- / Geschwisterfotos Nein Carolin Dirndorfer c.dirndorfer@gmx.de GZ3DQSPL 632143387747513 0 0 0
38 49770856 Anna Nguyen Bienen Ja Ja Nein IMG_8219.jpg Nein Thi Hien Minh Nguyen nthm30121996@gmail.com Anna Nguyen ging318@gmail.com CM9CMLBJ 122574286373832 2 0 0
39 50180008 Ilia Nickl Bienen Ja Ja Familien- / Geschwisterfotos IMG_8390.jpg Nein Familie Nickl cela990@hotmail.com KHPY6LQV 652142151577775 9 0 0
40 49575500 Mika Rubinstein Bienen Mia Rubinstein Ja Ja Familien- / Geschwisterfotos IMG_8548.jpg Nein Familie Rubinstein n.d.rubinstein@googlemail.com K7PX4J8Y 415300008608215 2 0 0
41 49652538 Alina Schillinger Bienen Ja Ja Familien- / Geschwisterfotos IMG_8244.jpg Nein Familie Schillinger schneggeno1@web.de X27P5L9Q 180935518874486 3 0 0
42 49663277 Malia Schlesinger Bienen Ja Ja Familien- / Geschwisterfotos IMG_8126.jpg Nein Familie Schlesinger stefanie2011@gmx.net 6672SN99 377539049099605 2 0 0
43 50257156 Marie Schöberl Bienen Ja Ja Nein IMG_8265.jpg Nein Michaela Schöberl michaela.schoeberl@gmx.de BKZWFCS4 504469516218803 2 0 0
44 50057519 Letizia Stachanczyk Bienen Leonardo Stachanczyk Ja Ja Familien- / Geschwisterfotos Nein Familie Stachanczyk Suzanna.Stachanczyk@web.de C7GX6BM2 268376387434609 0 0 0
45 49919594 Ela Torres Bienen Ja Ja Familien- / Geschwisterfotos IMG_8313.jpg Nein Familie Torres ftorrestapia@me.com YGL954RX 63682236385188 4 0 0
46 49837810 Maximilian Weber Bienen Ja Ja Familien- / Geschwisterfotos IMG_8522.jpg Nein Familie Weber mail.weber.melanie@googlemail.com 4QS8GPWR 7954462978689 3 0 0
47 50006492 Musa Yilmaz Bienen Ömer Yilmaz Ja Ja Familien- / Geschwisterfotos IMG_8466.jpg Nein Familie Yilmaz merve-ymz@hotmail.com 82YTD8FK 912359706713774 4 0 0
48 49652523 Nina Zhang Bienen Ja Ja Nein IMG_8174.jpg Nein Hua Zhang zhanghua0411@hotmail.com DTWR882P 201986576299456 3 0 1
49 49663112 Elias Bonifati Fische Ja Ja Familien- / Geschwisterfotos IMG_6891.jpg Nein Familie Misiano bonifati@hotmail.de BVMZFPHK 582378219071480 5 0 0
50 49652608 Mihael Bungic Fische Nein Ja Nein Nein Mirela Marijan Bungic m.bungic@web.de 2664S6D3 848993713584313 1 0 0
51 50248018 Alina Catak Fische Ja Ja Nein IMG_6424.jpg Nein Catak Admir catakadmir@gmail.com ML42KL42 532022002681433 7 0 1
52 50258123 Jakov Ceko Fische Ja Ja Nein IMG_7294.jpg Nein Familie Ceko brankoceko91@gmail.com CVBYPKBV 550993218062367 0 0 0
53 50258100 Luka Ceko Fische Ja Ja Nein IMG_7043.jpg Nein Familie Ceko brankoceko91@gmail.com DJKP3CBY 120492680122927 0 0 0
54 50222105 Saide Mira Cildir Fische Ja Ja Nein IMG_6617.jpg Nein Hasret Cildir hasretcildir@web.de M9B773FR 568587367870302 1 0 0
55 50218962 Valentin Gabauer Fische Ja Ja Nein IMG_7004.jpg Nein Sabine Gabauer sabine.gabauer@gmx.de QXWD3ZNS 377128045906117 2 0 1
56 50079276 Anika Gaßner Fische Ja Ja Familien- / Geschwisterfotos IMG_6555.jpg Nein Familie Karyakina veronika20@hotmail.de 3BSPHDHW 851569123151027 1 0 0
57 50211546 Kilian Glück Fische Ja Ja Familien- / Geschwisterfotos IMG_6960.jpg Nein Familie Glück katja_glueck@web.de 2LGTH3VN 783740741149373 2 0 0
58 50282221 Lisa Gumberger Fische Ja Ja Nein IMG_6470.jpg Nein Sarah Gumberger sarah.gumberger@gmx.de 2PHHTXT9 383948464807600 3 0 1
59 49616818 Nadine Hamed Fische Ja Ja Nein IMG_6648.jpg Nein Nadine sarasadaby@gmail.com V3KTTVNV 319851445592837 3 0 1
60 50208499 Noela Islami Fische Ja Ja Familien- / Geschwisterfotos IMG_6524.jpg Nein Familie Islami Zineta.islami@gmx.de CK5B7WWN 410922852620998 2 0 0
61 49901895 Miroslav Karpenko Fische Salomia Karpenko Ja Ja Nein IMG_6848.jpg Nein Familie Karpenko denis.k88@web.de 922GC8BH 915901506033102 1 0 0
62 50221939 Merjem Kaukovic Fische Ja Ja Nein IMG_6502.jpg Nein Edita Porcic-Kaukovic editta1996@hotmail.com KGVKW8HZ 305550439054156 1 0 0
63 50288355 Frauke Klinge Fische Nein Ja Nein Nein Frauke Klinge fs.klinge@t-online.de K4GCTQJG 948438313552204 0 0 0
64 49653461 Max Krämer Fische Ja Ja Nein IMG_6778.jpg Nein Michael Krämer m.k326@web.de PF5DCT5N 929652051737843 2 0 1
65 49663714 Casper Mettig Fische Ja Ja Familien- / Geschwisterfotos Nein Familie Roder stephanie.roder@gmail.com 4L2ZBL3M 296176221032781 6 0 0
66 50208680 Emre Mujic Fische Ja Ja Nein IMG_6927.jpg Nein Amra Mujic amra.mujic95@gmail.com QVBTGS73 16951665504680 2 0 0
67 49635375 Mila Nickl Fische Ja Ja Familien- / Geschwisterfotos IMG_6685.jpg Nein Familie Nickl cela990@hotmail.com S4Z3HPZY 736840096508400 8 0 0
68 49575499 Mia Rubinstein Fische Mika Rubinstein Ja Ja Familien- / Geschwisterfotos IMG_6443.jpg Nein Familie Rubinstein n.d.rubinstein@googlemail.com BGGHXNLC 908242966462743 3 0 0
69 49786711 Zoe Scholpp Fische Ja Ja Nein IMG_6721.jpg Nein Sabrina Scholpp sabrinasch1107@gmail.com CXMT4R9T 585861217320080 2 0 0
70 49755578 Valerie Schultze Fische Ja Ja Nein IMG_6743.jpg Nein Anita Schultze anitajuliane.schultze@gmail.com 4WVF24SR 499960849175102 2 0 0
71 50057518 Leonardo Stachanczyk Fische Letizia Stachanczyk Ja Ja Familien- / Geschwisterfotos IMG_7072.jpg Nein Familie Stachanczyk Suzanna.Stachanczyk@web.de BZC28W7T 900613948231467 7 0 0
72 50211898 Maya Watanabe Fische Ja Ja Nein IMG_6591.jpg Nein Barbara Watanabe barbara.j@live.de ZPVJ8R5Q 341636130475078 1 0 0
73 50006491 Ömer Yilmaz Fische Musa Yilmaz Ja Ja Familien- / Geschwisterfotos IMG_6809.jpg Nein Familie Yilmaz merve-ymz@hotmail.com C4NB42PX 659038103936299 4 0 0
74 50078572 Aurelia Adelsberger Spatzen Ja Ja Familien- / Geschwisterfotos IMG_7493.jpg Nein Familie Adelsberger barbara.adelsberger@yahoo.de Christian Godelmann floke.com@gmail.com 3NVRB2BM 230676178020824 1 0 0
75 50220084 Eymen Baldir Spatzen Ja Ja Nein IMG_6406.jpg Nein Seda Baldir seda.ay@icloud.com JDG5CDT8 381447885366279 1 0 0
76 49602297 Magdalena Bauer Spatzen Ja Ja Familien- / Geschwisterfotos IMG_7463.jpg Nein Familie Bauer bonprix29@yahoo.de V7W5ZPVY 411274063112493 8 0 0
77 49992146 Zoe Cajic Spatzen Ja Ja Nein IMG_7414.jpg Nein Amar Cajic amar.cajic@gmail.com JMZD8SNN 53184897942750 3 0 0
78 50057147 Mario Cakic Spatzen Ja Ja Nein IMG_7182.jpg Nein Lucija Zivkovic lucija.zivkovic16@gmail.com D8SZH5XL 508565079338980 3 0 1
79 50211538 Eray Dogan Spatzen Gökhan Dogan Ja Ja Familien- / Geschwisterfotos IMG_7380.jpg Nein Familie Dogan goeksel_dogan@web.de LMLH64KS 61030672835452 2 0 0
80 49552513 Antonia Freiwald Spatzen Ja Ja Nein IMG_7643.jpg Nein Stephanie Freiwald stephanie.freiwald@gmx.de 4BV76XQS 224897626646913 1 0 1
81 49601982 Heidi Götzberger Spatzen Ja Ja Familien- / Geschwisterfotos IMG_7618.jpg Nein Familie Götzberger franziska.lanzinger@t-online.de KSH3Y552 141723213815881 2 0 0
82 50063666 Una Hodzic Spatzen Ja Ja Nein IMG_7561.jpg Nein Hodžić Aldin menager21@hotmail.com 9W4CYMRX 170368609363861 4 0 1
83 49603438 Liara Honisch Spatzen Ja Ja Familien- / Geschwisterfotos IMG_7519.jpg Nein Familie Karayilan yasemin.karayilan@yahoo.de MSNCXQ77 787504756287604 2 0 0
84 49623482 Matteo Katterfeld Spatzen Ja Ja Familien- / Geschwisterfotos IMG_7347.jpg Nein Familie Ketterfeld madlen.katterfeld@gmx.de YJ9MM349 888691047601122 6 0 0
85 49654260 Christoph Klyszcz Spatzen Jan Klyszcz Ja Ja Familien- / Geschwisterfotos IMG_7320.jpg Nein Familie Klyszcz klyszcz.ewa92@gmail.com V3KSRPDM 389595058391936 6 0 0
86 50056841 Ludwig Lacen Spatzen Ja Ja Nein IMG_7246.jpg Nein Michael Lacen michael.lacen@gmx.de GJFNWHMY 205672235649590 2 0 1
87 50056690 Emilia Rodriguez Spatzen Ja Ja Nein IMG_7591.jpg Nein Daniela Rodriguez daniela-hinz-82@gmx.de 9LQLW7YV 289213156745302 1 0 1
88 49652595 Vaiana Slaiman Spatzen Ja Ja Nein Nein Slaiman hadeer94hasan@web.de YB24BVQR 230552964517174 0 0 0
89 49838169 Raphael Weber Spatzen Ja Ja Nein IMG_7214.jpg Nein Familie Weber mail.weber.melanie@googlemail.com W3VBKM3W 362639250675953 3 0 0
90 49906413 Ludwig Welz Spatzen Ja Ja Familien- / Geschwisterfotos IMG_7149.jpg Nein Familie Welz eva_welz@gmx.de VPJYZ48P 785597492180163 3 0 0
91 49726920 Amy Wieters Spatzen Ja Ja Nein IMG_7443.jpg Nein Janine Wieters janine28@gmx.de 69KFXLBD 921506261142206 4 0 1
92 50453287 Familie Adelsberger Ja Ja Nein IMG_0728.jpg Nein Familie Adelsberger barbara.adelsberger@yahoo.de JVXV9T2M 916908422224646 1 0 1
93 50451311 Familie Al Khadher Ja Ja Nein IMG_0153.jpg Nein Familie Al Khadher Husseinalkhadher8@gmail.com 4VTSN5J6 437618998198555 2 0 1
94 50453454 Familie Bauer Ja Ja Nein IMG_0353.jpg Nein Familie Bauer bonprix29@yahoo.de N8DZBDLW 169896993687826 2 0 0
95 50491788 Familie Baumgartner Ja Ja Nein IMG_0451.jpg Nein Franziska Baumgartner franziwild@gmx.de ZRH36VRS 847462383118786 2 0 1
96 50463803 Amla Bobo Ja Ja Nein IMG_7689.jpg Nein Xhulia Xhelci xhuliaxhelci@gmail.com C35CQ55V 950839783885570 1 0 0
97 50451304 Familie Ceko Ja Ja Nein IMG_0099.jpg Nein Familie Ceko brankoceko91@gmail.com 4NZXSHTW 798398153397116 2 0 0
98 50453898 Familie Cicek Ja Ja Nein IMG_2693.jpg Nein Familie Cicek uelke.ardak@hotmail.de YC9KVV76 290706892284805 3 0 1
99 50453136 Familie Dogan Ja Ja Nein IMG_9830.jpg Nein Familie Dogan goeksel_dogan@web.de GDBRDW6K 918773718877810 2 0 0
100 50453529 Familie Gabauer Ja Ja Nein IMG_0286.jpg Nein Familie Gabauer luzia.gabauer@web.de SQHNHMH6 49585341392454 6 0 1
101 50452028 Familie Glück Ja Ja Nein IMG_0406.jpg Nein Familie Glück katja_glueck@web.de 7WTXJNDC 628871407778415 2 0 0
102 50453448 Familie Götzberger Ja Ja Nein IMG_1262.jpg Nein Familie Götzberger franziska.lanzinger@t-online.de MPHRG7SP 954770009741299 2 0 1
103 50453434 Familie Islami Ja Ja Nein IMG_1330.jpg Nein Familie Islami Zineta.islami@gmx.de KF7CNCYZ 620178179159158 2 0 0
104 50452019 Familie Karayilan Ja Ja Nein IMG_0200.jpg Nein Familie Karayilan yasemin.karayilan@yahoo.de 6H73JV6B 765446752804075 1 0 0
105 50453439 Familie Karpenko Ja Ja Nein IMG_2825.jpg Nein Familie Karpenko denis.k88@web.de LR87SQ8C 963707649418838 1 0 0
106 50453495 Familie Karyakina Ja Ja Nein IMG_0800.jpg Nein Familie Karyakina veronika20@hotmail.de RYK4BQLQ 638219110542782 1 0 0
107 50453488 Familie Ketterfeld Ja Ja Nein IMG_1211.jpg Nein Familie Ketterfeld madlen.katterfeld@gmx.de PLX9G4V3 117752011222601 6 0 1
108 50453446 Familie Klyszcz Ja Ja Nein IMG_2740.jpg Nein Familie Klyszcz klyszcz.ewa92@gmail.com LZR8WFP9 874820410323668 9 0 1
109 50452151 Familie Misiano Ja Ja Nein IMG_1577.jpg Nein Familie Misiano bonifati@hotmail.de 7ZW9V666 394112462489259 5 0 1
110 50451284 Familie Nickl Ja Ja Nein IMG_1054.jpg Nein Familie Nickl cela990@hotmail.com 35CH589Q 438824910404667 8 0 1
111 50452230 Familie Roder Ja Ja Nein IMG_1138.jpg Nein Familie Roder stephanie.roder@gmail.com 848D3SWY 745487201848290 6 0 0
112 50452913 Familie Rubinstein Ja Ja Nein IMG_0041.jpg Nein Familie Rubinstein n.d.rubinstein@googlemail.com G9H8YFC4 180523151134386 1 0 1
113 50453768 Familie Schillinger Ja Ja Nein IMG_1404.jpg Nein Familie Schillinger schneggeno1@web.de SQJSP49C 593384265020703 3 0 1
114 50452236 Familie Schlesinger Ja Ja Nein IMG_1512.jpg Nein Familie Schlesinger stefanie2011@gmx.net 946G6HJH 269413107409936 3 0 1
115 50453894 Familie Schmid Ja Ja Nein IMG_0625.jpg Nein Familie Schmid izuther@googlemail.com W7W8P32C 486167508950250 4 0 1
116 50452248 Familie Schreibauer Ja Ja Nein IMG_0671.jpg Nein Familie Schreibauer a.schreibauer@gmail.com 97R4TRBC 130440825414681 3 0 0
117 50452273 Familie Stachanczyk Ja Ja Nein IMG_9974.jpg Nein Familie Stachanczyk Suzanna.Stachanczyk@web.de C334SSSL 733864213043388 5 0 0
118 50453485 Familie Torres Ja Ja Nein IMG_0967.jpg Nein Familie Torres ftorrestapia@me.com PCQ4CNV9 553742663210606 4 0 0
119 50452252 Familie Tuldi Ja Ja Nein IMG_2788.jpg Nein Familie Tuldi olga_tuldi@yahoo.de B99BYYYF 657381798122682 11 0 0
120 50452022 Familie Weber Ja Ja Nein IMG_0984.jpg Nein Familie Weber mail.weber.melanie@googlemail.com 7954G4C5 820357028620317 3 0 1
121 50451320 Familie Welz Ja Ja Nein IMG_1459.jpg Nein Familie Welz eva_welz@gmx.de 69N5WYFK 952025141929986 3 0 1
122 50452419 Familie Wild Ja Ja Nein IMG_0539.jpg Nein Familie Wild wildramona@gmx.de DVXKKJCZ 789239059675168 9 0 1
123 50452462 Familie Wolf Ja Ja Nein IMG_0841.jpg Nein Familie Wolf anjamichi77@gmail.com FXPLQYH9 784676508389646 1 0 0
124 50410050 Jonas Wolf Ja Ja Nein IMG_8489.jpg Nein Familie Wolf anjamichi77@gmail.com Q52NYB4N 18810570193338 0 0 0
125 50453882 Familie Yilmaz Ja Ja Nein IMG_9907.jpg Nein Familie Yilmaz merve-ymz@hotmail.com TXK86QSB 467856804734432 4 0 0
126 49655787 Joseph Wild Spatzen Ja Ja Nein IMG_7119.jpg Nein Familie Wild wildramona@gmx.de 8C56662R 226166391326912 5 0 0

25
check_db_links.py Normal file
View File

@@ -0,0 +1,25 @@
import sqlite3
import os
db_path = "/app/fotograf-de-scraper/backend/data/fotograf_jobs.db"
if not os.path.exists(db_path):
print(f"Database not found at {db_path}")
else:
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# Check candidates missing links for the current job
job_id = "576228454"
cursor.execute("""
SELECT COUNT(*)
FROM job_participants
WHERE job_id = ?
AND has_orders = 0
AND digital_package_ordered = 0
AND logins <= 5
AND quick_login_url IS NULL
""", (job_id,))
missing = cursor.fetchone()[0]
print(f"Missing links for candidates in job {job_id}: {missing}")
conn.close()

9
check_tables.py Normal file
View File

@@ -0,0 +1,9 @@
import sqlite3
db_path = "/app/fotograf-de-scraper/backend/data/fotograf_jobs.db"
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
tables = cursor.fetchall()
print(f"Tables: {[t[0] for t in tables]}")
conn.close()

View File

@@ -454,3 +454,52 @@ Der `company_explorer_connector.py` unterstützt nun den erweiterten Workflow:
| **Integration** | Lead-Engine (`book_slot`) prüft nun vor der Buchung gegen den CE-Bestand. |
**Vorteil:** Verhindert Dubletten in SuperOffice und ermöglicht die Wiederverwendung bestehender CRM-Datensätze für neue Leads.
## 🤖 Status-Update (2026-06-01 19:03 Berlin Time)
```yaml
Investierte Zeit in dieser Session: 00:16
Arbeitszusammenfassung:
Keine Zusammenfassung angegeben.
```
## 🤖 Status-Update (2026-06-01 23:01 Berlin Time)
```yaml
Investierte Zeit in dieser Session: 02:13
Arbeitszusammenfassung:
Keine Zusammenfassung angegeben.
```
## 🤖 Status-Update (2026-06-02 00:01 Berlin Time)
```yaml
Investierte Zeit in dieser Session: 01:00
Arbeitszusammenfassung:
Keine Zusammenfassung angegeben.
```
## 🤖 Status-Update (2026-06-03 01:33 Berlin Time)
```yaml
Investierte Zeit in dieser Session: 00:15
Arbeitszusammenfassung:
Keine Zusammenfassung angegeben.
```
## 🤖 Status-Update (2026-06-03 09:41 Berlin Time)
```yaml
Investierte Zeit in dieser Session: 00:15
Arbeitszusammenfassung:
✦ **Korrektur der Fortschrittsanzeige (Board-Präsentation) abgeschlossen:**
- **Präzise Scroll-Logik:** Die Fortschrittsanzeige wurde von der Bindung an den Body auf das gesamte Dokument () umgestellt und nutzt nun , um die tatsächliche Gesamthöhe inklusive Pinning-Offsets der Folie 0 exakt abzubilden.
- **Optimiertes C-Level Design:** Die Höhe des Fortschrittsbalkens wurde auf elegante 3px reduziert für einen subtileren, professionelleren Look.
- **Stabilitäts-Fix:** Das Skript wurde ans Ende der Initialisierung verschoben und ein automatischer Refresh nach dem Laden hinzugefügt, um Synchronisationsfehler bei dynamischen Scroll-Distanzen zu eliminieren.
- **Text-Feinschliff:** In der Einleitung (Folie 0) wurde das GTM-System präziser als wertvolles Nebenprodukt positioniert.
```

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,40 @@
# Development Insights: Sales Intelligence C-Level Story
Dieses Dokument fasst die strategischen und technischen Erkenntnisse zusammen, die zur aktuellen Version der "The Sales Loop" scrollytelling-Präsentation geführt haben.
## 1. Strategische Rahmung: "Ehrliche Pipeline"
Die wichtigste Erkenntnis war, dass das C-Level Datenbereinigung nicht als "Hausaufgabe" oder "Verlust" sehen darf.
* **Framing:** Das Löschen veralteter Deals ("Zombies") wurde als Gewinn an Transparenz und Fokus umgedeutet.
* **Ergebnis:** Der Fokus liegt auf dem **Aktiven Forecast (3,8 Mio. €)**. Die restlichen ~12 Mio. € Volumen wurden bewusst als 'ruhend' oder 'verloren' markiert, um die Präzision der Steuerung zu beweisen.
## 2. Der "Intelligence Loop" vs. Reporting
Ein klassisches Dashboard ist passiv. Die Präsentation muss den **Loop** zeigen:
* **Input:** CRM-Daten.
* **Analyse:** Dashboard identifiziert Stagnation (z.B. 1,3 Mio. € kritische Deals).
* **Aktion:** Sales-Manager steuern aktiv gegen.
* **Output:** Profitables Wachstum.
* *Insight:* C-Level interessiert sich weniger für die UI als für die Tatsache, dass die Maschine "alarmiert", bevor Deals sterben.
## 3. Architektur-Evolution: Synchrones Side-by-Side (v1.3)
Die größte technische Hürde war die Synchronität zwischen Narration und Dashboard-Beweis.
* **Problem:** Statische Hintergründe oder fixierte Layer führten zu "toten" Flächen beim Scrollen und Desynchronisation.
* **Lösung:** Umstellung auf ein **Zwei-Spalten-Modell**. Text (links) und Dashboard-Ausschnitt (rechts) wandern als feste Einheit beim Scrollen mit. Dies garantiert 100% Zuverlässigkeit und eine flüssige Storyline.
## 4. "Show, don't tell": Visual Evidence
Abstrakte Vorteile wie "Übersichtlichkeit" müssen visuell belegt werden.
* **Account-Grouping vs. Excel-Chaos:** Die Darstellung einer realen Account-Card (Bayernwerk Netz GmbH) beweist sofort den Mehrwert gegenüber 50 Excel-Spalten. Alle Dimensionen (Historie, Forecast, Tasks) sind an einem Ort gruppiert.
* **Drei-Ebenen-Logik:** Die Navigation wurde in Strategie (Header), Operation (Log) und Deep-Dive (Details) übersetzt, um die Komplexität für den Vorstand intuitiv abzubilden.
## 5. White Space: Die "Goldmine"
Die größte strategische Erkenntnis war die Bedeutung der Datenlücken.
* **75% Verlust-Quote:** 11.216 Accounts ohne Kontakte wurden nicht als Versäumnis, sondern als **"Wachstums-Rohstoff"** deklariert.
* **Argumentation:** Hier setzt der Intelligence Loop mit KI-Segmentierung an, um aus "toten" Accounts hochpräzise Leads zu generieren.
## 6. Operative Schlagzahl & Team-Update
Authentizität entsteht durch reale Namen und echte Daten.
* **Team-Bereinigung:** Ausscheidende Mitarbeiter wurden durch das aktive Sales-Team (Pierre Hollein, Ibrahim Akar, Sebastian Hosbach) ersetzt.
* **Puls-Check:** Die Darstellung von realen Terminen beweist, dass das Dashboard den echten Puls der Straße misst.
## 7. Technisches Design & Stabilität
* **Single-File Portability:** Alle JS-Bibliotheken (GSAP, ScrollTrigger) sind direkt eingebettet (Inlining), um maximale Offline-Stabilität für Präsentationen zu gewährleisten.
* **Progress Tracking:** Eine dezente Fortschrittsanzeige am oberen Rand gibt dem C-Level Orientierung über den Verlauf des Briefings.

View File

@@ -0,0 +1,28 @@
# Executive Briefing: Sales Intelligence Loop
Diese scrollytelling-basierte Präsentation dient dazu, dem C-Level die strategische Bedeutung der Datenbereinigung und der aktiven Steuerung näherzubringen.
## Key Narrative (Roter Faden)
1. **Intro: Sales Intelligence vs. Reporting**
* *Sprecher-Punkt:* "Wir zeigen heute nicht nur Zahlen, sondern wie wir die Maschine steuern."
2. **Die Strategie: Operation Pipeline**
* *Sprecher-Punkt:* "Ein CRM ist nur so viel wert wie seine Datenqualität. Wir haben 16 Mio. € 'ehrliche' Pipeline identifiziert."
3. **Das Cockpit: Früherkennung**
* *Sprecher-Punkt:* "Das System alarmiert uns proaktiv bei Stagnation (1,3 Mio. € kritisch). Wir agieren, bevor der Deal stirbt."
4. **Operative Schlagzahl: Die Termine**
* *Sprecher-Punkt:* "Hier sehen wir den Puls. Echte Termine, echte Kunden (Therme Erding, Samsung). Das ist die Basis für unseren Forecast."
5. **Das Potenzial: White Space**
* *Sprecher-Punkt:* "Hier liegt unser größter Hebel: 75% der Accounts haben noch keine Kontakte. Das ist kein Versäumnis, sondern unser Wachstums-Rohstoff für die KI-Segmentierung."
## Technische Hinweise
* **Bedienung:** Einfach mit dem Mausrad oder den Pfeiltasten scrollen. Die Animationen werden automatisch getriggert.
* **Offline-Modus:** Die Präsentation ist autark. Alle JavaScript-Bibliotheken (GSAP) befinden sich im Unterordner `lib/`.
* **Interaktion:** Der Button am Ende kann genutzt werden, um nahtlos in das echte Dashboard zu springen (muss ggf. verlinkt werden).
## Dateien
* `Sales_Intelligence_Cockpit_CLevel_v2.html` (Hauptdatei - v2.0)
* `NotebookLM_Background_Source.md` (Strategisches Hintergrund-Dossier für Podcast-Generierung)
* `lib/` (Lokal benötigte Skripte)
* `ROBOPLANET-Logo-2024_blue-white_4c.svg` (Logo)

View File

@@ -0,0 +1 @@
Git Push Test - Mon Jun 1 16:58:35 UTC 2026

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,70 @@
# Strategisches Hintergrund-Dossier: Sales Intelligence Cockpit (v2.0)
**Zentrales Source-Dokument für NotebookLM & Executive Briefing**
## 1. Vision & Strategischer Rahmen
Das Sales Intelligence Cockpit ist kein reines Reporting-Instrument ("Rückspiegel"), sondern eine operative Steuerungs-Zentrale ("Gaspedal"). Es wurde entwickelt, um die Lücke zwischen der massiven Datenmenge in SuperOffice und der täglichen Entscheidungsfindung im Vertrieb zu schließen.
**Zentraler Nordstern:**
*"Das Dashboard macht die Pipeline nicht größer. Es macht sie ehrlicher — und damit steuerbar."*
---
## 2. Die Folien-Logik & Daten-Fakten
### Slide 1: Die Ausgangslage (Verteilte Sichten)
* **Problem:** Daten waren in SuperOffice vorhanden, aber fragmentiert.
* **Kontext:** Um eine Übersicht über den Status eines Sales-Managers oder einer Region zu erhalten, mussten bisher mehrere Sichten in SuperOffice kombiniert oder manuelle Excel-Reports (Aufwand ca. 1h/Woche) erstellt werden.
* **Folge:** Wissen war personengebunden (Resilienz-Risiko) statt systemisch verfügbar.
### Slide 2: Die Lösung (Das Cockpit)
* **Konzept:** "Eine Seite statt zehn Klicks."
* **Funktion:** Bündelung von Account-Daten, Sale-Details, Aktivitäten-Stream und nächsten Schritten auf einer Oberfläche.
* **Hebel:** Direkte Deep-Links zurück ins CRM (SuperOffice) ermöglichen sofortiges Handeln ohne Suchzeiten.
### Slide 3: Sales Hygiene (Die Rote Liste)
* **Konzept:** 10 automatisierte Kontrollpunkte für Datenqualität (DQ).
* **Die 10 Punkte:**
1. **Alle Warnungen:** Aggregierter Status.
2. **Überfällig:** Abschlussdatum in der Vergangenheit.
3. **Fehlendes Startdatum:** Unklare Pipeline-Historie.
4. **Überfällige Aktivität:** Letzter Kontakt zu lange her.
5. **Demo ohne Follow-Up:** Kritischer Bruch in der "Winning DNA".
6. **Doppeltes Angebot:** Verwässerung des Forecasts.
7. **Fehlende Quelle:** Unklare Marketing-Attribution.
8. **Fehlende Vertragsart:** Erschwert die Umsatzplanung (Miete vs. Kauf).
9. **Won mit 0 EUR:** Datenfehler bei Abschluss.
10. **Falscher Status:** Logikfehler im Sales-Prozess.
### Slide 4: Performance (Vom Überblick zur Aktion)
* **Kennzahl:** **~20% Response Quote.**
* **Definition:** Erfasste Interaktion in SuperOffice innerhalb von 10 Tagen nach Ausgang einer systemgestützten E-Mail (z.B. einfache Rückrufbitte).
* **Beweis:** Getestet an ca. 70 Kontakten. Es belegt, dass datenbasierte "Micro-Touchpoints" echte Reaktionen erzeugen.
### Slide 5: Steuerung (Ehrliche Pipeline)
* **Kennzahl:** **+44% bereinigtes Lost-Volumen.**
* **Definition:** Vergleich des Volumens an "verlorenen" (geschlossenen) Deals.
* **Fakten:** Mai 2026 (2,052 Mio. €) vs. bisheriger Rekordwert März 2026 (1,422 Mio. €).
* **Strategischer Wert:** Dies ist kein Misserfolg, sondern ein massiver Gewinn an operativer Klarheit. Pipeline-Schulden werden abgebaut, der Forecast wird belastbar.
### Slide 6: Intelligence (Management Cockpit)
* **Habits:** Automatische Aufschlüsselung von KPIs pro Sales-Manager.
* **Beispiele:**
* **Sebastian Hosbach:** Fokus auf Pipeline-Stabilität (1,43 Mio. €) und Zykluszeiten (101 Tage).
* **Alexander Kiss:** Hohe Effizienz bei "Won"-Deals mit detaillierter Touchpoint-Analyse (32,9 Touchpoints/Deal).
* **Ziel:** Identifikation der "Winning DNA" (Was machen erfolgreiche Verkäufer anders?). Wir analysieren nicht nur das Ergebnis, sondern den Weg dorthin (Fahrtzeit vs. Abschlussquote).
### Slide 7: Potential (White Space)
* **Kennzahl:** **68% Accounts ohne Kontakte.**
* **Kontext:** 11.216 Roh-Accounts im CRM. Viele stammen aus automatisierten Lead-Quellen (Website-Besuche).
* **Strategie:** Hier liegt kein Versäumnis, sondern ein riesiger Wachstums-Rohstoff. Der nächste Schritt ist die systematische, KI-gestützte Anreicherung dieser Accounts mit Entscheider-Kontakten.
---
## 3. Executive Summary für den Podcast (NotebookLM)
* **Transformation:** RoboPlanet wechselt vom "Bauchgefühl-Vertrieb" zur datengestützten Präzision.
* **Kultur:** Wir fördern Transparenz ohne Schuldzuweisung. Eine "ehrliche Pipeline" ist das Fundament für Skalierung.
* **Resilienz:** Das Wissen liegt im System (Dashboard), nicht mehr in individuellen Excel-Tabellen.
* **Effizienz:** Reduktion von Suchzeiten und proaktive Alarmierung bei Stagnation führen zu einem schnelleren Sales-Cycle.
**Keyphrase für den Abschluss:**
"Wir sehen Vertrieb jetzt erstmals operativ steuerbar: transparent, priorisiert und handlungsfähig."

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 581.9 153.2">
<defs>
<style>
.cls-1 {
fill: none;
}
.cls-2 {
fill: #0b0a0d;
}
.cls-3 {
fill: #30bdea;
}
</style>
</defs>
<!-- Generator: Adobe Illustrator 28.7.5, SVG Export Plug-In . SVG Version: 1.2.0 Build 176) -->
<g>
<g id="Logo">
<path id="flow" class="cls-3" d="M67.5,22.2c8-3.9,17.6-.6,21.5,7.4,3.9,8,.6,17.6-7.4,21.5-8,3.9-17.6.6-21.5-7.4-3.9-8-.6-17.6,7.4-21.5ZM33.1,60.6c3.1,6.4,10.8,9,17.2,5.9,6.4-3.1,9-10.8,5.9-17.2-3.1-6.4-10.8-9-17.2-5.9-6.4,3.1-9,10.8-5.9,17.2ZM21.6,85.6c2.5,5.1,8.7,7.2,13.8,4.7,5.1-2.5,7.2-8.7,4.7-13.8-2.5-5.1-8.6-7.2-13.8-4.7-5.1,2.5-7.2,8.7-4.7,13.8ZM22.7,109.7c2,4.1,6.9,5.8,11,3.8,4.1-2,5.8-6.9,3.8-11-2-4.1-6.9-5.8-11-3.8-4.1,2-5.8,6.9-3.8,11ZM31.5,129c1.6,3.3,5.5,4.6,8.8,3,3.3-1.6,4.6-5.5,3-8.8-1.6-3.3-5.5-4.6-8.8-3-3.3,1.6-4.6,5.5-3,8.8Z"/>
<path class="cls-2" d="M97.9,124.8c0,.8-.4,1.2-1.2,1.2h-4.9c-.8,0-1.2-.4-1.2-1.2v-57.9h24.5c5.6,0,9.6,1.4,12,4.3,2.4,2.8,3.6,7,3.6,12.5v1.5c0,2-.2,3.8-.5,5.5-.3,1.7-.9,3.2-1.8,4.6-.8,1.4-1.9,2.5-3.3,3.5-1.4,1-3.1,1.7-5.1,2.2l10.6,24c.3.6,0,.9-.6.9h-5.6c-.8,0-1.2-.3-1.4-.9l-9.9-23h-15.1v22.7ZM123.1,83.5c0-3.4-.7-5.9-2-7.6-1.4-1.7-3.8-2.5-7.2-2.5h-16v22.7h16c1.2,0,2.3-.1,3.4-.4,1.1-.3,2.1-.8,2.9-1.7.9-.7,1.6-1.8,2.1-3.2.5-1.4.8-3.2.8-5.5v-1.9Z"/>
<path class="cls-2" d="M159.6,126.4c-3.8,0-7.1-.4-10-1.1-2.9-.7-5.2-2.2-7.1-4.3-1.9-2.1-3.3-5-4.3-8.9-1-3.8-1.5-8.9-1.5-15.2v-1c0-6.3.5-11.3,1.5-15.2,1-3.8,2.4-6.8,4.3-9,1.9-2.1,4.3-3.5,7.1-4.2,2.8-.7,6.2-1.1,10-1.1h.4c3.8,0,7.1.4,10,1.1,2.8.7,5.3,2.1,7.2,4.2,1.8,2.2,3.2,5.1,4.2,9,1,3.8,1.5,8.9,1.5,15.2v1c0,6.3-.5,11.3-1.5,15.2-1,3.8-2.4,6.8-4.2,8.9-2,2.2-4.4,3.6-7.2,4.3-2.8.7-6.2,1.1-10,1.1h-.4ZM160,119.8c2.7,0,5-.3,7-.8,1.9-.5,3.5-1.6,4.8-3.1,1.3-1.6,2.2-4,2.8-6.9.6-3,.9-7,.9-12.1v-1c0-5.1-.3-9.2-.9-12.2-.6-3-1.5-5.3-2.8-6.8-1.3-1.6-2.9-2.6-4.8-3.1-1.9-.5-4.3-.8-7-.8h-.4c-2.7,0-5,.3-6.9.8-1.9.5-3.5,1.6-4.8,3.1-1.3,1.6-2.2,3.9-2.8,6.8-.6,3-.9,7-.9,12.2v1c0,5.1.3,9.1.9,12.1.6,3,1.6,5.3,2.8,6.9,1.3,1.6,2.9,2.6,4.8,3.1,1.9.5,4.2.8,6.9.8h.4Z"/>
<path class="cls-2" d="M193.1,126c-1.4,0-2.1-.7-2.1-2.1v-55.3c0-1.1.6-1.7,1.7-1.7h19.4c10.3,0,15.4,5.1,15.4,15.2s-.8,6.6-2.3,8.9c-1.6,2.2-3.5,3.7-5.9,4.3,3.4.9,6,2.6,7.7,5.3,1.7,2.6,2.6,5.7,2.6,9.1,0,5.8-1.4,9.9-4.2,12.5-2.8,2.6-7,3.8-12.5,3.8h-19.8ZM198.4,92.8h13.9c2.4,0,4.3-.9,5.7-2.6,1.4-1.7,2.2-4,2.3-6.9.1-3.1-.5-5.5-1.9-7.2-1.4-1.7-3.6-2.6-6.4-2.6h-13.6v19.3ZM198.4,119.5h14.2c3.5,0,6.1-1,7.6-3,1.5-2,2.2-4.7,2.2-8s-.9-5.4-2.7-7.2c-1.8-1.8-4.1-2.7-7.1-2.7h-14.2v20.9Z"/>
<path class="cls-2" d="M259.1,126.4c-3.8,0-7.1-.4-10-1.1-2.9-.7-5.2-2.2-7.1-4.3-1.9-2.1-3.3-5-4.3-8.9-1-3.8-1.5-8.9-1.5-15.2v-1c0-6.3.5-11.3,1.5-15.2,1-3.8,2.4-6.8,4.3-9,1.9-2.1,4.3-3.5,7.1-4.2,2.8-.7,6.2-1.1,10-1.1h.4c3.8,0,7.1.4,10,1.1,2.8.7,5.3,2.1,7.2,4.2,1.8,2.2,3.2,5.1,4.2,9,1,3.8,1.5,8.9,1.5,15.2v1c0,6.3-.5,11.3-1.5,15.2-1,3.8-2.4,6.8-4.2,8.9-2,2.2-4.4,3.6-7.2,4.3-2.8.7-6.2,1.1-10,1.1h-.4ZM259.5,119.8c2.7,0,5-.3,7-.8,1.9-.5,3.5-1.6,4.8-3.1,1.3-1.6,2.2-4,2.8-6.9.6-3,.9-7,.9-12.1v-1c0-5.1-.3-9.2-.9-12.2-.6-3-1.5-5.3-2.8-6.8-1.3-1.6-2.9-2.6-4.8-3.1-1.9-.5-4.3-.8-7-.8h-.4c-2.7,0-5,.3-6.9.8-1.9.5-3.5,1.6-4.8,3.1-1.3,1.6-2.2,3.9-2.8,6.8-.6,3-.9,7-.9,12.2v1c0,5.1.3,9.1.9,12.1.6,3,1.6,5.3,2.8,6.9,1.3,1.6,2.9,2.6,4.8,3.1,1.9.5,4.2.8,6.9.8h.4Z"/>
<path class="cls-2" d="M297.3,124.8c0,.8-.4,1.2-1.2,1.2h-4.9c-.8,0-1.2-.4-1.2-1.2v-57.9h23.6c2.9,0,5.4.4,7.4,1.3,2,.9,3.6,2.1,4.8,3.8,1.2,1.6,2.1,3.5,2.6,5.8.5,2.2.8,4.8.8,7.6v1.2c0,2.7-.3,5.3-.9,7.6-.6,2.3-1.5,4.3-2.8,6-1.3,1.6-2.9,2.9-5,3.9-2.1.9-4.5,1.4-7.5,1.4h-15.6v19.3ZM321.5,85.2c0-1.5-.1-3-.4-4.4-.3-1.4-.7-2.6-1.4-3.7-.7-1.1-1.6-1.9-2.7-2.6-1.1-.7-2.6-1-4.3-1h-15.5v25.5h14.3c1.9,0,3.5-.3,4.8-.9,1.3-.6,2.3-1.5,3-2.6.8-1.1,1.3-2.4,1.6-3.9.3-1.5.5-3.1.5-4.8v-1.6Z"/>
<path class="cls-2" d="M342.3,66.9c.8,0,1.2.4,1.2,1.2v44.7c0,2.4.5,4.1,1.6,5.1,1.1,1,2.8,1.5,5,1.5h17.7c.8,0,1.2.4,1.2,1.2v4.1c0,.8-.4,1.2-1.2,1.2h-19c-4,0-7.1-.9-9.4-2.6-2.2-1.7-3.4-4.8-3.4-9.1v-46.2c0-.8.4-1.2,1.2-1.2h4.9Z"/>
<path class="cls-2" d="M391.3,70.3c.8-2.3,2.3-3.4,4.7-3.4h5.6c2.4,0,3.9,1.1,4.6,3.4l16.2,54.5c.3.8,0,1.1-.9,1.1h-5.5c-.7,0-1.1-.3-1.3-.9l-4.1-14h-23.5l-4.2,14c-.2.6-.6.9-1.2.9h-5.6c-.8,0-1.1-.4-.9-1.1l16.2-54.5ZM388.8,104.8h19.8l-8.8-30.3c-.2-.6-.5-.9-.9-.9h-.4c-.4,0-.7.3-.9.9l-8.8,30.3Z"/>
<path class="cls-2" d="M473.3,124.9c0,.8-.4,1.2-1.2,1.2h-4.9c-.8,0-1.2-.4-1.2-1.2v-39.5c0-2.4-.3-4.4-1-5.9-.7-1.6-1.7-2.8-2.9-3.7-1.2-.9-2.7-1.5-4.5-1.9-1.8-.4-3.7-.6-5.9-.6h-.9c-2.2,0-4.1.2-5.9.6-1.8.4-3.3,1-4.5,1.9-1.2.9-2.2,2.1-2.9,3.7-.7,1.6-1,3.5-1,5.9v39.5c0,.8-.4,1.2-1.2,1.2h-4.9c-.8,0-1.2-.4-1.2-1.2v-39.5c0-3.7.5-6.7,1.5-9.1,1-2.4,2.5-4.3,4.4-5.7,1.9-1.4,4.2-2.4,6.9-3,2.7-.6,5.7-.9,9-.9h.9c3.4,0,6.4.3,9.1.9,2.7.6,5,1.6,6.8,3,1.9,1.4,3.3,3.3,4.3,5.7,1,2.4,1.5,5.4,1.5,9.1v39.5Z"/>
<path class="cls-2" d="M512.5,66.9c.8,0,1.1.4,1.1,1.2v4.2c0,.8-.4,1.1-1.1,1.1h-18c-2,0-3.6.5-4.8,1.6-1.1,1-1.7,2.7-1.7,5v13.2h20.8c.8,0,1.2.4,1.2,1.1v4.2c0,.8-.4,1.1-1.2,1.1h-20.8v13.2c0,2.4.5,4.1,1.6,5.1,1.1,1,2.8,1.5,5,1.5h17.8c.8,0,1.1.4,1.1,1.2v4.1c0,.8-.4,1.2-1.1,1.2h-19.1c-4,0-7.1-.9-9.4-2.6-2.2-1.7-3.4-4.8-3.4-9.1v-35.2c0-3.8,1-6.8,3.1-8.9,2.1-2.2,5-3.2,8.8-3.2h19.9Z"/>
<path class="cls-2" d="M537.5,126c-.8,0-1.2-.4-1.2-1.2v-51.1h-16.5c-.8,0-1.1-.4-1.1-1.1v-4.4c0-.8.4-1.2,1.1-1.2h40.5c.8,0,1.1.4,1.1,1.2v4.4c0,.8-.4,1.1-1.1,1.1h-16.5v51.1c0,.8-.4,1.2-1.2,1.2h-4.9Z"/>
</g>
<g id="SCHUZTRAUM_-_mittlerer_Buble">
<path class="cls-1" d="M1,85.6c2.5,5.1,8.7,7.2,13.8,4.7,5.1-2.5,7.2-8.7,4.7-13.8-2.5-5.1-8.6-7.2-13.8-4.7-5.1,2.5-7.2,8.7-4.7,13.8Z"/>
<path class="cls-1" d="M28.3,147.4c2.5,5.1,8.7,7.2,13.8,4.7,5.1-2.5,7.2-8.7,4.7-13.8-2.5-5.1-8.6-7.2-13.8-4.7-5.1,2.5-7.2,8.7-4.7,13.8Z"/>
<path class="cls-1" d="M65.4,14.8c2.5,5.1,8.7,7.2,13.8,4.7,5.1-2.5,7.2-8.7,4.7-13.8-2.5-5.1-8.6-7.2-13.8-4.7-5.1,2.5-7.2,8.7-4.7,13.8Z"/>
<path class="cls-1" d="M562.3,14.9c2.5,5.1,8.7,7.2,13.8,4.7,5.1-2.5,7.2-8.7,4.7-13.8-2.5-5.1-8.6-7.2-13.8-4.7-5.1,2.5-7.2,8.7-4.7,13.8Z"/>
<path class="cls-1" d="M562.3,147.3c2.5,5.1,8.7,7.2,13.8,4.7,5.1-2.5,7.2-8.7,4.7-13.8-2.5-5.1-8.6-7.2-13.8-4.7-5.1,2.5-7.2,8.7-4.7,13.8Z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 581.9 153.2">
<defs>
<style>
.cls-1 {
fill: #fff;
}
.cls-2 {
fill: none;
}
.cls-3 {
fill: #30bdea;
}
</style>
</defs>
<!-- Generator: Adobe Illustrator 28.7.5, SVG Export Plug-In . SVG Version: 1.2.0 Build 176) -->
<g>
<g id="Logo">
<path id="flow" class="cls-3" d="M67.5,22.2c8-3.9,17.6-.6,21.5,7.4,3.9,8,.6,17.6-7.4,21.5-8,3.9-17.6.6-21.5-7.4-3.9-8-.6-17.6,7.4-21.5ZM33.1,60.6c3.1,6.4,10.8,9,17.2,5.9,6.4-3.1,9-10.8,5.9-17.2-3.1-6.4-10.8-9-17.2-5.9-6.4,3.1-9,10.8-5.9,17.2ZM21.6,85.6c2.5,5.1,8.7,7.2,13.8,4.7,5.1-2.5,7.2-8.7,4.7-13.8-2.5-5.1-8.6-7.2-13.8-4.7-5.1,2.5-7.2,8.7-4.7,13.8ZM22.7,109.7c2,4.1,6.9,5.8,11,3.8,4.1-2,5.8-6.9,3.8-11-2-4.1-6.9-5.8-11-3.8-4.1,2-5.8,6.9-3.8,11ZM31.5,129c1.6,3.3,5.5,4.6,8.8,3,3.3-1.6,4.6-5.5,3-8.8-1.6-3.3-5.5-4.6-8.8-3-3.3,1.6-4.6,5.5-3,8.8Z"/>
<path class="cls-1" d="M97.9,124.8c0,.8-.4,1.2-1.2,1.2h-4.9c-.8,0-1.2-.4-1.2-1.2v-57.9h24.5c5.6,0,9.6,1.4,12,4.3,2.4,2.8,3.6,7,3.6,12.5v1.5c0,2-.2,3.8-.5,5.5-.3,1.7-.9,3.2-1.8,4.6-.8,1.4-1.9,2.5-3.3,3.5-1.4,1-3.1,1.7-5.1,2.2l10.6,24c.3.6,0,.9-.6.9h-5.6c-.8,0-1.2-.3-1.4-.9l-9.9-23h-15.1v22.7ZM123.1,83.5c0-3.4-.7-5.9-2-7.6-1.4-1.7-3.8-2.5-7.2-2.5h-16v22.7h16c1.2,0,2.3-.1,3.4-.4,1.1-.3,2.1-.8,2.9-1.7.9-.7,1.6-1.8,2.1-3.2.5-1.4.8-3.2.8-5.5v-1.9Z"/>
<path class="cls-1" d="M159.6,126.4c-3.8,0-7.1-.4-10-1.1-2.9-.7-5.2-2.2-7.1-4.3-1.9-2.1-3.3-5-4.3-8.9-1-3.8-1.5-8.9-1.5-15.2v-1c0-6.3.5-11.3,1.5-15.2,1-3.8,2.4-6.8,4.3-9,1.9-2.1,4.3-3.5,7.1-4.2,2.8-.7,6.2-1.1,10-1.1h.4c3.8,0,7.1.4,10,1.1,2.8.7,5.3,2.1,7.2,4.2,1.8,2.2,3.2,5.1,4.2,9,1,3.8,1.5,8.9,1.5,15.2v1c0,6.3-.5,11.3-1.5,15.2-1,3.8-2.4,6.8-4.2,8.9-2,2.2-4.4,3.6-7.2,4.3-2.8.7-6.2,1.1-10,1.1h-.4ZM160,119.8c2.7,0,5-.3,7-.8,1.9-.5,3.5-1.6,4.8-3.1,1.3-1.6,2.2-4,2.8-6.9.6-3,.9-7,.9-12.1v-1c0-5.1-.3-9.2-.9-12.2-.6-3-1.5-5.3-2.8-6.8-1.3-1.6-2.9-2.6-4.8-3.1-1.9-.5-4.3-.8-7-.8h-.4c-2.7,0-5,.3-6.9.8-1.9.5-3.5,1.6-4.8,3.1-1.3,1.6-2.2,3.9-2.8,6.8-.6,3-.9,7-.9,12.2v1c0,5.1.3,9.1.9,12.1.6,3,1.6,5.3,2.8,6.9,1.3,1.6,2.9,2.6,4.8,3.1,1.9.5,4.2.8,6.9.8h.4Z"/>
<path class="cls-1" d="M193.1,126c-1.4,0-2.1-.7-2.1-2.1v-55.3c0-1.1.6-1.7,1.7-1.7h19.4c10.3,0,15.4,5.1,15.4,15.2s-.8,6.6-2.3,8.9c-1.6,2.2-3.5,3.7-5.9,4.3,3.4.9,6,2.6,7.7,5.3,1.7,2.6,2.6,5.7,2.6,9.1,0,5.8-1.4,9.9-4.2,12.5-2.8,2.6-7,3.8-12.5,3.8h-19.8ZM198.4,92.8h13.9c2.4,0,4.3-.9,5.7-2.6,1.4-1.7,2.2-4,2.3-6.9.1-3.1-.5-5.5-1.9-7.2-1.4-1.7-3.6-2.6-6.4-2.6h-13.6v19.3ZM198.4,119.5h14.2c3.5,0,6.1-1,7.6-3,1.5-2,2.2-4.7,2.2-8s-.9-5.4-2.7-7.2c-1.8-1.8-4.1-2.7-7.1-2.7h-14.2v20.9Z"/>
<path class="cls-1" d="M259.1,126.4c-3.8,0-7.1-.4-10-1.1-2.9-.7-5.2-2.2-7.1-4.3-1.9-2.1-3.3-5-4.3-8.9-1-3.8-1.5-8.9-1.5-15.2v-1c0-6.3.5-11.3,1.5-15.2,1-3.8,2.4-6.8,4.3-9,1.9-2.1,4.3-3.5,7.1-4.2,2.8-.7,6.2-1.1,10-1.1h.4c3.8,0,7.1.4,10,1.1,2.8.7,5.3,2.1,7.2,4.2,1.8,2.2,3.2,5.1,4.2,9,1,3.8,1.5,8.9,1.5,15.2v1c0,6.3-.5,11.3-1.5,15.2-1,3.8-2.4,6.8-4.2,8.9-2,2.2-4.4,3.6-7.2,4.3-2.8.7-6.2,1.1-10,1.1h-.4ZM259.5,119.8c2.7,0,5-.3,7-.8,1.9-.5,3.5-1.6,4.8-3.1,1.3-1.6,2.2-4,2.8-6.9.6-3,.9-7,.9-12.1v-1c0-5.1-.3-9.2-.9-12.2-.6-3-1.5-5.3-2.8-6.8-1.3-1.6-2.9-2.6-4.8-3.1-1.9-.5-4.3-.8-7-.8h-.4c-2.7,0-5,.3-6.9.8-1.9.5-3.5,1.6-4.8,3.1-1.3,1.6-2.2,3.9-2.8,6.8-.6,3-.9,7-.9,12.2v1c0,5.1.3,9.1.9,12.1.6,3,1.6,5.3,2.8,6.9,1.3,1.6,2.9,2.6,4.8,3.1,1.9.5,4.2.8,6.9.8h.4Z"/>
<path class="cls-1" d="M297.3,124.8c0,.8-.4,1.2-1.2,1.2h-4.9c-.8,0-1.2-.4-1.2-1.2v-57.9h23.6c2.9,0,5.4.4,7.4,1.3,2,.9,3.6,2.1,4.8,3.8,1.2,1.6,2.1,3.5,2.6,5.8.5,2.2.8,4.8.8,7.6v1.2c0,2.7-.3,5.3-.9,7.6-.6,2.3-1.5,4.3-2.8,6-1.3,1.6-2.9,2.9-5,3.9-2.1.9-4.5,1.4-7.5,1.4h-15.6v19.3ZM321.5,85.2c0-1.5-.1-3-.4-4.4-.3-1.4-.7-2.6-1.4-3.7-.7-1.1-1.6-1.9-2.7-2.6-1.1-.7-2.6-1-4.3-1h-15.5v25.5h14.3c1.9,0,3.5-.3,4.8-.9,1.3-.6,2.3-1.5,3-2.6.8-1.1,1.3-2.4,1.6-3.9.3-1.5.5-3.1.5-4.8v-1.6Z"/>
<path class="cls-1" d="M342.3,66.9c.8,0,1.2.4,1.2,1.2v44.7c0,2.4.5,4.1,1.6,5.1,1.1,1,2.8,1.5,5,1.5h17.7c.8,0,1.2.4,1.2,1.2v4.1c0,.8-.4,1.2-1.2,1.2h-19c-4,0-7.1-.9-9.4-2.6-2.2-1.7-3.4-4.8-3.4-9.1v-46.2c0-.8.4-1.2,1.2-1.2h4.9Z"/>
<path class="cls-1" d="M391.3,70.3c.8-2.3,2.3-3.4,4.7-3.4h5.6c2.4,0,3.9,1.1,4.6,3.4l16.2,54.5c.3.8,0,1.1-.9,1.1h-5.5c-.7,0-1.1-.3-1.3-.9l-4.1-14h-23.5l-4.2,14c-.2.6-.6.9-1.2.9h-5.6c-.8,0-1.1-.4-.9-1.1l16.2-54.5ZM388.8,104.8h19.8l-8.8-30.3c-.2-.6-.5-.9-.9-.9h-.4c-.4,0-.7.3-.9.9l-8.8,30.3Z"/>
<path class="cls-1" d="M473.3,124.9c0,.8-.4,1.2-1.2,1.2h-4.9c-.8,0-1.2-.4-1.2-1.2v-39.5c0-2.4-.3-4.4-1-5.9-.7-1.6-1.7-2.8-2.9-3.7-1.2-.9-2.7-1.5-4.5-1.9-1.8-.4-3.7-.6-5.9-.6h-.9c-2.2,0-4.1.2-5.9.6-1.8.4-3.3,1-4.5,1.9-1.2.9-2.2,2.1-2.9,3.7-.7,1.6-1,3.5-1,5.9v39.5c0,.8-.4,1.2-1.2,1.2h-4.9c-.8,0-1.2-.4-1.2-1.2v-39.5c0-3.7.5-6.7,1.5-9.1,1-2.4,2.5-4.3,4.4-5.7,1.9-1.4,4.2-2.4,6.9-3,2.7-.6,5.7-.9,9-.9h.9c3.4,0,6.4.3,9.1.9,2.7.6,5,1.6,6.8,3,1.9,1.4,3.3,3.3,4.3,5.7,1,2.4,1.5,5.4,1.5,9.1v39.5Z"/>
<path class="cls-1" d="M512.5,66.9c.8,0,1.1.4,1.1,1.2v4.2c0,.8-.4,1.1-1.1,1.1h-18c-2,0-3.6.5-4.8,1.6-1.1,1-1.7,2.7-1.7,5v13.2h20.8c.8,0,1.2.4,1.2,1.1v4.2c0,.8-.4,1.1-1.2,1.1h-20.8v13.2c0,2.4.5,4.1,1.6,5.1,1.1,1,2.8,1.5,5,1.5h17.8c.8,0,1.1.4,1.1,1.2v4.1c0,.8-.4,1.2-1.1,1.2h-19.1c-4,0-7.1-.9-9.4-2.6-2.2-1.7-3.4-4.8-3.4-9.1v-35.2c0-3.8,1-6.8,3.1-8.9,2.1-2.2,5-3.2,8.8-3.2h19.9Z"/>
<path class="cls-1" d="M537.5,126c-.8,0-1.2-.4-1.2-1.2v-51.1h-16.5c-.8,0-1.1-.4-1.1-1.1v-4.4c0-.8.4-1.2,1.1-1.2h40.5c.8,0,1.1.4,1.1,1.2v4.4c0,.8-.4,1.1-1.1,1.1h-16.5v51.1c0,.8-.4,1.2-1.2,1.2h-4.9Z"/>
</g>
<g id="SCHUZTRAUM_-_mittlerer_Buble">
<path class="cls-2" d="M1,85.6c2.5,5.1,8.7,7.2,13.8,4.7,5.1-2.5,7.2-8.7,4.7-13.8-2.5-5.1-8.6-7.2-13.8-4.7-5.1,2.5-7.2,8.7-4.7,13.8Z"/>
<path class="cls-2" d="M28.3,147.4c2.5,5.1,8.7,7.2,13.8,4.7,5.1-2.5,7.2-8.7,4.7-13.8-2.5-5.1-8.6-7.2-13.8-4.7-5.1,2.5-7.2,8.7-4.7,13.8Z"/>
<path class="cls-2" d="M65.4,14.8c2.5,5.1,8.7,7.2,13.8,4.7,5.1-2.5,7.2-8.7,4.7-13.8-2.5-5.1-8.6-7.2-13.8-4.7-5.1,2.5-7.2,8.7-4.7,13.8Z"/>
<path class="cls-2" d="M562.3,14.9c2.5,5.1,8.7,7.2,13.8,4.7,5.1-2.5,7.2-8.7,4.7-13.8-2.5-5.1-8.6-7.2-13.8-4.7-5.1,2.5-7.2,8.7-4.7,13.8Z"/>
<path class="cls-2" d="M562.3,147.3c2.5,5.1,8.7,7.2,13.8,4.7,5.1-2.5,7.2-8.7,4.7-13.8-2.5-5.1-8.6-7.2-13.8-4.7-5.1,2.5-7.2,8.7-4.7,13.8Z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.2 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#0a5e58" viewBox="0 0 256 256"><path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24ZM74.08,197.5a64,64,0,0,1,107.84,0,87.83,87.83,0,0,1-107.84,0ZM96,120a32,32,0,1,1,32,32A32,32,0,0,1,96,120Zm97.76,66.41a79.66,79.66,0,0,0-36.06-28.75,48,48,0,1,0-59.4,0,79.66,79.66,0,0,0-36.06,28.75,88,88,0,1,1,131.52,0Z"></path></svg>

After

Width:  |  Height:  |  Size: 400 B

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,430 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sales Cockpit: Intelligence & Yield Briefing</title>
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Oxanium:wght@400;700;800&family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
roboDark: '#081734',
roboLight: '#DDEEFE',
roboCyan: '#00E5FF',
roboYellow: '#FFD42C',
roboLogo: '#30BDEA'
},
fontFamily: {
'head': ['Oxanium', 'sans-serif'],
'body': ['Poppins', 'sans-serif']
}
}
}
}
</script>
<style>
body {
background-color: #081734;
color: #DDEEFE;
font-family: 'Poppins', sans-serif;
overflow-x: hidden;
line-height: 1.6;
}
h1, h2, h3, h4 { font-family: 'Oxanium', sans-serif; font-weight: 700; }
.glass-card {
background: rgba(221, 238, 254, 0.03);
backdrop-filter: blur(20px);
border: 1px solid rgba(0, 229, 255, 0.1);
border-radius: 24px;
}
.section-screen {
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
padding: 4rem 2rem;
}
/* Toby Styles */
#toby-fixed {
position: fixed;
bottom: 30px;
right: 30px;
width: 160px;
z-index: 100;
opacity: 0;
pointer-events: none;
transition: opacity 0.5s ease;
}
.toby-float { animation: float 6s ease-in-out infinite; }
@keyframes float {
0% { transform: translateY(0px) rotate(0deg); }
50% { transform: translateY(-15px) rotate(1deg); }
100% { transform: translateY(0px) rotate(0deg); }
}
#toby-bubble {
background: #DDEEFE;
color: #081734;
padding: 0.75rem 1rem;
border-radius: 12px;
font-size: 0.85rem;
font-weight: 700;
margin-bottom: 12px;
position: relative;
text-align: center;
box-shadow: 0 10px 30px rgba(0,229,255,0.3);
min-width: 180px;
}
#toby-bubble::after {
content: '';
position: absolute;
bottom: -8px;
left: 50%;
transform: translateX(-50%);
border-width: 8px 8px 0;
border-style: solid;
border-color: #DDEEFE transparent transparent transparent;
}
.text-gradient {
background: linear-gradient(135deg, #FFF 20%, #00E5FF 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.reveal { opacity: 0; transform: translateY(30px); }
/* Progress Bar */
#progress-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 4px;
background: rgba(255,255,255,0.05);
z-index: 1000;
}
#progress-bar {
height: 100%;
width: 0%;
background: #00E5FF;
box-shadow: 0 0 10px #00E5FF;
}
/* Charts Mocks */
.chart-bar {
height: 0;
transition: height 1.5s cubic-bezier(0.17, 0.67, 0.83, 0.67);
}
.reveal.active .chart-bar { height: var(--h); }
</style>
</head>
<body class="antialiased">
<div id="progress-container">
<div id="progress-bar"></div>
</div>
<!-- Toby Fixed -->
<div id="toby-fixed" class="toby-float">
<div id="toby-bubble">Initialisiere Cockpit...</div>
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAB9AAAAc2CAYAAABaJTzmAAAACXBIWXMAABsRAAAbEQEEnGAvAAAEvmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFjY2V0IGJlZ2luPSfvu78nIGlkPSdXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQnPz4KPHg6eG1wbWV0YSB4bWxuczp4PSdhZG9iZTpuczptZXRhLyc+CjxyZGY6UkRGIHhtbG5zOnJkZj0naHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyc+CgogPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9JycKICB4bWxuczpBdHRyaWI9J2h0dHA6Ly9ucy5hdHRyaWJ1dGlvbi5jb20vYWRzLzEuMC8nPgogIDxBdHRyaWI6QWRzPgogICA8cmRmOlNlcT4KICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0nUmVzb3VyY2UnPgogICAgIDxBdHRyaWI6Q3JlYXRlZD4yMDI2LTAxLTExPC9BdHRyaWI6Q3JlYXRlZD4KICAgICA8QXR0cmliOkV4dExtZD4zZjE1OTM5Ny04MWM3LTQyM2ItOWViMy02ODYyYzAxZjM4NGI8L0F0dHJpYjpFeHRJZD4KICAgICA8QXR0cmliOkZiSWQ+NTI1MjY1OTE0MTc5NTgwPC9BdHRyaWI6RmJJZD4KICAgICA8QXR0cmliOlRvdWNoVHlwZT4yPC9BdHRyaWI6VG91Y2hUeXBlPgogICAgPC9yZGY6bGk+CiAgIDwvcmRmOlNlcT4KICB8L0F0dHJpYjpBZHM+CiA8L3JkZjpEZXNjcmlwdGlvbj4KCiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0nJwogIHhtbG5zOmRjPSdodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyc+CiAgPGRjOnRpdGxlPgogICA8cmRmOkFsdD4KICAgIDxyZGY6bGkgeG1sOmxhbmc9J3gtZGVmYXVsdCc+Um9ib1BsYW5ldF9BdmF0YXJfaGVsbGJsYXUuYWkgLSAxPC9yZGY6bGk+CiAgIDwvcmRmOkFsdD4KICA8L2RjOnRpdGxlPgogPC9yZGY6RGVzY3JpcHRpb24+CgogPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9JycKICB4bWxuczpwZGY9J2h0dHA6Ly9ucy5hZG9iZS5jb20vcGRmLzEuMy8nPgogIDxwZGY6QXV0aG9yPldhY2tsZXJHcm91cDwvcGRmOkF1dGhvcm4+CiA8L3JkZjpEZXNjcmlwdGlvbj4KCiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0nJwogIHhtbG5zOnhtcD0naHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyc+CiAgPHhtcDpDcmVhdG9yVG9vbD5DYW52YSBkb2M9REFHLVN1Y0tTeXMgdXNlcj1VQUVUOG1KY3lhSSBicmFuZD1CQUVUOGd0OERsTSB0ZW1wbGF0ZT08L3htcDpDcmVhdG9yVG9vbD4KIDwvcmRmOkRlc2NyaXB0aW9uPgo8L3JkZjpSREY+CjwveDp4bXBtZXRhPgo8P3hwYWNrZXQgZW5kPSdyJz8+qsA63gAACAASURBVHic7N15uG5z/fj/zx+/6/urvqU5aVCSSFGhyJjMZAghY6aMoQxJEioaKBkjkTEJERWJFAopY6nOWvc+xzmpnNa695n4616v73qvc/o0knOcc9733vvxvK7HtU/lCnvf+17v4V7v9T//I0mSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmS... [truncated]" alt="Toby" class="w-full opacity-80">
</div>
<!-- 1. Intro Screen -->
<section class="section-screen">
<div class="max-w-4xl w-full text-center">
<h4 class="text-roboCyan uppercase tracking-[0.5em] mb-8 reveal text-sm">Board Briefing Q2 / 2026</h4>
<h1 class="text-7xl md:text-8xl font-black mb-10 reveal leading-none tracking-tighter">Sales <br> <span class="text-gradient uppercase">Cockpit</span></h1>
<p class="text-xl md:text-2xl text-roboLight/40 max-w-2xl mx-auto mb-16 reveal font-light">
Vom statischen Archiv zum aktiven Gaspedal. <br>
Echtzeit-Steuerung für Roboplanet.
</p>
<div class="flex justify-center gap-6 reveal">
<div class="h-[1px] w-12 bg-roboCyan/40 self-center"></div>
<span class="text-roboCyan font-mono uppercase text-xs tracking-widest">30 Tage Live-Betrieb</span>
<div class="h-[1px] w-12 bg-roboCyan/40 self-center"></div>
</div>
</div>
</section>
<!-- 2. Efficiency: Adieu Excel -->
<section class="section-screen bg-black/10">
<div class="max-w-5xl w-full">
<div class="grid grid-cols-1 md:grid-cols-2 gap-20 items-center">
<div class="reveal">
<h2 class="text-5xl mb-8 leading-tight">Die Stunde, <br> die wir uns <span class="text-roboCyan">zurückholen.</span></h2>
<p class="text-xl text-roboLight/60 font-light leading-relaxed mb-10">
Früher: Manuelle Excel-Listen, wöchentliche Datenpflege, Blindflug. <br><br>
Heute: <strong>Vollautomatisches Dashboard.</strong> Der Innendienst spart 4-6 Stunden pro Monat Zeit, die nun direkt in den Verkauf fließt.
</p>
<div class="flex gap-4">
<div class="glass-card px-6 py-4 border-roboCyan/30">
<span class="text-3xl font-bold text-white">100%</span>
<p class="text-[10px] uppercase tracking-widest text-roboCyan mt-1">Automatisierung</p>
</div>
<div class="glass-card px-6 py-4 border-roboCyan/30">
<span class="text-3xl font-bold text-white">-1h</span>
<p class="text-[10px] uppercase tracking-widest text-roboCyan mt-1">pro Woche / Sales</p>
</div>
</div>
</div>
<div class="reveal relative">
<div class="glass-card p-8 rotate-3 shadow-2xl bg-white/5 border-white/10">
<div class="space-y-4 opacity-20">
<div class="h-4 w-full bg-white/40 rounded"></div>
<div class="h-4 w-3/4 bg-white/40 rounded"></div>
<div class="h-4 w-full bg-white/40 rounded"></div>
</div>
<div class="absolute inset-0 flex items-center justify-center">
<span class="text-roboCyan text-8xl font-black rotate-[-15deg] drop-shadow-[0_0_15px_rgba(0,229,255,0.8)]">EXCEL ADIEU</span>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- 3. Winning DNA: Deep Insights -->
<section class="section-screen">
<div class="max-w-6xl w-full">
<div class="text-center mb-20 reveal">
<h2 class="text-6xl mb-6 uppercase tracking-tighter">Die Winning <span class="text-roboCyan">DNA</span></h2>
<p class="text-xl text-roboLight/40 font-light">Was SuperOffice uns verschweigt, macht das Dashboard sichtbar.</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-12">
<!-- Channel Chart -->
<div class="glass-card p-10 reveal">
<h4 class="text-lg mb-8 uppercase tracking-widest text-roboCyan">Top Kanäle (Konversion)</h4>
<div class="flex items-end justify-between h-64 gap-4 px-4">
<div class="flex flex-col items-center flex-1 gap-2">
<div class="chart-bar w-full bg-roboCyan rounded-t-lg shadow-[0_0_20px_rgba(0,229,255,0.3)]" style="--h: 85%;"></div>
<span class="text-[10px] uppercase tracking-widest text-roboLight/50 mt-2">LinkedIn</span>
</div>
<div class="flex flex-col items-center flex-1 gap-2">
<div class="chart-bar w-full bg-roboCyan/60 rounded-t-lg" style="--h: 45%;"></div>
<span class="text-[10px] uppercase tracking-widest text-roboLight/50 mt-2">Cold Call</span>
</div>
<div class="flex flex-col items-center flex-1 gap-2">
<div class="chart-bar w-full bg-roboCyan/80 rounded-t-lg" style="--h: 65%;"></div>
<span class="text-[10px] uppercase tracking-widest text-roboLight/50 mt-2">Email MA</span>
</div>
<div class="flex flex-col items-center flex-1 gap-2">
<div class="chart-bar w-full bg-roboCyan/40 rounded-t-lg" style="--h: 30%;"></div>
<span class="text-[10px] uppercase tracking-widest text-roboLight/50 mt-2">Referral</span>
</div>
</div>
</div>
<!-- Loss Analysis -->
<div class="glass-card p-10 reveal">
<h4 class="text-lg mb-8 uppercase tracking-widest text-roboCyan">Warum wir verlieren</h4>
<div class="space-y-6">
<div>
<div class="flex justify-between text-xs mb-2 uppercase tracking-widest">
<span>Budget / Preis</span>
<span class="text-roboCyan">42%</span>
</div>
<div class="h-2 w-full bg-white/5 rounded-full overflow-hidden">
<div class="h-full bg-roboCyan chart-bar" style="--h: 100%; width: 42%;"></div>
</div>
</div>
<div>
<div class="flex justify-between text-xs mb-2 uppercase tracking-widest">
<span>Timing (Projekt verschoben)</span>
<span class="text-roboCyan">28%</span>
</div>
<div class="h-2 w-full bg-white/5 rounded-full overflow-hidden">
<div class="h-full bg-roboCyan/70 chart-bar" style="--h: 100%; width: 28%;"></div>
</div>
</div>
<div>
<div class="flex justify-between text-xs mb-2 uppercase tracking-widest">
<span>Kein Bedarf (Infrastruktur)</span>
<span class="text-roboCyan">15%</span>
</div>
<div class="h-2 w-full bg-white/5 rounded-full overflow-hidden">
<div class="h-full bg-roboCyan/40 chart-bar" style="--h: 100%; width: 15%;"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- 4. Action Hub: From View to Do -->
<section class="section-screen bg-roboCyan/[0.02]">
<div class="max-w-5xl w-full">
<div class="grid grid-cols-1 md:grid-cols-2 gap-20 items-center">
<div class="reveal">
<h4 class="text-roboCyan font-mono uppercase tracking-[0.3em] text-xs mb-6 font-bold">Der Activity Hub</h4>
<h2 class="text-6xl mb-8 leading-tight">Kein Rückspiegel. <br> Ein <span class="text-gradient">Gaspedal.</span></h2>
<p class="text-xl text-roboLight/70 font-light mb-10 leading-relaxed">
Das Dashboard ermöglicht direkte Interaktion. <br><br>
Highlight: Die <strong>Rückrufbitte-Automation</strong>. Mit einem Klick wird eine personalisierte Nachricht versendet.
</p>
<div class="glass-card p-10 border-roboCyan shadow-[0_0_30px_rgba(0,229,255,0.1)]">
<div class="text-7xl font-black text-white mb-2 tracking-tighter">>20%</div>
<p class="text-xs uppercase tracking-[0.4em] font-bold text-roboCyan">Response-Rate</p>
<p class="text-sm text-roboLight/50 mt-6 leading-relaxed">Der Beweis: Einfache, datenbasierte Touchpoints schlagen komplexe Kampagnen.</p>
</div>
</div>
<div class="reveal">
<div class="glass-card p-12 border-white/10 relative overflow-hidden">
<div class="absolute top-0 right-0 p-4">
<span class="text-roboCyan/20 text-8xl font-black"></span>
</div>
<p class="text-xl text-roboLight leading-relaxed mb-8 italic font-light relative z-10">
"Früher habe ich 10 Minuten gesucht, heute brauche ich 10 Sekunden für die gleiche Entscheidung. Das Dashboard ist mein neues Zuhause im Vertrieb."
</p>
<div class="flex items-center gap-4">
<div class="w-12 h-12 rounded-full bg-gradient-to-br from-roboCyan to-roboLogo"></div>
<div>
<p class="text-sm font-bold uppercase tracking-widest">Stimme aus dem Team</p>
<p class="text-[10px] text-roboLight/40 uppercase">Sales Manager / Innendienst</p>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- 5. Red List: Early Warning -->
<section class="section-screen bg-black/20">
<div class="max-w-4xl w-full text-center">
<h2 class="text-5xl mb-10 reveal">Die Rote Liste: <br> <span class="text-red-500 uppercase font-black tracking-tighter">Gefahr erkannt.</span></h2>
<div class="glass-card p-12 reveal border-red-500/20 bg-red-500/5">
<p class="text-2xl text-white font-light leading-relaxed mb-8">
Deals, die länger als 14 Tage stagnieren, werden automatisch markiert. <br>
<strong>Das Dashboard wird zum Frühwarnsystem.</strong>
</p>
<div class="flex justify-center gap-12">
<div class="text-center">
<div class="text-4xl font-bold text-red-500">14</div>
<p class="text-[10px] uppercase tracking-widest text-white/40">Tage Stagnation</p>
</div>
<div class="text-center border-l border-white/10 pl-12">
<div class="text-4xl font-bold text-red-500">Aktion</div>
<p class="text-[10px] uppercase tracking-widest text-white/40">Sofort-Trigger</p>
</div>
</div>
</div>
</div>
</section>
<!-- 6. Summary -->
<section class="section-screen">
<div class="max-w-4xl w-full text-center">
<h4 class="text-roboCyan uppercase tracking-[0.5em] mb-12 reveal text-xs">Zusammenfassung</h4>
<h2 class="text-6xl md:text-8xl mb-12 reveal font-black tracking-tighter">Sales <span class="text-gradient">IQ.</span></h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-8 text-left reveal">
<div class="border-l-2 border-roboCyan pl-6">
<h5 class="font-bold mb-2">Transparenz</h5>
<p class="text-xs text-roboLight/50">Alle KPIs auf einer Seite. Keine Suche mehr.</p>
</div>
<div class="border-l-2 border-roboCyan pl-6">
<h5 class="font-bold mb-2">Geschwindigkeit</h5>
<p class="text-xs text-roboLight/50">Direkte Interaktion statt Daten-Administration.</p>
</div>
<div class="border-l-2 border-roboCyan pl-6">
<h5 class="font-bold mb-2">Skalierbarkeit</h5>
<p class="text-xs text-roboLight/50">Robuste Basis für die Expansion 2026.</p>
</div>
</div>
</div>
</section>
<footer class="py-20 text-center opacity-30 text-xs tracking-[0.5em] uppercase">
Roboplanet &copy; 2026 | Intelligence Driven Sales
</footer>
<!-- Scripts -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js"></script>
<script>
gsap.registerPlugin(ScrollTrigger);
const tobyFixed = document.getElementById('toby-fixed');
const tobyBubble = document.getElementById('toby-bubble');
const progressBar = document.getElementById('progress-bar');
const tobyMessages = [
"System bereit. Cockpit geladen.",
"Excel ist Geschichte. Live-Daten sind die Zukunft.",
"Hier sehen wir die DNA unseres Erfolgs.",
"Aktion schlägt Analyse. >20% Quote!",
"Frühwarnsystem aktiv. Keine Deals mehr verlieren.",
"Sales IQ: Wir sind bereit zum Skalieren."
];
function updateToby(index) {
if (tobyMessages[index]) {
tobyBubble.innerHTML = tobyMessages[index];
gsap.fromTo(tobyBubble, { scale: 0.8, opacity: 0 }, { scale: 1, opacity: 1, duration: 0.4, ease: "back.out(1.7)" });
}
}
// Reveal Animations
gsap.utils.toArray('.reveal').forEach((el, i) => {
gsap.to(el, {
scrollTrigger: {
trigger: el,
start: "top 90%",
onEnter: () => el.classList.add('active'),
toggleActions: "play none none reverse"
},
opacity: 1,
y: 0,
duration: 1,
ease: "power3.out"
});
});
// Toby Visibility & Updates
ScrollTrigger.create({
trigger: "body",
start: "100px top",
onEnter: () => gsap.to(tobyFixed, { opacity: 1, display: 'flex', duration: 0.5 }),
onLeaveBack: () => gsap.to(tobyFixed, { opacity: 0, duration: 0.5 })
});
const sections = gsap.utils.toArray('section');
sections.forEach((section, i) => {
ScrollTrigger.create({
trigger: section,
start: "top center",
onEnter: () => updateToby(i),
onEnterBack: () => updateToby(i)
});
});
// Progress Bar
gsap.to(progressBar, {
width: "100%",
ease: "none",
scrollTrigger: {
trigger: "body",
start: "top top",
end: "bottom bottom",
scrub: 0.3
}
});
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

11
docs/Praesentation/lib/gsap.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@@ -0,0 +1,430 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sales Cockpit: Intelligence & Yield Briefing</title>
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Oxanium:wght@400;700;800&family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
roboDark: '#081734',
roboLight: '#DDEEFE',
roboCyan: '#00E5FF',
roboYellow: '#FFD42C',
roboLogo: '#30BDEA'
},
fontFamily: {
'head': ['Oxanium', 'sans-serif'],
'body': ['Poppins', 'sans-serif']
}
}
}
}
</script>
<style>
body {
background-color: #081734;
color: #DDEEFE;
font-family: 'Poppins', sans-serif;
overflow-x: hidden;
line-height: 1.6;
}
h1, h2, h3, h4 { font-family: 'Oxanium', sans-serif; font-weight: 700; }
.glass-card {
background: rgba(221, 238, 254, 0.03);
backdrop-filter: blur(20px);
border: 1px solid rgba(0, 229, 255, 0.1);
border-radius: 24px;
}
.section-screen {
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
padding: 4rem 2rem;
}
/* Toby Styles */
#toby-fixed {
position: fixed;
bottom: 30px;
right: 30px;
width: 160px;
z-index: 100;
opacity: 0;
pointer-events: none;
transition: opacity 0.5s ease;
}
.toby-float { animation: float 6s ease-in-out infinite; }
@keyframes float {
0% { transform: translateY(0px) rotate(0deg); }
50% { transform: translateY(-15px) rotate(1deg); }
100% { transform: translateY(0px) rotate(0deg); }
}
#toby-bubble {
background: #DDEEFE;
color: #081734;
padding: 0.75rem 1rem;
border-radius: 12px;
font-size: 0.85rem;
font-weight: 700;
margin-bottom: 12px;
position: relative;
text-align: center;
box-shadow: 0 10px 30px rgba(0,229,255,0.3);
min-width: 180px;
}
#toby-bubble::after {
content: '';
position: absolute;
bottom: -8px;
left: 50%;
transform: translateX(-50%);
border-width: 8px 8px 0;
border-style: solid;
border-color: #DDEEFE transparent transparent transparent;
}
.text-gradient {
background: linear-gradient(135deg, #FFF 20%, #00E5FF 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.reveal { opacity: 0; transform: translateY(30px); }
/* Progress Bar */
#progress-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 4px;
background: rgba(255,255,255,0.05);
z-index: 1000;
}
#progress-bar {
height: 100%;
width: 0%;
background: #00E5FF;
box-shadow: 0 0 10px #00E5FF;
}
/* Charts Mocks */
.chart-bar {
height: 0;
transition: height 1.5s cubic-bezier(0.17, 0.67, 0.83, 0.67);
}
.reveal.active .chart-bar { height: var(--h); }
</style>
</head>
<body class="antialiased">
<div id="progress-container">
<div id="progress-bar"></div>
</div>
<!-- Toby Fixed -->
<div id="toby-fixed" class="toby-float">
<div id="toby-bubble">Initialisiere Cockpit...</div>
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAB9AAAAc2CAYAAABaJTzmAAAACXBIWXMAABsRAAAbEQEEnGAvAAAEvmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFjY2V0IGJlZ2luPSfvu78nIGlkPSdXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQnPz4KPHg6eG1wbWV0YSB4bWxuczp4PSdhZG9iZTpuczptZXRhLyc+CjxyZGY6UkRGIHhtbG5zOnJkZj0naHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyc+CgogPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9JycKICB4bWxuczpBdHRyaWI9J2h0dHA6Ly9ucy5hdHRyaWJ1dGlvbi5jb20vYWRzLzEuMC8nPgogIDxBdHRyaWI6QWRzPgogICA8cmRmOlNlcT4KICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0nUmVzb3VyY2UnPgogICAgIDxBdHRyaWI6Q3JlYXRlZD4yMDI2LTAxLTExPC9BdHRyaWI6Q3JlYXRlZD4KICAgICA8QXR0cmliOkV4dExtZD4zZjE1OTM5Ny04MWM3LTQyM2ItOWViMy02ODYyYzAxZjM4NGI8L0F0dHJpYjpFeHRJZD4KICAgICA8QXR0cmliOkZiSWQ+NTI1MjY1OTE0MTc5NTgwPC9BdHRyaWI6RmJJZD4KICAgICA8QXR0cmliOlRvdWNoVHlwZT4yPC9BdHRyaWI6VG91Y2hUeXBlPgogICAgPC9yZGY6bGk+CiAgIDwvcmRmOlNlcT4KICB8L0F0dHJpYjpBZHM+CiA8L3JkZjpEZXNjcmlwdGlvbj4KCiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0nJwogIHhtbG5zOmRjPSdodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyc+CiAgPGRjOnRpdGxlPgogICA8cmRmOkFsdD4KICAgIDxyZGY6bGkgeG1sOmxhbmc9J3gtZGVmYXVsdCc+Um9ib1BsYW5ldF9BdmF0YXJfaGVsbGJsYXUuYWkgLSAxPC9yZGY6bGk+CiAgIDwvcmRmOkFsdD4KICA8L2RjOnRpdGxlPgogPC9yZGY6RGVzY3JpcHRpb24+CgogPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9JycKICB4bWxuczpwZGY9J2h0dHA6Ly9ucy5hZG9iZS5jb20vcGRmLzEuMy8nPgogIDxwZGY6QXV0aG9yPldhY2tsZXJHcm91cDwvcGRmOkF1dGhvcm4+CiA8L3JkZjpEZXNjcmlwdGlvbj4KCiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0nJwogIHhtbG5zOnhtcD0naHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyc+CiAgPHhtcDpDcmVhdG9yVG9vbD5DYW52YSBkb2M9REFHLVN1Y0tTeXMgdXNlcj1VQUVUOG1KY3lhSSBicmFuZD1CQUVUOGd0OERsTSB0ZW1wbGF0ZT08L3htcDpDcmVhdG9yVG9vbD4KIDwvcmRmOkRlc2NyaXB0aW9uPgo8L3JkZjpSREY+CjwveDp4bXBtZXRhPgo8P3hwYWNrZXQgZW5kPSdyJz8+qsA63gAACAASURBVHic7N15uG5z/fj/zx+/6/urvqU5aVCSSFGhyJjMZAghY6aMoQxJEioaKBkjkTEJERWJFAopY6nOWvc+xzmpnNa695n4616v73qvc/o0knOcc9733vvxvK7HtU/lCnvf+17v4V7v9T//I0mSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmS... [truncated]" alt="Toby" class="w-full opacity-80">
</div>
<!-- 1. Intro Screen -->
<section class="section-screen">
<div class="max-w-4xl w-full text-center">
<h4 class="text-roboCyan uppercase tracking-[0.5em] mb-8 reveal text-sm">Board Briefing Q2 / 2026</h4>
<h1 class="text-7xl md:text-8xl font-black mb-10 reveal leading-none tracking-tighter">Sales <br> <span class="text-gradient uppercase">Cockpit</span></h1>
<p class="text-xl md:text-2xl text-roboLight/40 max-w-2xl mx-auto mb-16 reveal font-light">
Vom statischen Archiv zum aktiven Gaspedal. <br>
Echtzeit-Steuerung für Roboplanet.
</p>
<div class="flex justify-center gap-6 reveal">
<div class="h-[1px] w-12 bg-roboCyan/40 self-center"></div>
<span class="text-roboCyan font-mono uppercase text-xs tracking-widest">30 Tage Live-Betrieb</span>
<div class="h-[1px] w-12 bg-roboCyan/40 self-center"></div>
</div>
</div>
</section>
<!-- 2. Efficiency: Adieu Excel -->
<section class="section-screen bg-black/10">
<div class="max-w-5xl w-full">
<div class="grid grid-cols-1 md:grid-cols-2 gap-20 items-center">
<div class="reveal">
<h2 class="text-5xl mb-8 leading-tight">Die Stunde, <br> die wir uns <span class="text-roboCyan">zurückholen.</span></h2>
<p class="text-xl text-roboLight/60 font-light leading-relaxed mb-10">
Früher: Manuelle Excel-Listen, wöchentliche Datenpflege, Blindflug. <br><br>
Heute: <strong>Vollautomatisches Dashboard.</strong> Der Innendienst spart 4-6 Stunden pro Monat Zeit, die nun direkt in den Verkauf fließt.
</p>
<div class="flex gap-4">
<div class="glass-card px-6 py-4 border-roboCyan/30">
<span class="text-3xl font-bold text-white">100%</span>
<p class="text-[10px] uppercase tracking-widest text-roboCyan mt-1">Automatisierung</p>
</div>
<div class="glass-card px-6 py-4 border-roboCyan/30">
<span class="text-3xl font-bold text-white">-1h</span>
<p class="text-[10px] uppercase tracking-widest text-roboCyan mt-1">pro Woche / Sales</p>
</div>
</div>
</div>
<div class="reveal relative">
<div class="glass-card p-8 rotate-3 shadow-2xl bg-white/5 border-white/10">
<div class="space-y-4 opacity-20">
<div class="h-4 w-full bg-white/40 rounded"></div>
<div class="h-4 w-3/4 bg-white/40 rounded"></div>
<div class="h-4 w-full bg-white/40 rounded"></div>
</div>
<div class="absolute inset-0 flex items-center justify-center">
<span class="text-roboCyan text-8xl font-black rotate-[-15deg] drop-shadow-[0_0_15px_rgba(0,229,255,0.8)]">EXCEL ADIEU</span>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- 3. Winning DNA: Deep Insights -->
<section class="section-screen">
<div class="max-w-6xl w-full">
<div class="text-center mb-20 reveal">
<h2 class="text-6xl mb-6 uppercase tracking-tighter">Die Winning <span class="text-roboCyan">DNA</span></h2>
<p class="text-xl text-roboLight/40 font-light">Was SuperOffice uns verschweigt, macht das Dashboard sichtbar.</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-12">
<!-- Channel Chart -->
<div class="glass-card p-10 reveal">
<h4 class="text-lg mb-8 uppercase tracking-widest text-roboCyan">Top Kanäle (Konversion)</h4>
<div class="flex items-end justify-between h-64 gap-4 px-4">
<div class="flex flex-col items-center flex-1 gap-2">
<div class="chart-bar w-full bg-roboCyan rounded-t-lg shadow-[0_0_20px_rgba(0,229,255,0.3)]" style="--h: 85%;"></div>
<span class="text-[10px] uppercase tracking-widest text-roboLight/50 mt-2">LinkedIn</span>
</div>
<div class="flex flex-col items-center flex-1 gap-2">
<div class="chart-bar w-full bg-roboCyan/60 rounded-t-lg" style="--h: 45%;"></div>
<span class="text-[10px] uppercase tracking-widest text-roboLight/50 mt-2">Cold Call</span>
</div>
<div class="flex flex-col items-center flex-1 gap-2">
<div class="chart-bar w-full bg-roboCyan/80 rounded-t-lg" style="--h: 65%;"></div>
<span class="text-[10px] uppercase tracking-widest text-roboLight/50 mt-2">Email MA</span>
</div>
<div class="flex flex-col items-center flex-1 gap-2">
<div class="chart-bar w-full bg-roboCyan/40 rounded-t-lg" style="--h: 30%;"></div>
<span class="text-[10px] uppercase tracking-widest text-roboLight/50 mt-2">Referral</span>
</div>
</div>
</div>
<!-- Loss Analysis -->
<div class="glass-card p-10 reveal">
<h4 class="text-lg mb-8 uppercase tracking-widest text-roboCyan">Warum wir verlieren</h4>
<div class="space-y-6">
<div>
<div class="flex justify-between text-xs mb-2 uppercase tracking-widest">
<span>Budget / Preis</span>
<span class="text-roboCyan">42%</span>
</div>
<div class="h-2 w-full bg-white/5 rounded-full overflow-hidden">
<div class="h-full bg-roboCyan chart-bar" style="--h: 100%; width: 42%;"></div>
</div>
</div>
<div>
<div class="flex justify-between text-xs mb-2 uppercase tracking-widest">
<span>Timing (Projekt verschoben)</span>
<span class="text-roboCyan">28%</span>
</div>
<div class="h-2 w-full bg-white/5 rounded-full overflow-hidden">
<div class="h-full bg-roboCyan/70 chart-bar" style="--h: 100%; width: 28%;"></div>
</div>
</div>
<div>
<div class="flex justify-between text-xs mb-2 uppercase tracking-widest">
<span>Kein Bedarf (Infrastruktur)</span>
<span class="text-roboCyan">15%</span>
</div>
<div class="h-2 w-full bg-white/5 rounded-full overflow-hidden">
<div class="h-full bg-roboCyan/40 chart-bar" style="--h: 100%; width: 15%;"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- 4. Action Hub: From View to Do -->
<section class="section-screen bg-roboCyan/[0.02]">
<div class="max-w-5xl w-full">
<div class="grid grid-cols-1 md:grid-cols-2 gap-20 items-center">
<div class="reveal">
<h4 class="text-roboCyan font-mono uppercase tracking-[0.3em] text-xs mb-6 font-bold">Der Activity Hub</h4>
<h2 class="text-6xl mb-8 leading-tight">Kein Rückspiegel. <br> Ein <span class="text-gradient">Gaspedal.</span></h2>
<p class="text-xl text-roboLight/70 font-light mb-10 leading-relaxed">
Das Dashboard ermöglicht direkte Interaktion. <br><br>
Highlight: Die <strong>Rückrufbitte-Automation</strong>. Mit einem Klick wird eine personalisierte Nachricht versendet.
</p>
<div class="glass-card p-10 border-roboCyan shadow-[0_0_30px_rgba(0,229,255,0.1)]">
<div class="text-7xl font-black text-white mb-2 tracking-tighter">>20%</div>
<p class="text-xs uppercase tracking-[0.4em] font-bold text-roboCyan">Response-Rate</p>
<p class="text-sm text-roboLight/50 mt-6 leading-relaxed">Der Beweis: Einfache, datenbasierte Touchpoints schlagen komplexe Kampagnen.</p>
</div>
</div>
<div class="reveal">
<div class="glass-card p-12 border-white/10 relative overflow-hidden">
<div class="absolute top-0 right-0 p-4">
<span class="text-roboCyan/20 text-8xl font-black"></span>
</div>
<p class="text-xl text-roboLight leading-relaxed mb-8 italic font-light relative z-10">
"Früher habe ich 10 Minuten gesucht, heute brauche ich 10 Sekunden für die gleiche Entscheidung. Das Dashboard ist mein neues Zuhause im Vertrieb."
</p>
<div class="flex items-center gap-4">
<div class="w-12 h-12 rounded-full bg-gradient-to-br from-roboCyan to-roboLogo"></div>
<div>
<p class="text-sm font-bold uppercase tracking-widest">Stimme aus dem Team</p>
<p class="text-[10px] text-roboLight/40 uppercase">Sales Manager / Innendienst</p>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- 5. Red List: Early Warning -->
<section class="section-screen bg-black/20">
<div class="max-w-4xl w-full text-center">
<h2 class="text-5xl mb-10 reveal">Die Rote Liste: <br> <span class="text-red-500 uppercase font-black tracking-tighter">Gefahr erkannt.</span></h2>
<div class="glass-card p-12 reveal border-red-500/20 bg-red-500/5">
<p class="text-2xl text-white font-light leading-relaxed mb-8">
Deals, die länger als 14 Tage stagnieren, werden automatisch markiert. <br>
<strong>Das Dashboard wird zum Frühwarnsystem.</strong>
</p>
<div class="flex justify-center gap-12">
<div class="text-center">
<div class="text-4xl font-bold text-red-500">14</div>
<p class="text-[10px] uppercase tracking-widest text-white/40">Tage Stagnation</p>
</div>
<div class="text-center border-l border-white/10 pl-12">
<div class="text-4xl font-bold text-red-500">Aktion</div>
<p class="text-[10px] uppercase tracking-widest text-white/40">Sofort-Trigger</p>
</div>
</div>
</div>
</div>
</section>
<!-- 6. Summary -->
<section class="section-screen">
<div class="max-w-4xl w-full text-center">
<h4 class="text-roboCyan uppercase tracking-[0.5em] mb-12 reveal text-xs">Zusammenfassung</h4>
<h2 class="text-6xl md:text-8xl mb-12 reveal font-black tracking-tighter">Sales <span class="text-gradient">IQ.</span></h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-8 text-left reveal">
<div class="border-l-2 border-roboCyan pl-6">
<h5 class="font-bold mb-2">Transparenz</h5>
<p class="text-xs text-roboLight/50">Alle KPIs auf einer Seite. Keine Suche mehr.</p>
</div>
<div class="border-l-2 border-roboCyan pl-6">
<h5 class="font-bold mb-2">Geschwindigkeit</h5>
<p class="text-xs text-roboLight/50">Direkte Interaktion statt Daten-Administration.</p>
</div>
<div class="border-l-2 border-roboCyan pl-6">
<h5 class="font-bold mb-2">Skalierbarkeit</h5>
<p class="text-xs text-roboLight/50">Robuste Basis für die Expansion 2026.</p>
</div>
</div>
</div>
</section>
<footer class="py-20 text-center opacity-30 text-xs tracking-[0.5em] uppercase">
Roboplanet &copy; 2026 | Intelligence Driven Sales
</footer>
<!-- Scripts -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js"></script>
<script>
gsap.registerPlugin(ScrollTrigger);
const tobyFixed = document.getElementById('toby-fixed');
const tobyBubble = document.getElementById('toby-bubble');
const progressBar = document.getElementById('progress-bar');
const tobyMessages = [
"System bereit. Cockpit geladen.",
"Excel ist Geschichte. Live-Daten sind die Zukunft.",
"Hier sehen wir die DNA unseres Erfolgs.",
"Aktion schlägt Analyse. >20% Quote!",
"Frühwarnsystem aktiv. Keine Deals mehr verlieren.",
"Sales IQ: Wir sind bereit zum Skalieren."
];
function updateToby(index) {
if (tobyMessages[index]) {
tobyBubble.innerHTML = tobyMessages[index];
gsap.fromTo(tobyBubble, { scale: 0.8, opacity: 0 }, { scale: 1, opacity: 1, duration: 0.4, ease: "back.out(1.7)" });
}
}
// Reveal Animations
gsap.utils.toArray('.reveal').forEach((el, i) => {
gsap.to(el, {
scrollTrigger: {
trigger: el,
start: "top 90%",
onEnter: () => el.classList.add('active'),
toggleActions: "play none none reverse"
},
opacity: 1,
y: 0,
duration: 1,
ease: "power3.out"
});
});
// Toby Visibility & Updates
ScrollTrigger.create({
trigger: "body",
start: "100px top",
onEnter: () => gsap.to(tobyFixed, { opacity: 1, display: 'flex', duration: 0.5 }),
onLeaveBack: () => gsap.to(tobyFixed, { opacity: 0, duration: 0.5 })
});
const sections = gsap.utils.toArray('section');
sections.forEach((section, i) => {
ScrollTrigger.create({
trigger: section,
start: "top center",
onEnter: () => updateToby(i),
onEnterBack: () => updateToby(i)
});
});
// Progress Bar
gsap.to(progressBar, {
width: "100%",
ease: "none",
scrollTrigger: {
trigger: "body",
start: "top top",
end: "bottom bottom",
scrub: 0.3
}
});
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 MiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
# Fotograf.de Scraper & Management UI
**Status:** Production-Ready Microservice (Core Feature: PDF List Generation, QR Cards, Shooting Schedule, **Siblings List** & **Gmail API Integration**)
**Status:** Production-Ready Microservice (Core Feature: PDF List Generation, QR Cards, Shooting Schedule, **SQLite Data Sync**, **Gmail API Integration** & **Automated Release Requests**)
Dieser Service modernisiert die alten `Fotograf.de` Skripte, indem er eine robuste, web-basierte UI zur Verwaltung und Automatisierung von Foto-Aufträgen bereitstellt. Er ist als eigenständiger Microservice konzipiert, der unabhängig vom Haupt-Stack läuft.
@@ -10,85 +10,62 @@ Der Service besteht aus zwei Hauptkomponenten:
1. **Backend (Python / FastAPI / Selenium / SQLAlchemy):**
* **Automatisierung:** Nutzt Selenium für das Scraping von `fotograf.de`.
* **Persistenz:** Eine SQLite-Datenbank (`fotograf_jobs.db`) speichert die Auftragsliste, sodass langsame Scraping-Vorgänge nur bei Bedarf (Refresh) nötig sind. Speichert außerdem OAuth-Tokens (`GmailToken`) für persistente E-Mail-Sitzungen.
* **Persistenz:** Eine SQLite-Datenbank (`fotograf_jobs.db`) speichert die Auftragsliste, OAuth-Tokens (`GmailToken`), Gutscheincodes (`DiscountCode`), Teilnehmerdaten (`ReleaseParticipant`), **Auftragsteilnehmer (`JobParticipant`)** und die **Versand-Historie (`ReleaseHistory`)**.
* **PDF-Engine:** Nutzt WeasyPrint für Teilnehmerlisten und ReportLab/PyPDF2 für präzise PDF-Overlays (QR-Karten).
* **API-Integration:** Direkte Anbindung an die **Calendly API (v2)** zum Abruf von Live-Buchungsdaten sowie an die **Gmail API** für direkten E-Mail-Versand.
* **API-Integration:** Direkte Anbindung an die **Calendly API (v2)** sowie an die **Gmail API** für direkten E-Mail-Versand und automatisierte Webhook-Antworten.
2. **Frontend (TypeScript / React / Vite / TailwindCSS):**
* **Modernes UI:** Ein vollständig responsives Dashboard mit Tailwind CSS (Kachel-Layout, Tabs für Kiga/Schule).
* **Arbeitsfluss:** Tools sind direkt in der Detailansicht des jeweiligen Auftrags integriert (Shooting-Planung, E-Mail-Kampagnen).
* **Arbeitsfluss:** Tools sind in der Detailansicht eines Auftrags in logische Phasen (Vorbereitung, Follow-Up, Statistik) unterteilt.
## ✨ Core Features
### 🚀 Performance-Optimierung (SQLite Sync)
Statt wie früher jedes Mal mühsam durch alle Foto-Alben zu "crawlen", nutzt das System nun eine intelligente Synchronisierung:
* **One-Click Sync:** Über den Button "Daten von Fotograf.de abgleichen" lädt das System die detaillierte Namensliste (CSV) herunter.
* **Lokale Datenbank:** Alle relevanten Infos (E-Mail der Eltern, Login-Zahlen, Bestellstatus, Zugangscodes) werden in der Tabelle `job_participants` gespeichert.
* **Blitzschnelle Analyse:** Nachfass-Mails und Statistiken werden nun in Sekunden (statt Minuten) direkt aus der Datenbank generiert.
### Feature 1: Teilnehmerlisten (Vollständig)
Automatisierter Workflow zum Download und Formatieren der Anmeldelisten von `fotograf.de` als sortiertes PDF inkl. "Kinderfotos Erding" Branding.
* **Dynamische Terminologie:** Automatische Anpassung des Wordings basierend auf dem Profil (Kiga: "Kinder"/"Gruppen" vs. Schule: "Schüler"/"Klassen").
* **Intelligentes Datum:** Extraktion des echten Auftragsdatums aus der Datenbank mit automatischer Erweiterung auf einen 2-Tages-Zeitraum (z.B. "15. + 16.04.2026").
### Feature 2: Shooting-Planung (QR-Karten & Terminliste)
Spezielles Modul für Familien-Mini-Shootings, direkt integriert in die Auftragsdetails:
* **Dynamische Event-Auswahl:** Wähle direkt aus deinen Calendly-Event-Typen (z.B. "Neuching") aus.
* **Termin-Filter:** Zeigt nur noch aktuelle und zukünftige Buchungen an (alte recycelte Termine werden ignoriert).
* **QR-Karten-Andruck:**
* Präzises Overlay von Name, Kinderanzahl und Uhrzeit auf vorbereitete QR-Code-Bögen.
* **Einwilligungs-Checkbox (☑):** Automatischer Andruck eines Häkchens, wenn in Calendly der Veröffentlichung zugestimmt wurde.
* **Termin-Übersichtsliste:**
* Generiert eine A4-Tabelle für den Shooting-Tag im 6-Minuten-Takt.
* Füllt Lücken für nicht gebuchte Slots automatisch leer auf.
### Feature 2: Shooting-Planung (QR-Karten & Terminliste) (Vollständig)
Spezielles Modul für Familien-Mini-Shootings:
* **QR-Karten-Andruck:** Präzises Overlay von Name, Kinderanzahl und Uhrzeit inkl. automatischer **Einwilligungs-Checkbox (☑)** aus Calendly-Daten.
* **Termin-Übersichtsliste:** Generiert eine A4-Tabelle für den Shooting-Tag im 6-Minuten-Takt inkl. Lückenfüller.
### Feature 3: Nachfass-E-Mails & Gmail Direkt-Versand (Vollständig)
Identifizierung von potenziellen Käufern und automatisierter Kontakt.
* **Analyse-Logik:** Sucht nach Personen mit 0-1 Logins, die noch keine Bilder gekauft haben.
* **Supermailer Export:** Generierung einer fertigen CSV-Liste.
* **Direkter Gmail-Versand (Neu):**
* Volle OAuth 2.0 Integration. Einmaliger Login, das Refresh-Token hält die Sitzung aktiv.
* Inline-Editor für Betreff und HTML-Nachricht mit Live-Platzhaltern (`{Name Käufer}`, `{Kindernamen}`, `{LinksHTML}`).
* Massensendungs-API (Bulk Send) schickt Mails direkt über das verbundene Postfach.
### Feature 3: Nachfass-E-Mails & Gmail Direkt-Versand (Optimiert)
Identifizierung von Nicht-Käufern (0-1 Logins, keine Bestellung) basierend auf den synchronisierten Datenbank-Daten.
* **Vorschau-Modus:** Ermöglicht das Durchklicken der personalisierten E-Mails an jeden Empfänger vor dem eigentlichen Versand.
* **Quick-Login Automation:** Komfortabler "One-Click" Login-Link. Das System nutzt bevorzugt den via 'Link Magic' gesammelten Direkt-Link (`/gc/xyz`) oder fällt sicher auf die generische Anmeldung (`/login/ZUGANGSCODE`) inkl. automatischer Code-Übergabe zurück.
### Feature 4: Verkaufs-Statistiken (Vollständig)
* Detaillierte Analyse des Kaufverhaltens pro Album mit Echtzeit-Fortschrittsanzeige im Browser.
### Feature 4: Verkaufs-Statistiken (Optimiert)
Detaillierte Analyse des Kaufverhaltens pro Gruppe/Klasse basierend auf den lokalen Datenbank-Einträgen.
### Feature 5: Geschwisterliste (Einrichtungsintern) (Vollständig)
Spezielles Tool zur Identifizierung von Geschwistergruppen innerhalb einer Einrichtung.
* **Intelligente Erkennung:** Nutzt die "Email der Eltern (1)" aus der Fotograf.de-Anmeldeliste für einen automatischen Abgleich (Zählenwenn > 1).
* **Calendly-Cross-Check:** Gleicht die identifizierten Familien mit allen aktuellen Calendly-Buchungen ab, um Nachmittags-Termine automatisch in der Liste zu vermerken.
* **Optimiertes PDF:** Generiert eine alphabetisch nach Nachnamen sortierte Liste mit Kindern, deren Gruppen, Online-Wunsch-Status und Termin-Uhrzeit (inkl. Datum) sowie einem Erledigt-Feld für die manuelle Kontrolle vor Ort.
* **Geschwister-QR-Karten:** Erzeugt automatisch Zugangskarten ("Geschwisterbilder Familie [Nachname]") für alle identifizierten Geschwisterfamilien, die *keinen* Termin am Nachmittag gebucht haben.
Tool zur Identifizierung von Geschwistergruppen innerhalb einer Einrichtung inkl. Cross-Check mit Calendly-Buchungen und speziellen Geschwister-QR-Karten.
* **Flexibilität:** Optionaler Modus "Ohne Nachmittags-Shooting", um die Liste auch ohne Calendly-Abgleich (rein einrichtungsintern) zu generieren.
### Feature 6: Freigabeanfragen & Gutschein-Automation (Vollständig)
Vollautomatisierter DSGVO-Workflow zur Einholung von Veröffentlichungsgenehmigungen:
* **Schlanker Versand:** Manuelle Eingabe von Empfängern (E-Mail, Vorname, Kindernamen) mit **E-Mail-Vorschau**.
* **Versand-Planung:** Einstellbare Versandzeit (Berlin Timezone) via Hintergrund-Tasks.
* **Webhook-Integration:** Direkte Anbindung an **Google Forms**. Bei Absenden des Freigabe-Formulars wird automatisch ein Gutscheincode reserviert und eine Dankes-E-Mail versendet.
* **Antwort-Übersicht:** Tabelle aller eingegangenen Freigaben inkl. zugewiesenem Code und Zeitstempel.
---
## 🎯 Nächste Session: "Freigabeanfragen" (Feature 6)
Das nächste große Ziel ist der automatische Versand von Freigabeanfragen via Gmail.
**Der geplante Workflow (Bestätigt):**
Anstatt Bild für Bild auf `fotograf.de` zu prüfen, nutzen wir die bereits erteilte **Einwilligung aus Calendly**.
* **Logik:** Das System prüft die Calendly-Buchungen des Shootings auf die Frage *"Dürfen wir einige schöne Aufnahmen veröffentlichen?"*.
* Alle Kunden, die hier "Ja" (bzw. eine positive Antwort) angegeben haben, werden extrahiert und erhalten automatisiert über die Gmail-API die Freigabeanfrage.
* *Text & Template werden in der nächsten Sitzung definiert.*
---
## 🛠️ Technische Details & Fixes (April 2026)
* **E-Mail Signatur:** Die offizielle HTML-Signatur von "Kinderfotos Erding" (inkl. Logo via Google Drive Link) ist hartcodiert hinterlegt und wird bei jedem Massen- oder Testversand unsichtbar im Hintergrund an den HTML-Body angehängt.
* **Gmail OAuth Architektur:** Strikt getrennt. App-Credentials (`client_id`, `client_secret`) liegen in der `.env`. User-Credentials (Access & Refresh Tokens) werden dynamisch in der SQLite-Datenbank (`GmailToken`) gespeichert.
* **Routing / Reverse Proxy:**
* Das Frontend (React/Vite) nutzt den `base: '/fotograf-de/'` Pfad, um Assets korrekt hinter dem Nginx-Proxy zu laden.
* API-Aufrufe nutzen relative Pfade (`/fotograf-de-api/`), welche von Nginx sauber zum internen Backend-Port `8000` umgeschrieben (`rewrite`) werden. Dies verhindert Mixed-Content und CORS-Fehler im Produktionsbetrieb.
* **Zeitzonen:** Automatische Konversion von Calendly-UTC-Zeiten in die lokale Zeit (`Europe/Berlin`).
* **Pagination Fix:** Das Backend blättert durch alle Calendly-Seiten für lückenlose Daten.
## 🛠️ Technische Details & Sicherheit
* **BCC-Kontrolle:** Jede vom System versendete E-Mail sendet automatisch eine Blindkopie (BCC) an `kontakt@kinderfotos-erding.de`.
* **Versand-Historie:** Alle Aussendungen (Anzahl Empfänger, Zeitpunkt) werden in der Tabelle `release_history` protokolliert.
* **Sicherer Test-Modus:** Über `DEV_MODE_EMAIL_RECIPIENT` können alle E-Mails global an eine Test-Adresse umgeleitet werden.
* **Zeitzonen:** Durchgängige Verwendung von `Europe/Berlin`.
* **Gmail OAuth:** Persistente Speicherung der Refresh-Tokens in der Datenbank.
## 🚀 Deployment & Konfiguration
Der Service wird über eine eigene `docker-compose.yml` im Unterverzeichnis gestartet.
Der Service wird über die Haupt-`docker-compose.yml` des Projekts verwaltet.
### Umgebungsvariablen (`.env`)
Folgende Variablen müssen in der `.env` im Verzeichnis `/fotograf-de-scraper/` definiert sein:
* `KIGA_USER` / `KIGA_PW` / `SCHULE_USER` / `SCHULE_PW`: Logins für Fotograf.de.
* `CALENDLY_TOKEN`: Personal Access Token (JWT) von Calendly.
* `google_fotograf_client_id` / `google_fotograf_secret`: Die OAuth-App-Credentials aus der Google Cloud Console.
* `GOOGLE_REDIRECT_URI`: (Optional) Standard ist `https://floke-ai.duckdns.org/fotograf-de-api/api/auth/callback`.
### URLs & Ports
* **Produktion / Nginx:** `https://floke-ai.duckdns.org/fotograf-de/`
* **Persistenz:** Datenbank unter `./backend/data/fotograf_jobs.db`.
### URLs
* **Frontend:** `https://floke-ai.duckdns.org/fotograf-de/`
* **Webhook für Google Forms:** `https://floke-ai.duckdns.org/fotograf-de-api/api/publish-request/webhook`

View File

@@ -36,6 +36,47 @@ class DiscountCode(Base):
assigned_to_email = Column(String, nullable=True)
used_at = Column(DateTime, nullable=True)
class ReleaseParticipant(Base):
__tablename__ = "release_participants"
email = Column(String, primary_key=True)
first_name = Column(String)
last_updated = Column(DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow)
class ReleaseHistory(Base):
__tablename__ = "release_history"
id = Column(Integer, primary_key=True)
timestamp = Column(DateTime, default=datetime.datetime.utcnow)
recipient_count = Column(Integer)
scheduled_time = Column(String, nullable=True)
class ReminderHistory(Base):
__tablename__ = "reminder_history"
id = Column(Integer, primary_key=True)
job_id = Column(String, index=True)
timestamp = Column(DateTime, default=datetime.datetime.utcnow)
recipient_count = Column(Integer)
max_logins = Column(Integer)
recipients_json = Column(String) # JSON list of emails/names/children
scheduled_time = Column(String, nullable=True)
class JobParticipant(Base):
__tablename__ = "job_participants"
id = Column(Integer, primary_key=True)
job_id = Column(String, index=True)
child_id = Column(String, nullable=True)
vorname_kind = Column(String, nullable=True)
nachname_kind = Column(String, nullable=True)
vorname_eltern = Column(String, nullable=True)
nachname_eltern = Column(String, nullable=True)
email_eltern = Column(String, nullable=True)
zugangscode = Column(String, index=True)
gruppe = Column(String, nullable=True)
logins = Column(Integer, default=0)
has_orders = Column(Integer, default=0) # 0 for false, 1 for true
digital_package_ordered = Column(Integer, default=0) # 0 for false, 1 for true
quick_login_url = Column(String, nullable=True)
last_synced = Column(DateTime, default=datetime.datetime.utcnow)
Base.metadata.create_all(bind=engine)
def get_db():

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@@ -121,6 +121,7 @@ class GmailService:
message = MIMEText(body_html, 'html')
message['to'] = to
message['subject'] = subject
message['bcc'] = 'kontakt@kinderfotos-erding.de'
raw_message = base64.urlsafe_b64encode(message.as_bytes()).decode()

View File

@@ -0,0 +1,49 @@
import os
import sys
from dotenv import load_dotenv
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from database import Job
from main import setup_driver, login
import time
load_dotenv()
engine = create_engine("sqlite:////app/data/fotograf_jobs.db")
Session = sessionmaker(bind=engine)
db = Session()
# Get latest job
job = db.query(Job).order_by(Job.last_updated.desc()).first()
if not job:
print("No jobs found in database.")
sys.exit(1)
print(f"Using Job ID: {job.id} ({job.name}), Account: {job.account_type}")
username = os.getenv(f"{job.account_type.upper()}_USER")
password = os.getenv(f"{job.account_type.upper()}_PW")
driver = setup_driver()
if not driver:
print("Failed to init driver")
sys.exit(1)
if not login(driver, username, password):
print("Login failed")
driver.quit()
sys.exit(1)
orders_url = f"https://app.fotograf.de/config_jobs_orders/index/{job.id}/customer_orders"
print(f"Navigating to {orders_url}")
driver.get(orders_url)
time.sleep(5) # wait for page to load
html = driver.page_source
with open("orders_page.html", "w", encoding="utf-8") as f:
f.write(html)
driver.save_screenshot("orders_page.png")
print("Saved orders_page.html and orders_page.png")
driver.quit()

View File

@@ -10,13 +10,14 @@ from weasyprint import HTML
import tempfile
import shutil
import time
import json
from dotenv import load_dotenv
from fastapi import FastAPI, HTTPException, Depends, BackgroundTasks
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from typing import List, Dict, Any, Optional
from sqlalchemy.orm import Session
from database import get_db, Job as DBJob, engine, Base
from database import get_db, Job as DBJob, engine, Base, JobParticipant, SessionLocal, ReminderHistory
import math
import uuid
@@ -116,6 +117,8 @@ SELECTORS = {
"job_row_shooting_type": ".//td[count(//th[contains(., 'Typ')]/preceding-sibling::th) + 1]",
"export_dropdown": "[data-qa-id='dropdown:export']",
"export_csv_link": "button[data-qa-id='button:csv']",
# --- Reminder & Quick Login Selectors ---
"person_access_code_link": ".//a[contains(@data-qa-id, 'guest-access-banner-access-code')]",
# --- Statistics Selectors ---
"album_overview_rows": "//table/tbody/tr",
"album_overview_link": ".//td[2]//a",
@@ -488,264 +491,168 @@ def get_jobs_list(driver) -> List[Dict[str, Any]]:
task_store: Dict[str, Dict[str, Any]] = {}
def process_statistics(task_id: str, job_id: str, account_type: str):
logger.info(f"Task {task_id}: Starting statistics calculation for job {job_id}")
task_store[task_id] = {"status": "running", "progress": "Initialisiere Browser...", "result": None}
username = os.getenv(f"{account_type.upper()}_USER")
password = os.getenv(f"{account_type.upper()}_PW")
driver = None
logger.info(f"Task {task_id}: Starting fast statistics calculation for job {job_id}")
task_store[task_id] = {"status": "running", "progress": "Berechne Statistiken...", "result": None}
db = SessionLocal()
try:
driver = setup_driver()
if not driver or not login(driver, username, password):
task_store[task_id] = {"status": "error", "progress": "Login fehlgeschlagen. Überprüfe die Zugangsdaten."}
# Check if we have data at all
count = db.query(JobParticipant).filter(JobParticipant.job_id == job_id).count()
if count == 0:
task_store[task_id] = {"status": "error", "progress": "Keine Daten vorhanden. Bitte erst oben auf 'Daten abgleichen' klicken."}
return
task_store[task_id]["progress"] = f"Lade Alben-Übersicht für Auftrag..."
# Query DB and group by 'gruppe'
albums_overview_url = f"https://app.fotograf.de/config_jobs_photos/index/{job_id}"
logger.info(f"Navigating to albums: {albums_overview_url}")
driver.get(albums_overview_url)
wait = WebDriverWait(driver, 15)
# Get all participants for this job
participants = db.query(JobParticipant).filter(JobParticipant.job_id == job_id).all()
albums_to_visit = []
try:
album_rows = wait.until(EC.presence_of_all_elements_located((By.XPATH, SELECTORS["album_overview_rows"])))
for row in album_rows:
try:
album_link = row.find_element(By.XPATH, SELECTORS["album_overview_link"])
albums_to_visit.append({"name": album_link.text, "url": album_link.get_attribute('href')})
except NoSuchElementException:
continue
except TimeoutException:
task_store[task_id] = {"status": "error", "progress": "Konnte die Album-Liste nicht finden."}
return
total_albums = len(albums_to_visit)
task_store[task_id]["progress"] = f"{total_albums} Alben gefunden. Starte Auswertung..."
statistics = []
for index, album in enumerate(albums_to_visit):
album_name = album['name']
task_store[task_id]["progress"] = f"Bearbeite Album {index + 1}/{total_albums}: '{album_name}'..."
driver.get(album['url'])
try:
total_codes_text = wait.until(EC.visibility_of_element_located((By.XPATH, SELECTORS["access_code_count"]))).text
num_pages = math.ceil(int(total_codes_text) / 20)
total_children_in_album = 0
children_with_purchase = 0
children_with_all_purchased = 0
for page_num in range(1, num_pages + 1):
task_store[task_id]["progress"] = f"Bearbeite Album {index + 1}/{total_albums}: '{album_name}' (Seite {page_num}/{num_pages})..."
if page_num > 1:
driver.get(album['url'] + f"?page_guest_accesses={page_num}")
person_rows = wait.until(EC.presence_of_all_elements_located((By.XPATH, SELECTORS["person_rows"])))
for person_row in person_rows:
total_children_in_album += 1
try:
photo_container = person_row.find_element(By.XPATH, "./following-sibling::div[1]")
num_total_photos = len(photo_container.find_elements(By.XPATH, SELECTORS["person_all_photos"]))
num_purchased_photos = len(photo_container.find_elements(By.XPATH, SELECTORS["person_purchased_photos"]))
num_access_cards = len(photo_container.find_elements(By.XPATH, SELECTORS["person_access_card_photo"]))
buyable_photos = num_total_photos - num_access_cards
if num_purchased_photos > 0:
children_with_purchase += 1
if buyable_photos > 0 and buyable_photos == num_purchased_photos:
children_with_all_purchased += 1
except NoSuchElementException:
continue
statistics.append({
"Album": album_name,
"Kinder_insgesamt": total_children_in_album,
"Kinder_mit_Käufen": children_with_purchase,
"Kinder_Alle_Bilder_gekauft": children_with_all_purchased
})
except Exception as e:
logger.error(f"Fehler bei Auswertung von Album '{album_name}': {e}")
continue
# Group by group
groups = {}
for p in participants:
g_name = p.gruppe or "Unbekannt"
if g_name not in groups:
groups[g_name] = {
"Album": g_name,
"Kinder_insgesamt": 0,
"Kinder_mit_Käufen": 0,
"Kinder_Alle_Bilder_gekauft": 0
}
groups[g_name]["Kinder_insgesamt"] += 1
if p.has_orders:
groups[g_name]["Kinder_mit_Käufen"] += 1
if p.digital_package_ordered:
groups[g_name]["Kinder_Alle_Bilder_gekauft"] += 1
statistics = list(groups.values())
statistics.sort(key=lambda x: x["Album"])
task_store[task_id] = {
"status": "completed",
"progress": "Auswertung erfolgreich abgeschlossen!",
"progress": "Statistik erfolgreich berechnet!",
"result": statistics
}
except Exception as e:
logger.exception(f"Unexpected error in task {task_id}")
logger.exception(f"Unexpected error in statistics task {task_id}")
task_store[task_id] = {"status": "error", "progress": f"Unerwarteter Fehler: {str(e)}"}
finally:
if driver:
logger.debug(f"Task {task_id}: Closing driver.")
driver.quit()
db.close()
def process_reminder_analysis(task_id: str, job_id: str, account_type: str):
logger.info(f"Task {task_id}: Starting reminder analysis for job {job_id}")
task_store[task_id] = {"status": "running", "progress": "Initialisiere Browser...", "result": None}
username = os.getenv(f"{account_type.upper()}_USER")
password = os.getenv(f"{account_type.upper()}_PW")
driver = None
def process_reminder_analysis(task_id: str, job_id: str, account_type: str, max_logins: int = 1, exclude_purchased_emails: bool = True):
logger.info(f"Task {task_id}: Starting fast reminder analysis for job {job_id}")
task_store[task_id] = {"status": "running", "progress": "Analysiere Datenbank-Einträge...", "result": None}
db = SessionLocal()
try:
driver = setup_driver()
if not driver or not login(driver, username, password):
task_store[task_id] = {"status": "error", "progress": "Login fehlgeschlagen."}
# Check if we have data at all
count = db.query(JobParticipant).filter(JobParticipant.job_id == job_id).count()
if count == 0:
task_store[task_id] = {"status": "error", "progress": "Keine Daten vorhanden. Bitte erst oben auf 'Daten abgleichen' klicken."}
return
wait = WebDriverWait(driver, 15)
# 1. Navigate to albums overview
albums_overview_url = f"https://app.fotograf.de/config_jobs_photos/index/{job_id}"
task_store[task_id]["progress"] = "Lade Alben-Übersicht..."
driver.get(albums_overview_url)
albums_to_visit = []
try:
album_rows = wait.until(EC.presence_of_all_elements_located((By.XPATH, SELECTORS["album_overview_rows"])))
for row in album_rows:
try:
album_link = row.find_element(By.XPATH, SELECTORS["album_overview_link"])
albums_to_visit.append({"name": album_link.text, "url": album_link.get_attribute('href')})
except NoSuchElementException:
continue
except TimeoutException:
task_store[task_id] = {"status": "error", "progress": "Konnte die Album-Liste nicht finden."}
# 1. Get emails that have ALREADY purchased anything (in THIS specific job)
purchased_emails = set()
if exclude_purchased_emails:
from sqlalchemy import or_
# We look ONLY within the CURRENT job to find siblings that were already purchased
purchased_results = db.query(JobParticipant.email_eltern).filter(
JobParticipant.job_id == job_id,
or_(JobParticipant.has_orders == 1, JobParticipant.digital_package_ordered == 1),
JobParticipant.email_eltern != "",
JobParticipant.email_eltern != None
).all()
purchased_emails = {r[0].lower() for r in purchased_results}
logger.info(f"Task {task_id}: Found {len(purchased_emails)} unique emails with existing purchases in THIS job to exclude.")
# 2. Query DB for potential candidates (Logins <= max_logins and No Orders)
candidates = db.query(JobParticipant).filter(
JobParticipant.job_id == job_id,
JobParticipant.has_orders == 0,
JobParticipant.digital_package_ordered == 0,
JobParticipant.logins <= max_logins,
JobParticipant.email_eltern != "",
JobParticipant.email_eltern != None
).all()
if not candidates:
task_store[task_id] = {
"status": "completed",
"progress": f"Keine passenden Empfänger (0-{max_logins} Logins, keine Bestellung) gefunden.",
"result": []
}
return
raw_results = []
total_albums = len(albums_to_visit)
# 3. Aggregate results by Email
aggregation = {}
missing_links_count = 0
for index, album in enumerate(albums_to_visit):
album_name = album['name']
task_store[task_id]["progress"] = f"Album {index+1}/{total_albums}: '{album_name}'..."
driver.get(album['url'])
base_url = "https://kinderfoto-erding.fotograf.de" if account_type == "kiga" else "https://kinderfotos-erding.fotograf.de"
for c in candidates:
email = c.email_eltern.lower()
try:
total_codes_text = wait.until(EC.visibility_of_element_located((By.XPATH, SELECTORS["access_code_count"]))).text
num_pages = math.ceil(int(total_codes_text) / 20)
for page_num in range(1, num_pages + 1):
task_store[task_id]["progress"] = f"Album {index+1}/{total_albums}: '{album_name}' (Seite {page_num}/{num_pages})..."
if page_num > 1:
driver.get(album['url'] + f"?page_guest_accesses={page_num}")
person_rows = wait.until(EC.presence_of_all_elements_located((By.XPATH, SELECTORS["person_rows"])))
num_persons = len(person_rows)
for i in range(num_persons):
# Re-locate rows to avoid stale element reference
person_rows = wait.until(EC.presence_of_all_elements_located((By.XPATH, SELECTORS["person_rows"])))
person_row = person_rows[i]
login_count_text = person_row.find_element(By.XPATH, ".//span[text()='Logins']/following-sibling::strong").text
# Only interested in people with 0 or 1 logins (potential reminders)
# Actually, if they haven't bought yet, they might need a reminder regardless of logins,
# but the legacy logic uses login_count <= 1.
# Let's stick to the legacy logic for now.
if int(login_count_text) <= 1:
vorname = person_row.find_element(By.XPATH, ".//span[text()='Vorname']/following-sibling::strong").text
try:
photo_container = person_row.find_element(By.XPATH, "./following-sibling::div[1]")
purchase_icons = photo_container.find_elements(By.XPATH, ".//img[@alt='Bestellungen mit diesem Foto']")
if len(purchase_icons) > 0:
continue
except NoSuchElementException:
pass
# Potential candidate
access_code_page_url = person_row.find_element(By.XPATH, ".//a[contains(@data-qa-id, 'guest-access-banner-access-code')]").get_attribute('href')
# Open in new tab or navigate back and forth?
# Scraper.py navigates back and forth.
driver.get(access_code_page_url)
try:
wait.until(EC.visibility_of_element_located((By.XPATH, "//a[@id='quick-login-url']")))
quick_login_url = driver.find_element(By.XPATH, "//a[@id='quick-login-url']").get_attribute('href')
potential_buyer_element = driver.find_element(By.XPATH, "//a[contains(@href, '/config_customers/view_customer')]")
buyer_name = potential_buyer_element.text
potential_buyer_element.click()
email = wait.until(EC.visibility_of_element_located((By.XPATH, "//span[contains(., '@')]"))).text
raw_results.append({
"child_name": vorname,
"buyer_name": buyer_name,
"email": email,
"quick_login": quick_login_url
})
except Exception as e:
logger.warning(f"Error getting details for {vorname}: {e}")
# Go back to the album page
driver.get(album['url'] + (f"?page_guest_accesses={page_num}" if page_num > 1 else ""))
wait.until(EC.presence_of_element_located((By.XPATH, SELECTORS["person_rows"])))
except Exception as e:
logger.error(f"Fehler bei Album '{album_name}': {e}")
# Skip if this email already has a purchase for ANOTHER child
if exclude_purchased_emails and email in purchased_emails:
continue
# Aggregate Results
task_store[task_id]["progress"] = "Aggregiere Ergebnisse..."
aggregated_data = {}
for res in raw_results:
email = res['email']
child_name = "Familienbilder" if res['child_name'] == "Familie" else res['child_name']
html_link = f'<a href="{res["quick_login"]}">Fotos von {child_name}</a>'
if email not in aggregated_data:
aggregated_data[email] = {
'buyer_first_name': res['buyer_name'].split(' ')[0],
'email': email,
'children': [child_name],
'links': [html_link]
}
else:
if child_name not in aggregated_data[email]['children']:
aggregated_data[email]['children'].append(child_name)
aggregated_data[email]['links'].append(html_link)
final_list = []
for email, data in aggregated_data.items():
names = data['children']
if len(names) > 2:
names_str = ', '.join(names[:-1]) + ' und ' + names[-1]
else:
names_str = ' und '.join(names)
final_list.append({
'Name Käufer': data['buyer_first_name'],
'E-Mail-Adresse Käufer': email,
'Kindernamen': names_str,
'LinksHTML': '<br><br>'.join(data['links'])
})
if email not in aggregation:
aggregation[email] = {
"email": email,
"parent_name": c.vorname_eltern if c.vorname_eltern else "Liebe Eltern",
"children": [],
"links": []
}
# Add child name
child_name = c.vorname_kind or ""
child_label = "Familienbilder" if child_name.lower() == "familie" else child_name
if child_label and child_label not in aggregation[email]["children"]:
aggregation[email]["children"].append(child_label)
# Determine best link
if c.quick_login_url and "/gc/" in c.quick_login_url:
# Use scraped direct link if available
final_link = c.quick_login_url
link_text = f"Fotos von {child_label}"
else:
# Fallback to direct code navigation link
final_link = f"{base_url}/login/{c.zugangscode}"
link_text = f"Fotos von {child_label}"
missing_links_count += 1
html_link = f'<a href="{final_link}">{link_text}</a>'
if html_link not in aggregation[email]["links"]:
aggregation[email]["links"].append(html_link)
# 4. Format for Supermailer/Gmail
final_result = []
for email, data in aggregation.items():
children_str = " und ".join(data["children"]) if len(data["children"]) > 1 else (data["children"][0] if data["children"] else "Eurem Kind")
links_html = "".join([f"{l}<br>" for l in data["links"]])
final_result.append({
"E-Mail-Adresse Käufer": email,
"Name Käufer": data["parent_name"],
"Kindernamen": children_str,
"Anzahl Kinder": len(data["children"]),
"LinksHTML": links_html
})
progress_msg = f"Analyse fertig! {len(final_result)} Empfänger identifiziert."
if missing_links_count > 0:
progress_msg += f" (Hinweis: {missing_links_count} Links wurden generiert, da sie noch nicht gescraped wurden.)"
task_store[task_id] = {
"status": "completed",
"progress": "Analyse abgeschlossen!",
"result": final_list
"status": "completed",
"progress": progress_msg,
"result": final_result
}
except Exception as e:
logger.exception(f"Error in task {task_id}")
task_store[task_id] = {"status": "error", "progress": f"Fehler: {str(e)}"}
finally:
if driver: driver.quit()
db.close()
from fastapi import FastAPI, HTTPException, Depends, BackgroundTasks, UploadFile, File, Form
from fastapi.middleware.cors import CORSMiddleware
@@ -1036,10 +943,16 @@ async def start_statistics(job_id: str, account_type: str, background_tasks: Bac
return {"task_id": task_id}
@app.post("/api/jobs/{job_id}/reminder-analysis")
async def start_reminder_analysis(job_id: str, account_type: str, background_tasks: BackgroundTasks):
logger.info(f"API Request: Start reminder analysis for job {job_id} ({account_type})")
async def start_reminder_analysis(
job_id: str,
account_type: str,
background_tasks: BackgroundTasks,
max_logins: int = 1,
exclude_purchased_emails: bool = True
):
logger.info(f"API Request: Start reminder analysis for job {job_id} ({account_type}, max_logins={max_logins}, exclude_purchased={exclude_purchased_emails})")
task_id = str(uuid.uuid4())
background_tasks.add_task(process_reminder_analysis, task_id, job_id, account_type)
background_tasks.add_task(process_reminder_analysis, task_id, job_id, account_type, max_logins, exclude_purchased_emails)
return {"task_id": task_id}
@app.get("/api/tasks/{task_id}/download-csv")
@@ -1092,6 +1005,414 @@ async def send_bulk_emails(request: BulkEmailRequest, db: Session = Depends(get_
"failed": failed_emails
}
def sync_participants(job_id: str, account_type: str, db: Session, task_id: str = None):
logger.info(f"Syncing participants for job {job_id} ({account_type})")
username = os.getenv(f"{account_type.upper()}_USER")
password = os.getenv(f"{account_type.upper()}_PW")
with tempfile.TemporaryDirectory() as temp_dir:
driver = setup_driver(download_path=temp_dir)
try:
if not login(driver, username, password):
raise Exception("Login failed.")
# Navigate to the Persons tab
if task_id: task_store[task_id]["progress"] = "Hole Teilnehmerliste (CSV)..."
job_url = f"https://app.fotograf.de/config_jobs_settings/index/{job_id}"
driver.get(job_url)
wait = WebDriverWait(driver, 30)
personen_tab = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "[data-qa-id='link:photo-jobs-tabs-names_list']")))
driver.execute_script("arguments[0].click();", personen_tab)
# Click Export -> CSV
export_btn = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, SELECTORS["export_dropdown"])))
driver.execute_script("arguments[0].click();", export_btn)
time.sleep(1)
csv_btn = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, SELECTORS["export_csv_link"])))
driver.execute_script("arguments[0].click();", csv_btn)
# Wait for download
csv_file = None
for _ in range(45):
files = os.listdir(temp_dir)
csv_files = [f for f in files if f.endswith('.csv')]
if csv_files:
csv_file = os.path.join(temp_dir, csv_files[0])
break
time.sleep(1)
if not csv_file:
raise Exception("CSV download timed out.")
# Read CSV with pandas
df = None
for sep in [";", ","]:
try:
df = pd.read_csv(csv_file, sep=sep, encoding="utf-8-sig")
if len(df.columns) > 1: break
except: continue
if df is None: raise Exception("Could not parse CSV.")
# Clean columns
df.columns = df.columns.str.strip().str.replace("\"", "")
logger.debug(f"Sync CSV Columns: {list(df.columns)}")
# Column Mapping
mapping = {
"Child ID": "child_id",
"Email der Eltern (1)": "email_eltern",
"Vorname Eltern (1)": "vorname_eltern",
"Nachname Eltern (1)": "nachname_eltern",
"Vorname Kind": "vorname_kind",
"Nachname Kind": "nachname_kind",
"Zugangscode (1)": "zugangscode",
"Logins (1)": "logins",
"Bestellungen": "has_orders",
"Gruppe": "gruppe",
"Klasse": "gruppe"
}
if task_id: task_store[task_id]["progress"] = "Aktualisiere Datenbank..."
# Upsert into database
for _, row in df.iterrows():
code = str(row.get("Zugangscode (1)", "")).strip()
if not code or code == "nan": continue
def clean_val(val):
v = str(val).strip()
return "" if v.lower() == "nan" else v
# Determine order status
orders_val = str(row.get("Bestellungen", "0")).lower()
has_orders = 1 if (orders_val != "0" and orders_val != "nan" and orders_val != "") else 0
# Determine logins
logins_val = row.get("Logins (1)", 0)
try: logins = int(float(logins_val))
except: logins = 0
participant = db.query(JobParticipant).filter(JobParticipant.job_id == job_id, JobParticipant.zugangscode == code).first()
if not participant:
participant = JobParticipant(job_id=job_id, zugangscode=code)
db.add(participant)
participant.child_id = clean_val(row.get("Child ID"))
participant.vorname_kind = clean_val(row.get("Vorname Kind"))
participant.nachname_kind = clean_val(row.get("Nachname Kind"))
participant.vorname_eltern = clean_val(row.get("Vorname Eltern (1)"))
participant.nachname_eltern = clean_val(row.get("Nachname Eltern (1)"))
participant.email_eltern = clean_val(row.get("Email der Eltern (1)")).lower()
participant.gruppe = clean_val(row.get("Gruppe", row.get("Klasse")))
participant.logins = logins
participant.has_orders = has_orders
participant.last_synced = datetime.datetime.utcnow()
db.commit()
logger.info(f"Successfully synced {len(df)} participants from CSV.")
# --- PHASE 2: Scrape Orders for Digital Packages (Price Magic) ---
try:
if task_id: task_store[task_id]["progress"] = "Suche nach digitalen Käufen (Price Magic)..."
orders_url = f"https://app.fotograf.de/config_jobs_orders/{job_id}/customer_orders"
logger.info(f"Navigating to orders page for price magic: {orders_url}")
driver.get(orders_url)
time.sleep(3) # Wait for page/table to load
# Find all order rows
order_rows = driver.find_elements(By.XPATH, "//table/tbody/tr")
logger.info(f"Found {len(order_rows)} order rows to analyze.")
digital_matches = 0
for row in order_rows:
try:
cols = row.find_elements(By.TAG_NAME, "td")
if len(cols) < 11: continue
fname = cols[4].text.strip()
lname = cols[5].text.strip()
sum_text = cols[8].text.strip()
status_text = cols[10].text.strip()
# Parse Sum (e.g., "58,90 €")
clean_sum_text = sum_text.replace("", "").replace(",", ".").replace(" ", "").strip()
try:
order_sum = float(clean_sum_text)
except:
order_sum = 0.0
is_digital = False
# PRICE MAGIC: Defined package prices (regular & discounted)
# Digital Single: 58.90 / 53.90
# Digital Siblings: 109.90 / 94.90
# Digital Family: 75.90 / 70.90
target_prices = [58.90, 53.90, 109.90, 94.90, 75.90, 70.90]
if any(abs(order_sum - p) < 0.01 for p in target_prices):
is_digital = True
# STATUS FALLBACK: If status already says download
if "heruntergeladen" in status_text.lower() or "download" in status_text.lower():
is_digital = True
if is_digital and fname and lname:
# Update participants matching these parents
db.query(JobParticipant).filter(
JobParticipant.job_id == job_id,
JobParticipant.vorname_eltern == fname,
JobParticipant.nachname_eltern == lname
).update({JobParticipant.digital_package_ordered: 1})
digital_matches += 1
except Exception as row_err:
logger.warning(f"Error parsing order row: {row_err}")
continue
db.commit()
logger.info(f"Price Magic complete: Identified {digital_matches} digital packages.")
except Exception as order_err:
logger.error(f"Failed to scrape orders for price magic: {order_err}")
# --- PHASE 3: Link Magic (Scrape Quick Login URLs) ---
try:
# Find candidates for reminders who don't have a link yet
# We prioritize those with few logins and no orders
link_candidates = db.query(JobParticipant).filter(
JobParticipant.job_id == job_id,
JobParticipant.has_orders == 0,
JobParticipant.logins <= 5,
JobParticipant.quick_login_url == None
).all()
if link_candidates:
if task_id: task_store[task_id]["progress"] = f"Sammle Login-Links für {len(link_candidates)} Personen (Link Magic)..."
logger.info(f"Link Magic: Identified {len(link_candidates)} candidates for link scraping.")
# Navigate back to Persons tab
albums_overview_url = f"https://app.fotograf.de/config_jobs_photos/index/{job_id}"
logger.info(f"Navigating to Albums overview: {albums_overview_url}")
driver.get(albums_overview_url)
# Find all album links
album_elements = wait.until(EC.presence_of_all_elements_located((By.XPATH, SELECTORS["album_overview_link"])))
albums = [{"name": e.text, "url": e.get_attribute("href")} for e in album_elements]
codes_to_find = {c.zugangscode: c for c in link_candidates}
links_found = 0
for album in albums:
if not codes_to_find: break
logger.info(f"Searching for links in album: {album['name']}")
driver.get(album['url'])
try:
total_codes_text = wait.until(EC.visibility_of_element_located((By.XPATH, SELECTORS["access_code_count"]))).text
num_pages = math.ceil(int(total_codes_text) / 20)
for page_num in range(1, num_pages + 1):
if not codes_to_find: break
if page_num > 1:
driver.get(album['url'] + f"?page_guest_accesses={page_num}")
person_rows = wait.until(EC.presence_of_all_elements_located((By.XPATH, SELECTORS["person_rows"])))
# Map of codes on this page to their communication link
page_links = {}
for row in person_rows:
row_text = row.text
for code in list(codes_to_find.keys()):
if code in row_text:
try:
comm_link = row.find_element(By.XPATH, SELECTORS["person_access_code_link"]).get_attribute("href")
page_links[code] = comm_link
except: pass
# Now visit each communication page
for code, comm_link in page_links.items():
if code not in codes_to_find: continue
logger.debug(f"Scraping link for code {code}...")
if task_id: task_store[task_id]["progress"] = f"Hole Link {links_found+1} / {len(link_candidates)}..."
driver.get(comm_link)
for attempt in range(3):
try:
wait_short = WebDriverWait(driver, 5)
quick_link_el = wait_short.until(EC.visibility_of_element_located((By.XPATH, SELECTORS["quick_login_url"])))
quick_link = quick_link_el.get_attribute("href")
# Update DB
codes_to_find[code].quick_login_url = quick_link
del codes_to_find[code]
links_found += 1
if links_found % 5 == 0: db.commit()
break
except Exception as e:
time.sleep(1)
else:
logger.warning(f"Could not find quick login link for {code}")
# Go back to album page if we visited communication pages
if page_links:
driver.get(album['url'] + (f"?page_guest_accesses={page_num}" if page_num > 1 else ""))
wait.until(EC.presence_of_all_elements_located((By.XPATH, SELECTORS["person_rows"])))
except Exception as album_err:
logger.error(f"Error in album {album['name']}: {album_err}")
db.commit()
logger.info(f"Link Magic complete: Scraped {links_found} links.")
except Exception as link_err:
logger.error(f"Failed to scrape links: {link_err}")
return len(df)
finally:
driver.quit()
@app.get("/api/jobs/{job_id}/reminder-history")
async def get_reminder_history(job_id: str, db: Session = Depends(get_db)):
history = db.query(ReminderHistory).filter(ReminderHistory.job_id == job_id).order_by(ReminderHistory.timestamp.desc()).all()
return [
{
"id": h.id,
"timestamp": h.timestamp.isoformat(),
"recipient_count": h.recipient_count,
"max_logins": h.max_logins,
"scheduled_time": h.scheduled_time,
"recipients": json.loads(h.recipients_json) if h.recipients_json else []
}
for h in history
]
class SendReminderRequest(BaseModel):
emails: List[Dict[str, str]]
max_logins: int
scheduled_time: Optional[str] = None
recipients_data: List[Dict[str, Any]] # To store in history
@app.post("/api/jobs/{job_id}/reminder-send")
async def send_reminders(
job_id: str,
data: SendReminderRequest,
background_tasks: BackgroundTasks,
db: Session = Depends(get_db)
):
logger.info(f"Sending {len(data.emails)} reminders for job {job_id}")
# Save to history
new_history = ReminderHistory(
job_id=job_id,
recipient_count=len(data.emails),
max_logins=data.max_logins,
recipients_json=json.dumps(data.recipients_data),
scheduled_time=data.scheduled_time or "Sofort"
)
db.add(new_history)
db.commit()
# Reuse delayed_send logic from publish_request_api if scheduled
if data.scheduled_time:
from publish_request_api import delayed_send
from database import SessionLocal
background_tasks.add_task(delayed_send, data.emails, data.scheduled_time, SessionLocal)
return {"status": "scheduled", "message": f"Versand für {data.scheduled_time} geplant."}
# Immediate send
service = GmailService(db)
success = 0
failed = []
for email_data in data.emails:
if service.send_email(email_data["to"], email_data["subject"], email_data["body"]):
success += 1
else:
failed.append(email_data["to"])
return {"status": "success", "success": success, "failed": failed}
@app.get("/api/jobs/{job_id}/login-distribution")
async def get_login_distribution(job_id: str, db: Session = Depends(get_db)):
from sqlalchemy import func
results = db.query(
JobParticipant.logins,
func.count(JobParticipant.id)
).filter(JobParticipant.job_id == job_id).group_by(JobParticipant.logins).order_by(JobParticipant.logins).all()
return [{"logins": r[0], "count": r[1]} for r in results]
@app.get("/api/jobs/{job_id}/fast-stats")
async def get_fast_stats(job_id: str, db: Session = Depends(get_db)):
participants = db.query(JobParticipant).filter(JobParticipant.job_id == job_id).all()
if not participants:
return []
groups = {}
for p in participants:
g_name = p.gruppe or "Unbekannt"
if g_name not in groups:
groups[g_name] = {
"Album": g_name,
"Kinder_insgesamt": 0,
"Kinder_mit_Käufen": 0,
"Kinder_Alle_Bilder_gekauft": 0
}
groups[g_name]["Kinder_insgesamt"] += 1
if p.has_orders:
groups[g_name]["Kinder_mit_Käufen"] += 1
if p.digital_package_ordered:
groups[g_name]["Kinder_Alle_Bilder_gekauft"] += 1
statistics = list(groups.values())
statistics.sort(key=lambda x: x["Album"])
return statistics
def process_sync_task(task_id: str, job_id: str, account_type: str):
logger.info(f"Task {task_id}: Starting background sync for job {job_id}")
task_store[task_id] = {"status": "running", "progress": "Starte Synchronisierung...", "result": None}
db = SessionLocal()
try:
count = sync_participants(job_id, account_type, db, task_id)
task_store[task_id] = {
"status": "completed",
"progress": f"Abgleich fertig! {count} Personen synchronisiert.",
"result": count
}
except Exception as e:
logger.exception(f"Unexpected error in sync task {task_id}")
task_store[task_id] = {"status": "error", "progress": f"Fehler: {str(e)}"}
finally:
db.close()
@app.post("/api/jobs/{job_id}/sync-participants")
async def sync_participants_api(job_id: str, account_type: str, background_tasks: BackgroundTasks):
task_id = str(uuid.uuid4())
background_tasks.add_task(process_sync_task, task_id, job_id, account_type)
return {"task_id": task_id}
@app.get("/api/jobs/{job_id}/generate-pdf")
async def generate_pdf(job_id: str, account_type: str, db: Session = Depends(get_db)):
logger.info(f"API Request: Generate PDF for job {job_id} ({account_type})")
@@ -1200,23 +1521,24 @@ async def generate_pdf(job_id: str, account_type: str, db: Session = Depends(get
@app.get("/api/jobs/{job_id}/siblings-list")
async def generate_siblings_list(job_id: str, account_type: str, event_type_name: str = "", db: Session = Depends(get_db)):
logger.info(f"API Request: Generate siblings list for job {job_id}")
logger.info(f"API Request: Generate siblings list for job {job_id}, event_type: {event_type_name}")
username = os.getenv(f"{account_type.upper()}_USER")
password = os.getenv(f"{account_type.upper()}_PW")
api_token = os.getenv("CALENDLY_TOKEN")
if not api_token:
raise HTTPException(status_code=400, detail="Calendly API token missing.")
# Get Calendly events
from qr_generator import get_calendly_events_raw
try:
# Fetch ALL events to ensure we don't miss siblings due to event name mismatches
calendly_events = get_calendly_events_raw(api_token, event_type_name=None)
logger.info(f"Fetched {len(calendly_events)} total events from Calendly for siblings check.")
except Exception as e:
logger.error(f"Error fetching Calendly events: {e}")
calendly_events = []
calendly_events = []
if event_type_name:
if not api_token:
logger.warning("Calendly API token missing, skipping Calendly check.")
else:
# Get Calendly events
from qr_generator import get_calendly_events_raw
try:
# Fetch ALL events to ensure we don't miss siblings due to event name mismatches
calendly_events = get_calendly_events_raw(api_token, event_type_name=None)
logger.info(f"Fetched {len(calendly_events)} total events from Calendly for siblings check.")
except Exception as e:
logger.error(f"Error fetching Calendly events: {e}")
with tempfile.TemporaryDirectory() as temp_dir:
logger.debug(f"Using temp directory: {temp_dir}")

View File

@@ -0,0 +1,18 @@
import sqlite3
import os
db_path = "/app/data/fotograf_jobs.db"
if not os.path.exists(db_path):
db_path = "fotograf-de-scraper/backend/data/fotograf_jobs.db"
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
try:
cursor.execute("ALTER TABLE job_participants ADD COLUMN digital_package_ordered INTEGER DEFAULT 0;")
print("Column 'digital_package_ordered' added successfully.")
except sqlite3.OperationalError:
print("Column 'digital_package_ordered' already exists.")
conn.commit()
conn.close()

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

View File

@@ -1,7 +1,7 @@
from fastapi import APIRouter, Depends, HTTPException, Request, BackgroundTasks
from pydantic import BaseModel
from sqlalchemy.orm import Session
from database import get_db, DiscountCode
from database import get_db, DiscountCode, ReleaseParticipant, ReleaseHistory
import datetime
import logging
from gmail_service import GmailService
@@ -10,20 +10,47 @@ import time
import asyncio
from typing import List, Dict, Optional
from zoneinfo import ZoneInfo
router = APIRouter(prefix="/api/publish-request", tags=["publish-request"])
logger = logging.getLogger("publish-request")
# Timezone for Berlin
TZ_BERLIN = ZoneInfo("Europe/Berlin")
# Official Project Signature
SIGNATURE_HTML = """
<br><br>
<span style="color: #888;">--</span><br>
<div dir="ltr">
<table border="0" cellspacing="0" cellpadding="0" style="border-collapse:collapse; margin-top: 5px;">
<tbody>
<tr>
<td width="220" valign="top" style="padding-right: 15px;">
<img width="200" src="https://lh3.googleusercontent.com/d/1K7RODOqKE2e1nRJ3D4dEWdjthoTMyXUq" alt="Kinderfotos Erding Logo" style="display: block;">
</td>
<td valign="bottom" style="padding-left: 15px; border-left: 1px solid #ddd; font-family: sans-serif; font-size: 13px; color: #333; line-height: 1.5;">
<p style="margin: 0;"><b>Kinderfotos Erding</b> | <a href="http://www.kinderfotos-erding.de/" target="_blank" style="color: #1155cc; text-decoration: none;">www.kinderfotos-erding.de</a></p>
<p style="margin: 0; color: #666;">Gartenstr. 10 | 85445 Oberding | 08122-8470867</p>
</td>
</tr>
</tbody>
</table>
</div>
"""
class CodesUpload(BaseModel):
codes: str # comma separated
class SendReleaseRequest(BaseModel):
emails: List[Dict[str, str]]
scheduled_time: Optional[str] = None # e.g. "10:00"
participants: Optional[List[Dict[str, str]]] = None # [{email, first_name}]
async def delayed_send(emails: List[Dict[str, str]], scheduled_time: str, db: Session):
async def delayed_send(emails: List[Dict[str, str]], scheduled_time: str, db_session_factory):
try:
# Calculate delay
now = datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=2))) # Berlin Time Approx
# Calculate delay using Berlin Timezone
now = datetime.datetime.now(TZ_BERLIN)
target_h, target_m = map(int, scheduled_time.split(":"))
target_time = now.replace(hour=target_h, minute=target_m, second=0, microsecond=0)
@@ -31,27 +58,56 @@ async def delayed_send(emails: List[Dict[str, str]], scheduled_time: str, db: Se
target_time += datetime.timedelta(days=1)
delay_seconds = (target_time - now).total_seconds()
logger.info(f"Scheduling {len(emails)} emails for {scheduled_time} (in {delay_seconds} seconds)")
logger.info(f"Scheduling {len(emails)} emails for {scheduled_time} Berlin Time (in {delay_seconds} seconds)")
await asyncio.sleep(delay_seconds)
service = GmailService(db)
success_count = 0
for email_data in emails:
if service.send_email(email_data["to"], email_data["subject"], email_data["body"]):
success_count += 1
await asyncio.sleep(1) # Rate limiting
logger.info(f"Scheduled send complete: {success_count}/{len(emails)} success.")
# We need a fresh DB session for the background task
db = db_session_factory()
try:
service = GmailService(db)
success_count = 0
for email_data in emails:
if service.send_email(email_data["to"], email_data["subject"], email_data["body"]):
success_count += 1
await asyncio.sleep(1) # Rate limiting
logger.info(f"Scheduled send complete: {success_count}/{len(emails)} success.")
finally:
db.close()
except Exception as e:
logger.exception("Error in delayed_send background task")
@router.post("/send")
async def send_requests(data: SendReleaseRequest, background_tasks: BackgroundTasks, db: Session = Depends(get_db)):
# Store participant names for later (webhook)
if data.participants:
for p in data.participants:
email = p.get("email", "").strip().lower()
first_name = p.get("first_name", "").strip()
if email and first_name:
existing = db.query(ReleaseParticipant).filter(ReleaseParticipant.email == email).first()
if existing:
existing.first_name = first_name
else:
db.add(ReleaseParticipant(email=email, first_name=first_name))
db.commit()
if data.scheduled_time:
background_tasks.add_task(delayed_send, data.emails, data.scheduled_time, db)
# Pass a way to get a new session to the background task
from database import SessionLocal
# Log to history
db.add(ReleaseHistory(recipient_count=len(data.emails), scheduled_time=data.scheduled_time))
db.commit()
background_tasks.add_task(delayed_send, data.emails, data.scheduled_time, SessionLocal)
return {"status": "scheduled", "message": f"Versand für {data.scheduled_time} geplant."}
# Log immediate send to history
db.add(ReleaseHistory(recipient_count=len(data.emails), scheduled_time="Sofort"))
db.commit()
# Immediate send
service = GmailService(db)
success = 0
@@ -64,6 +120,11 @@ async def send_requests(data: SendReleaseRequest, background_tasks: BackgroundTa
return {"status": "success", "success": success, "failed": failed}
@router.get("/history")
def get_history(db: Session = Depends(get_db)):
history = db.query(ReleaseHistory).order_by(ReleaseHistory.timestamp.desc()).all()
return [{"id": h.id, "timestamp": h.timestamp.isoformat(), "recipient_count": h.recipient_count, "scheduled_time": h.scheduled_time} for h in history]
@router.get("/stats")
def get_stats(db: Session = Depends(get_db)):
total = db.query(DiscountCode).count()
@@ -71,6 +132,11 @@ def get_stats(db: Session = Depends(get_db)):
available = total - used
return {"total": total, "used": used, "available": available}
@router.get("/responses")
def get_responses(db: Session = Depends(get_db)):
responses = db.query(DiscountCode).filter(DiscountCode.is_used == 1).all()
return [{"email": r.assigned_to_email, "code": r.code, "used_at": r.used_at.isoformat()} for r in responses]
@router.post("/codes")
def upload_codes(data: CodesUpload, db: Session = Depends(get_db)):
codes_list = [c.strip() for c in data.codes.split(",") if c.strip()]
@@ -113,9 +179,12 @@ async def handle_webhook(request: Request, db: Session = Depends(get_db)):
free_code = db.query(DiscountCode).filter(DiscountCode.is_used == 0).first()
if not free_code:
logger.error("NO FREE DISCOUNT CODES LEFT!")
# Fallback logic: Notify admin?
return {"status": "error", "message": "No codes available"}
# Look up participant name
participant = db.query(ReleaseParticipant).filter(ReleaseParticipant.email == email).first()
first_name = participant.first_name if participant else "Ihr Lieben"
# Mark as used
free_code.is_used = 1
free_code.assigned_to_email = email
@@ -126,23 +195,16 @@ async def handle_webhook(request: Request, db: Session = Depends(get_db)):
service = GmailService(db)
subject = "Dankeschön für Eure Freigabe & Euer Rabattcode"
# HTML Signature from frontend
SIGNATURE_HTML = """
<br><br><br>
<div style="font-family: 'Verdana', sans-serif; font-size: 11px; color: #3D3C3F;">
<p><strong>Liebe Grüße,</strong><br>Euer Team von Kinderfotos Erding</p>
<p><strong>Zierl Fotografen GmbH</strong><br>
Anton-Bruckner-Straße 5<br>85435 Erding</p>
<p><a href="https://www.kinderfotos-erding.de" style="color: #3D3C3F; text-decoration: none;">www.kinderfotos-erding.de</a></p>
</div>
"""
# Image provided by user
INSTRUCTIONS_IMAGE_URL = "https://mail.google.com/mail/u/2?ui=2&ik=719adaa3c5&attid=0.1&permmsgid=msg-a:r7482671925923393616&th=196e322c399dbc7f&view=fimg&fur=ip&permmsgid=msg-a:r7482671925923393616&sz=s0-l75-ft&attbid=ANGjdJ9_U6ayMFgwbupt4HalTKO867IHx6N70eNbPfQmTLNzRXilJxI-n8a1gjM8xVcP5HEOgaVxfp3FnJPzTYEEYhK4gSU-Il_0a6OtzFYscp55_W4iyxuxjyPvK4&disp=emb&realattid=ii_maspzxv50&zw"
body_html = f"""
<p>Hallo,</p>
<p>vielen Dank für Eure Unterstützung und das Ausfüllen der Freigabe!</p>
<p>Als kleines Dankeschön hier Euer 25 € Rabattcode für Eure Bestellung:</p>
<p><strong style="font-size: 18px; color: #4F46E5; padding: 10px; border: 1px dashed #4F46E5; display: inline-block;">{free_code.code}</strong></p>
<p>Bitte wartet mit Eurer Bestellung, falls Ihr noch nicht bestellt habt, bis Ihr diesen Code an der Kasse einlösen könnt.</p>
<p>Hallo {first_name},</p>
<p>Vielen Dank nochmal für die Freigabe zur Veröffentlichung, das ist super nett von Euch!</p>
<p>Hier ist euer Gutscheincode über 25 Euro: <strong style="font-size: 18px; color: #4F46E5;">{free_code.code}</strong></p>
<p>Um den Gutschein einzugeben, musst du auf den Preis des Warenkorbs drücken (über dem Button zur Kasse gehen):</p>
<p><img src="{INSTRUCTIONS_IMAGE_URL}" alt="Anleitung Gutschein einlösen" style="max-width: 100%; border: 1px solid #ddd; border-radius: 8px;"></p>
<p>Liebe Grüße,<br>das Team von Kinderfotos Erding</p>
{SIGNATURE_HTML}
"""

View File

@@ -0,0 +1,44 @@
import sys
import os
sys.path.append('/app/fotograf-de-scraper/backend')
from database import SessionLocal, ReleaseParticipant, DiscountCode
from gmail_service import GmailService
from publish_request_api import SIGNATURE_HTML
def test_webhook_mail():
db = SessionLocal()
# Simulate data
test_email = "floke.com@gmail.com"
first_name = "Christian"
test_code = "M984AU-TEST"
# Simulate logic
service = GmailService(db)
subject = "Dankeschön für Eure Freigabe & Euer Rabattcode"
INSTRUCTIONS_IMAGE_URL = "https://mail.google.com/mail/u/2?ui=2&ik=719adaa3c5&attid=0.1&permmsgid=msg-a:r7482671925923393616&th=196e322c399dbc7f&view=fimg&fur=ip&permmsgid=msg-a:r7482671925923393616&sz=s0-l75-ft&attbid=ANGjdJ9_U6ayMFgwbupt4HalTKO867IHx6N70eNbPfQmTLNzRXilJxI-n8a1gjM8xVcP5HEOgaVxfp3FnJPzTYmEEyhK4gSU-Il_0a6OtzFYscp55_W4iyxuxjyPvK4&disp=emb&realattid=ii_maspzxv50&zw"
body_html = f"""
<p>Hallo {first_name},</p>
<p>Vielen Dank nochmal für die Freigabe zur Veröffentlichung, das ist super nett von Euch!</p>
<p>Hier ist euer Gutscheincode über 25 Euro: <strong style="font-size: 18px; color: #4F46E5;">{test_code}</strong></p>
<p>Um den Gutschein einzugeben, musst du auf den Preis des Warenkorbs drücken (über dem Button zur Kasse gehen):</p>
<p><img src="{INSTRUCTIONS_IMAGE_URL}" alt="Anleitung Gutschein einlösen" style="max-width: 100%; border: 1px solid #ddd; border-radius: 8px;"></p>
<p>Liebe Grüße,<br>das Team von Kinderfotos Erding</p>
{SIGNATURE_HTML}
"""
print(f"Sende Test-E-Mail an {test_email}...")
success = service.send_email(test_email, subject, body_html)
if success:
print("✅ E-Mail erfolgreich gesendet! Bitte prüfe dein Postfach.")
else:
print("❌ Fehler beim Senden. (Stelle sicher, dass Gmail Authentifiziert ist).")
db.close()
if __name__ == "__main__":
test_webhook_mail()

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -5,8 +5,8 @@
<link rel="icon" type="image/svg+xml" href="/fotograf-de/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Fotograf.de ERP</title>
<script type="module" crossorigin src="/fotograf-de/assets/index-0MFhRTsS.js"></script>
<link rel="stylesheet" crossorigin href="/fotograf-de/assets/index-uaZdwVxZ.css">
<script type="module" crossorigin src="/fotograf-de/assets/index-DnGj5v5p.js"></script>
<link rel="stylesheet" crossorigin href="/fotograf-de/assets/index-BnIZj8RP.css">
</head>
<body>
<div id="root"></div>

File diff suppressed because it is too large Load Diff

117
readme.md
View File

@@ -295,3 +295,120 @@ Investierte Zeit in dieser Session: 05:32
Arbeitszusammenfassung:
Keine Zusammenfassung angegeben.
```
## 🤖 Status-Update (2026-04-18 00:14 Berlin Time)
```yaml
Investierte Zeit in dieser Session: 02:12
Arbeitszusammenfassung:
Keine Zusammenfassung angegeben.
```
## 🤖 Status-Update (2026-04-18 15:09 Berlin Time)
```yaml
Investierte Zeit in dieser Session: 01:57
Arbeitszusammenfassung:
Keine Zusammenfassung angegeben.
```
## 🤖 Status-Update (2026-04-18 15:58 Berlin Time)
```yaml
Investierte Zeit in dieser Session: 00:49
Arbeitszusammenfassung:
Keine Zusammenfassung angegeben.
```
## 🤖 Status-Update (2026-04-18 22:58 Berlin Time)
```yaml
Investierte Zeit in dieser Session: 01:21
Arbeitszusammenfassung:
Keine Zusammenfassung angegeben.
```
## 🤖 Status-Update (2026-05-04 08:53 Berlin Time)
```yaml
Investierte Zeit in dieser Session: 00:39
Arbeitszusammenfassung:
Keine Zusammenfassung angegeben.
```
## 🤖 Status-Update (2026-05-15 20:56 Berlin Time)
```yaml
Investierte Zeit in dieser Session: 00:28
Arbeitszusammenfassung:
Keine Zusammenfassung angegeben.
```
## 🤖 Status-Update (2026-05-15 22:07 Berlin Time)
```yaml
Investierte Zeit in dieser Session: 01:11
Arbeitszusammenfassung:
Keine Zusammenfassung angegeben.
```
## 🤖 Status-Update (2026-05-15 22:43 Berlin Time)
```yaml
Investierte Zeit in dieser Session: 00:35
Arbeitszusammenfassung:
Keine Zusammenfassung angegeben.
```
## 🤖 Status-Update (2026-05-15 23:06 Berlin Time)
```yaml
Investierte Zeit in dieser Session: 00:22
Arbeitszusammenfassung:
Keine Zusammenfassung angegeben.
```
## 🤖 Status-Update (2026-05-15 23:10 Berlin Time)
```yaml
Investierte Zeit in dieser Session: 00:03
Arbeitszusammenfassung:
Keine Zusammenfassung angegeben.
```
## 🤖 Status-Update (2026-05-15 23:12 Berlin Time)
```yaml
Investierte Zeit in dieser Session: 00:02
Arbeitszusammenfassung:
Keine Zusammenfassung angegeben.
```
## 🤖 Status-Update (2026-05-16 13:33 Berlin Time)
```yaml
Investierte Zeit in dieser Session: 00:17
Arbeitszusammenfassung:
Keine Zusammenfassung angegeben.
```
## 🤖 Status-Update (2026-05-16 13:54 Berlin Time)
```yaml
Investierte Zeit in dieser Session: 00:06
Arbeitszusammenfassung:
Keine Zusammenfassung angegeben.
```