diff --git a/.dev_session/SESSION_INFO b/.dev_session/SESSION_INFO
index e6ca6962b..fe46ad138 100644
--- a/.dev_session/SESSION_INFO
+++ b/.dev_session/SESSION_INFO
@@ -1 +1 @@
-{"task_id": "35588f42-8544-800a-9eb7-e62ce4ce30e4", "token": "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", "readme_path": "readme.md", "session_start_time": "2026-05-15T21:06:40.526244"}
\ No newline at end of file
+{"task_id": "35588f42-8544-800a-9eb7-e62ce4ce30e4", "token": "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", "readme_path": "readme.md", "session_start_time": "2026-05-15T21:10:17.162200"}
\ No newline at end of file
diff --git a/patch_cleaning.py b/patch_cleaning.py
deleted file mode 100644
index f34b447a8..000000000
--- a/patch_cleaning.py
+++ /dev/null
@@ -1,18 +0,0 @@
-import sys
-
-with open('fotograf-de-scraper/frontend/src/App.tsx', 'r') as f:
- content = f.read()
-
-# Robust cleaning logic
-new_cleaning = ".replace(/\\(JOB\\d+\\)/gi, '').replace(/Kindergarten/gi, '').replace(/\\d{4}/g, '').replace(/\\s+/g, ' ').trim()"
-
-# Old cleaning pattern (multi-line)
-old_pattern = """selectedJob.name.replace(/\\(JOB\\d+\\)\\s*/, '').replace(/Kindergarten\\s+/gi, '').replace(/\\s+\\d{4}$/, '').trim()"""
-
-if old_pattern in content:
- content = content.replace(old_pattern, new_cleaning)
- with open('fotograf-de-scraper/frontend/src/App.tsx', 'w') as f:
- f.write(content)
- print("Cleaning logic updated")
-else:
- print("Old cleaning pattern not found")
diff --git a/patch_cleaning_release.py b/patch_cleaning_release.py
deleted file mode 100644
index bc8fdf33b..000000000
--- a/patch_cleaning_release.py
+++ /dev/null
@@ -1,19 +0,0 @@
-import sys
-
-with open('fotograf-de-scraper/frontend/src/App.tsx', 'r') as f:
- content = f.read()
-
-new_cleaning = ".replace(/\\(JOB\\d+\\)/gi, '').replace(/Kindergarten/gi, '').replace(/\\d{4}/g, '').replace(/\\s+/g, ' ').trim()"
-
-old_pattern = """.replace(/\\(JOB\\d+\\)\\s*/, '')
- .replace(/Kindergarten\\s+/gi, '') // Remove "Kindergarten" prefix
- .replace(/\\s+\\d{4}$/, '') // Remove year at the end
- .trim()"""
-
-if old_pattern in content:
- content = content.replace(old_pattern, new_cleaning)
- with open('fotograf-de-scraper/frontend/src/App.tsx', 'w') as f:
- f.write(content)
- print("Cleaning logic updated for release feature")
-else:
- print("Old cleaning pattern not found")
diff --git a/patch_filter.py b/patch_filter.py
deleted file mode 100644
index 834fae23e..000000000
--- a/patch_filter.py
+++ /dev/null
@@ -1,39 +0,0 @@
-import sys
-
-with open('fotograf-de-scraper/backend/main.py', 'r') as f:
- content = f.read()
-
-old_code = """ # 1. Get emails that have ALREADY purchased anything (in ANY job we have in DB)
- purchased_emails = set()
- if exclude_purchased_emails:
- from sqlalchemy import or_
- # We look globally across the whole job_participants table
- purchased_results = db.query(JobParticipant.email_eltern).filter(
- 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 DB to exclude.")"""
-
-new_code = """ # 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.")"""
-
-if old_code in content:
- content = content.replace(old_code, new_code)
- with open('fotograf-de-scraper/backend/main.py', 'w') as f:
- f.write(content)
- print("Filter logic patched successfully")
-else:
- print("Old code not found")
diff --git a/patch_frontend.py b/patch_frontend.py
deleted file mode 100644
index ccbccffd7..000000000
--- a/patch_frontend.py
+++ /dev/null
@@ -1,15 +0,0 @@
-import sys
-
-with open('fotograf-de-scraper/frontend/src/App.tsx', 'r') as f:
- content = f.read()
-
-old_body = """ const [emailBody, setEmailBody] = useState("Hallo {Name Käufer},
deine Fotos sind fertig und warten auf dich! Kopiere einfach deinen Zugangscode und klicke auf den Link zum Shop, um dich einzuloggen:
{LinksHTML}
Viel Spaß beim Anschauen!");"""
-new_body = """ const [emailBody, setEmailBody] = useState("Hallo {Name Käufer},
deine Fotos sind fertig und warten auf dich! Klicke einfach auf die Links unten, um direkt zu den Galerien zu gelangen:
{LinksHTML}
Viel Spaß beim Anschauen!");"""
-
-if old_body in content:
- content = content.replace(old_body, new_body)
- with open('fotograf-de-scraper/frontend/src/App.tsx', 'w') as f:
- f.write(content)
- print("Frontend patched")
-else:
- print("Frontend code not found")
diff --git a/patch_frontend_email.py b/patch_frontend_email.py
deleted file mode 100644
index 2896f09ab..000000000
--- a/patch_frontend_email.py
+++ /dev/null
@@ -1,60 +0,0 @@
-import sys
-
-with open('fotograf-de-scraper/frontend/src/App.tsx', 'r') as f:
- content = f.read()
-
-# Replace the generic subject
-old_subject = 'const [emailSubject, setEmailSubject] = useState("Fotos von {Kindernamen}");'
-new_subject = 'const [emailSubject, setEmailSubject] = useState("Die Kindergarten-Fotos von {Kindernamen} sind da! 📸");'
-
-# Replace the generic body
-old_body = 'const [emailBody, setEmailBody] = useState("Hallo {Name Käufer},
deine Fotos sind fertig und warten auf dich! Klicke einfach auf die Links unten, um direkt zu den Galerien zu gelangen:
{LinksHTML}
Viel Spaß beim Anschauen!");'
-new_body = 'const [emailBody, setEmailBody] = useState("Hallo {Name Käufer},
ich hoffe, es geht euch gut! 😊
Wir haben die wunderschönen Bilder vom Fotoshooting fertiggestellt. Die Fotos von {Kindernamen} sind wirklich ganz toll geworden und warten nun darauf, von euch entdeckt zu werden!
Klicke einfach auf den untenstehenden Link, um direkt, sicher und bequem zu eurer persönlichen Galerie zu gelangen:
{LinksHTML}
Wenn ihr Fragen habt, meldet euch gerne jederzeit bei mir.
Viel Freude beim Anschauen und Aussuchen der Erinnerungen!");'
-
-if old_subject in content and old_body in content:
- content = content.replace(old_subject, new_subject)
- content = content.replace(old_body, new_body)
-
- # We also need to add dynamic Einrichtungsname replacement to the parser
- old_parser = """ let subject = emailSubject.replace(/{Kindernamen}/g, row["Kindernamen"]);
- let body = emailBody
- .replace(/{Name Käufer}/g, row["Name Käufer"])
- .replace(/{Kindernamen}/g, row["Kindernamen"])
- .replace(/{LinksHTML}/g, row["LinksHTML"])
- .replace(/\\n/g, "
");"""
-
- new_parser = """ let einrichtung = selectedJob
- ? selectedJob.name.replace(/\\(JOB\\d+\\)\\s*/, '').replace(/Kindergarten\\s+/gi, '').replace(/\\s+\\d{4}$/, '').trim()
- : "eurer Einrichtung";
- let subject = emailSubject
- .replace(/{Kindernamen}/g, row["Kindernamen"])
- .replace(/{Einrichtung}/g, einrichtung);
- let body = emailBody
- .replace(/{Name Käufer}/g, row["Name Käufer"])
- .replace(/{Kindernamen}/g, row["Kindernamen"])
- .replace(/{Einrichtung}/g, einrichtung)
- .replace(/{LinksHTML}/g, row["LinksHTML"])
- .replace(/\\n/g, "
");"""
-
- content = content.replace(old_parser, new_parser)
-
- # Note: We need to replace the parser in the send function too
- old_sender = """ let subject = emailSubject.replace(/{Kindernamen}/g, row["Kindernamen"]);
- let body = emailBody
- .replace(/{Name Käufer}/g, row["Name Käufer"])
- .replace(/{Kindernamen}/g, row["Kindernamen"])
- .replace(/{LinksHTML}/g, row["LinksHTML"])
- .replace(/\\n/g, "
");"""
-
- content = content.replace(old_sender, new_parser)
-
- # Add {Einrichtung} to the hint text in the UI
- old_hint = 'Platzhalter: {Name Käufer}, {Kindernamen}, {LinksHTML}'
- new_hint = 'Platzhalter: {Name Käufer}, {Kindernamen}, {Einrichtung}, {LinksHTML}'
- content = content.replace(old_hint, new_hint)
-
- with open('fotograf-de-scraper/frontend/src/App.tsx', 'w') as f:
- f.write(content)
- print("Frontend email template patched")
-else:
- print("Frontend code not found")
diff --git a/patch_frontend_email2.py b/patch_frontend_email2.py
deleted file mode 100644
index 2e06f324c..000000000
--- a/patch_frontend_email2.py
+++ /dev/null
@@ -1,19 +0,0 @@
-import sys
-
-with open('fotograf-de-scraper/frontend/src/App.tsx', 'r') as f:
- content = f.read()
-
-# Fix default text to include the new parameter
-old_subject = 'const [emailSubject, setEmailSubject] = useState("Die Kindergarten-Fotos von {Kindernamen} sind da! 📸");'
-new_subject = 'const [emailSubject, setEmailSubject] = useState("Eure Bilder aus {Einrichtung} sind da! 📸");'
-
-old_body = 'const [emailBody, setEmailBody] = useState("Hallo {Name Käufer},
ich hoffe, es geht euch gut! 😊
Wir haben die wunderschönen Bilder vom Fotoshooting fertiggestellt. Die Fotos von {Kindernamen} sind wirklich ganz toll geworden und warten nun darauf, von euch entdeckt zu werden!
Klicke einfach auf den untenstehenden Link, um direkt, sicher und bequem zu eurer persönlichen Galerie zu gelangen:
{LinksHTML}
Wenn ihr Fragen habt, meldet euch gerne jederzeit bei mir.
Viel Freude beim Anschauen und Aussuchen der Erinnerungen!");'
-new_body = 'const [emailBody, setEmailBody] = useState("Hallo {Name Käufer},
ich hoffe, es geht euch gut! 😊
Wir haben die wunderschönen Bilder vom Fotoshooting in {Einrichtung} fertiggestellt. Die Fotos von {Kindernamen} sind wirklich ganz toll geworden und warten nun darauf, von euch entdeckt zu werden!
Klicke einfach auf den untenstehenden Link, um direkt, sicher und bequem zu eurer persönlichen Galerie zu gelangen:
{LinksHTML}
Wenn ihr Fragen habt, meldet euch gerne jederzeit bei mir.
Viel Freude beim Anschauen und Aussuchen der Erinnerungen!");'
-
-content = content.replace(old_subject, new_subject)
-content = content.replace(old_body, new_body)
-
-with open('fotograf-de-scraper/frontend/src/App.tsx', 'w') as f:
- f.write(content)
-print("Frontend email template patched 2")
-
diff --git a/patch_frontend_final.py b/patch_frontend_final.py
deleted file mode 100644
index e05d4b892..000000000
--- a/patch_frontend_final.py
+++ /dev/null
@@ -1,21 +0,0 @@
-import sys
-
-with open('fotograf-de-scraper/frontend/src/App.tsx', 'r') as f:
- content = f.read()
-
-#Approved Subject
-old_subject = 'const [emailSubject, setEmailSubject] = useState("Eure Bilder aus {Einrichtung} sind da! 📸");'
-new_subject = 'const [emailSubject, setEmailSubject] = useState("{Einrichtung}: Bilder von {Kindernamen} sind da! 📸");'
-
-#Approved Body
-old_body = 'const [emailBody, setEmailBody] = useState("Hallo {Name Käufer},
ich hoffe, es geht euch gut! 😊
Wir haben die wunderschönen Bilder vom Fotoshooting in {Einrichtung} fertiggestellt. Die Fotos von {Kindernamen} sind wirklich ganz toll geworden und warten nun darauf, von euch entdeckt zu werden!
Klicke einfach auf den untenstehenden Link, um direkt, sicher und bequem zu eurer persönlichen Galerie zu gelangen:
{LinksHTML}
Wenn ihr Fragen habt, meldet euch gerne jederzeit bei mir.
Viel Freude beim Anschauen und Aussuchen der Erinnerungen!");'
-new_body = 'const [emailBody, setEmailBody] = useState("Hallo {Name Käufer},
wir haben die Bilder vom Fototag ({Einrichtung}) fertiggestellt. Die Fotos von {Kindernamen} sind wirklich ganz toll geworden und warten nun darauf, von euch entdeckt zu werden!
Klicke einfach auf den untenstehenden Link, um direkt, sicher und bequem zu eurer persönlichen Galerie zu gelangen:
{LinksHTML}
Wenn ihr Fragen habt, meldet euch gerne jederzeit bei uns.
Viel Freude beim Anschauen und Aussuchen der Erinnerungen!");'
-
-if old_subject in content and old_body in content:
- content = content.replace(old_subject, new_subject)
- content = content.replace(old_body, new_body)
- with open('fotograf-de-scraper/frontend/src/App.tsx', 'w') as f:
- f.write(content)
- print("Frontend email template finalized")
-else:
- print("Old template strings not found")
diff --git a/patch_readme.py b/patch_readme.py
deleted file mode 100644
index cc55a997b..000000000
--- a/patch_readme.py
+++ /dev/null
@@ -1,15 +0,0 @@
-import sys
-
-with open('fotograf-de-scraper/README.md', 'r') as f:
- content = f.read()
-
-old_text = "* **Quick-Login Automation:** Die Login-Links (`https://www.kinderfotos-erding.de/a/{code}`) werden automatisch generiert."
-new_text = "* **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."
-
-if old_text in content:
- content = content.replace(old_text, new_text)
- with open('fotograf-de-scraper/README.md', 'w') as f:
- f.write(content)
- print("README patched")
-else:
- print("Old text not found in README")
diff --git a/patch_reminder.py b/patch_reminder.py
deleted file mode 100644
index 6498a914e..000000000
--- a/patch_reminder.py
+++ /dev/null
@@ -1,130 +0,0 @@
-import sys
-
-with open('fotograf-de-scraper/backend/main.py', 'r') as f:
- content = f.read()
-
-old_code = """ # 3. Aggregate results by Email
- aggregation = {}
- missing_links_count = 0
-
- for c in candidates:
- email = c.email_eltern.lower()
-
- # Skip if this email already has a purchase for ANOTHER child
- if exclude_purchased_emails and email in purchased_emails:
- continue
-
- # STRICT LINK CHECK: If we don't have a scraped Quick Login URL, skip this child.
- # We don't want to send broken /login/access/ links.
- if not c.quick_login_url:
- missing_links_count += 1
- continue
-
- 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)
-
- # Add Quick Login Link (Guaranteed to exist here)
- html_link = f'Fotos von {child_label}'
- 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}
" 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} Kinder ignoriert, da Quick-Login-Link fehlt. Bitte vorher 'Daten abgleichen' drücken!)"
-
- task_store[task_id] = {"""
-
-new_code = """ # 3. Aggregate results by Email
- aggregation = {}
- missing_links_count = 0
-
- 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()
-
- # Skip if this email already has a purchase for ANOTHER child
- if exclude_purchased_emails and email in purchased_emails:
- continue
-
- 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'{link_text}'
- 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}
" 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] = {"""
-
-if old_code in content:
- content = content.replace(old_code, new_code)
- with open('fotograf-de-scraper/backend/main.py', 'w') as f:
- f.write(content)
- print("Patched successfully")
-else:
- print("Old code not found")