Merge pull request 'Extract package for Numeric datatype and add unittests' (#18) from numeric-package into master
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #18
This commit is contained in:
commit
696fbee7cc
@ -65,6 +65,7 @@ tasks:
|
|||||||
desc: Run CI build
|
desc: Run CI build
|
||||||
cmds:
|
cmds:
|
||||||
- task: build-prod
|
- task: build-prod
|
||||||
|
- go test ./...
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
desc: Build vue frontend
|
desc: Build vue frontend
|
||||||
|
@ -6,6 +6,7 @@ package postgres
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"git.javil.eu/jacob1123/budgeteer/postgres/numeric"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -97,7 +98,7 @@ type GetAccountsWithBalanceRow struct {
|
|||||||
ID uuid.UUID
|
ID uuid.UUID
|
||||||
Name string
|
Name string
|
||||||
OnBudget bool
|
OnBudget bool
|
||||||
Balance Numeric
|
Balance numeric.Numeric
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) GetAccountsWithBalance(ctx context.Context, budgetID uuid.UUID) ([]GetAccountsWithBalanceRow, error) {
|
func (q *Queries) GetAccountsWithBalance(ctx context.Context, budgetID uuid.UUID) ([]GetAccountsWithBalanceRow, error) {
|
||||||
@ -127,3 +128,44 @@ func (q *Queries) GetAccountsWithBalance(ctx context.Context, budgetID uuid.UUID
|
|||||||
}
|
}
|
||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const searchAccounts = `-- name: SearchAccounts :many
|
||||||
|
SELECT accounts.id, accounts.budget_id, accounts.name FROM accounts
|
||||||
|
WHERE accounts.budget_id = $1
|
||||||
|
AND accounts.name LIKE $2
|
||||||
|
ORDER BY accounts.name
|
||||||
|
`
|
||||||
|
|
||||||
|
type SearchAccountsParams struct {
|
||||||
|
BudgetID uuid.UUID
|
||||||
|
Search string
|
||||||
|
}
|
||||||
|
|
||||||
|
type SearchAccountsRow struct {
|
||||||
|
ID uuid.UUID
|
||||||
|
BudgetID uuid.UUID
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) SearchAccounts(ctx context.Context, arg SearchAccountsParams) ([]SearchAccountsRow, error) {
|
||||||
|
rows, err := q.db.QueryContext(ctx, searchAccounts, arg.BudgetID, arg.Search)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []SearchAccountsRow
|
||||||
|
for rows.Next() {
|
||||||
|
var i SearchAccountsRow
|
||||||
|
if err := rows.Scan(&i.ID, &i.BudgetID, &i.Name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Close(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.javil.eu/jacob1123/budgeteer/postgres/numeric"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -21,7 +22,7 @@ RETURNING id, category_id, date, memo, amount
|
|||||||
|
|
||||||
type CreateAssignmentParams struct {
|
type CreateAssignmentParams struct {
|
||||||
Date time.Time
|
Date time.Time
|
||||||
Amount Numeric
|
Amount numeric.Numeric
|
||||||
CategoryID uuid.UUID
|
CategoryID uuid.UUID
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,7 +66,7 @@ type GetAllAssignmentsRow struct {
|
|||||||
Date time.Time
|
Date time.Time
|
||||||
Category string
|
Category string
|
||||||
Group string
|
Group string
|
||||||
Amount Numeric
|
Amount numeric.Numeric
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) GetAllAssignments(ctx context.Context, budgetID uuid.UUID) ([]GetAllAssignmentsRow, error) {
|
func (q *Queries) GetAllAssignments(ctx context.Context, budgetID uuid.UUID) ([]GetAllAssignmentsRow, error) {
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.javil.eu/jacob1123/budgeteer/postgres/numeric"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -23,10 +24,10 @@ ORDER BY COALESCE(ass.date, tra.date), COALESCE(ass.category_id, tra.category_id
|
|||||||
type GetCumultativeBalancesRow struct {
|
type GetCumultativeBalancesRow struct {
|
||||||
Date time.Time
|
Date time.Time
|
||||||
CategoryID uuid.UUID
|
CategoryID uuid.UUID
|
||||||
Assignments Numeric
|
Assignments numeric.Numeric
|
||||||
AssignmentsCum Numeric
|
AssignmentsCum numeric.Numeric
|
||||||
Transactions Numeric
|
Transactions numeric.Numeric
|
||||||
TransactionsCum Numeric
|
TransactionsCum numeric.Numeric
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) GetCumultativeBalances(ctx context.Context, budgetID uuid.UUID) ([]GetCumultativeBalancesRow, error) {
|
func (q *Queries) GetCumultativeBalances(ctx context.Context, budgetID uuid.UUID) ([]GetCumultativeBalancesRow, error) {
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.javil.eu/jacob1123/budgeteer/postgres/numeric"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -42,7 +43,7 @@ type Assignment struct {
|
|||||||
CategoryID uuid.UUID
|
CategoryID uuid.UUID
|
||||||
Date time.Time
|
Date time.Time
|
||||||
Memo sql.NullString
|
Memo sql.NullString
|
||||||
Amount Numeric
|
Amount numeric.Numeric
|
||||||
}
|
}
|
||||||
|
|
||||||
type AssignmentsByMonth struct {
|
type AssignmentsByMonth struct {
|
||||||
@ -81,7 +82,7 @@ type Transaction struct {
|
|||||||
ID uuid.UUID
|
ID uuid.UUID
|
||||||
Date time.Time
|
Date time.Time
|
||||||
Memo string
|
Memo string
|
||||||
Amount Numeric
|
Amount numeric.Numeric
|
||||||
AccountID uuid.UUID
|
AccountID uuid.UUID
|
||||||
CategoryID uuid.NullUUID
|
CategoryID uuid.NullUUID
|
||||||
PayeeID uuid.NullUUID
|
PayeeID uuid.NullUUID
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
package postgres
|
package numeric
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/jackc/pgtype"
|
"github.com/jackc/pgtype"
|
||||||
)
|
)
|
||||||
@ -11,10 +13,18 @@ type Numeric struct {
|
|||||||
pgtype.Numeric
|
pgtype.Numeric
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewZeroNumeric() Numeric {
|
func Zero() Numeric {
|
||||||
return Numeric{pgtype.Numeric{Exp: 0, Int: big.NewInt(0), Status: pgtype.Present, NaN: false}}
|
return Numeric{pgtype.Numeric{Exp: 0, Int: big.NewInt(0), Status: pgtype.Present, NaN: false}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func FromInt64(value int64) Numeric {
|
||||||
|
return Numeric{Numeric: pgtype.Numeric{Int: big.NewInt(value), Status: pgtype.Present}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FromInt64WithExp(value int64, exp int32) Numeric {
|
||||||
|
return Numeric{Numeric: pgtype.Numeric{Int: big.NewInt(value), Exp: exp, Status: pgtype.Present}}
|
||||||
|
}
|
||||||
|
|
||||||
func (n Numeric) GetFloat64() float64 {
|
func (n Numeric) GetFloat64() float64 {
|
||||||
if n.Status != pgtype.Present {
|
if n.Status != pgtype.Present {
|
||||||
return 0
|
return 0
|
||||||
@ -165,3 +175,40 @@ func (n Numeric) MarshalJSON() ([]byte, error) {
|
|||||||
bytesWithSeparator = append(bytesWithSeparator, bytes[split:]...)
|
bytesWithSeparator = append(bytesWithSeparator, bytes[split:]...)
|
||||||
return bytesWithSeparator, nil
|
return bytesWithSeparator, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func MustParse(text string) Numeric {
|
||||||
|
num, err := Parse(text)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return num
|
||||||
|
}
|
||||||
|
|
||||||
|
func Parse(text string) (Numeric, error) {
|
||||||
|
// Unify decimal separator
|
||||||
|
text = strings.Replace(text, ",", ".", 1)
|
||||||
|
|
||||||
|
num := Numeric{}
|
||||||
|
err := num.Set(text)
|
||||||
|
if err != nil {
|
||||||
|
return num, fmt.Errorf("parse numeric %s: %w", text, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return num, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseCurrency(text string) (Numeric, error) {
|
||||||
|
// Remove trailing currency
|
||||||
|
text = trimLastChar(text)
|
||||||
|
|
||||||
|
return Parse(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
func trimLastChar(s string) string {
|
||||||
|
r, size := utf8.DecodeLastRuneInString(s)
|
||||||
|
if r == utf8.RuneError && (size == 0 || size == 1) {
|
||||||
|
size = 0
|
||||||
|
}
|
||||||
|
return s[:len(s)-size]
|
||||||
|
}
|
88
postgres/numeric/numeric_test.go
Normal file
88
postgres/numeric/numeric_test.go
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
package numeric_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.javil.eu/jacob1123/budgeteer/postgres/numeric"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TestCaseMarshalJSON struct {
|
||||||
|
Value numeric.Numeric
|
||||||
|
Result string
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalJSON(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
tests := []TestCaseMarshalJSON{
|
||||||
|
{numeric.Zero(), `0`},
|
||||||
|
{numeric.MustParse("1.23"), "1.23"},
|
||||||
|
{numeric.MustParse("1,24"), "1.24"},
|
||||||
|
{numeric.MustParse("123456789.12345"), "123456789.12345"},
|
||||||
|
{numeric.MustParse("-1.23"), "-1.23"},
|
||||||
|
{numeric.MustParse("-1,24"), "-1.24"},
|
||||||
|
{numeric.MustParse("-123456789.12345"), "-123456789.12345"},
|
||||||
|
}
|
||||||
|
for i := range tests {
|
||||||
|
test := tests[i]
|
||||||
|
t.Run(test.Result, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
z := test.Value
|
||||||
|
result, err := z.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(result) != test.Result {
|
||||||
|
t.Errorf("Expected %s, got %s", test.Result, string(result))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestCaseParse struct {
|
||||||
|
Result numeric.Numeric
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParse(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
tests := []TestCaseParse{
|
||||||
|
{numeric.FromInt64WithExp(0, 0), `0`},
|
||||||
|
{numeric.FromInt64WithExp(1, 0), `1`},
|
||||||
|
{numeric.FromInt64WithExp(1, 1), `10`},
|
||||||
|
{numeric.FromInt64WithExp(1, 2), `100`},
|
||||||
|
{numeric.FromInt64WithExp(123, -2), "1.23"},
|
||||||
|
{numeric.FromInt64WithExp(124, -2), "1,24"},
|
||||||
|
{numeric.FromInt64WithExp(12345678912345, -5), "123456789.12345"},
|
||||||
|
{numeric.FromInt64WithExp(0, 0), `-0`},
|
||||||
|
{numeric.FromInt64WithExp(-1, 0), `-1`},
|
||||||
|
{numeric.FromInt64WithExp(-1, 1), `-10`},
|
||||||
|
{numeric.FromInt64WithExp(-1, 2), `-100`},
|
||||||
|
{numeric.FromInt64WithExp(-123, -2), "-1.23"},
|
||||||
|
{numeric.FromInt64WithExp(-124, -2), "-1,24"},
|
||||||
|
{numeric.FromInt64WithExp(-12345678912345, -5), "-123456789.12345"},
|
||||||
|
}
|
||||||
|
for i := range tests {
|
||||||
|
test := tests[i]
|
||||||
|
t.Run(test.Value, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
result, err := numeric.Parse(test.Value)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if test.Result.Int.Int64() != result.Int.Int64() {
|
||||||
|
t.Errorf("Expected int %d, got %d", test.Result.Int, result.Int)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if test.Result.Exp != result.Exp {
|
||||||
|
t.Errorf("Expected exp %d, got %d", test.Result.Exp, result.Exp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -19,4 +19,10 @@ FROM accounts
|
|||||||
LEFT JOIN transactions ON transactions.account_id = accounts.id AND transactions.date < NOW()
|
LEFT JOIN transactions ON transactions.account_id = accounts.id AND transactions.date < NOW()
|
||||||
WHERE accounts.budget_id = $1
|
WHERE accounts.budget_id = $1
|
||||||
GROUP BY accounts.id, accounts.name
|
GROUP BY accounts.id, accounts.name
|
||||||
|
ORDER BY accounts.name;
|
||||||
|
|
||||||
|
-- name: SearchAccounts :many
|
||||||
|
SELECT accounts.id, accounts.budget_id, accounts.name FROM accounts
|
||||||
|
WHERE accounts.budget_id = @budget_id
|
||||||
|
AND accounts.name LIKE @search
|
||||||
ORDER BY accounts.name;
|
ORDER BY accounts.name;
|
@ -7,6 +7,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.javil.eu/jacob1123/budgeteer/postgres/numeric"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -20,7 +21,7 @@ RETURNING id, date, memo, amount, account_id, category_id, payee_id, group_id, s
|
|||||||
type CreateTransactionParams struct {
|
type CreateTransactionParams struct {
|
||||||
Date time.Time
|
Date time.Time
|
||||||
Memo string
|
Memo string
|
||||||
Amount Numeric
|
Amount numeric.Numeric
|
||||||
AccountID uuid.UUID
|
AccountID uuid.UUID
|
||||||
PayeeID uuid.NullUUID
|
PayeeID uuid.NullUUID
|
||||||
CategoryID uuid.NullUUID
|
CategoryID uuid.NullUUID
|
||||||
@ -95,7 +96,7 @@ type GetAllTransactionsForBudgetRow struct {
|
|||||||
ID uuid.UUID
|
ID uuid.UUID
|
||||||
Date time.Time
|
Date time.Time
|
||||||
Memo string
|
Memo string
|
||||||
Amount Numeric
|
Amount numeric.Numeric
|
||||||
GroupID uuid.NullUUID
|
GroupID uuid.NullUUID
|
||||||
Status TransactionStatus
|
Status TransactionStatus
|
||||||
Account string
|
Account string
|
||||||
@ -222,7 +223,7 @@ type GetTransactionsForAccountRow struct {
|
|||||||
ID uuid.UUID
|
ID uuid.UUID
|
||||||
Date time.Time
|
Date time.Time
|
||||||
Memo string
|
Memo string
|
||||||
Amount Numeric
|
Amount numeric.Numeric
|
||||||
GroupID uuid.NullUUID
|
GroupID uuid.NullUUID
|
||||||
Status TransactionStatus
|
Status TransactionStatus
|
||||||
Account string
|
Account string
|
||||||
@ -281,7 +282,7 @@ WHERE id = $7
|
|||||||
type UpdateTransactionParams struct {
|
type UpdateTransactionParams struct {
|
||||||
Date time.Time
|
Date time.Time
|
||||||
Memo string
|
Memo string
|
||||||
Amount Numeric
|
Amount numeric.Numeric
|
||||||
AccountID uuid.UUID
|
AccountID uuid.UUID
|
||||||
PayeeID uuid.NullUUID
|
PayeeID uuid.NullUUID
|
||||||
CategoryID uuid.NullUUID
|
CategoryID uuid.NullUUID
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
"git.javil.eu/jacob1123/budgeteer/postgres/numeric"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -43,8 +44,8 @@ func (ynab *YNABExport) ExportAssignments(context context.Context, w io.Writer)
|
|||||||
assignment.Group,
|
assignment.Group,
|
||||||
assignment.Category,
|
assignment.Category,
|
||||||
assignment.Amount.String() + "€",
|
assignment.Amount.String() + "€",
|
||||||
NewZeroNumeric().String() + "€",
|
numeric.Zero().String() + "€",
|
||||||
NewZeroNumeric().String() + "€",
|
numeric.Zero().String() + "€",
|
||||||
}
|
}
|
||||||
|
|
||||||
err := csv.Write(row)
|
err := csv.Write(row)
|
||||||
@ -129,9 +130,9 @@ func GetTransactionRow(transaction GetAllTransactionsForBudgetRow) []string {
|
|||||||
row = append(row, transaction.Memo)
|
row = append(row, transaction.Memo)
|
||||||
|
|
||||||
if transaction.Amount.IsPositive() {
|
if transaction.Amount.IsPositive() {
|
||||||
row = append(row, NewZeroNumeric().String()+"€", transaction.Amount.String()+"€")
|
row = append(row, numeric.Zero().String()+"€", transaction.Amount.String()+"€")
|
||||||
} else {
|
} else {
|
||||||
row = append(row, transaction.Amount.String()[1:]+"€", NewZeroNumeric().String()+"€")
|
row = append(row, transaction.Amount.String()[1:]+"€", numeric.Zero().String()+"€")
|
||||||
}
|
}
|
||||||
|
|
||||||
return append(row, string(transaction.Status))
|
return append(row, string(transaction.Status))
|
||||||
|
@ -7,8 +7,8 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
|
"git.javil.eu/jacob1123/budgeteer/postgres/numeric"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -242,7 +242,7 @@ func (ynab *YNABImport) ImportRegularTransaction(context context.Context, payeeN
|
|||||||
|
|
||||||
func (ynab *YNABImport) ImportTransferTransaction(context context.Context, payeeName string,
|
func (ynab *YNABImport) ImportTransferTransaction(context context.Context, payeeName string,
|
||||||
transaction CreateTransactionParams, openTransfers *[]Transfer,
|
transaction CreateTransactionParams, openTransfers *[]Transfer,
|
||||||
account *Account, amount Numeric) error {
|
account *Account, amount numeric.Numeric) error {
|
||||||
transferToAccountName := payeeName[11:]
|
transferToAccountName := payeeName[11:]
|
||||||
transferToAccount, err := ynab.GetAccount(context, transferToAccountName)
|
transferToAccount, err := ynab.GetAccount(context, transferToAccountName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -295,34 +295,10 @@ func (ynab *YNABImport) ImportTransferTransaction(context context.Context, payee
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func trimLastChar(s string) string {
|
func GetAmount(inflow string, outflow string) (numeric.Numeric, error) {
|
||||||
r, size := utf8.DecodeLastRuneInString(s)
|
in, err := numeric.ParseCurrency(inflow)
|
||||||
if r == utf8.RuneError && (size == 0 || size == 1) {
|
|
||||||
size = 0
|
|
||||||
}
|
|
||||||
return s[:len(s)-size]
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseNumeric(text string) (Numeric, error) {
|
|
||||||
// Remove trailing currency
|
|
||||||
text = trimLastChar(text)
|
|
||||||
|
|
||||||
// Unify decimal separator
|
|
||||||
text = strings.Replace(text, ",", ".", 1)
|
|
||||||
|
|
||||||
num := Numeric{}
|
|
||||||
err := num.Set(text)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return num, fmt.Errorf("parse numeric %s: %w", text, err)
|
return in, fmt.Errorf("parse inflow: %w", err)
|
||||||
}
|
|
||||||
|
|
||||||
return num, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetAmount(inflow string, outflow string) (Numeric, error) {
|
|
||||||
in, err := ParseNumeric(inflow)
|
|
||||||
if err != nil {
|
|
||||||
return in, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !in.IsZero() {
|
if !in.IsZero() {
|
||||||
@ -330,9 +306,9 @@ func GetAmount(inflow string, outflow string) (Numeric, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if inflow is zero, use outflow
|
// if inflow is zero, use outflow
|
||||||
out, err := ParseNumeric("-" + outflow)
|
out, err := numeric.ParseCurrency("-" + outflow)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return out, err
|
return out, fmt.Errorf("parse outflow: %w", err)
|
||||||
}
|
}
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strings"
|
"strings"
|
||||||
@ -22,7 +23,8 @@ func TestRegisterUser(t *testing.T) { //nolint:funlen
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
database, err := postgres.Connect("pgtx", "example")
|
database, err := postgres.Connect("pgtx", "example")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("could not connect to db: %s", err)
|
fmt.Printf("could not connect to db: %s\n", err)
|
||||||
|
t.Skip()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"git.javil.eu/jacob1123/budgeteer/postgres"
|
"git.javil.eu/jacob1123/budgeteer/postgres"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@ -39,15 +40,33 @@ func (h *Handler) autocompletePayee(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
query := c.Request.URL.Query().Get("s")
|
query := c.Request.URL.Query().Get("s")
|
||||||
searchParams := postgres.SearchPayeesParams{
|
|
||||||
BudgetID: budgetUUID,
|
|
||||||
Search: query + "%",
|
|
||||||
}
|
|
||||||
payees, err := h.Service.SearchPayees(c.Request.Context(), searchParams)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithError(http.StatusInternalServerError, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, payees)
|
transferPrefix := "Transfer"
|
||||||
|
if strings.HasPrefix(query, transferPrefix) {
|
||||||
|
searchParams := postgres.SearchAccountsParams{
|
||||||
|
BudgetID: budgetUUID,
|
||||||
|
Search: "%" + strings.Trim(query[len(transferPrefix):], " \t\n:") + "%",
|
||||||
|
}
|
||||||
|
|
||||||
|
accounts, err := h.Service.SearchAccounts(c.Request.Context(), searchParams)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, accounts)
|
||||||
|
} else {
|
||||||
|
searchParams := postgres.SearchPayeesParams{
|
||||||
|
BudgetID: budgetUUID,
|
||||||
|
Search: query + "%",
|
||||||
|
}
|
||||||
|
|
||||||
|
payees, err := h.Service.SearchPayees(c.Request.Context(), searchParams)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, payees)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.javil.eu/jacob1123/budgeteer/postgres"
|
"git.javil.eu/jacob1123/budgeteer/postgres"
|
||||||
|
"git.javil.eu/jacob1123/budgeteer/postgres/numeric"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
@ -24,19 +25,19 @@ func getFirstOfMonthTime(date time.Time) time.Time {
|
|||||||
|
|
||||||
type CategoryWithBalance struct {
|
type CategoryWithBalance struct {
|
||||||
*postgres.GetCategoriesRow
|
*postgres.GetCategoriesRow
|
||||||
Available postgres.Numeric
|
Available numeric.Numeric
|
||||||
AvailableLastMonth postgres.Numeric
|
AvailableLastMonth numeric.Numeric
|
||||||
Activity postgres.Numeric
|
Activity numeric.Numeric
|
||||||
Assigned postgres.Numeric
|
Assigned numeric.Numeric
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCategoryWithBalance(category *postgres.GetCategoriesRow) CategoryWithBalance {
|
func NewCategoryWithBalance(category *postgres.GetCategoriesRow) CategoryWithBalance {
|
||||||
return CategoryWithBalance{
|
return CategoryWithBalance{
|
||||||
GetCategoriesRow: category,
|
GetCategoriesRow: category,
|
||||||
Available: postgres.NewZeroNumeric(),
|
Available: numeric.Zero(),
|
||||||
AvailableLastMonth: postgres.NewZeroNumeric(),
|
AvailableLastMonth: numeric.Zero(),
|
||||||
Activity: postgres.NewZeroNumeric(),
|
Activity: numeric.Zero(),
|
||||||
Assigned: postgres.NewZeroNumeric(),
|
Assigned: numeric.Zero(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,15 +102,15 @@ func (h *Handler) budgetingForMonth(c *gin.Context) {
|
|||||||
|
|
||||||
data := struct {
|
data := struct {
|
||||||
Categories []CategoryWithBalance
|
Categories []CategoryWithBalance
|
||||||
AvailableBalance postgres.Numeric
|
AvailableBalance numeric.Numeric
|
||||||
}{categoriesWithBalance, availableBalance}
|
}{categoriesWithBalance, availableBalance}
|
||||||
c.JSON(http.StatusOK, data)
|
c.JSON(http.StatusOK, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*Handler) getAvailableBalance(categories []postgres.GetCategoriesRow, budget postgres.Budget,
|
func (*Handler) getAvailableBalance(categories []postgres.GetCategoriesRow, budget postgres.Budget,
|
||||||
moneyUsed postgres.Numeric, cumultativeBalances []postgres.GetCumultativeBalancesRow,
|
moneyUsed numeric.Numeric, cumultativeBalances []postgres.GetCumultativeBalancesRow,
|
||||||
firstOfNextMonth time.Time) postgres.Numeric {
|
firstOfNextMonth time.Time) numeric.Numeric {
|
||||||
availableBalance := postgres.NewZeroNumeric()
|
availableBalance := numeric.Zero()
|
||||||
for _, cat := range categories {
|
for _, cat := range categories {
|
||||||
if cat.ID != budget.IncomeCategoryID {
|
if cat.ID != budget.IncomeCategoryID {
|
||||||
continue
|
continue
|
||||||
@ -161,10 +162,10 @@ func (h *Handler) budgeting(c *gin.Context) {
|
|||||||
|
|
||||||
func (h *Handler) calculateBalances(budget postgres.Budget,
|
func (h *Handler) calculateBalances(budget postgres.Budget,
|
||||||
firstOfNextMonth time.Time, firstOfMonth time.Time, categories []postgres.GetCategoriesRow,
|
firstOfNextMonth time.Time, firstOfMonth time.Time, categories []postgres.GetCategoriesRow,
|
||||||
cumultativeBalances []postgres.GetCumultativeBalancesRow) ([]CategoryWithBalance, postgres.Numeric) {
|
cumultativeBalances []postgres.GetCumultativeBalancesRow) ([]CategoryWithBalance, numeric.Numeric) {
|
||||||
categoriesWithBalance := []CategoryWithBalance{}
|
categoriesWithBalance := []CategoryWithBalance{}
|
||||||
|
|
||||||
moneyUsed := postgres.NewZeroNumeric()
|
moneyUsed := numeric.Zero()
|
||||||
for i := range categories {
|
for i := range categories {
|
||||||
cat := &categories[i]
|
cat := &categories[i]
|
||||||
// do not show hidden categories
|
// do not show hidden categories
|
||||||
@ -183,7 +184,7 @@ func (h *Handler) calculateBalances(budget postgres.Budget,
|
|||||||
|
|
||||||
func (*Handler) CalculateCategoryBalances(cat *postgres.GetCategoriesRow,
|
func (*Handler) CalculateCategoryBalances(cat *postgres.GetCategoriesRow,
|
||||||
cumultativeBalances []postgres.GetCumultativeBalancesRow, firstOfNextMonth time.Time,
|
cumultativeBalances []postgres.GetCumultativeBalancesRow, firstOfNextMonth time.Time,
|
||||||
moneyUsed *postgres.Numeric, firstOfMonth time.Time, budget postgres.Budget) CategoryWithBalance {
|
moneyUsed *numeric.Numeric, firstOfMonth time.Time, budget postgres.Budget) CategoryWithBalance {
|
||||||
categoryWithBalance := NewCategoryWithBalance(cat)
|
categoryWithBalance := NewCategoryWithBalance(cat)
|
||||||
for _, bal := range cumultativeBalances {
|
for _, bal := range cumultativeBalances {
|
||||||
if bal.CategoryID != cat.ID {
|
if bal.CategoryID != cat.ID {
|
||||||
@ -200,7 +201,7 @@ func (*Handler) CalculateCategoryBalances(cat *postgres.GetCategoriesRow,
|
|||||||
categoryWithBalance.Available = categoryWithBalance.Available.Add(bal.Transactions)
|
categoryWithBalance.Available = categoryWithBalance.Available.Add(bal.Transactions)
|
||||||
if !categoryWithBalance.Available.IsPositive() && bal.Date.Before(firstOfMonth) {
|
if !categoryWithBalance.Available.IsPositive() && bal.Date.Before(firstOfMonth) {
|
||||||
*moneyUsed = moneyUsed.Add(categoryWithBalance.Available)
|
*moneyUsed = moneyUsed.Add(categoryWithBalance.Available)
|
||||||
categoryWithBalance.Available = postgres.NewZeroNumeric()
|
categoryWithBalance.Available = numeric.Zero()
|
||||||
}
|
}
|
||||||
|
|
||||||
if bal.Date.Before(firstOfMonth) {
|
if bal.Date.Before(firstOfMonth) {
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.javil.eu/jacob1123/budgeteer/postgres"
|
"git.javil.eu/jacob1123/budgeteer/postgres"
|
||||||
|
"git.javil.eu/jacob1123/budgeteer/postgres/numeric"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
@ -35,7 +36,7 @@ func (h *Handler) newTransaction(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
amount := postgres.Numeric{}
|
amount := numeric.Numeric{}
|
||||||
err = amount.Set(payload.Amount)
|
err = amount.Set(payload.Amount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(http.StatusBadRequest, fmt.Errorf("amount: %w", err))
|
c.AbortWithError(http.StatusBadRequest, fmt.Errorf("amount: %w", err))
|
||||||
|
@ -7,9 +7,11 @@ packages:
|
|||||||
queries: "postgres/queries/"
|
queries: "postgres/queries/"
|
||||||
overrides:
|
overrides:
|
||||||
- go_type:
|
- go_type:
|
||||||
type: "Numeric"
|
import: "git.javil.eu/jacob1123/budgeteer/postgres/numeric"
|
||||||
|
type: Numeric
|
||||||
db_type: "pg_catalog.numeric"
|
db_type: "pg_catalog.numeric"
|
||||||
- go_type:
|
- go_type:
|
||||||
type: "Numeric"
|
import: "git.javil.eu/jacob1123/budgeteer/postgres/numeric"
|
||||||
|
type: Numeric
|
||||||
db_type: "pg_catalog.numeric"
|
db_type: "pg_catalog.numeric"
|
||||||
nullable: true
|
nullable: true
|
Loading…
x
Reference in New Issue
Block a user