[34288f42] Keine Zusammenfassung angegeben.

Keine Zusammenfassung angegeben.
This commit is contained in:
2026-04-14 14:09:58 +00:00
parent 0cca30a956
commit 1a3568f69e
14 changed files with 347 additions and 48 deletions

View File

@@ -1274,7 +1274,10 @@ async def generate_siblings_list(job_id: str, account_type: str, event_type_name
final_storage = os.path.join("/tmp", output_pdf_name)
shutil.copy(output_pdf_path, final_storage)
return FileResponse(path=final_storage, filename=output_pdf_name, media_type="application/pdf")
# Since the frontend has trouble triggering a blob download, return a JSON with a download link
download_url = f"/api/jobs/download-qr/{job_id}/{output_pdf_name}"
return JSONResponse(content={"status": "success", "download_url": download_url, "filename": output_pdf_name})
except HTTPException as he:
raise he
@@ -1284,3 +1287,102 @@ async def generate_siblings_list(job_id: str, account_type: str, event_type_name
finally:
if driver: driver.quit()
@app.post("/api/jobs/{job_id}/siblings-qr-cards")
async def generate_siblings_qr_endpoint(
job_id: str,
account_type: str,
pdf_file: UploadFile = File(...),
db: Session = Depends(get_db)
):
logger.info(f"API Request: Generate siblings QR cards for job {job_id}")
username = os.getenv(f"{account_type.upper()}_USER")
password = os.getenv(f"{account_type.upper()}_PW")
with tempfile.TemporaryDirectory() as temp_dir:
input_pdf_path = os.path.join(temp_dir, "input.pdf")
with open(input_pdf_path, "wb") as buffer:
shutil.copyfileobj(pdf_file.file, buffer)
driver = setup_driver(download_path=temp_dir)
try:
if not login(driver, username, password):
raise HTTPException(status_code=401, detail="Login failed.")
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)
export_btn = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, SELECTORS["export_dropdown"])))
driver.execute_script("arguments[0].scrollIntoView(true);", export_btn)
time.sleep(1)
driver.execute_script("arguments[0].click();", export_btn)
time.sleep(2)
try:
csv_btn = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, SELECTORS["export_csv_link"])))
driver.execute_script("arguments[0].click();", csv_btn)
except TimeoutException:
raise HTTPException(status_code=500, detail="CSV Export Button nicht gefunden.")
timeout = 45
start_time = time.time()
csv_file = None
while time.time() - start_time < timeout:
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 HTTPException(status_code=500, detail="CSV Download fehlgeschlagen.")
output_pdf_name = f"Geschwister_QR_{job_id}.pdf"
output_pdf_path = os.path.join(temp_dir, output_pdf_name)
from siblings_logic import get_sibling_families_from_csv
# Fetch Calendly events to exclude those who already have a meeting
api_token = os.getenv("CALENDLY_TOKEN")
from qr_generator import get_calendly_events_raw
try:
calendly_events = get_calendly_events_raw(api_token, event_type_name=None)
except:
calendly_events = []
families = get_sibling_families_from_csv(csv_file, calendly_events=calendly_events)
if not families:
raise HTTPException(status_code=404, detail="Keine Geschwisterkinder für QR-Karten gefunden.")
from qr_generator import generate_siblings_qr_overlay
generate_siblings_qr_overlay(input_pdf_path, output_pdf_path, families)
final_storage = os.path.join("/tmp", output_pdf_name)
shutil.copy(output_pdf_path, final_storage)
# Since the frontend has trouble triggering a blob download, return a JSON with a download link
download_url = f"/api/jobs/download-qr/{job_id}/{output_pdf_name}"
return JSONResponse(content={"status": "success", "download_url": download_url, "filename": output_pdf_name})
except HTTPException as he:
raise he
except Exception as e:
logger.exception("Error generating siblings QR cards")
raise HTTPException(status_code=500, detail=str(e))
finally:
if driver: driver.quit()
@app.get("/api/jobs/download-qr/{job_id}/{filename}")
async def download_generated_qr(job_id: str, filename: str):
file_path = os.path.join("/tmp", filename)
if os.path.exists(file_path):
return FileResponse(path=file_path, filename=filename, media_type="application/pdf")
raise HTTPException(status_code=404, detail="File not found")

View File

@@ -292,3 +292,62 @@ def overlay_text_on_pdf(base_pdf_path: str, output_pdf_path: str, texts: list):
writer.write(output_file)
logger.info(f"Successfully generated overlaid PDF at {output_pdf_path}")
def generate_siblings_qr_overlay(base_pdf_path: str, output_pdf_path: str, families: list):
import io
from PyPDF2 import PdfReader, PdfWriter
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
import os
font_path = os.path.join(os.path.dirname(__file__), "assets", "OpenSans-Regular.ttf")
if os.path.exists(font_path):
pdfmetrics.registerFont(TTFont('OpenSans', font_path))
font_name = 'OpenSans'
else:
font_name = 'Helvetica'
mm_to_pt = 2.83465
page_width, page_height = A4
x_pos = 72 * mm_to_pt
y_pos_1 = page_height - (31 * mm_to_pt)
y_pos_2 = page_height - (180 * mm_to_pt)
reader = PdfReader(base_pdf_path)
writer = PdfWriter()
family_idx = 0
total_families = len(families)
for i in range(len(reader.pages)):
page = reader.pages[i]
if family_idx < total_families:
packet = io.BytesIO()
c = canvas.Canvas(packet, pagesize=A4)
c.setFont(font_name, 11)
# First card on the page
if family_idx < total_families:
text_top = f"Geschwisterbilder Familie {families[family_idx]['nachname']}"
c.drawString(x_pos, y_pos_1, text_top)
family_idx += 1
# Second card on the page
if family_idx < total_families:
text_bottom = f"Geschwisterbilder Familie {families[family_idx]['nachname']}"
c.drawString(x_pos, y_pos_2, text_bottom)
family_idx += 1
c.save()
packet.seek(0)
overlay_pdf = PdfReader(packet)
page.merge_page(overlay_pdf.pages[0])
writer.add_page(page)
with open(output_pdf_path, "wb") as output_file:
writer.write(output_file)

View File

@@ -26,7 +26,7 @@ def generate_siblings_pdf_from_csv(csv_path: str, institution: str, calendly_eve
except:
raise Exception("CSV konnte nicht gelesen werden.")
df.columns = df.columns.str.strip().str.replace("\"", "")
df.columns = df.columns.str.strip().str.replace('"', "")
# Identify Email Column
email_col = next((c for c in df.columns if "email" in c.lower()), None)
@@ -56,8 +56,6 @@ def generate_siblings_pdf_from_csv(csv_path: str, institution: str, calendly_eve
try:
start_dt = datetime.datetime.fromisoformat(event['start_time'].replace('Z', '+00:00'))
start_dt = start_dt.astimezone(ZoneInfo("Europe/Berlin"))
# Allow all events for siblings logic, regardless of date, just to be sure we match them
calendly_map[event['invitee_email'].lower().strip()] = start_dt.strftime("%d.%m. %H:%M")
except:
pass
@@ -123,3 +121,63 @@ def generate_siblings_pdf_from_csv(csv_path: str, institution: str, calendly_eve
with open(output_path, "wb") as f:
f.write(pdf)
logger.info(f"Siblings PDF saved to {output_path}")
def get_sibling_families_from_csv(csv_path: str, calendly_events: list = None) -> list:
df = None
for sep in [";", ","]:
try:
test_df = pd.read_csv(csv_path, sep=sep, encoding="utf-8-sig", nrows=5)
if len(test_df.columns) > 1:
df = pd.read_csv(csv_path, sep=sep, encoding="utf-8-sig")
break
except Exception as e:
continue
if df is None:
try:
df = pd.read_csv(csv_path, sep=";", encoding="latin1")
except:
raise Exception("CSV konnte nicht gelesen werden.")
df.columns = df.columns.str.strip().str.replace('"', "")
email_col = next((c for c in df.columns if "email" in c.lower()), None)
if not email_col:
email_col = next((c for c in df.columns if "e-mail" in c.lower()), None)
if not email_col:
return []
lastname_col = next((c for c in df.columns if "nachname" in c.lower()), None)
# Build Calendly Email Set for filtering
booked_emails = set()
if calendly_events:
for event in calendly_events:
email = event.get('invitee_email', '').lower().strip()
if email:
booked_emails.add(email)
families_dict = defaultdict(list)
df = df.fillna("")
for _, row in df.iterrows():
email = str(row[email_col]).strip().lower()
if email and "@" in email:
families_dict[email].append(row)
families = []
for email, rows in families_dict.items():
if len(rows) > 1: # SIBLINGS DETECTED
# FILTER OUT if they already have an appointment
if email in booked_emails:
logger.info(f"Family {email} already has Calendly appointment, skipping QR card.")
continue
family_last_name = str(rows[0][lastname_col]).strip() if lastname_col else "Unbekannt"
families.append({
"nachname": family_last_name
})
families.sort(key=lambda x: x["nachname"])
return families