fix: csv-importer (#10)

* update item fields to support import_ref

* add additional rows to CSV importer

* add CSV import documentation

* update readme

* update readme

* fix failed test
This commit is contained in:
Hayden 2022-09-12 20:54:30 -08:00 committed by GitHub
parent 90813abf76
commit ca36e3b080
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 447 additions and 135 deletions

View file

@ -30,21 +30,22 @@
- [x] Create - [x] Create
- [x] Update - [x] Update
- [x] Delete - [x] Delete
- [ ] Items CRUD - [x] Items CRUD
- [x] Create - [x] Create
- [ ] Update - [x] Update
- [x] Delete - [x] Delete
- [ ] Asset Attachments for Items - [ ] Asset Attachments for Items
- [x] Fields To Add - [x] Fields To Add
- [x] Quantity - [x] Quantity
- [x] Insured (bool) - [x] Insured (bool)
- [ ] Bulk Import via CSV - [x] Bulk Import via CSV
- [x] Initial - [x] Initial
- [ ] Add Warranty Columns - [x] Add Warranty Columns
- [x] All Fields
- [x] Documentations
- [ ] Documentation - [ ] Documentation
- [ ] Docker Compose - [ ] Docker Compose
- [ ] Config File - [ ] Config File
- [ ] Import CSV Format
- [ ] TLDR; Getting Started - [ ] TLDR; Getting Started
- [x] Release Flow - [x] Release Flow
- [x] CI/CD Docker Builds w/ Multi-arch - [x] CI/CD Docker Builds w/ Multi-arch

View file

@ -27,6 +27,8 @@ type Item struct {
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
// Description holds the value of the "description" field. // Description holds the value of the "description" field.
Description string `json:"description,omitempty"` Description string `json:"description,omitempty"`
// ImportRef holds the value of the "import_ref" field.
ImportRef string `json:"import_ref,omitempty"`
// Notes holds the value of the "notes" field. // Notes holds the value of the "notes" field.
Notes string `json:"notes,omitempty"` Notes string `json:"notes,omitempty"`
// Quantity holds the value of the "quantity" field. // Quantity holds the value of the "quantity" field.
@ -147,7 +149,7 @@ func (*Item) scanValues(columns []string) ([]interface{}, error) {
values[i] = new(sql.NullFloat64) values[i] = new(sql.NullFloat64)
case item.FieldQuantity: case item.FieldQuantity:
values[i] = new(sql.NullInt64) values[i] = new(sql.NullInt64)
case item.FieldName, item.FieldDescription, item.FieldNotes, item.FieldSerialNumber, item.FieldModelNumber, item.FieldManufacturer, item.FieldWarrantyDetails, item.FieldPurchaseFrom, item.FieldSoldTo, item.FieldSoldNotes: case item.FieldName, item.FieldDescription, item.FieldImportRef, item.FieldNotes, item.FieldSerialNumber, item.FieldModelNumber, item.FieldManufacturer, item.FieldWarrantyDetails, item.FieldPurchaseFrom, item.FieldSoldTo, item.FieldSoldNotes:
values[i] = new(sql.NullString) values[i] = new(sql.NullString)
case item.FieldCreatedAt, item.FieldUpdatedAt, item.FieldWarrantyExpires, item.FieldPurchaseTime, item.FieldSoldTime: case item.FieldCreatedAt, item.FieldUpdatedAt, item.FieldWarrantyExpires, item.FieldPurchaseTime, item.FieldSoldTime:
values[i] = new(sql.NullTime) values[i] = new(sql.NullTime)
@ -202,6 +204,12 @@ func (i *Item) assignValues(columns []string, values []interface{}) error {
} else if value.Valid { } else if value.Valid {
i.Description = value.String i.Description = value.String
} }
case item.FieldImportRef:
if value, ok := values[j].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field import_ref", values[j])
} else if value.Valid {
i.ImportRef = value.String
}
case item.FieldNotes: case item.FieldNotes:
if value, ok := values[j].(*sql.NullString); !ok { if value, ok := values[j].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field notes", values[j]) return fmt.Errorf("unexpected type %T for field notes", values[j])
@ -377,6 +385,9 @@ func (i *Item) String() string {
builder.WriteString("description=") builder.WriteString("description=")
builder.WriteString(i.Description) builder.WriteString(i.Description)
builder.WriteString(", ") builder.WriteString(", ")
builder.WriteString("import_ref=")
builder.WriteString(i.ImportRef)
builder.WriteString(", ")
builder.WriteString("notes=") builder.WriteString("notes=")
builder.WriteString(i.Notes) builder.WriteString(i.Notes)
builder.WriteString(", ") builder.WriteString(", ")

View file

@ -21,6 +21,8 @@ const (
FieldName = "name" FieldName = "name"
// FieldDescription holds the string denoting the description field in the database. // FieldDescription holds the string denoting the description field in the database.
FieldDescription = "description" FieldDescription = "description"
// FieldImportRef holds the string denoting the import_ref field in the database.
FieldImportRef = "import_ref"
// FieldNotes holds the string denoting the notes field in the database. // FieldNotes holds the string denoting the notes field in the database.
FieldNotes = "notes" FieldNotes = "notes"
// FieldQuantity holds the string denoting the quantity field in the database. // FieldQuantity holds the string denoting the quantity field in the database.
@ -107,6 +109,7 @@ var Columns = []string{
FieldUpdatedAt, FieldUpdatedAt,
FieldName, FieldName,
FieldDescription, FieldDescription,
FieldImportRef,
FieldNotes, FieldNotes,
FieldQuantity, FieldQuantity,
FieldInsured, FieldInsured,
@ -164,6 +167,8 @@ var (
NameValidator func(string) error NameValidator func(string) error
// DescriptionValidator is a validator for the "description" field. It is called by the builders before save. // DescriptionValidator is a validator for the "description" field. It is called by the builders before save.
DescriptionValidator func(string) error DescriptionValidator func(string) error
// ImportRefValidator is a validator for the "import_ref" field. It is called by the builders before save.
ImportRefValidator func(string) error
// NotesValidator is a validator for the "notes" field. It is called by the builders before save. // NotesValidator is a validator for the "notes" field. It is called by the builders before save.
NotesValidator func(string) error NotesValidator func(string) error
// DefaultQuantity holds the default value on creation for the "quantity" field. // DefaultQuantity holds the default value on creation for the "quantity" field.

View file

@ -110,6 +110,13 @@ func Description(v string) predicate.Item {
}) })
} }
// ImportRef applies equality check predicate on the "import_ref" field. It's identical to ImportRefEQ.
func ImportRef(v string) predicate.Item {
return predicate.Item(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldImportRef), v))
})
}
// Notes applies equality check predicate on the "notes" field. It's identical to NotesEQ. // Notes applies equality check predicate on the "notes" field. It's identical to NotesEQ.
func Notes(v string) predicate.Item { func Notes(v string) predicate.Item {
return predicate.Item(func(s *sql.Selector) { return predicate.Item(func(s *sql.Selector) {
@ -562,6 +569,119 @@ func DescriptionContainsFold(v string) predicate.Item {
}) })
} }
// ImportRefEQ applies the EQ predicate on the "import_ref" field.
func ImportRefEQ(v string) predicate.Item {
return predicate.Item(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldImportRef), v))
})
}
// ImportRefNEQ applies the NEQ predicate on the "import_ref" field.
func ImportRefNEQ(v string) predicate.Item {
return predicate.Item(func(s *sql.Selector) {
s.Where(sql.NEQ(s.C(FieldImportRef), v))
})
}
// ImportRefIn applies the In predicate on the "import_ref" field.
func ImportRefIn(vs ...string) predicate.Item {
v := make([]interface{}, len(vs))
for i := range v {
v[i] = vs[i]
}
return predicate.Item(func(s *sql.Selector) {
s.Where(sql.In(s.C(FieldImportRef), v...))
})
}
// ImportRefNotIn applies the NotIn predicate on the "import_ref" field.
func ImportRefNotIn(vs ...string) predicate.Item {
v := make([]interface{}, len(vs))
for i := range v {
v[i] = vs[i]
}
return predicate.Item(func(s *sql.Selector) {
s.Where(sql.NotIn(s.C(FieldImportRef), v...))
})
}
// ImportRefGT applies the GT predicate on the "import_ref" field.
func ImportRefGT(v string) predicate.Item {
return predicate.Item(func(s *sql.Selector) {
s.Where(sql.GT(s.C(FieldImportRef), v))
})
}
// ImportRefGTE applies the GTE predicate on the "import_ref" field.
func ImportRefGTE(v string) predicate.Item {
return predicate.Item(func(s *sql.Selector) {
s.Where(sql.GTE(s.C(FieldImportRef), v))
})
}
// ImportRefLT applies the LT predicate on the "import_ref" field.
func ImportRefLT(v string) predicate.Item {
return predicate.Item(func(s *sql.Selector) {
s.Where(sql.LT(s.C(FieldImportRef), v))
})
}
// ImportRefLTE applies the LTE predicate on the "import_ref" field.
func ImportRefLTE(v string) predicate.Item {
return predicate.Item(func(s *sql.Selector) {
s.Where(sql.LTE(s.C(FieldImportRef), v))
})
}
// ImportRefContains applies the Contains predicate on the "import_ref" field.
func ImportRefContains(v string) predicate.Item {
return predicate.Item(func(s *sql.Selector) {
s.Where(sql.Contains(s.C(FieldImportRef), v))
})
}
// ImportRefHasPrefix applies the HasPrefix predicate on the "import_ref" field.
func ImportRefHasPrefix(v string) predicate.Item {
return predicate.Item(func(s *sql.Selector) {
s.Where(sql.HasPrefix(s.C(FieldImportRef), v))
})
}
// ImportRefHasSuffix applies the HasSuffix predicate on the "import_ref" field.
func ImportRefHasSuffix(v string) predicate.Item {
return predicate.Item(func(s *sql.Selector) {
s.Where(sql.HasSuffix(s.C(FieldImportRef), v))
})
}
// ImportRefIsNil applies the IsNil predicate on the "import_ref" field.
func ImportRefIsNil() predicate.Item {
return predicate.Item(func(s *sql.Selector) {
s.Where(sql.IsNull(s.C(FieldImportRef)))
})
}
// ImportRefNotNil applies the NotNil predicate on the "import_ref" field.
func ImportRefNotNil() predicate.Item {
return predicate.Item(func(s *sql.Selector) {
s.Where(sql.NotNull(s.C(FieldImportRef)))
})
}
// ImportRefEqualFold applies the EqualFold predicate on the "import_ref" field.
func ImportRefEqualFold(v string) predicate.Item {
return predicate.Item(func(s *sql.Selector) {
s.Where(sql.EqualFold(s.C(FieldImportRef), v))
})
}
// ImportRefContainsFold applies the ContainsFold predicate on the "import_ref" field.
func ImportRefContainsFold(v string) predicate.Item {
return predicate.Item(func(s *sql.Selector) {
s.Where(sql.ContainsFold(s.C(FieldImportRef), v))
})
}
// NotesEQ applies the EQ predicate on the "notes" field. // NotesEQ applies the EQ predicate on the "notes" field.
func NotesEQ(v string) predicate.Item { func NotesEQ(v string) predicate.Item {
return predicate.Item(func(s *sql.Selector) { return predicate.Item(func(s *sql.Selector) {

View file

@ -74,6 +74,20 @@ func (ic *ItemCreate) SetNillableDescription(s *string) *ItemCreate {
return ic return ic
} }
// SetImportRef sets the "import_ref" field.
func (ic *ItemCreate) SetImportRef(s string) *ItemCreate {
ic.mutation.SetImportRef(s)
return ic
}
// SetNillableImportRef sets the "import_ref" field if the given value is not nil.
func (ic *ItemCreate) SetNillableImportRef(s *string) *ItemCreate {
if s != nil {
ic.SetImportRef(*s)
}
return ic
}
// SetNotes sets the "notes" field. // SetNotes sets the "notes" field.
func (ic *ItemCreate) SetNotes(s string) *ItemCreate { func (ic *ItemCreate) SetNotes(s string) *ItemCreate {
ic.mutation.SetNotes(s) ic.mutation.SetNotes(s)
@ -519,6 +533,11 @@ func (ic *ItemCreate) check() error {
return &ValidationError{Name: "description", err: fmt.Errorf(`ent: validator failed for field "Item.description": %w`, err)} return &ValidationError{Name: "description", err: fmt.Errorf(`ent: validator failed for field "Item.description": %w`, err)}
} }
} }
if v, ok := ic.mutation.ImportRef(); ok {
if err := item.ImportRefValidator(v); err != nil {
return &ValidationError{Name: "import_ref", err: fmt.Errorf(`ent: validator failed for field "Item.import_ref": %w`, err)}
}
}
if v, ok := ic.mutation.Notes(); ok { if v, ok := ic.mutation.Notes(); ok {
if err := item.NotesValidator(v); err != nil { if err := item.NotesValidator(v); err != nil {
return &ValidationError{Name: "notes", err: fmt.Errorf(`ent: validator failed for field "Item.notes": %w`, err)} return &ValidationError{Name: "notes", err: fmt.Errorf(`ent: validator failed for field "Item.notes": %w`, err)}
@ -635,6 +654,14 @@ func (ic *ItemCreate) createSpec() (*Item, *sqlgraph.CreateSpec) {
}) })
_node.Description = value _node.Description = value
} }
if value, ok := ic.mutation.ImportRef(); ok {
_spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{
Type: field.TypeString,
Value: value,
Column: item.FieldImportRef,
})
_node.ImportRef = value
}
if value, ok := ic.mutation.Notes(); ok { if value, ok := ic.mutation.Notes(); ok {
_spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{ _spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{
Type: field.TypeString, Type: field.TypeString,

View file

@ -694,6 +694,12 @@ func (iu *ItemUpdate) sqlSave(ctx context.Context) (n int, err error) {
Column: item.FieldDescription, Column: item.FieldDescription,
}) })
} }
if iu.mutation.ImportRefCleared() {
_spec.Fields.Clear = append(_spec.Fields.Clear, &sqlgraph.FieldSpec{
Type: field.TypeString,
Column: item.FieldImportRef,
})
}
if value, ok := iu.mutation.Notes(); ok { if value, ok := iu.mutation.Notes(); ok {
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{ _spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
Type: field.TypeString, Type: field.TypeString,
@ -1834,6 +1840,12 @@ func (iuo *ItemUpdateOne) sqlSave(ctx context.Context) (_node *Item, err error)
Column: item.FieldDescription, Column: item.FieldDescription,
}) })
} }
if iuo.mutation.ImportRefCleared() {
_spec.Fields.Clear = append(_spec.Fields.Clear, &sqlgraph.FieldSpec{
Type: field.TypeString,
Column: item.FieldImportRef,
})
}
if value, ok := iuo.mutation.Notes(); ok { if value, ok := iuo.mutation.Notes(); ok {
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{ _spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
Type: field.TypeString, Type: field.TypeString,

View file

@ -142,6 +142,7 @@ var (
{Name: "updated_at", Type: field.TypeTime}, {Name: "updated_at", Type: field.TypeTime},
{Name: "name", Type: field.TypeString, Size: 255}, {Name: "name", Type: field.TypeString, Size: 255},
{Name: "description", Type: field.TypeString, Nullable: true, Size: 1000}, {Name: "description", Type: field.TypeString, Nullable: true, Size: 1000},
{Name: "import_ref", Type: field.TypeString, Nullable: true, Size: 100},
{Name: "notes", Type: field.TypeString, Nullable: true, Size: 1000}, {Name: "notes", Type: field.TypeString, Nullable: true, Size: 1000},
{Name: "quantity", Type: field.TypeInt, Default: 1}, {Name: "quantity", Type: field.TypeInt, Default: 1},
{Name: "insured", Type: field.TypeBool, Default: false}, {Name: "insured", Type: field.TypeBool, Default: false},
@ -169,15 +170,15 @@ var (
ForeignKeys: []*schema.ForeignKey{ ForeignKeys: []*schema.ForeignKey{
{ {
Symbol: "items_groups_items", Symbol: "items_groups_items",
Columns: []*schema.Column{ItemsColumns[21]}, Columns: []*schema.Column{ItemsColumns[22]},
RefColumns: []*schema.Column{GroupsColumns[0]}, RefColumns: []*schema.Column{GroupsColumns[0]},
OnDelete: schema.Cascade, OnDelete: schema.Cascade,
}, },
{ {
Symbol: "items_locations_items", Symbol: "items_locations_items",
Columns: []*schema.Column{ItemsColumns[22]}, Columns: []*schema.Column{ItemsColumns[23]},
RefColumns: []*schema.Column{LocationsColumns[0]}, RefColumns: []*schema.Column{LocationsColumns[0]},
OnDelete: schema.SetNull, OnDelete: schema.Cascade,
}, },
}, },
Indexes: []*schema.Index{ Indexes: []*schema.Index{
@ -189,17 +190,17 @@ var (
{ {
Name: "item_manufacturer", Name: "item_manufacturer",
Unique: false, Unique: false,
Columns: []*schema.Column{ItemsColumns[10]}, Columns: []*schema.Column{ItemsColumns[11]},
}, },
{ {
Name: "item_model_number", Name: "item_model_number",
Unique: false, Unique: false,
Columns: []*schema.Column{ItemsColumns[9]}, Columns: []*schema.Column{ItemsColumns[10]},
}, },
{ {
Name: "item_serial_number", Name: "item_serial_number",
Unique: false, Unique: false,
Columns: []*schema.Column{ItemsColumns[8]}, Columns: []*schema.Column{ItemsColumns[9]},
}, },
}, },
} }

View file

@ -3413,6 +3413,7 @@ type ItemMutation struct {
updated_at *time.Time updated_at *time.Time
name *string name *string
description *string description *string
import_ref *string
notes *string notes *string
quantity *int quantity *int
addquantity *int addquantity *int
@ -3712,6 +3713,55 @@ func (m *ItemMutation) ResetDescription() {
delete(m.clearedFields, item.FieldDescription) delete(m.clearedFields, item.FieldDescription)
} }
// SetImportRef sets the "import_ref" field.
func (m *ItemMutation) SetImportRef(s string) {
m.import_ref = &s
}
// ImportRef returns the value of the "import_ref" field in the mutation.
func (m *ItemMutation) ImportRef() (r string, exists bool) {
v := m.import_ref
if v == nil {
return
}
return *v, true
}
// OldImportRef returns the old "import_ref" field's value of the Item entity.
// If the Item object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
func (m *ItemMutation) OldImportRef(ctx context.Context) (v string, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldImportRef is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldImportRef requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldImportRef: %w", err)
}
return oldValue.ImportRef, nil
}
// ClearImportRef clears the value of the "import_ref" field.
func (m *ItemMutation) ClearImportRef() {
m.import_ref = nil
m.clearedFields[item.FieldImportRef] = struct{}{}
}
// ImportRefCleared returns if the "import_ref" field was cleared in this mutation.
func (m *ItemMutation) ImportRefCleared() bool {
_, ok := m.clearedFields[item.FieldImportRef]
return ok
}
// ResetImportRef resets all changes to the "import_ref" field.
func (m *ItemMutation) ResetImportRef() {
m.import_ref = nil
delete(m.clearedFields, item.FieldImportRef)
}
// SetNotes sets the "notes" field. // SetNotes sets the "notes" field.
func (m *ItemMutation) SetNotes(s string) { func (m *ItemMutation) SetNotes(s string) {
m.notes = &s m.notes = &s
@ -4750,7 +4800,7 @@ func (m *ItemMutation) Type() string {
// order to get all numeric fields that were incremented/decremented, call // order to get all numeric fields that were incremented/decremented, call
// AddedFields(). // AddedFields().
func (m *ItemMutation) Fields() []string { func (m *ItemMutation) Fields() []string {
fields := make([]string, 0, 20) fields := make([]string, 0, 21)
if m.created_at != nil { if m.created_at != nil {
fields = append(fields, item.FieldCreatedAt) fields = append(fields, item.FieldCreatedAt)
} }
@ -4763,6 +4813,9 @@ func (m *ItemMutation) Fields() []string {
if m.description != nil { if m.description != nil {
fields = append(fields, item.FieldDescription) fields = append(fields, item.FieldDescription)
} }
if m.import_ref != nil {
fields = append(fields, item.FieldImportRef)
}
if m.notes != nil { if m.notes != nil {
fields = append(fields, item.FieldNotes) fields = append(fields, item.FieldNotes)
} }
@ -4827,6 +4880,8 @@ func (m *ItemMutation) Field(name string) (ent.Value, bool) {
return m.Name() return m.Name()
case item.FieldDescription: case item.FieldDescription:
return m.Description() return m.Description()
case item.FieldImportRef:
return m.ImportRef()
case item.FieldNotes: case item.FieldNotes:
return m.Notes() return m.Notes()
case item.FieldQuantity: case item.FieldQuantity:
@ -4876,6 +4931,8 @@ func (m *ItemMutation) OldField(ctx context.Context, name string) (ent.Value, er
return m.OldName(ctx) return m.OldName(ctx)
case item.FieldDescription: case item.FieldDescription:
return m.OldDescription(ctx) return m.OldDescription(ctx)
case item.FieldImportRef:
return m.OldImportRef(ctx)
case item.FieldNotes: case item.FieldNotes:
return m.OldNotes(ctx) return m.OldNotes(ctx)
case item.FieldQuantity: case item.FieldQuantity:
@ -4945,6 +5002,13 @@ func (m *ItemMutation) SetField(name string, value ent.Value) error {
} }
m.SetDescription(v) m.SetDescription(v)
return nil return nil
case item.FieldImportRef:
v, ok := value.(string)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetImportRef(v)
return nil
case item.FieldNotes: case item.FieldNotes:
v, ok := value.(string) v, ok := value.(string)
if !ok { if !ok {
@ -5129,6 +5193,9 @@ func (m *ItemMutation) ClearedFields() []string {
if m.FieldCleared(item.FieldDescription) { if m.FieldCleared(item.FieldDescription) {
fields = append(fields, item.FieldDescription) fields = append(fields, item.FieldDescription)
} }
if m.FieldCleared(item.FieldImportRef) {
fields = append(fields, item.FieldImportRef)
}
if m.FieldCleared(item.FieldNotes) { if m.FieldCleared(item.FieldNotes) {
fields = append(fields, item.FieldNotes) fields = append(fields, item.FieldNotes)
} }
@ -5179,6 +5246,9 @@ func (m *ItemMutation) ClearField(name string) error {
case item.FieldDescription: case item.FieldDescription:
m.ClearDescription() m.ClearDescription()
return nil return nil
case item.FieldImportRef:
m.ClearImportRef()
return nil
case item.FieldNotes: case item.FieldNotes:
m.ClearNotes() m.ClearNotes()
return nil return nil
@ -5232,6 +5302,9 @@ func (m *ItemMutation) ResetField(name string) error {
case item.FieldDescription: case item.FieldDescription:
m.ResetDescription() m.ResetDescription()
return nil return nil
case item.FieldImportRef:
m.ResetImportRef()
return nil
case item.FieldNotes: case item.FieldNotes:
m.ResetNotes() m.ResetNotes()
return nil return nil

View file

@ -227,48 +227,52 @@ func init() {
itemDescDescription := itemMixinFields1[1].Descriptor() itemDescDescription := itemMixinFields1[1].Descriptor()
// item.DescriptionValidator is a validator for the "description" field. It is called by the builders before save. // item.DescriptionValidator is a validator for the "description" field. It is called by the builders before save.
item.DescriptionValidator = itemDescDescription.Validators[0].(func(string) error) item.DescriptionValidator = itemDescDescription.Validators[0].(func(string) error)
// itemDescImportRef is the schema descriptor for import_ref field.
itemDescImportRef := itemFields[0].Descriptor()
// item.ImportRefValidator is a validator for the "import_ref" field. It is called by the builders before save.
item.ImportRefValidator = itemDescImportRef.Validators[0].(func(string) error)
// itemDescNotes is the schema descriptor for notes field. // itemDescNotes is the schema descriptor for notes field.
itemDescNotes := itemFields[0].Descriptor() itemDescNotes := itemFields[1].Descriptor()
// item.NotesValidator is a validator for the "notes" field. It is called by the builders before save. // item.NotesValidator is a validator for the "notes" field. It is called by the builders before save.
item.NotesValidator = itemDescNotes.Validators[0].(func(string) error) item.NotesValidator = itemDescNotes.Validators[0].(func(string) error)
// itemDescQuantity is the schema descriptor for quantity field. // itemDescQuantity is the schema descriptor for quantity field.
itemDescQuantity := itemFields[1].Descriptor() itemDescQuantity := itemFields[2].Descriptor()
// item.DefaultQuantity holds the default value on creation for the quantity field. // item.DefaultQuantity holds the default value on creation for the quantity field.
item.DefaultQuantity = itemDescQuantity.Default.(int) item.DefaultQuantity = itemDescQuantity.Default.(int)
// itemDescInsured is the schema descriptor for insured field. // itemDescInsured is the schema descriptor for insured field.
itemDescInsured := itemFields[2].Descriptor() itemDescInsured := itemFields[3].Descriptor()
// item.DefaultInsured holds the default value on creation for the insured field. // item.DefaultInsured holds the default value on creation for the insured field.
item.DefaultInsured = itemDescInsured.Default.(bool) item.DefaultInsured = itemDescInsured.Default.(bool)
// itemDescSerialNumber is the schema descriptor for serial_number field. // itemDescSerialNumber is the schema descriptor for serial_number field.
itemDescSerialNumber := itemFields[3].Descriptor() itemDescSerialNumber := itemFields[4].Descriptor()
// item.SerialNumberValidator is a validator for the "serial_number" field. It is called by the builders before save. // item.SerialNumberValidator is a validator for the "serial_number" field. It is called by the builders before save.
item.SerialNumberValidator = itemDescSerialNumber.Validators[0].(func(string) error) item.SerialNumberValidator = itemDescSerialNumber.Validators[0].(func(string) error)
// itemDescModelNumber is the schema descriptor for model_number field. // itemDescModelNumber is the schema descriptor for model_number field.
itemDescModelNumber := itemFields[4].Descriptor() itemDescModelNumber := itemFields[5].Descriptor()
// item.ModelNumberValidator is a validator for the "model_number" field. It is called by the builders before save. // item.ModelNumberValidator is a validator for the "model_number" field. It is called by the builders before save.
item.ModelNumberValidator = itemDescModelNumber.Validators[0].(func(string) error) item.ModelNumberValidator = itemDescModelNumber.Validators[0].(func(string) error)
// itemDescManufacturer is the schema descriptor for manufacturer field. // itemDescManufacturer is the schema descriptor for manufacturer field.
itemDescManufacturer := itemFields[5].Descriptor() itemDescManufacturer := itemFields[6].Descriptor()
// item.ManufacturerValidator is a validator for the "manufacturer" field. It is called by the builders before save. // item.ManufacturerValidator is a validator for the "manufacturer" field. It is called by the builders before save.
item.ManufacturerValidator = itemDescManufacturer.Validators[0].(func(string) error) item.ManufacturerValidator = itemDescManufacturer.Validators[0].(func(string) error)
// itemDescLifetimeWarranty is the schema descriptor for lifetime_warranty field. // itemDescLifetimeWarranty is the schema descriptor for lifetime_warranty field.
itemDescLifetimeWarranty := itemFields[6].Descriptor() itemDescLifetimeWarranty := itemFields[7].Descriptor()
// item.DefaultLifetimeWarranty holds the default value on creation for the lifetime_warranty field. // item.DefaultLifetimeWarranty holds the default value on creation for the lifetime_warranty field.
item.DefaultLifetimeWarranty = itemDescLifetimeWarranty.Default.(bool) item.DefaultLifetimeWarranty = itemDescLifetimeWarranty.Default.(bool)
// itemDescWarrantyDetails is the schema descriptor for warranty_details field. // itemDescWarrantyDetails is the schema descriptor for warranty_details field.
itemDescWarrantyDetails := itemFields[8].Descriptor() itemDescWarrantyDetails := itemFields[9].Descriptor()
// item.WarrantyDetailsValidator is a validator for the "warranty_details" field. It is called by the builders before save. // item.WarrantyDetailsValidator is a validator for the "warranty_details" field. It is called by the builders before save.
item.WarrantyDetailsValidator = itemDescWarrantyDetails.Validators[0].(func(string) error) item.WarrantyDetailsValidator = itemDescWarrantyDetails.Validators[0].(func(string) error)
// itemDescPurchasePrice is the schema descriptor for purchase_price field. // itemDescPurchasePrice is the schema descriptor for purchase_price field.
itemDescPurchasePrice := itemFields[11].Descriptor() itemDescPurchasePrice := itemFields[12].Descriptor()
// item.DefaultPurchasePrice holds the default value on creation for the purchase_price field. // item.DefaultPurchasePrice holds the default value on creation for the purchase_price field.
item.DefaultPurchasePrice = itemDescPurchasePrice.Default.(float64) item.DefaultPurchasePrice = itemDescPurchasePrice.Default.(float64)
// itemDescSoldPrice is the schema descriptor for sold_price field. // itemDescSoldPrice is the schema descriptor for sold_price field.
itemDescSoldPrice := itemFields[14].Descriptor() itemDescSoldPrice := itemFields[15].Descriptor()
// item.DefaultSoldPrice holds the default value on creation for the sold_price field. // item.DefaultSoldPrice holds the default value on creation for the sold_price field.
item.DefaultSoldPrice = itemDescSoldPrice.Default.(float64) item.DefaultSoldPrice = itemDescSoldPrice.Default.(float64)
// itemDescSoldNotes is the schema descriptor for sold_notes field. // itemDescSoldNotes is the schema descriptor for sold_notes field.
itemDescSoldNotes := itemFields[15].Descriptor() itemDescSoldNotes := itemFields[16].Descriptor()
// item.SoldNotesValidator is a validator for the "sold_notes" field. It is called by the builders before save. // item.SoldNotesValidator is a validator for the "sold_notes" field. It is called by the builders before save.
item.SoldNotesValidator = itemDescSoldNotes.Validators[0].(func(string) error) item.SoldNotesValidator = itemDescSoldNotes.Validators[0].(func(string) error)
// itemDescID is the schema descriptor for id field. // itemDescID is the schema descriptor for id field.

View file

@ -34,6 +34,10 @@ func (Item) Indexes() []ent.Index {
// Fields of the Item. // Fields of the Item.
func (Item) Fields() []ent.Field { func (Item) Fields() []ent.Field {
return []ent.Field{ return []ent.Field{
field.String("import_ref").
Optional().
MaxLen(100).
Immutable(),
field.String("notes"). field.String("notes").
MaxLen(1000). MaxLen(1000).
Optional(), Optional(),
@ -102,7 +106,10 @@ func (Item) Edges() []ent.Edge {
OnDelete: entsql.Cascade, OnDelete: entsql.Cascade,
}), }),
edge.From("label", Label.Type). edge.From("label", Label.Type).
Ref("items"), Ref("items").
Annotations(entsql.Annotation{
OnDelete: entsql.Cascade,
}),
edge.To("attachments", Attachment.Type). edge.To("attachments", Attachment.Type).
Annotations(entsql.Annotation{ Annotations(entsql.Annotation{
OnDelete: entsql.Cascade, OnDelete: entsql.Cascade,

View file

@ -2,6 +2,7 @@ package schema
import ( import (
"entgo.io/ent" "entgo.io/ent"
"entgo.io/ent/dialect/entsql"
"entgo.io/ent/schema/edge" "entgo.io/ent/schema/edge"
"entgo.io/ent/schema/field" "entgo.io/ent/schema/field"
"github.com/hay-kot/content/backend/ent/schema/mixins" "github.com/hay-kot/content/backend/ent/schema/mixins"
@ -35,6 +36,9 @@ func (Label) Edges() []ent.Edge {
Ref("labels"). Ref("labels").
Required(). Required().
Unique(), Unique(),
edge.To("items", Item.Type), edge.To("items", Item.Type).
Annotations(entsql.Annotation{
OnDelete: entsql.Cascade,
}),
} }
} }

View file

@ -2,6 +2,7 @@ package schema
import ( import (
"entgo.io/ent" "entgo.io/ent"
"entgo.io/ent/dialect/entsql"
"entgo.io/ent/schema/edge" "entgo.io/ent/schema/edge"
"github.com/hay-kot/content/backend/ent/schema/mixins" "github.com/hay-kot/content/backend/ent/schema/mixins"
) )
@ -30,6 +31,9 @@ func (Location) Edges() []ent.Edge {
Ref("locations"). Ref("locations").
Unique(). Unique().
Required(), Required(),
edge.To("items", Item.Type), edge.To("items", Item.Type).
Annotations(entsql.Annotation{
OnDelete: entsql.Cascade,
}),
} }
} }

View file

@ -84,12 +84,10 @@ func TestItemsRepository_Create(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, result.ID) assert.NotEmpty(t, result.ID)
// Cleanup // Cleanup - Also deletes item
err = tRepos.Locations.Delete(context.Background(), location.ID) err = tRepos.Locations.Delete(context.Background(), location.ID)
assert.NoError(t, err) assert.NoError(t, err)
err = tRepos.Items.Delete(context.Background(), result.ID)
assert.NoError(t, err)
} }
func TestItemsRepository_Create_Location(t *testing.T) { func TestItemsRepository_Create_Location(t *testing.T) {
@ -111,11 +109,9 @@ func TestItemsRepository_Create_Location(t *testing.T) {
assert.Equal(t, result.ID, foundItem.ID) assert.Equal(t, result.ID, foundItem.ID)
assert.Equal(t, location.ID, foundItem.Edges.Location.ID) assert.Equal(t, location.ID, foundItem.Edges.Location.ID)
// Cleanup // Cleanup - Also deletes item
err = tRepos.Locations.Delete(context.Background(), location.ID) err = tRepos.Locations.Delete(context.Background(), location.ID)
assert.NoError(t, err) assert.NoError(t, err)
err = tRepos.Items.Delete(context.Background(), result.ID)
assert.NoError(t, err)
} }
func TestItemsRepository_Delete(t *testing.T) { func TestItemsRepository_Delete(t *testing.T) {

View file

@ -156,7 +156,8 @@ func (svc *ItemService) CsvImport(ctx context.Context, gid uuid.UUID, data [][]s
if len(row) == 0 { if len(row) == 0 {
continue continue
} }
if len(row) != 14 {
if len(row) != NumOfCols {
return ErrInvalidCsv return ErrInvalidCsv
} }
@ -227,15 +228,16 @@ func (svc *ItemService) CsvImport(ctx context.Context, gid uuid.UUID, data [][]s
} }
log.Info(). log.Info().
Str("name", row.Name). Str("name", row.Item.Name).
Str("location", row.Location). Str("location", row.Location).
Strs("labels", row.getLabels()). Strs("labels", row.getLabels()).
Str("locationId", locationID.String()). 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{ result, err := svc.repo.Items.Create(ctx, gid, types.ItemCreate{
Name: row.Name, ImportRef: row.Item.ImportRef,
Description: row.Description, Name: row.Item.Name,
Description: row.Item.Description,
LabelIDs: labelIDs, LabelIDs: labelIDs,
LocationID: locationID, 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 // Update the item with the rest of the data
_, err = svc.repo.Items.Update(ctx, types.ItemUpdate{ _, err = svc.repo.Items.Update(ctx, types.ItemUpdate{
ID: result.ID, // Edges
Name: result.Name,
LocationID: locationID, LocationID: locationID,
LabelIDs: labelIDs, LabelIDs: labelIDs,
// General Fields
ID: result.ID,
Name: result.Name,
Description: result.Description, Description: result.Description,
SerialNumber: row.SerialNumber, Insured: row.Item.Insured,
ModelNumber: row.ModelNumber, Notes: row.Item.Notes,
Manufacturer: row.Manufacturer,
Notes: row.Notes, // Identifies the item as imported
PurchaseFrom: row.PurchaseFrom, SerialNumber: row.Item.SerialNumber,
PurchasePrice: row.parsedPurchasedPrice(), ModelNumber: row.Item.ModelNumber,
PurchaseTime: row.parsedPurchasedAt(), Manufacturer: row.Item.Manufacturer,
SoldTo: row.SoldTo,
SoldPrice: row.parsedSoldPrice(), // Purchase
SoldTime: row.parsedSoldAt(), 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 { if err != nil {

View file

@ -5,10 +5,14 @@ import (
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/hay-kot/content/backend/internal/types"
) )
var ErrInvalidCsv = errors.New("invalid csv") var ErrInvalidCsv = errors.New("invalid csv")
const NumOfCols = 21
func parseFloat(s string) float64 { func parseFloat(s string) float64 {
if s == "" { if s == "" {
return 0 return 0
@ -26,60 +30,56 @@ func parseDate(s string) time.Time {
return p 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 { type csvRow struct {
Item types.ItemSummary
Location string Location string
Labels string LabelStr 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
} }
func newCsvRow(row []string) csvRow { func newCsvRow(row []string) csvRow {
return csvRow{ return csvRow{
Location: row[0], Location: row[1],
Labels: row[1], LabelStr: row[2],
Name: row[2], Item: types.ItemSummary{
Description: row[3], ImportRef: row[0],
SerialNumber: row[4], Quantity: parseInt(row[3]),
ModelNumber: row[5], Name: row[4],
Manufacturer: row[6], Description: row[5],
Notes: row[7], Insured: parseBool(row[6]),
PurchaseFrom: row[8], SerialNumber: row[7],
PurchasedPrice: row[9], ModelNumber: row[8],
PurchasedAt: row[10], Manufacturer: row[9],
SoldTo: row[11], Notes: row[10],
SoldPrice: row[12], PurchaseFrom: row[11],
SoldAt: row[13], 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 { func (c csvRow) getLabels() []string {
split := strings.Split(c.Labels, ";") split := strings.Split(c.LabelStr, ";")
// Trim each // Trim each
for i, s := range split { for i, s := range split {

View file

@ -8,14 +8,13 @@ import (
) )
const CSV_DATA = ` 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 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,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,,, ,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,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,,, ,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,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,,, ,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,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,,, ,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,Yale Security Touchscreen Deadbolt,"Yale Security YRD226-ZW2-619 YRD226ZW2619 Touchscreen Deadbolt, Satin Nickel",,YRD226ZW2619,Yale,,Amazon,120.39,10/14/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,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,,, ,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 { func loadcsv() [][]string {
reader := csv.NewReader(bytes.NewBuffer([]byte(CSV_DATA))) reader := csv.NewReader(bytes.NewBuffer([]byte(CSV_DATA)))
@ -30,7 +29,7 @@ func loadcsv() [][]string {
func Test_csvRow_getLabels(t *testing.T) { func Test_csvRow_getLabels(t *testing.T) {
type fields struct { type fields struct {
Labels string LabelStr string
} }
tests := []struct { tests := []struct {
name string name string
@ -40,28 +39,28 @@ func Test_csvRow_getLabels(t *testing.T) {
{ {
name: "basic test", name: "basic test",
fields: fields{ fields: fields{
Labels: "IOT;Home Assistant;Z-Wave", LabelStr: "IOT;Home Assistant;Z-Wave",
}, },
want: []string{"IOT", "Home Assistant", "Z-Wave"}, want: []string{"IOT", "Home Assistant", "Z-Wave"},
}, },
{ {
name: "no labels", name: "no labels",
fields: fields{ fields: fields{
Labels: "", LabelStr: "",
}, },
want: []string{}, want: []string{},
}, },
{ {
name: "single label", name: "single label",
fields: fields{ fields: fields{
Labels: "IOT", LabelStr: "IOT",
}, },
want: []string{"IOT"}, want: []string{"IOT"},
}, },
{ {
name: "trailing semicolon", name: "trailing semicolon",
fields: fields{ fields: fields{
Labels: "IOT;", LabelStr: "IOT;",
}, },
want: []string{"IOT"}, want: []string{"IOT"},
}, },
@ -69,7 +68,7 @@ func Test_csvRow_getLabels(t *testing.T) {
{ {
name: "whitespace", name: "whitespace",
fields: fields{ fields: fields{
Labels: " IOT; Home Assistant; Z-Wave ", LabelStr: " IOT; Home Assistant; Z-Wave ",
}, },
want: []string{"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 { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
c := csvRow{ c := csvRow{
Labels: tt.fields.Labels, LabelStr: tt.fields.LabelStr,
} }
if got := c.getLabels(); !reflect.DeepEqual(got, tt.want) { if got := c.getLabels(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("csvRow.getLabels() = %v, want %v", got, tt.want) t.Errorf("csvRow.getLabels() = %v, want %v", got, tt.want)

View file

@ -73,21 +73,21 @@ func TestItemService_CsvImport(t *testing.T) {
} }
for _, csvRow := range dataCsv { for _, csvRow := range dataCsv {
if csvRow.Name == item.Name { if csvRow.Item.Name == item.Name {
assert.Equal(t, csvRow.Description, item.Description) assert.Equal(t, csvRow.Item.Description, item.Description)
assert.Equal(t, csvRow.SerialNumber, item.SerialNumber) assert.Equal(t, csvRow.Item.SerialNumber, item.SerialNumber)
assert.Equal(t, csvRow.Manufacturer, item.Manufacturer) assert.Equal(t, csvRow.Item.Manufacturer, item.Manufacturer)
assert.Equal(t, csvRow.Notes, item.Notes) assert.Equal(t, csvRow.Item.Notes, item.Notes)
// Purchase Fields // Purchase Fields
assert.Equal(t, csvRow.parsedPurchasedAt(), item.PurchaseTime) assert.Equal(t, csvRow.Item.PurchaseTime, item.PurchaseTime)
assert.Equal(t, csvRow.PurchaseFrom, item.PurchaseFrom) assert.Equal(t, csvRow.Item.PurchaseFrom, item.PurchaseFrom)
assert.Equal(t, csvRow.parsedPurchasedPrice(), item.PurchasePrice) assert.Equal(t, csvRow.Item.PurchasePrice, item.PurchasePrice)
// Sold Fields // Sold Fields
assert.Equal(t, csvRow.parsedSoldAt(), item.SoldTime) assert.Equal(t, csvRow.Item.SoldTime, item.SoldTime)
assert.Equal(t, csvRow.SoldTo, item.SoldTo) assert.Equal(t, csvRow.Item.SoldTo, item.SoldTo)
assert.Equal(t, csvRow.parsedSoldPrice(), item.SoldPrice) assert.Equal(t, csvRow.Item.SoldPrice, item.SoldPrice)
} }
} }
} }

View file

@ -7,6 +7,7 @@ import (
) )
type ItemCreate struct { type ItemCreate struct {
ImportRef string `json:"-"`
Name string `json:"name"` Name string `json:"name"`
Description string `json:"description"` Description string `json:"description"`
@ -53,6 +54,7 @@ type ItemUpdate struct {
} }
type ItemSummary struct { type ItemSummary struct {
ImportRef string `json:"-"`
ID uuid.UUID `json:"id"` ID uuid.UUID `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Description string `json:"description"` Description string `json:"description"`

View file

@ -1,5 +1,5 @@
[data-md-color-scheme="homebox"] { [data-md-color-scheme="homebox"] {
--md-primary-fg-color: #5d5656; --md-primary-fg-color: #5b7f67;
--md-primary-fg-color--light: #5b7f67; --md-primary-fg-color--light: #5b7f67;
--md-primary-fg-color--dark: #90030c; --md-primary-fg-color--dark: #90030c;
} }

View file

@ -1,28 +1,41 @@
# CSV Imports # CSV Imports
This document outlines the CSV import feature of Homebox and how to use it.
## Quick Start ## Quick Start
Using the CSV import is the recommended way for adding items to the database. It is always going to be the fastest way to import any large amount of items and provides the most flexibility when it comes to adding items. Using the CSV import is the recommended way for adding items to the database. It is always going to be the fastest way to import any large amount of items and provides the most flexibility when it comes to adding items.
**Limitations** **Limitations**
- Currently only supports importing items
- Currently only supports importing items, locations, and labels
- Does not support attachments. Attachments must be uploaded after import - Does not support attachments. Attachments must be uploaded after import
**Template**
You can use this snippet as the headers for your CSV. Copy and paste it into your spreadsheet editor of choice and fill in the value.
```csv
Import RefLocation Labels Quantity Name Description Insured Serial Number Model Number Manufacturer Notes Purchase From Purchased Price Purchased Time Lifetime Warranty Warranty Expires Warranty Details Sold To Sold Price Sold Time Sold Notes
```
!!! tip "Column Order"
Column headers are just there for reference, the important thing is that the order is correct. You can change the headers to anything you like.
## CSV Reference ## CSV Reference
| Column Heading | Type | Description | | Column | Type | Description |
| ----------------- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------- | | ----------------- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| ImportRef | String (100) | Future |
| Location | String | This is the location of the item that will be created. These are de-duplicated and won't create another instance when reused. | | Location | String | This is the location of the item that will be created. These are de-duplicated and won't create another instance when reused. |
| Labels | `;` Separated String | List of labels to apply to the item seperated by a `;`, can be existing or new | | Labels | `;` Separated String | List of labels to apply to the item separated by a `;`, can be existing or new |
| Quantity | Integer | The quantity of items to create | | Quantity | Integer | The quantity of items to create |
| Name | String | Name of the item | | Name | String | Name of the item |
| Description | String | Description of the item | | Description | String | Description of the item |
| Insured | Boolean | Whether or not the item is insured |
| Serial Number | String | Serial number of the item | | Serial Number | String | Serial number of the item |
| Model Number | String | Model of the item | | Model Number | String | Model of the item |
| Manufacturer | String | Manufacturer of the item | | Manufacturer | String | Manufacturer of the item |
| Notes | String | General notes about the product | | Notes | String (1000) | General notes about the product |
| Purchase From | String | Name of the place the item was purchased from | | Purchase From | String | Name of the place the item was purchased from |
| Purchase Price | Float64 | | | Purchase Price | Float64 | |
| Purchase At | Date | Date the item was purchased | | Purchase At | Date | Date the item was purchased |
@ -32,10 +45,12 @@ Using the CSV import is the recommended way for adding items to the database. It
| Sold To | String | Name of the person the item was sold to | | Sold To | String | Name of the person the item was sold to |
| Sold At | Date | Date the item was sold | | Sold At | Date | Date the item was sold |
| Sold Price | Float64 | | | Sold Price | Float64 | |
| Sold Notes | String (1000) | |
**Type Key** **Type Key**
| Type | Format | | Type | Format |
| ------ | ------------------ | | ------- | --------------------------------------------------- |
| String | Max 255 Characters | | String | Max 255 Characters unless otherwise specified |
| Date | YYYY-MM-DD | | Date | YYYY-MM-DD |
| Boolean | true or false, yes or no, 1 or 0 - case insensitive |

View file

@ -27,3 +27,17 @@ theme:
extra_css: extra_css:
- assets/stylesheets/extras.css - assets/stylesheets/extras.css
markdown_extensions:
- pymdownx.emoji:
emoji_index: !!python/name:materialx.emoji.twemoji
emoji_generator: !!python/name:materialx.emoji.to_svg
- def_list
- pymdownx.highlight
- pymdownx.superfences
- pymdownx.tasklist:
custom_checkbox: true
- admonition
- attr_list
- pymdownx.tabbed
- pymdownx.superfences