From 025521431efadb516482e5a29aa99e6f1392c578 Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Sun, 26 Feb 2023 18:42:23 -0900 Subject: [PATCH] feat: add scheduled maintenance tasks (#320) * add scheduled maintenance tasks * fix failing typecheck --- .../api/handlers/v1/v1_ctrl_maint_entry.go | 10 +- backend/app/api/static/docs/docs.go | 33 +++-- backend/app/api/static/docs/swagger.json | 33 +++-- backend/app/api/static/docs/swagger.yaml | 24 +++- backend/internal/data/ent/maintenanceentry.go | 13 +- .../ent/maintenanceentry/maintenanceentry.go | 5 +- .../data/ent/maintenanceentry/where.go | 65 +++++++++ .../data/ent/maintenanceentry_create.go | 25 +++- .../data/ent/maintenanceentry_update.go | 70 ++++++++++ backend/internal/data/ent/migrate/schema.go | 5 +- backend/internal/data/ent/mutation.go | 126 +++++++++++++++--- backend/internal/data/ent/runtime.go | 10 +- .../data/ent/schema/maintenance_entry.go | 6 +- .../20230227024134_add_scheduled_date.sql | 12 ++ .../data/migrations/migrations/atlas.sum | 3 +- .../data/repo/repo_maintenance_entry.go | 81 +++++++---- .../data/repo/repo_maintenance_entry_test.go | 13 +- frontend/components/Form/DatePicker.vue | 10 +- frontend/composables/utils.ts | 4 + frontend/lib/api/__test__/user/items.test.ts | 3 +- frontend/lib/api/classes/items.ts | 9 +- frontend/lib/api/types/data-contracts.ts | 15 ++- frontend/pages/item/[id]/index.vue | 4 +- .../[id]/index/{log.vue => maintenance.vue} | 61 +++++++-- scripts/process-types/main.go | 2 + 25 files changed, 521 insertions(+), 121 deletions(-) create mode 100644 backend/internal/data/migrations/migrations/20230227024134_add_scheduled_date.sql rename frontend/pages/item/[id]/index/{log.vue => maintenance.vue} (76%) diff --git a/backend/app/api/handlers/v1/v1_ctrl_maint_entry.go b/backend/app/api/handlers/v1/v1_ctrl_maint_entry.go index 3f3f1a17..2a09f078 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_maint_entry.go +++ b/backend/app/api/handlers/v1/v1_ctrl_maint_entry.go @@ -2,6 +2,7 @@ package v1 import ( "net/http" + "strconv" "github.com/hay-kot/homebox/backend/internal/core/services" "github.com/hay-kot/homebox/backend/internal/data/repo" @@ -66,7 +67,14 @@ func (ctrl *V1Controller) handleMaintenanceLog() server.HandlerFunc { switch r.Method { case http.MethodGet: - mlog, err := ctrl.repo.MaintEntry.GetLog(ctx, itemID) + completed, _ := strconv.ParseBool(r.URL.Query().Get("completed")) + scheduled, _ := strconv.ParseBool(r.URL.Query().Get("scheduled")) + query := repo.MaintenanceLogQuery{ + Completed: completed, + Scheduled: scheduled, + } + + mlog, err := ctrl.repo.MaintEntry.GetLog(ctx, itemID, query) if err != nil { log.Err(err).Msg("failed to get items") return validate.NewRequestError(err, http.StatusInternalServerError) diff --git a/backend/app/api/static/docs/docs.go b/backend/app/api/static/docs/docs.go index 77def9d9..a5a117eb 100644 --- a/backend/app/api/static/docs/docs.go +++ b/backend/app/api/static/docs/docs.go @@ -2204,13 +2204,14 @@ const docTemplate = `{ "repo.MaintenanceEntry": { "type": "object", "properties": { + "completedDate": { + "description": "Sold", + "type": "string" + }, "cost": { "type": "string", "example": "0" }, - "date": { - "type": "string" - }, "description": { "type": "string" }, @@ -2219,42 +2220,56 @@ const docTemplate = `{ }, "name": { "type": "string" + }, + "scheduledDate": { + "description": "Sold", + "type": "string" } } }, "repo.MaintenanceEntryCreate": { "type": "object", "properties": { + "completedDate": { + "description": "Sold", + "type": "string" + }, "cost": { "type": "string", "example": "0" }, - "date": { - "type": "string" - }, "description": { "type": "string" }, "name": { "type": "string" + }, + "scheduledDate": { + "description": "Sold", + "type": "string" } } }, "repo.MaintenanceEntryUpdate": { "type": "object", "properties": { + "completedDate": { + "description": "Sold", + "type": "string" + }, "cost": { "type": "string", "example": "0" }, - "date": { - "type": "string" - }, "description": { "type": "string" }, "name": { "type": "string" + }, + "scheduledDate": { + "description": "Sold", + "type": "string" } } }, diff --git a/backend/app/api/static/docs/swagger.json b/backend/app/api/static/docs/swagger.json index 08dbb4e8..6ca48a05 100644 --- a/backend/app/api/static/docs/swagger.json +++ b/backend/app/api/static/docs/swagger.json @@ -2196,13 +2196,14 @@ "repo.MaintenanceEntry": { "type": "object", "properties": { + "completedDate": { + "description": "Sold", + "type": "string" + }, "cost": { "type": "string", "example": "0" }, - "date": { - "type": "string" - }, "description": { "type": "string" }, @@ -2211,42 +2212,56 @@ }, "name": { "type": "string" + }, + "scheduledDate": { + "description": "Sold", + "type": "string" } } }, "repo.MaintenanceEntryCreate": { "type": "object", "properties": { + "completedDate": { + "description": "Sold", + "type": "string" + }, "cost": { "type": "string", "example": "0" }, - "date": { - "type": "string" - }, "description": { "type": "string" }, "name": { "type": "string" + }, + "scheduledDate": { + "description": "Sold", + "type": "string" } } }, "repo.MaintenanceEntryUpdate": { "type": "object", "properties": { + "completedDate": { + "description": "Sold", + "type": "string" + }, "cost": { "type": "string", "example": "0" }, - "date": { - "type": "string" - }, "description": { "type": "string" }, "name": { "type": "string" + }, + "scheduledDate": { + "description": "Sold", + "type": "string" } } }, diff --git a/backend/app/api/static/docs/swagger.yaml b/backend/app/api/static/docs/swagger.yaml index 9c97791f..e7f4dc04 100644 --- a/backend/app/api/static/docs/swagger.yaml +++ b/backend/app/api/static/docs/swagger.yaml @@ -390,41 +390,53 @@ definitions: type: object repo.MaintenanceEntry: properties: + completedDate: + description: Sold + type: string cost: example: "0" type: string - date: - type: string description: type: string id: type: string name: type: string + scheduledDate: + description: Sold + type: string type: object repo.MaintenanceEntryCreate: properties: + completedDate: + description: Sold + type: string cost: example: "0" type: string - date: - type: string description: type: string name: type: string + scheduledDate: + description: Sold + type: string type: object repo.MaintenanceEntryUpdate: properties: + completedDate: + description: Sold + type: string cost: example: "0" type: string - date: - type: string description: type: string name: type: string + scheduledDate: + description: Sold + type: string type: object repo.MaintenanceLog: properties: diff --git a/backend/internal/data/ent/maintenanceentry.go b/backend/internal/data/ent/maintenanceentry.go index b571bcef..98301c88 100644 --- a/backend/internal/data/ent/maintenanceentry.go +++ b/backend/internal/data/ent/maintenanceentry.go @@ -26,6 +26,8 @@ type MaintenanceEntry struct { ItemID uuid.UUID `json:"item_id,omitempty"` // Date holds the value of the "date" field. Date time.Time `json:"date,omitempty"` + // ScheduledDate holds the value of the "scheduled_date" field. + ScheduledDate time.Time `json:"scheduled_date,omitempty"` // Name holds the value of the "name" field. Name string `json:"name,omitempty"` // Description holds the value of the "description" field. @@ -68,7 +70,7 @@ func (*MaintenanceEntry) scanValues(columns []string) ([]any, error) { values[i] = new(sql.NullFloat64) case maintenanceentry.FieldName, maintenanceentry.FieldDescription: values[i] = new(sql.NullString) - case maintenanceentry.FieldCreatedAt, maintenanceentry.FieldUpdatedAt, maintenanceentry.FieldDate: + case maintenanceentry.FieldCreatedAt, maintenanceentry.FieldUpdatedAt, maintenanceentry.FieldDate, maintenanceentry.FieldScheduledDate: values[i] = new(sql.NullTime) case maintenanceentry.FieldID, maintenanceentry.FieldItemID: values[i] = new(uuid.UUID) @@ -117,6 +119,12 @@ func (me *MaintenanceEntry) assignValues(columns []string, values []any) error { } else if value.Valid { me.Date = value.Time } + case maintenanceentry.FieldScheduledDate: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field scheduled_date", values[i]) + } else if value.Valid { + me.ScheduledDate = value.Time + } case maintenanceentry.FieldName: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field name", values[i]) @@ -180,6 +188,9 @@ func (me *MaintenanceEntry) String() string { builder.WriteString("date=") builder.WriteString(me.Date.Format(time.ANSIC)) builder.WriteString(", ") + builder.WriteString("scheduled_date=") + builder.WriteString(me.ScheduledDate.Format(time.ANSIC)) + builder.WriteString(", ") builder.WriteString("name=") builder.WriteString(me.Name) builder.WriteString(", ") diff --git a/backend/internal/data/ent/maintenanceentry/maintenanceentry.go b/backend/internal/data/ent/maintenanceentry/maintenanceentry.go index c1dcffce..690a6960 100644 --- a/backend/internal/data/ent/maintenanceentry/maintenanceentry.go +++ b/backend/internal/data/ent/maintenanceentry/maintenanceentry.go @@ -21,6 +21,8 @@ const ( FieldItemID = "item_id" // FieldDate holds the string denoting the date field in the database. FieldDate = "date" + // FieldScheduledDate holds the string denoting the scheduled_date field in the database. + FieldScheduledDate = "scheduled_date" // FieldName holds the string denoting the name field in the database. FieldName = "name" // FieldDescription holds the string denoting the description field in the database. @@ -47,6 +49,7 @@ var Columns = []string{ FieldUpdatedAt, FieldItemID, FieldDate, + FieldScheduledDate, FieldName, FieldDescription, FieldCost, @@ -69,8 +72,6 @@ var ( DefaultUpdatedAt func() time.Time // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. UpdateDefaultUpdatedAt func() time.Time - // DefaultDate holds the default value on creation for the "date" field. - DefaultDate func() time.Time // NameValidator is a validator for the "name" field. It is called by the builders before save. NameValidator func(string) error // DescriptionValidator is a validator for the "description" field. It is called by the builders before save. diff --git a/backend/internal/data/ent/maintenanceentry/where.go b/backend/internal/data/ent/maintenanceentry/where.go index 6a88550d..798ce633 100644 --- a/backend/internal/data/ent/maintenanceentry/where.go +++ b/backend/internal/data/ent/maintenanceentry/where.go @@ -76,6 +76,11 @@ func Date(v time.Time) predicate.MaintenanceEntry { return predicate.MaintenanceEntry(sql.FieldEQ(FieldDate, v)) } +// ScheduledDate applies equality check predicate on the "scheduled_date" field. It's identical to ScheduledDateEQ. +func ScheduledDate(v time.Time) predicate.MaintenanceEntry { + return predicate.MaintenanceEntry(sql.FieldEQ(FieldScheduledDate, v)) +} + // Name applies equality check predicate on the "name" field. It's identical to NameEQ. func Name(v string) predicate.MaintenanceEntry { return predicate.MaintenanceEntry(sql.FieldEQ(FieldName, v)) @@ -231,6 +236,66 @@ func DateLTE(v time.Time) predicate.MaintenanceEntry { return predicate.MaintenanceEntry(sql.FieldLTE(FieldDate, v)) } +// DateIsNil applies the IsNil predicate on the "date" field. +func DateIsNil() predicate.MaintenanceEntry { + return predicate.MaintenanceEntry(sql.FieldIsNull(FieldDate)) +} + +// DateNotNil applies the NotNil predicate on the "date" field. +func DateNotNil() predicate.MaintenanceEntry { + return predicate.MaintenanceEntry(sql.FieldNotNull(FieldDate)) +} + +// ScheduledDateEQ applies the EQ predicate on the "scheduled_date" field. +func ScheduledDateEQ(v time.Time) predicate.MaintenanceEntry { + return predicate.MaintenanceEntry(sql.FieldEQ(FieldScheduledDate, v)) +} + +// ScheduledDateNEQ applies the NEQ predicate on the "scheduled_date" field. +func ScheduledDateNEQ(v time.Time) predicate.MaintenanceEntry { + return predicate.MaintenanceEntry(sql.FieldNEQ(FieldScheduledDate, v)) +} + +// ScheduledDateIn applies the In predicate on the "scheduled_date" field. +func ScheduledDateIn(vs ...time.Time) predicate.MaintenanceEntry { + return predicate.MaintenanceEntry(sql.FieldIn(FieldScheduledDate, vs...)) +} + +// ScheduledDateNotIn applies the NotIn predicate on the "scheduled_date" field. +func ScheduledDateNotIn(vs ...time.Time) predicate.MaintenanceEntry { + return predicate.MaintenanceEntry(sql.FieldNotIn(FieldScheduledDate, vs...)) +} + +// ScheduledDateGT applies the GT predicate on the "scheduled_date" field. +func ScheduledDateGT(v time.Time) predicate.MaintenanceEntry { + return predicate.MaintenanceEntry(sql.FieldGT(FieldScheduledDate, v)) +} + +// ScheduledDateGTE applies the GTE predicate on the "scheduled_date" field. +func ScheduledDateGTE(v time.Time) predicate.MaintenanceEntry { + return predicate.MaintenanceEntry(sql.FieldGTE(FieldScheduledDate, v)) +} + +// ScheduledDateLT applies the LT predicate on the "scheduled_date" field. +func ScheduledDateLT(v time.Time) predicate.MaintenanceEntry { + return predicate.MaintenanceEntry(sql.FieldLT(FieldScheduledDate, v)) +} + +// ScheduledDateLTE applies the LTE predicate on the "scheduled_date" field. +func ScheduledDateLTE(v time.Time) predicate.MaintenanceEntry { + return predicate.MaintenanceEntry(sql.FieldLTE(FieldScheduledDate, v)) +} + +// ScheduledDateIsNil applies the IsNil predicate on the "scheduled_date" field. +func ScheduledDateIsNil() predicate.MaintenanceEntry { + return predicate.MaintenanceEntry(sql.FieldIsNull(FieldScheduledDate)) +} + +// ScheduledDateNotNil applies the NotNil predicate on the "scheduled_date" field. +func ScheduledDateNotNil() predicate.MaintenanceEntry { + return predicate.MaintenanceEntry(sql.FieldNotNull(FieldScheduledDate)) +} + // NameEQ applies the EQ predicate on the "name" field. func NameEQ(v string) predicate.MaintenanceEntry { return predicate.MaintenanceEntry(sql.FieldEQ(FieldName, v)) diff --git a/backend/internal/data/ent/maintenanceentry_create.go b/backend/internal/data/ent/maintenanceentry_create.go index ef5602ba..d06195e8 100644 --- a/backend/internal/data/ent/maintenanceentry_create.go +++ b/backend/internal/data/ent/maintenanceentry_create.go @@ -70,6 +70,20 @@ func (mec *MaintenanceEntryCreate) SetNillableDate(t *time.Time) *MaintenanceEnt return mec } +// SetScheduledDate sets the "scheduled_date" field. +func (mec *MaintenanceEntryCreate) SetScheduledDate(t time.Time) *MaintenanceEntryCreate { + mec.mutation.SetScheduledDate(t) + return mec +} + +// SetNillableScheduledDate sets the "scheduled_date" field if the given value is not nil. +func (mec *MaintenanceEntryCreate) SetNillableScheduledDate(t *time.Time) *MaintenanceEntryCreate { + if t != nil { + mec.SetScheduledDate(*t) + } + return mec +} + // SetName sets the "name" field. func (mec *MaintenanceEntryCreate) SetName(s string) *MaintenanceEntryCreate { mec.mutation.SetName(s) @@ -166,10 +180,6 @@ func (mec *MaintenanceEntryCreate) defaults() { v := maintenanceentry.DefaultUpdatedAt() mec.mutation.SetUpdatedAt(v) } - if _, ok := mec.mutation.Date(); !ok { - v := maintenanceentry.DefaultDate() - mec.mutation.SetDate(v) - } if _, ok := mec.mutation.Cost(); !ok { v := maintenanceentry.DefaultCost mec.mutation.SetCost(v) @@ -191,9 +201,6 @@ func (mec *MaintenanceEntryCreate) check() error { if _, ok := mec.mutation.ItemID(); !ok { return &ValidationError{Name: "item_id", err: errors.New(`ent: missing required field "MaintenanceEntry.item_id"`)} } - if _, ok := mec.mutation.Date(); !ok { - return &ValidationError{Name: "date", err: errors.New(`ent: missing required field "MaintenanceEntry.date"`)} - } if _, ok := mec.mutation.Name(); !ok { return &ValidationError{Name: "name", err: errors.New(`ent: missing required field "MaintenanceEntry.name"`)} } @@ -260,6 +267,10 @@ func (mec *MaintenanceEntryCreate) createSpec() (*MaintenanceEntry, *sqlgraph.Cr _spec.SetField(maintenanceentry.FieldDate, field.TypeTime, value) _node.Date = value } + if value, ok := mec.mutation.ScheduledDate(); ok { + _spec.SetField(maintenanceentry.FieldScheduledDate, field.TypeTime, value) + _node.ScheduledDate = value + } if value, ok := mec.mutation.Name(); ok { _spec.SetField(maintenanceentry.FieldName, field.TypeString, value) _node.Name = value diff --git a/backend/internal/data/ent/maintenanceentry_update.go b/backend/internal/data/ent/maintenanceentry_update.go index cade06ba..8881e589 100644 --- a/backend/internal/data/ent/maintenanceentry_update.go +++ b/backend/internal/data/ent/maintenanceentry_update.go @@ -56,6 +56,32 @@ func (meu *MaintenanceEntryUpdate) SetNillableDate(t *time.Time) *MaintenanceEnt return meu } +// ClearDate clears the value of the "date" field. +func (meu *MaintenanceEntryUpdate) ClearDate() *MaintenanceEntryUpdate { + meu.mutation.ClearDate() + return meu +} + +// SetScheduledDate sets the "scheduled_date" field. +func (meu *MaintenanceEntryUpdate) SetScheduledDate(t time.Time) *MaintenanceEntryUpdate { + meu.mutation.SetScheduledDate(t) + return meu +} + +// SetNillableScheduledDate sets the "scheduled_date" field if the given value is not nil. +func (meu *MaintenanceEntryUpdate) SetNillableScheduledDate(t *time.Time) *MaintenanceEntryUpdate { + if t != nil { + meu.SetScheduledDate(*t) + } + return meu +} + +// ClearScheduledDate clears the value of the "scheduled_date" field. +func (meu *MaintenanceEntryUpdate) ClearScheduledDate() *MaintenanceEntryUpdate { + meu.mutation.ClearScheduledDate() + return meu +} + // SetName sets the "name" field. func (meu *MaintenanceEntryUpdate) SetName(s string) *MaintenanceEntryUpdate { meu.mutation.SetName(s) @@ -191,6 +217,15 @@ func (meu *MaintenanceEntryUpdate) sqlSave(ctx context.Context) (n int, err erro if value, ok := meu.mutation.Date(); ok { _spec.SetField(maintenanceentry.FieldDate, field.TypeTime, value) } + if meu.mutation.DateCleared() { + _spec.ClearField(maintenanceentry.FieldDate, field.TypeTime) + } + if value, ok := meu.mutation.ScheduledDate(); ok { + _spec.SetField(maintenanceentry.FieldScheduledDate, field.TypeTime, value) + } + if meu.mutation.ScheduledDateCleared() { + _spec.ClearField(maintenanceentry.FieldScheduledDate, field.TypeTime) + } if value, ok := meu.mutation.Name(); ok { _spec.SetField(maintenanceentry.FieldName, field.TypeString, value) } @@ -287,6 +322,32 @@ func (meuo *MaintenanceEntryUpdateOne) SetNillableDate(t *time.Time) *Maintenanc return meuo } +// ClearDate clears the value of the "date" field. +func (meuo *MaintenanceEntryUpdateOne) ClearDate() *MaintenanceEntryUpdateOne { + meuo.mutation.ClearDate() + return meuo +} + +// SetScheduledDate sets the "scheduled_date" field. +func (meuo *MaintenanceEntryUpdateOne) SetScheduledDate(t time.Time) *MaintenanceEntryUpdateOne { + meuo.mutation.SetScheduledDate(t) + return meuo +} + +// SetNillableScheduledDate sets the "scheduled_date" field if the given value is not nil. +func (meuo *MaintenanceEntryUpdateOne) SetNillableScheduledDate(t *time.Time) *MaintenanceEntryUpdateOne { + if t != nil { + meuo.SetScheduledDate(*t) + } + return meuo +} + +// ClearScheduledDate clears the value of the "scheduled_date" field. +func (meuo *MaintenanceEntryUpdateOne) ClearScheduledDate() *MaintenanceEntryUpdateOne { + meuo.mutation.ClearScheduledDate() + return meuo +} + // SetName sets the "name" field. func (meuo *MaintenanceEntryUpdateOne) SetName(s string) *MaintenanceEntryUpdateOne { meuo.mutation.SetName(s) @@ -452,6 +513,15 @@ func (meuo *MaintenanceEntryUpdateOne) sqlSave(ctx context.Context) (_node *Main if value, ok := meuo.mutation.Date(); ok { _spec.SetField(maintenanceentry.FieldDate, field.TypeTime, value) } + if meuo.mutation.DateCleared() { + _spec.ClearField(maintenanceentry.FieldDate, field.TypeTime) + } + if value, ok := meuo.mutation.ScheduledDate(); ok { + _spec.SetField(maintenanceentry.FieldScheduledDate, field.TypeTime, value) + } + if meuo.mutation.ScheduledDateCleared() { + _spec.ClearField(maintenanceentry.FieldScheduledDate, field.TypeTime) + } if value, ok := meuo.mutation.Name(); ok { _spec.SetField(maintenanceentry.FieldName, field.TypeString, value) } diff --git a/backend/internal/data/ent/migrate/schema.go b/backend/internal/data/ent/migrate/schema.go index df67cbf7..9cfefa99 100644 --- a/backend/internal/data/ent/migrate/schema.go +++ b/backend/internal/data/ent/migrate/schema.go @@ -323,7 +323,8 @@ var ( {Name: "id", Type: field.TypeUUID}, {Name: "created_at", Type: field.TypeTime}, {Name: "updated_at", Type: field.TypeTime}, - {Name: "date", Type: field.TypeTime}, + {Name: "date", Type: field.TypeTime, Nullable: true}, + {Name: "scheduled_date", Type: field.TypeTime, Nullable: true}, {Name: "name", Type: field.TypeString, Size: 255}, {Name: "description", Type: field.TypeString, Nullable: true, Size: 2500}, {Name: "cost", Type: field.TypeFloat64, Default: 0}, @@ -337,7 +338,7 @@ var ( ForeignKeys: []*schema.ForeignKey{ { Symbol: "maintenance_entries_items_maintenance_entries", - Columns: []*schema.Column{MaintenanceEntriesColumns[7]}, + Columns: []*schema.Column{MaintenanceEntriesColumns[8]}, RefColumns: []*schema.Column{ItemsColumns[0]}, OnDelete: schema.Cascade, }, diff --git a/backend/internal/data/ent/mutation.go b/backend/internal/data/ent/mutation.go index 164071e5..89a3a717 100644 --- a/backend/internal/data/ent/mutation.go +++ b/backend/internal/data/ent/mutation.go @@ -8918,22 +8918,23 @@ func (m *LocationMutation) ResetEdge(name string) error { // MaintenanceEntryMutation represents an operation that mutates the MaintenanceEntry nodes in the graph. type MaintenanceEntryMutation struct { config - op Op - typ string - id *uuid.UUID - created_at *time.Time - updated_at *time.Time - date *time.Time - name *string - description *string - cost *float64 - addcost *float64 - clearedFields map[string]struct{} - item *uuid.UUID - cleareditem bool - done bool - oldValue func(context.Context) (*MaintenanceEntry, error) - predicates []predicate.MaintenanceEntry + op Op + typ string + id *uuid.UUID + created_at *time.Time + updated_at *time.Time + date *time.Time + scheduled_date *time.Time + name *string + description *string + cost *float64 + addcost *float64 + clearedFields map[string]struct{} + item *uuid.UUID + cleareditem bool + done bool + oldValue func(context.Context) (*MaintenanceEntry, error) + predicates []predicate.MaintenanceEntry } var _ ent.Mutation = (*MaintenanceEntryMutation)(nil) @@ -9179,9 +9180,71 @@ func (m *MaintenanceEntryMutation) OldDate(ctx context.Context) (v time.Time, er return oldValue.Date, nil } +// ClearDate clears the value of the "date" field. +func (m *MaintenanceEntryMutation) ClearDate() { + m.date = nil + m.clearedFields[maintenanceentry.FieldDate] = struct{}{} +} + +// DateCleared returns if the "date" field was cleared in this mutation. +func (m *MaintenanceEntryMutation) DateCleared() bool { + _, ok := m.clearedFields[maintenanceentry.FieldDate] + return ok +} + // ResetDate resets all changes to the "date" field. func (m *MaintenanceEntryMutation) ResetDate() { m.date = nil + delete(m.clearedFields, maintenanceentry.FieldDate) +} + +// SetScheduledDate sets the "scheduled_date" field. +func (m *MaintenanceEntryMutation) SetScheduledDate(t time.Time) { + m.scheduled_date = &t +} + +// ScheduledDate returns the value of the "scheduled_date" field in the mutation. +func (m *MaintenanceEntryMutation) ScheduledDate() (r time.Time, exists bool) { + v := m.scheduled_date + if v == nil { + return + } + return *v, true +} + +// OldScheduledDate returns the old "scheduled_date" field's value of the MaintenanceEntry entity. +// If the MaintenanceEntry 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 *MaintenanceEntryMutation) OldScheduledDate(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldScheduledDate is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldScheduledDate requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldScheduledDate: %w", err) + } + return oldValue.ScheduledDate, nil +} + +// ClearScheduledDate clears the value of the "scheduled_date" field. +func (m *MaintenanceEntryMutation) ClearScheduledDate() { + m.scheduled_date = nil + m.clearedFields[maintenanceentry.FieldScheduledDate] = struct{}{} +} + +// ScheduledDateCleared returns if the "scheduled_date" field was cleared in this mutation. +func (m *MaintenanceEntryMutation) ScheduledDateCleared() bool { + _, ok := m.clearedFields[maintenanceentry.FieldScheduledDate] + return ok +} + +// ResetScheduledDate resets all changes to the "scheduled_date" field. +func (m *MaintenanceEntryMutation) ResetScheduledDate() { + m.scheduled_date = nil + delete(m.clearedFields, maintenanceentry.FieldScheduledDate) } // SetName sets the "name" field. @@ -9385,7 +9448,7 @@ func (m *MaintenanceEntryMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *MaintenanceEntryMutation) Fields() []string { - fields := make([]string, 0, 7) + fields := make([]string, 0, 8) if m.created_at != nil { fields = append(fields, maintenanceentry.FieldCreatedAt) } @@ -9398,6 +9461,9 @@ func (m *MaintenanceEntryMutation) Fields() []string { if m.date != nil { fields = append(fields, maintenanceentry.FieldDate) } + if m.scheduled_date != nil { + fields = append(fields, maintenanceentry.FieldScheduledDate) + } if m.name != nil { fields = append(fields, maintenanceentry.FieldName) } @@ -9423,6 +9489,8 @@ func (m *MaintenanceEntryMutation) Field(name string) (ent.Value, bool) { return m.ItemID() case maintenanceentry.FieldDate: return m.Date() + case maintenanceentry.FieldScheduledDate: + return m.ScheduledDate() case maintenanceentry.FieldName: return m.Name() case maintenanceentry.FieldDescription: @@ -9446,6 +9514,8 @@ func (m *MaintenanceEntryMutation) OldField(ctx context.Context, name string) (e return m.OldItemID(ctx) case maintenanceentry.FieldDate: return m.OldDate(ctx) + case maintenanceentry.FieldScheduledDate: + return m.OldScheduledDate(ctx) case maintenanceentry.FieldName: return m.OldName(ctx) case maintenanceentry.FieldDescription: @@ -9489,6 +9559,13 @@ func (m *MaintenanceEntryMutation) SetField(name string, value ent.Value) error } m.SetDate(v) return nil + case maintenanceentry.FieldScheduledDate: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetScheduledDate(v) + return nil case maintenanceentry.FieldName: v, ok := value.(string) if !ok { @@ -9555,6 +9632,12 @@ func (m *MaintenanceEntryMutation) AddField(name string, value ent.Value) error // mutation. func (m *MaintenanceEntryMutation) ClearedFields() []string { var fields []string + if m.FieldCleared(maintenanceentry.FieldDate) { + fields = append(fields, maintenanceentry.FieldDate) + } + if m.FieldCleared(maintenanceentry.FieldScheduledDate) { + fields = append(fields, maintenanceentry.FieldScheduledDate) + } if m.FieldCleared(maintenanceentry.FieldDescription) { fields = append(fields, maintenanceentry.FieldDescription) } @@ -9572,6 +9655,12 @@ func (m *MaintenanceEntryMutation) FieldCleared(name string) bool { // error if the field is not defined in the schema. func (m *MaintenanceEntryMutation) ClearField(name string) error { switch name { + case maintenanceentry.FieldDate: + m.ClearDate() + return nil + case maintenanceentry.FieldScheduledDate: + m.ClearScheduledDate() + return nil case maintenanceentry.FieldDescription: m.ClearDescription() return nil @@ -9595,6 +9684,9 @@ func (m *MaintenanceEntryMutation) ResetField(name string) error { case maintenanceentry.FieldDate: m.ResetDate() return nil + case maintenanceentry.FieldScheduledDate: + m.ResetScheduledDate() + return nil case maintenanceentry.FieldName: m.ResetName() return nil diff --git a/backend/internal/data/ent/runtime.go b/backend/internal/data/ent/runtime.go index 4ce9d2c1..5cbd076a 100644 --- a/backend/internal/data/ent/runtime.go +++ b/backend/internal/data/ent/runtime.go @@ -446,12 +446,8 @@ func init() { maintenanceentry.DefaultUpdatedAt = maintenanceentryDescUpdatedAt.Default.(func() time.Time) // maintenanceentry.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. maintenanceentry.UpdateDefaultUpdatedAt = maintenanceentryDescUpdatedAt.UpdateDefault.(func() time.Time) - // maintenanceentryDescDate is the schema descriptor for date field. - maintenanceentryDescDate := maintenanceentryFields[1].Descriptor() - // maintenanceentry.DefaultDate holds the default value on creation for the date field. - maintenanceentry.DefaultDate = maintenanceentryDescDate.Default.(func() time.Time) // maintenanceentryDescName is the schema descriptor for name field. - maintenanceentryDescName := maintenanceentryFields[2].Descriptor() + maintenanceentryDescName := maintenanceentryFields[3].Descriptor() // maintenanceentry.NameValidator is a validator for the "name" field. It is called by the builders before save. maintenanceentry.NameValidator = func() func(string) error { validators := maintenanceentryDescName.Validators @@ -469,11 +465,11 @@ func init() { } }() // maintenanceentryDescDescription is the schema descriptor for description field. - maintenanceentryDescDescription := maintenanceentryFields[3].Descriptor() + maintenanceentryDescDescription := maintenanceentryFields[4].Descriptor() // maintenanceentry.DescriptionValidator is a validator for the "description" field. It is called by the builders before save. maintenanceentry.DescriptionValidator = maintenanceentryDescDescription.Validators[0].(func(string) error) // maintenanceentryDescCost is the schema descriptor for cost field. - maintenanceentryDescCost := maintenanceentryFields[4].Descriptor() + maintenanceentryDescCost := maintenanceentryFields[5].Descriptor() // maintenanceentry.DefaultCost holds the default value on creation for the cost field. maintenanceentry.DefaultCost = maintenanceentryDescCost.Default.(float64) // maintenanceentryDescID is the schema descriptor for id field. diff --git a/backend/internal/data/ent/schema/maintenance_entry.go b/backend/internal/data/ent/schema/maintenance_entry.go index 7fd96431..1c623cf0 100644 --- a/backend/internal/data/ent/schema/maintenance_entry.go +++ b/backend/internal/data/ent/schema/maintenance_entry.go @@ -1,8 +1,6 @@ package schema import ( - "time" - "entgo.io/ent" "entgo.io/ent/schema/edge" "entgo.io/ent/schema/field" @@ -24,7 +22,9 @@ func (MaintenanceEntry) Fields() []ent.Field { return []ent.Field{ field.UUID("item_id", uuid.UUID{}), field.Time("date"). - Default(time.Now), + Optional(), + field.Time("scheduled_date"). + Optional(), field.String("name"). MaxLen(255). NotEmpty(), diff --git a/backend/internal/data/migrations/migrations/20230227024134_add_scheduled_date.sql b/backend/internal/data/migrations/migrations/20230227024134_add_scheduled_date.sql new file mode 100644 index 00000000..a43ecfb0 --- /dev/null +++ b/backend/internal/data/migrations/migrations/20230227024134_add_scheduled_date.sql @@ -0,0 +1,12 @@ +-- disable the enforcement of foreign-keys constraints +PRAGMA foreign_keys = off; +-- create "new_maintenance_entries" table +CREATE TABLE `new_maintenance_entries` (`id` uuid NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, `date` datetime NULL, `scheduled_date` datetime NULL, `name` text NOT NULL, `description` text NULL, `cost` real NOT NULL DEFAULT 0, `item_id` uuid NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `maintenance_entries_items_maintenance_entries` FOREIGN KEY (`item_id`) REFERENCES `items` (`id`) ON DELETE CASCADE); +-- copy rows from old table "maintenance_entries" to new temporary table "new_maintenance_entries" +INSERT INTO `new_maintenance_entries` (`id`, `created_at`, `updated_at`, `date`, `name`, `description`, `cost`, `item_id`) SELECT `id`, `created_at`, `updated_at`, `date`, `name`, `description`, `cost`, `item_id` FROM `maintenance_entries`; +-- drop "maintenance_entries" table after copying rows +DROP TABLE `maintenance_entries`; +-- rename temporary table "new_maintenance_entries" to "maintenance_entries" +ALTER TABLE `new_maintenance_entries` RENAME TO `maintenance_entries`; +-- enable back the enforcement of foreign-keys constraints +PRAGMA foreign_keys = on; diff --git a/backend/internal/data/migrations/migrations/atlas.sum b/backend/internal/data/migrations/migrations/atlas.sum index 5de79cc4..eb05def7 100644 --- a/backend/internal/data/migrations/migrations/atlas.sum +++ b/backend/internal/data/migrations/migrations/atlas.sum @@ -1,4 +1,4 @@ -h1:dn3XsqwgjCxEtpLXmHlt2ALRwg2cZB6m8lg2faxeLXM= +h1:o94ZiQarQV54hzXXKoOUNL/DvHYieveswCLwJaUMGPo= 20220929052825_init.sql h1:ZlCqm1wzjDmofeAcSX3jE4h4VcdTNGpRg2eabztDy9Q= 20221001210956_group_invitations.sql h1:YQKJFtE39wFOcRNbZQ/d+ZlHwrcfcsZlcv/pLEYdpjw= 20221009173029_add_user_roles.sql h1:vWmzAfgEWQeGk0Vn70zfVPCcfEZth3E0JcvyKTjpYyU= @@ -9,3 +9,4 @@ h1:dn3XsqwgjCxEtpLXmHlt2ALRwg2cZB6m8lg2faxeLXM= 20221205230404_drop_document_tokens.sql h1:9dCbNFcjtsT6lEhkxCn/vYaGRmQrl1LefdEJgvkfhGg= 20221205234214_add_maintenance_entries.sql h1:B56VzCuDsed1k3/sYUoKlOkP90DcdLufxFK0qYvoafU= 20221205234812_cascade_delete_roles.sql h1:VIiaImR48nCHF3uFbOYOX1E79Ta5HsUBetGaSAbh9Gk= +20230227024134_add_scheduled_date.sql h1:8qO5OBZ0AzsfYEQOAQQrYIjyhSwM+v1A+/ylLSoiyoc= diff --git a/backend/internal/data/repo/repo_maintenance_entry.go b/backend/internal/data/repo/repo_maintenance_entry.go index 5f95e503..96eae75c 100644 --- a/backend/internal/data/repo/repo_maintenance_entry.go +++ b/backend/internal/data/repo/repo_maintenance_entry.go @@ -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/maintenanceentry" + "github.com/hay-kot/homebox/backend/internal/data/types" ) // MaintenanceEntryRepository is a repository for maintenance entries that are @@ -17,25 +18,28 @@ type MaintenanceEntryRepository struct { } type ( MaintenanceEntryCreate struct { - Date time.Time `json:"date"` - Name string `json:"name"` - Description string `json:"description"` - Cost float64 `json:"cost,string"` + CompletedDate types.Date `json:"completedDate"` + ScheduledDate types.Date `json:"scheduledDate"` + Name string `json:"name"` + Description string `json:"description"` + Cost float64 `json:"cost,string"` } MaintenanceEntry struct { - ID uuid.UUID `json:"id"` - Date time.Time `json:"date"` - Name string `json:"name"` - Description string `json:"description"` - Cost float64 `json:"cost,string"` + ID uuid.UUID `json:"id"` + CompletedDate types.Date `json:"completedDate"` + ScheduledDate types.Date `json:"scheduledDate"` + Name string `json:"name"` + Description string `json:"description"` + Cost float64 `json:"cost,string"` } MaintenanceEntryUpdate struct { - Date time.Time `json:"date"` - Name string `json:"name"` - Description string `json:"description"` - Cost float64 `json:"cost,string"` + CompletedDate types.Date `json:"completedDate"` + ScheduledDate types.Date `json:"scheduledDate"` + Name string `json:"name"` + Description string `json:"description"` + Cost float64 `json:"cost,string"` } MaintenanceLog struct { @@ -53,18 +57,20 @@ var ( func mapMaintenanceEntry(entry *ent.MaintenanceEntry) MaintenanceEntry { return MaintenanceEntry{ - ID: entry.ID, - Date: entry.Date, - Name: entry.Name, - Description: entry.Description, - Cost: entry.Cost, + ID: entry.ID, + CompletedDate: types.Date(entry.Date), + ScheduledDate: types.Date(entry.ScheduledDate), + Name: entry.Name, + Description: entry.Description, + Cost: entry.Cost, } } func (r *MaintenanceEntryRepository) Create(ctx context.Context, itemID uuid.UUID, input MaintenanceEntryCreate) (MaintenanceEntry, error) { item, err := r.db.MaintenanceEntry.Create(). SetItemID(itemID). - SetDate(input.Date). + SetDate(input.CompletedDate.Time()). + SetScheduledDate(input.ScheduledDate.Time()). SetName(input.Name). SetDescription(input.Description). SetCost(input.Cost). @@ -75,7 +81,8 @@ func (r *MaintenanceEntryRepository) Create(ctx context.Context, itemID uuid.UUI func (r *MaintenanceEntryRepository) Update(ctx context.Context, ID uuid.UUID, input MaintenanceEntryUpdate) (MaintenanceEntry, error) { item, err := r.db.MaintenanceEntry.UpdateOneID(ID). - SetDate(input.Date). + SetDate(input.CompletedDate.Time()). + SetScheduledDate(input.ScheduledDate.Time()). SetName(input.Name). SetDescription(input.Description). SetCost(input.Cost). @@ -84,14 +91,36 @@ func (r *MaintenanceEntryRepository) Update(ctx context.Context, ID uuid.UUID, i return mapMaintenanceEntryErr(item, err) } -func (r *MaintenanceEntryRepository) GetLog(ctx context.Context, itemID uuid.UUID) (MaintenanceLog, error) { +type MaintenanceLogQuery struct { + Completed bool + Scheduled bool +} + +func (r *MaintenanceEntryRepository) GetLog(ctx context.Context, itemID uuid.UUID, query MaintenanceLogQuery) (MaintenanceLog, error) { log := MaintenanceLog{ ItemID: itemID, } - entries, err := r.db.MaintenanceEntry.Query(). - Where(maintenanceentry.ItemID(itemID)). - Order(ent.Desc(maintenanceentry.FieldDate)). + q := r.db.MaintenanceEntry.Query().Where(maintenanceentry.ItemID(itemID)) + + if query.Completed { + q = q.Where(maintenanceentry.And( + maintenanceentry.DateNotNil(), + maintenanceentry.DateNEQ(time.Time{}), + )) + + } else if query.Scheduled { + q = q.Where(maintenanceentry.And( + maintenanceentry.Or( + maintenanceentry.DateIsNil(), + maintenanceentry.DateEQ(time.Time{}), + ), + maintenanceentry.ScheduledDateNotNil(), + maintenanceentry.ScheduledDateNEQ(time.Time{}), + )) + } + + entries, err := q.Order(ent.Desc(maintenanceentry.FieldDate)). All(ctx) if err != nil { return MaintenanceLog{}, err @@ -102,7 +131,7 @@ func (r *MaintenanceEntryRepository) GetLog(ctx context.Context, itemID uuid.UUI var maybeTotal *float64 var maybeAverage *float64 - q := ` + statement := ` SELECT SUM(cost_total) AS total_of_totals, AVG(cost_total) AS avg_of_averages @@ -119,7 +148,7 @@ FROM my )` - row := r.db.Sql().QueryRowContext(ctx, q, itemID) + row := r.db.Sql().QueryRowContext(ctx, statement, itemID) err = row.Scan(&maybeTotal, &maybeAverage) if err != nil { return MaintenanceLog{}, err diff --git a/backend/internal/data/repo/repo_maintenance_entry_test.go b/backend/internal/data/repo/repo_maintenance_entry_test.go index aafb08e6..e9763e78 100644 --- a/backend/internal/data/repo/repo_maintenance_entry_test.go +++ b/backend/internal/data/repo/repo_maintenance_entry_test.go @@ -5,6 +5,7 @@ import ( "testing" "time" + "github.com/hay-kot/homebox/backend/internal/data/types" "github.com/stretchr/testify/assert" ) @@ -43,10 +44,10 @@ func TestMaintenanceEntryRepository_GetLog(t *testing.T) { } created[i] = MaintenanceEntryCreate{ - Date: dt, - Name: "Maintenance", - Description: "Maintenance description", - Cost: 10, + CompletedDate: types.DateFromTime(dt), + Name: "Maintenance", + Description: "Maintenance description", + Cost: 10, } } @@ -58,7 +59,9 @@ func TestMaintenanceEntryRepository_GetLog(t *testing.T) { } // Get the log for the item - log, err := tRepos.MaintEntry.GetLog(context.Background(), item.ID) + log, err := tRepos.MaintEntry.GetLog(context.Background(), item.ID, MaintenanceLogQuery{ + Completed: true, + }) if err != nil { t.Fatalf("failed to get maintenance log: %v", err) } diff --git a/frontend/components/Form/DatePicker.vue b/frontend/components/Form/DatePicker.vue index 71d7c084..5f18bae9 100644 --- a/frontend/components/Form/DatePicker.vue +++ b/frontend/components/Form/DatePicker.vue @@ -1,13 +1,13 @@