Services: Admin-Compliance, Backend-Compliance, AI-Compliance-SDK, Consent-SDK, Developer-Portal, PCA-Platform, DSMS Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
511 lines
16 KiB
Go
511 lines
16 KiB
Go
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
|
|
}
|