diff --git a/list_generator.py b/list_generator.py index 905aebb3..43296824 100644 --- a/list_generator.py +++ b/list_generator.py @@ -47,9 +47,44 @@ def get_docs_service_with_service_account(): return None def create_google_doc_from_csv(service): - # ... (CSV-Verarbeitung bleibt gleich) ... + """Liest CSV, verarbeitet Daten und erstellt/befüllt das Google Doc.""" + kinder_nach_gruppen = collections.defaultdict(list) - # 1. Neues Google Doc erstellen (bleibt gleich) + try: + with open(CSV_FILENAME, mode='r', encoding='utf-8-sig', newline='') as csvfile: + reader = csv.DictReader(csvfile, delimiter=';') + for row in reader: + vorname = row.get('Vorname', '').strip() + nachname = row.get('Nachname', '').strip() + gruppe_original = row.get('Gruppe', '').strip() + + if not vorname or not nachname or not gruppe_original: + print(f"Warnung: Zeile übersprungen wegen fehlender Daten: {row}") + continue + kinder_nach_gruppen[gruppe_original].append({ + 'Nachname': nachname, + 'Vorname': vorname + }) + except FileNotFoundError: + print(f"FEHLER: Die Datei '{CSV_FILENAME}' wurde nicht gefunden.") + return None + except Exception as e: + print(f"FEHLER beim Lesen der CSV-Datei: {e}") + return None + + if not kinder_nach_gruppen: + print("Keine Daten aus der CSV-Datei geladen.") + return None + + # Kinder innerhalb jeder Gruppe sortieren + for gruppe_key in kinder_nach_gruppen: # Geändert von 'gruppe' zu 'gruppe_key' um Verwechslung zu vermeiden + kinder_nach_gruppen[gruppe_key].sort(key=lambda x: (x['Nachname'].lower(), x['Vorname'].lower())) + + # ***** KORREKTUR HIER: sorted_gruppen_namen definieren ***** + sorted_gruppen_namen = sorted(kinder_nach_gruppen.keys()) + stand_zeit = datetime.now().strftime("%d.%m.%Y %H:%M Uhr") + + # 1. Neues Google Doc erstellen try: doc_body = {'title': GOOGLE_DOC_TITLE} doc = service.documents().create(body=doc_body).execute() @@ -63,7 +98,7 @@ def create_google_doc_from_csv(service): requests = [] current_index = 1 - for i, gruppe_original in enumerate(sorted_gruppen_namen): + for i, gruppe_original in enumerate(sorted_gruppen_namen): # Jetzt ist sorted_gruppen_namen definiert kinder_liste = kinder_nach_gruppen[gruppe_original] anzahl_kinder = len(kinder_liste) gruppe_display_name = gruppe_original + GRUPPENNAME_SUFFIX @@ -77,11 +112,9 @@ def create_google_doc_from_csv(service): current_index += len(header_text_for_page) # --- Tabelle --- - # Tabelle erstellen num_rows_for_table = len(kinder_liste) + 1 num_cols_for_table = 3 - # Füge die Tabellenstruktur ein requests.append({ 'insertTable': { 'location': {'index': current_index}, @@ -89,60 +122,26 @@ def create_google_doc_from_csv(service): 'columns': num_cols_for_table } }) - # WICHTIG: Nach insertTable befindet sich der Cursor (und somit der nächste Einfügepunkt - # für Text, den die API in die Zellen füllen soll) typischerweise direkt - # AM ANFANG der ersten Zelle. Der Index für diesen Punkt ist current_index + 1 - # (da insertTable selbst eine bestimmte Länge im JSON-Request hat, aber der - # Dokumentenindex sich auf den Punkt bezieht, an dem die *Tabelle* beginnt). - table_fill_start_index = current_index + 1 # Index, an dem der Text für die Zellen beginnt - - # Baue den gesamten Textinhalt für die Tabelle + table_fill_start_index = current_index + 1 + table_text_content = [] - # Kopfzeile - table_text_content.append("Nachname\tVorname\tGruppe") # Tabs für Spalten - # Datenzeilen + table_text_content.append("Nachname\tVorname\tGruppe") for kind in kinder_liste: table_text_content.append(f"{kind['Nachname']}\t{kind['Vorname']}\t{gruppe_display_name}") - full_table_text = "\n".join(table_text_content) + "\n" # Newlines für Zeilen, plus ein extra Newline am Ende + full_table_text = "\n".join(table_text_content) + "\n" - # Füge den gesamten Tabelleninhalt in einem Rutsch ein. - # Die API sollte dies in die Zellen der zuvor eingefügten Tabelle füllen. requests.append({ 'insertText': { - 'location': {'index': table_fill_start_index}, # Am Anfang der ersten Zelle + 'location': {'index': table_fill_start_index}, 'text': full_table_text } }) - # Aktualisiere den current_index um die Länge des eingefügten Tabellentextes - # PLUS die Strukturzeichen der Tabelle selbst. Dies ist schwer genau zu bestimmen. - # Eine sicherere Methode ist, nach diesem Batch-Update den aktuellen Dokumenten-Endindex - # abzufragen, aber für eine einfache Sequenz versuchen wir es mit einer Annahme. - # Die Länge der Struktur ist ungefähr: rows * cols * (1 für cell end) + rows * (1 für row end) + 1 (für table end) - # Das ist zu komplex. Einfacher: Der current_index wird auf den Start der Tabelle gesetzt, - # plus die Länge des reinen Textes, der eingefügt wurde. Die API fügt den Text *in* die Tabelle ein. - # Der nächste Text wird *nach* der Tabelle eingefügt. - # current_index bleibt current_index (Start der Tabelle), der nächste Text wird DANACH eingefügt. - # Also: current_index für den Footer muss *nach* der Tabelle sein. - # Die Tabelle selbst belegt `1` (für `insertTable`) im `requests` Array. - # Der Text der Tabelle belegt `len(full_table_text)`. - # Der Cursor ist nach dem Einfügen des Textes am Ende des Textes *innerhalb* der letzten Zelle. - # Um *nach* der Tabelle zu sein, brauchen wir `endOfSegmentLocation` oder eine bessere Indexlogik. - - # NEUER ANSATZ für Index nach Tabelle: - # Der `current_index` zeigt auf den Beginn der Tabelle. - # Der nächste Text (Footer) muss nach der Tabelle eingefügt werden. - # Wir setzen den Index für den Footer auf current_index + 1 (symbolisch für "nach der Tabelle"). - # Die API wird versuchen, dies nach der gerade bearbeiteten Tabellenstruktur einzufügen. - current_index_for_footer = current_index + 1 # Symbolisch "nach der Tabelle" - # --- Footer --- + # Korrektur: 'text' muss auf derselben Ebene wie 'endOfSegmentLocation' sein footer_text_for_page = ( - # Ein Newline am Anfang, um sicherzustellen, dass wir aus der Tabelle heraus sind - # Dies ist oft nicht nötig, wenn die API den Cursor korrekt nach der Tabelle platziert. - # Wir lassen es erstmal weg, da full_table_text mit \n endet. f"\n{anzahl_kinder} angemeldete Kinder\n\n" "Dies ist die Liste der bereits angemeldeten Kinder. Bitte die Eltern der noch fehlenden\n" "Kinder an die Anmeldung erinnern.\n\n" @@ -151,40 +150,53 @@ def create_google_doc_from_csv(service): ) requests.append({ 'insertText': { - # 'location': {'index': current_index_for_footer}, # VERSUCH 1 - 'endOfSegmentLocation': {} # VERSUCH 2: Sicherer, fügt am Ende des aktuellen Haupttextkörpers ein - # Nachdem die Tabelle und ihr Inhalt verarbeitet wurden. - }, - 'text': footer_text_for_page # Text muss auf derselben Ebene wie location sein + 'endOfSegmentLocation': {}, + 'text': footer_text_for_page + } }) - # Wenn endOfSegmentLocation verwendet wird, ist die manuelle Indexverfolgung hier schwierig. # --- Seitenumbruch --- if i < len(sorted_gruppen_namen) - 1: requests.append({'insertPageBreak': {'endOfSegmentLocation': {}}}) - # current_index wird für die nächste Seite wieder auf 1 gesetzt, - # da ein PageBreak einen neuen Abschnitt startet, in dem die Indizes neu beginnen. - # Dies ist eine Vereinfachung. Technisch gesehen addiert sich der Index weiter, - # aber für `endOfSegmentLocation` ist es nicht so kritisch. - # Für den nächsten Header setzen wir current_index NICHT zurück, da endOfSegmentLocation verwendet wird. - # Wir nehmen an, dass `endOfSegmentLocation` den Cursor richtig für den nächsten Block positioniert. - # Wenn `location` verwendet wird, müssten wir den `current_index` genau weiterführen. + # Reset current_index for the new page if not using endOfSegmentLocation for header + # Da wir endOfSegmentLocation für den nächsten Header nicht explizit verwenden (er basiert auf current_index), + # müssen wir sicherstellen, dass current_index für die nächste Seite korrekt ist. + # Mit endOfSegmentLocation für Footer und PageBreak ist die manuelle Index-Verfolgung + # für den *nächsten* Header schwierig. + # Eine sicherere Methode wäre, den Header der nächsten Seite auch mit endOfSegmentLocation zu beginnen. + # Für jetzt lassen wir current_index einfach weiterlaufen und hoffen, dass + # endOfSegmentLocation für Footer/PageBreak den Cursor korrekt für den *nächsten* Block + # (der wieder 'location' mit 'current_index' verwendet) positioniert. + # Dies könnte ein potenzielles Problem für die nächste Iteration sein, wenn der Footer/PageBreak + # den `current_index` nicht so verschiebt, wie es für den nächsten Header-Insert benötigt wird. + # EINFACHERE LÖSUNG: current_index nach endOfSegmentLocation nicht mehr verwenden. + # Wenn endOfSegmentLocation verwendet wird, sollte der nächste Insert auch endOfSegmentLocation verwenden oder + # man muss den Index explizit auf 1 für eine neue Seite setzen (was aber nicht korrekt ist, wenn das Dokument + # als ein langer Stream behandelt wird). + # Für jetzt: Da der Header der nächsten Seite `current_index` verwendet, müssen wir ihn aktualisieren. + # Ein Page Break fügt 1 zum Index hinzu. Der Footer hat `len(footer_text_for_page)`. + current_index += len(footer_text_for_page) + 1 # +1 für den PageBreak - # Batch-Update ausführen (bleibt gleich) + + # Batch-Update ausführen if requests: try: print("Sende Batch Update an Google Docs API...") print("Anzahl der Requests:", len(requests)) - # print("Requests (Auszug):", requests[:5]) # Für Debugging ggf. komplett ausgeben service.documents().batchUpdate( documentId=document_id, body={'requests': requests} ).execute() print("Dokument erfolgreich befüllt.") except HttpError as err: print(f"Fehler beim Befüllen des Google Dokuments: {err}") - error_details = err.content.decode('utf-8') if err.content else "Keine Details verfügbar." + error_details = "Keine Fehlerdetails im Content." + if err.content: + try: + error_details = err.content.decode('utf-8') + except Exception as e_decode: + error_details = f"Fehler beim Dekodieren der Fehlerdetails: {e_decode}" + print(f"Details zum Fehler ({err.resp.status} {err._get_reason()}): {error_details}") - # print("Gesendete Requests:", requests) # Alle Requests ausgeben für genaues Debugging return document_id