From 1a13e59c5e300ca35d416100d12017aa78951f94 Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Mon, 12 Sep 2022 20:29:54 -0800 Subject: [PATCH] add additional rows to CSV importer --- backend/internal/services/service_items.go | 57 +++++++----- .../internal/services/service_items_csv.go | 90 +++++++++---------- .../services/service_items_csv_test.go | 29 +++--- .../internal/services/service_items_test.go | 22 ++--- backend/internal/types/item_types.go | 2 + 5 files changed, 109 insertions(+), 91 deletions(-) diff --git a/backend/internal/services/service_items.go b/backend/internal/services/service_items.go index bb83845..147cb5f 100644 --- a/backend/internal/services/service_items.go +++ b/backend/internal/services/service_items.go @@ -156,7 +156,8 @@ func (svc *ItemService) CsvImport(ctx context.Context, gid uuid.UUID, data [][]s if len(row) == 0 { continue } - if len(row) != 14 { + + if len(row) != NumOfCols { return ErrInvalidCsv } @@ -227,15 +228,16 @@ func (svc *ItemService) CsvImport(ctx context.Context, gid uuid.UUID, data [][]s } log.Info(). - Str("name", row.Name). + Str("name", row.Item.Name). Str("location", row.Location). Strs("labels", row.getLabels()). Str("locationId", locationID.String()). - Msgf("Creating Item: %s", row.Name) + Msgf("Creating Item: %s", row.Item.Name) result, err := svc.repo.Items.Create(ctx, gid, types.ItemCreate{ - Name: row.Name, - Description: row.Description, + ImportRef: row.Item.ImportRef, + Name: row.Item.Name, + Description: row.Item.Description, LabelIDs: labelIDs, LocationID: locationID, }) @@ -246,21 +248,36 @@ func (svc *ItemService) CsvImport(ctx context.Context, gid uuid.UUID, data [][]s // Update the item with the rest of the data _, err = svc.repo.Items.Update(ctx, types.ItemUpdate{ - ID: result.ID, - Name: result.Name, - LocationID: locationID, - LabelIDs: labelIDs, - Description: result.Description, - SerialNumber: row.SerialNumber, - ModelNumber: row.ModelNumber, - Manufacturer: row.Manufacturer, - Notes: row.Notes, - PurchaseFrom: row.PurchaseFrom, - PurchasePrice: row.parsedPurchasedPrice(), - PurchaseTime: row.parsedPurchasedAt(), - SoldTo: row.SoldTo, - SoldPrice: row.parsedSoldPrice(), - SoldTime: row.parsedSoldAt(), + // Edges + LocationID: locationID, + LabelIDs: labelIDs, + + // General Fields + ID: result.ID, + Name: result.Name, + Description: result.Description, + Insured: row.Item.Insured, + Notes: row.Item.Notes, + + // Identifies the item as imported + SerialNumber: row.Item.SerialNumber, + ModelNumber: row.Item.ModelNumber, + Manufacturer: row.Item.Manufacturer, + + // Purchase + PurchaseFrom: row.Item.PurchaseFrom, + PurchasePrice: row.Item.PurchasePrice, + PurchaseTime: row.Item.PurchaseTime, + + // Warranty + LifetimeWarranty: row.Item.LifetimeWarranty, + WarrantyExpires: row.Item.WarrantyExpires, + WarrantyDetails: row.Item.WarrantyDetails, + + SoldTo: row.Item.SoldTo, + SoldPrice: row.Item.SoldPrice, + SoldTime: row.Item.SoldTime, + SoldNotes: row.Item.SoldNotes, }) if err != nil { diff --git a/backend/internal/services/service_items_csv.go b/backend/internal/services/service_items_csv.go index 5e25127..ed8e5a4 100644 --- a/backend/internal/services/service_items_csv.go +++ b/backend/internal/services/service_items_csv.go @@ -5,10 +5,14 @@ import ( "strconv" "strings" "time" + + "github.com/hay-kot/content/backend/internal/types" ) var ErrInvalidCsv = errors.New("invalid csv") +const NumOfCols = 21 + func parseFloat(s string) float64 { if s == "" { return 0 @@ -26,60 +30,56 @@ func parseDate(s string) time.Time { return p } +func parseBool(s string) bool { + switch strings.ToLower(s) { + case "true", "yes", "1": + return true + default: + return false + } +} + +func parseInt(s string) int { + i, _ := strconv.Atoi(s) + return i +} + type csvRow struct { - Location string - Labels string - Name string - Description string - SerialNumber string - ModelNumber string - Manufacturer string - Notes string - PurchaseFrom string - PurchasedPrice string - PurchasedAt string - SoldTo string - SoldPrice string - SoldAt string + Item types.ItemSummary + Location string + LabelStr string } func newCsvRow(row []string) csvRow { return csvRow{ - Location: row[0], - Labels: row[1], - Name: row[2], - Description: row[3], - SerialNumber: row[4], - ModelNumber: row[5], - Manufacturer: row[6], - Notes: row[7], - PurchaseFrom: row[8], - PurchasedPrice: row[9], - PurchasedAt: row[10], - SoldTo: row[11], - SoldPrice: row[12], - SoldAt: row[13], + Location: row[1], + LabelStr: row[2], + Item: types.ItemSummary{ + ImportRef: row[0], + Quantity: parseInt(row[3]), + Name: row[4], + Description: row[5], + Insured: parseBool(row[6]), + SerialNumber: row[7], + ModelNumber: row[8], + Manufacturer: row[9], + Notes: row[10], + PurchaseFrom: row[11], + PurchasePrice: parseFloat(row[12]), + PurchaseTime: parseDate(row[13]), + LifetimeWarranty: parseBool(row[14]), + WarrantyExpires: parseDate(row[15]), + WarrantyDetails: row[16], + SoldTo: row[17], + SoldPrice: parseFloat(row[18]), + SoldTime: parseDate(row[19]), + SoldNotes: row[20], + }, } } -func (c csvRow) parsedSoldPrice() float64 { - return parseFloat(c.SoldPrice) -} - -func (c csvRow) parsedPurchasedPrice() float64 { - return parseFloat(c.PurchasedPrice) -} - -func (c csvRow) parsedPurchasedAt() time.Time { - return parseDate(c.PurchasedAt) -} - -func (c csvRow) parsedSoldAt() time.Time { - return parseDate(c.SoldAt) -} - func (c csvRow) getLabels() []string { - split := strings.Split(c.Labels, ";") + split := strings.Split(c.LabelStr, ";") // Trim each for i, s := range split { diff --git a/backend/internal/services/service_items_csv_test.go b/backend/internal/services/service_items_csv_test.go index 34f35d0..fed6e31 100644 --- a/backend/internal/services/service_items_csv_test.go +++ b/backend/internal/services/service_items_csv_test.go @@ -8,14 +8,13 @@ import ( ) const CSV_DATA = ` -Location,Labels,Name,Description,Serial Number,Mode Number,Manufacturer,Notes,Purchase From,Purchased Price,Purchased At,Sold To,Sold Price,Sold At -Garage,IOT;Home Assistant; Z-Wave,Zooz Universal Relay ZEN17,"Zooz 700 Series Z-Wave Universal Relay ZEN17 for Awnings, Garage Doors, Sprinklers, and More | 2 NO-C-NC Relays (20A, 10A) | Signal Repeater | Hub Required (Compatible with SmartThings and Hubitat)",,ZEN17,Zooz,,Amazon,39.95,10/13/2021,,, -Living Room,IOT;Home Assistant; Z-Wave,Zooz Motion Sensor,"Zooz Z-Wave Plus S2 Motion Sensor ZSE18 with Magnetic Mount, Works with Vera and SmartThings",,ZSE18,Zooz,,Amazon,29.95,10/15/2021,,, -Office,IOT;Home Assistant; Z-Wave,Zooz 110v Power Switch,"Zooz Z-Wave Plus Power Switch ZEN15 for 110V AC Units, Sump Pumps, Humidifiers, and More",,ZEN15,Zooz,,Amazon,39.95,10/13/2021,,, -Downstairs,IOT;Home Assistant; Z-Wave,Ecolink Z-Wave PIR Motion Sensor,"Ecolink Z-Wave PIR Motion Detector Pet Immune, White (PIRZWAVE2.5-ECO)",,PIRZWAVE2.5-ECO,Ecolink,,Amazon,35.58,10/21/2020,,, -Entry,IOT;Home Assistant; Z-Wave,Yale Security Touchscreen Deadbolt,"Yale Security YRD226-ZW2-619 YRD226ZW2619 Touchscreen Deadbolt, Satin Nickel",,YRD226ZW2619,Yale,,Amazon,120.39,10/14/2020,,, -Kitchen,IOT;Home Assistant; Z-Wave,Smart Rocker Light Dimmer,"UltraPro Z-Wave Smart Rocker Light Dimmer with QuickFit and SimpleWire, 3-Way Ready, Compatible with Alexa, Google Assistant, ZWave Hub Required, Repeater/Range Extender, White Paddle Only, 39351",,39351,Honeywell,,Amazon,65.98,09/30/0202,,, -` +Import Ref,Location,Labels,Quantity,Name,Description,Insured,Serial Number,Mode Number,Manufacturer,Notes,Purchase From,Purchased Price,Purchased Time,Lifetime Warranty,Warranty Expires,Warranty Details,Sold To,Sold Price,Sold Time,Sold Notes +,Garage,IOT;Home Assistant; Z-Wave,1,Zooz Universal Relay ZEN17,"Zooz 700 Series Z-Wave Universal Relay ZEN17 for Awnings, Garage Doors, Sprinklers, and More | 2 NO-C-NC Relays (20A, 10A) | Signal Repeater | Hub Required (Compatible with SmartThings and Hubitat)",,,ZEN17,Zooz,,Amazon,39.95,10/13/2021,,,,,,, +,Living Room,IOT;Home Assistant; Z-Wave,1,Zooz Motion Sensor,"Zooz Z-Wave Plus S2 Motion Sensor ZSE18 with Magnetic Mount, Works with Vera and SmartThings",,,ZSE18,Zooz,,Amazon,29.95,10/15/2021,,,,,,, +,Office,IOT;Home Assistant; Z-Wave,1,Zooz 110v Power Switch,"Zooz Z-Wave Plus Power Switch ZEN15 for 110V AC Units, Sump Pumps, Humidifiers, and More",,,ZEN15,Zooz,,Amazon,39.95,10/13/2021,,,,,,, +,Downstairs,IOT;Home Assistant; Z-Wave,1,Ecolink Z-Wave PIR Motion Sensor,"Ecolink Z-Wave PIR Motion Detector Pet Immune, White (PIRZWAVE2.5-ECO)",,,PIRZWAVE2.5-ECO,Ecolink,,Amazon,35.58,10/21/2020,,,,,,, +,Entry,IOT;Home Assistant; Z-Wave,1,Yale Security Touchscreen Deadbolt,"Yale Security YRD226-ZW2-619 YRD226ZW2619 Touchscreen Deadbolt, Satin Nickel",,,YRD226ZW2619,Yale,,Amazon,120.39,10/14/2020,,,,,,, +,Kitchen,IOT;Home Assistant; Z-Wave,1,Smart Rocker Light Dimmer,"UltraPro Z-Wave Smart Rocker Light Dimmer with QuickFit and SimpleWire, 3-Way Ready, Compatible with Alexa, Google Assistant, ZWave Hub Required, Repeater/Range Extender, White Paddle Only, 39351",,,39351,Honeywell,,Amazon,65.98,09/30/0202,,,,,,,` func loadcsv() [][]string { reader := csv.NewReader(bytes.NewBuffer([]byte(CSV_DATA))) @@ -30,7 +29,7 @@ func loadcsv() [][]string { func Test_csvRow_getLabels(t *testing.T) { type fields struct { - Labels string + LabelStr string } tests := []struct { name string @@ -40,28 +39,28 @@ func Test_csvRow_getLabels(t *testing.T) { { name: "basic test", fields: fields{ - Labels: "IOT;Home Assistant;Z-Wave", + LabelStr: "IOT;Home Assistant;Z-Wave", }, want: []string{"IOT", "Home Assistant", "Z-Wave"}, }, { name: "no labels", fields: fields{ - Labels: "", + LabelStr: "", }, want: []string{}, }, { name: "single label", fields: fields{ - Labels: "IOT", + LabelStr: "IOT", }, want: []string{"IOT"}, }, { name: "trailing semicolon", fields: fields{ - Labels: "IOT;", + LabelStr: "IOT;", }, want: []string{"IOT"}, }, @@ -69,7 +68,7 @@ func Test_csvRow_getLabels(t *testing.T) { { name: "whitespace", fields: fields{ - Labels: " IOT; Home Assistant; Z-Wave ", + LabelStr: " IOT; Home Assistant; Z-Wave ", }, want: []string{"IOT", "Home Assistant", "Z-Wave"}, }, @@ -77,7 +76,7 @@ func Test_csvRow_getLabels(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := csvRow{ - Labels: tt.fields.Labels, + LabelStr: tt.fields.LabelStr, } if got := c.getLabels(); !reflect.DeepEqual(got, tt.want) { t.Errorf("csvRow.getLabels() = %v, want %v", got, tt.want) diff --git a/backend/internal/services/service_items_test.go b/backend/internal/services/service_items_test.go index ceeb764..b71a74e 100644 --- a/backend/internal/services/service_items_test.go +++ b/backend/internal/services/service_items_test.go @@ -73,21 +73,21 @@ func TestItemService_CsvImport(t *testing.T) { } for _, csvRow := range dataCsv { - if csvRow.Name == item.Name { - assert.Equal(t, csvRow.Description, item.Description) - assert.Equal(t, csvRow.SerialNumber, item.SerialNumber) - assert.Equal(t, csvRow.Manufacturer, item.Manufacturer) - assert.Equal(t, csvRow.Notes, item.Notes) + if csvRow.Item.Name == item.Name { + assert.Equal(t, csvRow.Item.Description, item.Description) + assert.Equal(t, csvRow.Item.SerialNumber, item.SerialNumber) + assert.Equal(t, csvRow.Item.Manufacturer, item.Manufacturer) + assert.Equal(t, csvRow.Item.Notes, item.Notes) // Purchase Fields - assert.Equal(t, csvRow.parsedPurchasedAt(), item.PurchaseTime) - assert.Equal(t, csvRow.PurchaseFrom, item.PurchaseFrom) - assert.Equal(t, csvRow.parsedPurchasedPrice(), item.PurchasePrice) + assert.Equal(t, csvRow.Item.PurchaseTime, item.PurchaseTime) + assert.Equal(t, csvRow.Item.PurchaseFrom, item.PurchaseFrom) + assert.Equal(t, csvRow.Item.PurchasePrice, item.PurchasePrice) // Sold Fields - assert.Equal(t, csvRow.parsedSoldAt(), item.SoldTime) - assert.Equal(t, csvRow.SoldTo, item.SoldTo) - assert.Equal(t, csvRow.parsedSoldPrice(), item.SoldPrice) + assert.Equal(t, csvRow.Item.SoldTime, item.SoldTime) + assert.Equal(t, csvRow.Item.SoldTo, item.SoldTo) + assert.Equal(t, csvRow.Item.SoldPrice, item.SoldPrice) } } } diff --git a/backend/internal/types/item_types.go b/backend/internal/types/item_types.go index 36bc8ff..ae682ae 100644 --- a/backend/internal/types/item_types.go +++ b/backend/internal/types/item_types.go @@ -7,6 +7,7 @@ import ( ) type ItemCreate struct { + ImportRef string `json:"-"` Name string `json:"name"` Description string `json:"description"` @@ -53,6 +54,7 @@ type ItemUpdate struct { } type ItemSummary struct { + ImportRef string `json:"-"` ID uuid.UUID `json:"id"` Name string `json:"name"` Description string `json:"description"`