diff --git a/README.md b/README.md index 9202783..6f6de5a 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ -->

Hammond

-

Current Version - 2021.07.14

+

Current Version - 2021.07.23

A self-hosted vehicle expense tracking system with support for multiple users. diff --git a/server/controllers/vehicle.go b/server/controllers/vehicle.go index 83148b7..d914b7c 100644 --- a/server/controllers/vehicle.go +++ b/server/controllers/vehicle.go @@ -26,6 +26,7 @@ func RegisterVehicleController(router *gin.RouterGroup) { router.GET("/me/stats", getMystats) router.GET("/vehicles/:id/fillups", getFillupsByVehicleId) + router.GET("/vehicles/:id/fuelSubTypes", getFuelSubTypesByVehicleId) router.POST("/vehicles/:id/fillups", createFillup) router.GET("/vehicles/:id/fillups/:subId", getFillupById) router.PUT("/vehicles/:id/fillups/:subId", updateFillup) @@ -121,6 +122,22 @@ func getFillupsByVehicleId(c *gin.Context) { c.JSON(http.StatusUnprocessableEntity, common.NewValidatorError(err)) } } +func getFuelSubTypesByVehicleId(c *gin.Context) { + + var searchByIdQuery models.SearchByIdQuery + + if err := c.ShouldBindUri(&searchByIdQuery); err == nil { + + fuelSubtypes, err := service.GetDistinctFuelSubtypesForVehicle(searchByIdQuery.Id) + if err != nil { + c.JSON(http.StatusUnprocessableEntity, common.NewError("getFuelSubTypesByVehicleId", err)) + return + } + c.JSON(http.StatusOK, fuelSubtypes) + } else { + c.JSON(http.StatusUnprocessableEntity, common.NewValidatorError(err)) + } +} func getExpensesByVehicleId(c *gin.Context) { diff --git a/server/db/dbModels.go b/server/db/dbModels.go index 2d09a08..c0ac9fe 100644 --- a/server/db/dbModels.go +++ b/server/db/dbModels.go @@ -119,6 +119,7 @@ type Fillup struct { Currency string `json:"currency"` DistanceUnit DistanceUnit `json:"distanceUnit"` Source string `json:"source"` + FuelSubType string `json:"fuelSubType"` } func (v *Fillup) FuelUnitDetail() EnumDetail { diff --git a/server/models/vehicle.go b/server/models/vehicle.go index fc5d044..6e394b4 100644 --- a/server/models/vehicle.go +++ b/server/models/vehicle.go @@ -50,6 +50,7 @@ type CreateFillupRequest struct { FillingStation string `form:"fillingStation" json:"fillingStation"` UserID string `form:"userId" json:"userId" binding:"required"` Date time.Time `form:"date" json:"date" binding:"required" time_format:"2006-01-02"` + FuelSubType string `form:"fuelSubType" json:"fuelSubType"` } type UpdateFillupRequest struct { diff --git a/server/service/vehicleService.go b/server/service/vehicleService.go index e8ee23a..5e853af 100644 --- a/server/service/vehicleService.go +++ b/server/service/vehicleService.go @@ -139,6 +139,7 @@ func CreateFillup(model models.CreateFillupRequest) (*db.Fillup, error) { Date: model.Date, Currency: user.Currency, DistanceUnit: user.DistanceUnit, + FuelSubType: model.FuelSubType, Source: "API", } @@ -196,6 +197,7 @@ func UpdateFillup(fillupId string, model models.UpdateFillupRequest) error { Comments: model.Comments, FillingStation: model.FillingStation, UserID: model.UserID, + FuelSubType: model.FuelSubType, Date: model.Date, }).Error } @@ -235,6 +237,11 @@ func GetVehicleAttachments(vehicleId string) (*[]db.Attachment, error) { return db.GetVehicleAttachments(vehicleId) } +func GetDistinctFuelSubtypesForVehicle(vehicleId string) ([]string, error) { + var names []string + tx := db.DB.Model(&db.Fillup{}).Where("vehicle_id=? and fuel_sub_type is not null", vehicleId).Distinct().Pluck("fuel_sub_type", &names) + return names, tx.Error +} func GetUserStats(userId string, model models.UserStatsQueryModel) ([]models.VehicleStatsModel, error) { diff --git a/ui/src/main.js b/ui/src/main.js index 191dd10..2ff0390 100644 --- a/ui/src/main.js +++ b/ui/src/main.js @@ -21,6 +21,7 @@ import { faTrash, faShare, faUserFriends, + faTimesCircle, } from '@fortawesome/free-solid-svg-icons' import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' @@ -51,6 +52,7 @@ library.add( faTrash, faShare, faUserFriends, + faTimesCircle ) Vue.use(Buefy, { defaultIconComponent: 'vue-fontawesome', diff --git a/ui/src/router/views/createFillup.vue b/ui/src/router/views/createFillup.vue index d2280e6..d26cf16 100644 --- a/ui/src/router/views/createFillup.vue +++ b/ui/src/router/views/createFillup.vue @@ -34,6 +34,7 @@ export default { quickEntry: null, myVehicles: [], users: [], + fuelSubTypes: [], selectedVehicle: this.vehicle, fillupModel: this.fillup, processQuickEntry: false, @@ -46,6 +47,14 @@ export default { }, ...mapState('users', ['me']), ...mapState('vehicles', ['fuelUnitMasters', 'fuelTypeMasters', 'vehicles']), + filteredFuelSubtypes() { + if (!this.fillupModel.fuelSubType) { + return this.fuelSubTypes + } + return this.fuelSubTypes.filter((option) => { + return option.toLowerCase().indexOf(this.fillupModel.fuelSubType.toLowerCase()) >= 0 + }) + }, }, watch: { 'fillupModel.fuelQuantity': function(old, newOne) { @@ -64,6 +73,7 @@ export default { this.myVehicles = this.vehicles this.selectedVehicle = this.vehicle this.fetchVehicleUsers() + this.fetchVehicleFuelSubTypes() if (!this.fillup.id) { this.fillupModel = this.getEmptyFillup() this.fillupModel.userId = this.me.id @@ -82,6 +92,14 @@ export default { }) .catch((err) => console.log(err)) }, + fetchVehicleFuelSubTypes() { + store + .dispatch('vehicles/fetchFuelSubtypesByVehicleId', { vehicleId: this.selectedVehicle.id }) + .then((data) => { + this.fuelSubTypes = data + }) + .catch((err) => console.log(err)) + }, getEmptyFillup() { return { vehicleId: this.selectedVehicle.id, @@ -96,6 +114,7 @@ export default { fillingStation: '', comments: '', userId: '', + fuelSubType: '', } }, async createFillup() { @@ -201,6 +220,17 @@ export default { > + + + + diff --git a/ui/src/router/views/settings.vue b/ui/src/router/views/settings.vue index d3bd10d..a0080f6 100644 --- a/ui/src/router/views/settings.vue +++ b/ui/src/router/views/settings.vue @@ -181,7 +181,7 @@ export default { - + diff --git a/ui/src/router/views/vehicle.vue b/ui/src/router/views/vehicle.vue index f130420..3c96d46 100644 --- a/ui/src/router/views/vehicle.vue +++ b/ui/src/router/views/vehicle.vue @@ -311,6 +311,9 @@ export default { {{ formatDate(props.row.date) }} + + {{ props.row.fuelSubType }} + {{ `${props.row.fuelQuantity} ${props.row.fuelUnitDetail.short}` }} diff --git a/ui/src/state/modules/vehicles.js b/ui/src/state/modules/vehicles.js index 075d29f..d9282be 100644 --- a/ui/src/state/modules/vehicles.js +++ b/ui/src/state/modules/vehicles.js @@ -140,6 +140,12 @@ export const actions = { return data }) }, + fetchFuelSubtypesByVehicleId({ commit, state, rootState }, { vehicleId, force }) { + return axios.get(`/api/vehicles/${vehicleId}/fuelSubTypes`).then((response) => { + const data = response.data + return data + }) + }, fetchStatsByVehicleId({ commit, state, rootState }, { vehicleId, force }) { if (state.vehicleStats.has(vehicleId) && !force) { return Promise.resolve(state.vehicleStats.get(vehicleId))
Current Version2021.07.142021.07.23
Website