From bfaebf17d0a3adfce2a77888c857b19f3cf5d430 Mon Sep 17 00:00:00 2001 From: Alf Sebastian Houge Date: Mon, 4 Apr 2022 14:55:22 +0200 Subject: [PATCH 01/10] Add functions for parsing drivvo CSVs. --- server/controllers/import.go | 15 +++ server/service/importService.go | 182 ++++++++++++++++++++++++++ ui/src/router/routes.js | 9 ++ ui/src/router/views/import-drivvo.vue | 146 +++++++++++++++++++++ ui/src/router/views/import.vue | 21 ++- 5 files changed, 368 insertions(+), 5 deletions(-) create mode 100644 ui/src/router/views/import-drivvo.vue diff --git a/server/controllers/import.go b/server/controllers/import.go index 38a897d..3ea9a9d 100644 --- a/server/controllers/import.go +++ b/server/controllers/import.go @@ -9,6 +9,7 @@ import ( func RegisteImportController(router *gin.RouterGroup) { router.POST("/import/fuelly", fuellyImport) + router.POST("/import/drivvo", drivvoImport) } func fuellyImport(c *gin.Context) { @@ -24,3 +25,17 @@ func fuellyImport(c *gin.Context) { } c.JSON(http.StatusOK, gin.H{}) } + +func drivvoImport(c *gin.Context) { + bytes, err := getFileBytes(c, "file") + if err != nil { + c.JSON(http.StatusUnprocessableEntity, err) + return + } + errors := service.DrivvoImport(bytes, 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/service/importService.go b/server/service/importService.go index dbd5eab..2c97d09 100644 --- a/server/service/importService.go +++ b/server/service/importService.go @@ -11,6 +11,188 @@ import ( "github.com/leekchan/accounting" ) +// TODO: Move drivvo stuff to separate file + +func DrivvoParseExpenses(content []byte, user *db.User) ([]db.Expense, []string) { + expenseReader := csv.NewReader(bytes.NewReader(content)) + expenseReader.Comment = '#' + // Read headers (there is a trailing comma at the end, that's why we have to read the first line) + expenseReader.Read() + expenseReader.FieldsPerRecord = 6 + expenseRecords, err := expenseReader.ReadAll() + + var errors []string + if err != nil { + errors = append(errors, err.Error()) + println(err.Error()) + return nil, errors + } + + var expenses []db.Expense + for index, record := range expenseRecords { + expense := db.Expense{} + + date, err := time.Parse("2006-01-02 15:04:05", record[1]) + if err != nil { + errors = append(errors, "Found an invalid date/time at service/expense row "+strconv.Itoa(index+1)) + } + expense.Date = date + + totalCost, err := strconv.ParseFloat(record[2], 32) + if err != nil { + errors = append(errors, "Found and invalid total cost at service/expense row "+strconv.Itoa(index+1)) + } + expense.Amount = float32(totalCost) + + odometer, err := strconv.Atoi(record[0]) + if err != nil { + errors = append(errors, "Found an invalid odometer reading at service/expense row "+strconv.Itoa(index+1)) + } + expense.OdoReading = odometer + + notes := fmt.Sprintf("Location: %s\nNotes: %s\n", record[4], record[5]) + expense.Comments = notes + + expense.ExpenseType = record[3] + expense.UserID = user.ID + expense.Currency = user.Currency + expense.DistanceUnit = user.DistanceUnit + expense.Source = "Drivvo" + + expenses = append(expenses, expense) + } + + return expenses, errors +} + +func DrivvoParseRefuelings(content []byte, user *db.User) ([]db.Fillup, []string) { + refuelingReader := csv.NewReader(bytes.NewReader(content)) + refuelingReader.Comment = '#' + refuelingRecords, err := refuelingReader.ReadAll() + + var errors []string + if err != nil { + errors = append(errors, err.Error()) + println(err.Error()) + return nil, errors + } + + var fillups []db.Fillup + for index, record := range refuelingRecords { + // Skip column titles + if index == 0 { + continue + } + + fillup := db.Fillup{} + + date, err := time.Parse("2006-01-02 15:04:05", record[1]) + if err != nil { + errors = append(errors, "Found an invalid date/time at refuel row "+strconv.Itoa(index+1)) + } + fillup.Date = date + + totalCost, err := strconv.ParseFloat(record[4], 32) + if err != nil { + errors = append(errors, "Found and invalid total cost at refuel row "+strconv.Itoa(index+1)) + } + fillup.TotalAmount = float32(totalCost) + + odometer, err := strconv.Atoi(record[0]) + if err != nil { + errors = append(errors, "Found an invalid odometer reading at refuel row "+strconv.Itoa(index+1)) + } + fillup.OdoReading = odometer + + // TODO: Make optional + location := record[17] + fillup.FillingStation = location + + pricePerUnit, err := strconv.ParseFloat(record[3], 32) + if err != nil { + // TODO: Add unit type to error message + errors = append(errors, "Found an invalid cost per unit at refuel row "+strconv.Itoa(index+1)) + } + fillup.PerUnitPrice = float32(pricePerUnit) + + quantity, err := strconv.ParseFloat(record[5], 32) + if err != nil { + errors = append(errors, "Found an invalid quantity at refuel row "+strconv.Itoa(index+1)) + } + fillup.FuelQuantity = float32(quantity) + + isTankFull := record[6] == "Yes" + fillup.IsTankFull = &isTankFull + + // Unfortunatly, drivvo doesn't expose this info in their export + fal := false + fillup.HasMissedFillup = &fal + + notes := fmt.Sprintf("Reason: %s\nNotes: %s\nFuel: %s\n", record[18], record[19], record[2]) + fillup.Comments = notes + + fillup.UserID = user.ID + fillup.Currency = user.Currency + fillup.DistanceUnit = user.DistanceUnit + fillup.Source = "Drivvo" + + fillups = append(fillups, fillup) + } + return fillups, errors +} + +func DrivvoImport(content []byte, userId string) []string { + var errors []string + user, err := GetUserById(userId) + if err != nil { + errors = append(errors, err.Error()) + return errors + } + + serviceSectionIndex := bytes.Index(content, []byte("#Service")) + + endParseIndex := bytes.Index(content, []byte("#Income")) + if endParseIndex == -1 { + endParseIndex = bytes.Index(content, []byte("#Route")) + if endParseIndex == -1 { + endParseIndex = len(content) + } + + } + + expenseSectionIndex := bytes.Index(content, []byte("#Expense")) + if expenseSectionIndex == -1 { + expenseSectionIndex = endParseIndex + } + + fillups, errors := DrivvoParseRefuelings(content[:serviceSectionIndex], user) + _ = fillups + + var allExpenses []db.Expense + if serviceSectionIndex != -1 { + services, parseErrors := DrivvoParseExpenses(content[serviceSectionIndex:expenseSectionIndex], user) + if parseErrors != nil { + errors = append(errors, parseErrors...) + } + allExpenses = append(allExpenses, services...) + } + + if expenseSectionIndex != endParseIndex { + expenses, parseErrors := DrivvoParseExpenses(content[expenseSectionIndex:endParseIndex], user) + if parseErrors != nil { + errors = append(errors, parseErrors...) + } + allExpenses = append(allExpenses, expenses...) + } + + if len(errors) != 0 { + return errors + } + + errors = append(errors, "Not implemented") + return errors +} + func FuellyImport(content []byte, userId string) []string { stream := bytes.NewReader(content) reader := csv.NewReader(stream) diff --git a/ui/src/router/routes.js b/ui/src/router/routes.js index 82495f7..011de78 100644 --- a/ui/src/router/routes.js +++ b/ui/src/router/routes.js @@ -410,6 +410,15 @@ export default [ }, props: (route) => ({ user: store.state.auth.currentUser || {} }), }, + { + path: '/import/drivvo', + name: 'import-drivvo', + component: () => lazyLoadView(import('@views/import-drivvo.vue')), + meta: { + authRequired: true, + }, + props: (route) => ({ user: store.state.auth.currentUser || {} }), + }, { path: '/logout', name: 'logout', diff --git a/ui/src/router/views/import-drivvo.vue b/ui/src/router/views/import-drivvo.vue new file mode 100644 index 0000000..f30b377 --- /dev/null +++ b/ui/src/router/views/import-drivvo.vue @@ -0,0 +1,146 @@ + + + diff --git a/ui/src/router/views/import.vue b/ui/src/router/views/import.vue index f92b9f9..0742cbd 100644 --- a/ui/src/router/views/import.vue +++ b/ui/src/router/views/import.vue @@ -26,11 +26,22 @@ export default { >
-
-

Fuelly

-

If you have been using Fuelly to store your vehicle data, export the CSV file from Fuelly and click here to import.

-
- Import +
+
+

Fuelly

+

If you have been using Fuelly to store your vehicle data, export the CSV file from Fuelly and click here to import.

+
+ Import +
+
+ +
+
+

Drivvo

+

Import data stored in Drivvo to Hammond by exporting the CSV file from Drivvo and uploading it here.

+
+ Import +
From e8f7815d8df6a6956bcc07cbe2c9d9c215345373 Mon Sep 17 00:00:00 2001 From: Alf Sebastian Houge Date: Mon, 4 Apr 2022 17:13:14 +0200 Subject: [PATCH 02/10] Select vehicle when importing drivvo csv. Select what vehicle to import for on frontend. Actually import the data to the db --- server/controllers/import.go | 7 ++- server/service/importService.go | 45 +++++++++++++++--- ui/src/router/views/import-drivvo.vue | 67 ++++++++++++++++----------- ui/src/router/views/import-fuelly.vue | 2 +- 4 files changed, 84 insertions(+), 37 deletions(-) diff --git a/server/controllers/import.go b/server/controllers/import.go index 3ea9a9d..5d36d53 100644 --- a/server/controllers/import.go +++ b/server/controllers/import.go @@ -32,7 +32,12 @@ func drivvoImport(c *gin.Context) { c.JSON(http.StatusUnprocessableEntity, err) return } - errors := service.DrivvoImport(bytes, c.MustGet("userId").(string)) + vehicleId := c.PostForm("vehicleID") + if vehicleId == "" { + c.JSON(http.StatusUnprocessableEntity, "Missing Vehicle ID") + return + } + errors := service.DrivvoImport(bytes, c.MustGet("userId").(string), vehicleId) if len(errors) > 0 { c.JSON(http.StatusUnprocessableEntity, gin.H{"errors": errors}) return diff --git a/server/service/importService.go b/server/service/importService.go index 2c97d09..68619ea 100644 --- a/server/service/importService.go +++ b/server/service/importService.go @@ -13,7 +13,7 @@ import ( // TODO: Move drivvo stuff to separate file -func DrivvoParseExpenses(content []byte, user *db.User) ([]db.Expense, []string) { +func DrivvoParseExpenses(content []byte, user *db.User, vehicle *db.Vehicle) ([]db.Expense, []string) { expenseReader := csv.NewReader(bytes.NewReader(content)) expenseReader.Comment = '#' // Read headers (there is a trailing comma at the end, that's why we have to read the first line) @@ -53,6 +53,7 @@ func DrivvoParseExpenses(content []byte, user *db.User) ([]db.Expense, []string) notes := fmt.Sprintf("Location: %s\nNotes: %s\n", record[4], record[5]) expense.Comments = notes + expense.VehicleID = vehicle.ID expense.ExpenseType = record[3] expense.UserID = user.ID expense.Currency = user.Currency @@ -65,7 +66,7 @@ func DrivvoParseExpenses(content []byte, user *db.User) ([]db.Expense, []string) return expenses, errors } -func DrivvoParseRefuelings(content []byte, user *db.User) ([]db.Fillup, []string) { +func DrivvoParseRefuelings(content []byte, user *db.User, vehicle *db.Vehicle) ([]db.Fillup, []string) { refuelingReader := csv.NewReader(bytes.NewReader(content)) refuelingReader.Comment = '#' refuelingRecords, err := refuelingReader.ReadAll() @@ -131,6 +132,8 @@ func DrivvoParseRefuelings(content []byte, user *db.User) ([]db.Fillup, []string notes := fmt.Sprintf("Reason: %s\nNotes: %s\nFuel: %s\n", record[18], record[19], record[2]) fillup.Comments = notes + fillup.VehicleID = vehicle.ID + fillup.FuelUnit = vehicle.FuelUnit fillup.UserID = user.ID fillup.Currency = user.Currency fillup.DistanceUnit = user.DistanceUnit @@ -141,7 +144,7 @@ func DrivvoParseRefuelings(content []byte, user *db.User) ([]db.Fillup, []string return fillups, errors } -func DrivvoImport(content []byte, userId string) []string { +func DrivvoImport(content []byte, userId string, vehicleId string) []string { var errors []string user, err := GetUserById(userId) if err != nil { @@ -149,6 +152,12 @@ func DrivvoImport(content []byte, userId string) []string { return errors } + vehicle, err := GetVehicleById(vehicleId) + if err != nil { + errors = append(errors, err.Error()) + return errors + } + serviceSectionIndex := bytes.Index(content, []byte("#Service")) endParseIndex := bytes.Index(content, []byte("#Income")) @@ -165,12 +174,12 @@ func DrivvoImport(content []byte, userId string) []string { expenseSectionIndex = endParseIndex } - fillups, errors := DrivvoParseRefuelings(content[:serviceSectionIndex], user) + fillups, errors := DrivvoParseRefuelings(content[:serviceSectionIndex], user, vehicle) _ = fillups var allExpenses []db.Expense if serviceSectionIndex != -1 { - services, parseErrors := DrivvoParseExpenses(content[serviceSectionIndex:expenseSectionIndex], user) + services, parseErrors := DrivvoParseExpenses(content[serviceSectionIndex:expenseSectionIndex], user, vehicle) if parseErrors != nil { errors = append(errors, parseErrors...) } @@ -178,7 +187,7 @@ func DrivvoImport(content []byte, userId string) []string { } if expenseSectionIndex != endParseIndex { - expenses, parseErrors := DrivvoParseExpenses(content[expenseSectionIndex:endParseIndex], user) + expenses, parseErrors := DrivvoParseExpenses(content[expenseSectionIndex:endParseIndex], user, vehicle) if parseErrors != nil { errors = append(errors, parseErrors...) } @@ -189,7 +198,29 @@ func DrivvoImport(content []byte, userId string) []string { return errors } - errors = append(errors, "Not implemented") + tx := db.DB.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + if err := tx.Error; err != nil { + errors = append(errors, err.Error()) + return errors + } + if err := tx.Create(&fillups).Error; err != nil { + tx.Rollback() + errors = append(errors, err.Error()) + return errors + } + if err := tx.Create(&allExpenses).Error; err != nil { + tx.Rollback() + errors = append(errors, err.Error()) + return errors + } + if err := tx.Commit().Error; err != nil { + errors = append(errors, err.Error()) + } return errors } diff --git a/ui/src/router/views/import-drivvo.vue b/ui/src/router/views/import-drivvo.vue index f30b377..b2d12e8 100644 --- a/ui/src/router/views/import-drivvo.vue +++ b/ui/src/router/views/import-drivvo.vue @@ -9,8 +9,24 @@ export default { meta: [{ name: 'description', content: 'The Import Drivvo page.' }], }, components: { Layout }, + props: { + user: { + type: Object, + required: true, + }, + }, + data: function() { + return { + myVehicles: [], + file: null, + selectedVehicle: null, + tryingToCreate: false, + errors: [], + } + }, computed: { ...mapState('utils', ['isMobile']), + ...mapState('vehicles', ['vehicles']), uploadButtonLabel() { if (this.isMobile) { if (this.file == null) { @@ -27,18 +43,8 @@ export default { } }, }, - props: { - user: { - type: Object, - required: true, - }, - }, - data: function() { - return { - file: null, - tryingToCreate: false, - errors: [], - } + mounted() { + this.myVehicles = this.vehicles }, methods: { importDrivvo() { @@ -49,6 +55,7 @@ export default { this.tryingToCreate = true this.errorMessage = '' const formData = new FormData() + formData.append('vehicleID', this.selectedVehicle) formData.append('file', this.file, this.file.name) axios .post(`/api/import/drivvo`, formData) @@ -92,31 +99,35 @@ export default {

Steps to import data from Drivvo

    +
  1. Export your data from Drivvo in the CSV format.
  2. +
  3. Select the vehicle the exported data is for. You may need to create the vehicle in Hammond first if you haven't already done so
  4. Export your data from Fuelly in the CSV format. Steps to do that can be found - here.
  5. -
  6. Make sure that you have already created the vehicles in Hammond platform.
  7. -
  8. Make sure that the Vehicle nickname in Hammond is exactly the same as the name on Fuelly CSV or the import will not work.
  9. -
  10. 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.
  11. Make sure that the Currency and Distance Unit are set correctly in Hammond. Drivvo does not include this information in + their export, instead Hammond will use the values set for the user.
  12. Similiarly, make sure that the Fuel Unit and Fuel Type are correctly set in the Vehicle.
  13. -
  14. Once you have checked all these points,just import the CSV below.
  15. -
  16. Make sure that you do not import the file again and that will create repeat entries.
  17. +
  18. Once you have checked all these points, select the vehicle and import the CSV below.
  19. +
  20. Make sure that you do not import the file again as that will create repeat entries.
-
-

Choose the Drivvo CSV and press the import button.

-
+
+

Choose the vehicle, then select the Drivvo CSV and press the import button.

+
-
+
+
+ + + + + +
+ +
- + {{ uploadButtonLabel }} diff --git a/ui/src/router/views/import-fuelly.vue b/ui/src/router/views/import-fuelly.vue index 5a43d5a..76d9e03 100644 --- a/ui/src/router/views/import-fuelly.vue +++ b/ui/src/router/views/import-fuelly.vue @@ -114,7 +114,7 @@ export default {
- + {{ uploadButtonLabel }} From 1e099ec8b641e339948b3a939fc8250192e51fb6 Mon Sep 17 00:00:00 2001 From: Alf Sebastian Houge Date: Tue, 5 Apr 2022 21:52:58 +0200 Subject: [PATCH 03/10] Create db.Expense and db.Fillup in one place Create Expense and Fillup in one place instead of throughout function --- server/service/importService.go | 66 ++++++++++++++++----------------- 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/server/service/importService.go b/server/service/importService.go index 68619ea..ac58d15 100644 --- a/server/service/importService.go +++ b/server/service/importService.go @@ -5,6 +5,7 @@ import ( "encoding/csv" "fmt" "strconv" + "strings" "time" "github.com/akhilrex/hammond/db" @@ -30,37 +31,35 @@ func DrivvoParseExpenses(content []byte, user *db.User, vehicle *db.Vehicle) ([] var expenses []db.Expense for index, record := range expenseRecords { - expense := db.Expense{} - date, err := time.Parse("2006-01-02 15:04:05", record[1]) if err != nil { errors = append(errors, "Found an invalid date/time at service/expense row "+strconv.Itoa(index+1)) } - expense.Date = date totalCost, err := strconv.ParseFloat(record[2], 32) if err != nil { errors = append(errors, "Found and invalid total cost at service/expense row "+strconv.Itoa(index+1)) } - expense.Amount = float32(totalCost) odometer, err := strconv.Atoi(record[0]) if err != nil { errors = append(errors, "Found an invalid odometer reading at service/expense row "+strconv.Itoa(index+1)) } - expense.OdoReading = odometer notes := fmt.Sprintf("Location: %s\nNotes: %s\n", record[4], record[5]) - expense.Comments = notes - expense.VehicleID = vehicle.ID - expense.ExpenseType = record[3] - expense.UserID = user.ID - expense.Currency = user.Currency - expense.DistanceUnit = user.DistanceUnit - expense.Source = "Drivvo" - - expenses = append(expenses, expense) + expenses = append(expenses, db.Expense{ + UserID: user.ID, + VehicleID: vehicle.ID, + Date: date, + OdoReading: odometer, + Amount: float32(totalCost), + ExpenseType: record[3], + Currency: user.Currency, + DistanceUnit: user.DistanceUnit, + Comments: notes, + Source: "Drivvo", + }) } return expenses, errors @@ -85,61 +84,60 @@ func DrivvoParseRefuelings(content []byte, user *db.User, vehicle *db.Vehicle) ( continue } - fillup := db.Fillup{} - date, err := time.Parse("2006-01-02 15:04:05", record[1]) if err != nil { errors = append(errors, "Found an invalid date/time at refuel row "+strconv.Itoa(index+1)) } - fillup.Date = date totalCost, err := strconv.ParseFloat(record[4], 32) if err != nil { errors = append(errors, "Found and invalid total cost at refuel row "+strconv.Itoa(index+1)) } - fillup.TotalAmount = float32(totalCost) odometer, err := strconv.Atoi(record[0]) if err != nil { errors = append(errors, "Found an invalid odometer reading at refuel row "+strconv.Itoa(index+1)) } - fillup.OdoReading = odometer // TODO: Make optional location := record[17] - fillup.FillingStation = location pricePerUnit, err := strconv.ParseFloat(record[3], 32) if err != nil { - // TODO: Add unit type to error message - errors = append(errors, "Found an invalid cost per unit at refuel row "+strconv.Itoa(index+1)) + unit := strings.ToLower(db.FuelUnitDetails[vehicle.FuelUnit].Long) + errors = append(errors, fmt.Sprintf("Found an invalid cost per %s at refuel row %d", unit, index+1)) } - fillup.PerUnitPrice = float32(pricePerUnit) quantity, err := strconv.ParseFloat(record[5], 32) if err != nil { errors = append(errors, "Found an invalid quantity at refuel row "+strconv.Itoa(index+1)) } - fillup.FuelQuantity = float32(quantity) isTankFull := record[6] == "Yes" - fillup.IsTankFull = &isTankFull // Unfortunatly, drivvo doesn't expose this info in their export fal := false - fillup.HasMissedFillup = &fal notes := fmt.Sprintf("Reason: %s\nNotes: %s\nFuel: %s\n", record[18], record[19], record[2]) - fillup.Comments = notes - fillup.VehicleID = vehicle.ID - fillup.FuelUnit = vehicle.FuelUnit - fillup.UserID = user.ID - fillup.Currency = user.Currency - fillup.DistanceUnit = user.DistanceUnit - fillup.Source = "Drivvo" + fillups = append(fillups, db.Fillup{ + VehicleID: vehicle.ID, + UserID: user.ID, + Date: date, + HasMissedFillup: &fal, + IsTankFull: &isTankFull, + FuelQuantity: float32(quantity), + PerUnitPrice: float32(pricePerUnit), + FillingStation: location, + OdoReading: odometer, + TotalAmount: float32(totalCost), + FuelUnit: vehicle.FuelUnit, + Currency: user.Currency, + DistanceUnit: user.DistanceUnit, + Comments: notes, + Source: "Drivvo", + }) - fillups = append(fillups, fillup) } return fillups, errors } From 15cf09f326aab4f17db56597cda3dffe526dcfbd Mon Sep 17 00:00:00 2001 From: Alf Sebastian Houge Date: Wed, 6 Apr 2022 11:34:26 +0200 Subject: [PATCH 04/10] Add notice about what fields aren't imported from drivvo --- ui/src/router/views/import-drivvo.vue | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ui/src/router/views/import-drivvo.vue b/ui/src/router/views/import-drivvo.vue index b2d12e8..59f43be 100644 --- a/ui/src/router/views/import-drivvo.vue +++ b/ui/src/router/views/import-drivvo.vue @@ -96,7 +96,6 @@ export default {
-

Steps to import data from Drivvo

  1. Export your data from Drivvo in the CSV format.
  2. @@ -111,6 +110,12 @@ export default {
+

PS: If you have 'income' and 'trips' in your export, they will not be imported to Hammond. The fields + 'Second fuel' and 'Third fuel' are are are also ignored as the use case for these is not understood by us. If you have a use + case for this, please open a issue on + issue tracker +

Choose the vehicle, then select the Drivvo CSV and press the import button.

From dc33aaad496bc199e219bb9e0b1b6951fb1ba47f Mon Sep 17 00:00:00 2001 From: Alf Sebastian Houge Date: Wed, 6 Apr 2022 12:06:45 +0200 Subject: [PATCH 05/10] Add option for not importing location --- server/controllers/import.go | 9 ++++++++- server/service/importService.go | 12 +++++++----- ui/src/router/views/import-drivvo.vue | 9 +++++++++ 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/server/controllers/import.go b/server/controllers/import.go index 5d36d53..fab641e 100644 --- a/server/controllers/import.go +++ b/server/controllers/import.go @@ -2,6 +2,7 @@ package controllers import ( "net/http" + "strconv" "github.com/akhilrex/hammond/service" "github.com/gin-gonic/gin" @@ -37,7 +38,13 @@ func drivvoImport(c *gin.Context) { c.JSON(http.StatusUnprocessableEntity, "Missing Vehicle ID") return } - errors := service.DrivvoImport(bytes, c.MustGet("userId").(string), vehicleId) + importLocation, err := strconv.ParseBool(c.PostForm("importLocation")) + if err != nil { + c.JSON(http.StatusUnprocessableEntity, "Please include importLocation option.") + return + } + + errors := service.DrivvoImport(bytes, c.MustGet("userId").(string), vehicleId, importLocation) if len(errors) > 0 { c.JSON(http.StatusUnprocessableEntity, gin.H{"errors": errors}) return diff --git a/server/service/importService.go b/server/service/importService.go index ac58d15..946b4c5 100644 --- a/server/service/importService.go +++ b/server/service/importService.go @@ -65,7 +65,7 @@ func DrivvoParseExpenses(content []byte, user *db.User, vehicle *db.Vehicle) ([] return expenses, errors } -func DrivvoParseRefuelings(content []byte, user *db.User, vehicle *db.Vehicle) ([]db.Fillup, []string) { +func DrivvoParseRefuelings(content []byte, user *db.User, vehicle *db.Vehicle, importLocation bool) ([]db.Fillup, []string) { refuelingReader := csv.NewReader(bytes.NewReader(content)) refuelingReader.Comment = '#' refuelingRecords, err := refuelingReader.ReadAll() @@ -99,8 +99,10 @@ func DrivvoParseRefuelings(content []byte, user *db.User, vehicle *db.Vehicle) ( errors = append(errors, "Found an invalid odometer reading at refuel row "+strconv.Itoa(index+1)) } - // TODO: Make optional - location := record[17] + location := "" + if importLocation { + location = record[17] + } pricePerUnit, err := strconv.ParseFloat(record[3], 32) if err != nil { @@ -142,7 +144,7 @@ func DrivvoParseRefuelings(content []byte, user *db.User, vehicle *db.Vehicle) ( return fillups, errors } -func DrivvoImport(content []byte, userId string, vehicleId string) []string { +func DrivvoImport(content []byte, userId string, vehicleId string, importLocation bool) []string { var errors []string user, err := GetUserById(userId) if err != nil { @@ -172,7 +174,7 @@ func DrivvoImport(content []byte, userId string, vehicleId string) []string { expenseSectionIndex = endParseIndex } - fillups, errors := DrivvoParseRefuelings(content[:serviceSectionIndex], user, vehicle) + fillups, errors := DrivvoParseRefuelings(content[:serviceSectionIndex], user, vehicle, importLocation) _ = fillups var allExpenses []db.Expense diff --git a/ui/src/router/views/import-drivvo.vue b/ui/src/router/views/import-drivvo.vue index 59f43be..a1363d1 100644 --- a/ui/src/router/views/import-drivvo.vue +++ b/ui/src/router/views/import-drivvo.vue @@ -22,6 +22,7 @@ export default { selectedVehicle: null, tryingToCreate: false, errors: [], + importLocation: true, } }, computed: { @@ -56,6 +57,7 @@ export default { this.errorMessage = '' const formData = new FormData() formData.append('vehicleID', this.selectedVehicle) + formData.append('importLocation', this.importLocation) formData.append('file', this.file, this.file.name) axios .post(`/api/import/drivvo`, formData) @@ -129,6 +131,13 @@ export default {
+
+ + + Import Location? + + +
From 9ef929dbd57a0ceb35620166b12f3cda3cc76d39 Mon Sep 17 00:00:00 2001 From: Alf Sebastian Houge Date: Wed, 6 Apr 2022 12:16:14 +0200 Subject: [PATCH 06/10] Redirect to home after importing --- ui/src/router/views/import-drivvo.vue | 1 + ui/src/router/views/import-fuelly.vue | 1 + 2 files changed, 2 insertions(+) diff --git a/ui/src/router/views/import-drivvo.vue b/ui/src/router/views/import-drivvo.vue index a1363d1..fb673d2 100644 --- a/ui/src/router/views/import-drivvo.vue +++ b/ui/src/router/views/import-drivvo.vue @@ -82,6 +82,7 @@ export default { }) .finally(() => { this.tryingToCreate = false + setTimeout(() => this.$router.push({ name: 'home' }), 1000) }) }, }, diff --git a/ui/src/router/views/import-fuelly.vue b/ui/src/router/views/import-fuelly.vue index 76d9e03..6b9e1a6 100644 --- a/ui/src/router/views/import-fuelly.vue +++ b/ui/src/router/views/import-fuelly.vue @@ -72,6 +72,7 @@ export default { }) .finally(() => { this.tryingToCreate = false + setTimeout(() => this.$router.push({ name: 'home' }), 1000) }) }, }, From 3322e2f6bd220282ac00d678897408b4f5db1449 Mon Sep 17 00:00:00 2001 From: Alf Sebastian Houge Date: Wed, 6 Apr 2022 14:43:01 +0200 Subject: [PATCH 07/10] Don't redirect from import page on erro --- ui/src/router/views/import-drivvo.vue | 2 +- ui/src/router/views/import-fuelly.vue | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/router/views/import-drivvo.vue b/ui/src/router/views/import-drivvo.vue index fb673d2..143b2d5 100644 --- a/ui/src/router/views/import-drivvo.vue +++ b/ui/src/router/views/import-drivvo.vue @@ -68,6 +68,7 @@ export default { duration: 3000, }) this.file = null + setTimeout(() => this.$router.push({ name: 'home' }), 1000) }) .catch((ex) => { this.$buefy.toast.open({ @@ -82,7 +83,6 @@ export default { }) .finally(() => { this.tryingToCreate = false - setTimeout(() => this.$router.push({ name: 'home' }), 1000) }) }, }, diff --git a/ui/src/router/views/import-fuelly.vue b/ui/src/router/views/import-fuelly.vue index 6b9e1a6..39fedfd 100644 --- a/ui/src/router/views/import-fuelly.vue +++ b/ui/src/router/views/import-fuelly.vue @@ -58,6 +58,7 @@ export default { duration: 3000, }) this.file = null + setTimeout(() => this.$router.push({ name: 'home' }), 1000) }) .catch((ex) => { this.$buefy.toast.open({ @@ -72,7 +73,6 @@ export default { }) .finally(() => { this.tryingToCreate = false - setTimeout(() => this.$router.push({ name: 'home' }), 1000) }) }, }, From f1bf36bcb9b17f7c890a110fb06efd5a4f464efc Mon Sep 17 00:00:00 2001 From: Alf Sebastian Houge Date: Wed, 6 Apr 2022 15:02:46 +0200 Subject: [PATCH 08/10] Separate imports into provider specific files --- server/service/drivvoImportService.go | 142 +++++++++++ server/service/fuellyImportService.go | 140 +++++++++++ server/service/importService.go | 335 +++----------------------- 3 files changed, 312 insertions(+), 305 deletions(-) create mode 100644 server/service/drivvoImportService.go create mode 100644 server/service/fuellyImportService.go diff --git a/server/service/drivvoImportService.go b/server/service/drivvoImportService.go new file mode 100644 index 0000000..7f87bac --- /dev/null +++ b/server/service/drivvoImportService.go @@ -0,0 +1,142 @@ +package service + +import ( + "bytes" + "encoding/csv" + "fmt" + "strconv" + "strings" + "time" + + "github.com/akhilrex/hammond/db" +) + +func DrivvoParseExpenses(content []byte, user *db.User, vehicle *db.Vehicle) ([]db.Expense, []string) { + expenseReader := csv.NewReader(bytes.NewReader(content)) + expenseReader.Comment = '#' + // Read headers (there is a trailing comma at the end, that's why we have to read the first line) + expenseReader.Read() + expenseReader.FieldsPerRecord = 6 + expenseRecords, err := expenseReader.ReadAll() + + var errors []string + if err != nil { + errors = append(errors, err.Error()) + println(err.Error()) + return nil, errors + } + + var expenses []db.Expense + for index, record := range expenseRecords { + date, err := time.Parse("2006-01-02 15:04:05", record[1]) + if err != nil { + errors = append(errors, "Found an invalid date/time at service/expense row "+strconv.Itoa(index+1)) + } + + totalCost, err := strconv.ParseFloat(record[2], 32) + if err != nil { + errors = append(errors, "Found and invalid total cost at service/expense row "+strconv.Itoa(index+1)) + } + + odometer, err := strconv.Atoi(record[0]) + if err != nil { + errors = append(errors, "Found an invalid odometer reading at service/expense row "+strconv.Itoa(index+1)) + } + + notes := fmt.Sprintf("Location: %s\nNotes: %s\n", record[4], record[5]) + + expenses = append(expenses, db.Expense{ + UserID: user.ID, + VehicleID: vehicle.ID, + Date: date, + OdoReading: odometer, + Amount: float32(totalCost), + ExpenseType: record[3], + Currency: user.Currency, + DistanceUnit: user.DistanceUnit, + Comments: notes, + Source: "Drivvo", + }) + } + + return expenses, errors +} + +func DrivvoParseRefuelings(content []byte, user *db.User, vehicle *db.Vehicle, importLocation bool) ([]db.Fillup, []string) { + refuelingReader := csv.NewReader(bytes.NewReader(content)) + refuelingReader.Comment = '#' + refuelingRecords, err := refuelingReader.ReadAll() + + var errors []string + if err != nil { + errors = append(errors, err.Error()) + println(err.Error()) + return nil, errors + } + + var fillups []db.Fillup + for index, record := range refuelingRecords { + // Skip column titles + if index == 0 { + continue + } + + date, err := time.Parse("2006-01-02 15:04:05", record[1]) + if err != nil { + errors = append(errors, "Found an invalid date/time at refuel row "+strconv.Itoa(index+1)) + } + + totalCost, err := strconv.ParseFloat(record[4], 32) + if err != nil { + errors = append(errors, "Found and invalid total cost at refuel row "+strconv.Itoa(index+1)) + } + + odometer, err := strconv.Atoi(record[0]) + if err != nil { + errors = append(errors, "Found an invalid odometer reading at refuel row "+strconv.Itoa(index+1)) + } + + location := "" + if importLocation { + location = record[17] + } + + pricePerUnit, err := strconv.ParseFloat(record[3], 32) + if err != nil { + unit := strings.ToLower(db.FuelUnitDetails[vehicle.FuelUnit].Long) + errors = append(errors, fmt.Sprintf("Found an invalid cost per %s at refuel row %d", unit, index+1)) + } + + quantity, err := strconv.ParseFloat(record[5], 32) + if err != nil { + errors = append(errors, "Found an invalid quantity at refuel row "+strconv.Itoa(index+1)) + } + + isTankFull := record[6] == "Yes" + + // Unfortunatly, drivvo doesn't expose this info in their export + fal := false + + notes := fmt.Sprintf("Reason: %s\nNotes: %s\nFuel: %s\n", record[18], record[19], record[2]) + + fillups = append(fillups, db.Fillup{ + VehicleID: vehicle.ID, + UserID: user.ID, + Date: date, + HasMissedFillup: &fal, + IsTankFull: &isTankFull, + FuelQuantity: float32(quantity), + PerUnitPrice: float32(pricePerUnit), + FillingStation: location, + OdoReading: odometer, + TotalAmount: float32(totalCost), + FuelUnit: vehicle.FuelUnit, + Currency: user.Currency, + DistanceUnit: user.DistanceUnit, + Comments: notes, + Source: "Drivvo", + }) + + } + return fillups, errors +} diff --git a/server/service/fuellyImportService.go b/server/service/fuellyImportService.go new file mode 100644 index 0000000..6b4da88 --- /dev/null +++ b/server/service/fuellyImportService.go @@ -0,0 +1,140 @@ +package service + +import ( + "bytes" + "encoding/csv" + "fmt" + "strconv" + "time" + + "github.com/akhilrex/hammond/db" + "github.com/leekchan/accounting" +) + +func FuellyParseAll(content []byte, userId string) ([]db.Fillup, []db.Expense, []string) { + stream := bytes.NewReader(content) + reader := csv.NewReader(stream) + records, err := reader.ReadAll() + + var errors []string + user, err := GetUserById(userId) + if err != nil { + errors = append(errors, err.Error()) + return nil, nil, errors + } + + vehicles, err := GetUserVehicles(userId) + if err != nil { + errors = append(errors, err.Error()) + return nil, nil, errors + } + + if err != nil { + errors = append(errors, err.Error()) + return nil, nil, errors + } + + var vehicleMap map[string]db.Vehicle = make(map[string]db.Vehicle) + for _, vehicle := range *vehicles { + vehicleMap[vehicle.Nickname] = vehicle + } + + var fillups []db.Fillup + var expenses []db.Expense + layout := "2006-01-02 15:04" + altLayout := "2006-01-02 3:04 PM" + + for index, record := range records { + if index == 0 { + continue + } + + var vehicle db.Vehicle + var ok bool + if vehicle, ok = vehicleMap[record[4]]; !ok { + errors = append(errors, "Found an unmapped vehicle entry at row "+strconv.Itoa(index+1)) + } + dateStr := record[2] + " " + record[3] + date, err := time.Parse(layout, dateStr) + if err != nil { + date, err = time.Parse(altLayout, dateStr) + } + if err != nil { + errors = append(errors, "Found an invalid date/time at row "+strconv.Itoa(index+1)) + } + + totalCostStr := accounting.UnformatNumber(record[9], 3, user.Currency) + totalCost64, err := strconv.ParseFloat(totalCostStr, 32) + if err != nil { + errors = append(errors, "Found an invalid total cost at row "+strconv.Itoa(index+1)) + } + + totalCost := float32(totalCost64) + odoStr := accounting.UnformatNumber(record[5], 0, user.Currency) + odoreading, err := strconv.Atoi(odoStr) + if err != nil { + errors = append(errors, "Found an invalid odo reading at row "+strconv.Itoa(index+1)) + } + location := record[12] + + //Create Fillup + if record[0] == "Gas" { + rateStr := accounting.UnformatNumber(record[7], 3, user.Currency) + ratet64, err := strconv.ParseFloat(rateStr, 32) + if err != nil { + errors = append(errors, "Found an invalid cost per gallon at row "+strconv.Itoa(index+1)) + } + rate := float32(ratet64) + + quantity64, err := strconv.ParseFloat(record[8], 32) + if err != nil { + errors = append(errors, "Found an invalid quantity at row "+strconv.Itoa(index+1)) + } + quantity := float32(quantity64) + + notes := fmt.Sprintf("Octane:%s\nGas Brand:%s\nLocation%s\nTags:%s\nPayment Type:%s\nTire Pressure:%s\nNotes:%s\nMPG:%s", + record[10], record[11], record[12], record[13], record[14], record[15], record[16], record[1], + ) + + isTankFull := record[6] == "Full" + fal := false + fillups = append(fillups, db.Fillup{ + VehicleID: vehicle.ID, + FuelUnit: vehicle.FuelUnit, + FuelQuantity: quantity, + PerUnitPrice: rate, + TotalAmount: totalCost, + OdoReading: odoreading, + IsTankFull: &isTankFull, + Comments: notes, + FillingStation: location, + HasMissedFillup: &fal, + UserID: userId, + Date: date, + Currency: user.Currency, + DistanceUnit: user.DistanceUnit, + Source: "Fuelly", + }) + + } + if record[0] == "Service" { + notes := fmt.Sprintf("Tags:%s\nPayment Type:%s\nNotes:%s", + record[13], record[14], record[16], + ) + expenses = append(expenses, db.Expense{ + VehicleID: vehicle.ID, + Amount: totalCost, + OdoReading: odoreading, + Comments: notes, + ExpenseType: record[17], + UserID: userId, + Currency: user.Currency, + Date: date, + DistanceUnit: user.DistanceUnit, + Source: "Fuelly", + }) + } + + } + return fillups, expenses, errors +} diff --git a/server/service/importService.go b/server/service/importService.go index 946b4c5..c27d7d5 100644 --- a/server/service/importService.go +++ b/server/service/importService.go @@ -2,146 +2,42 @@ package service import ( "bytes" - "encoding/csv" - "fmt" - "strconv" - "strings" - "time" "github.com/akhilrex/hammond/db" - "github.com/leekchan/accounting" ) -// TODO: Move drivvo stuff to separate file - -func DrivvoParseExpenses(content []byte, user *db.User, vehicle *db.Vehicle) ([]db.Expense, []string) { - expenseReader := csv.NewReader(bytes.NewReader(content)) - expenseReader.Comment = '#' - // Read headers (there is a trailing comma at the end, that's why we have to read the first line) - expenseReader.Read() - expenseReader.FieldsPerRecord = 6 - expenseRecords, err := expenseReader.ReadAll() - +func WriteToDB(fillups []db.Fillup, expenses []db.Expense) []string { var errors []string + tx := db.DB.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + if err := tx.Error; err != nil { + errors = append(errors, err.Error()) + return errors + } + if fillups != nil { + if err := tx.Create(&fillups).Error; err != nil { + tx.Rollback() + errors = append(errors, err.Error()) + return errors + } + } + if expenses != nil { + if err := tx.Create(&expenses).Error; err != nil { + tx.Rollback() + errors = append(errors, err.Error()) + return errors + } + } + err := tx.Commit().Error if err != nil { errors = append(errors, err.Error()) - println(err.Error()) - return nil, errors } + return errors - var expenses []db.Expense - for index, record := range expenseRecords { - date, err := time.Parse("2006-01-02 15:04:05", record[1]) - if err != nil { - errors = append(errors, "Found an invalid date/time at service/expense row "+strconv.Itoa(index+1)) - } - - totalCost, err := strconv.ParseFloat(record[2], 32) - if err != nil { - errors = append(errors, "Found and invalid total cost at service/expense row "+strconv.Itoa(index+1)) - } - - odometer, err := strconv.Atoi(record[0]) - if err != nil { - errors = append(errors, "Found an invalid odometer reading at service/expense row "+strconv.Itoa(index+1)) - } - - notes := fmt.Sprintf("Location: %s\nNotes: %s\n", record[4], record[5]) - - expenses = append(expenses, db.Expense{ - UserID: user.ID, - VehicleID: vehicle.ID, - Date: date, - OdoReading: odometer, - Amount: float32(totalCost), - ExpenseType: record[3], - Currency: user.Currency, - DistanceUnit: user.DistanceUnit, - Comments: notes, - Source: "Drivvo", - }) - } - - return expenses, errors -} - -func DrivvoParseRefuelings(content []byte, user *db.User, vehicle *db.Vehicle, importLocation bool) ([]db.Fillup, []string) { - refuelingReader := csv.NewReader(bytes.NewReader(content)) - refuelingReader.Comment = '#' - refuelingRecords, err := refuelingReader.ReadAll() - - var errors []string - if err != nil { - errors = append(errors, err.Error()) - println(err.Error()) - return nil, errors - } - - var fillups []db.Fillup - for index, record := range refuelingRecords { - // Skip column titles - if index == 0 { - continue - } - - date, err := time.Parse("2006-01-02 15:04:05", record[1]) - if err != nil { - errors = append(errors, "Found an invalid date/time at refuel row "+strconv.Itoa(index+1)) - } - - totalCost, err := strconv.ParseFloat(record[4], 32) - if err != nil { - errors = append(errors, "Found and invalid total cost at refuel row "+strconv.Itoa(index+1)) - } - - odometer, err := strconv.Atoi(record[0]) - if err != nil { - errors = append(errors, "Found an invalid odometer reading at refuel row "+strconv.Itoa(index+1)) - } - - location := "" - if importLocation { - location = record[17] - } - - pricePerUnit, err := strconv.ParseFloat(record[3], 32) - if err != nil { - unit := strings.ToLower(db.FuelUnitDetails[vehicle.FuelUnit].Long) - errors = append(errors, fmt.Sprintf("Found an invalid cost per %s at refuel row %d", unit, index+1)) - } - - quantity, err := strconv.ParseFloat(record[5], 32) - if err != nil { - errors = append(errors, "Found an invalid quantity at refuel row "+strconv.Itoa(index+1)) - } - - isTankFull := record[6] == "Yes" - - // Unfortunatly, drivvo doesn't expose this info in their export - fal := false - - notes := fmt.Sprintf("Reason: %s\nNotes: %s\nFuel: %s\n", record[18], record[19], record[2]) - - fillups = append(fillups, db.Fillup{ - VehicleID: vehicle.ID, - UserID: user.ID, - Date: date, - HasMissedFillup: &fal, - IsTankFull: &isTankFull, - FuelQuantity: float32(quantity), - PerUnitPrice: float32(pricePerUnit), - FillingStation: location, - OdoReading: odometer, - TotalAmount: float32(totalCost), - FuelUnit: vehicle.FuelUnit, - Currency: user.Currency, - DistanceUnit: user.DistanceUnit, - Comments: notes, - Source: "Drivvo", - }) - - } - return fillups, errors } func DrivvoImport(content []byte, userId string, vehicleId string, importLocation bool) []string { @@ -175,7 +71,6 @@ func DrivvoImport(content []byte, userId string, vehicleId string, importLocatio } fillups, errors := DrivvoParseRefuelings(content[:serviceSectionIndex], user, vehicle, importLocation) - _ = fillups var allExpenses []db.Expense if serviceSectionIndex != -1 { @@ -198,184 +93,14 @@ func DrivvoImport(content []byte, userId string, vehicleId string, importLocatio return errors } - tx := db.DB.Begin() - defer func() { - if r := recover(); r != nil { - tx.Rollback() - } - }() - if err := tx.Error; err != nil { - errors = append(errors, err.Error()) - return errors - } - if err := tx.Create(&fillups).Error; err != nil { - tx.Rollback() - errors = append(errors, err.Error()) - return errors - } - if err := tx.Create(&allExpenses).Error; err != nil { - tx.Rollback() - errors = append(errors, err.Error()) - return errors - } - if err := tx.Commit().Error; err != nil { - errors = append(errors, err.Error()) - } - return errors + return WriteToDB(fillups, allExpenses) } func FuellyImport(content []byte, userId string) []string { - stream := bytes.NewReader(content) - reader := csv.NewReader(stream) - records, err := reader.ReadAll() - - var errors []string - if err != nil { - errors = append(errors, err.Error()) - return errors - } - - vehicles, err := GetUserVehicles(userId) - if err != nil { - errors = append(errors, err.Error()) - return errors - } - user, err := GetUserById(userId) - - if err != nil { - errors = append(errors, err.Error()) - return errors - } - - var vehicleMap map[string]db.Vehicle = make(map[string]db.Vehicle) - for _, vehicle := range *vehicles { - vehicleMap[vehicle.Nickname] = vehicle - } - - var fillups []db.Fillup - var expenses []db.Expense - layout := "2006-01-02 15:04" - altLayout := "2006-01-02 3:04 PM" - - for index, record := range records { - if index == 0 { - continue - } - - var vehicle db.Vehicle - var ok bool - if vehicle, ok = vehicleMap[record[4]]; !ok { - errors = append(errors, "Found an unmapped vehicle entry at row "+strconv.Itoa(index+1)) - } - dateStr := record[2] + " " + record[3] - date, err := time.Parse(layout, dateStr) - if err != nil { - date, err = time.Parse(altLayout, dateStr) - } - if err != nil { - errors = append(errors, "Found an invalid date/time at row "+strconv.Itoa(index+1)) - } - - totalCostStr := accounting.UnformatNumber(record[9], 3, user.Currency) - totalCost64, err := strconv.ParseFloat(totalCostStr, 32) - if err != nil { - errors = append(errors, "Found an invalid total cost at row "+strconv.Itoa(index+1)) - } - - totalCost := float32(totalCost64) - odoStr := accounting.UnformatNumber(record[5], 0, user.Currency) - odoreading, err := strconv.Atoi(odoStr) - if err != nil { - errors = append(errors, "Found an invalid odo reading at row "+strconv.Itoa(index+1)) - } - location := record[12] - - //Create Fillup - if record[0] == "Gas" { - rateStr := accounting.UnformatNumber(record[7], 3, user.Currency) - ratet64, err := strconv.ParseFloat(rateStr, 32) - if err != nil { - errors = append(errors, "Found an invalid cost per gallon at row "+strconv.Itoa(index+1)) - } - rate := float32(ratet64) - - quantity64, err := strconv.ParseFloat(record[8], 32) - if err != nil { - errors = append(errors, "Found an invalid quantity at row "+strconv.Itoa(index+1)) - } - quantity := float32(quantity64) - - notes := fmt.Sprintf("Octane:%s\nGas Brand:%s\nLocation%s\nTags:%s\nPayment Type:%s\nTire Pressure:%s\nNotes:%s\nMPG:%s", - record[10], record[11], record[12], record[13], record[14], record[15], record[16], record[1], - ) - - isTankFull := record[6] == "Full" - fal := false - fillups = append(fillups, db.Fillup{ - VehicleID: vehicle.ID, - FuelUnit: vehicle.FuelUnit, - FuelQuantity: quantity, - PerUnitPrice: rate, - TotalAmount: totalCost, - OdoReading: odoreading, - IsTankFull: &isTankFull, - Comments: notes, - FillingStation: location, - HasMissedFillup: &fal, - UserID: userId, - Date: date, - Currency: user.Currency, - DistanceUnit: user.DistanceUnit, - Source: "Fuelly", - }) - - } - if record[0] == "Service" { - notes := fmt.Sprintf("Tags:%s\nPayment Type:%s\nNotes:%s", - record[13], record[14], record[16], - ) - expenses = append(expenses, db.Expense{ - VehicleID: vehicle.ID, - Amount: totalCost, - OdoReading: odoreading, - Comments: notes, - ExpenseType: record[17], - UserID: userId, - Currency: user.Currency, - Date: date, - DistanceUnit: user.DistanceUnit, - Source: "Fuelly", - }) - } - - } + fillups, expenses, errors := FuellyParseAll(content, userId) if len(errors) != 0 { return errors } - tx := db.DB.Begin() - defer func() { - if r := recover(); r != nil { - tx.Rollback() - } - }() - if err := tx.Error; err != nil { - errors = append(errors, err.Error()) - return errors - } - if err := tx.Create(&fillups).Error; err != nil { - tx.Rollback() - errors = append(errors, err.Error()) - return errors - } - if err := tx.Create(&expenses).Error; err != nil { - tx.Rollback() - errors = append(errors, err.Error()) - return errors - } - err = tx.Commit().Error - if err != nil { - errors = append(errors, err.Error()) - } - return errors + return WriteToDB(fillups, expenses) } From 27497075461c87638d9ebe96adfb39c949de0d3b Mon Sep 17 00:00:00 2001 From: Alf Sebastian Houge Date: Wed, 6 Apr 2022 15:37:20 +0200 Subject: [PATCH 09/10] Change logic in how indexes of sections in csv work Change the logic in how indexes of the different sections in drivvos csv work --- server/service/importService.go | 39 +++++++++++++++++---------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/server/service/importService.go b/server/service/importService.go index c27d7d5..b8850c4 100644 --- a/server/service/importService.go +++ b/server/service/importService.go @@ -54,8 +54,6 @@ func DrivvoImport(content []byte, userId string, vehicleId string, importLocatio return errors } - serviceSectionIndex := bytes.Index(content, []byte("#Service")) - endParseIndex := bytes.Index(content, []byte("#Income")) if endParseIndex == -1 { endParseIndex = bytes.Index(content, []byte("#Route")) @@ -65,29 +63,32 @@ func DrivvoImport(content []byte, userId string, vehicleId string, importLocatio } - expenseSectionIndex := bytes.Index(content, []byte("#Expense")) - if expenseSectionIndex == -1 { - expenseSectionIndex = endParseIndex + serviceEndIndex := bytes.Index(content, []byte("#Expense")) + if serviceEndIndex == -1 { + serviceEndIndex = endParseIndex } - fillups, errors := DrivvoParseRefuelings(content[:serviceSectionIndex], user, vehicle, importLocation) + refuelEndIndex := bytes.Index(content, []byte("#Service")) + if refuelEndIndex == -1 { + refuelEndIndex = serviceEndIndex + } + + var fillups []db.Fillup + fillups, errors = DrivvoParseRefuelings(content[:refuelEndIndex], user, vehicle, importLocation) var allExpenses []db.Expense - if serviceSectionIndex != -1 { - services, parseErrors := DrivvoParseExpenses(content[serviceSectionIndex:expenseSectionIndex], user, vehicle) - if parseErrors != nil { - errors = append(errors, parseErrors...) - } - allExpenses = append(allExpenses, services...) + services, parseErrors := DrivvoParseExpenses(content[refuelEndIndex:serviceEndIndex], user, vehicle) + if parseErrors != nil { + errors = append(errors, parseErrors...) + } + allExpenses = append(allExpenses, services...) + + expenses, parseErrors := DrivvoParseExpenses(content[serviceEndIndex:endParseIndex], user, vehicle) + if parseErrors != nil { + errors = append(errors, parseErrors...) } - if expenseSectionIndex != endParseIndex { - expenses, parseErrors := DrivvoParseExpenses(content[expenseSectionIndex:endParseIndex], user, vehicle) - if parseErrors != nil { - errors = append(errors, parseErrors...) - } - allExpenses = append(allExpenses, expenses...) - } + allExpenses = append(allExpenses, expenses...) if len(errors) != 0 { return errors From ebebcacdc91d666fef17c49dbeb0060fea6a3dab Mon Sep 17 00:00:00 2001 From: Alf Sebastian Houge Date: Sat, 28 Jan 2023 22:49:38 +0100 Subject: [PATCH 10/10] Fix pricePerUnit parsing --- server/service/drivvoImportService.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/service/drivvoImportService.go b/server/service/drivvoImportService.go index 7f87bac..567c951 100644 --- a/server/service/drivvoImportService.go +++ b/server/service/drivvoImportService.go @@ -103,7 +103,7 @@ func DrivvoParseRefuelings(content []byte, user *db.User, vehicle *db.Vehicle, i pricePerUnit, err := strconv.ParseFloat(record[3], 32) if err != nil { - unit := strings.ToLower(db.FuelUnitDetails[vehicle.FuelUnit].Long) + unit := strings.ToLower(db.FuelUnitDetails[vehicle.FuelUnit].Key) errors = append(errors, fmt.Sprintf("Found an invalid cost per %s at refuel row %d", unit, index+1)) }