From f8d1f195de230e9700a98c03a431690ef61ecd53 Mon Sep 17 00:00:00 2001 From: Sebastiaan de Schaetzen Date: Sat, 24 May 2025 06:28:07 +0200 Subject: [PATCH] Support decimal currency amounts (#74) Reviewed-on: https://gitea.seeseepuff.be/seeseemelk/allowance_planner_2000/pulls/74 --- backend/api_test.go | 6 +++--- backend/db.go | 50 ++++++++++++++++++++++++++++++++------------- backend/dto.go | 32 ++++++++++++++--------------- backend/web.go | 4 ++-- 4 files changed, 57 insertions(+), 35 deletions(-) diff --git a/backend/api_test.go b/backend/api_test.go index 4bf31d4..584364b 100644 --- a/backend/api_test.go +++ b/backend/api_test.go @@ -568,9 +568,9 @@ func TestCompleteTask(t *testing.T) { allowances := e.GET("/user/1/allowance").Expect().Status(200).JSON().Array() allowances.Length().IsEqual(3) allowances.Value(0).Object().Value("id").Number().IsEqual(0) - allowances.Value(0).Object().Value("progress").Number().IsEqual(31) + allowances.Value(0).Object().Value("progress").Number().InDelta(30.34, 0.01) allowances.Value(1).Object().Value("id").Number().IsEqual(1) - allowances.Value(1).Object().Value("progress").Number().IsEqual(60) + allowances.Value(1).Object().Value("progress").Number().InDelta(60.66, 0.01) allowances.Value(2).Object().Value("id").Number().IsEqual(2) allowances.Value(2).Object().Value("progress").Number().IsEqual(10) @@ -696,7 +696,7 @@ func getDelta(base time.Time, delta float64) (time.Time, time.Time) { return start, end } -func createTestAllowance(e *httpexpect.Expect, name string, target int, weight float64) { +func createTestAllowance(e *httpexpect.Expect, name string, target float64, weight float64) { e.POST("/user/1/allowance").WithJSON(CreateAllowanceRequest{ Name: name, Target: target, diff --git a/backend/db.go b/backend/db.go index 0aa5570..71bd650 100644 --- a/backend/db.go +++ b/backend/db.go @@ -3,6 +3,7 @@ package main import ( "errors" "log" + "math" "time" "gitea.seeseepuff.be/seeseemelk/mysqlite" @@ -49,8 +50,10 @@ func (db *Db) GetUsers() ([]User, error) { func (db *Db) GetUser(id int) (*UserWithAllowance, error) { user := &UserWithAllowance{} + var allowance int err := db.db.Query("select u.id, u.name, (select ifnull(sum(h.amount), 0) from history h where h.user_id = u.id) from users u where u.id = ?"). - Bind(id).ScanSingle(&user.ID, &user.Name, &user.Allowance) + Bind(id).ScanSingle(&user.ID, &user.Name, &allowance) + user.Allowance = float64(allowance) / 100.0 if err != nil { return nil, err } @@ -70,18 +73,23 @@ func (db *Db) UserExists(userId int) (bool, error) { func (db *Db) GetUserAllowances(userId int) ([]Allowance, error) { allowances := make([]Allowance, 0) var err error + var progress int64 totalAllowance := Allowance{} - err = db.db.Query("select balance, weight from users where id = ?").Bind(userId).ScanSingle(&totalAllowance.Progress, &totalAllowance.Weight) + err = db.db.Query("select balance, weight from users where id = ?").Bind(userId).ScanSingle(&progress, &totalAllowance.Weight) if err != nil { return nil, err } + totalAllowance.Progress = float64(progress) / 100.0 allowances = append(allowances, totalAllowance) for row := range db.db.Query("select id, name, target, balance, weight from allowances where user_id = ?"). Bind(userId).Range(&err) { allowance := Allowance{} - err = row.Scan(&allowance.ID, &allowance.Name, &allowance.Target, &allowance.Progress, &allowance.Weight) + var target, progress int + err = row.Scan(&allowance.ID, &allowance.Name, &target, &progress, &allowance.Weight) + allowance.Target = float64(target) / 100.0 + allowance.Progress = float64(progress) / 100.0 if err != nil { return nil, err } @@ -96,15 +104,20 @@ func (db *Db) GetUserAllowances(userId int) ([]Allowance, error) { func (db *Db) GetUserAllowanceById(userId int, allowanceId int) (*Allowance, error) { allowance := &Allowance{} if allowanceId == 0 { + var progress int64 err := db.db.Query("select balance, weight from users where id = ?"). - Bind(userId).ScanSingle(&allowance.Progress, &allowance.Weight) + Bind(userId).ScanSingle(&progress, &allowance.Weight) + allowance.Progress = float64(progress) / 100.0 if err != nil { return nil, err } } else { + var target, progress int64 err := db.db.Query("select id, name, target, balance, weight from allowances where user_id = ? and id = ?"). Bind(userId, allowanceId). - ScanSingle(&allowance.ID, &allowance.Name, &allowance.Target, &allowance.Progress, &allowance.Weight) + ScanSingle(&allowance.ID, &allowance.Name, &target, &progress, &allowance.Weight) + allowance.Target = float64(target) / 100.0 + allowance.Progress = float64(progress) / 100.0 if err != nil { return nil, err } @@ -130,7 +143,7 @@ func (db *Db) CreateAllowance(userId int, allowance *CreateAllowanceRequest) (in // Insert the new allowance err = tx.Query("insert into allowances (user_id, name, target, weight) values (?, ?, ?, ?)"). - Bind(userId, allowance.Name, allowance.Target, allowance.Weight). + Bind(userId, allowance.Name, int(math.Round(allowance.Target*100.0)), allowance.Weight). Exec() if err != nil { @@ -242,8 +255,9 @@ func (db *Db) UpdateAllowance(userId int, allowanceId int, allowance *UpdateAllo } defer tx.MustRollback() + target := int(math.Round(allowance.Target * 100.0)) err = tx.Query("update allowances set name=?, target=?, weight=? where id = ? and user_id = ?"). - Bind(allowance.Name, allowance.Target, allowance.Weight, allowanceId, userId). + Bind(allowance.Name, target, allowance.Weight, allowanceId, userId). Exec() if err != nil { return err @@ -284,8 +298,9 @@ func (db *Db) CreateTask(task *CreateTaskRequest) (int, error) { defer tx.MustRollback() // Insert the new task + reward := int(math.Round(task.Reward * 100.0)) err = tx.Query("insert into tasks (name, reward, assigned) values (?, ?, ?)"). - Bind(task.Name, task.Reward, task.Assigned). + Bind(task.Name, reward, task.Assigned). Exec() if err != nil { @@ -314,7 +329,9 @@ func (db *Db) GetTasks() ([]Task, error) { for row := range db.db.Query("select id, name, reward, assigned from tasks").Range(&err) { task := Task{} - err = row.Scan(&task.ID, &task.Name, &task.Reward, &task.Assigned) + var reward int64 + err = row.Scan(&task.ID, &task.Name, &reward, &task.Assigned) + task.Reward = float64(reward) / 100.0 if err != nil { return nil, err } @@ -329,8 +346,10 @@ func (db *Db) GetTasks() ([]Task, error) { func (db *Db) GetTask(id int) (Task, error) { task := Task{} + var reward int64 err := db.db.Query("select id, name, reward, assigned from tasks where id = ?"). - Bind(id).ScanSingle(&task.ID, &task.Name, &task.Reward, &task.Assigned) + Bind(id).ScanSingle(&task.ID, &task.Name, &reward, &task.Assigned) + task.Reward = float64(reward) / 100.0 if err != nil { return Task{}, err } @@ -369,8 +388,9 @@ func (db *Db) UpdateTask(id int, task *CreateTaskRequest) error { } defer tx.MustRollback() + reward := int(math.Round(task.Reward * 100.0)) err = tx.Query("update tasks set name=?, reward=?, assigned=? where id = ?"). - Bind(task.Name, task.Reward, task.Assigned, id). + Bind(task.Name, reward, task.Assigned, id). Exec() if err != nil { return err @@ -464,8 +484,9 @@ func (db *Db) AddHistory(userId int, allowance *PostHistory) error { } defer tx.MustRollback() + amount := int(math.Round(allowance.Allowance * 100.0)) err = tx.Query("insert into history (user_id, timestamp, amount) values (?, ?, ?)"). - Bind(userId, time.Now().Unix(), allowance.Allowance). + Bind(userId, time.Now().Unix(), amount). Exec() if err != nil { return err @@ -480,11 +501,12 @@ func (db *Db) GetHistory(userId int) ([]History, error) { for row := range db.db.Query("select amount, `timestamp` from history where user_id = ? order by `timestamp` desc"). Bind(userId).Range(&err) { allowance := History{} - var timestamp int64 - err = row.Scan(&allowance.Allowance, ×tamp) + var timestamp, amount int64 + err = row.Scan(&amount, ×tamp) if err != nil { return nil, err } + allowance.Allowance = float64(amount) / 100.0 allowance.Timestamp = time.Unix(timestamp, 0) history = append(history, allowance) } diff --git a/backend/dto.go b/backend/dto.go index 1cd3b27..3b859d1 100644 --- a/backend/dto.go +++ b/backend/dto.go @@ -8,45 +8,45 @@ type User struct { } type UserWithAllowance struct { - ID int `json:"id"` - Name string `json:"name"` - Allowance int `json:"allowance"` + ID int `json:"id"` + Name string `json:"name"` + Allowance float64 `json:"allowance"` } type History struct { - Allowance int `json:"allowance"` + Allowance float64 `json:"allowance"` Timestamp time.Time `json:"timestamp"` } type PostHistory struct { - Allowance int `json:"allowance"` + Allowance float64 `json:"allowance"` } // Task represents a task in the system. type Task struct { - ID int `json:"id"` - Name string `json:"name"` - Reward int `json:"reward"` - Assigned *int `json:"assigned"` // Pointer to allow null + ID int `json:"id"` + Name string `json:"name"` + Reward float64 `json:"reward"` + Assigned *int `json:"assigned"` // Pointer to allow null } type Allowance struct { ID int `json:"id"` Name string `json:"name"` - Target int `json:"target"` - Progress int `json:"progress"` + Target float64 `json:"target"` + Progress float64 `json:"progress"` Weight float64 `json:"weight"` } type CreateAllowanceRequest struct { Name string `json:"name"` - Target int `json:"target"` + Target float64 `json:"target"` Weight float64 `json:"weight"` } type UpdateAllowanceRequest struct { Name string `json:"name"` - Target int `json:"target"` + Target float64 `json:"target"` Weight float64 `json:"weight"` } @@ -60,9 +60,9 @@ type CreateGoalResponse struct { } type CreateTaskRequest struct { - Name string `json:"name" binding:"required"` - Reward int `json:"reward"` - Assigned *int `json:"assigned"` + Name string `json:"name" binding:"required"` + Reward float64 `json:"reward"` + Assigned *int `json:"assigned"` } type CreateTaskResponse struct { diff --git a/backend/web.go b/backend/web.go index 12abbd0..820e89e 100644 --- a/backend/web.go +++ b/backend/web.go @@ -49,7 +49,7 @@ func renderCreateTask(c *gin.Context) { name := c.PostForm("name") rewardStr := c.PostForm("reward") - reward, err := strconv.Atoi(rewardStr) + reward, err := strconv.ParseFloat(rewardStr, 64) if err != nil { renderError(c, http.StatusBadRequest, err) return @@ -96,7 +96,7 @@ func renderCreateAllowance(c *gin.Context) { name := c.PostForm("name") targetStr := c.PostForm("target") - target, err := strconv.Atoi(targetStr) + target, err := strconv.ParseFloat(targetStr, 64) if err != nil { renderError(c, http.StatusBadRequest, err) return