summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormatin <matin.kaufmann@gmail.com>2025-09-30 16:55:49 +0200
committermatin <matin.kaufmann@gmail.com>2025-09-30 16:55:49 +0200
commitef3464569123d8f91a7300490a818d0ca299e398 (patch)
tree0b8403f177c552f20b238aa9dc38c08f3fdb250e
parent38129948c609e12202b8876ab2b87cde941f82bb (diff)
Berichfunktion hinzugefügt
-rw-r--r--.gitignore1
-rw-r--r--calendar_gui.py31
-rw-r--r--prediction_report_service.py158
3 files changed, 190 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index 8691330..ec38f61 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
__pycache__
*.json
+*.pdf \ No newline at end of file
diff --git a/calendar_gui.py b/calendar_gui.py
index f0edb52..ab0cf65 100644
--- a/calendar_gui.py
+++ b/calendar_gui.py
@@ -16,6 +16,7 @@ from prediction_controller import PredictionController
from event_type_handler import EventTypeHandler
from date_service import DateService
from config import EventConfig
+from prediction_report_service import PredictionReportService
class EventDialog(QDialog):
def __init__(self, entry=None, parent=None):
@@ -230,6 +231,14 @@ class CalendarManagerGUI(QMainWindow):
load_action.setIcon(self.style().standardIcon(QStyle.SP_DialogOpenButton))
load_action.triggered.connect(self.load_file)
file_menu.addAction(load_action)
+
+ # Export PDF action
+ export_pdf_action = QAction('Als PDF exportieren', self)
+ export_pdf_action.setShortcut('Ctrl+P')
+ # Fallback icon; SP_DialogPrintButton may not exist in some Qt styles
+ export_pdf_action.setIcon(self.style().standardIcon(QStyle.SP_DialogOpenButton))
+ export_pdf_action.triggered.connect(self.export_pdf)
+ file_menu.addAction(export_pdf_action)
# Note: Saving/Loading now operate on complete prediction state
@@ -643,6 +652,28 @@ class CalendarManagerGUI(QMainWindow):
except Exception as e:
QMessageBox.critical(self, "Error", f"Fehler beim Laden des Komplettzustands: {str(e)}")
+ def export_pdf(self):
+ """Export a formatted report to PDF including metadata and full table."""
+ out_path, _ = QFileDialog.getSaveFileName(
+ self,
+ "PDF exportieren",
+ "",
+ "PDF (*.pdf)"
+ )
+ if not out_path:
+ return
+ success = PredictionReportService.export_pdf(
+ self.calendar_manager,
+ self.prediction_controller,
+ out_path,
+ self.dateformat,
+ )
+ if success:
+ if not out_path.lower().endswith('.pdf'):
+ out_path = out_path + '.pdf'
+ QMessageBox.information(self, "Export erfolgreich", f"PDF exportiert nach {out_path}")
+ else:
+ QMessageBox.critical(self, "Fehler", "PDF-Export fehlgeschlagen.")
def clear_entries(self):
"""Clear all calendar entries"""
diff --git a/prediction_report_service.py b/prediction_report_service.py
new file mode 100644
index 0000000..b7e3620
--- /dev/null
+++ b/prediction_report_service.py
@@ -0,0 +1,158 @@
+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><head><meta charset='utf-8'>")
+ html.append("""
+ <style>
+ body { font-family: Arial, sans-serif; color: #222; }
+ h1 { font-size: 20px; margin-bottom: 6px; }
+ .meta { margin-bottom: 12px; }
+ .meta div { margin: 2px 0; }
+ table { border-collapse: collapse; width: 100%; }
+ th, td { border: 1px solid #e1e4ee; padding: 4px 6px; vertical-align: top; font-size: 150px; line-height: 1.3; }
+ th { background: #f5f7fb; text-align: left; }
+ tfoot td { font-weight: bold; }
+ .stamp { margin-top: 16px; font-size: 10px; color: #666; }
+ </style>
+ """)
+ html.append("</head><body>")
+ html.append("<h1>Ausfallzeitenrechner — Bericht</h1>")
+ html.append("<div class='meta'>")
+ html.append(f"<div><strong>Promotionsdatum:</strong> {launch_str}</div>")
+ html.append(f"<div><strong>Bewerbungszeitraum:</strong> {duration_str}</div>")
+ html.append(f"<div><strong>Bewerbungsfrist:</strong> {prediction_str}</div>")
+ html.append("</div>")
+
+ # Table header
+ html.append("<table>")
+ html.append(
+ "<thead><tr>"
+ "<th>Beginn</th>"
+ "<th>Ende</th>"
+ "<th>Art</th>"
+ "<th>Kommentar</th>"
+ "<th>Beginn Anrechnung</th>"
+ "<th>Ende Anrechnung</th>"
+ "<th>Anrechnungszeitraum</th>"
+ "</tr></thead><tbody>"
+ )
+
+ # 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('&', '&amp;')
+ .replace('<', '&lt;')
+ .replace('>', '&gt;'))
+
+ html.append(
+ f"<tr><td>{esc(start_text)}</td>"
+ f"<td>{esc(end_text)}</td>"
+ f"<td>{esc(keyword_text)}</td>"
+ f"<td>{esc(commentary_text)}</td>"
+ f"<td>{esc(c_start_text)}</td>"
+ f"<td>{esc(c_end_text)}</td>"
+ f"<td>{esc(time_text)}</td></tr>"
+ )
+
+ html.append("</tbody>")
+ html.append(f"<tfoot><tr><td colspan='7'>Summe Anrechnungszeitraum: {sum_str}</td></tr></tfoot>")
+ html.append("</table>")
+
+ html.append(f"<div class='stamp'>Erstellt am: {created_stamp}</div>")
+ html.append("</body></html>")
+
+ # 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
+
+