from datetime import datetime from typing import List from io import BytesIO # Import xhtml2pdf lazily in export to avoid heavy import at module load 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: from xhtml2pdf import pisa # 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