feat: add archive item options (#122)

Add archive option feature. Archived items can only be seen on the items page when including archived is selected. Archived items are excluded from the count and from other views
This commit is contained in:
Hayden 2022-10-31 23:30:42 -08:00 committed by GitHub
parent c722495fdd
commit a886fa86ca
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 325 additions and 38 deletions

View file

@ -47,15 +47,24 @@ func (ctrl *V1Controller) HandleItemsGetAll() server.HandlerFunc {
return i
}
getBool := func(s string) bool {
b, err := strconv.ParseBool(s)
if err != nil {
return false
}
return b
}
extractQuery := func(r *http.Request) repo.ItemQuery {
params := r.URL.Query()
return repo.ItemQuery{
Page: intOrNegativeOne(params.Get("page")),
PageSize: intOrNegativeOne(params.Get("perPage")),
Search: params.Get("q"),
LocationIDs: uuidList(params, "locations"),
LabelIDs: uuidList(params, "labels"),
Page: intOrNegativeOne(params.Get("page")),
PageSize: intOrNegativeOne(params.Get("perPage")),
Search: params.Get("q"),
LocationIDs: uuidList(params, "locations"),
LabelIDs: uuidList(params, "labels"),
IncludeArchived: getBool(params.Get("includeArchived")),
}
}

View file

@ -1274,6 +1274,9 @@ const docTemplate = `{
"repo.ItemOut": {
"type": "object",
"properties": {
"archived": {
"type": "boolean"
},
"attachments": {
"type": "array",
"items": {
@ -1383,6 +1386,9 @@ const docTemplate = `{
"repo.ItemSummary": {
"type": "object",
"properties": {
"archived": {
"type": "boolean"
},
"createdAt": {
"type": "string"
},
@ -1421,6 +1427,9 @@ const docTemplate = `{
"repo.ItemUpdate": {
"type": "object",
"properties": {
"archived": {
"type": "boolean"
},
"description": {
"type": "string"
},

View file

@ -1266,6 +1266,9 @@
"repo.ItemOut": {
"type": "object",
"properties": {
"archived": {
"type": "boolean"
},
"attachments": {
"type": "array",
"items": {
@ -1375,6 +1378,9 @@
"repo.ItemSummary": {
"type": "object",
"properties": {
"archived": {
"type": "boolean"
},
"createdAt": {
"type": "string"
},
@ -1413,6 +1419,9 @@
"repo.ItemUpdate": {
"type": "object",
"properties": {
"archived": {
"type": "boolean"
},
"description": {
"type": "string"
},

View file

@ -85,6 +85,8 @@ definitions:
type: object
repo.ItemOut:
properties:
archived:
type: boolean
attachments:
items:
$ref: '#/definitions/repo.ItemAttachment'
@ -161,6 +163,8 @@ definitions:
type: object
repo.ItemSummary:
properties:
archived:
type: boolean
createdAt:
type: string
description:
@ -187,6 +191,8 @@ definitions:
type: object
repo.ItemUpdate:
properties:
archived:
type: boolean
description:
type: string
fields:

View file

@ -35,6 +35,8 @@ type Item struct {
Quantity int `json:"quantity,omitempty"`
// Insured holds the value of the "insured" field.
Insured bool `json:"insured,omitempty"`
// Archived holds the value of the "archived" field.
Archived bool `json:"archived,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.
@ -170,7 +172,7 @@ func (*Item) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case item.FieldInsured, item.FieldLifetimeWarranty:
case item.FieldInsured, item.FieldArchived, item.FieldLifetimeWarranty:
values[i] = new(sql.NullBool)
case item.FieldPurchasePrice, item.FieldSoldPrice:
values[i] = new(sql.NullFloat64)
@ -257,6 +259,12 @@ func (i *Item) assignValues(columns []string, values []any) error {
} else if value.Valid {
i.Insured = value.Bool
}
case item.FieldArchived:
if value, ok := values[j].(*sql.NullBool); !ok {
return fmt.Errorf("unexpected type %T for field archived", values[j])
} else if value.Valid {
i.Archived = 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])
@ -443,6 +451,9 @@ func (i *Item) String() string {
builder.WriteString("insured=")
builder.WriteString(fmt.Sprintf("%v", i.Insured))
builder.WriteString(", ")
builder.WriteString("archived=")
builder.WriteString(fmt.Sprintf("%v", i.Archived))
builder.WriteString(", ")
builder.WriteString("serial_number=")
builder.WriteString(i.SerialNumber)
builder.WriteString(", ")

View file

@ -29,6 +29,8 @@ const (
FieldQuantity = "quantity"
// FieldInsured holds the string denoting the insured field in the database.
FieldInsured = "insured"
// FieldArchived holds the string denoting the archived field in the database.
FieldArchived = "archived"
// 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.
@ -125,6 +127,7 @@ var Columns = []string{
FieldNotes,
FieldQuantity,
FieldInsured,
FieldArchived,
FieldSerialNumber,
FieldModelNumber,
FieldManufacturer,
@ -188,6 +191,8 @@ var (
DefaultQuantity int
// DefaultInsured holds the default value on creation for the "insured" field.
DefaultInsured bool
// DefaultArchived holds the default value on creation for the "archived" field.
DefaultArchived 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.

View file

@ -138,6 +138,13 @@ func Insured(v bool) predicate.Item {
})
}
// Archived applies equality check predicate on the "archived" field. It's identical to ArchivedEQ.
func Archived(v bool) predicate.Item {
return predicate.Item(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldArchived), 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(func(s *sql.Selector) {
@ -873,6 +880,20 @@ func InsuredNEQ(v bool) predicate.Item {
})
}
// ArchivedEQ applies the EQ predicate on the "archived" field.
func ArchivedEQ(v bool) predicate.Item {
return predicate.Item(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldArchived), v))
})
}
// ArchivedNEQ applies the NEQ predicate on the "archived" field.
func ArchivedNEQ(v bool) predicate.Item {
return predicate.Item(func(s *sql.Selector) {
s.Where(sql.NEQ(s.C(FieldArchived), v))
})
}
// SerialNumberEQ applies the EQ predicate on the "serial_number" field.
func SerialNumberEQ(v string) predicate.Item {
return predicate.Item(func(s *sql.Selector) {

View file

@ -130,6 +130,20 @@ func (ic *ItemCreate) SetNillableInsured(b *bool) *ItemCreate {
return ic
}
// SetArchived sets the "archived" field.
func (ic *ItemCreate) SetArchived(b bool) *ItemCreate {
ic.mutation.SetArchived(b)
return ic
}
// SetNillableArchived sets the "archived" field if the given value is not nil.
func (ic *ItemCreate) SetNillableArchived(b *bool) *ItemCreate {
if b != nil {
ic.SetArchived(*b)
}
return ic
}
// SetSerialNumber sets the "serial_number" field.
func (ic *ItemCreate) SetSerialNumber(s string) *ItemCreate {
ic.mutation.SetSerialNumber(s)
@ -528,6 +542,10 @@ func (ic *ItemCreate) defaults() {
v := item.DefaultInsured
ic.mutation.SetInsured(v)
}
if _, ok := ic.mutation.Archived(); !ok {
v := item.DefaultArchived
ic.mutation.SetArchived(v)
}
if _, ok := ic.mutation.LifetimeWarranty(); !ok {
v := item.DefaultLifetimeWarranty
ic.mutation.SetLifetimeWarranty(v)
@ -583,6 +601,9 @@ func (ic *ItemCreate) check() error {
if _, ok := ic.mutation.Insured(); !ok {
return &ValidationError{Name: "insured", err: errors.New(`ent: missing required field "Item.insured"`)}
}
if _, ok := ic.mutation.Archived(); !ok {
return &ValidationError{Name: "archived", err: errors.New(`ent: missing required field "Item.archived"`)}
}
if v, ok := ic.mutation.SerialNumber(); ok {
if err := item.SerialNumberValidator(v); err != nil {
return &ValidationError{Name: "serial_number", err: fmt.Errorf(`ent: validator failed for field "Item.serial_number": %w`, err)}
@ -720,6 +741,14 @@ func (ic *ItemCreate) createSpec() (*Item, *sqlgraph.CreateSpec) {
})
_node.Insured = value
}
if value, ok := ic.mutation.Archived(); ok {
_spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{
Type: field.TypeBool,
Value: value,
Column: item.FieldArchived,
})
_node.Archived = value
}
if value, ok := ic.mutation.SerialNumber(); ok {
_spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{
Type: field.TypeString,

View file

@ -121,6 +121,20 @@ func (iu *ItemUpdate) SetNillableInsured(b *bool) *ItemUpdate {
return iu
}
// SetArchived sets the "archived" field.
func (iu *ItemUpdate) SetArchived(b bool) *ItemUpdate {
iu.mutation.SetArchived(b)
return iu
}
// SetNillableArchived sets the "archived" field if the given value is not nil.
func (iu *ItemUpdate) SetNillableArchived(b *bool) *ItemUpdate {
if b != nil {
iu.SetArchived(*b)
}
return iu
}
// SetSerialNumber sets the "serial_number" field.
func (iu *ItemUpdate) SetSerialNumber(s string) *ItemUpdate {
iu.mutation.SetSerialNumber(s)
@ -795,6 +809,13 @@ func (iu *ItemUpdate) sqlSave(ctx context.Context) (n int, err error) {
Column: item.FieldInsured,
})
}
if value, ok := iu.mutation.Archived(); ok {
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
Type: field.TypeBool,
Value: value,
Column: item.FieldArchived,
})
}
if value, ok := iu.mutation.SerialNumber(); ok {
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
Type: field.TypeString,
@ -1387,6 +1408,20 @@ func (iuo *ItemUpdateOne) SetNillableInsured(b *bool) *ItemUpdateOne {
return iuo
}
// SetArchived sets the "archived" field.
func (iuo *ItemUpdateOne) SetArchived(b bool) *ItemUpdateOne {
iuo.mutation.SetArchived(b)
return iuo
}
// SetNillableArchived sets the "archived" field if the given value is not nil.
func (iuo *ItemUpdateOne) SetNillableArchived(b *bool) *ItemUpdateOne {
if b != nil {
iuo.SetArchived(*b)
}
return iuo
}
// SetSerialNumber sets the "serial_number" field.
func (iuo *ItemUpdateOne) SetSerialNumber(s string) *ItemUpdateOne {
iuo.mutation.SetSerialNumber(s)
@ -2091,6 +2126,13 @@ func (iuo *ItemUpdateOne) sqlSave(ctx context.Context) (_node *Item, err error)
Column: item.FieldInsured,
})
}
if value, ok := iuo.mutation.Archived(); ok {
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
Type: field.TypeBool,
Value: value,
Column: item.FieldArchived,
})
}
if value, ok := iuo.mutation.SerialNumber(); ok {
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
Type: field.TypeString,

View file

@ -170,6 +170,7 @@ var (
{Name: "notes", Type: field.TypeString, Nullable: true, Size: 1000},
{Name: "quantity", Type: field.TypeInt, Default: 1},
{Name: "insured", Type: field.TypeBool, Default: false},
{Name: "archived", 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},
@ -195,19 +196,19 @@ var (
ForeignKeys: []*schema.ForeignKey{
{
Symbol: "items_groups_items",
Columns: []*schema.Column{ItemsColumns[22]},
Columns: []*schema.Column{ItemsColumns[23]},
RefColumns: []*schema.Column{GroupsColumns[0]},
OnDelete: schema.Cascade,
},
{
Symbol: "items_items_children",
Columns: []*schema.Column{ItemsColumns[23]},
Columns: []*schema.Column{ItemsColumns[24]},
RefColumns: []*schema.Column{ItemsColumns[0]},
OnDelete: schema.SetNull,
},
{
Symbol: "items_locations_items",
Columns: []*schema.Column{ItemsColumns[24]},
Columns: []*schema.Column{ItemsColumns[25]},
RefColumns: []*schema.Column{LocationsColumns[0]},
OnDelete: schema.Cascade,
},
@ -221,16 +222,21 @@ var (
{
Name: "item_manufacturer",
Unique: false,
Columns: []*schema.Column{ItemsColumns[11]},
Columns: []*schema.Column{ItemsColumns[12]},
},
{
Name: "item_model_number",
Unique: false,
Columns: []*schema.Column{ItemsColumns[10]},
Columns: []*schema.Column{ItemsColumns[11]},
},
{
Name: "item_serial_number",
Unique: false,
Columns: []*schema.Column{ItemsColumns[10]},
},
{
Name: "item_archived",
Unique: false,
Columns: []*schema.Column{ItemsColumns[9]},
},
},

View file

@ -4133,6 +4133,7 @@ type ItemMutation struct {
quantity *int
addquantity *int
insured *bool
archived *bool
serial_number *string
model_number *string
manufacturer *string
@ -4623,6 +4624,42 @@ func (m *ItemMutation) ResetInsured() {
m.insured = nil
}
// SetArchived sets the "archived" field.
func (m *ItemMutation) SetArchived(b bool) {
m.archived = &b
}
// Archived returns the value of the "archived" field in the mutation.
func (m *ItemMutation) Archived() (r bool, exists bool) {
v := m.archived
if v == nil {
return
}
return *v, true
}
// OldArchived returns the old "archived" 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) OldArchived(ctx context.Context) (v bool, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldArchived is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldArchived requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldArchived: %w", err)
}
return oldValue.Archived, nil
}
// ResetArchived resets all changes to the "archived" field.
func (m *ItemMutation) ResetArchived() {
m.archived = nil
}
// SetSerialNumber sets the "serial_number" field.
func (m *ItemMutation) SetSerialNumber(s string) {
m.serial_number = &s
@ -5613,7 +5650,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, 21)
fields := make([]string, 0, 22)
if m.created_at != nil {
fields = append(fields, item.FieldCreatedAt)
}
@ -5638,6 +5675,9 @@ func (m *ItemMutation) Fields() []string {
if m.insured != nil {
fields = append(fields, item.FieldInsured)
}
if m.archived != nil {
fields = append(fields, item.FieldArchived)
}
if m.serial_number != nil {
fields = append(fields, item.FieldSerialNumber)
}
@ -5701,6 +5741,8 @@ func (m *ItemMutation) Field(name string) (ent.Value, bool) {
return m.Quantity()
case item.FieldInsured:
return m.Insured()
case item.FieldArchived:
return m.Archived()
case item.FieldSerialNumber:
return m.SerialNumber()
case item.FieldModelNumber:
@ -5752,6 +5794,8 @@ func (m *ItemMutation) OldField(ctx context.Context, name string) (ent.Value, er
return m.OldQuantity(ctx)
case item.FieldInsured:
return m.OldInsured(ctx)
case item.FieldArchived:
return m.OldArchived(ctx)
case item.FieldSerialNumber:
return m.OldSerialNumber(ctx)
case item.FieldModelNumber:
@ -5843,6 +5887,13 @@ func (m *ItemMutation) SetField(name string, value ent.Value) error {
}
m.SetInsured(v)
return nil
case item.FieldArchived:
v, ok := value.(bool)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetArchived(v)
return nil
case item.FieldSerialNumber:
v, ok := value.(string)
if !ok {
@ -6127,6 +6178,9 @@ func (m *ItemMutation) ResetField(name string) error {
case item.FieldInsured:
m.ResetInsured()
return nil
case item.FieldArchived:
m.ResetArchived()
return nil
case item.FieldSerialNumber:
m.ResetSerialNumber()
return nil

View file

@ -271,36 +271,40 @@ func init() {
itemDescInsured := itemFields[3].Descriptor()
// item.DefaultInsured holds the default value on creation for the insured field.
item.DefaultInsured = itemDescInsured.Default.(bool)
// itemDescArchived is the schema descriptor for archived field.
itemDescArchived := itemFields[4].Descriptor()
// item.DefaultArchived holds the default value on creation for the archived field.
item.DefaultArchived = itemDescArchived.Default.(bool)
// itemDescSerialNumber is the schema descriptor for serial_number field.
itemDescSerialNumber := itemFields[4].Descriptor()
itemDescSerialNumber := itemFields[5].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[5].Descriptor()
itemDescModelNumber := itemFields[6].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[6].Descriptor()
itemDescManufacturer := itemFields[7].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[7].Descriptor()
itemDescLifetimeWarranty := itemFields[8].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[9].Descriptor()
itemDescWarrantyDetails := itemFields[10].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[12].Descriptor()
itemDescPurchasePrice := itemFields[13].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[15].Descriptor()
itemDescSoldPrice := itemFields[16].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[16].Descriptor()
itemDescSoldNotes := itemFields[17].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.

View file

@ -28,6 +28,7 @@ func (Item) Indexes() []ent.Index {
index.Fields("manufacturer"),
index.Fields("model_number"),
index.Fields("serial_number"),
index.Fields("archived"),
}
}
@ -45,6 +46,8 @@ func (Item) Fields() []ent.Field {
Default(1),
field.Bool("insured").
Default(false),
field.Bool("archived").
Default(false),
// ------------------------------------
// item identification

View file

@ -0,0 +1,22 @@
-- disable the enforcement of foreign-keys constraints
PRAGMA foreign_keys = off;
-- create "new_items" table
CREATE TABLE `new_items` (`id` uuid NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, `name` text NOT NULL, `description` text NULL, `import_ref` text NULL, `notes` text NULL, `quantity` integer NOT NULL DEFAULT 1, `insured` bool NOT NULL DEFAULT false, `archived` bool NOT NULL DEFAULT false, `serial_number` text NULL, `model_number` text NULL, `manufacturer` text NULL, `lifetime_warranty` bool NOT NULL DEFAULT false, `warranty_expires` datetime NULL, `warranty_details` text NULL, `purchase_time` datetime NULL, `purchase_from` text NULL, `purchase_price` real NOT NULL DEFAULT 0, `sold_time` datetime NULL, `sold_to` text NULL, `sold_price` real NOT NULL DEFAULT 0, `sold_notes` text NULL, `group_items` uuid NOT NULL, `item_children` uuid NULL, `location_items` uuid NULL, PRIMARY KEY (`id`), CONSTRAINT `items_groups_items` FOREIGN KEY (`group_items`) REFERENCES `groups` (`id`) ON DELETE CASCADE, CONSTRAINT `items_items_children` FOREIGN KEY (`item_children`) REFERENCES `items` (`id`) ON DELETE SET NULL, CONSTRAINT `items_locations_items` FOREIGN KEY (`location_items`) REFERENCES `locations` (`id`) ON DELETE CASCADE);
-- copy rows from old table "items" to new temporary table "new_items"
INSERT INTO `new_items` (`id`, `created_at`, `updated_at`, `name`, `description`, `import_ref`, `notes`, `quantity`, `insured`, `serial_number`, `model_number`, `manufacturer`, `lifetime_warranty`, `warranty_expires`, `warranty_details`, `purchase_time`, `purchase_from`, `purchase_price`, `sold_time`, `sold_to`, `sold_price`, `sold_notes`, `group_items`, `item_children`, `location_items`) SELECT `id`, `created_at`, `updated_at`, `name`, `description`, `import_ref`, `notes`, `quantity`, `insured`, `serial_number`, `model_number`, `manufacturer`, `lifetime_warranty`, `warranty_expires`, `warranty_details`, `purchase_time`, `purchase_from`, `purchase_price`, `sold_time`, `sold_to`, `sold_price`, `sold_notes`, `group_items`, `item_children`, `location_items` FROM `items`;
-- drop "items" table after copying rows
DROP TABLE `items`;
-- rename temporary table "new_items" to "items"
ALTER TABLE `new_items` RENAME TO `items`;
-- create index "item_name" to table: "items"
CREATE INDEX `item_name` ON `items` (`name`);
-- create index "item_manufacturer" to table: "items"
CREATE INDEX `item_manufacturer` ON `items` (`manufacturer`);
-- create index "item_model_number" to table: "items"
CREATE INDEX `item_model_number` ON `items` (`model_number`);
-- create index "item_serial_number" to table: "items"
CREATE INDEX `item_serial_number` ON `items` (`serial_number`);
-- create index "item_archived" to table: "items"
CREATE INDEX `item_archived` ON `items` (`archived`);
-- enable back the enforcement of foreign-keys constraints
PRAGMA foreign_keys = on;

View file

@ -1,5 +1,6 @@
h1:mYTnmyrnBDST/r93NGJM33mIJqhp/U9qR440zI99eqQ=
h1:i76VRMDIPdcmQtXTe9bzrgITAzLGjjVy9y8XaXIchAs=
20220929052825_init.sql h1:ZlCqm1wzjDmofeAcSX3jE4h4VcdTNGpRg2eabztDy9Q=
20221001210956_group_invitations.sql h1:YQKJFtE39wFOcRNbZQ/d+ZlHwrcfcsZlcv/pLEYdpjw=
20221009173029_add_user_roles.sql h1:vWmzAfgEWQeGk0Vn70zfVPCcfEZth3E0JcvyKTjpYyU=
20221020043305_allow_nesting_types.sql h1:4AyJpZ7l7SSJtJAQETYY802FHJ64ufYPJTqvwdiGn3M=
20221101041931_add_archived_field.sql h1:L2WxiOh1svRn817cNURgqnEQg6DIcodZ1twK4tvxW94=

View file

@ -20,12 +20,13 @@ type ItemsRepository struct {
type (
ItemQuery struct {
Page int
PageSize int
Search string `json:"search"`
LocationIDs []uuid.UUID `json:"locationIds"`
LabelIDs []uuid.UUID `json:"labelIds"`
SortBy string `json:"sortBy"`
Page int
PageSize int
Search string `json:"search"`
LocationIDs []uuid.UUID `json:"locationIds"`
LabelIDs []uuid.UUID `json:"labelIds"`
SortBy string `json:"sortBy"`
IncludeArchived bool `json:"includeArchived"`
}
ItemField struct {
@ -55,6 +56,7 @@ type (
Description string `json:"description"`
Quantity int `json:"quantity"`
Insured bool `json:"insured"`
Archived bool `json:"archived"`
// Edges
LocationID uuid.UUID `json:"locationId"`
@ -93,6 +95,7 @@ type (
Description string `json:"description"`
Quantity int `json:"quantity"`
Insured bool `json:"insured"`
Archived bool `json:"archived"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
@ -157,6 +160,7 @@ func mapItemSummary(item *ent.Item) ItemSummary {
Quantity: item.Quantity,
CreatedAt: item.CreatedAt,
UpdatedAt: item.UpdatedAt,
Archived: item.Archived,
// Edges
Location: location,
@ -276,7 +280,20 @@ func (e *ItemsRepository) GetOneByGroup(ctx context.Context, gid, id uuid.UUID)
// QueryByGroup returns a list of items that belong to a specific group based on the provided query.
func (e *ItemsRepository) QueryByGroup(ctx context.Context, gid uuid.UUID, q ItemQuery) (PaginationResult[ItemSummary], error) {
qb := e.db.Item.Query().Where(item.HasGroupWith(group.ID(gid)))
qb := e.db.Item.Query().Where(
item.HasGroupWith(group.ID(gid)),
)
if q.IncludeArchived {
qb = qb.Where(
item.Or(
item.Archived(true),
item.Archived(false),
),
)
} else {
qb = qb.Where(item.Archived(false))
}
if len(q.LabelIDs) > 0 {
labels := make([]predicate.Item, 0, len(q.LabelIDs))
@ -384,6 +401,7 @@ func (e *ItemsRepository) UpdateByGroup(ctx context.Context, gid uuid.UUID, data
SetSerialNumber(data.SerialNumber).
SetModelNumber(data.ModelNumber).
SetManufacturer(data.Manufacturer).
SetArchived(data.Archived).
SetPurchaseTime(data.PurchaseTime).
SetPurchaseFrom(data.PurchaseFrom).
SetPurchasePrice(data.PurchasePrice).

View file

@ -7,6 +7,7 @@ import (
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/data/ent"
"github.com/hay-kot/homebox/backend/internal/data/ent/group"
"github.com/hay-kot/homebox/backend/internal/data/ent/item"
"github.com/hay-kot/homebox/backend/internal/data/ent/label"
"github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
)
@ -68,7 +69,9 @@ func (r *LabelRepository) getOne(ctx context.Context, where ...predicate.Label)
return mapLabelOutErr(r.db.Label.Query().
Where(where...).
WithGroup().
WithItems().
WithItems(func(iq *ent.ItemQuery) {
iq.Where(item.Archived(false))
}).
Only(ctx),
)
}

View file

@ -7,6 +7,7 @@ import (
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/data/ent"
"github.com/hay-kot/homebox/backend/internal/data/ent/group"
"github.com/hay-kot/homebox/backend/internal/data/ent/item"
"github.com/hay-kot/homebox/backend/internal/data/ent/location"
"github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
)
@ -105,6 +106,7 @@ func (r *LocationRepository) GetAll(ctx context.Context, groupId uuid.UUID) ([]L
items
WHERE
items.location_items = locations.id
AND items.archived = false
) as item_count
FROM
locations
@ -139,7 +141,7 @@ func (r *LocationRepository) getOne(ctx context.Context, where ...predicate.Loca
Where(where...).
WithGroup().
WithItems(func(iq *ent.ItemQuery) {
iq.WithLabel()
iq.Where(item.Archived(false)).WithLabel()
}).
WithParent().
WithChildren().

View file

@ -2,7 +2,7 @@
<div v-if="!inline" class="form-control w-full">
<label class="label cursor-pointer">
<span class="label-text"> {{ label }}</span>
<input v-model="value" type="checkbox" class="checkbox" />
<input v-model="value" type="checkbox" class="checkbox checkbox-primary" />
</label>
</div>
<div v-else class="label cursor-pointer sm:grid sm:grid-cols-4 sm:items-start sm:gap-4">
@ -11,7 +11,7 @@
{{ label }}
</span>
</label>
<input v-model="value" type="checkbox" class="checkbox" />
<input v-model="value" type="checkbox" class="checkbox checkbox-primary" />
</div>
</template>

View file

@ -7,6 +7,7 @@
<h2 class="card-title">
<Icon name="mdi-package-variant" />
{{ item.name }}
<Icon v-if="item.archived" class="ml-auto" name="mdi-archive-outline" />
</h2>
<p>{{ description }}</p>
<div class="flex gap-2 flex-wrap justify-end">

View file

@ -0,0 +1,3 @@
<template>
<div class="grow-1 max-w-full"></div>
</template>

View file

@ -10,13 +10,19 @@ export function useItemSearch(client: UserClient, opts?: SearchOptions) {
const locations = ref<LocationSummary[]>([]);
const labels = ref<LabelSummary[]>([]);
const results = ref<ItemSummary[]>([]);
const includeArchived = ref(false);
watchDebounced(query, search, { debounce: 250, maxWait: 1000 });
async function search() {
const locIds = locations.value.map(l => l.id);
const labelIds = labels.value.map(l => l.id);
const { data, error } = await client.items.getAll({ q: query.value, locations: locIds, labels: labelIds });
const { data, error } = await client.items.getAll({
q: query.value,
locations: locIds,
labels: labelIds,
includeArchived: includeArchived.value,
});
if (error) {
return;
}

View file

@ -11,6 +11,7 @@ import {
import { AttachmentTypes, PaginationResult } from "../types/non-generated";
export type ItemsQuery = {
includeArchived?: boolean;
page?: number;
pageSize?: number;
locations?: string[];

View file

@ -63,6 +63,7 @@ export interface ItemField {
}
export interface ItemOut {
archived: boolean;
attachments: ItemAttachment[];
children: ItemSummary[];
createdAt: Date;
@ -107,6 +108,7 @@ export interface ItemOut {
}
export interface ItemSummary {
archived: boolean;
createdAt: Date;
description: string;
id: string;
@ -121,6 +123,7 @@ export interface ItemSummary {
}
export interface ItemUpdate {
archived: boolean;
description: string;
fields: ItemField[];
id: string;

View file

@ -115,6 +115,11 @@
label: "Insured",
ref: "insured",
},
{
type: "checkbox",
label: "Archived",
ref: "archived",
},
];
const purchaseFields: FormField[] = [

View file

@ -1,5 +1,5 @@
<script setup lang="ts">
import { Detail, Details } from "~~/components/global/DetailsSection/types";
import { CustomDetail, Detail, Details } from "~~/components/global/DetailsSection/types";
import { ItemAttachment } from "~~/lib/api/types/data-contracts";
definePageMeta({
@ -70,7 +70,7 @@
);
});
const itemDetails = computed(() => {
const itemDetails = computed<Details>(() => {
return [
{
name: "Description",
@ -107,11 +107,11 @@
const url = maybeUrl(field.textValue);
if (url.isUrl) {
return {
type: "link",
name: field.name,
text: url.text,
type: "link",
href: url.url,
};
} as CustomDetail;
}
return {

View file

@ -23,7 +23,12 @@
const locations = selectedLocations.value.map(l => l.id);
const labels = selectedLabels.value.map(l => l.id);
const { data, error } = await api.items.getAll({ q: query.value, locations, labels });
const { data, error } = await api.items.getAll({
q: query.value,
locations,
labels,
includeArchived: includeArchived.value,
});
if (error) {
loading.value = false;
return;
@ -46,6 +51,7 @@
const advanced = ref(false);
const selectedLocations = ref([]);
const selectedLabels = ref([]);
const includeArchived = ref(false);
watchEffect(() => {
if (!advanced.value) {
@ -57,6 +63,7 @@
watchDebounced(query, search, { debounce: 250, maxWait: 1000 });
watchDebounced(selectedLocations, search, { debounce: 250, maxWait: 1000 });
watchDebounced(selectedLabels, search, { debounce: 250, maxWait: 1000 });
watch(includeArchived, search);
</script>
<template>
@ -77,6 +84,13 @@
<div class="px-4 pb-4">
<FormMultiselect v-model="selectedLabels" label="Labels" :items="labels ?? []" />
<FormMultiselect v-model="selectedLocations" label="Locations" :items="locations ?? []" />
<div class="flex pb-2 pt-5">
<label class="label cursor-pointer mr-auto">
<input v-model="includeArchived" type="checkbox" class="toggle toggle-primary" />
<span class="label-text ml-4"> Include Archived Items </span>
</label>
<Spacer />
</div>
</div>
</BaseCard>
<section class="mt-10">