diff --git a/backend/app/api/static/docs/docs.go b/backend/app/api/static/docs/docs.go index 4e3a5db2..3311227d 100644 --- a/backend/app/api/static/docs/docs.go +++ b/backend/app/api/static/docs/docs.go @@ -2242,6 +2242,9 @@ const docTemplate = `{ "soldTo": { "type": "string" }, + "syncChildItemsLocations": { + "type": "boolean" + }, "updatedAt": { "type": "string" }, @@ -2440,6 +2443,9 @@ const docTemplate = `{ "type": "string", "maxLength": 255 }, + "syncChildItemsLocations": { + "type": "boolean" + }, "warrantyDetails": { "type": "string" }, diff --git a/backend/app/api/static/docs/swagger.json b/backend/app/api/static/docs/swagger.json index 532020b8..ddec411c 100644 --- a/backend/app/api/static/docs/swagger.json +++ b/backend/app/api/static/docs/swagger.json @@ -2235,6 +2235,9 @@ "soldTo": { "type": "string" }, + "syncChildItemsLocations": { + "type": "boolean" + }, "updatedAt": { "type": "string" }, @@ -2433,6 +2436,9 @@ "type": "string", "maxLength": 255 }, + "syncChildItemsLocations": { + "type": "boolean" + }, "warrantyDetails": { "type": "string" }, diff --git a/backend/app/api/static/docs/swagger.yaml b/backend/app/api/static/docs/swagger.yaml index 7939eb3d..fa9b7c77 100644 --- a/backend/app/api/static/docs/swagger.yaml +++ b/backend/app/api/static/docs/swagger.yaml @@ -188,6 +188,8 @@ definitions: type: string soldTo: type: string + syncChildItemsLocations: + type: boolean updatedAt: type: string warrantyDetails: @@ -323,6 +325,8 @@ definitions: soldTo: maxLength: 255 type: string + syncChildItemsLocations: + type: boolean warrantyDetails: type: string warrantyExpires: diff --git a/backend/internal/data/ent/item.go b/backend/internal/data/ent/item.go index 682616b0..07712bd1 100644 --- a/backend/internal/data/ent/item.go +++ b/backend/internal/data/ent/item.go @@ -40,6 +40,8 @@ type Item struct { Archived bool `json:"archived,omitempty"` // AssetID holds the value of the "asset_id" field. AssetID int `json:"asset_id,omitempty"` + // SyncChildItemsLocations holds the value of the "sync_child_items_locations" field. + SyncChildItemsLocations bool `json:"sync_child_items_locations,omitempty"` // SerialNumber holds the value of the "serial_number" field. SerialNumber string `json:"serial_number,omitempty"` // ModelNumber holds the value of the "model_number" field. @@ -181,7 +183,7 @@ func (*Item) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) for i := range columns { switch columns[i] { - case item.FieldInsured, item.FieldArchived, item.FieldLifetimeWarranty: + case item.FieldInsured, item.FieldArchived, item.FieldSyncChildItemsLocations, item.FieldLifetimeWarranty: values[i] = new(sql.NullBool) case item.FieldPurchasePrice, item.FieldSoldPrice: values[i] = new(sql.NullFloat64) @@ -280,6 +282,12 @@ func (i *Item) assignValues(columns []string, values []any) error { } else if value.Valid { i.AssetID = int(value.Int64) } + case item.FieldSyncChildItemsLocations: + if value, ok := values[j].(*sql.NullBool); !ok { + return fmt.Errorf("unexpected type %T for field sync_child_items_locations", values[j]) + } else if value.Valid { + i.SyncChildItemsLocations = value.Bool + } case item.FieldSerialNumber: if value, ok := values[j].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field serial_number", values[j]) @@ -485,6 +493,9 @@ func (i *Item) String() string { builder.WriteString("asset_id=") builder.WriteString(fmt.Sprintf("%v", i.AssetID)) builder.WriteString(", ") + builder.WriteString("sync_child_items_locations=") + builder.WriteString(fmt.Sprintf("%v", i.SyncChildItemsLocations)) + builder.WriteString(", ") builder.WriteString("serial_number=") builder.WriteString(i.SerialNumber) builder.WriteString(", ") diff --git a/backend/internal/data/ent/item/item.go b/backend/internal/data/ent/item/item.go index bd046791..d3601c64 100644 --- a/backend/internal/data/ent/item/item.go +++ b/backend/internal/data/ent/item/item.go @@ -35,6 +35,8 @@ const ( FieldArchived = "archived" // FieldAssetID holds the string denoting the asset_id field in the database. FieldAssetID = "asset_id" + // FieldSyncChildItemsLocations holds the string denoting the sync_child_items_locations field in the database. + FieldSyncChildItemsLocations = "sync_child_items_locations" // FieldSerialNumber holds the string denoting the serial_number field in the database. FieldSerialNumber = "serial_number" // FieldModelNumber holds the string denoting the model_number field in the database. @@ -142,6 +144,7 @@ var Columns = []string{ FieldInsured, FieldArchived, FieldAssetID, + FieldSyncChildItemsLocations, FieldSerialNumber, FieldModelNumber, FieldManufacturer, @@ -209,6 +212,8 @@ var ( DefaultArchived bool // DefaultAssetID holds the default value on creation for the "asset_id" field. DefaultAssetID int + // DefaultSyncChildItemsLocations holds the default value on creation for the "sync_child_items_locations" field. + DefaultSyncChildItemsLocations bool // SerialNumberValidator is a validator for the "serial_number" field. It is called by the builders before save. SerialNumberValidator func(string) error // ModelNumberValidator is a validator for the "model_number" field. It is called by the builders before save. @@ -287,6 +292,11 @@ func ByAssetID(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldAssetID, opts...).ToFunc() } +// BySyncChildItemsLocations orders the results by the sync_child_items_locations field. +func BySyncChildItemsLocations(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldSyncChildItemsLocations, opts...).ToFunc() +} + // BySerialNumber orders the results by the serial_number field. func BySerialNumber(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldSerialNumber, opts...).ToFunc() diff --git a/backend/internal/data/ent/item/where.go b/backend/internal/data/ent/item/where.go index 61a8dae0..5166fa54 100644 --- a/backend/internal/data/ent/item/where.go +++ b/backend/internal/data/ent/item/where.go @@ -106,6 +106,11 @@ func AssetID(v int) predicate.Item { return predicate.Item(sql.FieldEQ(FieldAssetID, v)) } +// SyncChildItemsLocations applies equality check predicate on the "sync_child_items_locations" field. It's identical to SyncChildItemsLocationsEQ. +func SyncChildItemsLocations(v bool) predicate.Item { + return predicate.Item(sql.FieldEQ(FieldSyncChildItemsLocations, v)) +} + // SerialNumber applies equality check predicate on the "serial_number" field. It's identical to SerialNumberEQ. func SerialNumber(v string) predicate.Item { return predicate.Item(sql.FieldEQ(FieldSerialNumber, v)) @@ -641,6 +646,16 @@ func AssetIDLTE(v int) predicate.Item { return predicate.Item(sql.FieldLTE(FieldAssetID, v)) } +// SyncChildItemsLocationsEQ applies the EQ predicate on the "sync_child_items_locations" field. +func SyncChildItemsLocationsEQ(v bool) predicate.Item { + return predicate.Item(sql.FieldEQ(FieldSyncChildItemsLocations, v)) +} + +// SyncChildItemsLocationsNEQ applies the NEQ predicate on the "sync_child_items_locations" field. +func SyncChildItemsLocationsNEQ(v bool) predicate.Item { + return predicate.Item(sql.FieldNEQ(FieldSyncChildItemsLocations, v)) +} + // SerialNumberEQ applies the EQ predicate on the "serial_number" field. func SerialNumberEQ(v string) predicate.Item { return predicate.Item(sql.FieldEQ(FieldSerialNumber, v)) diff --git a/backend/internal/data/ent/item_update.go b/backend/internal/data/ent/item_update.go index 16c3464b..844eaf60 100644 --- a/backend/internal/data/ent/item_update.go +++ b/backend/internal/data/ent/item_update.go @@ -185,6 +185,20 @@ func (iu *ItemUpdate) AddAssetID(i int) *ItemUpdate { return iu } +// SetSyncChildItemsLocations sets the "sync_child_items_locations" field. +func (iu *ItemUpdate) SetSyncChildItemsLocations(b bool) *ItemUpdate { + iu.mutation.SetSyncChildItemsLocations(b) + return iu +} + +// SetNillableSyncChildItemsLocations sets the "sync_child_items_locations" field if the given value is not nil. +func (iu *ItemUpdate) SetNillableSyncChildItemsLocations(b *bool) *ItemUpdate { + if b != nil { + iu.SetSyncChildItemsLocations(*b) + } + return iu +} + // SetSerialNumber sets the "serial_number" field. func (iu *ItemUpdate) SetSerialNumber(s string) *ItemUpdate { iu.mutation.SetSerialNumber(s) @@ -836,6 +850,9 @@ func (iu *ItemUpdate) sqlSave(ctx context.Context) (n int, err error) { if value, ok := iu.mutation.AddedAssetID(); ok { _spec.AddField(item.FieldAssetID, field.TypeInt, value) } + if value, ok := iu.mutation.SyncChildItemsLocations(); ok { + _spec.SetField(item.FieldSyncChildItemsLocations, field.TypeBool, value) + } if value, ok := iu.mutation.SerialNumber(); ok { _spec.SetField(item.FieldSerialNumber, field.TypeString, value) } @@ -1393,6 +1410,20 @@ func (iuo *ItemUpdateOne) AddAssetID(i int) *ItemUpdateOne { return iuo } +// SetSyncChildItemsLocations sets the "sync_child_items_locations" field. +func (iuo *ItemUpdateOne) SetSyncChildItemsLocations(b bool) *ItemUpdateOne { + iuo.mutation.SetSyncChildItemsLocations(b) + return iuo +} + +// SetNillableSyncChildItemsLocations sets the "sync_child_items_locations" field if the given value is not nil. +func (iuo *ItemUpdateOne) SetNillableSyncChildItemsLocations(b *bool) *ItemUpdateOne { + if b != nil { + iuo.SetSyncChildItemsLocations(*b) + } + return iuo +} + // SetSerialNumber sets the "serial_number" field. func (iuo *ItemUpdateOne) SetSerialNumber(s string) *ItemUpdateOne { iuo.mutation.SetSerialNumber(s) @@ -2074,6 +2105,9 @@ func (iuo *ItemUpdateOne) sqlSave(ctx context.Context) (_node *Item, err error) if value, ok := iuo.mutation.AddedAssetID(); ok { _spec.AddField(item.FieldAssetID, field.TypeInt, value) } + if value, ok := iuo.mutation.SyncChildItemsLocations(); ok { + _spec.SetField(item.FieldSyncChildItemsLocations, field.TypeBool, value) + } if value, ok := iuo.mutation.SerialNumber(); ok { _spec.SetField(item.FieldSerialNumber, field.TypeString, value) } diff --git a/backend/internal/data/ent/migrate/schema.go b/backend/internal/data/ent/migrate/schema.go index 2b588380..05fe8ec2 100644 --- a/backend/internal/data/ent/migrate/schema.go +++ b/backend/internal/data/ent/migrate/schema.go @@ -162,6 +162,7 @@ var ( {Name: "insured", Type: field.TypeBool, Default: false}, {Name: "archived", Type: field.TypeBool, Default: false}, {Name: "asset_id", Type: field.TypeInt, Default: 0}, + {Name: "sync_child_items_locations", Type: field.TypeBool, Default: false}, {Name: "serial_number", Type: field.TypeString, Nullable: true, Size: 255}, {Name: "model_number", Type: field.TypeString, Nullable: true, Size: 255}, {Name: "manufacturer", Type: field.TypeString, Nullable: true, Size: 255}, @@ -187,19 +188,19 @@ var ( ForeignKeys: []*schema.ForeignKey{ { Symbol: "items_groups_items", - Columns: []*schema.Column{ItemsColumns[24]}, + Columns: []*schema.Column{ItemsColumns[25]}, RefColumns: []*schema.Column{GroupsColumns[0]}, OnDelete: schema.Cascade, }, { Symbol: "items_items_children", - Columns: []*schema.Column{ItemsColumns[25]}, + Columns: []*schema.Column{ItemsColumns[26]}, RefColumns: []*schema.Column{ItemsColumns[0]}, OnDelete: schema.SetNull, }, { Symbol: "items_locations_items", - Columns: []*schema.Column{ItemsColumns[26]}, + Columns: []*schema.Column{ItemsColumns[27]}, RefColumns: []*schema.Column{LocationsColumns[0]}, OnDelete: schema.Cascade, }, @@ -213,17 +214,17 @@ var ( { Name: "item_manufacturer", Unique: false, - Columns: []*schema.Column{ItemsColumns[13]}, + Columns: []*schema.Column{ItemsColumns[14]}, }, { Name: "item_model_number", Unique: false, - Columns: []*schema.Column{ItemsColumns[12]}, + Columns: []*schema.Column{ItemsColumns[13]}, }, { Name: "item_serial_number", Unique: false, - Columns: []*schema.Column{ItemsColumns[11]}, + Columns: []*schema.Column{ItemsColumns[12]}, }, { Name: "item_archived", diff --git a/backend/internal/data/ent/mutation.go b/backend/internal/data/ent/mutation.go index 228de48e..4f7e41e6 100644 --- a/backend/internal/data/ent/mutation.go +++ b/backend/internal/data/ent/mutation.go @@ -4085,6 +4085,7 @@ type ItemMutation struct { archived *bool asset_id *int addasset_id *int + sync_child_items_locations *bool serial_number *string model_number *string manufacturer *string @@ -4670,6 +4671,42 @@ func (m *ItemMutation) ResetAssetID() { m.addasset_id = nil } +// SetSyncChildItemsLocations sets the "sync_child_items_locations" field. +func (m *ItemMutation) SetSyncChildItemsLocations(b bool) { + m.sync_child_items_locations = &b +} + +// SyncChildItemsLocations returns the value of the "sync_child_items_locations" field in the mutation. +func (m *ItemMutation) SyncChildItemsLocations() (r bool, exists bool) { + v := m.sync_child_items_locations + if v == nil { + return + } + return *v, true +} + +// OldSyncChildItemsLocations returns the old "sync_child_items_locations" 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) OldSyncChildItemsLocations(ctx context.Context) (v bool, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldSyncChildItemsLocations is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldSyncChildItemsLocations requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldSyncChildItemsLocations: %w", err) + } + return oldValue.SyncChildItemsLocations, nil +} + +// ResetSyncChildItemsLocations resets all changes to the "sync_child_items_locations" field. +func (m *ItemMutation) ResetSyncChildItemsLocations() { + m.sync_child_items_locations = nil +} + // SetSerialNumber sets the "serial_number" field. func (m *ItemMutation) SetSerialNumber(s string) { m.serial_number = &s @@ -5729,7 +5766,7 @@ func (m *ItemMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *ItemMutation) Fields() []string { - fields := make([]string, 0, 23) + fields := make([]string, 0, 24) if m.created_at != nil { fields = append(fields, item.FieldCreatedAt) } @@ -5760,6 +5797,9 @@ func (m *ItemMutation) Fields() []string { if m.asset_id != nil { fields = append(fields, item.FieldAssetID) } + if m.sync_child_items_locations != nil { + fields = append(fields, item.FieldSyncChildItemsLocations) + } if m.serial_number != nil { fields = append(fields, item.FieldSerialNumber) } @@ -5827,6 +5867,8 @@ func (m *ItemMutation) Field(name string) (ent.Value, bool) { return m.Archived() case item.FieldAssetID: return m.AssetID() + case item.FieldSyncChildItemsLocations: + return m.SyncChildItemsLocations() case item.FieldSerialNumber: return m.SerialNumber() case item.FieldModelNumber: @@ -5882,6 +5924,8 @@ func (m *ItemMutation) OldField(ctx context.Context, name string) (ent.Value, er return m.OldArchived(ctx) case item.FieldAssetID: return m.OldAssetID(ctx) + case item.FieldSyncChildItemsLocations: + return m.OldSyncChildItemsLocations(ctx) case item.FieldSerialNumber: return m.OldSerialNumber(ctx) case item.FieldModelNumber: @@ -5987,6 +6031,13 @@ func (m *ItemMutation) SetField(name string, value ent.Value) error { } m.SetAssetID(v) return nil + case item.FieldSyncChildItemsLocations: + v, ok := value.(bool) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetSyncChildItemsLocations(v) + return nil case item.FieldSerialNumber: v, ok := value.(string) if !ok { @@ -6289,6 +6340,9 @@ func (m *ItemMutation) ResetField(name string) error { case item.FieldAssetID: m.ResetAssetID() return nil + case item.FieldSyncChildItemsLocations: + m.ResetSyncChildItemsLocations() + return nil case item.FieldSerialNumber: m.ResetSerialNumber() return nil diff --git a/backend/internal/data/ent/runtime.go b/backend/internal/data/ent/runtime.go index 12e507bd..08aaf3f1 100644 --- a/backend/internal/data/ent/runtime.go +++ b/backend/internal/data/ent/runtime.go @@ -259,36 +259,40 @@ func init() { itemDescAssetID := itemFields[5].Descriptor() // item.DefaultAssetID holds the default value on creation for the asset_id field. item.DefaultAssetID = itemDescAssetID.Default.(int) + // itemDescSyncChildItemsLocations is the schema descriptor for sync_child_items_locations field. + itemDescSyncChildItemsLocations := itemFields[6].Descriptor() + // item.DefaultSyncChildItemsLocations holds the default value on creation for the sync_child_items_locations field. + item.DefaultSyncChildItemsLocations = itemDescSyncChildItemsLocations.Default.(bool) // itemDescSerialNumber is the schema descriptor for serial_number field. - itemDescSerialNumber := itemFields[6].Descriptor() + itemDescSerialNumber := itemFields[7].Descriptor() // 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) // itemDescModelNumber is the schema descriptor for model_number field. - itemDescModelNumber := itemFields[7].Descriptor() + itemDescModelNumber := itemFields[8].Descriptor() // 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) // itemDescManufacturer is the schema descriptor for manufacturer field. - itemDescManufacturer := itemFields[8].Descriptor() + itemDescManufacturer := itemFields[9].Descriptor() // 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) // itemDescLifetimeWarranty is the schema descriptor for lifetime_warranty field. - itemDescLifetimeWarranty := itemFields[9].Descriptor() + itemDescLifetimeWarranty := itemFields[10].Descriptor() // item.DefaultLifetimeWarranty holds the default value on creation for the lifetime_warranty field. item.DefaultLifetimeWarranty = itemDescLifetimeWarranty.Default.(bool) // itemDescWarrantyDetails is the schema descriptor for warranty_details field. - itemDescWarrantyDetails := itemFields[11].Descriptor() + itemDescWarrantyDetails := itemFields[12].Descriptor() // 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) // itemDescPurchasePrice is the schema descriptor for purchase_price field. - itemDescPurchasePrice := itemFields[14].Descriptor() + itemDescPurchasePrice := itemFields[15].Descriptor() // item.DefaultPurchasePrice holds the default value on creation for the purchase_price field. item.DefaultPurchasePrice = itemDescPurchasePrice.Default.(float64) // itemDescSoldPrice is the schema descriptor for sold_price field. - itemDescSoldPrice := itemFields[17].Descriptor() + itemDescSoldPrice := itemFields[18].Descriptor() // item.DefaultSoldPrice holds the default value on creation for the sold_price field. item.DefaultSoldPrice = itemDescSoldPrice.Default.(float64) // itemDescSoldNotes is the schema descriptor for sold_notes field. - itemDescSoldNotes := itemFields[18].Descriptor() + itemDescSoldNotes := itemFields[19].Descriptor() // 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) // itemDescID is the schema descriptor for id field. diff --git a/backend/internal/data/ent/schema/item.go b/backend/internal/data/ent/schema/item.go index 43f4859f..b689933e 100644 --- a/backend/internal/data/ent/schema/item.go +++ b/backend/internal/data/ent/schema/item.go @@ -51,6 +51,8 @@ func (Item) Fields() []ent.Field { Default(false), field.Int("asset_id"). Default(0), + field.Bool("sync_child_items_locations"). + Default(false), // ------------------------------------ // item identification diff --git a/backend/internal/data/repo/repo_items.go b/backend/internal/data/repo/repo_items.go index cffc910f..a4642116 100644 --- a/backend/internal/data/repo/repo_items.go +++ b/backend/internal/data/repo/repo_items.go @@ -55,11 +55,11 @@ type ( } ItemCreate struct { - ImportRef string `json:"-"` - ParentID uuid.UUID `json:"parentId" extensions:"x-nullable"` - Name string `json:"name" validate:"required,min=1,max=255"` - Description string `json:"description" validate:"max=1000"` - AssetID AssetID `json:"-"` + ImportRef string `json:"-"` + ParentID uuid.UUID `json:"parentId" extensions:"x-nullable"` + Name string `json:"name" validate:"required,min=1,max=255"` + Description string `json:"description" validate:"max=1000"` + AssetID AssetID `json:"-"` // Edges LocationID uuid.UUID `json:"locationId"` @@ -67,14 +67,15 @@ type ( } ItemUpdate struct { - ParentID uuid.UUID `json:"parentId" extensions:"x-nullable,x-omitempty"` - ID uuid.UUID `json:"id"` - AssetID AssetID `json:"assetId" swaggertype:"string"` - Name string `json:"name" validate:"required,min=1,max=255"` - Description string `json:"description" validate:"max=1000"` - Quantity int `json:"quantity"` - Insured bool `json:"insured"` - Archived bool `json:"archived"` + ParentID uuid.UUID `json:"parentId" extensions:"x-nullable,x-omitempty"` + ID uuid.UUID `json:"id"` + AssetID AssetID `json:"assetId" swaggertype:"string"` + Name string `json:"name" validate:"required,min=1,max=255"` + Description string `json:"description" validate:"max=1000"` + Quantity int `json:"quantity"` + Insured bool `json:"insured"` + Archived bool `json:"archived"` + SyncChildItemsLocations bool `json:"syncChildItemsLocations"` // Edges LocationID uuid.UUID `json:"locationId"` @@ -137,6 +138,8 @@ type ( ItemSummary AssetID AssetID `json:"assetId,string"` + SyncChildItemsLocations bool `json:"syncChildItemsLocations"` + SerialNumber string `json:"serialNumber"` ModelNumber string `json:"modelNumber"` Manufacturer string `json:"manufacturer"` @@ -248,12 +251,13 @@ func mapItemOut(item *ent.Item) ItemOut { } return ItemOut{ - Parent: parent, - AssetID: AssetID(item.AssetID), - ItemSummary: mapItemSummary(item), - LifetimeWarranty: item.LifetimeWarranty, - WarrantyExpires: types.DateFromTime(item.WarrantyExpires), - WarrantyDetails: item.WarrantyDetails, + Parent: parent, + AssetID: AssetID(item.AssetID), + ItemSummary: mapItemSummary(item), + LifetimeWarranty: item.LifetimeWarranty, + WarrantyExpires: types.DateFromTime(item.WarrantyExpires), + WarrantyDetails: item.WarrantyDetails, + SyncChildItemsLocations: item.SyncChildItemsLocations, // Identification SerialNumber: item.SerialNumber, @@ -606,7 +610,8 @@ func (e *ItemsRepository) UpdateByGroup(ctx context.Context, gid uuid.UUID, data SetWarrantyExpires(data.WarrantyExpires.Time()). SetWarrantyDetails(data.WarrantyDetails). SetQuantity(data.Quantity). - SetAssetID(int(data.AssetID)) + SetAssetID(int(data.AssetID)). + SetSyncChildItemsLocations(data.SyncChildItemsLocations) currentLabels, err := e.db.Item.Query().Where(item.ID(data.ID)).QueryLabel().All(ctx) if err != nil { @@ -633,6 +638,28 @@ func (e *ItemsRepository) UpdateByGroup(ctx context.Context, gid uuid.UUID, data q.ClearParent() } + if data.SyncChildItemsLocations { + children, err := e.db.Item.Query().Where(item.ID(data.ID)).QueryChildren().All(ctx) + if err != nil { + return ItemOut{}, err + } + location := data.LocationID + + for _, child := range children { + child_location, err := child.QueryLocation().First(ctx) + if err != nil { + return ItemOut{}, err + } + + if location != child_location.ID { + err = child.Update().SetLocationID(location).Exec(ctx) + if err != nil { + return ItemOut{}, err + } + } + } + } + err = q.Exec(ctx) if err != nil { return ItemOut{}, err diff --git a/frontend/components/Form/Toggle.vue b/frontend/components/Form/Toggle.vue new file mode 100644 index 00000000..2e68d63e --- /dev/null +++ b/frontend/components/Form/Toggle.vue @@ -0,0 +1,36 @@ + + + + \ No newline at end of file diff --git a/frontend/lib/api/__test__/user/items.test.ts b/frontend/lib/api/__test__/user/items.test.ts index 4f8973f9..e32a4baa 100644 --- a/frontend/lib/api/__test__/user/items.test.ts +++ b/frontend/lib/api/__test__/user/items.test.ts @@ -193,4 +193,72 @@ describe("user should be able to create an item and add an attachment", () => { cleanup(); }); + + test("child items sync their location to their parent", async () => { + const api = await sharedUserClient(); + const [parentLocation, parentCleanup] = await useLocation(api); + const [childsLocation, childsCleanup] = await useLocation(api); + + const { response: parentResponse, data: parent } = await api.items.create({ + name: "parent-item", + labelIds: [], + description: "test-description", + locationId: parentLocation.id, + }); + expect(parentResponse.status).toBe(201); + expect(parent.id).toBeTruthy(); + + const { response: child1Response, data: child1Item } = await api.items.create({ + name: "child1-item", + labelIds: [], + description: "test-description", + locationId: childsLocation.id, + }); + expect(child1Response.status).toBe(201); + const child1ItemUpdate = { + parentId: parent.id, + ...child1Item, + locationId: child1Item.location?.id, + labelIds: [] + }; + const { response: child1UpdatedResponse, data: child1UpdatedData } = await api.items.update(child1Item.id, child1ItemUpdate as ItemUpdate); + expect(child1UpdatedResponse.status).toBe(200); + + const { response: child2Response, data: child2Item } = await api.items.create({ + name: "child2-item", + labelIds: [], + description: "test-description", + locationId: childsLocation.id, + }); + expect(child2Response.status).toBe(201); + const child2ItemUpdate = { + parentId: parent.id, + ...child2Item, + locationId: child2Item.location?.id, + labelIds: [] + } + const { response: child2UpdatedResponse, data: child2UpdatedData } = await api.items.update(child2Item.id, child2ItemUpdate as ItemUpdate); + expect(child2UpdatedResponse.status).toBe(200); + + const itemUpdate = { + parentId: null, + ...parent, + locationId: parentLocation.id, + labelIds: [], + syncChildItemsLocations: true + }; + const { response: updateResponse, data: updateData } = await api.items.update(parent.id, itemUpdate) + expect(updateResponse.status).toBe(200); + + const { response: child1FinalResponse, data: child1FinalData } = await api.items.get(child1Item.id); + expect(child1FinalResponse.status).toBe(200); + expect(child1FinalData.location?.id).toBe(parentLocation.id); + + const { response: child2FinalResponse, data: child2FinalData } = await api.items.get(child2Item.id); + expect(child2FinalResponse.status).toBe(200); + expect(child2FinalData.location?.id).toBe(parentLocation.id); + + parentCleanup(); + childsCleanup(); + }); }); diff --git a/frontend/lib/api/types/data-contracts.ts b/frontend/lib/api/types/data-contracts.ts index c664c93f..6cfda968 100644 --- a/frontend/lib/api/types/data-contracts.ts +++ b/frontend/lib/api/types/data-contracts.ts @@ -116,6 +116,7 @@ export interface ItemOut { /** Sold */ soldTime: Date | string; soldTo: string; + syncChildItemsLocations: boolean; updatedAt: Date | string; warrantyDetails: string; warrantyExpires: Date | string; @@ -190,6 +191,7 @@ export interface ItemUpdate { soldTime: Date | string; /** @maxLength 255 */ soldTo: string; + syncChildItemsLocations: boolean; warrantyDetails: string; warrantyExpires: Date | string; } diff --git a/frontend/pages/item/[id]/index/edit.vue b/frontend/pages/item/[id]/index/edit.vue index 1f3cd0bc..e26acc37 100644 --- a/frontend/pages/item/[id]/index/edit.vue +++ b/frontend/pages/item/[id]/index/edit.vue @@ -427,6 +427,63 @@ } } + async function maybeSyncWithParentLocation() { + if (parent.value && parent.value.id) { + const { data, error } = await api.items.get(parent.value.id); + + if (error) { + toast.error("Something went wrong trying to load parent data"); + } else { + if (data.syncChildItemsLocations) { + toast.info("Selected parent syncs its children's locations to its own. The location has been updated."); + item.value.location = data.location + } + } + } + } + + async function informAboutDesyncingLocationFromParent() { + if (parent.value && parent.value.id) { + const { data, error } = await api.items.get(parent.value.id); + + if (error) { + toast.error("Something went wrong trying to load parent data"); + } + + if (data.syncChildItemsLocations) { + toast.info("Changing location will de-sync it from the parent's location"); + } + } + } + + async function syncChildItemsLocations() { + if (!item.value.location?.id) { + toast.error("Failed to save item: no location selected"); + return; + } + + const payload: ItemUpdate = { + ...item.value, + locationId: item.value.location?.id, + labelIds: item.value.labels.map(l => l.id), + parentId: parent.value ? parent.value.id : null, + assetId: item.value.assetId, + }; + + const { error } = await api.items.update(itemId.value, payload); + + if (error) { + toast.error("Failed to save item"); + return; + } + + if (!item.value.syncChildItemsLocations) { + toast.success("Child items' locations will no longer be synced with this item.") + } else { + toast.success("Child items' locations have been synced with this item"); + } + } + onMounted(() => { window.addEventListener("keydown", keyboardSave); }); @@ -488,8 +545,9 @@
- + +