Implement budgeting views by calculating most values locally

This commit is contained in:
Jan Bader 2021-12-11 12:47:41 +00:00
parent 6da1b26a2f
commit caf0126b86
6 changed files with 55 additions and 250 deletions

View File

@ -3,9 +3,7 @@ package http
import (
"fmt"
"net/http"
"time"
"git.javil.eu/jacob1123/budgeteer/postgres"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/pressly/goose/v3"
@ -72,14 +70,14 @@ func (h *Handler) clearBudget(c *gin.Context) {
}
func (h *Handler) cleanNegativeBudget(c *gin.Context) {
budgetID := c.Param("budgetid")
/*budgetID := c.Param("budgetid")
budgetUUID, err := uuid.Parse(budgetID)
if err != nil {
c.Redirect(http.StatusTemporaryRedirect, "/login")
return
}
}*/
min_date, err := h.Service.DB.GetFirstActivity(c.Request.Context(), budgetUUID)
/*min_date, err := h.Service.DB.GetFirstActivity(c.Request.Context(), budgetUUID)
date := getFirstOfMonthTime(min_date)
for {
nextDate := date.AddDate(0, 1, 0)
@ -114,6 +112,6 @@ func (h *Handler) cleanNegativeBudget(c *gin.Context) {
} else {
break
}
}
}*/
}

View File

@ -13,7 +13,7 @@ import (
type BudgetingData struct {
AlwaysNeededData
Categories []postgres.GetCategoriesWithBalanceRow
Categories []CategoryWithBalance
AvailableBalance postgres.Numeric
Date time.Time
Next time.Time
@ -31,6 +31,14 @@ func getFirstOfMonthTime(date time.Time) time.Time {
return getFirstOfMonth(year, month, date.Location())
}
type CategoryWithBalance struct {
*postgres.GetCategoriesRow
Available float64
AvailableLastMonth float64
Activity float64
Assigned float64
}
func (h *Handler) budgeting(c *gin.Context) {
budgetID := c.Param("budgetid")
budgetUUID, err := uuid.Parse(budgetID)
@ -64,17 +72,7 @@ func (h *Handler) budgeting(c *gin.Context) {
firstOfNextMonth := firstOfMonth.AddDate(0, 1, 0)
firstOfPreviousMonth := firstOfMonth.AddDate(0, -1, 0)
params := postgres.GetCategoriesWithBalanceParams{
BudgetID: budgetUUID,
FromDate: firstOfMonth,
ToDate: firstOfNextMonth,
PrevFromDate: firstOfMonth.AddDate(0, -1, 0),
}
categories, err := h.Service.DB.GetCategoriesWithBalance(c.Request.Context(), params)
if err != nil {
c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("load categories: %w", err))
return
}
categories, err := h.Service.DB.GetCategories(c.Request.Context(), budgetUUID)
cumultativeBalances, err := h.Service.DB.GetCumultativeBalances(c.Request.Context(), budgetUUID)
if err != nil {
@ -82,12 +80,15 @@ func (h *Handler) budgeting(c *gin.Context) {
return
}
var available float64 = 0
categoriesWithBalance := []CategoryWithBalance{}
var added float64 = 0
var assigned float64 = 0
for i := range categories {
cat := &categories[i]
var balance float64 = 0
categoryWithBalance := CategoryWithBalance{
GetCategoriesRow: cat,
}
for _, bal := range cumultativeBalances {
if bal.CategoryID != cat.ID {
continue
@ -99,25 +100,23 @@ func (h *Handler) budgeting(c *gin.Context) {
}
assigned += bal.Assignments.GetFloat64()
balance += bal.Assignments.GetFloat64()
balance += bal.Transactions.GetFloat64()
if balance < 0 && bal.Date.Before(firstOfMonth) {
added -= balance
balance = 0
categoryWithBalance.Available += bal.Assignments.GetFloat64()
categoryWithBalance.Available += bal.Transactions.GetFloat64()
if categoryWithBalance.Available < 0 && bal.Date.Before(firstOfMonth) {
added -= categoryWithBalance.Available
categoryWithBalance.Available = 0
}
if bal.Date.Before(firstOfMonth) {
available = balance
categoryWithBalance.AvailableLastMonth = categoryWithBalance.Available
} else if bal.Date.Before(firstOfNextMonth) {
categoryWithBalance.Activity = bal.Transactions.GetFloat64()
categoryWithBalance.Assigned = bal.Assignments.GetFloat64()
}
}
num := postgres.Numeric{}
num.Set(balance)
cat.Available = num
num2 := postgres.Numeric{}
num2.Set(available)
cat.AvailableLastMonth = num2
categoriesWithBalance = append(categoriesWithBalance, categoryWithBalance)
}
data := c.MustGet("data").(AlwaysNeededData)
@ -145,12 +144,12 @@ func (h *Handler) budgeting(c *gin.Context) {
availableBalanceNum.Set(availableBalance)
d := BudgetingData{
c.MustGet("data").(AlwaysNeededData),
categories,
availableBalanceNum,
firstOfMonth,
firstOfNextMonth,
firstOfPreviousMonth,
AlwaysNeededData: c.MustGet("data").(AlwaysNeededData),
Categories: categoriesWithBalance,
AvailableBalance: availableBalanceNum,
Date: firstOfMonth,
Next: firstOfNextMonth,
Previous: firstOfPreviousMonth,
}
c.HTML(http.StatusOK, "budgeting.html", d)

View File

@ -5,7 +5,6 @@ package postgres
import (
"context"
"time"
"github.com/google/uuid"
)
@ -48,43 +47,11 @@ func (q *Queries) CreateCategoryGroup(ctx context.Context, arg CreateCategoryGro
return i, err
}
const getAvailableBalance = `-- name: GetAvailableBalance :one
SELECT
((
SELECT SUM(transactions.amount)
FROM transactions
LEFT JOIN categories ON categories.id = transactions.category_id
LEFT JOIN budgets ON budgets.income_category_id = categories.id
INNER JOIN accounts ON accounts.id = transactions.account_id
WHERE budgets.id = $1
AND transactions.date < $2
AND accounts.on_budget
) - (
SELECT SUM(assignments.amount)
FROM assignments
INNER JOIN categories ON categories.id = assignments.category_id
INNER JOIN category_groups ON category_groups.id = categories.category_group_id
WHERE category_groups.budget_id = $1
AND assignments.date < $2
))::decimal(12,2)
`
type GetAvailableBalanceParams struct {
BudgetID uuid.UUID
FromDate time.Time
}
func (q *Queries) GetAvailableBalance(ctx context.Context, arg GetAvailableBalanceParams) (Numeric, error) {
row := q.db.QueryRowContext(ctx, getAvailableBalance, arg.BudgetID, arg.FromDate)
var column_1 Numeric
err := row.Scan(&column_1)
return column_1, err
}
const getCategories = `-- name: GetCategories :many
SELECT categories.id, categories.category_group_id, categories.name, category_groups.name as group FROM categories
INNER JOIN category_groups ON categories.category_group_id = category_groups.id
WHERE category_groups.budget_id = $1
ORDER BY category_groups.name, categories.name
`
type GetCategoriesRow struct {
@ -122,109 +89,6 @@ func (q *Queries) GetCategories(ctx context.Context, budgetID uuid.UUID) ([]GetC
return items, nil
}
const getCategoriesWithBalance = `-- name: GetCategoriesWithBalance :many
SELECT categories.id, categories.name, category_groups.name as group,
(
COALESCE((
SELECT SUM(a_hist.amount) FROM assignments a_hist
WHERE categories.id = a_hist.category_id AND a_hist.date < $1
), 0)+COALESCE((
SELECT SUM(t_hist.amount) FROM transactions t_hist
WHERE categories.id = t_hist.category_id AND t_hist.date < $1
), 0)
)::decimal(12,2) as available,
(
COALESCE((
SELECT SUM(a_hist.amount) FROM assignments a_hist
WHERE categories.id = a_hist.category_id AND a_hist.date < $2
), 0)+COALESCE((
SELECT SUM(t_hist.amount) FROM transactions t_hist
WHERE categories.id = t_hist.category_id AND t_hist.date < $2
), 0)-CASE WHEN (COALESCE((
SELECT SUM(a_hist.amount) FROM assignments a_hist
WHERE categories.id = a_hist.category_id AND a_hist.date < $3
), 0)+COALESCE((
SELECT SUM(t_hist.amount) FROM transactions t_hist
WHERE categories.id = t_hist.category_id AND t_hist.date < $3
), 0)) < 0 THEN (COALESCE((
SELECT SUM(a_hist.amount) FROM assignments a_hist
WHERE categories.id = a_hist.category_id AND a_hist.date < $3
), 0)+COALESCE((
SELECT SUM(t_hist.amount) FROM transactions t_hist
WHERE categories.id = t_hist.category_id AND t_hist.date < $3
), 0)) ELSE 0 END
)::decimal(12,2) as available_last_month,
COALESCE((
SELECT SUM(t_this.amount) FROM transactions t_this
WHERE categories.id = t_this.category_id AND t_this.date >= $2 AND t_this.date < $1
), 0)::decimal(12,2) as activity,
COALESCE((
SELECT SUM(a_hist.amount) FROM assignments a_hist
WHERE categories.id = a_hist.category_id AND a_hist.date >= $2 AND a_hist.date < $1
), 0)::decimal(12,2) as assigned
FROM categories
INNER JOIN category_groups ON categories.category_group_id = category_groups.id
WHERE category_groups.budget_id = $4
GROUP BY categories.id, categories.name, category_groups.name
ORDER BY category_groups.name, categories.name
`
type GetCategoriesWithBalanceParams struct {
ToDate time.Time
FromDate time.Time
PrevFromDate time.Time
BudgetID uuid.UUID
}
type GetCategoriesWithBalanceRow struct {
ID uuid.UUID
Name string
Group string
Available Numeric
AvailableLastMonth Numeric
Activity Numeric
Assigned Numeric
}
func (q *Queries) GetCategoriesWithBalance(ctx context.Context, arg GetCategoriesWithBalanceParams) ([]GetCategoriesWithBalanceRow, error) {
rows, err := q.db.QueryContext(ctx, getCategoriesWithBalance,
arg.ToDate,
arg.FromDate,
arg.PrevFromDate,
arg.BudgetID,
)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetCategoriesWithBalanceRow
for rows.Next() {
var i GetCategoriesWithBalanceRow
if err := rows.Scan(
&i.ID,
&i.Name,
&i.Group,
&i.Available,
&i.AvailableLastMonth,
&i.Activity,
&i.Assigned,
); 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
}
const getCategoryGroups = `-- name: GetCategoryGroups :many
SELECT category_groups.id, category_groups.budget_id, category_groups.name FROM category_groups
WHERE category_groups.budget_id = $1

View File

@ -17,73 +17,5 @@ RETURNING *;
-- name: GetCategories :many
SELECT categories.*, category_groups.name as group FROM categories
INNER JOIN category_groups ON categories.category_group_id = category_groups.id
WHERE category_groups.budget_id = $1;
-- name: GetCategoriesWithBalance :many
SELECT categories.id, categories.name, category_groups.name as group,
(
COALESCE((
SELECT SUM(a_hist.amount) FROM assignments a_hist
WHERE categories.id = a_hist.category_id AND a_hist.date < @to_date
), 0)+COALESCE((
SELECT SUM(t_hist.amount) FROM transactions t_hist
WHERE categories.id = t_hist.category_id AND t_hist.date < @to_date
), 0)
)::decimal(12,2) as available,
(
COALESCE((
SELECT SUM(a_hist.amount) FROM assignments a_hist
WHERE categories.id = a_hist.category_id AND a_hist.date < @from_date
), 0)+COALESCE((
SELECT SUM(t_hist.amount) FROM transactions t_hist
WHERE categories.id = t_hist.category_id AND t_hist.date < @from_date
), 0)-CASE WHEN (COALESCE((
SELECT SUM(a_hist.amount) FROM assignments a_hist
WHERE categories.id = a_hist.category_id AND a_hist.date < @prev_from_date
), 0)+COALESCE((
SELECT SUM(t_hist.amount) FROM transactions t_hist
WHERE categories.id = t_hist.category_id AND t_hist.date < @prev_from_date
), 0)) < 0 THEN (COALESCE((
SELECT SUM(a_hist.amount) FROM assignments a_hist
WHERE categories.id = a_hist.category_id AND a_hist.date < @prev_from_date
), 0)+COALESCE((
SELECT SUM(t_hist.amount) FROM transactions t_hist
WHERE categories.id = t_hist.category_id AND t_hist.date < @prev_from_date
), 0)) ELSE 0 END
)::decimal(12,2) as available_last_month,
COALESCE((
SELECT SUM(t_this.amount) FROM transactions t_this
WHERE categories.id = t_this.category_id AND t_this.date >= @from_date AND t_this.date < @to_date
), 0)::decimal(12,2) as activity,
COALESCE((
SELECT SUM(a_hist.amount) FROM assignments a_hist
WHERE categories.id = a_hist.category_id AND a_hist.date >= @from_date AND a_hist.date < @to_date
), 0)::decimal(12,2) as assigned
FROM categories
INNER JOIN category_groups ON categories.category_group_id = category_groups.id
WHERE category_groups.budget_id = @budget_id
GROUP BY categories.id, categories.name, category_groups.name
ORDER BY category_groups.name, categories.name;
-- name: GetAvailableBalance :one
SELECT
((
SELECT SUM(transactions.amount)
FROM transactions
LEFT JOIN categories ON categories.id = transactions.category_id
LEFT JOIN budgets ON budgets.income_category_id = categories.id
INNER JOIN accounts ON accounts.id = transactions.account_id
WHERE budgets.id = @budget_id
AND transactions.date < @from_date
AND accounts.on_budget
) - (
SELECT SUM(assignments.amount)
FROM assignments
INNER JOIN categories ON categories.id = assignments.category_id
INNER JOIN category_groups ON category_groups.id = categories.category_group_id
WHERE category_groups.budget_id = @budget_id
AND assignments.date < @from_date
))::decimal(12,2);
WHERE category_groups.budget_id = $1
ORDER BY category_groups.name, categories.name;

View File

@ -8,4 +8,16 @@
<td class="right {{if .IsZero}}zero{{else if not .IsPositive}}negative{{end}}">
{{printf "%.2f" .GetFloat64}}
</td>
{{end}}
{{define "amountf64"}}
<span class="right {{if eq . 0.0}}zero{{else if lt . 0.0}}negative{{end}}">
{{printf "%.2f" .}}
</span>
{{end}}
{{define "amountf64-cell"}}
<td class="right {{if eq . 0.0}}zero{{else if lt . 0.0}}negative{{end}}">
{{printf "%.2f" .}}
</td>
{{end}}

View File

@ -41,10 +41,10 @@
</td>
<td>
</td>
{{template "amount-cell" .AvailableLastMonth}}
{{template "amount-cell" .Assigned}}
{{template "amount-cell" .Activity}}
{{template "amount-cell" .Available}}
{{template "amountf64-cell" .AvailableLastMonth}}
{{template "amountf64-cell" .Assigned}}
{{template "amountf64-cell" .Activity}}
{{template "amountf64-cell" .Available}}
</tr>
{{end}}
</table>