diff --git a/server/controllers/import.go b/server/controllers/import.go index 17b0a0a..0e32186 100644 --- a/server/controllers/import.go +++ b/server/controllers/import.go @@ -4,6 +4,7 @@ import ( "net/http" "strconv" + "hammond/models" "hammond/service" "github.com/gin-gonic/gin" @@ -12,6 +13,7 @@ import ( func RegisteImportController(router *gin.RouterGroup) { router.POST("/import/fuelly", fuellyImport) router.POST("/import/drivvo", drivvoImport) + router.POST("/import/generic", genericImport) } func fuellyImport(c *gin.Context) { @@ -52,3 +54,21 @@ func drivvoImport(c *gin.Context) { } c.JSON(http.StatusOK, gin.H{}) } + +func genericImport(c *gin.Context) { + var json models.ImportData + if err := c.ShouldBindJSON(&json); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + if json.VehicleId == "" { + c.JSON(http.StatusUnprocessableEntity, "Missing Vehicle ID") + return + } + errors := service.GenericImport(json, c.MustGet("userId").(string)) + if len(errors) > 0 { + c.JSON(http.StatusUnprocessableEntity, gin.H{"errors": errors}) + return + } + c.JSON(http.StatusOK, gin.H{}) +} diff --git a/server/models/import.go b/server/models/import.go new file mode 100644 index 0000000..bf342b0 --- /dev/null +++ b/server/models/import.go @@ -0,0 +1,22 @@ +package models + +type ImportData struct { + Data []ImportFillup `json:"data" binding:"required"` + VehicleId string `json:"vehicleId" binding:"required"` + TimeZone string `json:"timezone" binding:"required"` +} + +type ImportFillup struct { + VehicleID string `json:"vehicleId"` + FuelQuantity float32 `json:"fuelQuantity"` + PerUnitPrice float32 `json:"perUnitPrice"` + TotalAmount float32 `json:"totalAmount"` + OdoReading int `json:"odoReading"` + IsTankFull *bool `json:"isTankFull"` + HasMissedFillup *bool `json:"hasMissedFillup"` + Comments string `json:"comments"` + FillingStation string `json:"fillingStation"` + UserID string `json:"userId"` + Date string `json:"date"` + FuelSubType string `json:"fuelSubType"` +} \ No newline at end of file diff --git a/server/service/genericImportService.go b/server/service/genericImportService.go new file mode 100644 index 0000000..7f9e2c9 --- /dev/null +++ b/server/service/genericImportService.go @@ -0,0 +1,47 @@ +package service + +import ( + "hammond/db" + "hammond/models" + "time" +) + +func GenericParseRefuelings(content []models.ImportFillup, user *db.User, vehicle *db.Vehicle, timezone string) ([]db.Fillup, []string) { + var errors []string + var fillups []db.Fillup + dateLayout := "2006-01-02T15:04:05.000Z" + loc, _ := time.LoadLocation(timezone) + for _, record := range content { + date, err := time.ParseInLocation(dateLayout, record.Date, loc) + if err != nil { + date = time.Date(2000, time.December, 0, 0, 0, 0, 0, loc) + } + + var missedFillup bool + if record.HasMissedFillup == nil { + missedFillup = false + } else { + missedFillup = *record.HasMissedFillup + } + + fillups = append(fillups, db.Fillup{ + VehicleID: vehicle.ID, + UserID: user.ID, + Date: date, + IsTankFull: record.IsTankFull, + HasMissedFillup: &missedFillup, + FuelQuantity: float32(record.FuelQuantity), + PerUnitPrice: float32(record.PerUnitPrice), + FillingStation: record.FillingStation, + OdoReading: record.OdoReading, + TotalAmount: float32(record.TotalAmount), + FuelUnit: vehicle.FuelUnit, + Currency: user.Currency, + DistanceUnit: user.DistanceUnit, + Comments: record.Comments, + Source: "Generic Import", + }) + } + + return fillups, errors +} diff --git a/server/service/importService.go b/server/service/importService.go index 60192c2..fccc15a 100644 --- a/server/service/importService.go +++ b/server/service/importService.go @@ -4,6 +4,7 @@ import ( "bytes" "hammond/db" + "hammond/models" ) func WriteToDB(fillups []db.Fillup, expenses []db.Expense) []string { @@ -105,3 +106,27 @@ func FuellyImport(content []byte, userId string) []string { return WriteToDB(fillups, expenses) } + +func GenericImport(content models.ImportData, userId string) []string { + var errors []string + user, err := GetUserById(userId) + if err != nil { + errors = append(errors, err.Error()) + return errors + } + + vehicle, err := GetVehicleById(content.VehicleId) + if err != nil { + errors = append(errors, err.Error()) + return errors + } + + var fillups []db.Fillup + fillups, errors = GenericParseRefuelings(content.Data, user, vehicle, content.TimeZone) + + if len(errors) != 0 { + return errors + } + + return WriteToDB(fillups, nil) +} \ No newline at end of file diff --git a/ui/package.json b/ui/package.json index c166776..2d53a0b 100644 --- a/ui/package.json +++ b/ui/package.json @@ -36,6 +36,7 @@ "node-gyp": "^9.3.1", "normalize.css": "^8.0.1", "nprogress": "^0.2.0", + "papaparse": "^5.4.1", "vue": "^2.6.11", "vue-chartjs": "^3.5.1", "vue-i18n": "^8.28.2", diff --git a/ui/src/locales/de.json b/ui/src/locales/de.json index c2e2ac7..fa62964 100644 --- a/ui/src/locales/de.json +++ b/ui/src/locales/de.json @@ -137,7 +137,7 @@ "dontimportagain": "Achte darauf, dass du die Datei nicht erneut importierst, da dies zu mehrfachen Einträgen führen würde.", "checkpointsimportcsv": "Wenn du alle diese Punkte überprüft hast kannst du unten die CSV importieren.", "importhintunits": "Vergewissere dich ebenfalls, dass die Kraftstoffeinheit und der Kraftstofftyp im Fahrzeug richtig eingestellt sind.", - "importhintcurrdist": "Stelle sicher, dass die Währung und die Entfernungseinheit in Hammond korrekt eingestellt sind. Der Import erkennt die Währung nicht automatisch aus der CSV-Datei, sondern verwendet die für den Benutzer eingestellte Währung.", + "importhintcurrdist": "Stelle sicher, dass die Währung und die Entfernungseinheit in Hammond korrekt eingestellt sind. Der Import erkennt die Währung nicht automatisch aus der datei, sondern verwendet die für den Benutzer eingestellte Währung.", "importhintnickname": "Vergewissere dich, dass der Fahrzeugname in Hammond genau mit dem Namen in der Fuelly-CSV-Datei übereinstimmt, sonst funktioniert der Import nicht.", "importhintvehiclecreated": "Vergewissere dich, dass du die Fahrzeuge bereits in Hammond erstellt hast.", "importhintcreatecsv": "Exportiere deine Daten aus {name} im CSV-Format. Die Schritte dazu findest du", diff --git a/ui/src/locales/en.json b/ui/src/locales/en.json index 0bb7fc0..1216960 100644 --- a/ui/src/locales/en.json +++ b/ui/src/locales/en.json @@ -5,7 +5,7 @@ "thismonth": "This month", "pastxdays": "Past one day | Past {count} days", "pastxmonths": "Past one month | Past {count} months", - "thisyear": "This year", + "thisyear": "This year", "alltime": "All Time", "noattachments": "No Attachments so far", "attachments": "Attachments", @@ -43,15 +43,15 @@ "createnow": "Create Now", "yourvehicles": "Your Vehicles", "menu": { - "quickentries": "Quick Entries", - "logout": "Log out", - "import": "Import", - "home": "Home", - "settings": "Settings", - "admin": "Admin", - "sitesettings": "Site Settings", - "users": "Users", - "login": "Log in" + "quickentries": "Quick Entries", + "logout": "Log out", + "import": "Import", + "home": "Home", + "settings": "Settings", + "admin": "Admin", + "sitesettings": "Site Settings", + "users": "Users", + "login": "Log in" }, "enterusername": "Enter your username", "enterpassword": "Enter your password", @@ -81,34 +81,34 @@ "quantity": "Quantity", "gasstation": "Gas Station", "fuel": { - "petrol": "Petrol", - "diesel": "Diesel", - "cng": "CNG", - "lpg": "LPG", - "electric": "Electric", - "ethanol": "Ethanol" + "petrol": "Petrol", + "diesel": "Diesel", + "cng": "CNG", + "lpg": "LPG", + "electric": "Electric", + "ethanol": "Ethanol" }, "unit": { - "long": { - "litre": "Litre", - "gallon": "Gallon", - "kilowatthour": "Kilowatt Hour", - "kilogram": "Kilogram", - "usgallon": "US Gallon", - "minutes": "Minutes", - "kilometers": "Kilometers", - "miles": "Miles" - }, - "short": { - "litre": "Lt", - "gallon": "Gal", - "kilowatthour": "KwH", - "kilogram": "Kg", - "usgallon": "US Gal", - "minutes": "Mins", - "kilometers": "Km", - "miles": "Mi" - } + "long": { + "litre": "Litre", + "gallon": "Gallon", + "kilowatthour": "Kilowatt Hour", + "kilogram": "Kilogram", + "usgallon": "US Gallon", + "minutes": "Minutes", + "kilometers": "Kilometers", + "miles": "Miles" + }, + "short": { + "litre": "Lt", + "gallon": "Gal", + "kilowatthour": "KwH", + "kilogram": "Kg", + "usgallon": "US Gal", + "minutes": "Mins", + "kilometers": "Km", + "miles": "Mi" + } }, "avgfillupqty": "Avg Fillup Qty", "avgfillupexpense": "Avg Fillup Expense", @@ -117,7 +117,9 @@ "price": "Price", "total": "Total", "fulltank": "Tank Full", + "partialfillup": "Partial Fillup", "getafulltank": "Did you get a full tank?", + "tankpartialfull": "Which do you track?", "by": "By", "expenses": "Expenses", "expensetype": "Expense Type", @@ -130,6 +132,8 @@ "importdatadesc": "Choose from the following options to import data into Hammond", "import": "Import", "importcsv": "If you have been using {name} to store your vehicle data, export the CSV file from {name} and click here to import.", + "importgeneric": "Generic Fillups Import", + "importgenericdesc": "Fillups CSV import.", "choosecsv": "Choose CSV", "choosephoto": "Choose Photo", "importsuccessfull": "Data Imported Successfully", @@ -137,13 +141,15 @@ "importfrom": "Import from {0}", "stepstoimport": "Steps to import data from {name}", "choosecsvimport": "Choose the {name} CSV and press the import button.", + "choosedatafile": "Choose the CSV file and then press the import button.", "dontimportagain": "Make sure that you do not import the file again because that will create repeat entries.", "checkpointsimportcsv": "Once you have checked all these points, just import the CSV below.", "importhintunits": "Similiarly, make sure that the Fuel Unit and Fuel Type are correctly set in the Vehicle.", - "importhintcurrdist": "Make sure that the Currency and Distance Unit are set correctly in Hammond. Import will not autodetect Currency from the CSV but use the one set for the user.", + "importhintcurrdist": "Make sure that the Currency and Distance Unit are set correctly in Hammond. Import will not autodetect Currency from the file but use the one set for the user.", "importhintnickname": "Make sure that the Vehicle nickname in Hammond is exactly the same as the name on Fuelly CSV or the import will not work.", "importhintvehiclecreated": "Make sure that you have already created the vehicles in Hammond platform.", "importhintcreatecsv": "Export your data from {name} in the CSV format. Steps to do that can be found", + "importgenerichintdata": "Data must be in CSV format.", "here": "here", "unprocessedquickentries": "You have one quick entry to be processed. | You have {0} quick entries pending to be processed.", "show": "Show", @@ -178,6 +184,7 @@ "fillingstation": "Filling Station Name", "comments": "Comments", "missfillupbefore": "Did you miss the fillup entry before this one?", + "missedfillup": "Missed Fillup", "fillupdate": "Fillup Date", "fillupsavedsuccessfully": "Fillup Saved Successfully", "expensesavedsuccessfully": "Expense Saved Successfully", @@ -195,25 +202,25 @@ "testconn": "Test Connection", "migrate": "Migrate", "init": { - "migrateclarkson": "Migrate from Clarkson", - "migrateclarksondesc": "If you have an existing Clarkson deployment and you want to migrate your data from that, press the following button.", - "freshinstall": "Fresh Install", - "freshinstalldesc": "If you want a fresh install of Hammond, press the following button.", - "clarkson": { - "desc": "

You need to make sure that this deployment of Hammond can access the MySQL database used by Clarkson.

If that is not directly possible, you can make a copy of that database somewhere accessible from this instance.

Once that is done, enter the connection string to the MySQL instance in the following format.

All the users imported from Clarkson will have their username as their email in Clarkson database and pasword set tohammond

user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local

", - "success": "We have successfully migrated the data from Clarkson. You will be redirected to the login screen shortly where you can login using your existing email and password : hammond" - }, - "fresh": { - "setupadminuser": "Setup Admin Users", - "yourpassword": "Your Password", - "youremail": "Your Email", - "yourname": "Your Name", - "success": "You have been registered successfully. You will be redirected to the login screen shortly where you can login and start using the system." - } + "migrateclarkson": "Migrate from Clarkson", + "migrateclarksondesc": "If you have an existing Clarkson deployment and you want to migrate your data from that, press the following button.", + "freshinstall": "Fresh Install", + "freshinstalldesc": "If you want a fresh install of Hammond, press the following button.", + "clarkson": { + "desc": "

You need to make sure that this deployment of Hammond can access the MySQL database used by Clarkson.

If that is not directly possible, you can make a copy of that database somewhere accessible from this instance.

Once that is done, enter the connection string to the MySQL instance in the following format.

All the users imported from Clarkson will have their username as their email in Clarkson database and pasword set tohammond

user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local

", + "success": "We have successfully migrated the data from Clarkson. You will be redirected to the login screen shortly where you can login using your existing email and password : hammond" + }, + "fresh": { + "setupadminuser": "Setup Admin Users", + "yourpassword": "Your Password", + "youremail": "Your Email", + "yourname": "Your Name", + "success": "You have been registered successfully. You will be redirected to the login screen shortly where you can login and start using the system." + } }, "roles": { - "ADMIN": "ADMIN", - "USER": "USER" + "ADMIN": "ADMIN", + "USER": "USER" }, "profile": "Profile", "processedon": "Processed on", @@ -221,4 +228,4 @@ "disable": "Disable", "confirm": "Go Ahead", "labelforfile": "Label for this file" - } \ No newline at end of file +} \ No newline at end of file diff --git a/ui/src/main.js b/ui/src/main.js index cb336b0..e21c08b 100644 --- a/ui/src/main.js +++ b/ui/src/main.js @@ -7,6 +7,7 @@ import { faCheck, faTimes, faArrowUp, + faArrowRotateLeft, faAngleLeft, faAngleRight, faCalendar, @@ -38,6 +39,7 @@ library.add( faCheck, faTimes, faArrowUp, + faArrowRotateLeft, faAngleLeft, faAngleRight, faCalendar, diff --git a/ui/src/router/routes.js b/ui/src/router/routes.js index 011de78..c5d01c4 100644 --- a/ui/src/router/routes.js +++ b/ui/src/router/routes.js @@ -419,6 +419,15 @@ export default [ }, props: (route) => ({ user: store.state.auth.currentUser || {} }), }, + { + path: '/import/generic', + name: 'import-generic', + component: () => lazyLoadView(import('@views/import-generic.vue')), + meta: { + authRequired: true, + }, + props: (route) => ({ user: store.state.auth.currentUser || {} }), + }, { path: '/logout', name: 'logout', diff --git a/ui/src/router/views/import-generic.unit.js b/ui/src/router/views/import-generic.unit.js new file mode 100644 index 0000000..51d032e --- /dev/null +++ b/ui/src/router/views/import-generic.unit.js @@ -0,0 +1,7 @@ +import ImportGeneric from './import-generic' + +describe('@views/import-generic', () => { + it('is a valid view', () => { + expect(ImportGeneric).toBeAViewComponent() + }) +}) diff --git a/ui/src/router/views/import-generic.vue b/ui/src/router/views/import-generic.vue new file mode 100644 index 0000000..1945909 --- /dev/null +++ b/ui/src/router/views/import-generic.vue @@ -0,0 +1,411 @@ + + + diff --git a/ui/src/router/views/import.vue b/ui/src/router/views/import.vue index 40597e6..6a31b92 100644 --- a/ui/src/router/views/import.vue +++ b/ui/src/router/views/import.vue @@ -18,18 +18,19 @@ export default {