Merge pull request 'Implement reconciliation' (#26) from reconcilation 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: #26
This commit is contained in:
commit
6b3ac199fc
@ -72,6 +72,24 @@ type CategoryGroup struct {
|
|||||||
Name string
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DisplayTransaction struct {
|
||||||
|
ID uuid.UUID
|
||||||
|
Date time.Time
|
||||||
|
Memo string
|
||||||
|
Amount numeric.Numeric
|
||||||
|
GroupID uuid.NullUUID
|
||||||
|
Status TransactionStatus
|
||||||
|
Account string
|
||||||
|
PayeeID uuid.NullUUID
|
||||||
|
CategoryID uuid.NullUUID
|
||||||
|
Payee string
|
||||||
|
CategoryGroup string
|
||||||
|
Category string
|
||||||
|
TransferAccount string
|
||||||
|
BudgetID uuid.UUID
|
||||||
|
AccountID uuid.UUID
|
||||||
|
}
|
||||||
|
|
||||||
type Payee struct {
|
type Payee struct {
|
||||||
ID uuid.UUID
|
ID uuid.UUID
|
||||||
BudgetID uuid.UUID
|
BudgetID uuid.UUID
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
-- name: GetTransaction :one
|
-- name: GetTransaction :one
|
||||||
SELECT * FROM transactions
|
SELECT * FROM display_transactions
|
||||||
WHERE id = $1;
|
WHERE id = $1;
|
||||||
|
|
||||||
-- name: CreateTransaction :one
|
-- name: CreateTransaction :one
|
||||||
INSERT INTO transactions
|
INSERT INTO transactions
|
||||||
(date, memo, amount, account_id, payee_id, category_id, group_id, status)
|
(date, memo, amount, account_id, payee_id, category_id, group_id, status)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||||
RETURNING *;
|
RETURNING id;
|
||||||
|
|
||||||
-- name: UpdateTransaction :exec
|
-- name: UpdateTransaction :exec
|
||||||
UPDATE transactions
|
UPDATE transactions
|
||||||
@ -17,53 +17,24 @@ SET date = $1,
|
|||||||
category_id = $5
|
category_id = $5
|
||||||
WHERE id = $6;
|
WHERE id = $6;
|
||||||
|
|
||||||
|
-- name: SetTransactionReconciled :exec
|
||||||
|
UPDATE transactions
|
||||||
|
SET status = 'Reconciled'
|
||||||
|
WHERE id = $1;
|
||||||
|
|
||||||
-- name: DeleteTransaction :exec
|
-- name: DeleteTransaction :exec
|
||||||
DELETE FROM transactions
|
DELETE FROM transactions
|
||||||
WHERE id = $1;
|
WHERE id = $1;
|
||||||
|
|
||||||
-- name: GetAllTransactionsForBudget :many
|
-- name: GetAllTransactionsForBudget :many
|
||||||
SELECT transactions.id, transactions.date, transactions.memo,
|
SELECT t.*
|
||||||
transactions.amount, transactions.group_id, transactions.status,
|
FROM display_transactions AS t
|
||||||
accounts.name as account, transactions.payee_id, transactions.category_id,
|
WHERE t.budget_id = $1;
|
||||||
COALESCE(payees.name, '') as payee,
|
|
||||||
COALESCE(category_groups.name, '') as category_group,
|
|
||||||
COALESCE(categories.name, '') as category,
|
|
||||||
COALESCE((
|
|
||||||
SELECT CONCAT(otherAccounts.name)
|
|
||||||
FROM transactions otherTransactions
|
|
||||||
LEFT JOIN accounts otherAccounts ON otherAccounts.id = otherTransactions.account_id
|
|
||||||
WHERE otherTransactions.group_id = transactions.group_id
|
|
||||||
AND otherTransactions.id != transactions.id
|
|
||||||
), '')::text as transfer_account
|
|
||||||
FROM transactions
|
|
||||||
INNER JOIN accounts ON accounts.id = transactions.account_id
|
|
||||||
LEFT JOIN payees ON payees.id = transactions.payee_id
|
|
||||||
LEFT JOIN categories ON categories.id = transactions.category_id
|
|
||||||
LEFT JOIN category_groups ON category_groups.id = categories.category_group_id
|
|
||||||
WHERE accounts.budget_id = $1
|
|
||||||
ORDER BY transactions.date DESC;
|
|
||||||
|
|
||||||
-- name: GetTransactionsForAccount :many
|
-- name: GetTransactionsForAccount :many
|
||||||
SELECT transactions.id, transactions.date, transactions.memo,
|
SELECT t.*
|
||||||
transactions.amount, transactions.group_id, transactions.status,
|
FROM display_transactions AS t
|
||||||
accounts.name as account, transactions.payee_id, transactions.category_id,
|
WHERE t.account_id = $1
|
||||||
COALESCE(payees.name, '') as payee,
|
|
||||||
COALESCE(category_groups.name, '') as category_group,
|
|
||||||
COALESCE(categories.name, '') as category,
|
|
||||||
COALESCE((
|
|
||||||
SELECT CONCAT(otherAccounts.name)
|
|
||||||
FROM transactions otherTransactions
|
|
||||||
LEFT JOIN accounts otherAccounts ON otherAccounts.id = otherTransactions.account_id
|
|
||||||
WHERE otherTransactions.group_id = transactions.group_id
|
|
||||||
AND otherTransactions.id != transactions.id
|
|
||||||
), '')::text as transfer_account
|
|
||||||
FROM transactions
|
|
||||||
INNER JOIN accounts ON accounts.id = transactions.account_id
|
|
||||||
LEFT JOIN payees ON payees.id = transactions.payee_id
|
|
||||||
LEFT JOIN categories ON categories.id = transactions.category_id
|
|
||||||
LEFT JOIN category_groups ON category_groups.id = categories.category_group_id
|
|
||||||
WHERE transactions.account_id = $1
|
|
||||||
ORDER BY transactions.date DESC
|
|
||||||
LIMIT 200;
|
LIMIT 200;
|
||||||
|
|
||||||
-- name: DeleteAllTransactions :execrows
|
-- name: DeleteAllTransactions :execrows
|
||||||
|
25
postgres/schema/0015_transactions-view.sql
Normal file
25
postgres/schema/0015_transactions-view.sql
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
-- +goose Up
|
||||||
|
CREATE VIEW display_transactions AS
|
||||||
|
SELECT transactions.id, transactions.date, transactions.memo,
|
||||||
|
transactions.amount, transactions.group_id, transactions.status,
|
||||||
|
accounts.name as account, transactions.payee_id, transactions.category_id,
|
||||||
|
COALESCE(payees.name, '') as payee,
|
||||||
|
COALESCE(category_groups.name, '') as category_group,
|
||||||
|
COALESCE(categories.name, '') as category,
|
||||||
|
COALESCE((
|
||||||
|
SELECT CONCAT(otherAccounts.name)
|
||||||
|
FROM transactions otherTransactions
|
||||||
|
LEFT JOIN accounts otherAccounts ON otherAccounts.id = otherTransactions.account_id
|
||||||
|
WHERE otherTransactions.group_id = transactions.group_id
|
||||||
|
AND otherTransactions.id != transactions.id
|
||||||
|
), '')::text as transfer_account,
|
||||||
|
accounts.budget_id, transactions.account_id
|
||||||
|
FROM transactions
|
||||||
|
INNER JOIN accounts ON accounts.id = transactions.account_id
|
||||||
|
LEFT JOIN payees ON payees.id = transactions.payee_id
|
||||||
|
LEFT JOIN categories ON categories.id = transactions.category_id
|
||||||
|
LEFT JOIN category_groups ON category_groups.id = categories.category_group_id
|
||||||
|
ORDER BY transactions.date DESC;
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
DROP VIEW display_transactions;
|
@ -15,7 +15,7 @@ const createTransaction = `-- name: CreateTransaction :one
|
|||||||
INSERT INTO transactions
|
INSERT INTO transactions
|
||||||
(date, memo, amount, account_id, payee_id, category_id, group_id, status)
|
(date, memo, amount, account_id, payee_id, category_id, group_id, status)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||||
RETURNING id, date, memo, amount, account_id, category_id, payee_id, group_id, status
|
RETURNING id
|
||||||
`
|
`
|
||||||
|
|
||||||
type CreateTransactionParams struct {
|
type CreateTransactionParams struct {
|
||||||
@ -29,7 +29,7 @@ type CreateTransactionParams struct {
|
|||||||
Status TransactionStatus
|
Status TransactionStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) CreateTransaction(ctx context.Context, arg CreateTransactionParams) (Transaction, error) {
|
func (q *Queries) CreateTransaction(ctx context.Context, arg CreateTransactionParams) (uuid.UUID, error) {
|
||||||
row := q.db.QueryRowContext(ctx, createTransaction,
|
row := q.db.QueryRowContext(ctx, createTransaction,
|
||||||
arg.Date,
|
arg.Date,
|
||||||
arg.Memo,
|
arg.Memo,
|
||||||
@ -40,19 +40,9 @@ func (q *Queries) CreateTransaction(ctx context.Context, arg CreateTransactionPa
|
|||||||
arg.GroupID,
|
arg.GroupID,
|
||||||
arg.Status,
|
arg.Status,
|
||||||
)
|
)
|
||||||
var i Transaction
|
var id uuid.UUID
|
||||||
err := row.Scan(
|
err := row.Scan(&id)
|
||||||
&i.ID,
|
return id, err
|
||||||
&i.Date,
|
|
||||||
&i.Memo,
|
|
||||||
&i.Amount,
|
|
||||||
&i.AccountID,
|
|
||||||
&i.CategoryID,
|
|
||||||
&i.PayeeID,
|
|
||||||
&i.GroupID,
|
|
||||||
&i.Status,
|
|
||||||
)
|
|
||||||
return i, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteAllTransactions = `-- name: DeleteAllTransactions :execrows
|
const deleteAllTransactions = `-- name: DeleteAllTransactions :execrows
|
||||||
@ -81,53 +71,20 @@ func (q *Queries) DeleteTransaction(ctx context.Context, id uuid.UUID) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getAllTransactionsForBudget = `-- name: GetAllTransactionsForBudget :many
|
const getAllTransactionsForBudget = `-- name: GetAllTransactionsForBudget :many
|
||||||
SELECT transactions.id, transactions.date, transactions.memo,
|
SELECT t.id, t.date, t.memo, t.amount, t.group_id, t.status, t.account, t.payee_id, t.category_id, t.payee, t.category_group, t.category, t.transfer_account, t.budget_id, t.account_id
|
||||||
transactions.amount, transactions.group_id, transactions.status,
|
FROM display_transactions AS t
|
||||||
accounts.name as account, transactions.payee_id, transactions.category_id,
|
WHERE t.budget_id = $1
|
||||||
COALESCE(payees.name, '') as payee,
|
|
||||||
COALESCE(category_groups.name, '') as category_group,
|
|
||||||
COALESCE(categories.name, '') as category,
|
|
||||||
COALESCE((
|
|
||||||
SELECT CONCAT(otherAccounts.name)
|
|
||||||
FROM transactions otherTransactions
|
|
||||||
LEFT JOIN accounts otherAccounts ON otherAccounts.id = otherTransactions.account_id
|
|
||||||
WHERE otherTransactions.group_id = transactions.group_id
|
|
||||||
AND otherTransactions.id != transactions.id
|
|
||||||
), '')::text as transfer_account
|
|
||||||
FROM transactions
|
|
||||||
INNER JOIN accounts ON accounts.id = transactions.account_id
|
|
||||||
LEFT JOIN payees ON payees.id = transactions.payee_id
|
|
||||||
LEFT JOIN categories ON categories.id = transactions.category_id
|
|
||||||
LEFT JOIN category_groups ON category_groups.id = categories.category_group_id
|
|
||||||
WHERE accounts.budget_id = $1
|
|
||||||
ORDER BY transactions.date DESC
|
|
||||||
`
|
`
|
||||||
|
|
||||||
type GetAllTransactionsForBudgetRow struct {
|
func (q *Queries) GetAllTransactionsForBudget(ctx context.Context, budgetID uuid.UUID) ([]DisplayTransaction, error) {
|
||||||
ID uuid.UUID
|
|
||||||
Date time.Time
|
|
||||||
Memo string
|
|
||||||
Amount numeric.Numeric
|
|
||||||
GroupID uuid.NullUUID
|
|
||||||
Status TransactionStatus
|
|
||||||
Account string
|
|
||||||
PayeeID uuid.NullUUID
|
|
||||||
CategoryID uuid.NullUUID
|
|
||||||
Payee string
|
|
||||||
CategoryGroup string
|
|
||||||
Category string
|
|
||||||
TransferAccount string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Queries) GetAllTransactionsForBudget(ctx context.Context, budgetID uuid.UUID) ([]GetAllTransactionsForBudgetRow, error) {
|
|
||||||
rows, err := q.db.QueryContext(ctx, getAllTransactionsForBudget, budgetID)
|
rows, err := q.db.QueryContext(ctx, getAllTransactionsForBudget, budgetID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
var items []GetAllTransactionsForBudgetRow
|
var items []DisplayTransaction
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var i GetAllTransactionsForBudgetRow
|
var i DisplayTransaction
|
||||||
if err := rows.Scan(
|
if err := rows.Scan(
|
||||||
&i.ID,
|
&i.ID,
|
||||||
&i.Date,
|
&i.Date,
|
||||||
@ -142,6 +99,8 @@ func (q *Queries) GetAllTransactionsForBudget(ctx context.Context, budgetID uuid
|
|||||||
&i.CategoryGroup,
|
&i.CategoryGroup,
|
||||||
&i.Category,
|
&i.Category,
|
||||||
&i.TransferAccount,
|
&i.TransferAccount,
|
||||||
|
&i.BudgetID,
|
||||||
|
&i.AccountID,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -157,23 +116,29 @@ func (q *Queries) GetAllTransactionsForBudget(ctx context.Context, budgetID uuid
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getTransaction = `-- name: GetTransaction :one
|
const getTransaction = `-- name: GetTransaction :one
|
||||||
SELECT id, date, memo, amount, account_id, category_id, payee_id, group_id, status FROM transactions
|
SELECT id, date, memo, amount, group_id, status, account, payee_id, category_id, payee, category_group, category, transfer_account, budget_id, account_id FROM display_transactions
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) GetTransaction(ctx context.Context, id uuid.UUID) (Transaction, error) {
|
func (q *Queries) GetTransaction(ctx context.Context, id uuid.UUID) (DisplayTransaction, error) {
|
||||||
row := q.db.QueryRowContext(ctx, getTransaction, id)
|
row := q.db.QueryRowContext(ctx, getTransaction, id)
|
||||||
var i Transaction
|
var i DisplayTransaction
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
&i.ID,
|
&i.ID,
|
||||||
&i.Date,
|
&i.Date,
|
||||||
&i.Memo,
|
&i.Memo,
|
||||||
&i.Amount,
|
&i.Amount,
|
||||||
&i.AccountID,
|
|
||||||
&i.CategoryID,
|
|
||||||
&i.PayeeID,
|
|
||||||
&i.GroupID,
|
&i.GroupID,
|
||||||
&i.Status,
|
&i.Status,
|
||||||
|
&i.Account,
|
||||||
|
&i.PayeeID,
|
||||||
|
&i.CategoryID,
|
||||||
|
&i.Payee,
|
||||||
|
&i.CategoryGroup,
|
||||||
|
&i.Category,
|
||||||
|
&i.TransferAccount,
|
||||||
|
&i.BudgetID,
|
||||||
|
&i.AccountID,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
@ -213,54 +178,21 @@ func (q *Queries) GetTransactionsByMonthAndCategory(ctx context.Context, budgetI
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getTransactionsForAccount = `-- name: GetTransactionsForAccount :many
|
const getTransactionsForAccount = `-- name: GetTransactionsForAccount :many
|
||||||
SELECT transactions.id, transactions.date, transactions.memo,
|
SELECT t.id, t.date, t.memo, t.amount, t.group_id, t.status, t.account, t.payee_id, t.category_id, t.payee, t.category_group, t.category, t.transfer_account, t.budget_id, t.account_id
|
||||||
transactions.amount, transactions.group_id, transactions.status,
|
FROM display_transactions AS t
|
||||||
accounts.name as account, transactions.payee_id, transactions.category_id,
|
WHERE t.account_id = $1
|
||||||
COALESCE(payees.name, '') as payee,
|
|
||||||
COALESCE(category_groups.name, '') as category_group,
|
|
||||||
COALESCE(categories.name, '') as category,
|
|
||||||
COALESCE((
|
|
||||||
SELECT CONCAT(otherAccounts.name)
|
|
||||||
FROM transactions otherTransactions
|
|
||||||
LEFT JOIN accounts otherAccounts ON otherAccounts.id = otherTransactions.account_id
|
|
||||||
WHERE otherTransactions.group_id = transactions.group_id
|
|
||||||
AND otherTransactions.id != transactions.id
|
|
||||||
), '')::text as transfer_account
|
|
||||||
FROM transactions
|
|
||||||
INNER JOIN accounts ON accounts.id = transactions.account_id
|
|
||||||
LEFT JOIN payees ON payees.id = transactions.payee_id
|
|
||||||
LEFT JOIN categories ON categories.id = transactions.category_id
|
|
||||||
LEFT JOIN category_groups ON category_groups.id = categories.category_group_id
|
|
||||||
WHERE transactions.account_id = $1
|
|
||||||
ORDER BY transactions.date DESC
|
|
||||||
LIMIT 200
|
LIMIT 200
|
||||||
`
|
`
|
||||||
|
|
||||||
type GetTransactionsForAccountRow struct {
|
func (q *Queries) GetTransactionsForAccount(ctx context.Context, accountID uuid.UUID) ([]DisplayTransaction, error) {
|
||||||
ID uuid.UUID
|
|
||||||
Date time.Time
|
|
||||||
Memo string
|
|
||||||
Amount numeric.Numeric
|
|
||||||
GroupID uuid.NullUUID
|
|
||||||
Status TransactionStatus
|
|
||||||
Account string
|
|
||||||
PayeeID uuid.NullUUID
|
|
||||||
CategoryID uuid.NullUUID
|
|
||||||
Payee string
|
|
||||||
CategoryGroup string
|
|
||||||
Category string
|
|
||||||
TransferAccount string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Queries) GetTransactionsForAccount(ctx context.Context, accountID uuid.UUID) ([]GetTransactionsForAccountRow, error) {
|
|
||||||
rows, err := q.db.QueryContext(ctx, getTransactionsForAccount, accountID)
|
rows, err := q.db.QueryContext(ctx, getTransactionsForAccount, accountID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
var items []GetTransactionsForAccountRow
|
var items []DisplayTransaction
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var i GetTransactionsForAccountRow
|
var i DisplayTransaction
|
||||||
if err := rows.Scan(
|
if err := rows.Scan(
|
||||||
&i.ID,
|
&i.ID,
|
||||||
&i.Date,
|
&i.Date,
|
||||||
@ -275,6 +207,8 @@ func (q *Queries) GetTransactionsForAccount(ctx context.Context, accountID uuid.
|
|||||||
&i.CategoryGroup,
|
&i.CategoryGroup,
|
||||||
&i.Category,
|
&i.Category,
|
||||||
&i.TransferAccount,
|
&i.TransferAccount,
|
||||||
|
&i.BudgetID,
|
||||||
|
&i.AccountID,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -289,6 +223,17 @@ func (q *Queries) GetTransactionsForAccount(ctx context.Context, accountID uuid.
|
|||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const setTransactionReconciled = `-- name: SetTransactionReconciled :exec
|
||||||
|
UPDATE transactions
|
||||||
|
SET status = 'Reconciled'
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) SetTransactionReconciled(ctx context.Context, id uuid.UUID) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, setTransactionReconciled, id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
const updateTransaction = `-- name: UpdateTransaction :exec
|
const updateTransaction = `-- name: UpdateTransaction :exec
|
||||||
UPDATE transactions
|
UPDATE transactions
|
||||||
SET date = $1,
|
SET date = $1,
|
||||||
|
@ -110,7 +110,7 @@ func (ynab *YNABExport) ExportTransactions(context context.Context, w io.Writer)
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetTransactionRow(transaction GetAllTransactionsForBudgetRow) []string {
|
func GetTransactionRow(transaction DisplayTransaction) []string {
|
||||||
row := []string{
|
row := []string{
|
||||||
transaction.Account,
|
transaction.Account,
|
||||||
"", // Flag
|
"", // Flag
|
||||||
|
@ -33,7 +33,7 @@ func (h *Handler) transactionsForAccount(c *gin.Context) {
|
|||||||
|
|
||||||
type TransactionsResponse struct {
|
type TransactionsResponse struct {
|
||||||
Account postgres.Account
|
Account postgres.Account
|
||||||
Transactions []postgres.GetTransactionsForAccountRow
|
Transactions []postgres.DisplayTransaction
|
||||||
}
|
}
|
||||||
|
|
||||||
type EditAccountRequest struct {
|
type EditAccountRequest struct {
|
||||||
|
@ -36,6 +36,10 @@ type ErrorResponse struct {
|
|||||||
Message string
|
Message string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SuccessResponse struct {
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
// LoadRoutes initializes all the routes.
|
// LoadRoutes initializes all the routes.
|
||||||
func (h *Handler) LoadRoutes(router *gin.Engine) {
|
func (h *Handler) LoadRoutes(router *gin.Engine) {
|
||||||
router.Use(enableCachingForStaticFiles())
|
router.Use(enableCachingForStaticFiles())
|
||||||
@ -52,6 +56,7 @@ func (h *Handler) LoadRoutes(router *gin.Engine) {
|
|||||||
authenticated.Use(h.verifyLoginWithForbidden)
|
authenticated.Use(h.verifyLoginWithForbidden)
|
||||||
authenticated.GET("/dashboard", h.dashboard)
|
authenticated.GET("/dashboard", h.dashboard)
|
||||||
authenticated.GET("/account/:accountid/transactions", h.transactionsForAccount)
|
authenticated.GET("/account/:accountid/transactions", h.transactionsForAccount)
|
||||||
|
authenticated.POST("/account/:accountid/reconcile", h.reconcileTransactions)
|
||||||
authenticated.POST("/account/:accountid", h.editAccount)
|
authenticated.POST("/account/:accountid", h.editAccount)
|
||||||
authenticated.GET("/admin/clear-database", h.clearDatabase)
|
authenticated.GET("/admin/clear-database", h.clearDatabase)
|
||||||
|
|
||||||
|
103
server/reconcile.go
Normal file
103
server/reconcile.go
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.javil.eu/jacob1123/budgeteer/postgres"
|
||||||
|
"git.javil.eu/jacob1123/budgeteer/postgres/numeric"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ReconcileTransactionsRequest struct {
|
||||||
|
TransactionIDs []uuid.UUID `json:"transactionIds"`
|
||||||
|
ReconcilationTransactionAmount string `json:"reconciliationTransactionAmount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReconcileTransactionsResponse struct {
|
||||||
|
Message string
|
||||||
|
ReconciliationTransaction *postgres.DisplayTransaction
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) reconcileTransactions(c *gin.Context) {
|
||||||
|
accountID := c.Param("accountid")
|
||||||
|
accountUUID, err := uuid.Parse(accountID)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var request ReconcileTransactionsRequest
|
||||||
|
err = c.BindJSON(&request)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(http.StatusBadRequest, fmt.Errorf("parse request: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var amount numeric.Numeric
|
||||||
|
err = amount.Set(request.ReconcilationTransactionAmount)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(http.StatusBadRequest, fmt.Errorf("parse request: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tx, err := h.Service.BeginTx(c.Request.Context(), &sql.TxOptions{})
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("begin tx: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
db := h.Service.WithTx(tx)
|
||||||
|
for _, transactionID := range request.TransactionIDs {
|
||||||
|
err := db.SetTransactionReconciled(c.Request.Context(), transactionID)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("update transaction: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reconciliationTransaction, err := h.CreateReconcilationTransaction(amount, accountUUID, db, c)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("insert new transaction: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tx.Commit()
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("commit: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, ReconcileTransactionsResponse{
|
||||||
|
Message: fmt.Sprintf("Set status for %d transactions", len(request.TransactionIDs)),
|
||||||
|
ReconciliationTransaction: reconciliationTransaction,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Handler) CreateReconcilationTransaction(amount numeric.Numeric, accountUUID uuid.UUID, db *postgres.Queries, c *gin.Context) (*postgres.DisplayTransaction, error) {
|
||||||
|
if amount.IsZero() {
|
||||||
|
return nil, nil //nolint: nilnil
|
||||||
|
}
|
||||||
|
|
||||||
|
createTransaction := postgres.CreateTransactionParams{
|
||||||
|
Date: time.Now(),
|
||||||
|
Memo: "Reconciliation Transaction",
|
||||||
|
Amount: amount,
|
||||||
|
AccountID: accountUUID,
|
||||||
|
Status: "Reconciled",
|
||||||
|
}
|
||||||
|
transactionUUID, err := db.CreateTransaction(c.Request.Context(), createTransaction)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("insert new transaction: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction, err := db.GetTransaction(c.Request.Context(), transactionUUID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get created transaction: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &transaction, nil
|
||||||
|
}
|
@ -70,11 +70,18 @@ func (h *Handler) newTransaction(c *gin.Context) {
|
|||||||
newTransaction.PayeeID = payeeID
|
newTransaction.PayeeID = payeeID
|
||||||
}
|
}
|
||||||
|
|
||||||
transaction, err := h.Service.CreateTransaction(c.Request.Context(), newTransaction)
|
transactionUUID, err := h.Service.CreateTransaction(c.Request.Context(), newTransaction)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("create transaction: %w", err))
|
c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("create transaction: %w", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transaction, err := h.Service.GetTransaction(c.Request.Context(), transactionUUID)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("get transaction: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, transaction)
|
c.JSON(http.StatusOK, transaction)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,7 +107,16 @@ func (h *Handler) UpdateTransaction(payload NewTransactionPayload, amount numeri
|
|||||||
err := h.Service.UpdateTransaction(c.Request.Context(), editTransaction)
|
err := h.Service.UpdateTransaction(c.Request.Context(), editTransaction)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("edit transaction: %w", err))
|
c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("edit transaction: %w", err))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transaction, err := h.Service.GetTransaction(c.Request.Context(), transactionUUID)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("get transaction: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, transaction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) CreateTransferForOtherAccount(newTransaction postgres.CreateTransactionParams, amount numeric.Numeric, payload NewTransactionPayload, c *gin.Context) error {
|
func (h *Handler) CreateTransferForOtherAccount(newTransaction postgres.CreateTransactionParams, amount numeric.Numeric, payload NewTransactionPayload, c *gin.Context) error {
|
||||||
|
@ -8,6 +8,8 @@ const props = defineProps<{
|
|||||||
transactionid: string
|
transactionid: string
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits(["save"]);
|
||||||
|
|
||||||
const accountStore = useAccountStore();
|
const accountStore = useAccountStore();
|
||||||
const TX = accountStore.Transactions.get(props.transactionid)!;
|
const TX = accountStore.Transactions.get(props.transactionid)!;
|
||||||
const payeeType = ref<string|undefined>(undefined);
|
const payeeType = ref<string|undefined>(undefined);
|
||||||
@ -28,6 +30,7 @@ const payload = computed(() => JSON.stringify({
|
|||||||
function saveTransaction(e: MouseEvent) {
|
function saveTransaction(e: MouseEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
accountStore.editTransaction(TX.ID, payload.value);
|
accountStore.editTransaction(TX.ID, payload.value);
|
||||||
|
emit('save');
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref } from "vue";
|
import { computed, ref } from "vue";
|
||||||
import { useBudgetsStore } from "../stores/budget";
|
import { useBudgetsStore } from "../stores/budget";
|
||||||
import { Transaction } from "../stores/budget-account";
|
import { Transaction, useAccountStore } from "../stores/budget-account";
|
||||||
import Currency from "./Currency.vue";
|
import Currency from "./Currency.vue";
|
||||||
import TransactionEditRow from "./TransactionEditRow.vue";
|
import TransactionEditRow from "./TransactionEditRow.vue";
|
||||||
import { formatDate } from "../date";
|
import { formatDate } from "../date";
|
||||||
@ -13,32 +13,38 @@ const props = defineProps<{
|
|||||||
|
|
||||||
const edit = ref(false);
|
const edit = ref(false);
|
||||||
|
|
||||||
const CurrentBudgetID = computed(()=> useBudgetsStore().CurrentBudgetID);
|
const CurrentBudgetID = computed(() => useBudgetsStore().CurrentBudgetID);
|
||||||
|
const Reconciling = computed(() => useAccountStore().Reconciling);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<tr v-if="!edit" class="{{new Date(transaction.Date) > new Date() ? 'future' : ''}}"
|
<tr
|
||||||
:class="[index % 6 < 3 ? 'bg-gray-300' : 'bg-gray-100']">
|
v-if="!edit"
|
||||||
|
class="{{new Date(transaction.Date) > new Date() ? 'future' : ''}}"
|
||||||
|
:class="[index % 6 < 3 ? 'bg-gray-300' : 'bg-gray-100']"
|
||||||
|
>
|
||||||
<!--:class="[index % 6 < 3 ? index % 6 === 1 ? 'bg-gray-400' : 'bg-gray-300' : index % 6 !== 4 ? 'bg-gray-100' : '']">-->
|
<!--:class="[index % 6 < 3 ? index % 6 === 1 ? 'bg-gray-400' : 'bg-gray-300' : index % 6 !== 4 ? 'bg-gray-100' : '']">-->
|
||||||
<td>{{ formatDate(transaction.Date) }}</td>
|
<td>{{ formatDate(transaction.Date) }}</td>
|
||||||
<td>{{ transaction.TransferAccount ? "Transfer : " + transaction.TransferAccount : transaction.Payee }}</td>
|
<td>{{ transaction.TransferAccount ? "Transfer : " + transaction.TransferAccount : transaction.Payee }}</td>
|
||||||
|
<td>{{ transaction.CategoryGroup ? transaction.CategoryGroup + " : " + transaction.Category : "" }}</td>
|
||||||
<td>
|
<td>
|
||||||
{{ transaction.CategoryGroup ? transaction.CategoryGroup + " : " + transaction.Category : "" }}
|
<a
|
||||||
</td>
|
:href="'/budget/' + CurrentBudgetID + '/transaction/' + transaction.ID"
|
||||||
<td>
|
>{{ transaction.Memo }}</a>
|
||||||
<a :href="'/budget/' + CurrentBudgetID + '/transaction/' + transaction.ID">
|
|
||||||
{{ transaction.Memo }}
|
|
||||||
</a>
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<Currency class="block" :value="transaction.Amount" />
|
<Currency class="block" :value="transaction.Amount" />
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>{{ transaction.Status == "Reconciled" ? "✔" : (transaction.Status == "Uncleared" ? "" : "*") }}</td>
|
||||||
{{ transaction.Status == "Reconciled" ? "✔" : (transaction.Status == "Uncleared" ? "" : "*") }}
|
<td class="text-right">
|
||||||
|
{{ transaction.GroupID ? "☀" : "" }}
|
||||||
|
<a @click="edit = true;">✎</a>
|
||||||
|
</td>
|
||||||
|
<td v-if="Reconciling && transaction.Status != 'Reconciled'">
|
||||||
|
<input type="checkbox" v-model="transaction.Reconciled" />
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right">{{ transaction.GroupID ? "☀" : "" }}<a @click="edit = true;">✎</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<TransactionEditRow v-if="edit" :transactionid="transaction.ID" />
|
<TransactionEditRow v-if="edit" :transactionid="transaction.ID" @save="edit = false" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -5,35 +5,76 @@ import TransactionRow from "../components/TransactionRow.vue";
|
|||||||
import TransactionInputRow from "../components/TransactionInputRow.vue";
|
import TransactionInputRow from "../components/TransactionInputRow.vue";
|
||||||
import { useAccountStore } from "../stores/budget-account";
|
import { useAccountStore } from "../stores/budget-account";
|
||||||
import EditAccount from "../dialogs/EditAccount.vue";
|
import EditAccount from "../dialogs/EditAccount.vue";
|
||||||
|
import Button from "../components/Button.vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
defineProps<{
|
||||||
budgetid: string
|
budgetid: string
|
||||||
accountid: string
|
accountid: string
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const accountStore = useAccountStore();
|
const accounts = useAccountStore();
|
||||||
const CurrentAccount = computed(() => accountStore.CurrentAccount);
|
const TargetReconcilingBalance = ref(0);
|
||||||
const TransactionsList = computed(() => accountStore.TransactionsList);
|
|
||||||
|
|
||||||
|
function setReconciled(event: Event) {
|
||||||
|
const target = event.target as HTMLInputElement;
|
||||||
|
accounts.SetReconciledForAllTransactions(target.checked);
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelReconcilation() {
|
||||||
|
accounts.SetReconciledForAllTransactions(false);
|
||||||
|
accounts.Reconciling = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitReconcilation() {
|
||||||
|
accounts.SubmitReconcilation(0);
|
||||||
|
accounts.Reconciling = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createReconcilationTransaction() {
|
||||||
|
const diff = TargetReconcilingBalance.value - accounts.ReconcilingBalance ;
|
||||||
|
accounts.SubmitReconcilation(diff);
|
||||||
|
accounts.Reconciling = false;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<h1 class="inline">{{ CurrentAccount?.Name }}</h1>
|
<h1 class="inline">{{ accounts.CurrentAccount?.Name }}</h1>
|
||||||
<EditAccount /> <br />
|
<EditAccount />
|
||||||
|
<br />
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
Current Balance:
|
Current Balance:
|
||||||
<Currency :value="CurrentAccount?.WorkingBalance" />
|
<Currency :value="accounts.CurrentAccount?.WorkingBalance" />
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
Cleared Balance:
|
Cleared Balance:
|
||||||
<Currency :value="CurrentAccount?.ClearedBalance" />
|
<Currency :value="accounts.CurrentAccount?.ClearedBalance" />
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span>
|
<span v-if="accounts.Reconciling" class="border-2 block bg-gray-200 rounded-lg p-2">
|
||||||
|
Is <Currency :value="accounts.ReconcilingBalance" /> your current balance?
|
||||||
|
<Button
|
||||||
|
class="bg-blue-500 mx-3"
|
||||||
|
@click="submitReconcilation">Yes!</Button>
|
||||||
|
<br />
|
||||||
|
|
||||||
|
No, it's: <input class="text-right" type="number" v-model="TargetReconcilingBalance" />
|
||||||
|
Difference: <Currency :value="accounts.ReconcilingBalance - TargetReconcilingBalance" />
|
||||||
|
<Button
|
||||||
|
class="bg-orange-500 mx-3"
|
||||||
|
v-if="Math.abs(accounts.ReconcilingBalance - TargetReconcilingBalance) > 0.01"
|
||||||
|
@click="createReconcilationTransaction"
|
||||||
|
>Create reconciling Transaction</Button>
|
||||||
|
<Button
|
||||||
|
class="bg-red-500 mx-3"
|
||||||
|
@click="cancelReconcilation"
|
||||||
|
>Cancel</Button>
|
||||||
|
</span>
|
||||||
|
<span v-if="!accounts.Reconciling">
|
||||||
Reconciled Balance:
|
Reconciled Balance:
|
||||||
<Currency :value="CurrentAccount?.ReconciledBalance" />
|
<Currency :value="accounts.CurrentAccount?.ReconciledBalance" />
|
||||||
|
<Button class="bg-blue-500" @click="accounts.Reconciling = true" v-if="!accounts.Reconciling">Reconcile</Button>
|
||||||
</span>
|
</span>
|
||||||
<table>
|
<table>
|
||||||
<tr class="font-bold">
|
<tr class="font-bold">
|
||||||
@ -44,10 +85,13 @@ const TransactionsList = computed(() => accountStore.TransactionsList);
|
|||||||
<td class="text-right">Amount</td>
|
<td class="text-right">Amount</td>
|
||||||
<td style="width: 20px;"></td>
|
<td style="width: 20px;"></td>
|
||||||
<td style="width: 40px;"></td>
|
<td style="width: 40px;"></td>
|
||||||
|
<td style="width: 20px;" v-if="accounts.Reconciling">
|
||||||
|
<input type="checkbox" @input="setReconciled" />
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<TransactionInputRow :budgetid="budgetid" :accountid="accountid" />
|
<TransactionInputRow :budgetid="budgetid" :accountid="accountid" />
|
||||||
<TransactionRow
|
<TransactionRow
|
||||||
v-for="(transaction, index) in TransactionsList"
|
v-for="(transaction, index) in accounts.TransactionsList" :key="transaction.ID"
|
||||||
:transaction="transaction"
|
:transaction="transaction"
|
||||||
:index="index"
|
:index="index"
|
||||||
/>
|
/>
|
||||||
|
@ -4,27 +4,29 @@ import { useBudgetsStore } from "./budget";
|
|||||||
import { useSessionStore } from "./session";
|
import { useSessionStore } from "./session";
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
Accounts: Map<string, Account>,
|
Accounts: Map<string, Account>
|
||||||
CurrentAccountID: string | null,
|
CurrentAccountID: string | null
|
||||||
Categories: Map<string, Category>,
|
Categories: Map<string, Category>
|
||||||
Months: Map<number, Map<number, Map<string, Category>>>,
|
Months: Map<number, Map<number, Map<string, Category>>>
|
||||||
Transactions: Map<string, Transaction>,
|
Transactions: Map<string, Transaction>
|
||||||
Assignments: []
|
Assignments: []
|
||||||
|
Reconciling: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Transaction {
|
export interface Transaction {
|
||||||
ID: string,
|
ID: string
|
||||||
Date: Date,
|
Date: Date
|
||||||
TransferAccount: string,
|
TransferAccount: string
|
||||||
CategoryGroup: string,
|
CategoryGroup: string
|
||||||
Category: string,
|
Category: string
|
||||||
CategoryID: string | undefined,
|
CategoryID: string | undefined
|
||||||
Memo: string,
|
Memo: string
|
||||||
Status: string,
|
Status: string
|
||||||
GroupID: string,
|
GroupID: string
|
||||||
Payee: string,
|
Payee: string
|
||||||
PayeeID: string | undefined,
|
PayeeID: string | undefined
|
||||||
Amount: number,
|
Amount: number
|
||||||
|
Reconciled: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Account {
|
export interface Account {
|
||||||
@ -54,7 +56,8 @@ export const useAccountStore = defineStore("budget/account", {
|
|||||||
Months: new Map<number, Map<number, Map<string, Category>>>(),
|
Months: new Map<number, Map<number, Map<string, Category>>>(),
|
||||||
Categories: new Map<string, Category>(),
|
Categories: new Map<string, Category>(),
|
||||||
Transactions: new Map<string, Transaction>(),
|
Transactions: new Map<string, Transaction>(),
|
||||||
Assignments: []
|
Assignments: [],
|
||||||
|
Reconciling: false,
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
AccountsList(state) {
|
AccountsList(state) {
|
||||||
@ -71,7 +74,7 @@ export const useAccountStore = defineStore("budget/account", {
|
|||||||
const categoryGroups = [];
|
const categoryGroups = [];
|
||||||
let prev = undefined;
|
let prev = undefined;
|
||||||
for (const category of categories) {
|
for (const category of categories) {
|
||||||
if(category.Group != prev)
|
if (category.Group != prev)
|
||||||
categoryGroups.push({
|
categoryGroups.push({
|
||||||
Name: category.Group,
|
Name: category.Group,
|
||||||
Expand: category.Group != "Hidden Categories",
|
Expand: category.Group != "Hidden Categories",
|
||||||
@ -82,7 +85,7 @@ export const useAccountStore = defineStore("budget/account", {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
CategoriesForMonthAndGroup(state) {
|
CategoriesForMonthAndGroup(state) {
|
||||||
return (year: number, month: number, group : string) => {
|
return (year: number, month: number, group: string) => {
|
||||||
const categories = this.AllCategoriesForMonth(year, month);
|
const categories = this.AllCategoriesForMonth(year, month);
|
||||||
return categories.filter(x => x.Group == group);
|
return categories.filter(x => x.Group == group);
|
||||||
}
|
}
|
||||||
@ -93,6 +96,14 @@ export const useAccountStore = defineStore("budget/account", {
|
|||||||
|
|
||||||
return state.Accounts.get(state.CurrentAccountID);
|
return state.Accounts.get(state.CurrentAccountID);
|
||||||
},
|
},
|
||||||
|
ReconcilingBalance(state): number {
|
||||||
|
let reconciledBalance = this.CurrentAccount!.ReconciledBalance;
|
||||||
|
for (const transaction of this.TransactionsList) {
|
||||||
|
if (transaction.Reconciled)
|
||||||
|
reconciledBalance += transaction.Amount;
|
||||||
|
}
|
||||||
|
return reconciledBalance;
|
||||||
|
},
|
||||||
OnBudgetAccounts(state) {
|
OnBudgetAccounts(state) {
|
||||||
return [...state.Accounts.values()].filter(x => x.OnBudget);
|
return [...state.Accounts.values()].filter(x => x.OnBudget);
|
||||||
},
|
},
|
||||||
@ -105,11 +116,11 @@ export const useAccountStore = defineStore("budget/account", {
|
|||||||
OffBudgetAccountsBalance(state): number {
|
OffBudgetAccountsBalance(state): number {
|
||||||
return this.OffBudgetAccounts.reduce((prev, curr) => prev + Number(curr.ClearedBalance), 0);
|
return this.OffBudgetAccounts.reduce((prev, curr) => prev + Number(curr.ClearedBalance), 0);
|
||||||
},
|
},
|
||||||
TransactionsList(state) : Transaction[] {
|
TransactionsList(state): Transaction[] {
|
||||||
return this.CurrentAccount!.Transactions.map(x => {
|
return this.CurrentAccount!.Transactions.map(x => {
|
||||||
return this.Transactions.get(x)!
|
return this.Transactions.get(x)!
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
async SetCurrentAccount(budgetid: string, accountid: string) {
|
async SetCurrentAccount(budgetid: string, accountid: string) {
|
||||||
@ -124,25 +135,28 @@ export const useAccountStore = defineStore("budget/account", {
|
|||||||
useSessionStore().setTitle(account.Name);
|
useSessionStore().setTitle(account.Name);
|
||||||
await this.FetchAccount(account);
|
await this.FetchAccount(account);
|
||||||
},
|
},
|
||||||
|
AddTransaction(account: Account, transaction: any) {
|
||||||
|
transaction.Date = new Date(transaction.Date);
|
||||||
|
this.Transactions.set(transaction.ID, transaction);
|
||||||
|
},
|
||||||
async FetchAccount(account: Account) {
|
async FetchAccount(account: Account) {
|
||||||
const result = await GET("/account/" + account.ID + "/transactions");
|
const result = await GET("/account/" + account.ID + "/transactions");
|
||||||
const response = await result.json();
|
const response = await result.json();
|
||||||
account.Transactions = [];
|
account.Transactions = [];
|
||||||
for (const transaction of response.Transactions) {
|
for (const transaction of response.Transactions) {
|
||||||
transaction.Date = new Date(transaction.Date);
|
this.AddTransaction(account, transaction);
|
||||||
this.Transactions.set(transaction.ID, transaction);
|
|
||||||
account.Transactions.push(transaction.ID);
|
account.Transactions.push(transaction.ID);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async FetchMonthBudget(budgetid: string, year: number, month: number) {
|
async FetchMonthBudget(budgetid: string, year: number, month: number) {
|
||||||
const result = await GET("/budget/" + budgetid + "/" + year + "/" + month);
|
const result = await GET("/budget/" + budgetid + "/" + year + "/" + month);
|
||||||
const response = await result.json();
|
const response = await result.json();
|
||||||
if(response.Categories == undefined || response.Categories.length <= 0)
|
if (response.Categories == undefined || response.Categories.length <= 0)
|
||||||
return;
|
return;
|
||||||
this.addCategoriesForMonth(year, month, response.Categories);
|
this.addCategoriesForMonth(year, month, response.Categories);
|
||||||
},
|
},
|
||||||
async EditAccount(accountid : string, name : string, onBudget : boolean) {
|
async EditAccount(accountid: string, name: string, onBudget: boolean) {
|
||||||
const result = await POST("/account/" + accountid, JSON.stringify({name: name, onBudget: onBudget}));
|
const result = await POST("/account/" + accountid, JSON.stringify({ name: name, onBudget: onBudget }));
|
||||||
const response = await result.json();
|
const response = await result.json();
|
||||||
useBudgetsStore().MergeBudgetingData(response);
|
useBudgetsStore().MergeBudgetingData(response);
|
||||||
},
|
},
|
||||||
@ -158,18 +172,47 @@ export const useAccountStore = defineStore("budget/account", {
|
|||||||
state.Months.set(year, yearMap);
|
state.Months.set(year, yearMap);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
SetReconciledForAllTransactions(value: boolean) {
|
||||||
|
for (const transaction of this.TransactionsList) {
|
||||||
|
if (transaction.Status == "Reconciled")
|
||||||
|
continue;
|
||||||
|
|
||||||
|
transaction.Reconciled = value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async SubmitReconcilation(reconciliationTransactionAmount: number) {
|
||||||
|
const account = this.CurrentAccount!;
|
||||||
|
const reconciledTransactions = this.TransactionsList.filter(x => x.Reconciled);
|
||||||
|
for (const transaction of reconciledTransactions) {
|
||||||
|
account.ReconciledBalance += transaction.Amount;
|
||||||
|
transaction.Status = "Reconciled";
|
||||||
|
transaction.Reconciled = false;
|
||||||
|
}
|
||||||
|
const result = await POST("/account/" + this.CurrentAccountID + "/reconcile", JSON.stringify({
|
||||||
|
transactionIDs: reconciledTransactions.map(x => x.ID),
|
||||||
|
reconciliationTransactionAmount: reconciliationTransactionAmount.toString(),
|
||||||
|
}));
|
||||||
|
const response = await result.json();
|
||||||
|
const recTrans = response.ReconciliationTransaction;
|
||||||
|
if (recTrans) {
|
||||||
|
this.AddTransaction(account, recTrans);
|
||||||
|
account.Transactions.unshift(recTrans.ID);
|
||||||
|
}
|
||||||
|
console.log("Reconcile: " + response.message);
|
||||||
|
},
|
||||||
logout() {
|
logout() {
|
||||||
this.$reset()
|
this.$reset()
|
||||||
},
|
},
|
||||||
async saveTransaction(payload: string) {
|
async saveTransaction(payload: string) {
|
||||||
const result = await POST("/transaction/new", payload);
|
const result = await POST("/transaction/new", payload);
|
||||||
const response = await result.json();
|
const response = await result.json();
|
||||||
this.CurrentAccount?.Transactions.unshift(response);
|
this.AddTransaction(this.CurrentAccount!, response);
|
||||||
|
this.CurrentAccount?.Transactions.unshift(response.ID);
|
||||||
},
|
},
|
||||||
async editTransaction(transactionid : string, payload: string) {
|
async editTransaction(transactionid: string, payload: string) {
|
||||||
const result = await POST("/transaction/" + transactionid, payload);
|
const result = await POST("/transaction/" + transactionid, payload);
|
||||||
const response = await result.json();
|
const response = await result.json();
|
||||||
this.CurrentAccount?.Transactions.unshift(response);
|
this.AddTransaction(this.CurrentAccount!, response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user