Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better? mileage compute #120

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 13 additions & 10 deletions server/models/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,19 @@ import (
)

type MileageModel struct {
Date time.Time `form:"date" json:"date" binding:"required" time_format:"2006-01-02"`
VehicleID string `form:"vehicleId" json:"vehicleId" binding:"required"`
FuelUnit db.FuelUnit `form:"fuelUnit" json:"fuelUnit" binding:"required"`
FuelQuantity float32 `form:"fuelQuantity" json:"fuelQuantity" binding:"required"`
PerUnitPrice float32 `form:"perUnitPrice" json:"perUnitPrice" binding:"required"`
Currency string `json:"currency"`
DistanceUnit db.DistanceUnit `form:"distanceUnit" json:"distanceUnit"`
Mileage float32 `form:"mileage" json:"mileage" binding:"mileage"`
CostPerMile float32 `form:"costPerMile" json:"costPerMile" binding:"costPerMile"`
OdoReading int `form:"odoReading" json:"odoReading" binding:"odoReading"`
Date time.Time `form:"date" json:"date" binding:"required" time_format:"2006-01-02"`
StartDate time.Time `form:"startDate" json:"startDate" binding:"required" time_format:"2006-01-02"`
EndDate time.Time `form:"endDate" json:"endDate" binding:"required" time_format:"2006-01-02"`
VehicleID string `form:"vehicleId" json:"vehicleId" binding:"required"`
FuelUnit db.FuelUnit `form:"fuelUnit" json:"fuelUnit" binding:"required"`
FuelQuantity float32 `form:"fuelQuantity" json:"fuelQuantity" binding:"required"`
PerUnitPrice float32 `form:"perUnitPrice" json:"perUnitPrice" binding:"required"`
Currency string `json:"currency"`
DistanceUnit db.DistanceUnit `form:"distanceUnit" json:"distanceUnit"`
Mileage float32 `form:"mileage" json:"mileage" binding:"mileage"`
CostPerMile float32 `form:"costPerMile" json:"costPerMile" binding:"costPerMile"`
OdoReading int `form:"odoReading" json:"odoReading" binding:"odoReading"`
DistanceTravel float32 `form:"distanceTravel" json:"distanceTravel" binding:"distanceTravel"`
}

func (v *MileageModel) FuelUnitDetail() db.EnumDetail {
Expand Down
238 changes: 178 additions & 60 deletions server/service/reportService.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,116 @@ import (
"hammond/models"
)

type Range struct {
Start int
End int
}

func ComputeMileage_litre_100km(fillups []db.Fillup, mileages *[]models.MileageModel) {
var ranges []Range

// fillups ordered by odo descending
// travel from lower odo to higher
for i := len(fillups) - 1; i >= 0; i-- {
currentFillup := fillups[i]
currTankFull := currentFillup.IsTankFull != nil && *currentFillup.IsTankFull
if !currTankFull {
continue
}

for j := i-1; j >= 0; j-- {
lastFillup := fillups[j]
lastHasMissed := lastFillup.HasMissedFillup != nil && *lastFillup.HasMissedFillup
if lastHasMissed {
i = j + 1 // so that i loop lands on j in the next iter
break
}

lastTankFull := lastFillup.IsTankFull != nil && *lastFillup.IsTankFull
if lastTankFull {
ranges = append(ranges, Range{Start: i, End: j})
i = j + 1
break
}
}
}

for _, r := range ranges {
startFillup := fillups[r.Start]
endFillup := fillups[r.End]

mileage := models.MileageModel{
Date: startFillup.Date,
StartDate: startFillup.Date,
EndDate: endFillup.Date,
VehicleID: startFillup.VehicleID,
FuelUnit: db.LITRE,
FuelQuantity: 0,
PerUnitPrice: startFillup.PerUnitPrice,
OdoReading: 0,
DistanceTravel: 0,
Currency: startFillup.Currency,
DistanceUnit: db.KILOMETERS,
Mileage: 0,
CostPerMile: 0,
}

// This computes the distance traveled
startOdo := float32(startFillup.OdoReading)
endOdo := float32(endFillup.OdoReading)

// Convert into km if needed
if startFillup.DistanceUnit != mileage.DistanceUnit {
startOdo = common.MilesToKm(startOdo)
}
if endFillup.DistanceUnit != mileage.DistanceUnit {
endOdo = common.MilesToKm(endOdo)
}
mileage.DistanceTravel = endOdo - startOdo

// This computes the spent fuel
// first we convert into Litre
// reverse order...
// also should skip start fillup
for idx := r.Start-1; idx >= r.End; idx-- {
f := fillups[idx]
if (f.FuelUnit != mileage.FuelUnit) {
f.FuelUnit = mileage.FuelUnit
f.FuelQuantity = common.GallonToLitre(f.FuelQuantity)
}
// second sum them all
mileage.FuelQuantity += f.FuelQuantity
}
// third divide
mileage.Mileage = mileage.FuelQuantity / mileage.DistanceTravel
mileage.Mileage *= 100
// forth append
// append blank
// if last date != this date
// meaning has missing fillups
mileagesSize := len(*mileages)
if mileagesSize > 0 && ((*mileages)[mileagesSize-1].EndDate != mileage.StartDate) {
blankMileage := models.MileageModel {
Date: (*mileages)[mileagesSize-1].EndDate,
StartDate: (*mileages)[mileagesSize-1].EndDate,
EndDate: mileage.StartDate,
VehicleID: startFillup.VehicleID,
FuelUnit: db.LITRE,
FuelQuantity: 0,
PerUnitPrice: startFillup.PerUnitPrice,
OdoReading: 0,
DistanceTravel: 0,
Currency: startFillup.Currency,
DistanceUnit: db.KILOMETERS,
Mileage: 0,
CostPerMile: 0,
}
*mileages = append(*mileages, blankMileage)
}
*mileages = append(*mileages, mileage)
}
}

func GetMileageByVehicleId(vehicleId string, since time.Time, mileageOption string) (mileage []models.MileageModel, err error) {
data, err := db.GetFillupsByVehicleIdSince(vehicleId, since)
if err != nil {
Expand All @@ -23,67 +133,75 @@ func GetMileageByVehicleId(vehicleId string, since time.Time, mileageOption stri

var mileages []models.MileageModel

for i := 0; i < len(fillups)-1; i++ {
last := i + 1
if mileageOption == "litre_100km" {
ComputeMileage_litre_100km(fillups, &mileages)
} else {

for i := 0; i < len(fillups)-1; i++ {
last := i + 1

currentFillup := fillups[i]
lastFillup := fillups[last]

mileage := models.MileageModel{
StartDate: currentFillup.Date,
EndDate: currentFillup.Date,
Date: currentFillup.Date,
VehicleID: currentFillup.VehicleID,
FuelUnit: currentFillup.FuelUnit,
FuelQuantity: currentFillup.FuelQuantity,
PerUnitPrice: currentFillup.PerUnitPrice,
OdoReading: currentFillup.OdoReading,
Currency: currentFillup.Currency,
DistanceUnit: currentFillup.DistanceUnit,
Mileage: 0,
CostPerMile: 0,
}

if currentFillup.IsTankFull != nil && *currentFillup.IsTankFull && (currentFillup.HasMissedFillup == nil || !(*currentFillup.HasMissedFillup)) {
currentOdoReading := float32(currentFillup.OdoReading);
lastFillupOdoReading := float32(lastFillup.OdoReading);
currentFuelQuantity := float32(currentFillup.FuelQuantity);
// If miles per gallon option and distanceUnit is km, convert from km to miles
// then check if fuel unit is litres. If it is, convert to gallons
if (mileageOption == "mpg" && mileage.DistanceUnit == db.KILOMETERS) {
currentOdoReading = common.KmToMiles(currentOdoReading);
lastFillupOdoReading = common.KmToMiles(lastFillupOdoReading);
if (mileage.FuelUnit == db.LITRE) {
currentFuelQuantity = common.LitreToGallon(currentFuelQuantity);
}
}

// If km_litre option or litre per 100km and distanceUnit is miles, convert from miles to km
// then check if fuel unit is not litres. If it isn't, convert to litres

if ((mileageOption == "km_litre" || mileageOption == "litre_100km") && mileage.DistanceUnit == db.MILES) {
currentOdoReading = common.MilesToKm(currentOdoReading);
lastFillupOdoReading = common.MilesToKm(lastFillupOdoReading);

if (mileage.FuelUnit == db.US_GALLON) {
currentFuelQuantity = common.GallonToLitre(currentFuelQuantity);
}
}




distance := float32(currentOdoReading - lastFillupOdoReading);
if (mileageOption == "litre_100km") {
mileage.Mileage = currentFuelQuantity / distance * 100;
} else {
mileage.Mileage = distance / currentFuelQuantity;
}

mileage.CostPerMile = distance / currentFillup.TotalAmount;

}

mileages = append(mileages, mileage)
}
}

currentFillup := fillups[i]
lastFillup := fillups[last]

mileage := models.MileageModel{
Date: currentFillup.Date,
VehicleID: currentFillup.VehicleID,
FuelUnit: currentFillup.FuelUnit,
FuelQuantity: currentFillup.FuelQuantity,
PerUnitPrice: currentFillup.PerUnitPrice,
OdoReading: currentFillup.OdoReading,
Currency: currentFillup.Currency,
DistanceUnit: currentFillup.DistanceUnit,
Mileage: 0,
CostPerMile: 0,
}

if currentFillup.IsTankFull != nil && *currentFillup.IsTankFull && (currentFillup.HasMissedFillup == nil || !(*currentFillup.HasMissedFillup)) {
currentOdoReading := float32(currentFillup.OdoReading);
lastFillupOdoReading := float32(lastFillup.OdoReading);
currentFuelQuantity := float32(currentFillup.FuelQuantity);
// If miles per gallon option and distanceUnit is km, convert from km to miles
// then check if fuel unit is litres. If it is, convert to gallons
if (mileageOption == "mpg" && mileage.DistanceUnit == db.KILOMETERS) {
currentOdoReading = common.KmToMiles(currentOdoReading);
lastFillupOdoReading = common.KmToMiles(lastFillupOdoReading);
if (mileage.FuelUnit == db.LITRE) {
currentFuelQuantity = common.LitreToGallon(currentFuelQuantity);
}
}

// If km_litre option or litre per 100km and distanceUnit is miles, convert from miles to km
// then check if fuel unit is not litres. If it isn't, convert to litres

if ((mileageOption == "km_litre" || mileageOption == "litre_100km") && mileage.DistanceUnit == db.MILES) {
currentOdoReading = common.MilesToKm(currentOdoReading);
lastFillupOdoReading = common.MilesToKm(lastFillupOdoReading);

if (mileage.FuelUnit == db.US_GALLON) {
currentFuelQuantity = common.GallonToLitre(currentFuelQuantity);
}
}




distance := float32(currentOdoReading - lastFillupOdoReading);
if (mileageOption == "litre_100km") {
mileage.Mileage = currentFuelQuantity / distance * 100;
} else {
mileage.Mileage = distance / currentFuelQuantity;
}

mileage.CostPerMile = distance / currentFillup.TotalAmount;

}

mileages = append(mileages, mileage)
}
if mileages == nil {
mileages = make([]models.MileageModel, 0)
}
Expand Down
87 changes: 87 additions & 0 deletions ui/src/components/mileageTable.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<script>
import axios from 'axios'
import { mapState } from 'vuex'
import { string } from 'yargs'
export default {
props: {
vehicle: { type: Object, required: true },
since: { type: Date, default: '' },
user: { type: Object, required: true },
mileageOption: { type: string, default: 'litre_100km' },
},
data: function() {
return {
chartData: [],
}
},
computed: {
...mapState('utils', ['isMobile']),
},
watch: {
since(newOne, old) {
if (newOne === old) {
return
}
this.fetchMileage()
},
},
mounted() {
this.fetchMileage()
},
methods: {
showChart() {
let mileageLabel = ''
switch (this.mileageOption) {
case 'litre_100km':
mileageLabel = 'L/100km'
break
case 'km_litre':
mileageLabel = 'km/L'
break
case 'mpg':
mileageLabel = 'mpg'
break
}

var labels = this.chartData.map((x) => x.date.substr(0, 10))
var dataset = {
steppedLine: true,
label: `Mileage (${mileageLabel})`,
fill: true,
data: this.chartData.map((x) => x.mileage),
}
this.renderChart({ labels, datasets: [dataset] }, { responsive: true, maintainAspectRatio: false })
},
fetchMileage() {
axios
.get(`/api/vehicles/${this.vehicle.id}/mileage`, {
params: {
since: this.since,
mileageOption: this.mileageOption,
},
})
.then((response) => {
this.chartData = response.data
this.showChart()
})
.catch((err) => console.log(err))
},
getDataGap(data) {
const start = new Date(data.startDate);
const end = new Date(data.endDate);
const diffTime = Math.abs(end - start);
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
return diffDays;
},
formatDate(date) {
return new Date(date).toLocaleDateString();
},
},
}
</script>

<template>
<div>
<p v-for="data in chartData" :key="data.date">{{ formatDate(data.date) }} - {{ (data.mileage).toFixed(2) }}</p>
</div>
</template>
Loading