[34288f42] Keine Zusammenfassung angegeben.
Keine Zusammenfassung angegeben.
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user