package report import ( "bytes" "context" "database/sql" "fmt" "log/slog" "reflect" "strconv" "time" "vctp/db" "github.com/xuri/excelize/v2" ) func CreateReport(logger *slog.Logger, Database db.Database, ctx context.Context) ([]byte, error) { //var xlsx *excelize.File sheetName := "Inventory Report" var buffer bytes.Buffer logger.Debug("Querying inventory table") results, err := Database.Queries().GetReportInventory(ctx) if err != nil { logger.Error("Unable to query inventory table", "error", err) return nil, err } if len(results) == 0 { logger.Error("Empty inventory results") return nil, fmt.Errorf("Empty inventory results") } // Create excek workbook xlsx := excelize.NewFile() err = xlsx.SetSheetName("Sheet1", sheetName) if err != nil { logger.Error("Error setting sheet name", "error", err, "sheet_name", sheetName) return nil, err } // Set the document properties err = xlsx.SetDocProps(&excelize.DocProperties{ Creator: "json2excel", Created: time.Now().Format(time.RFC3339), }) if err != nil { logger.Error("Error setting document properties", "error", err, "sheet_name", sheetName) } // Use reflection to determine column headings from the first item firstItem := results[0] v := reflect.ValueOf(firstItem) typeOfItem := v.Type() // Create column headers dynamically for i := 0; i < v.NumField(); i++ { column := string(rune('A'+i)) + "1" // A1, B1, C1, etc. xlsx.SetCellValue(sheetName, column, typeOfItem.Field(i).Name) } // Populate the Excel file with data from the Inventory table for i, item := range results { v = reflect.ValueOf(item) for j := 0; j < v.NumField(); j++ { column := string(rune('A'+j)) + strconv.Itoa(i+2) // Start from row 2 value := getFieldValue(v.Field(j)) xlsx.SetCellValue(sheetName, column, value) } } // Save the Excel file into a byte buffer if err := xlsx.Write(&buffer); err != nil { return nil, err } return buffer.Bytes(), nil } // Helper function to get the actual value of sql.Null types func getFieldValue(field reflect.Value) interface{} { switch field.Kind() { case reflect.Struct: // Handle sql.Null types based on their concrete type switch field.Interface().(type) { case sql.NullString: ns := field.Interface().(sql.NullString) if ns.Valid { return ns.String } return "" case sql.NullInt64: ni := field.Interface().(sql.NullInt64) if ni.Valid { return ni.Int64 } return 0 case sql.NullFloat64: nf := field.Interface().(sql.NullFloat64) if nf.Valid { return nf.Float64 } return nil case sql.NullBool: nb := field.Interface().(sql.NullBool) if nb.Valid { return nb.Bool } return false } } return field.Interface() // Return the value as-is for non-sql.Null types }