From 2719abf966db6e77fc8096f92755742e5bb6d8b3 Mon Sep 17 00:00:00 2001 From: "Dr. David Krassnig" Date: Sat, 14 Oct 2023 22:19:01 +0200 Subject: [PATCH] Added E-Mail Support and Improved Splitting --- personal_db.csv | 2 + ubffm-buchungsjournal-email.py | 238 ++++++++++++++++++++++++++++++ ubffm-buchungsjournal-splitter.py | 65 ++++---- 3 files changed, 270 insertions(+), 35 deletions(-) create mode 100644 personal_db.csv create mode 100644 ubffm-buchungsjournal-email.py mode change 100644 => 100755 ubffm-buchungsjournal-splitter.py diff --git a/personal_db.csv b/personal_db.csv new file mode 100644 index 0000000..2823102 --- /dev/null +++ b/personal_db.csv @@ -0,0 +1,2 @@ +Personalnummer,Nachname,Vorname,E-Mail +000000,Mustermann,Max,M.Mustermann@ub.uni-frankfurt.de diff --git a/ubffm-buchungsjournal-email.py b/ubffm-buchungsjournal-email.py new file mode 100644 index 0000000..e0474fd --- /dev/null +++ b/ubffm-buchungsjournal-email.py @@ -0,0 +1,238 @@ +import os +import re +import json +import csv +import win32com.client as win32 +from datetime import datetime +import tqdm # Erweiterte Fortschrittsanzeige + +verzeichnis_pfad = "buchungsjournale" # Pfad zum Verzeichnis (anpassen) +csv_datei_pfad = "personal_db.csv" # Pfad zur CSV-Datei (anpassen) + +def datumKonvertierer(yyyymm_date): + try: + date_obj = datetime.strptime(yyyymm_date, '%Y%m') + german_month_names = [ + "Januar", "Februar", "März", "April", "Mai", "Juni", + "Juli", "August", "September", "Oktober", "November", "Dezember" + ] + full_month_name = german_month_names[date_obj.month - 1] # Subtract 1 to account for 0-based index + full_date = f"{full_month_name} {date_obj.year}" + return full_date + except ValueError: + return "Ungültiges Datumsformat" + +def datumExtrahierer(filename): + pattern = r'_(\d+)_\w+\.pdf$' + match = re.search(pattern, filename) + if match: + return match.group(1) + else: + return None + +def personalnummerExtrahierer(dateiname): + treffer = re.search(r'_(\d+)\.pdf$', dateiname) + if treffer: + return treffer.group(1) + return None + +def dictionaryFlacher(originalDict): + flacheresDict = {} + + for key, value in originalDict.items(): + if isinstance(value, dict): + for subkey, subvalue in value.items(): + flacheresDict[subkey] = subvalue + else: + flacheresDict[key] = value + + return flacheresDict + +def csvLeser(csv_datei): + daten = {} + with open(csv_datei, 'r', newline='') as datei: + csv_leser = csv.reader(datei) + titelzeile = next(csv_leser) # Titelzeile überspringen + for zeile in csv_leser: + if len(zeile) >= 4: + personalnummer = zeile[0] + nachname = zeile[1] + vorname = zeile[2] + pdf = zeile[3] + daten[personalnummer] = { + "Nachname": nachname, + "Vorname": vorname, + "E-Mail": pdf + } + return daten + +def datenbanks(): + if os.path.exists(csv_datei_pfad) and os.path.isfile(csv_datei_pfad): + csv_daten = csvLeser(csv_datei_pfad) + + if os.path.exists(verzeichnis_pfad) and os.path.isdir(verzeichnis_pfad): + datenbank = datenbankErsteller(verzeichnis_pfad, csv_daten) + csv_daten = None + return datenbank + else: + print(f"Das Verzeichnis '{verzeichnis_pfad}' existiert nicht oder ist kein Verzeichnis.") + exit(1) + else: + print(f"Die CSV-Datei '{csv_datei_pfad}' existiert nicht oder ist keine Datei.") + exit(1) + +def datenbankErsteller(pfad, csv_daten): + datenbank = {} + for wurzel, verzeichnisse, dateien in os.walk(pfad): + aktuell = datenbank + ordner = wurzel.split(os.path.sep)[1:] + for ordnername in ordner: + aktuell = aktuell.setdefault(ordnername, {}) + for datei in dateien: + personalnummer = None + if datei.lower().endswith('.pdf'): + vollerPdfPfad = os.path.abspath(os.path.join(wurzel, datei)) + personalnummer = personalnummerExtrahierer(datei) + datum = datumKonvertierer(datumExtrahierer(datei)) + if personalnummer in csv_daten: + aktuell[datei] = { + "Personalnummer": personalnummer, + "Nachname": csv_daten[personalnummer]["Nachname"], + "Vorname": csv_daten[personalnummer]["Vorname"], + "E-Mail": csv_daten[personalnummer]["E-Mail"], + "Pfad": vollerPdfPfad, # Add the full file path + "Datum": datum + } + else: + aktuell[datei] = { + "Personalnummer": personalnummer, + "Nachname": None, + "Vorname": None, + "E-Mail": None, + "Pfad": vollerPdfPfad, # Add the full file path + "Datum": datum + } + + return datenbank + +def auswahlAbteilung(abteilung): + while True: + print("\nVerfügbare Abteilungen:") + subkeys = list(abteilung.keys()) + print("0. Alle Abteilungen") + for i, key in enumerate(subkeys): + print(f"{i + 1}. {key}") + + choice = input("Geben Sie die Zahl der auszuwählenden Abteilung ein: ") + try: + choice = int(choice) + if 0 <= choice <= len(subkeys): + if choice == 0: + return dictionaryFlacher(abteilung) + else: + selected_subkey = subkeys[choice - 1] + return abteilung[selected_subkey] + else: + print("Keine valide Wahl. Bitte wählen Sie eine valide Nummer.") + except ValueError: + print("Keine valide Wahl. Bitte wählen Sie eine valide Nummer.") + +def auswahlGesamt(datenbank): + while True: + print("\nVerfügbare Zeiträume:") + main_keys = sorted(list(datenbank.keys()), reverse=True) + for i, key in enumerate(main_keys): + print(f"{i + 1}. {key}") + + choice = input("Geben Sie die Zahl des auszuwählenden Zeitraumes ein: ") + try: + choice = int(choice) + if 1 <= choice <= len(main_keys): + selected_key = main_keys[choice - 1] + abteilung = datenbank[selected_key] + selected_abteilung = auswahlAbteilung(abteilung) + return selected_abteilung + break # Exit the loop after selecting a subkey + else: + print("Keine valide Wahl. Bitte wählen Sie eine valide Nummer.") + except ValueError: + print("Keine valide Wahl. Bitte wählen Sie eine valide Nummer.") + +def mailTextErzeuger(vorname, nachname, pdf, path, personalnummer, datum): + if None in (vorname, nachname, pdf, path): + return "Fehler!" + else: + message = f"Guten Tag {vorname} {nachname},\n\nBitte finden Sie dieser E-Mail beigefügt Ihr Buchungsjournal für {datum}.\n\n\nMit freundlichen Grüßen\n\nIhre Personalabteilung\n(dies ist eine automatisch generierte E-Mail)" + return message + +keineMail = [] +eineMail = [] + +def mailVerschicker(pdfs): + buchungsjournale_email_account_name = 'Buchungsjournale@ub.uni-frankfurt.de' + outlook = win32.Dispatch('outlook.application') + namespace = outlook.GetNamespace('MAPI') + buchungsjournale_email_account = None + + for account in namespace.Accounts: + if account.DisplayName == buchungsjournale_email_account_name: + buchungsjournale_email_account = account + break + if buchungsjournale_email_account is None: + print(f"E-Mail-Account '{buchungsjournale_email_account_name}' wurde nicht gefunden.") + exit(1) + + with tqdm.tqdm(total=len(pdfs)) as bar: + for pdf in pdfs: + vorname = pdfs[pdf]['Vorname'] + nachname = pdfs[pdf]['Nachname'] + emailAdresse = pdfs[pdf]['E-Mail'] + anhangPfad = pdfs[pdf]['Pfad'] + personalnummer = pdfs[pdf]['Personalnummer'] + datum = pdfs[pdf]['Datum'] + message = mailTextErzeuger(vorname, nachname, emailAdresse, anhangPfad, personalnummer, datum) + if message == 'Fehler!': + keineMail.append(pdf) + bar.update(1) + else: + mail = outlook.CreateItem(0) + + mail._oleobj_.Invoke(*(64209, 0, 8, 0, buchungsjournale_email_account)) # Select the pdf account + + mail.To = emailAdresse + mail.Subject = f'[Buchungsjournal] {datum}' + mail.Body = message + mail.Attachments.Add(anhangPfad) + + mail.Send() + + eineMail.append(pdf) + + bar.update(1) + +def dateiPrint(text): + print(text) + output_file.write(text+'\n') + +if __name__ == '__main__': + datenbank = datenbanks() + auswahl = auswahlGesamt(datenbank) + mailVerschicker(auswahl) + anzahlKeineMail = len(keineMail) + anzahlEineMail = len(eineMail) + anzahlMail = len(auswahl) + jetzt = datetime.now().strftime('%Y%m%dT%H%M%S') + with open(f'logs/log_{jetzt}.txt', "w") as output_file: + dateiPrint(f'Es wurden {anzahlEineMail}/{anzahlMail} Buchungsjournale erfolgreich verschickt!') + print(f'Details zu (nicht) verschickten E-Mails stehen in der Log-Datei (log_{jetzt}.txt).') + output_file.write('--------------------------------------------------\n') + output_file.write('Folgende Buchungsjournale wurden via E-Mail verschickt:\n') + for i in eineMail: + output_file.write(i+'\n') + output_file.write('--------------------------------------------------\n') + output_file.write('Folgende Buchungsjournale konnten wegen fehlenden Informationen nicht via E-Mail verschickt werden:\n') + for i in keineMail: + output_file.write(i+'\n') + + input('\nDrücken Sie die Eingabetaste, um das Skript zu beenden...') + exit(0) diff --git a/ubffm-buchungsjournal-splitter.py b/ubffm-buchungsjournal-splitter.py old mode 100644 new mode 100755 index 6cf37fe..88fea3c --- a/ubffm-buchungsjournal-splitter.py +++ b/ubffm-buchungsjournal-splitter.py @@ -1,33 +1,25 @@ import fitz # PyMuPDF für PDF-Befehle import re # Regex -import progressbar # Erweiterte Fortschrittsanzeige +import tqdm # Erweiterte Fortschrittsanzeige import sys # Interaktion mit Terminal import os # Interaktion mit Betriebssystemen import shutil # Einfachere Dateiverschiebung +if os.name == 'nt': + from colorama import just_fix_windows_console + just_fix_windows_console() + class formatierung: # Definiert verschiedene Formatierungsmöglichkeiten - if os.name == 'nt': - PURPLE = '' - CYAN = '' - DARKCYAN = '' - BLUE = '' - GREEN = '' - YELLOW = '' - RED = '' - BOLD = '' - UNDERLINE = '' - END = '' - else: - PURPLE = '\033[95m' - CYAN = '\033[96m' - DARKCYAN = '\033[36m' - BLUE = '\033[94m' - GREEN = '\033[92m' - YELLOW = '\033[93m' - RED = '\033[91m' - BOLD = '\033[1m' - UNDERLINE = '\033[4m' - END = '\033[0m' + PURPLE = '\033[95m' + CYAN = '\033[96m' + DARKCYAN = '\033[36m' + BLUE = '\033[94m' + GREEN = '\033[92m' + YELLOW = '\033[93m' + RED = '\033[91m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + END = '\033[0m' def dateiNamenRegeln(s): # Vereinheitlicht Namen der Dateiausgabe (optimiert für Interaktionen im Terminal) # Entfernt alle nicht-Wörterzeichen (alles abgesehen von Buchstaben und Ziffern) @@ -42,13 +34,13 @@ def pdfDateiSucheBuchungsjournal(pdfDatei, suchText): doc = fitz.open(pdfDatei) buchungsjournalSeiten = [] - with progressbar.ProgressBar(max_value=doc.page_count) as bar: + with tqdm.tqdm(total=doc.page_count) as bar: for seiteJetzt in range(doc.page_count): page = doc.load_page(seiteJetzt) text = page.get_text() if suchText in text: buchungsjournalSeiten.append(seiteJetzt) - bar.update(seiteJetzt) + bar.update(1) doc.close() return buchungsjournalSeiten @@ -91,7 +83,7 @@ def pdfDateiAuftrennungNachSeiten(pdfDatei, seiteGesamt, tempVerzeichnis): if not os.path.exists(tempVerzeichnis): os.makedirs(tempVerzeichnis) - with progressbar.ProgressBar(max_value=len(seiteGesamt)) as bar: + with tqdm.tqdm(total=len(seiteGesamt)) as bar: for i, seiteJetzt in enumerate(seiteGesamt): seiteStart = seiteJetzt seiteEnde = seiteGesamt[i + 1] if i + 1 < len(seiteGesamt) else doc.page_count @@ -103,7 +95,7 @@ def pdfDateiAuftrennungNachSeiten(pdfDatei, seiteGesamt, tempVerzeichnis): neuPdfDatei = f'{tempVerzeichnis}/pages_{seiteStart + 1}_to_{seiteEnde}.pdf' neuDoc.save(neuPdfDatei) neuDoc.close() - bar.update(i) + bar.update(1) doc.close() @@ -111,7 +103,7 @@ def pdfDateiUmbenennung(tempVerzeichnis): pdfDateien = [pdfFile for pdfFile in os.listdir(tempVerzeichnis) if pdfFile.endswith('.pdf')] gesamtDateien = len(pdfDateien) - with progressbar.ProgressBar(max_value=gesamtDateien) as bar: + with tqdm.tqdm(total=gesamtDateien) as bar: for pdfDatei in pdfDateien: datenExtrahiert = pdfDateiPersonaldaten(os.path.join(tempVerzeichnis, pdfDatei)) if datenExtrahiert: @@ -123,7 +115,7 @@ def pdfDateiUmbenennung(tempVerzeichnis): os.makedirs(neuPdfPfad) neuPdfDatei = f'{neuPdfPfad}/{neuPdfName}' os.rename(os.path.join(tempVerzeichnis, pdfDatei), neuPdfDatei) - bar.update() + bar.update(1) def machTitel(s): @@ -135,7 +127,7 @@ def meldungErfolgreich(): def meldungFehlschlag(): print(formatierung.BOLD+formatierung.RED+'Fehler!'+formatierung.END) -def ueberschreiben(ziel): +def ueberschreibenFrage(ziel): response = input(formatierung.BOLD+formatierung.YELLOW+f'{os.path.basename(ziel)} existiert bereits im Ausgabeverzeichnis. Überschreiben?'+formatierung.END+' [j/N]\n') return response.lower().strip() == 'j' @@ -143,9 +135,9 @@ def ueberschreiben(ziel): suchText = 'Buchungsjournal' ausgabeVerzeichnis = 'buchungsjournale' -width = os.get_terminal_size().columns def trennlinie(): - print('-'*width) + w, h = shutil.get_terminal_size() + print("—" * w) if __name__ == '__main__': if len(sys.argv) == 1: @@ -187,14 +179,15 @@ def trennlinie(): os.makedirs(ausgabeVerzeichnis) tempVerzeichnis_items = os.listdir(tempVerzeichnis) - with progressbar.ProgressBar(max_value=len(tempVerzeichnis_items)) as bar: + with tqdm.tqdm(total=len(tempVerzeichnis_items)) as bar: for item in tempVerzeichnis_items: quelle = os.path.join(tempVerzeichnis, item) ziel = os.path.join(ausgabeVerzeichnis, item) if os.path.exists(ziel): - user_response = ueberschreiben(ziel) + user_response = ueberschreibenFrage(ziel) if not user_response: print(f'Überspringe {item}...') + bar.update(1) continue if os.path.isdir(ziel): shutil.rmtree(ziel) # Entferne das Zielverzeichnis @@ -204,10 +197,12 @@ def trennlinie(): shutil.move(quelle, ziel) else: shutil.copy2(quelle, ziel) - bar.update() + bar.update(1) # Entferne das temporäre Verzeichnis if os.path.exists(tempVerzeichnis): shutil.rmtree(tempVerzeichnis) meldungErfolgreich() + trennlinie() + input('Drücken Sie die Eingabetaste, um das Skript zu beenden') exit(0)