package iace import ( "fmt" "github.com/xuri/excelize/v2" ) // ExportExcel generates an XLSX workbook with project data across multiple sheets func (e *DocumentExporter) ExportExcel( project *Project, sections []TechFileSection, hazards []Hazard, assessments []RiskAssessment, mitigations []Mitigation, ) ([]byte, error) { if project == nil { return nil, fmt.Errorf("project must not be nil") } f := excelize.NewFile() defer f.Close() overviewSheet := "Uebersicht" f.SetSheetName("Sheet1", overviewSheet) e.xlsxOverview(f, overviewSheet, project) hazardSheet := "Gefaehrdungsprotokoll" f.NewSheet(hazardSheet) e.xlsxHazardLog(f, hazardSheet, hazards, assessments) mitigationSheet := "Massnahmen" f.NewSheet(mitigationSheet) e.xlsxMitigations(f, mitigationSheet, mitigations) matrixSheet := "Risikomatrix" f.NewSheet(matrixSheet) e.xlsxRiskMatrix(f, matrixSheet, assessments) sectionSheet := "Sektionen" f.NewSheet(sectionSheet) e.xlsxSections(f, sectionSheet, sections) buf, err := f.WriteToBuffer() if err != nil { return nil, fmt.Errorf("failed to write Excel: %w", err) } return buf.Bytes(), nil } func (e *DocumentExporter) xlsxOverview(f *excelize.File, sheet string, project *Project) { headerStyle, _ := f.NewStyle(&excelize.Style{ Font: &excelize.Font{Bold: true, Size: 11}, Fill: excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{"D9E1F2"}}, }) f.SetColWidth(sheet, "A", "A", 30) f.SetColWidth(sheet, "B", "B", 50) rows := [][]string{ {"Eigenschaft", "Wert"}, {"Maschinenname", project.MachineName}, {"Maschinentyp", project.MachineType}, {"Hersteller", project.Manufacturer}, {"Beschreibung", project.Description}, {"CE-Kennzeichnungsziel", project.CEMarkingTarget}, {"Projektstatus", string(project.Status)}, {"Vollstaendigkeits-Score", fmt.Sprintf("%.1f%%", project.CompletenessScore*100)}, {"Erstellt am", project.CreatedAt.Format("02.01.2006 15:04")}, {"Aktualisiert am", project.UpdatedAt.Format("02.01.2006 15:04")}, } for i, row := range rows { rowNum := i + 1 f.SetCellValue(sheet, cellRef("A", rowNum), row[0]) f.SetCellValue(sheet, cellRef("B", rowNum), row[1]) if i == 0 { f.SetCellStyle(sheet, cellRef("A", rowNum), cellRef("B", rowNum), headerStyle) } } } func (e *DocumentExporter) xlsxHazardLog(f *excelize.File, sheet string, hazards []Hazard, assessments []RiskAssessment) { headerStyle, _ := f.NewStyle(&excelize.Style{ Font: &excelize.Font{Bold: true, Size: 10, Color: "FFFFFF"}, Fill: excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{"4472C4"}}, Alignment: &excelize.Alignment{Horizontal: "center"}, }) headers := []string{"Nr", "Name", "Kategorie", "Beschreibung", "S", "E", "P", "A", "Inherent Risk", "C_eff", "Residual Risk", "Risk Level", "Akzeptabel"} colWidths := map[string]float64{ "A": 6, "B": 25, "C": 20, "D": 35, "E": 8, "F": 8, "G": 8, "H": 8, "I": 14, "J": 10, "K": 14, "L": 18, "M": 12, } for col, w := range colWidths { f.SetColWidth(sheet, col, col, w) } cols := []string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M"} for i, h := range headers { f.SetCellValue(sheet, cellRef(cols[i], 1), h) } f.SetCellStyle(sheet, "A1", cellRef(cols[len(cols)-1], 1), headerStyle) assessMap := buildAssessmentMap(assessments) for i, hazard := range hazards { row := i + 2 a := assessMap[hazard.ID.String()] f.SetCellValue(sheet, cellRef("A", row), i+1) f.SetCellValue(sheet, cellRef("B", row), hazard.Name) f.SetCellValue(sheet, cellRef("C", row), hazard.Category) f.SetCellValue(sheet, cellRef("D", row), hazard.Description) if a != nil { f.SetCellValue(sheet, cellRef("E", row), a.Severity) f.SetCellValue(sheet, cellRef("F", row), a.Exposure) f.SetCellValue(sheet, cellRef("G", row), a.Probability) f.SetCellValue(sheet, cellRef("H", row), a.Avoidance) f.SetCellValue(sheet, cellRef("I", row), fmt.Sprintf("%.1f", a.InherentRisk)) f.SetCellValue(sheet, cellRef("J", row), fmt.Sprintf("%.2f", a.CEff)) f.SetCellValue(sheet, cellRef("K", row), fmt.Sprintf("%.1f", a.ResidualRisk)) f.SetCellValue(sheet, cellRef("L", row), riskLevelLabel(a.RiskLevel)) acceptStr := "Nein" if a.IsAcceptable { acceptStr = "Ja" } f.SetCellValue(sheet, cellRef("M", row), acceptStr) r, g, b := riskLevelColor(a.RiskLevel) style, _ := f.NewStyle(&excelize.Style{ Fill: excelize.Fill{ Type: "pattern", Pattern: 1, Color: []string{rgbHex(r, g, b)}, }, Alignment: &excelize.Alignment{Horizontal: "center"}, }) f.SetCellStyle(sheet, cellRef("L", row), cellRef("L", row), style) } } } func (e *DocumentExporter) xlsxMitigations(f *excelize.File, sheet string, mitigations []Mitigation) { headerStyle, _ := f.NewStyle(&excelize.Style{ Font: &excelize.Font{Bold: true, Size: 10, Color: "FFFFFF"}, Fill: excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{"4472C4"}}, Alignment: &excelize.Alignment{Horizontal: "center"}, }) headers := []string{"Nr", "Name", "Typ", "Beschreibung", "Status", "Verifikationsmethode", "Ergebnis"} cols := []string{"A", "B", "C", "D", "E", "F", "G"} f.SetColWidth(sheet, "A", "A", 6) f.SetColWidth(sheet, "B", "B", 25) f.SetColWidth(sheet, "C", "C", 15) f.SetColWidth(sheet, "D", "D", 35) f.SetColWidth(sheet, "E", "E", 15) f.SetColWidth(sheet, "F", "F", 22) f.SetColWidth(sheet, "G", "G", 25) for i, h := range headers { f.SetCellValue(sheet, cellRef(cols[i], 1), h) } f.SetCellStyle(sheet, "A1", cellRef(cols[len(cols)-1], 1), headerStyle) for i, m := range mitigations { row := i + 2 f.SetCellValue(sheet, cellRef("A", row), i+1) f.SetCellValue(sheet, cellRef("B", row), m.Name) f.SetCellValue(sheet, cellRef("C", row), reductionTypeLabel(m.ReductionType)) f.SetCellValue(sheet, cellRef("D", row), m.Description) f.SetCellValue(sheet, cellRef("E", row), mitigationStatusLabel(m.Status)) f.SetCellValue(sheet, cellRef("F", row), string(m.VerificationMethod)) f.SetCellValue(sheet, cellRef("G", row), m.VerificationResult) } } func (e *DocumentExporter) xlsxRiskMatrix(f *excelize.File, sheet string, assessments []RiskAssessment) { headerStyle, _ := f.NewStyle(&excelize.Style{ Font: &excelize.Font{Bold: true, Size: 10, Color: "FFFFFF"}, Fill: excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{"4472C4"}}, Alignment: &excelize.Alignment{Horizontal: "center"}, }) f.SetColWidth(sheet, "A", "A", 25) f.SetColWidth(sheet, "B", "B", 12) f.SetCellValue(sheet, "A1", "Risikostufe") f.SetCellValue(sheet, "B1", "Anzahl") f.SetCellStyle(sheet, "A1", "B1", headerStyle) counts := countByRiskLevel(assessments) levels := []RiskLevel{ RiskLevelNotAcceptable, RiskLevelVeryHigh, RiskLevelCritical, RiskLevelHigh, RiskLevelMedium, RiskLevelLow, RiskLevelNegligible, } row := 2 for _, level := range levels { count := counts[level] f.SetCellValue(sheet, cellRef("A", row), riskLevelLabel(level)) f.SetCellValue(sheet, cellRef("B", row), count) r, g, b := riskLevelColor(level) style, _ := f.NewStyle(&excelize.Style{ Fill: excelize.Fill{ Type: "pattern", Pattern: 1, Color: []string{rgbHex(r, g, b)}, }, }) f.SetCellStyle(sheet, cellRef("A", row), cellRef("B", row), style) row++ } totalStyle, _ := f.NewStyle(&excelize.Style{ Font: &excelize.Font{Bold: true}, Fill: excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{"D9E1F2"}}, }) f.SetCellValue(sheet, cellRef("A", row), "Gesamt") f.SetCellValue(sheet, cellRef("B", row), len(assessments)) f.SetCellStyle(sheet, cellRef("A", row), cellRef("B", row), totalStyle) } func (e *DocumentExporter) xlsxSections(f *excelize.File, sheet string, sections []TechFileSection) { headerStyle, _ := f.NewStyle(&excelize.Style{ Font: &excelize.Font{Bold: true, Size: 10, Color: "FFFFFF"}, Fill: excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{"4472C4"}}, Alignment: &excelize.Alignment{Horizontal: "center"}, }) f.SetColWidth(sheet, "A", "A", 25) f.SetColWidth(sheet, "B", "B", 40) f.SetColWidth(sheet, "C", "C", 15) headers := []string{"Sektion", "Titel", "Status"} cols := []string{"A", "B", "C"} for i, h := range headers { f.SetCellValue(sheet, cellRef(cols[i], 1), h) } f.SetCellStyle(sheet, "A1", cellRef(cols[len(cols)-1], 1), headerStyle) for i, s := range sections { row := i + 2 f.SetCellValue(sheet, cellRef("A", row), s.SectionType) f.SetCellValue(sheet, cellRef("B", row), s.Title) f.SetCellValue(sheet, cellRef("C", row), string(s.Status)) } }