From 4b216267034d767c27001f54d6df5327e4be149f Mon Sep 17 00:00:00 2001 From: matin Date: Sat, 4 Oct 2025 17:57:39 +0200 Subject: changed pdf printer to xhtml2pdf --- prediction_report_service.py | 350 ++++++++++++++++++++++++------------------- 1 file changed, 192 insertions(+), 158 deletions(-) (limited to 'prediction_report_service.py') diff --git a/prediction_report_service.py b/prediction_report_service.py index b7e3620..4b2e521 100644 --- a/prediction_report_service.py +++ b/prediction_report_service.py @@ -1,158 +1,192 @@ -from datetime import datetime -from typing import List - -from PyQt5.QtGui import QTextDocument -from PyQt5.QtPrintSupport import QPrinter - -from calendar_manager import CalendarManager -from prediction_controller import PredictionController -from date_service import DateService - - -class PredictionReportService: - """Service to export the current prediction and entries into a PDF report.""" - - @staticmethod - def export_pdf(calendar_manager: CalendarManager, - prediction_controller: PredictionController, - out_path: str, - dateformat: str = "dd.MM.yyyy") -> bool: - try: - # Gather data - launch_dt = prediction_controller.get_launch_date() - duration = prediction_controller.get_duration() - prediction_dt = prediction_controller.get_prediction() - entries = calendar_manager.list_entries() - - # Format helpers - def fmt_date(dt): - return DateService.format_date_for_display(dt, dateformat) if dt else "" - - launch_str = fmt_date(launch_dt) - duration_str = f"{duration.years} Jahre" if duration and hasattr(duration, 'years') else "" - prediction_str = fmt_date(prediction_dt) - - # Sum time_periods (months and days separately) - total_months = 0 - total_days = 0 - for e in entries: - tp = getattr(e, 'time_period', None) - if not tp: - continue - try: - parts = tp.split() - if len(parts) >= 2: - value = int(parts[0]) - unit = parts[1].lower() - if value > 0: - if 'monat' in unit: - total_months += value - elif 'tag' in unit: - total_days += value - except Exception: - # Ignore unparsable time_periods - pass - - sum_parts = [] - if total_months > 0: - sum_parts.append(f"{total_months} Monate") - if total_days > 0: - sum_parts.append(f"{total_days} Tage") - sum_str = ", ".join(sum_parts) if sum_parts else "0" - - # Build HTML - created_stamp = datetime.now().strftime('%d.%m.%Y %H:%M:%S') - html = [] - html.append("") - html.append(""" - - """) - html.append("") - html.append("

Ausfallzeitenrechner — Bericht

") - html.append("
") - html.append(f"
Promotionsdatum: {launch_str}
") - html.append(f"
Bewerbungszeitraum: {duration_str}
") - html.append(f"
Bewerbungsfrist: {prediction_str}
") - html.append("
") - - # Table header - html.append("") - html.append( - "" - "" - "" - "" - "" - "" - "" - "" - "" - ) - - # Rows - for e in entries: - start_text = fmt_date(e.start_date) - end_text = fmt_date(e.end_date) - keyword_text = e.keyword - commentary_text = getattr(e, 'commentary', '') or '' - c_start_text = fmt_date(getattr(e, 'corrected_start_date', None)) - c_end_text = fmt_date(getattr(e, 'corrected_end_date', None)) - time_text = getattr(e, 'time_period', '') or '' - - # Escape - def esc(s: str) -> str: - return (s.replace('&', '&') - .replace('<', '<') - .replace('>', '>')) - - html.append( - f"" - f"" - f"" - f"" - f"" - f"" - f"" - ) - - html.append("") - html.append(f"") - html.append("
BeginnEndeArtKommentarBeginn AnrechnungEnde AnrechnungAnrechnungszeitraum
{esc(start_text)}{esc(end_text)}{esc(keyword_text)}{esc(commentary_text)}{esc(c_start_text)}{esc(c_end_text)}{esc(time_text)}
Summe Anrechnungszeitraum: {sum_str}
") - - html.append(f"
Erstellt am: {created_stamp}
") - html.append("") - - # Ensure extension - if not out_path.lower().endswith('.pdf'): - out_path = out_path + '.pdf' - - # Render HTML to PDF - printer = QPrinter(QPrinter.HighResolution) - printer.setOutputFormat(QPrinter.PdfFormat) - printer.setOutputFileName(out_path) - # Try to set margins (not all bindings support Millimeter overload) - try: - printer.setPageMargins(12, 12, 12, 12, QPrinter.Millimeter) - except Exception: - pass - - doc = QTextDocument() - doc.setHtml("".join(html)) - doc.print_(printer) - - return True - except Exception as e: - print(f"Error exporting PDF: {e}") - return False - - +from datetime import datetime +from typing import List +from io import BytesIO + +from xhtml2pdf import pisa + +from calendar_manager import CalendarManager +from prediction_controller import PredictionController +from date_service import DateService + + +class PredictionReportService: + """Service to export the current prediction and entries into a PDF report.""" + + @staticmethod + def export_pdf(calendar_manager: CalendarManager, + prediction_controller: PredictionController, + out_path: str, + dateformat: str = "dd.MM.yyyy") -> bool: + try: + # Gather data + launch_dt = prediction_controller.get_launch_date() + duration = prediction_controller.get_duration() + prediction_dt = prediction_controller.get_prediction() + entries = calendar_manager.list_entries() + + # Format helpers + def fmt_date(dt): + return DateService.format_date_for_display(dt, dateformat) if dt else "" + + launch_str = fmt_date(launch_dt) + duration_str = f"{duration.years} Jahre" if duration and hasattr(duration, 'years') else "" + prediction_str = fmt_date(prediction_dt) + + # Sum time_periods (months and days separately) + total_months = 0 + total_days = 0 + for e in entries: + tp = getattr(e, 'time_period', None) + if not tp: + continue + try: + parts = tp.split() + if len(parts) >= 2: + value = int(parts[0]) + unit = parts[1].lower() + if value > 0: + if 'monat' in unit: + total_months += value + elif 'tag' in unit: + total_days += value + except Exception: + # Ignore unparsable time_periods + pass + + sum_parts = [] + if total_months > 0: + sum_parts.append(f"{total_months} Monate") + if total_days > 0: + sum_parts.append(f"{total_days} Tage") + sum_str = ", ".join(sum_parts) if sum_parts else "0" + + # Build HTML + created_stamp = datetime.now().strftime('%d.%m.%Y %H:%M:%S') + html = [] + html.append("") + html.append("") + html.append(""" + + """) + html.append("") + html.append("

Ausfallzeitenrechner — Bericht

") + html.append("
") + html.append(f"
Promotionsdatum: {launch_str}
") + html.append(f"
Bewerbungszeitraum: {duration_str}
") + html.append(f"
Bewerbungsfrist: {prediction_str}
") + html.append("
") + + # Table header + html.append("") + html.append( + "" + "" + "" + "" + "" + "" + "" + "" + "" + ) + + # Rows + for e in entries: + start_text = fmt_date(e.start_date) + end_text = fmt_date(e.end_date) + keyword_text = e.keyword + commentary_text = getattr(e, 'commentary', '') or '' + c_start_text = fmt_date(getattr(e, 'corrected_start_date', None)) + c_end_text = fmt_date(getattr(e, 'corrected_end_date', None)) + time_text = getattr(e, 'time_period', '') or '' + + # Escape HTML special characters + def esc(s: str) -> str: + return (s.replace('&', '&') + .replace('<', '<') + .replace('>', '>') + .replace('"', '"')) + + html.append( + f"" + f"" + f"" + f"" + f"" + f"" + f"" + ) + + html.append("") + html.append(f"") + html.append("
BeginnEndeArtKommentarBeginn AnrechnungEnde AnrechnungZeitraum
{esc(start_text)}{esc(end_text)}{esc(keyword_text)}{esc(commentary_text)}{esc(c_start_text)}{esc(c_end_text)}{esc(time_text)}
Summe Anrechnungszeitraum: {sum_str}
") + + html.append(f"
Erstellt am: {created_stamp}
") + html.append("") + + # Ensure extension + if not out_path.lower().endswith('.pdf'): + out_path = out_path + '.pdf' + + # Generate PDF using xhtml2pdf + html_content = "".join(html) + + with open(out_path, "wb") as pdf_file: + pisa_status = pisa.CreatePDF( + html_content, + dest=pdf_file, + encoding='utf-8' + ) + + return not pisa_status.err + + except Exception as e: + print(f"Error exporting PDF: {e}") + return False \ No newline at end of file -- cgit v1.1