simplify/factorize backend for maintenance

This commit is contained in:
Maximilien Carbonne
2024-09-22 19:12:06 +02:00
parent 17ecfa2c66
commit ca85d4b483
10 changed files with 103 additions and 172 deletions

View File

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

View File

@@ -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": [

View File

@@ -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": [

View File

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

View File

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

View File

@@ -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)
} }

View File

@@ -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": [

View File

@@ -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");
} }

View File

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

View File

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