feat: add scheduled maintenance tasks (#320)

* add scheduled maintenance tasks

* fix failing typecheck
This commit is contained in:
Hayden 2023-02-26 18:42:23 -09:00 committed by GitHub
parent 70297b9d27
commit 025521431e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 521 additions and 121 deletions

View file

@ -2,6 +2,7 @@ package v1
import ( import (
"net/http" "net/http"
"strconv"
"github.com/hay-kot/homebox/backend/internal/core/services" "github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/data/repo" "github.com/hay-kot/homebox/backend/internal/data/repo"
@ -66,7 +67,14 @@ func (ctrl *V1Controller) handleMaintenanceLog() server.HandlerFunc {
switch r.Method { switch r.Method {
case http.MethodGet: 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 { if err != nil {
log.Err(err).Msg("failed to get items") log.Err(err).Msg("failed to get items")
return validate.NewRequestError(err, http.StatusInternalServerError) return validate.NewRequestError(err, http.StatusInternalServerError)

View file

@ -2204,13 +2204,14 @@ const docTemplate = `{
"repo.MaintenanceEntry": { "repo.MaintenanceEntry": {
"type": "object", "type": "object",
"properties": { "properties": {
"completedDate": {
"description": "Sold",
"type": "string"
},
"cost": { "cost": {
"type": "string", "type": "string",
"example": "0" "example": "0"
}, },
"date": {
"type": "string"
},
"description": { "description": {
"type": "string" "type": "string"
}, },
@ -2219,42 +2220,56 @@ const docTemplate = `{
}, },
"name": { "name": {
"type": "string" "type": "string"
},
"scheduledDate": {
"description": "Sold",
"type": "string"
} }
} }
}, },
"repo.MaintenanceEntryCreate": { "repo.MaintenanceEntryCreate": {
"type": "object", "type": "object",
"properties": { "properties": {
"completedDate": {
"description": "Sold",
"type": "string"
},
"cost": { "cost": {
"type": "string", "type": "string",
"example": "0" "example": "0"
}, },
"date": {
"type": "string"
},
"description": { "description": {
"type": "string" "type": "string"
}, },
"name": { "name": {
"type": "string" "type": "string"
},
"scheduledDate": {
"description": "Sold",
"type": "string"
} }
} }
}, },
"repo.MaintenanceEntryUpdate": { "repo.MaintenanceEntryUpdate": {
"type": "object", "type": "object",
"properties": { "properties": {
"completedDate": {
"description": "Sold",
"type": "string"
},
"cost": { "cost": {
"type": "string", "type": "string",
"example": "0" "example": "0"
}, },
"date": {
"type": "string"
},
"description": { "description": {
"type": "string" "type": "string"
}, },
"name": { "name": {
"type": "string" "type": "string"
},
"scheduledDate": {
"description": "Sold",
"type": "string"
} }
} }
}, },

View file

@ -2196,13 +2196,14 @@
"repo.MaintenanceEntry": { "repo.MaintenanceEntry": {
"type": "object", "type": "object",
"properties": { "properties": {
"completedDate": {
"description": "Sold",
"type": "string"
},
"cost": { "cost": {
"type": "string", "type": "string",
"example": "0" "example": "0"
}, },
"date": {
"type": "string"
},
"description": { "description": {
"type": "string" "type": "string"
}, },
@ -2211,42 +2212,56 @@
}, },
"name": { "name": {
"type": "string" "type": "string"
},
"scheduledDate": {
"description": "Sold",
"type": "string"
} }
} }
}, },
"repo.MaintenanceEntryCreate": { "repo.MaintenanceEntryCreate": {
"type": "object", "type": "object",
"properties": { "properties": {
"completedDate": {
"description": "Sold",
"type": "string"
},
"cost": { "cost": {
"type": "string", "type": "string",
"example": "0" "example": "0"
}, },
"date": {
"type": "string"
},
"description": { "description": {
"type": "string" "type": "string"
}, },
"name": { "name": {
"type": "string" "type": "string"
},
"scheduledDate": {
"description": "Sold",
"type": "string"
} }
} }
}, },
"repo.MaintenanceEntryUpdate": { "repo.MaintenanceEntryUpdate": {
"type": "object", "type": "object",
"properties": { "properties": {
"completedDate": {
"description": "Sold",
"type": "string"
},
"cost": { "cost": {
"type": "string", "type": "string",
"example": "0" "example": "0"
}, },
"date": {
"type": "string"
},
"description": { "description": {
"type": "string" "type": "string"
}, },
"name": { "name": {
"type": "string" "type": "string"
},
"scheduledDate": {
"description": "Sold",
"type": "string"
} }
} }
}, },

View file

@ -390,41 +390,53 @@ definitions:
type: object type: object
repo.MaintenanceEntry: repo.MaintenanceEntry:
properties: properties:
completedDate:
description: Sold
type: string
cost: cost:
example: "0" example: "0"
type: string type: string
date:
type: string
description: description:
type: string type: string
id: id:
type: string type: string
name: name:
type: string type: string
scheduledDate:
description: Sold
type: string
type: object type: object
repo.MaintenanceEntryCreate: repo.MaintenanceEntryCreate:
properties: properties:
completedDate:
description: Sold
type: string
cost: cost:
example: "0" example: "0"
type: string type: string
date:
type: string
description: description:
type: string type: string
name: name:
type: string type: string
scheduledDate:
description: Sold
type: string
type: object type: object
repo.MaintenanceEntryUpdate: repo.MaintenanceEntryUpdate:
properties: properties:
completedDate:
description: Sold
type: string
cost: cost:
example: "0" example: "0"
type: string type: string
date:
type: string
description: description:
type: string type: string
name: name:
type: string type: string
scheduledDate:
description: Sold
type: string
type: object type: object
repo.MaintenanceLog: repo.MaintenanceLog:
properties: properties:

View file

@ -26,6 +26,8 @@ type MaintenanceEntry struct {
ItemID uuid.UUID `json:"item_id,omitempty"` ItemID uuid.UUID `json:"item_id,omitempty"`
// Date holds the value of the "date" field. // Date holds the value of the "date" field.
Date time.Time `json:"date,omitempty"` 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 holds the value of the "name" field.
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
// Description holds the value of the "description" field. // Description holds the value of the "description" field.
@ -68,7 +70,7 @@ func (*MaintenanceEntry) scanValues(columns []string) ([]any, error) {
values[i] = new(sql.NullFloat64) values[i] = new(sql.NullFloat64)
case maintenanceentry.FieldName, maintenanceentry.FieldDescription: case maintenanceentry.FieldName, maintenanceentry.FieldDescription:
values[i] = new(sql.NullString) 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) values[i] = new(sql.NullTime)
case maintenanceentry.FieldID, maintenanceentry.FieldItemID: case maintenanceentry.FieldID, maintenanceentry.FieldItemID:
values[i] = new(uuid.UUID) values[i] = new(uuid.UUID)
@ -117,6 +119,12 @@ func (me *MaintenanceEntry) assignValues(columns []string, values []any) error {
} else if value.Valid { } else if value.Valid {
me.Date = value.Time 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: case maintenanceentry.FieldName:
if value, ok := values[i].(*sql.NullString); !ok { if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field name", values[i]) 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("date=")
builder.WriteString(me.Date.Format(time.ANSIC)) builder.WriteString(me.Date.Format(time.ANSIC))
builder.WriteString(", ") builder.WriteString(", ")
builder.WriteString("scheduled_date=")
builder.WriteString(me.ScheduledDate.Format(time.ANSIC))
builder.WriteString(", ")
builder.WriteString("name=") builder.WriteString("name=")
builder.WriteString(me.Name) builder.WriteString(me.Name)
builder.WriteString(", ") builder.WriteString(", ")

View file

@ -21,6 +21,8 @@ const (
FieldItemID = "item_id" FieldItemID = "item_id"
// FieldDate holds the string denoting the date field in the database. // FieldDate holds the string denoting the date field in the database.
FieldDate = "date" 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 holds the string denoting the name field in the database.
FieldName = "name" FieldName = "name"
// FieldDescription holds the string denoting the description field in the database. // FieldDescription holds the string denoting the description field in the database.
@ -47,6 +49,7 @@ var Columns = []string{
FieldUpdatedAt, FieldUpdatedAt,
FieldItemID, FieldItemID,
FieldDate, FieldDate,
FieldScheduledDate,
FieldName, FieldName,
FieldDescription, FieldDescription,
FieldCost, FieldCost,
@ -69,8 +72,6 @@ var (
DefaultUpdatedAt func() time.Time DefaultUpdatedAt func() time.Time
// UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field.
UpdateDefaultUpdatedAt func() time.Time 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 is a validator for the "name" field. It is called by the builders before save.
NameValidator func(string) error NameValidator func(string) error
// DescriptionValidator is a validator for the "description" field. It is called by the builders before save. // DescriptionValidator is a validator for the "description" field. It is called by the builders before save.

View file

@ -76,6 +76,11 @@ func Date(v time.Time) predicate.MaintenanceEntry {
return predicate.MaintenanceEntry(sql.FieldEQ(FieldDate, v)) 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. // Name applies equality check predicate on the "name" field. It's identical to NameEQ.
func Name(v string) predicate.MaintenanceEntry { func Name(v string) predicate.MaintenanceEntry {
return predicate.MaintenanceEntry(sql.FieldEQ(FieldName, v)) 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)) 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. // NameEQ applies the EQ predicate on the "name" field.
func NameEQ(v string) predicate.MaintenanceEntry { func NameEQ(v string) predicate.MaintenanceEntry {
return predicate.MaintenanceEntry(sql.FieldEQ(FieldName, v)) return predicate.MaintenanceEntry(sql.FieldEQ(FieldName, v))

View file

@ -70,6 +70,20 @@ func (mec *MaintenanceEntryCreate) SetNillableDate(t *time.Time) *MaintenanceEnt
return mec 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. // SetName sets the "name" field.
func (mec *MaintenanceEntryCreate) SetName(s string) *MaintenanceEntryCreate { func (mec *MaintenanceEntryCreate) SetName(s string) *MaintenanceEntryCreate {
mec.mutation.SetName(s) mec.mutation.SetName(s)
@ -166,10 +180,6 @@ func (mec *MaintenanceEntryCreate) defaults() {
v := maintenanceentry.DefaultUpdatedAt() v := maintenanceentry.DefaultUpdatedAt()
mec.mutation.SetUpdatedAt(v) mec.mutation.SetUpdatedAt(v)
} }
if _, ok := mec.mutation.Date(); !ok {
v := maintenanceentry.DefaultDate()
mec.mutation.SetDate(v)
}
if _, ok := mec.mutation.Cost(); !ok { if _, ok := mec.mutation.Cost(); !ok {
v := maintenanceentry.DefaultCost v := maintenanceentry.DefaultCost
mec.mutation.SetCost(v) mec.mutation.SetCost(v)
@ -191,9 +201,6 @@ func (mec *MaintenanceEntryCreate) check() error {
if _, ok := mec.mutation.ItemID(); !ok { if _, ok := mec.mutation.ItemID(); !ok {
return &ValidationError{Name: "item_id", err: errors.New(`ent: missing required field "MaintenanceEntry.item_id"`)} 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 { if _, ok := mec.mutation.Name(); !ok {
return &ValidationError{Name: "name", err: errors.New(`ent: missing required field "MaintenanceEntry.name"`)} 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) _spec.SetField(maintenanceentry.FieldDate, field.TypeTime, value)
_node.Date = 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 { if value, ok := mec.mutation.Name(); ok {
_spec.SetField(maintenanceentry.FieldName, field.TypeString, value) _spec.SetField(maintenanceentry.FieldName, field.TypeString, value)
_node.Name = value _node.Name = value

View file

@ -56,6 +56,32 @@ func (meu *MaintenanceEntryUpdate) SetNillableDate(t *time.Time) *MaintenanceEnt
return meu 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. // SetName sets the "name" field.
func (meu *MaintenanceEntryUpdate) SetName(s string) *MaintenanceEntryUpdate { func (meu *MaintenanceEntryUpdate) SetName(s string) *MaintenanceEntryUpdate {
meu.mutation.SetName(s) 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 { if value, ok := meu.mutation.Date(); ok {
_spec.SetField(maintenanceentry.FieldDate, field.TypeTime, value) _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 { if value, ok := meu.mutation.Name(); ok {
_spec.SetField(maintenanceentry.FieldName, field.TypeString, value) _spec.SetField(maintenanceentry.FieldName, field.TypeString, value)
} }
@ -287,6 +322,32 @@ func (meuo *MaintenanceEntryUpdateOne) SetNillableDate(t *time.Time) *Maintenanc
return meuo 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. // SetName sets the "name" field.
func (meuo *MaintenanceEntryUpdateOne) SetName(s string) *MaintenanceEntryUpdateOne { func (meuo *MaintenanceEntryUpdateOne) SetName(s string) *MaintenanceEntryUpdateOne {
meuo.mutation.SetName(s) meuo.mutation.SetName(s)
@ -452,6 +513,15 @@ func (meuo *MaintenanceEntryUpdateOne) sqlSave(ctx context.Context) (_node *Main
if value, ok := meuo.mutation.Date(); ok { if value, ok := meuo.mutation.Date(); ok {
_spec.SetField(maintenanceentry.FieldDate, field.TypeTime, value) _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 { if value, ok := meuo.mutation.Name(); ok {
_spec.SetField(maintenanceentry.FieldName, field.TypeString, value) _spec.SetField(maintenanceentry.FieldName, field.TypeString, value)
} }

View file

@ -323,7 +323,8 @@ var (
{Name: "id", Type: field.TypeUUID}, {Name: "id", Type: field.TypeUUID},
{Name: "created_at", Type: field.TypeTime}, {Name: "created_at", Type: field.TypeTime},
{Name: "updated_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: "name", Type: field.TypeString, Size: 255},
{Name: "description", Type: field.TypeString, Nullable: true, Size: 2500}, {Name: "description", Type: field.TypeString, Nullable: true, Size: 2500},
{Name: "cost", Type: field.TypeFloat64, Default: 0}, {Name: "cost", Type: field.TypeFloat64, Default: 0},
@ -337,7 +338,7 @@ var (
ForeignKeys: []*schema.ForeignKey{ ForeignKeys: []*schema.ForeignKey{
{ {
Symbol: "maintenance_entries_items_maintenance_entries", Symbol: "maintenance_entries_items_maintenance_entries",
Columns: []*schema.Column{MaintenanceEntriesColumns[7]}, Columns: []*schema.Column{MaintenanceEntriesColumns[8]},
RefColumns: []*schema.Column{ItemsColumns[0]}, RefColumns: []*schema.Column{ItemsColumns[0]},
OnDelete: schema.Cascade, OnDelete: schema.Cascade,
}, },

View file

@ -8918,22 +8918,23 @@ func (m *LocationMutation) ResetEdge(name string) error {
// MaintenanceEntryMutation represents an operation that mutates the MaintenanceEntry nodes in the graph. // MaintenanceEntryMutation represents an operation that mutates the MaintenanceEntry nodes in the graph.
type MaintenanceEntryMutation struct { type MaintenanceEntryMutation struct {
config config
op Op op Op
typ string typ string
id *uuid.UUID id *uuid.UUID
created_at *time.Time created_at *time.Time
updated_at *time.Time updated_at *time.Time
date *time.Time date *time.Time
name *string scheduled_date *time.Time
description *string name *string
cost *float64 description *string
addcost *float64 cost *float64
clearedFields map[string]struct{} addcost *float64
item *uuid.UUID clearedFields map[string]struct{}
cleareditem bool item *uuid.UUID
done bool cleareditem bool
oldValue func(context.Context) (*MaintenanceEntry, error) done bool
predicates []predicate.MaintenanceEntry oldValue func(context.Context) (*MaintenanceEntry, error)
predicates []predicate.MaintenanceEntry
} }
var _ ent.Mutation = (*MaintenanceEntryMutation)(nil) var _ ent.Mutation = (*MaintenanceEntryMutation)(nil)
@ -9179,9 +9180,71 @@ func (m *MaintenanceEntryMutation) OldDate(ctx context.Context) (v time.Time, er
return oldValue.Date, nil 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. // ResetDate resets all changes to the "date" field.
func (m *MaintenanceEntryMutation) ResetDate() { func (m *MaintenanceEntryMutation) ResetDate() {
m.date = nil 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. // 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 // order to get all numeric fields that were incremented/decremented, call
// AddedFields(). // AddedFields().
func (m *MaintenanceEntryMutation) Fields() []string { func (m *MaintenanceEntryMutation) Fields() []string {
fields := make([]string, 0, 7) fields := make([]string, 0, 8)
if m.created_at != nil { if m.created_at != nil {
fields = append(fields, maintenanceentry.FieldCreatedAt) fields = append(fields, maintenanceentry.FieldCreatedAt)
} }
@ -9398,6 +9461,9 @@ func (m *MaintenanceEntryMutation) Fields() []string {
if m.date != nil { if m.date != nil {
fields = append(fields, maintenanceentry.FieldDate) fields = append(fields, maintenanceentry.FieldDate)
} }
if m.scheduled_date != nil {
fields = append(fields, maintenanceentry.FieldScheduledDate)
}
if m.name != nil { if m.name != nil {
fields = append(fields, maintenanceentry.FieldName) fields = append(fields, maintenanceentry.FieldName)
} }
@ -9423,6 +9489,8 @@ func (m *MaintenanceEntryMutation) Field(name string) (ent.Value, bool) {
return m.ItemID() return m.ItemID()
case maintenanceentry.FieldDate: case maintenanceentry.FieldDate:
return m.Date() return m.Date()
case maintenanceentry.FieldScheduledDate:
return m.ScheduledDate()
case maintenanceentry.FieldName: case maintenanceentry.FieldName:
return m.Name() return m.Name()
case maintenanceentry.FieldDescription: case maintenanceentry.FieldDescription:
@ -9446,6 +9514,8 @@ func (m *MaintenanceEntryMutation) OldField(ctx context.Context, name string) (e
return m.OldItemID(ctx) return m.OldItemID(ctx)
case maintenanceentry.FieldDate: case maintenanceentry.FieldDate:
return m.OldDate(ctx) return m.OldDate(ctx)
case maintenanceentry.FieldScheduledDate:
return m.OldScheduledDate(ctx)
case maintenanceentry.FieldName: case maintenanceentry.FieldName:
return m.OldName(ctx) return m.OldName(ctx)
case maintenanceentry.FieldDescription: case maintenanceentry.FieldDescription:
@ -9489,6 +9559,13 @@ func (m *MaintenanceEntryMutation) SetField(name string, value ent.Value) error
} }
m.SetDate(v) m.SetDate(v)
return nil 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: case maintenanceentry.FieldName:
v, ok := value.(string) v, ok := value.(string)
if !ok { if !ok {
@ -9555,6 +9632,12 @@ func (m *MaintenanceEntryMutation) AddField(name string, value ent.Value) error
// mutation. // mutation.
func (m *MaintenanceEntryMutation) ClearedFields() []string { func (m *MaintenanceEntryMutation) ClearedFields() []string {
var fields []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) { if m.FieldCleared(maintenanceentry.FieldDescription) {
fields = append(fields, 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. // error if the field is not defined in the schema.
func (m *MaintenanceEntryMutation) ClearField(name string) error { func (m *MaintenanceEntryMutation) ClearField(name string) error {
switch name { switch name {
case maintenanceentry.FieldDate:
m.ClearDate()
return nil
case maintenanceentry.FieldScheduledDate:
m.ClearScheduledDate()
return nil
case maintenanceentry.FieldDescription: case maintenanceentry.FieldDescription:
m.ClearDescription() m.ClearDescription()
return nil return nil
@ -9595,6 +9684,9 @@ func (m *MaintenanceEntryMutation) ResetField(name string) error {
case maintenanceentry.FieldDate: case maintenanceentry.FieldDate:
m.ResetDate() m.ResetDate()
return nil return nil
case maintenanceentry.FieldScheduledDate:
m.ResetScheduledDate()
return nil
case maintenanceentry.FieldName: case maintenanceentry.FieldName:
m.ResetName() m.ResetName()
return nil return nil

View file

@ -446,12 +446,8 @@ func init() {
maintenanceentry.DefaultUpdatedAt = maintenanceentryDescUpdatedAt.Default.(func() time.Time) maintenanceentry.DefaultUpdatedAt = maintenanceentryDescUpdatedAt.Default.(func() time.Time)
// maintenanceentry.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. // maintenanceentry.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
maintenanceentry.UpdateDefaultUpdatedAt = maintenanceentryDescUpdatedAt.UpdateDefault.(func() time.Time) 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 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 is a validator for the "name" field. It is called by the builders before save.
maintenanceentry.NameValidator = func() func(string) error { maintenanceentry.NameValidator = func() func(string) error {
validators := maintenanceentryDescName.Validators validators := maintenanceentryDescName.Validators
@ -469,11 +465,11 @@ func init() {
} }
}() }()
// maintenanceentryDescDescription is the schema descriptor for description field. // 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 is a validator for the "description" field. It is called by the builders before save.
maintenanceentry.DescriptionValidator = maintenanceentryDescDescription.Validators[0].(func(string) error) maintenanceentry.DescriptionValidator = maintenanceentryDescDescription.Validators[0].(func(string) error)
// maintenanceentryDescCost is the schema descriptor for cost field. // 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 holds the default value on creation for the cost field.
maintenanceentry.DefaultCost = maintenanceentryDescCost.Default.(float64) maintenanceentry.DefaultCost = maintenanceentryDescCost.Default.(float64)
// maintenanceentryDescID is the schema descriptor for id field. // maintenanceentryDescID is the schema descriptor for id field.

View file

@ -1,8 +1,6 @@
package schema package schema
import ( import (
"time"
"entgo.io/ent" "entgo.io/ent"
"entgo.io/ent/schema/edge" "entgo.io/ent/schema/edge"
"entgo.io/ent/schema/field" "entgo.io/ent/schema/field"
@ -24,7 +22,9 @@ func (MaintenanceEntry) Fields() []ent.Field {
return []ent.Field{ return []ent.Field{
field.UUID("item_id", uuid.UUID{}), field.UUID("item_id", uuid.UUID{}),
field.Time("date"). field.Time("date").
Default(time.Now), Optional(),
field.Time("scheduled_date").
Optional(),
field.String("name"). field.String("name").
MaxLen(255). MaxLen(255).
NotEmpty(), NotEmpty(),

View file

@ -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;

View file

@ -1,4 +1,4 @@
h1:dn3XsqwgjCxEtpLXmHlt2ALRwg2cZB6m8lg2faxeLXM= h1:o94ZiQarQV54hzXXKoOUNL/DvHYieveswCLwJaUMGPo=
20220929052825_init.sql h1:ZlCqm1wzjDmofeAcSX3jE4h4VcdTNGpRg2eabztDy9Q= 20220929052825_init.sql h1:ZlCqm1wzjDmofeAcSX3jE4h4VcdTNGpRg2eabztDy9Q=
20221001210956_group_invitations.sql h1:YQKJFtE39wFOcRNbZQ/d+ZlHwrcfcsZlcv/pLEYdpjw= 20221001210956_group_invitations.sql h1:YQKJFtE39wFOcRNbZQ/d+ZlHwrcfcsZlcv/pLEYdpjw=
20221009173029_add_user_roles.sql h1:vWmzAfgEWQeGk0Vn70zfVPCcfEZth3E0JcvyKTjpYyU= 20221009173029_add_user_roles.sql h1:vWmzAfgEWQeGk0Vn70zfVPCcfEZth3E0JcvyKTjpYyU=
@ -9,3 +9,4 @@ h1:dn3XsqwgjCxEtpLXmHlt2ALRwg2cZB6m8lg2faxeLXM=
20221205230404_drop_document_tokens.sql h1:9dCbNFcjtsT6lEhkxCn/vYaGRmQrl1LefdEJgvkfhGg= 20221205230404_drop_document_tokens.sql h1:9dCbNFcjtsT6lEhkxCn/vYaGRmQrl1LefdEJgvkfhGg=
20221205234214_add_maintenance_entries.sql h1:B56VzCuDsed1k3/sYUoKlOkP90DcdLufxFK0qYvoafU= 20221205234214_add_maintenance_entries.sql h1:B56VzCuDsed1k3/sYUoKlOkP90DcdLufxFK0qYvoafU=
20221205234812_cascade_delete_roles.sql h1:VIiaImR48nCHF3uFbOYOX1E79Ta5HsUBetGaSAbh9Gk= 20221205234812_cascade_delete_roles.sql h1:VIiaImR48nCHF3uFbOYOX1E79Ta5HsUBetGaSAbh9Gk=
20230227024134_add_scheduled_date.sql h1:8qO5OBZ0AzsfYEQOAQQrYIjyhSwM+v1A+/ylLSoiyoc=

View file

@ -7,6 +7,7 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/data/ent" "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/ent/maintenanceentry"
"github.com/hay-kot/homebox/backend/internal/data/types"
) )
// MaintenanceEntryRepository is a repository for maintenance entries that are // MaintenanceEntryRepository is a repository for maintenance entries that are
@ -17,25 +18,28 @@ type MaintenanceEntryRepository struct {
} }
type ( type (
MaintenanceEntryCreate struct { MaintenanceEntryCreate struct {
Date time.Time `json:"date"` CompletedDate types.Date `json:"completedDate"`
Name string `json:"name"` ScheduledDate types.Date `json:"scheduledDate"`
Description string `json:"description"` Name string `json:"name"`
Cost float64 `json:"cost,string"` Description string `json:"description"`
Cost float64 `json:"cost,string"`
} }
MaintenanceEntry struct { MaintenanceEntry struct {
ID uuid.UUID `json:"id"` ID uuid.UUID `json:"id"`
Date time.Time `json:"date"` CompletedDate types.Date `json:"completedDate"`
Name string `json:"name"` ScheduledDate types.Date `json:"scheduledDate"`
Description string `json:"description"` Name string `json:"name"`
Cost float64 `json:"cost,string"` Description string `json:"description"`
Cost float64 `json:"cost,string"`
} }
MaintenanceEntryUpdate struct { MaintenanceEntryUpdate struct {
Date time.Time `json:"date"` CompletedDate types.Date `json:"completedDate"`
Name string `json:"name"` ScheduledDate types.Date `json:"scheduledDate"`
Description string `json:"description"` Name string `json:"name"`
Cost float64 `json:"cost,string"` Description string `json:"description"`
Cost float64 `json:"cost,string"`
} }
MaintenanceLog struct { MaintenanceLog struct {
@ -53,18 +57,20 @@ var (
func mapMaintenanceEntry(entry *ent.MaintenanceEntry) MaintenanceEntry { func mapMaintenanceEntry(entry *ent.MaintenanceEntry) MaintenanceEntry {
return MaintenanceEntry{ return MaintenanceEntry{
ID: entry.ID, ID: entry.ID,
Date: entry.Date, CompletedDate: types.Date(entry.Date),
Name: entry.Name, ScheduledDate: types.Date(entry.ScheduledDate),
Description: entry.Description, Name: entry.Name,
Cost: entry.Cost, Description: entry.Description,
Cost: entry.Cost,
} }
} }
func (r *MaintenanceEntryRepository) Create(ctx context.Context, itemID uuid.UUID, input MaintenanceEntryCreate) (MaintenanceEntry, error) { func (r *MaintenanceEntryRepository) Create(ctx context.Context, itemID uuid.UUID, input MaintenanceEntryCreate) (MaintenanceEntry, error) {
item, err := r.db.MaintenanceEntry.Create(). item, err := r.db.MaintenanceEntry.Create().
SetItemID(itemID). SetItemID(itemID).
SetDate(input.Date). SetDate(input.CompletedDate.Time()).
SetScheduledDate(input.ScheduledDate.Time()).
SetName(input.Name). SetName(input.Name).
SetDescription(input.Description). SetDescription(input.Description).
SetCost(input.Cost). 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) { func (r *MaintenanceEntryRepository) Update(ctx context.Context, ID uuid.UUID, input MaintenanceEntryUpdate) (MaintenanceEntry, error) {
item, err := r.db.MaintenanceEntry.UpdateOneID(ID). item, err := r.db.MaintenanceEntry.UpdateOneID(ID).
SetDate(input.Date). SetDate(input.CompletedDate.Time()).
SetScheduledDate(input.ScheduledDate.Time()).
SetName(input.Name). SetName(input.Name).
SetDescription(input.Description). SetDescription(input.Description).
SetCost(input.Cost). SetCost(input.Cost).
@ -84,14 +91,36 @@ func (r *MaintenanceEntryRepository) Update(ctx context.Context, ID uuid.UUID, i
return mapMaintenanceEntryErr(item, err) 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{ log := MaintenanceLog{
ItemID: itemID, ItemID: itemID,
} }
entries, err := r.db.MaintenanceEntry.Query(). q := r.db.MaintenanceEntry.Query().Where(maintenanceentry.ItemID(itemID))
Where(maintenanceentry.ItemID(itemID)).
Order(ent.Desc(maintenanceentry.FieldDate)). 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) All(ctx)
if err != nil { if err != nil {
return MaintenanceLog{}, err return MaintenanceLog{}, err
@ -102,7 +131,7 @@ func (r *MaintenanceEntryRepository) GetLog(ctx context.Context, itemID uuid.UUI
var maybeTotal *float64 var maybeTotal *float64
var maybeAverage *float64 var maybeAverage *float64
q := ` statement := `
SELECT SELECT
SUM(cost_total) AS total_of_totals, SUM(cost_total) AS total_of_totals,
AVG(cost_total) AS avg_of_averages AVG(cost_total) AS avg_of_averages
@ -119,7 +148,7 @@ FROM
my my
)` )`
row := r.db.Sql().QueryRowContext(ctx, q, itemID) row := r.db.Sql().QueryRowContext(ctx, statement, itemID)
err = row.Scan(&maybeTotal, &maybeAverage) err = row.Scan(&maybeTotal, &maybeAverage)
if err != nil { if err != nil {
return MaintenanceLog{}, err return MaintenanceLog{}, err

View file

@ -5,6 +5,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/hay-kot/homebox/backend/internal/data/types"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -43,10 +44,10 @@ func TestMaintenanceEntryRepository_GetLog(t *testing.T) {
} }
created[i] = MaintenanceEntryCreate{ created[i] = MaintenanceEntryCreate{
Date: dt, CompletedDate: types.DateFromTime(dt),
Name: "Maintenance", Name: "Maintenance",
Description: "Maintenance description", Description: "Maintenance description",
Cost: 10, Cost: 10,
} }
} }
@ -58,7 +59,9 @@ func TestMaintenanceEntryRepository_GetLog(t *testing.T) {
} }
// Get the log for the item // 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 { if err != nil {
t.Fatalf("failed to get maintenance log: %v", err) t.Fatalf("failed to get maintenance log: %v", err)
} }

View file

@ -1,13 +1,13 @@
<template> <template>
<div v-if="!inline" class="form-control w-full"> <div v-if="!inline" class="form-control w-full">
<label class="label"> <label class="label">
<span class="label-text"> Date </span> <span class="label-text"> {{ label }}</span>
</label> </label>
<input ref="input" v-model="selected" type="date" class="input input-bordered w-full" /> <input ref="input" v-model="selected" type="date" class="input input-bordered w-full" />
</div> </div>
<div v-else class="sm:grid sm:grid-cols-4 sm:items-start sm:gap-4"> <div v-else class="sm:grid sm:grid-cols-4 sm:items-start sm:gap-4">
<label class="label"> <label class="label">
<span class="label-text"> Date </span> <span class="label-text"> {{ label }} </span>
</label> </label>
<input v-model="selected" type="date" class="input input-bordered col-span-3 w-full mt-2" /> <input v-model="selected" type="date" class="input input-bordered col-span-3 w-full mt-2" />
</div> </div>
@ -18,7 +18,7 @@
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
type: Date as () => Date | string, type: Date as () => Date | string | null,
required: false, required: false,
default: null, default: null,
}, },
@ -26,6 +26,10 @@
type: Boolean, type: Boolean,
default: false, default: false,
}, },
label: {
type: String,
default: "Date",
},
}); });
const selected = computed({ const selected = computed({

View file

@ -5,6 +5,10 @@ export function validDate(dt: Date | string | null | undefined): boolean {
// If it's a string, try to parse it // If it's a string, try to parse it
if (typeof dt === "string") { if (typeof dt === "string") {
if (dt.startsWith("0001")) {
return false;
}
const parsed = new Date(dt); const parsed = new Date(dt);
if (isNaN(parsed.getTime())) { if (isNaN(parsed.getTime())) {
return false; return false;

View file

@ -135,7 +135,8 @@ describe("user should be able to create an item and add an attachment", () => {
const { response, data } = await api.items.maintenance.create(item.id, { const { response, data } = await api.items.maintenance.create(item.id, {
name: faker.vehicle.model(), name: faker.vehicle.model(),
description: faker.lorem.paragraph(1), description: faker.lorem.paragraph(1),
date: faker.date.past(1), completedDate: faker.date.past(1),
scheduledDate: "null",
cost: faker.datatype.number(100).toString(), cost: faker.datatype.number(100).toString(),
}); });

View file

@ -59,9 +59,14 @@ export class FieldsAPI extends BaseAPI {
} }
} }
type MaintenanceEntryQuery = {
scheduled?: boolean;
completed?: boolean;
};
export class MaintenanceAPI extends BaseAPI { export class MaintenanceAPI extends BaseAPI {
getLog(itemId: string) { getLog(itemId: string, q: MaintenanceEntryQuery = {}) {
return this.http.get<MaintenanceLog>({ url: route(`/items/${itemId}/maintenance`) }); return this.http.get<MaintenanceLog>({ url: route(`/items/${itemId}/maintenance`, q) });
} }
create(itemId: string, data: MaintenanceEntryCreate) { create(itemId: string, data: MaintenanceEntryCreate) {

View file

@ -227,28 +227,37 @@ export interface LocationUpdate {
} }
export interface MaintenanceEntry { export interface MaintenanceEntry {
/** Sold */
completedDate: Date | string;
/** @example "0" */ /** @example "0" */
cost: string; cost: string;
date: Date | string;
description: string; description: string;
id: string; id: string;
name: string; name: string;
/** Sold */
scheduledDate: Date | string;
} }
export interface MaintenanceEntryCreate { export interface MaintenanceEntryCreate {
/** Sold */
completedDate: Date | string;
/** @example "0" */ /** @example "0" */
cost: string; cost: string;
date: Date | string;
description: string; description: string;
name: string; name: string;
/** Sold */
scheduledDate: Date | string;
} }
export interface MaintenanceEntryUpdate { export interface MaintenanceEntryUpdate {
/** Sold */
completedDate: Date | string;
/** @example "0" */ /** @example "0" */
cost: string; cost: string;
date: Date | string;
description: string; description: string;
name: string; name: string;
/** Sold */
scheduledDate: Date | string;
} }
export interface MaintenanceLog { export interface MaintenanceLog {

View file

@ -360,8 +360,8 @@
}, },
{ {
id: "log", id: "log",
name: "Log", name: "Maintenance",
to: `/item/${itemId.value}/log`, to: `/item/${itemId.value}/maintenance`,
}, },
{ {
id: "edit", id: "edit",

View file

@ -10,8 +10,20 @@
const api = useUserApi(); const api = useUserApi();
const toast = useNotifier(); const toast = useNotifier();
const scheduled = ref(true);
watch(
() => scheduled.value,
() => {
refreshLog();
}
);
const { data: log, refresh: refreshLog } = useAsyncData(async () => { const { data: log, refresh: refreshLog } = useAsyncData(async () => {
const { data } = await api.items.maintenance.getLog(props.item.id); const { data } = await api.items.maintenance.getLog(props.item.id, {
scheduled: scheduled.value,
completed: !scheduled.value,
});
return data; return data;
}); });
@ -48,7 +60,8 @@
id: null as string | null, id: null as string | null,
modal: false, modal: false,
name: "", name: "",
date: new Date(), completedDate: null as Date | null,
scheduledDate: null as Date | null,
description: "", description: "",
cost: "", cost: "",
}); });
@ -60,7 +73,8 @@
function resetEntry() { function resetEntry() {
entry.id = null; entry.id = null;
entry.name = ""; entry.name = "";
entry.date = new Date(); entry.completedDate = null;
entry.scheduledDate = null;
entry.description = ""; entry.description = "";
entry.cost = ""; entry.cost = "";
} }
@ -74,9 +88,10 @@
const { error } = await api.items.maintenance.create(props.item.id, { const { error } = await api.items.maintenance.create(props.item.id, {
name: entry.name, name: entry.name,
date: entry.date, completedDate: entry.completedDate ?? "",
scheduledDate: entry.scheduledDate ?? "",
description: entry.description, description: entry.description,
cost: entry.cost, cost: parseFloat(entry.cost) ? entry.cost : "0",
}); });
if (error) { if (error) {
@ -108,10 +123,12 @@
} }
function openEditDialog(e: MaintenanceEntry) { function openEditDialog(e: MaintenanceEntry) {
console.log(e);
entry.modal = true; entry.modal = true;
entry.id = e.id; entry.id = e.id;
entry.name = e.name; entry.name = e.name;
entry.date = new Date(e.date); entry.completedDate = new Date(e.completedDate);
entry.scheduledDate = new Date(e.scheduledDate);
entry.description = e.description; entry.description = e.description;
entry.cost = e.cost; entry.cost = e.cost;
} }
@ -123,7 +140,8 @@
const { error } = await api.items.maintenance.update(props.item.id, entry.id, { const { error } = await api.items.maintenance.update(props.item.id, entry.id, {
name: entry.name, name: entry.name,
date: entry.date, completedDate: entry.completedDate ?? "null",
scheduledDate: entry.scheduledDate ?? "null",
description: entry.description, description: entry.description,
cost: entry.cost, cost: entry.cost,
}); });
@ -142,10 +160,13 @@
<template> <template>
<div v-if="log"> <div v-if="log">
<BaseModal v-model="entry.modal"> <BaseModal v-model="entry.modal">
<template #title> Create Entry </template> <template #title>
{{ entry.id ? "Edit Entry" : "New Entry" }}
</template>
<form @submit.prevent="createEntry"> <form @submit.prevent="createEntry">
<FormTextField v-model="entry.name" autofocus label="Entry Name" /> <FormTextField v-model="entry.name" autofocus label="Entry Name" />
<DatePicker v-model="entry.date" label="Date" /> <DatePicker v-model="entry.completedDate" label="Completed Date" />
<DatePicker v-model="entry.scheduledDate" label="Scheduled Date" />
<FormTextArea v-model="entry.description" label="Notes" /> <FormTextArea v-model="entry.description" label="Notes" />
<FormTextField v-model="entry.cost" autofocus label="Cost" /> <FormTextField v-model="entry.cost" autofocus label="Cost" />
<div class="py-2 flex justify-end"> <div class="py-2 flex justify-end">
@ -171,11 +192,19 @@
/> />
</div> </div>
<div class="flex"> <div class="flex">
<div class="btn-group">
<button class="btn btn-sm" :class="`${scheduled ? 'btn-active' : ''}`" @click="scheduled = true">
Scheduled
</button>
<button class="btn btn-sm" :class="`${scheduled ? '' : 'btn-active'}`" @click="scheduled = false">
Completed
</button>
</div>
<BaseButton class="ml-auto" size="sm" @click="newEntry()"> <BaseButton class="ml-auto" size="sm" @click="newEntry()">
<template #icon> <template #icon>
<Icon name="mdi-post" /> <Icon name="mdi-plus" />
</template> </template>
Log Maintenance New
</BaseButton> </BaseButton>
</div> </div>
<div class="container space-y-6"> <div class="container space-y-6">
@ -185,10 +214,14 @@
{{ e.name }} {{ e.name }}
</span> </span>
<template #description> <template #description>
<div class="flex gap-2"> <div class="flex flex-wrap gap-2">
<div class="badge p-3"> <div v-if="validDate(e.completedDate)" class="badge p-3">
<Icon name="mdi-check" class="mr-2" />
<DateTime :date="e.completedDate" format="human" />
</div>
<div v-else-if="validDate(e.scheduledDate)" class="badge p-3">
<Icon name="mdi-calendar" class="mr-2" /> <Icon name="mdi-calendar" class="mr-2" />
<DateTime :date="e.date" format="human" /> <DateTime :date="e.scheduledDate" format="human" />
</div> </div>
<div class="tooltip tooltip-primary" data-tip="Cost"> <div class="tooltip tooltip-primary" data-tip="Cost">
<div class="badge badge-primary p-3"> <div class="badge badge-primary p-3">

View file

@ -61,6 +61,8 @@ func main() {
NewReDate("warrantyExpires"), NewReDate("warrantyExpires"),
NewReDate("expiresAt"), NewReDate("expiresAt"),
NewReDate("date"), NewReDate("date"),
NewReDate("completedDate"),
NewReDate("scheduledDate"),
} }
for _, replace := range replaces { for _, replace := range replaces {