mirror of
https://github.com/sysadminsmedia/homebox.git
synced 2025-12-25 06:49:18 +01:00
simplify/factorize backend for maintenance
This commit is contained in:
@@ -15,13 +15,14 @@ import (
|
|||||||
// @Summary Get Maintenance Log
|
// @Summary Get Maintenance Log
|
||||||
// @Tags Item Maintenance
|
// @Tags Item Maintenance
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} repo.MaintenanceLog
|
// @Param filters query repo.MaintenanceFilters false "which maintenance to retrieve"
|
||||||
|
// @Success 200 {array} repo.MaintenanceEntryWithDetails[]
|
||||||
// @Router /v1/items/{id}/maintenance [GET]
|
// @Router /v1/items/{id}/maintenance [GET]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleMaintenanceLogGet() errchain.HandlerFunc {
|
func (ctrl *V1Controller) HandleMaintenanceLogGet() errchain.HandlerFunc {
|
||||||
fn := func(r *http.Request, ID uuid.UUID, q repo.MaintenanceLogQuery) (repo.MaintenanceLog, error) {
|
fn := func(r *http.Request, ID uuid.UUID, filters repo.MaintenanceFilters) ([]repo.MaintenanceEntryWithDetails, error) {
|
||||||
auth := services.NewContext(r.Context())
|
auth := services.NewContext(r.Context())
|
||||||
return ctrl.repo.MaintEntry.GetLog(auth, auth.GID, ID, q)
|
return ctrl.repo.MaintEntry.GetMaintenanceByItemID(auth, auth.GID, ID, filters)
|
||||||
}
|
}
|
||||||
|
|
||||||
return adapters.QueryID("id", fn, http.StatusOK)
|
return adapters.QueryID("id", fn, http.StatusOK)
|
||||||
|
|||||||
@@ -920,11 +920,31 @@ const docTemplate = `{
|
|||||||
"Item Maintenance"
|
"Item Maintenance"
|
||||||
],
|
],
|
||||||
"summary": "Get Maintenance Log",
|
"summary": "Get Maintenance Log",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"enum": [
|
||||||
|
"scheduled",
|
||||||
|
"completed",
|
||||||
|
"both"
|
||||||
|
],
|
||||||
|
"type": "string",
|
||||||
|
"x-enum-varnames": [
|
||||||
|
"MaintenanceFilterStatusScheduled",
|
||||||
|
"MaintenanceFilterStatusCompleted",
|
||||||
|
"MaintenanceFilterStatusBoth"
|
||||||
|
],
|
||||||
|
"name": "status",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/repo.MaintenanceLog"
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/repo.MaintenanceEntryWithDetails"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2701,26 +2721,6 @@ const docTemplate = `{
|
|||||||
"MaintenanceFilterStatusBoth"
|
"MaintenanceFilterStatusBoth"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"repo.MaintenanceLog": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"costAverage": {
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"costTotal": {
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"entries": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/repo.MaintenanceEntry"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"itemId": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"repo.NotifierCreate": {
|
"repo.NotifierCreate": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
|
|||||||
@@ -913,11 +913,31 @@
|
|||||||
"Item Maintenance"
|
"Item Maintenance"
|
||||||
],
|
],
|
||||||
"summary": "Get Maintenance Log",
|
"summary": "Get Maintenance Log",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"enum": [
|
||||||
|
"scheduled",
|
||||||
|
"completed",
|
||||||
|
"both"
|
||||||
|
],
|
||||||
|
"type": "string",
|
||||||
|
"x-enum-varnames": [
|
||||||
|
"MaintenanceFilterStatusScheduled",
|
||||||
|
"MaintenanceFilterStatusCompleted",
|
||||||
|
"MaintenanceFilterStatusBoth"
|
||||||
|
],
|
||||||
|
"name": "status",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/repo.MaintenanceLog"
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/repo.MaintenanceEntryWithDetails"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2694,26 +2714,6 @@
|
|||||||
"MaintenanceFilterStatusBoth"
|
"MaintenanceFilterStatusBoth"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"repo.MaintenanceLog": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"costAverage": {
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"costTotal": {
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"entries": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/repo.MaintenanceEntry"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"itemId": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"repo.NotifierCreate": {
|
"repo.NotifierCreate": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
|
|||||||
@@ -511,19 +511,6 @@ definitions:
|
|||||||
- MaintenanceFilterStatusScheduled
|
- MaintenanceFilterStatusScheduled
|
||||||
- MaintenanceFilterStatusCompleted
|
- MaintenanceFilterStatusCompleted
|
||||||
- MaintenanceFilterStatusBoth
|
- MaintenanceFilterStatusBoth
|
||||||
repo.MaintenanceLog:
|
|
||||||
properties:
|
|
||||||
costAverage:
|
|
||||||
type: number
|
|
||||||
costTotal:
|
|
||||||
type: number
|
|
||||||
entries:
|
|
||||||
items:
|
|
||||||
$ref: '#/definitions/repo.MaintenanceEntry'
|
|
||||||
type: array
|
|
||||||
itemId:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
repo.NotifierCreate:
|
repo.NotifierCreate:
|
||||||
properties:
|
properties:
|
||||||
isActive:
|
isActive:
|
||||||
@@ -1249,13 +1236,27 @@ paths:
|
|||||||
- Items Attachments
|
- Items Attachments
|
||||||
/v1/items/{id}/maintenance:
|
/v1/items/{id}/maintenance:
|
||||||
get:
|
get:
|
||||||
|
parameters:
|
||||||
|
- enum:
|
||||||
|
- scheduled
|
||||||
|
- completed
|
||||||
|
- both
|
||||||
|
in: query
|
||||||
|
name: status
|
||||||
|
type: string
|
||||||
|
x-enum-varnames:
|
||||||
|
- MaintenanceFilterStatusScheduled
|
||||||
|
- MaintenanceFilterStatusCompleted
|
||||||
|
- MaintenanceFilterStatusBoth
|
||||||
produces:
|
produces:
|
||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/repo.MaintenanceLog'
|
items:
|
||||||
|
$ref: '#/definitions/repo.MaintenanceEntryWithDetails'
|
||||||
|
type: array
|
||||||
security:
|
security:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: Get Maintenance Log
|
summary: Get Maintenance Log
|
||||||
|
|||||||
@@ -59,13 +59,6 @@ type (
|
|||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Cost float64 `json:"cost,string"`
|
Cost float64 `json:"cost,string"`
|
||||||
}
|
}
|
||||||
|
|
||||||
MaintenanceLog struct {
|
|
||||||
ItemID uuid.UUID `json:"itemId"`
|
|
||||||
CostAverage float64 `json:"costAverage"`
|
|
||||||
CostTotal float64 `json:"costTotal"`
|
|
||||||
Entries []MaintenanceEntry `json:"entries"`
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -130,76 +123,32 @@ func (r *MaintenanceEntryRepository) Update(ctx context.Context, id uuid.UUID, i
|
|||||||
return mapMaintenanceEntryErr(item, err)
|
return mapMaintenanceEntryErr(item, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
type MaintenanceLogQuery struct {
|
func (r *MaintenanceEntryRepository) GetMaintenanceByItemID(ctx context.Context, groupID, itemID uuid.UUID, filters MaintenanceFilters) ([]MaintenanceEntryWithDetails, error) {
|
||||||
Completed bool `json:"completed" schema:"completed"`
|
query := r.db.MaintenanceEntry.Query().Where(
|
||||||
Scheduled bool `json:"scheduled" schema:"scheduled"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *MaintenanceEntryRepository) GetLog(ctx context.Context, groupID, itemID uuid.UUID, query MaintenanceLogQuery) (MaintenanceLog, error) {
|
|
||||||
log := MaintenanceLog{
|
|
||||||
ItemID: itemID,
|
|
||||||
}
|
|
||||||
|
|
||||||
q := r.db.MaintenanceEntry.Query().Where(
|
|
||||||
maintenanceentry.ItemID(itemID),
|
maintenanceentry.ItemID(itemID),
|
||||||
maintenanceentry.HasItemWith(
|
maintenanceentry.HasItemWith(
|
||||||
item.HasGroupWith(group.IDEQ(groupID)),
|
item.HasGroupWith(group.IDEQ(groupID)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
if filters.Status == MaintenanceFilterStatusScheduled {
|
||||||
if query.Completed {
|
query = query.Where(maintenanceentry.Or(
|
||||||
q = q.Where(maintenanceentry.And(
|
maintenanceentry.DateIsNil(),
|
||||||
maintenanceentry.DateNotNil(),
|
maintenanceentry.DateEQ(time.Time{}),
|
||||||
maintenanceentry.DateNEQ(time.Time{}),
|
|
||||||
))
|
))
|
||||||
} else if query.Scheduled {
|
} else if filters.Status == MaintenanceFilterStatusCompleted {
|
||||||
q = q.Where(maintenanceentry.And(
|
query = query.Where(
|
||||||
maintenanceentry.Or(
|
maintenanceentry.Not(maintenanceentry.Or(
|
||||||
maintenanceentry.DateIsNil(),
|
maintenanceentry.DateIsNil(),
|
||||||
maintenanceentry.DateEQ(time.Time{}),
|
maintenanceentry.DateEQ(time.Time{})),
|
||||||
),
|
))
|
||||||
maintenanceentry.ScheduledDateNotNil(),
|
|
||||||
maintenanceentry.ScheduledDateNEQ(time.Time{}),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
entries, err := query.WithItem().Order(maintenanceentry.ByScheduledDate()).All(ctx)
|
||||||
|
|
||||||
entries, err := q.Order(ent.Desc(maintenanceentry.FieldDate)).
|
|
||||||
All(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return MaintenanceLog{}, err
|
return []MaintenanceEntryWithDetails{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Entries = mapEachMaintenanceEntry(entries)
|
return mapEachMaintenanceEntryWithDetails(entries), nil
|
||||||
|
|
||||||
var maybeTotal *float64
|
|
||||||
var maybeAverage *float64
|
|
||||||
|
|
||||||
statement := `
|
|
||||||
SELECT
|
|
||||||
SUM(cost_total) AS total_of_totals,
|
|
||||||
AVG(cost_total) AS avg_of_averages
|
|
||||||
FROM
|
|
||||||
(
|
|
||||||
SELECT
|
|
||||||
strftime('%m-%Y', date) AS my,
|
|
||||||
SUM(cost) AS cost_total
|
|
||||||
FROM
|
|
||||||
maintenance_entries
|
|
||||||
WHERE
|
|
||||||
item_id = ?
|
|
||||||
GROUP BY
|
|
||||||
my
|
|
||||||
)`
|
|
||||||
|
|
||||||
row := r.db.Sql().QueryRowContext(ctx, statement, itemID)
|
|
||||||
err = row.Scan(&maybeTotal, &maybeAverage)
|
|
||||||
if err != nil {
|
|
||||||
return MaintenanceLog{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.CostAverage = orDefault(maybeAverage, 0)
|
|
||||||
log.CostTotal = orDefault(maybeTotal, 0)
|
|
||||||
return log, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *MaintenanceEntryRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
func (r *MaintenanceEntryRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
||||||
|
|||||||
@@ -60,27 +60,14 @@ 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(), tGroup.ID, item.ID, MaintenanceLogQuery{
|
log, err := tRepos.MaintEntry.GetMaintenanceByItemID(context.Background(), tGroup.ID, item.ID, MaintenanceFilters{Status: MaintenanceFilterStatusCompleted})
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, item.ID, log.ItemID)
|
assert.Len(t, log, 10)
|
||||||
assert.Len(t, log.Entries, 10)
|
|
||||||
|
|
||||||
// Calculate the average cost
|
for _, entry := range log {
|
||||||
var total float64
|
|
||||||
|
|
||||||
for _, entry := range log.Entries {
|
|
||||||
total += entry.Cost
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.InDelta(t, total, log.CostTotal, .001, "total cost should be equal to the sum of all entries")
|
|
||||||
assert.InDelta(t, total/2, log.CostAverage, 001, "average cost should be the average of the two months")
|
|
||||||
|
|
||||||
for _, entry := range log.Entries {
|
|
||||||
err := tRepos.MaintEntry.Delete(context.Background(), entry.ID)
|
err := tRepos.MaintEntry.Delete(context.Background(), entry.ID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -913,11 +913,31 @@
|
|||||||
"Item Maintenance"
|
"Item Maintenance"
|
||||||
],
|
],
|
||||||
"summary": "Get Maintenance Log",
|
"summary": "Get Maintenance Log",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"enum": [
|
||||||
|
"scheduled",
|
||||||
|
"completed",
|
||||||
|
"both"
|
||||||
|
],
|
||||||
|
"type": "string",
|
||||||
|
"x-enum-varnames": [
|
||||||
|
"MaintenanceFilterStatusScheduled",
|
||||||
|
"MaintenanceFilterStatusCompleted",
|
||||||
|
"MaintenanceFilterStatusBoth"
|
||||||
|
],
|
||||||
|
"name": "status",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/repo.MaintenanceLog"
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/repo.MaintenanceEntryWithDetails"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2694,26 +2714,6 @@
|
|||||||
"MaintenanceFilterStatusBoth"
|
"MaintenanceFilterStatusBoth"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"repo.MaintenanceLog": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"costAverage": {
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"costTotal": {
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"entries": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/repo.MaintenanceEntry"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"itemId": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"repo.NotifierCreate": {
|
"repo.NotifierCreate": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
|
|||||||
@@ -136,7 +136,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function complete(maintenanceEntry: MaintenanceEntry) {
|
async function complete(maintenanceEntry: MaintenanceEntry) {
|
||||||
const { error } = await api.maintenances.update(maintenanceEntry.id, {
|
const { error } = await api.maintenance.update(maintenanceEntry.id, {
|
||||||
name: maintenanceEntry.name,
|
name: maintenanceEntry.name,
|
||||||
completedDate: new Date(Date.now()),
|
completedDate: new Date(Date.now()),
|
||||||
scheduledDate: maintenanceEntry.scheduledDate ?? "null",
|
scheduledDate: maintenanceEntry.scheduledDate ?? "null",
|
||||||
@@ -144,7 +144,7 @@
|
|||||||
cost: maintenanceEntry.cost,
|
cost: maintenanceEntry.cost,
|
||||||
});
|
});
|
||||||
if (error) {
|
if (error) {
|
||||||
toast.error(t("maintenances.toast.failed_to_update"));
|
toast.error(t("maintenance.toast.failed_to_update"));
|
||||||
}
|
}
|
||||||
emit("changed");
|
emit("changed");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -306,13 +306,6 @@ export enum MaintenanceFilterStatus {
|
|||||||
MaintenanceFilterStatusBoth = "both",
|
MaintenanceFilterStatusBoth = "both",
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MaintenanceLog {
|
|
||||||
costAverage: number;
|
|
||||||
costTotal: number;
|
|
||||||
entries: MaintenanceEntry[];
|
|
||||||
itemId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NotifierCreate {
|
export interface NotifierCreate {
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -127,7 +127,7 @@
|
|||||||
<template #icon>
|
<template #icon>
|
||||||
<MdiCheck />
|
<MdiCheck />
|
||||||
</template>
|
</template>
|
||||||
{{ $t("maintenances.list.complete") }}
|
{{ $t("maintenance.list.complete") }}
|
||||||
</BaseButton>
|
</BaseButton>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
v-if="!validDate(e.completedDate)"
|
v-if="!validDate(e.completedDate)"
|
||||||
@@ -137,7 +137,7 @@
|
|||||||
<template #icon>
|
<template #icon>
|
||||||
<MdiContentDuplicate />
|
<MdiContentDuplicate />
|
||||||
</template>
|
</template>
|
||||||
{{ $t("maintenances.list.complete_and_duplicate") }}
|
{{ $t("maintenance.list.complete_and_duplicate") }}
|
||||||
</BaseButton>
|
</BaseButton>
|
||||||
<BaseButton size="sm" @click="maintenanceEditModal?.deleteEntry(e.id)">
|
<BaseButton size="sm" @click="maintenanceEditModal?.deleteEntry(e.id)">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
|
|||||||
Reference in New Issue
Block a user