commit 949603e89678eb098926d53335b7bd19bc66412f Author: Md Bayazid Bostame Date: Tue Oct 8 18:49:36 2024 +0200 Final Onboarding processes diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c6fe762 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "livePreview.defaultPreviewPath": "/templates/onboarding_template.html" +} \ No newline at end of file diff --git a/email_text/dritich_email.txt b/email_text/dritich_email.txt new file mode 100644 index 0000000..e17dc01 --- /dev/null +++ b/email_text/dritich_email.txt @@ -0,0 +1,16 @@ +Subject: HR Works Access Request for New Employee + +Hello Dritich, + +A new employee requires access to the HR system (HR Works) for their onboarding process. + +Please proceed with creating the HR Works account for the following employee: + +Employee Name: {{VORNAME}} {{NACHNAME}} +Department: {{ABTEILUNG}} +Start Date: {{VERTRAGSBEGINN}} + +If you require any additional information, please let us know. + +Best regards, +The Onboarding Team diff --git a/email_text/minuth_email.txt b/email_text/minuth_email.txt new file mode 100644 index 0000000..2e67c5d --- /dev/null +++ b/email_text/minuth_email.txt @@ -0,0 +1,16 @@ +Subject: New Key Request for Onboarding Employee + +Hello Minuth, + +We have a new employee who requires a key (Schlüssel) as part of their onboarding process. + +Please proceed with creating and providing the necessary key for the following employee: + +Employee Name: {{VORNAME}} {{NACHNAME}} +Department: {{ABTEILUNG}} +Start Date: {{VERTRAGSBEGINN}} + +If you have any questions or need further details, feel free to reach out. + +Best regards, +The Onboarding Team diff --git a/env/.env b/env/.env new file mode 100644 index 0000000..c50b5e4 --- /dev/null +++ b/env/.env @@ -0,0 +1,19 @@ +# Email Server Configuration +IMAP_SERVER=mxe839.netcup.net +SMTP_SERVER=mxe839.netcup.net +EMAIL_PORT=465 # Port for SSL/TLS (Port 465) +EMAIL_ACCOUNT=onboarding@bostame.de +PASSWORD= +MAILBOX=INBOX + +# Recipients for notification emails +MINUTH_EMAIL=minuth@bostame.de +DRITICH_EMAIL=dittrich@bostame.de + +# Additional directories or paths +TEMPLATES_DIR=templates +ATTACHMENTS_DIR=attachments +ONBOARDED_DIR=onboarded_person +TEMP_PDF_DIR=temp_pdf +EMAIL_TEXT_DIR=email_text + diff --git a/main.py b/main.py new file mode 100644 index 0000000..bc8e9ed --- /dev/null +++ b/main.py @@ -0,0 +1,276 @@ +import imaplib +import email +import os +import pandas as pd +import pdfkit +from jinja2 import Template +from PyPDF2 import PdfWriter, PdfReader, PageObject +from datetime import datetime +from email.header import decode_header +import time +from pathlib import Path +from dotenv import load_dotenv +import uuid +import smtplib +from email.mime.text import MIMEText +from email.mime.multipart import MIMEMultipart +import re + +# Load environment variables from .env file +load_dotenv(dotenv_path=Path(__file__).parent / 'env' / '.env') + +# Retrieve secret credentials and directories from environment variables +IMAP_SERVER = os.getenv('IMAP_SERVER') +SMTP_SERVER = os.getenv('SMTP_SERVER') +EMAIL_PORT = int(os.getenv('EMAIL_PORT')) +EMAIL_ACCOUNT = os.getenv('EMAIL_ACCOUNT') +PASSWORD = os.getenv('PASSWORD') +MAILBOX = os.getenv('MAILBOX') + +# Recipients from .env file +MINUTH_EMAIL = os.getenv('MINUTH_EMAIL') +DRITICH_EMAIL = os.getenv('DRITICH_EMAIL') + +# Retrieve directory paths from environment variables +TEMPLATES_DIR = Path(os.getenv('TEMPLATES_DIR')) +ATTACHMENTS_DIR = Path(os.getenv('ATTACHMENTS_DIR')) +ONBOARDED_DIR = Path(os.getenv('ONBOARDED_DIR')) +TEMP_PDF_DIR = Path(os.getenv('TEMP_PDF_DIR')) +EMAIL_TEXT_DIR = Path(os.getenv('EMAIL_TEXT_DIR')) + +# Ensure directories exist +for directory in [ATTACHMENTS_DIR, TEMPLATES_DIR, ONBOARDED_DIR, TEMP_PDF_DIR, EMAIL_TEXT_DIR]: + directory.mkdir(parents=True, exist_ok=True) + +# Configure path to wkhtmltopdf +path_to_wkhtmltopdf = Path(r'C:\Program Files\wkhtmltopdf\bin\wkhtmltopdf.exe') +config = pdfkit.configuration(wkhtmltopdf=str(path_to_wkhtmltopdf)) +options = { + 'enable-local-file-access': None, + 'page-size': 'A4', + 'margin-top': '50mm', + 'margin-bottom': '20mm', + 'margin-left': '20mm', + 'margin-right': '20mm' +} + +# Function to send an email with dynamic content +def send_email_notification(to_email, subject, template_file, context): + try: + # Load the email content from the text file and render the template + with open(template_file, 'r', encoding='utf-8') as file: + template_content = file.read() + + # Render the email content with the context (employee data) + template = Template(template_content) + rendered_content = template.render(context) + + msg = MIMEMultipart() + msg['From'] = EMAIL_ACCOUNT + msg['To'] = to_email + msg['Subject'] = subject + + # Attach the rendered email body + msg.attach(MIMEText(rendered_content, 'plain')) + + # Send the email via SMTP over SSL + print(f"Attempting to connect to SMTP server {SMTP_SERVER} on port {EMAIL_PORT} using SSL...") + with smtplib.SMTP_SSL(SMTP_SERVER, EMAIL_PORT) as server: + print(f"Connected to SMTP server {SMTP_SERVER}, attempting to login...") + server.login(EMAIL_ACCOUNT, PASSWORD) + print(f"Logged in successfully, sending email to {to_email}...") + server.send_message(msg) + print(f"Email sent to {to_email}") + except Exception as e: + print(f"Failed to send email to {to_email}: {e}") + finally: + # Introduce a small delay between emails to avoid potential throttling issues + time.sleep(5) + +# Helper function to split a list into chunks of a specified size and format it in title case +def chunk_list(data_list, chunk_size=4): + title_case_list = [item.title() for item in data_list] + for i in range(0, len(title_case_list), chunk_size): + yield title_case_list[i:i + chunk_size] + +# Function to generate PDF from HTML +def generate_pdf_from_html(html_content, output_pdf): + try: + pdfkit.from_string(html_content, output_pdf, configuration=config, options=options) + print(f"Generated PDF: {output_pdf}") + except Exception as e: + print(f"Error generating PDF {output_pdf}: {e}") + +# Overlay generated PDF content onto the letterhead +def overlay_content_on_letterhead(content_pdf, letterhead_pdf, output_pdf): + try: + letterhead_reader = PdfReader(letterhead_pdf) + content_reader = PdfReader(content_pdf) + + writer = PdfWriter() + letterhead_page = letterhead_reader.pages[0] + + for page_num in range(len(content_reader.pages)): + content_page = content_reader.pages[page_num] + overlay_page = PageObject.create_blank_page(width=letterhead_page.mediabox.width, height=letterhead_page.mediabox.height) + overlay_page.merge_page(letterhead_page) + overlay_page.merge_page(content_page) + writer.add_page(overlay_page) + + with open(output_pdf, 'wb') as output_file: + writer.write(output_file) + print(f"Final PDF saved to: {output_pdf}") + + except Exception as e: + print(f"Error overlaying content on letterhead: {e}") + +# Function to process CSV and generate PDF with email feature +def process_csv_and_generate_pdf(csv_file): + try: + print(f"Processing CSV file: {csv_file}") + + # Load CSV data + data = pd.read_csv(csv_file) + print(f"CSV Data loaded successfully for {csv_file}") + + # Load the HTML template from the templates directory + with open(TEMPLATES_DIR / 'onboarding_template.html', 'r', encoding='utf-8') as file: + html_template = file.read() + print("HTML template loaded successfully") + + for index, row in data.iterrows(): + try: + print(f"Processing row {index}") + + # Split "Vorname und Nachname" into first and last name + name_parts = row['Vorname und Nachname'].split() + vorname = name_parts[0] + nachname = " ".join(name_parts[1:]) + + # Chunk the multiline fields into formatted lists + arbeitsegeraete_list = list(chunk_list(row.get('Der Mitarbeiter Benötigt Folgende Arbeitsgeräte', '').split('\n'))) + zugaenge_list = list(chunk_list(row.get('Die Folgenden Zugänge und Rollen Sollen Zu Workspace Eingerichtet Werden', '').split('\n'))) + software_list = list(chunk_list(row.get('Darüber Hinaus Benötigt Er Folgende Software', '').split('\n'))) + account_list = list(chunk_list(row.get('Zugänge, Die Standardmäßig Eingerichtet Werden Sollen, Bitte Benennen', '').split('\n'))) + standard_zugaenge_list = row.get('Zugänge, Die Standardmäßig Eingerichtet Werden Sollen, Bitte Benennen', '').split('\n') + printer_list = list(chunk_list(row.get('Ressourcen, Die Standardmäßig Eingerichtet Werden Sollen, Bitte Benennen', '').split('\n'))) + telephone = row.get('TUBS-Telefon-Direktwahl-Nr. 030 447202 (10-89)', None) + + + + # Prepare the context with employee data + context = { + 'VORNAME': vorname, + 'NACHNAME': nachname, + 'BERUFSBEZEICHNUNG': row.get('Berufsbezeichnung', 'N/A'), + 'ABTEILUNG': row.get('Abteilung', 'N/A'), + 'EMAIL': row.get('Gewünschte Dienstliche E-Mail-Adresse', 'N/A'), + 'VERTRAGSBEGINN': row.get('Vertragsbeginn', 'N/A'), + 'UEBERGABEDATUM': row.get('Gewünschtes Übergabedatum der Geräte', 'N/A'), + 'GRUPPENPOSTFAECHER_ERFORDERLICH': row.get('Gruppenpostfächer Erforderlich?', 'N/A'), + 'ZUGAENGE_LIST': zugaenge_list, + 'ARBEITSGERÄTE_LIST': arbeitsegeraete_list, + 'SOFTWARE_LIST': software_list, + 'ACCOUNT_LIST': account_list, # Pass Standard-Zugänge data + 'STANDARD_ZUGAENGE': row.get('Zugänge, Die Standardmäßig Eingerichtet Werden Sollen, Bitte Benennen', ''), + 'SOFTWAREWUNSCH': row.get('Haben Sie Einen Zusätzlichen Softwarewunsch?', ''), + 'STANDARD_RESSOURCEN': printer_list, + 'TELEFONNUMMER': telephone, + 'BEMERKUNGEN': row.get('Haben Wir Irgendetwas Übersehen? Schreiben Sie Uns Hier.', 'nan'), + 'VEREINBARUNG': row.get('Vereinbarung', ''), + 'UNTERSCHRIFT': row.get('Unterschrift', ''), + } + + # Fill the template with data + html_content = Template(html_template).render(context) + + # Generate content PDF and save to temp_pdf directory + content_pdf_path = TEMP_PDF_DIR / f'temp_content_{vorname}_{nachname}.pdf' + print(f"Generating content PDF at: {content_pdf_path}") + generate_pdf_from_html(html_content, str(content_pdf_path)) + + # Define output PDF filename and save to onboarded_person directory + output_pdf_path = ONBOARDED_DIR / f'onboarding_letter_{vorname}_{nachname}.pdf'.replace(" ", "_") + + # Overlay content on letterhead and save final PDF + letterhead_pdf = TEMPLATES_DIR / 'templates.pdf' + print(f"Overlaying content on letterhead: {letterhead_pdf}") + overlay_content_on_letterhead(str(content_pdf_path), letterhead_pdf, output_pdf_path) + + print(f"Final PDF generated and saved at: {output_pdf_path}") + + # Email notifications based on conditions + # Check for "Schlüssel" in ARBEITSGERÄTE_LIST and send email + if any("Schlüssel".lower() in s.lower() for sublist in arbeitsegeraete_list for s in sublist if isinstance(s, str)): + print(f"'Schlüssel' found for {vorname} {nachname}") + send_email_notification(MINUTH_EMAIL, 'Schlüssel Required', EMAIL_TEXT_DIR / 'minuth_email.txt', context) + + # Check for "HR Works" in STANDARD_ZUGAENGE_LIST and send email + if any("HR Works".lower() in s.lower() for s in standard_zugaenge_list if isinstance(s, str)): + print(f"'HR Works' found for {vorname} {nachname}") + send_email_notification(DRITICH_EMAIL, 'HR Works Access Required', EMAIL_TEXT_DIR / 'dritich_email.txt', context) + + except Exception as e: + print(f"Error processing row {index}: {e}") + + except Exception as e: + print(f"Error processing CSV {csv_file}: {e}") + + +# Function to check email for CSV attachments +def check_email_for_csv(): + try: + # Connect to the email server + mail = imaplib.IMAP4_SSL(IMAP_SERVER) + mail.login(EMAIL_ACCOUNT, PASSWORD) + mail.select(MAILBOX) + + # Search for all unread emails + status, messages = mail.search(None, '(UNSEEN)') + email_ids = messages[0].split() + + for e_id in email_ids: + res, msg = mail.fetch(e_id, "(RFC822)") + for response_part in msg: + if isinstance(response_part, tuple): + msg = email.message_from_bytes(response_part[1]) + subject, encoding = decode_header(msg["Subject"])[0] + + if isinstance(subject, bytes): + subject = subject.decode(encoding or "utf-8") + + print(f"Processing email: {subject}") + + # Loop through email parts to find attachments + if msg.is_multipart(): + for part in msg.walk(): + content_disposition = str(part.get("Content-Disposition")) + if "attachment" in content_disposition: + filename = part.get_filename() + + if filename.endswith(".csv"): + # Save the CSV attachment with a unique name + unique_filename = f"{filename.split('.')[0]}_{uuid.uuid4().hex}.csv" + filepath = ATTACHMENTS_DIR / unique_filename + with open(filepath, "wb") as f: + f.write(part.get_payload(decode=True)) + print(f"CSV file saved as {filepath}") + + # Process CSV and generate PDF + process_csv_and_generate_pdf(filepath) + + mail.logout() + except Exception as e: + print(f"Failed to check email: {e}") + +# Countdown timer for 30 seconds before checking again +def countdown_timer(seconds): + for i in range(seconds, 0, -1): + print(f"Waiting {i} seconds...", end="\r") + time.sleep(1) + print("Checking emails now...") + +# Continuously check for new emails every 30 seconds +while True: + check_email_for_csv() + countdown_timer(30) diff --git a/templates/onboarding_template.html b/templates/onboarding_template.html new file mode 100644 index 0000000..931e13a --- /dev/null +++ b/templates/onboarding_template.html @@ -0,0 +1,204 @@ + + + + + + Onboarding Letter + + + +
+

"Willkommen im Team! Wir sind begeistert, Sie auf dieser spannenden Reise an unserer Seite zu haben."

+ + +

Sehr geehrte/r {{VORNAME}} {{NACHNAME}},

+ +

Wir freuen uns riesig, Sie als unsere/n neue/n {{BERUFSBEZEICHNUNG}} in der Abteilung {{ABTEILUNG}} begrüßen zu dürfen. Ihr Arbeitsvertrag beginnt am {{VERTRAGSBEGINN}}. Der Termin für die Übergabe der benötigten Arbeitsmittel ist für den {{UEBERGABEDATUM}} vorgesehen. Ihre dienstliche E-Mail-Adresse lautet: {{EMAIL}}

+ +

Mit Ihnen gemeinsam werden wir großartige Dinge erreichen!

+ +

Ihre Arbeitsgeräte

+ + + + + + + + {% for row in ARBEITSGERÄTE_LIST %} + + {% for cell in row %} + + {% endfor %} + + {% endfor %} + +
Arbeitsgerät
{{ cell }}
+ +

Zugänge und Rollen

+ + + + + + + + {% for row in ZUGAENGE_LIST %} + + {% for cell in row %} + + {% endfor %} + + {% endfor %} + +
Zugang
{{ cell }}
+ +

Benötigte Software

+ + + + + + + + {% for row in SOFTWARE_LIST %} + + {% for cell in row %} + + {% endfor %} + + {% endfor %} + +
Software
{{ cell }}
+ +

Liste der Konten

+ + + + + + + + {% for row in ACCOUNT_LIST %} + + {% for cell in row %} + + {% endfor %} + + {% endfor %} + +
Konten
{{ cell }}
+ +

Drucker im Büro

+ + + + + + + + {% for row in STANDARD_RESSOURCEN %} + + {% for cell in row %} + + {% endfor %} + + {% endfor %} + +
Drucker
{{ cell }}
+ +

TUBS-Telefon

+ + + + + + + + {% for row in TELEFONNUMMER %} + + {% for cell in row %} + + {% endfor %} + + {% endfor %} + +
Direktwahl-Nr.
{{ cell }}
+ + +

Unterschrift:

+ Unterschrift + + +
+ + diff --git a/templates/templates.pdf b/templates/templates.pdf new file mode 100644 index 0000000..76b07df Binary files /dev/null and b/templates/templates.pdf differ