2022-09-03 09:17:48 +00:00
|
|
|
package services
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2022-09-24 19:33:38 +00:00
|
|
|
"errors"
|
2022-09-03 09:17:48 +00:00
|
|
|
|
|
|
|
"github.com/google/uuid"
|
2022-10-30 04:05:38 +00:00
|
|
|
"github.com/hay-kot/homebox/backend/internal/data/repo"
|
2022-09-06 19:15:07 +00:00
|
|
|
"github.com/rs/zerolog/log"
|
2022-09-03 09:17:48 +00:00
|
|
|
)
|
|
|
|
|
2022-09-24 19:33:38 +00:00
|
|
|
var (
|
|
|
|
ErrNotFound = errors.New("not found")
|
|
|
|
ErrFileNotFound = errors.New("file not found")
|
|
|
|
)
|
|
|
|
|
2022-09-03 09:17:48 +00:00
|
|
|
type ItemService struct {
|
|
|
|
repo *repo.AllRepos
|
2022-09-12 22:47:27 +00:00
|
|
|
|
|
|
|
filepath string
|
2022-09-24 19:33:38 +00:00
|
|
|
// at is a map of tokens to attachment IDs. This is used to store the attachment ID
|
|
|
|
// for issued URLs
|
|
|
|
at attachmentTokens
|
2022-09-03 09:17:48 +00:00
|
|
|
}
|
|
|
|
|
2022-10-16 01:46:57 +00:00
|
|
|
func (svc *ItemService) CsvImport(ctx context.Context, gid uuid.UUID, data [][]string) (int, error) {
|
2022-09-06 19:15:07 +00:00
|
|
|
loaded := []csvRow{}
|
|
|
|
|
|
|
|
// Skip first row
|
|
|
|
for _, row := range data[1:] {
|
|
|
|
// Skip empty rows
|
|
|
|
if len(row) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
2022-09-13 04:54:30 +00:00
|
|
|
|
|
|
|
if len(row) != NumOfCols {
|
2022-10-16 01:46:57 +00:00
|
|
|
return 0, ErrInvalidCsv
|
2022-09-06 19:15:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
r := newCsvRow(row)
|
|
|
|
loaded = append(loaded, r)
|
|
|
|
}
|
|
|
|
|
2022-10-16 01:46:57 +00:00
|
|
|
// validate rows
|
|
|
|
var errMap = map[int][]error{}
|
|
|
|
var hasErr bool
|
|
|
|
for i, r := range loaded {
|
|
|
|
|
|
|
|
errs := r.validate()
|
|
|
|
|
|
|
|
if len(errs) > 0 {
|
|
|
|
hasErr = true
|
|
|
|
lineNum := i + 2
|
|
|
|
|
|
|
|
errMap[lineNum] = errs
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if hasErr {
|
|
|
|
for lineNum, errs := range errMap {
|
|
|
|
for _, err := range errs {
|
|
|
|
log.Error().Err(err).Int("line", lineNum).Msg("csv import error")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-06 19:15:07 +00:00
|
|
|
// Bootstrap the locations and labels so we can reuse the created IDs for the items
|
|
|
|
locations := map[string]uuid.UUID{}
|
|
|
|
existingLocation, err := svc.repo.Locations.GetAll(ctx, gid)
|
|
|
|
if err != nil {
|
2022-10-16 01:46:57 +00:00
|
|
|
return 0, err
|
2022-09-06 19:15:07 +00:00
|
|
|
}
|
|
|
|
for _, loc := range existingLocation {
|
|
|
|
locations[loc.Name] = loc.ID
|
|
|
|
}
|
|
|
|
|
|
|
|
labels := map[string]uuid.UUID{}
|
|
|
|
existingLabels, err := svc.repo.Labels.GetAll(ctx, gid)
|
|
|
|
if err != nil {
|
2022-10-16 01:46:57 +00:00
|
|
|
return 0, err
|
2022-09-06 19:15:07 +00:00
|
|
|
}
|
|
|
|
for _, label := range existingLabels {
|
|
|
|
labels[label.Name] = label.ID
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, row := range loaded {
|
|
|
|
|
|
|
|
// Locations
|
2022-10-16 01:46:57 +00:00
|
|
|
if _, exists := locations[row.Location]; !exists {
|
|
|
|
result, err := svc.repo.Locations.Create(ctx, gid, repo.LocationCreate{
|
|
|
|
Name: row.Location,
|
|
|
|
Description: "",
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
locations[row.Location] = result.ID
|
2022-09-06 19:15:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Labels
|
|
|
|
|
|
|
|
for _, label := range row.getLabels() {
|
2022-10-16 01:46:57 +00:00
|
|
|
if _, exists := labels[label]; exists {
|
2022-09-06 19:15:07 +00:00
|
|
|
continue
|
|
|
|
}
|
2022-09-27 23:52:13 +00:00
|
|
|
result, err := svc.repo.Labels.Create(ctx, gid, repo.LabelCreate{
|
2022-09-06 19:15:07 +00:00
|
|
|
Name: label,
|
|
|
|
Description: "",
|
|
|
|
})
|
|
|
|
if err != nil {
|
2022-10-16 01:46:57 +00:00
|
|
|
return 0, err
|
2022-09-06 19:15:07 +00:00
|
|
|
}
|
|
|
|
labels[label] = result.ID
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create the items
|
2022-10-16 01:46:57 +00:00
|
|
|
var count int
|
2022-09-06 19:15:07 +00:00
|
|
|
for _, row := range loaded {
|
2022-10-16 01:46:57 +00:00
|
|
|
// Check Import Ref
|
|
|
|
if row.Item.ImportRef != "" {
|
|
|
|
exists, err := svc.repo.Items.CheckRef(ctx, gid, row.Item.ImportRef)
|
|
|
|
if exists {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
log.Err(err).Msg("error checking import ref")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-06 19:15:07 +00:00
|
|
|
locationID := locations[row.Location]
|
|
|
|
labelIDs := []uuid.UUID{}
|
|
|
|
for _, label := range row.getLabels() {
|
|
|
|
labelIDs = append(labelIDs, labels[label])
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Info().
|
2022-09-13 04:54:30 +00:00
|
|
|
Str("name", row.Item.Name).
|
2022-09-06 19:15:07 +00:00
|
|
|
Str("location", row.Location).
|
2022-09-13 04:54:30 +00:00
|
|
|
Msgf("Creating Item: %s", row.Item.Name)
|
2022-09-06 19:15:07 +00:00
|
|
|
|
2022-09-27 23:52:13 +00:00
|
|
|
result, err := svc.repo.Items.Create(ctx, gid, repo.ItemCreate{
|
2022-09-13 04:54:30 +00:00
|
|
|
ImportRef: row.Item.ImportRef,
|
|
|
|
Name: row.Item.Name,
|
|
|
|
Description: row.Item.Description,
|
2022-09-06 19:15:07 +00:00
|
|
|
LabelIDs: labelIDs,
|
|
|
|
LocationID: locationID,
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
2022-10-16 01:46:57 +00:00
|
|
|
return count, err
|
2022-09-06 19:15:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Update the item with the rest of the data
|
2022-09-27 23:52:13 +00:00
|
|
|
_, err = svc.repo.Items.UpdateByGroup(ctx, gid, repo.ItemUpdate{
|
2022-09-13 04:54:30 +00:00
|
|
|
// Edges
|
|
|
|
LocationID: locationID,
|
|
|
|
LabelIDs: labelIDs,
|
|
|
|
|
|
|
|
// General Fields
|
|
|
|
ID: result.ID,
|
|
|
|
Name: result.Name,
|
|
|
|
Description: result.Description,
|
|
|
|
Insured: row.Item.Insured,
|
|
|
|
Notes: row.Item.Notes,
|
2022-09-27 23:52:13 +00:00
|
|
|
Quantity: row.Item.Quantity,
|
2022-09-13 04:54:30 +00:00
|
|
|
|
|
|
|
// 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,
|
2022-09-06 19:15:07 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
2022-10-16 01:46:57 +00:00
|
|
|
return count, err
|
2022-09-06 19:15:07 +00:00
|
|
|
}
|
2022-10-16 01:46:57 +00:00
|
|
|
|
|
|
|
count++
|
2022-09-06 19:15:07 +00:00
|
|
|
}
|
2022-10-16 01:46:57 +00:00
|
|
|
return count, nil
|
2022-09-06 19:15:07 +00:00
|
|
|
}
|