package ucca import ( "bytes" "encoding/base64" "fmt" "time" "github.com/jung-kurt/gofpdf" ) // PDFExporter generates PDF documents from obligations assessments type PDFExporter struct { language string } // NewPDFExporter creates a new PDF exporter func NewPDFExporter(language string) *PDFExporter { if language == "" { language = "de" } return &PDFExporter{language: language} } // ExportManagementMemo exports the management obligations overview as a PDF func (e *PDFExporter) ExportManagementMemo(overview *ManagementObligationsOverview) (*ExportMemoResponse, error) { pdf := gofpdf.New("P", "mm", "A4", "") // Set UTF-8 support with DejaVu font (fallback to core fonts) pdf.SetFont("Helvetica", "", 12) // Add first page pdf.AddPage() // Add title e.addTitle(pdf, overview) // Add executive summary e.addExecutiveSummary(pdf, overview) // Add applicable regulations e.addApplicableRegulations(pdf, overview) // Add sanctions summary e.addSanctionsSummary(pdf, overview) // Add obligations table e.addObligationsTable(pdf, overview) // Add incident deadlines if present if len(overview.IncidentDeadlines) > 0 { e.addIncidentDeadlines(pdf, overview) } // Add footer with generation date e.addFooter(pdf, overview) // Generate PDF bytes var buf bytes.Buffer if err := pdf.Output(&buf); err != nil { return nil, fmt.Errorf("failed to generate PDF: %w", err) } // Encode as base64 content := base64.StdEncoding.EncodeToString(buf.Bytes()) return &ExportMemoResponse{ Content: content, ContentType: "application/pdf", Filename: fmt.Sprintf("pflichten-uebersicht-%s.pdf", time.Now().Format("2006-01-02")), GeneratedAt: time.Now(), }, nil } // addTitle adds the document title func (e *PDFExporter) addTitle(pdf *gofpdf.Fpdf, overview *ManagementObligationsOverview) { pdf.SetFont("Helvetica", "B", 24) pdf.SetTextColor(0, 0, 0) title := "Regulatorische Pflichten-Uebersicht" if e.language == "en" { title = "Regulatory Obligations Overview" } pdf.CellFormat(0, 15, title, "", 1, "C", false, 0, "") // Organization name if overview.OrganizationName != "" { pdf.SetFont("Helvetica", "", 14) pdf.CellFormat(0, 10, overview.OrganizationName, "", 1, "C", false, 0, "") } // Date pdf.SetFont("Helvetica", "I", 10) dateStr := overview.AssessmentDate.Format("02.01.2006") pdf.CellFormat(0, 8, fmt.Sprintf("Stand: %s", dateStr), "", 1, "C", false, 0, "") pdf.Ln(10) } // addExecutiveSummary adds the executive summary section func (e *PDFExporter) addExecutiveSummary(pdf *gofpdf.Fpdf, overview *ManagementObligationsOverview) { e.addSectionHeader(pdf, "Executive Summary") summary := overview.ExecutiveSummary // Stats table pdf.SetFont("Helvetica", "", 11) stats := []struct { label string value string }{ {"Anwendbare Regulierungen", fmt.Sprintf("%d", summary.TotalRegulations)}, {"Gesamtzahl Pflichten", fmt.Sprintf("%d", summary.TotalObligations)}, {"Kritische Pflichten", fmt.Sprintf("%d", summary.CriticalObligations)}, {"Kommende Fristen (30 Tage)", fmt.Sprintf("%d", summary.UpcomingDeadlines)}, {"Compliance Score", fmt.Sprintf("%d%%", summary.ComplianceScore)}, } for _, stat := range stats { pdf.SetFont("Helvetica", "B", 11) pdf.CellFormat(80, 7, stat.label+":", "", 0, "L", false, 0, "") pdf.SetFont("Helvetica", "", 11) pdf.CellFormat(0, 7, stat.value, "", 1, "L", false, 0, "") } // Key risks if len(summary.KeyRisks) > 0 { pdf.Ln(5) pdf.SetFont("Helvetica", "B", 11) pdf.CellFormat(0, 7, "Wesentliche Risiken:", "", 1, "L", false, 0, "") pdf.SetFont("Helvetica", "", 10) for _, risk := range summary.KeyRisks { pdf.CellFormat(10, 6, "", "", 0, "L", false, 0, "") pdf.CellFormat(0, 6, "- "+risk, "", 1, "L", false, 0, "") } } // Recommended actions if len(summary.RecommendedActions) > 0 { pdf.Ln(5) pdf.SetFont("Helvetica", "B", 11) pdf.CellFormat(0, 7, "Empfohlene Massnahmen:", "", 1, "L", false, 0, "") pdf.SetFont("Helvetica", "", 10) for _, action := range summary.RecommendedActions { pdf.CellFormat(10, 6, "", "", 0, "L", false, 0, "") pdf.CellFormat(0, 6, "- "+action, "", 1, "L", false, 0, "") } } pdf.Ln(10) } // addApplicableRegulations adds the applicable regulations section func (e *PDFExporter) addApplicableRegulations(pdf *gofpdf.Fpdf, overview *ManagementObligationsOverview) { e.addSectionHeader(pdf, "Anwendbare Regulierungen") pdf.SetFont("Helvetica", "", 10) // Table header pdf.SetFillColor(240, 240, 240) pdf.SetFont("Helvetica", "B", 10) pdf.CellFormat(60, 8, "Regulierung", "1", 0, "L", true, 0, "") pdf.CellFormat(50, 8, "Klassifizierung", "1", 0, "L", true, 0, "") pdf.CellFormat(30, 8, "Pflichten", "1", 0, "C", true, 0, "") pdf.CellFormat(50, 8, "Grund", "1", 1, "L", true, 0, "") pdf.SetFont("Helvetica", "", 10) for _, reg := range overview.ApplicableRegulations { pdf.CellFormat(60, 7, reg.Name, "1", 0, "L", false, 0, "") pdf.CellFormat(50, 7, truncateString(reg.Classification, 25), "1", 0, "L", false, 0, "") pdf.CellFormat(30, 7, fmt.Sprintf("%d", reg.ObligationCount), "1", 0, "C", false, 0, "") pdf.CellFormat(50, 7, truncateString(reg.Reason, 25), "1", 1, "L", false, 0, "") } pdf.Ln(10) } // addSanctionsSummary adds the sanctions summary section func (e *PDFExporter) addSanctionsSummary(pdf *gofpdf.Fpdf, overview *ManagementObligationsOverview) { e.addSectionHeader(pdf, "Sanktionsrisiken") sanctions := overview.SanctionsSummary pdf.SetFont("Helvetica", "", 11) // Max financial risk if sanctions.MaxFinancialRisk != "" { pdf.SetFont("Helvetica", "B", 11) pdf.CellFormat(60, 7, "Max. Finanzrisiko:", "", 0, "L", false, 0, "") pdf.SetFont("Helvetica", "", 11) pdf.SetTextColor(200, 0, 0) pdf.CellFormat(0, 7, sanctions.MaxFinancialRisk, "", 1, "L", false, 0, "") pdf.SetTextColor(0, 0, 0) } // Personal liability pdf.SetFont("Helvetica", "B", 11) pdf.CellFormat(60, 7, "Persoenliche Haftung:", "", 0, "L", false, 0, "") pdf.SetFont("Helvetica", "", 11) liabilityText := "Nein" if sanctions.PersonalLiabilityRisk { liabilityText = "Ja - Geschaeftsfuehrung kann persoenlich haften" pdf.SetTextColor(200, 0, 0) } pdf.CellFormat(0, 7, liabilityText, "", 1, "L", false, 0, "") pdf.SetTextColor(0, 0, 0) // Criminal liability pdf.SetFont("Helvetica", "B", 11) pdf.CellFormat(60, 7, "Strafrechtliche Konsequenzen:", "", 0, "L", false, 0, "") pdf.SetFont("Helvetica", "", 11) criminalText := "Nein" if sanctions.CriminalLiabilityRisk { criminalText = "Moeglich" pdf.SetTextColor(200, 0, 0) } pdf.CellFormat(0, 7, criminalText, "", 1, "L", false, 0, "") pdf.SetTextColor(0, 0, 0) // Summary if sanctions.Summary != "" { pdf.Ln(3) pdf.SetFont("Helvetica", "I", 10) pdf.MultiCell(0, 5, sanctions.Summary, "", "L", false) } pdf.Ln(10) } // addObligationsTable adds the obligations table func (e *PDFExporter) addObligationsTable(pdf *gofpdf.Fpdf, overview *ManagementObligationsOverview) { e.addSectionHeader(pdf, "Pflichten-Uebersicht") // Group by priority criticalObls := []Obligation{} highObls := []Obligation{} otherObls := []Obligation{} for _, obl := range overview.Obligations { switch obl.Priority { case PriorityCritical, ObligationPriority("kritisch"): criticalObls = append(criticalObls, obl) case PriorityHigh, ObligationPriority("hoch"): highObls = append(highObls, obl) default: otherObls = append(otherObls, obl) } } // Critical obligations if len(criticalObls) > 0 { pdf.SetFont("Helvetica", "B", 11) pdf.SetTextColor(200, 0, 0) pdf.CellFormat(0, 8, fmt.Sprintf("Kritische Pflichten (%d)", len(criticalObls)), "", 1, "L", false, 0, "") pdf.SetTextColor(0, 0, 0) e.addObligationsList(pdf, criticalObls) } // High priority obligations if len(highObls) > 0 { pdf.SetFont("Helvetica", "B", 11) pdf.SetTextColor(200, 100, 0) pdf.CellFormat(0, 8, fmt.Sprintf("Hohe Prioritaet (%d)", len(highObls)), "", 1, "L", false, 0, "") pdf.SetTextColor(0, 0, 0) e.addObligationsList(pdf, highObls) } // Other obligations if len(otherObls) > 0 { pdf.SetFont("Helvetica", "B", 11) pdf.CellFormat(0, 8, fmt.Sprintf("Weitere Pflichten (%d)", len(otherObls)), "", 1, "L", false, 0, "") e.addObligationsList(pdf, otherObls) } } // addObligationsList adds a list of obligations func (e *PDFExporter) addObligationsList(pdf *gofpdf.Fpdf, obligations []Obligation) { // Check if we need a new page if pdf.GetY() > 250 { pdf.AddPage() } pdf.SetFont("Helvetica", "", 9) for _, obl := range obligations { // Check if we need a new page if pdf.GetY() > 270 { pdf.AddPage() } // Obligation ID and title pdf.SetFont("Helvetica", "B", 9) pdf.CellFormat(25, 5, obl.ID, "", 0, "L", false, 0, "") pdf.SetFont("Helvetica", "", 9) pdf.CellFormat(0, 5, truncateString(obl.Title, 80), "", 1, "L", false, 0, "") // Legal basis if len(obl.LegalBasis) > 0 { pdf.SetFont("Helvetica", "I", 8) pdf.CellFormat(25, 4, "", "", 0, "L", false, 0, "") legalText := "" for i, lb := range obl.LegalBasis { if i > 0 { legalText += ", " } legalText += lb.Norm } pdf.CellFormat(0, 4, truncateString(legalText, 100), "", 1, "L", false, 0, "") } // Deadline if obl.Deadline != nil { pdf.SetFont("Helvetica", "", 8) pdf.CellFormat(25, 4, "", "", 0, "L", false, 0, "") deadlineText := "Frist: " if obl.Deadline.Date != nil { deadlineText += obl.Deadline.Date.Format("02.01.2006") } else if obl.Deadline.Duration != "" { deadlineText += obl.Deadline.Duration } else if obl.Deadline.Interval != "" { deadlineText += obl.Deadline.Interval } pdf.CellFormat(0, 4, deadlineText, "", 1, "L", false, 0, "") } // Responsible pdf.SetFont("Helvetica", "", 8) pdf.CellFormat(25, 4, "", "", 0, "L", false, 0, "") pdf.CellFormat(0, 4, fmt.Sprintf("Verantwortlich: %s", obl.Responsible), "", 1, "L", false, 0, "") pdf.Ln(2) } pdf.Ln(5) } // addIncidentDeadlines adds the incident deadlines section func (e *PDFExporter) addIncidentDeadlines(pdf *gofpdf.Fpdf, overview *ManagementObligationsOverview) { // Check if we need a new page if pdf.GetY() > 220 { pdf.AddPage() } e.addSectionHeader(pdf, "Meldepflichten bei Vorfaellen") pdf.SetFont("Helvetica", "", 10) // Group by regulation byRegulation := make(map[string][]IncidentDeadline) for _, deadline := range overview.IncidentDeadlines { byRegulation[deadline.RegulationID] = append(byRegulation[deadline.RegulationID], deadline) } for regID, deadlines := range byRegulation { pdf.SetFont("Helvetica", "B", 10) regName := regID for _, reg := range overview.ApplicableRegulations { if reg.ID == regID { regName = reg.Name break } } pdf.CellFormat(0, 7, regName, "", 1, "L", false, 0, "") pdf.SetFont("Helvetica", "", 9) for _, dl := range deadlines { pdf.CellFormat(40, 6, dl.Phase+":", "", 0, "L", false, 0, "") pdf.SetFont("Helvetica", "B", 9) pdf.CellFormat(30, 6, dl.Deadline, "", 0, "L", false, 0, "") pdf.SetFont("Helvetica", "", 9) pdf.CellFormat(0, 6, "an "+dl.Recipient, "", 1, "L", false, 0, "") } pdf.Ln(3) } pdf.Ln(5) } // addFooter adds the document footer func (e *PDFExporter) addFooter(pdf *gofpdf.Fpdf, overview *ManagementObligationsOverview) { pdf.SetY(-30) pdf.SetFont("Helvetica", "I", 8) pdf.SetTextColor(128, 128, 128) pdf.CellFormat(0, 5, fmt.Sprintf("Generiert am %s mit BreakPilot AI Compliance SDK", time.Now().Format("02.01.2006 15:04")), "", 1, "C", false, 0, "") pdf.CellFormat(0, 5, "Dieses Dokument ersetzt keine rechtliche Beratung.", "", 1, "C", false, 0, "") } // addSectionHeader adds a section header func (e *PDFExporter) addSectionHeader(pdf *gofpdf.Fpdf, title string) { pdf.SetFont("Helvetica", "B", 14) pdf.SetTextColor(50, 50, 50) pdf.CellFormat(0, 10, title, "", 1, "L", false, 0, "") pdf.SetTextColor(0, 0, 0) // Underline pdf.SetDrawColor(200, 200, 200) pdf.Line(10, pdf.GetY(), 200, pdf.GetY()) pdf.Ln(5) } // truncateString truncates a string to maxLen characters func truncateString(s string, maxLen int) string { if len(s) <= maxLen { return s } return s[:maxLen-3] + "..." } // ExportMarkdown exports the overview as Markdown (for compatibility) func (e *PDFExporter) ExportMarkdown(overview *ManagementObligationsOverview) (*ExportMemoResponse, error) { var buf bytes.Buffer // Title buf.WriteString(fmt.Sprintf("# Regulatorische Pflichten-Uebersicht\n\n")) if overview.OrganizationName != "" { buf.WriteString(fmt.Sprintf("**Organisation:** %s\n\n", overview.OrganizationName)) } buf.WriteString(fmt.Sprintf("**Stand:** %s\n\n", overview.AssessmentDate.Format("02.01.2006"))) // Executive Summary buf.WriteString("## Executive Summary\n\n") summary := overview.ExecutiveSummary buf.WriteString(fmt.Sprintf("| Metrik | Wert |\n")) buf.WriteString(fmt.Sprintf("|--------|------|\n")) buf.WriteString(fmt.Sprintf("| Anwendbare Regulierungen | %d |\n", summary.TotalRegulations)) buf.WriteString(fmt.Sprintf("| Gesamtzahl Pflichten | %d |\n", summary.TotalObligations)) buf.WriteString(fmt.Sprintf("| Kritische Pflichten | %d |\n", summary.CriticalObligations)) buf.WriteString(fmt.Sprintf("| Kommende Fristen (30 Tage) | %d |\n", summary.UpcomingDeadlines)) buf.WriteString(fmt.Sprintf("| Compliance Score | %d%% |\n\n", summary.ComplianceScore)) // Key Risks if len(summary.KeyRisks) > 0 { buf.WriteString("### Wesentliche Risiken\n\n") for _, risk := range summary.KeyRisks { buf.WriteString(fmt.Sprintf("- %s\n", risk)) } buf.WriteString("\n") } // Recommended Actions if len(summary.RecommendedActions) > 0 { buf.WriteString("### Empfohlene Massnahmen\n\n") for _, action := range summary.RecommendedActions { buf.WriteString(fmt.Sprintf("- %s\n", action)) } buf.WriteString("\n") } // Applicable Regulations buf.WriteString("## Anwendbare Regulierungen\n\n") buf.WriteString("| Regulierung | Klassifizierung | Pflichten | Grund |\n") buf.WriteString("|-------------|-----------------|-----------|-------|\n") for _, reg := range overview.ApplicableRegulations { buf.WriteString(fmt.Sprintf("| %s | %s | %d | %s |\n", reg.Name, reg.Classification, reg.ObligationCount, reg.Reason)) } buf.WriteString("\n") // Sanctions Summary buf.WriteString("## Sanktionsrisiken\n\n") sanctions := overview.SanctionsSummary if sanctions.MaxFinancialRisk != "" { buf.WriteString(fmt.Sprintf("- **Max. Finanzrisiko:** %s\n", sanctions.MaxFinancialRisk)) } buf.WriteString(fmt.Sprintf("- **Persoenliche Haftung:** %v\n", sanctions.PersonalLiabilityRisk)) buf.WriteString(fmt.Sprintf("- **Strafrechtliche Konsequenzen:** %v\n\n", sanctions.CriminalLiabilityRisk)) if sanctions.Summary != "" { buf.WriteString(fmt.Sprintf("*%s*\n\n", sanctions.Summary)) } // Obligations buf.WriteString("## Pflichten-Uebersicht\n\n") for _, obl := range overview.Obligations { buf.WriteString(fmt.Sprintf("### %s - %s\n\n", obl.ID, obl.Title)) buf.WriteString(fmt.Sprintf("**Prioritaet:** %s | **Verantwortlich:** %s\n\n", obl.Priority, obl.Responsible)) if len(obl.LegalBasis) > 0 { buf.WriteString("**Rechtsgrundlage:** ") for i, lb := range obl.LegalBasis { if i > 0 { buf.WriteString(", ") } buf.WriteString(lb.Norm) } buf.WriteString("\n\n") } buf.WriteString(fmt.Sprintf("%s\n\n", obl.Description)) } // Incident Deadlines if len(overview.IncidentDeadlines) > 0 { buf.WriteString("## Meldepflichten bei Vorfaellen\n\n") for _, dl := range overview.IncidentDeadlines { buf.WriteString(fmt.Sprintf("- **%s:** %s an %s\n", dl.Phase, dl.Deadline, dl.Recipient)) } buf.WriteString("\n") } // Footer buf.WriteString("---\n\n") buf.WriteString(fmt.Sprintf("*Generiert am %s mit BreakPilot AI Compliance SDK*\n", time.Now().Format("02.01.2006 15:04"))) return &ExportMemoResponse{ Content: buf.String(), ContentType: "text/markdown", Filename: fmt.Sprintf("pflichten-uebersicht-%s.md", time.Now().Format("2006-01-02")), GeneratedAt: time.Now(), }, nil }