From ec877ef65b27caead55465bcbee26a459581ca59 Mon Sep 17 00:00:00 2001 From: Floke Date: Sat, 21 Mar 2026 13:35:16 +0000 Subject: [PATCH] [32788f42] Update QR card generator: adjust Y-coordinates, add timezone support, and render checkboxes --- fotograf-de-scraper/backend/qr_generator.py | 69 ++++++++++++++------- 1 file changed, 48 insertions(+), 21 deletions(-) diff --git a/fotograf-de-scraper/backend/qr_generator.py b/fotograf-de-scraper/backend/qr_generator.py index bd3f7c8be..52a127613 100644 --- a/fotograf-de-scraper/backend/qr_generator.py +++ b/fotograf-de-scraper/backend/qr_generator.py @@ -79,21 +79,25 @@ def get_calendly_events(api_token: str, start_time: str, end_time: str, event_ty """ Fetches events from Calendly API for the current user within a time range. """ + from zoneinfo import ZoneInfo + raw_data = get_calendly_events_raw(api_token, start_time, end_time, event_type_name) formatted_data = [] for item in raw_data: - # Parse start time + # Parse start time from UTC start_dt = datetime.datetime.fromisoformat(item['start_time'].replace('Z', '+00:00')) + # Convert to Europe/Berlin (CET/CEST) + start_dt = start_dt.astimezone(ZoneInfo("Europe/Berlin")) # Format as HH:MM time_str = start_dt.strftime('%H:%M') name = item['invitee_name'] # Extract specific answers from the Calendly form - # We look for the number of children and any additional notes num_children = "" additional_notes = "" + has_consent = False questions_and_answers = item.get('questions_and_answers', []) for q_a in questions_and_answers: @@ -103,12 +107,20 @@ def get_calendly_events(api_token: str, start_time: str, end_time: str, event_ty if "wie viele kinder" in q_text: num_children = a_text elif "nachricht" in q_text or "anmerkung" in q_text: - # If there's a custom notes field in some events additional_notes = a_text + elif "schöne bilder" in q_text and "website veröffentlichen" in q_text: + if "ja, gerne" in a_text.lower(): + has_consent = True - # Construct the final string: "Name, X Kinder // HH:MM Uhr (Notes)" + # Construct the final string: "[☑] Name, X Kinder // HH:MM Uhr (Notes)" # matching: Halime Türe, 1 Kind // 12:00 Uhr - final_text = f"{name}" + final_text = "" + if has_consent: + # We use a placeholder character or string that we will handle in overlay_text_on_pdf + # because standard Helvetica doesn't support Unicode checkbox. + final_text += "☑ " + + final_text += f"{name}" if num_children: final_text += f", {num_children}" @@ -124,13 +136,9 @@ def get_calendly_events(api_token: str, start_time: str, end_time: str, event_ty def overlay_text_on_pdf(base_pdf_path: str, output_pdf_path: str, texts: list): - """ - Overlays text from the `texts` list onto a base PDF. - Expects two text entries per page (top and bottom element). - Coordinates are in mm from bottom-left (ReportLab default). - Target: - Element 1: X: 72mm, Y: 22mm (from top-left in user spec, need to convert) - Element 2: X: 72mm, Y: 171mm (from top-left in user spec, need to convert) + # Target: + # Element 1: X: 72mm, Y: 22mm + 9mm = 31mm + # Element 2: X: 72mm, Y: 171mm + 9mm = 180mm """ # Convert mm to points (1 mm = 2.83465 points) @@ -141,12 +149,12 @@ def overlay_text_on_pdf(base_pdf_path: str, output_pdf_path: str, texts: list): # User coordinates are from top-left. # ReportLab uses bottom-left as (0,0). - # Element 1 (Top): X = 72mm, Y = 22mm (from top) -> Y = page_height - 22mm - # Element 2 (Bottom): X = 72mm, Y = 171mm (from top) -> Y = page_height - 171mm + # Element 1 (Top): X = 72mm, Y = 31mm (from top) -> Y = page_height - 31mm + # Element 2 (Bottom): X = 72mm, Y = 180mm (from top) -> Y = page_height - 180mm x_pos = 72 * mm_to_pt - y_pos_1 = page_height - (22 * mm_to_pt) - y_pos_2 = page_height - (171 * 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() @@ -163,19 +171,38 @@ def overlay_text_on_pdf(base_pdf_path: str, output_pdf_path: str, texts: list): for page_idx, pair in enumerate(text_pairs): if page_idx >= total_pages: - break # Should be caught by the truncation above, but safety first + break # Safety first # Create a new blank page in memory to draw the text packet = io.BytesIO() can = canvas.Canvas(packet, pagesize=A4) # Draw the text. - can.setFont("Helvetica", 12) - + # We handle the "☑" character manually since Helvetica might not support it. + def draw_text_with_checkbox(can, x, y, text): + current_x = x + if text.startswith("☑ "): + # Draw a checkbox manually + size = 10 + # Draw box (baseline adjustment to align with text) + can.rect(x, y - 1, size, size) + # Draw checkmark + can.setLineWidth(1.5) + can.line(x + 2, y + 3, x + 4.5, y + 0.5) + can.line(x + 4.5, y + 0.5, x + 8.5, y + 7) + can.setLineWidth(1) + + # Move text X position to the right + current_x += size + 5 + text = text[2:] # Remove the "☑ " from string + + can.setFont("Helvetica", 12) + can.drawString(current_x, y, text) + if len(pair) > 0: - can.drawString(x_pos, y_pos_1, pair[0]) + draw_text_with_checkbox(can, x_pos, y_pos_1, pair[0]) if len(pair) > 1: - can.drawString(x_pos, y_pos_2, pair[1]) + draw_text_with_checkbox(can, x_pos, y_pos_2, pair[1]) can.save() packet.seek(0)