2024-01-15 22:49:29 +00:00
|
|
|
package api
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"net/http"
|
|
|
|
"strconv"
|
2024-02-02 21:18:15 +00:00
|
|
|
"time"
|
2024-01-15 22:49:29 +00:00
|
|
|
|
|
|
|
"git.luzifer.io/luzifer/accounting/pkg/database"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/gorilla/mux"
|
|
|
|
"gorm.io/gorm"
|
|
|
|
)
|
|
|
|
|
|
|
|
func (a apiServer) handleCreateAccount(w http.ResponseWriter, r *http.Request) {
|
|
|
|
var payload struct {
|
2024-02-02 21:18:15 +00:00
|
|
|
Name string `json:"name"`
|
|
|
|
StartingBalance float64 `json:"startingBalance"`
|
|
|
|
Type database.AccountType `json:"type"`
|
2024-01-15 22:49:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
|
|
|
a.errorResponse(w, err, "parsing body", http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if payload.Name == "" {
|
|
|
|
a.errorResponse(w, errors.New("empty name"), "validating request", http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if !payload.Type.IsValid() {
|
|
|
|
a.errorResponse(w, errors.New("invalid account type"), "validating request", http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
acc, err := a.dbc.CreateAccount(payload.Name, payload.Type)
|
|
|
|
if err != nil {
|
|
|
|
a.errorResponse(w, err, "creating account", http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-02-02 21:18:15 +00:00
|
|
|
if payload.StartingBalance != 0 {
|
|
|
|
switch payload.Type {
|
|
|
|
case database.AccountTypeBudget:
|
|
|
|
_, err = a.dbc.CreateTransaction(database.Transaction{
|
|
|
|
Time: time.Now(),
|
|
|
|
Description: "Starting Balance",
|
|
|
|
Amount: payload.StartingBalance,
|
|
|
|
Account: uuid.NullUUID{UUID: acc.ID, Valid: true},
|
|
|
|
Category: uuid.NullUUID{UUID: database.UnallocatedMoney, Valid: true},
|
|
|
|
Cleared: true,
|
|
|
|
})
|
|
|
|
|
|
|
|
case database.AccountTypeCategory:
|
2024-02-04 11:40:16 +00:00
|
|
|
err = a.dbc.TransferMoney(database.UnallocatedMoney, acc.ID, payload.StartingBalance, "")
|
2024-02-02 21:18:15 +00:00
|
|
|
|
|
|
|
case database.AccountTypeTracking:
|
|
|
|
_, err = a.dbc.CreateTransaction(database.Transaction{
|
|
|
|
Time: time.Now(),
|
|
|
|
Description: "Starting Balance",
|
|
|
|
Amount: payload.StartingBalance,
|
|
|
|
Account: uuid.NullUUID{UUID: acc.ID, Valid: true},
|
|
|
|
Cleared: true,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
a.errorResponse(w, err, "creating starting balance transaction", http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-15 22:49:29 +00:00
|
|
|
u, err := a.router.Get("GetAccount").URL("id", acc.ID.String())
|
|
|
|
if err != nil {
|
|
|
|
a.errorResponse(w, err, "getting redirect url", http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-01-16 15:13:04 +00:00
|
|
|
http.Redirect(w, r, u.String(), http.StatusFound)
|
2024-01-15 22:49:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (a apiServer) handleGetAccount(w http.ResponseWriter, r *http.Request) {
|
|
|
|
accid, err := uuid.Parse(mux.Vars(r)["id"])
|
|
|
|
if err != nil {
|
|
|
|
a.errorResponse(w, err, "parsing id", http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
acc, err := a.dbc.GetAccount(accid)
|
|
|
|
if err != nil {
|
|
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
|
|
a.errorResponse(w, err, "getting account", http.StatusNotFound)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
a.errorResponse(w, err, "getting account", http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
a.jsonResponse(w, http.StatusOK, acc)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a apiServer) handleListAccounts(w http.ResponseWriter, r *http.Request) {
|
2024-01-16 15:13:04 +00:00
|
|
|
var (
|
|
|
|
payload any
|
|
|
|
showHidden = r.URL.Query().Has("with-hidden")
|
|
|
|
)
|
2024-01-15 22:49:29 +00:00
|
|
|
if r.URL.Query().Has("with-balances") {
|
2024-01-16 15:13:04 +00:00
|
|
|
accs, err := a.dbc.ListAccountBalances(showHidden)
|
2024-01-15 22:49:29 +00:00
|
|
|
if err != nil {
|
|
|
|
a.errorResponse(w, err, "getting account balances", http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
payload = accs
|
|
|
|
} else {
|
|
|
|
at := database.AccountType(r.URL.Query().Get("account-type"))
|
|
|
|
if at.IsValid() {
|
2024-01-16 15:13:04 +00:00
|
|
|
accs, err := a.dbc.ListAccountsByType(at, showHidden)
|
2024-01-15 22:49:29 +00:00
|
|
|
if err != nil {
|
|
|
|
a.errorResponse(w, err, "getting accounts", http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
payload = accs
|
|
|
|
} else {
|
2024-01-16 15:13:04 +00:00
|
|
|
accs, err := a.dbc.ListAccounts(showHidden)
|
2024-01-15 22:49:29 +00:00
|
|
|
if err != nil {
|
|
|
|
a.errorResponse(w, err, "getting accounts", http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
payload = accs
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
a.jsonResponse(w, http.StatusOK, payload)
|
|
|
|
}
|
|
|
|
|
2024-02-01 11:44:56 +00:00
|
|
|
func (a apiServer) handleAccountReconcile(w http.ResponseWriter, r *http.Request) {
|
|
|
|
var (
|
|
|
|
acctID uuid.UUID
|
|
|
|
err error
|
|
|
|
)
|
|
|
|
|
|
|
|
if acctID, err = uuid.Parse(mux.Vars(r)["id"]); err != nil {
|
|
|
|
a.errorResponse(w, err, "parsing id", http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = a.dbc.MarkAccountReconciled(acctID); err != nil {
|
|
|
|
a.errorResponse(w, err, "marking reconciled", http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
|
|
}
|
|
|
|
|
2024-01-15 22:49:29 +00:00
|
|
|
func (a apiServer) handleTransferMoney(w http.ResponseWriter, r *http.Request) {
|
|
|
|
var (
|
|
|
|
amount float64
|
|
|
|
err error
|
|
|
|
from, to, category uuid.UUID
|
|
|
|
)
|
|
|
|
|
|
|
|
if from, err = uuid.Parse(mux.Vars(r)["id"]); err != nil {
|
|
|
|
a.errorResponse(w, err, "parsing id", http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if to, err = uuid.Parse(mux.Vars(r)["to"]); err != nil {
|
|
|
|
a.errorResponse(w, err, "parsing to", http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-01-16 15:13:04 +00:00
|
|
|
if amount, err = strconv.ParseFloat(r.URL.Query().Get("amount"), 64); err != nil {
|
2024-01-15 22:49:29 +00:00
|
|
|
a.errorResponse(w, err, "parsing amount", http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if r.URL.Query().Has("category") {
|
|
|
|
if category, err = uuid.Parse(mux.Vars(r)["category"]); err != nil {
|
|
|
|
a.errorResponse(w, err, "parsing category", http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if category == uuid.Nil {
|
2024-02-04 11:40:16 +00:00
|
|
|
if err = a.dbc.TransferMoney(from, to, amount, r.URL.Query().Get("description")); err != nil {
|
2024-01-15 22:49:29 +00:00
|
|
|
a.errorResponse(w, err, "transferring money", http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
2024-01-16 16:30:17 +00:00
|
|
|
} else {
|
2024-02-04 11:40:16 +00:00
|
|
|
if err = a.dbc.TransferMoneyWithCategory(from, to, amount, r.URL.Query().Get("description"), category); err != nil {
|
2024-01-16 16:30:17 +00:00
|
|
|
a.errorResponse(w, err, "transferring money", http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
2024-01-15 22:49:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a apiServer) handleUpdateAccount(w http.ResponseWriter, r *http.Request) {
|
|
|
|
var (
|
|
|
|
acctID uuid.UUID
|
|
|
|
err error
|
|
|
|
)
|
|
|
|
|
|
|
|
if acctID, err = uuid.Parse(mux.Vars(r)["id"]); err != nil {
|
|
|
|
a.errorResponse(w, err, "parsing id", http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if r.URL.Query().Has("name") {
|
|
|
|
if err = a.dbc.UpdateAccountName(acctID, r.URL.Query().Get("name")); err != nil {
|
|
|
|
a.errorResponse(w, err, "renaming account", http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if r.URL.Query().Has("hidden") {
|
|
|
|
if err = a.dbc.UpdateAccountHidden(acctID, r.URL.Query().Get("hidden") == "true"); err != nil {
|
|
|
|
a.errorResponse(w, err, "updating account visibility", http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
|
|
}
|