diff --git a/backend/internal/core/services/reporting/io_sheet.go b/backend/internal/core/services/reporting/io_sheet.go index 6fb0c9d..1d9c22d 100644 --- a/backend/internal/core/services/reporting/io_sheet.go +++ b/backend/internal/core/services/reporting/io_sheet.go @@ -41,6 +41,12 @@ func (s *IOSheet) indexHeaders() { } } +// Ooi J Sen +// function to return headers from excel sheet +func (s *IOSheet) GetHeaders() []string { + return s.headers +} + func (s *IOSheet) GetColumn(str string) (col int, ok bool) { if s.index == nil { s.indexHeaders() diff --git a/backend/internal/core/services/service_items.go b/backend/internal/core/services/service_items.go index 3ea79e2..af26427 100644 --- a/backend/internal/core/services/service_items.go +++ b/backend/internal/core/services/service_items.go @@ -89,6 +89,36 @@ func serializeLocation[T ~[]string](location T) string { return strings.Join(location, "/") } +// Ooi J Sen +// Function to validate headers +func validateHeaders(expected, actual []string) bool { + actualHeaderCount := make(map[string]int) + + // Count occurrences of headers in the actual slice + for _, header := range actual { + actualHeaderCount[header]++ + } + + // Check if all actual headers are within the expected headers + for header, count := range actualHeaderCount { + if count > 0 && !contains(expected, header) { + return false + } + } + + return true +} + +// Function to check if a string slice contains a specific string +func contains(slice []string, str string) bool { + for _, s := range slice { + if s == str { + return true + } + } + return false +} + // CsvImport imports items from a CSV file. using the standard defined format. // // CsvImport applies the following rules/operations @@ -103,6 +133,16 @@ func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data io.Re if err != nil { return 0, err } + + // Ooi J Sen + // Access excel sheet headers + headers := sheet.GetHeaders() + + // Validate column headers + expectedHeaders := []string{"HB.import_ref", "HB.location", "HB.labels", "HB.asset_id", "HB.archived", "HB.name", "HB.quantity", "HB.description", "HB.insured", "HB.notes", "HB.purchase_price", "HB.purchase_from", "HB.purchase_time", "HB.manufacturer", "HB.model_number", "HB.serial_number", "HB.lifetime_warranty", "HB.warranty_expires", "HB.warranty_details", "HB.sold_to", "HB.sold_price", "HB.sold_time", "HB.sold_notes",} + if !validateHeaders(expectedHeaders, headers) { + return 0, fmt.Errorf("CSV columns do not match the expected format") + } // ======================================== // Labels @@ -161,8 +201,12 @@ func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data io.Re finished := 0 + var errorMessage string + for i := range sheet.Rows { row := sheet.Rows[i] + + var hasNegativeValues bool createRequired := true @@ -178,6 +222,25 @@ func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data io.Re createRequired = false } } + + // Ooi J Sen + // Check integer fields for negative values + if row.Quantity < 0 { + errorMessage += fmt.Sprintf("Negative quantity at row %d\n", i+1) + hasNegativeValues = true + } + if row.PurchasePrice < 0 { + errorMessage += fmt.Sprintf("Negative purchase price at row %d\n", i+1) + hasNegativeValues = true + } + if row.SoldPrice < 0 { + errorMessage += fmt.Sprintf("Negative sold price at row %d\n", i+1) + hasNegativeValues = true + } + + if (hasNegativeValues) { + continue + } // ======================================== // Pre-Create Labels as necessary @@ -326,6 +389,15 @@ func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data io.Re finished++ } + // Ooi J Sen + // Display error messages in console + if errorMessage != "" { + // Log or handle the error message here + fmt.Println("Error Messages:") + fmt.Println(errorMessage) + return 0, fmt.Errorf("Errors detected in CSV:\n%s", errorMessage) + } + return finished, nil }