220 lines
8.3 KiB
TypeScript
220 lines
8.3 KiB
TypeScript
import { defineStore } from "pinia"
|
|
import { GET, POST } from "../api";
|
|
import { useBudgetsStore } from "./budget";
|
|
import { useSessionStore } from "./session";
|
|
|
|
interface State {
|
|
Accounts: Map<string, Account>
|
|
CurrentAccountID: string | null
|
|
Categories: Map<string, Category>
|
|
Months: Map<number, Map<number, Map<string, Category>>>
|
|
Transactions: Map<string, Transaction>
|
|
Assignments: []
|
|
Reconciling: boolean
|
|
}
|
|
|
|
export interface Transaction {
|
|
ID: string
|
|
Date: Date
|
|
TransferAccount: string
|
|
CategoryGroup: string
|
|
Category: string
|
|
CategoryID: string | undefined
|
|
Memo: string
|
|
Status: string
|
|
GroupID: string
|
|
Payee: string
|
|
PayeeID: string | undefined
|
|
Amount: number
|
|
Reconciled: boolean
|
|
}
|
|
|
|
export interface Account {
|
|
ID: string
|
|
Name: string
|
|
OnBudget: boolean
|
|
ClearedBalance: number
|
|
WorkingBalance: number
|
|
ReconciledBalance: number
|
|
Transactions: string[]
|
|
}
|
|
|
|
export interface Category {
|
|
ID: string
|
|
Group: string
|
|
Name: string
|
|
AvailableLastMonth: number
|
|
Assigned: number
|
|
Activity: number
|
|
Available: number
|
|
}
|
|
|
|
export const useAccountStore = defineStore("budget/account", {
|
|
state: (): State => ({
|
|
Accounts: new Map<string, Account>(),
|
|
CurrentAccountID: null,
|
|
Months: new Map<number, Map<number, Map<string, Category>>>(),
|
|
Categories: new Map<string, Category>(),
|
|
Transactions: new Map<string, Transaction>(),
|
|
Assignments: [],
|
|
Reconciling: false,
|
|
}),
|
|
getters: {
|
|
AccountsList(state) {
|
|
return [...state.Accounts.values()];
|
|
},
|
|
AllCategoriesForMonth: (state) => (year: number, month: number) => {
|
|
const yearMap = state.Months.get(year);
|
|
const monthMap = yearMap?.get(month);
|
|
return [...monthMap?.values() || []];
|
|
},
|
|
CategoryGroupsForMonth(state) {
|
|
return (year: number, month: number) => {
|
|
const categories = this.AllCategoriesForMonth(year, month);
|
|
const categoryGroups = [];
|
|
let prev = undefined;
|
|
for (const category of categories) {
|
|
if (category.Group != prev)
|
|
categoryGroups.push({
|
|
Name: category.Group,
|
|
Expand: category.Group != "Hidden Categories",
|
|
});
|
|
prev = category.Group;
|
|
}
|
|
return categoryGroups;
|
|
}
|
|
},
|
|
CategoriesForMonthAndGroup(state) {
|
|
return (year: number, month: number, group: string) => {
|
|
const categories = this.AllCategoriesForMonth(year, month);
|
|
return categories.filter(x => x.Group == group);
|
|
}
|
|
},
|
|
CurrentAccount(state): Account | undefined {
|
|
if (state.CurrentAccountID == null)
|
|
return undefined;
|
|
|
|
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) {
|
|
return [...state.Accounts.values()].filter(x => x.OnBudget);
|
|
},
|
|
OnBudgetAccountsBalance(state): number {
|
|
return this.OnBudgetAccounts.reduce((prev, curr) => prev + Number(curr.ClearedBalance), 0);
|
|
},
|
|
OffBudgetAccounts(state) {
|
|
return [...state.Accounts.values()].filter(x => !x.OnBudget);
|
|
},
|
|
OffBudgetAccountsBalance(state): number {
|
|
return this.OffBudgetAccounts.reduce((prev, curr) => prev + Number(curr.ClearedBalance), 0);
|
|
},
|
|
TransactionsList(state): Transaction[] {
|
|
return this.CurrentAccount!.Transactions.map(x => {
|
|
return this.Transactions.get(x)!
|
|
});
|
|
},
|
|
},
|
|
actions: {
|
|
async SetCurrentAccount(budgetid: string, accountid: string) {
|
|
if (budgetid == null)
|
|
return
|
|
|
|
this.CurrentAccountID = accountid;
|
|
const account = this.CurrentAccount;
|
|
if (account == undefined)
|
|
return
|
|
|
|
useSessionStore().setTitle(account.Name);
|
|
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) {
|
|
const result = await GET("/account/" + account.ID + "/transactions");
|
|
const response = await result.json();
|
|
account.Transactions = [];
|
|
for (const transaction of response.Transactions) {
|
|
this.AddTransaction(account, transaction);
|
|
account.Transactions.push(transaction.ID);
|
|
}
|
|
},
|
|
async FetchMonthBudget(budgetid: string, year: number, month: number) {
|
|
const result = await GET("/budget/" + budgetid + "/" + year + "/" + month);
|
|
const response = await result.json();
|
|
if (response.Categories == undefined || response.Categories.length <= 0)
|
|
return;
|
|
this.addCategoriesForMonth(year, month, response.Categories);
|
|
},
|
|
async EditAccount(accountid: string, name: string, onBudget: boolean) {
|
|
const result = await POST("/account/" + accountid, JSON.stringify({ name: name, onBudget: onBudget }));
|
|
const response = await result.json();
|
|
useBudgetsStore().MergeBudgetingData(response);
|
|
},
|
|
addCategoriesForMonth(year: number, month: number, categories: Category[]): void {
|
|
this.$patch((state) => {
|
|
const yearMap = state.Months.get(year) || new Map<number, Map<string, Category>>();
|
|
const monthMap = yearMap.get(month) || new Map<string, Category>();
|
|
for (const category of categories) {
|
|
monthMap.set(category.ID, category);
|
|
}
|
|
|
|
yearMap.set(month, monthMap);
|
|
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() {
|
|
this.$reset()
|
|
},
|
|
async saveTransaction(payload: string) {
|
|
const result = await POST("/transaction/new", payload);
|
|
const response = await result.json();
|
|
this.AddTransaction(this.CurrentAccount!, response);
|
|
this.CurrentAccount?.Transactions.unshift(response.ID);
|
|
},
|
|
async editTransaction(transactionid: string, payload: string) {
|
|
const result = await POST("/transaction/" + transactionid, payload);
|
|
const response = await result.json();
|
|
this.AddTransaction(this.CurrentAccount!, response);
|
|
}
|
|
}
|
|
|
|
})
|