Each of the four oversized files (training/store.go 1569 LOC, ucca/rules.go 1231 LOC, ucca_handlers.go 1135 LOC, document_export.go 1101 LOC) is split by logical group into same-package files, all under the 500-line hard cap. Zero behavior changes, no renamed exported symbols. Also fixed pre-existing hazard_library split (missing functions and duplicate UUID keys from a prior session). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
262 lines
8.4 KiB
Go
262 lines
8.4 KiB
Go
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))
|
|
}
|
|
}
|