Handle more of categories locally and update Modal #17
@ -1,17 +1,16 @@
|
||||
FROM alpine as godeps
|
||||
RUN apk add go
|
||||
RUN apk --no-cache add go
|
||||
RUN go install github.com/kyleconroy/sqlc/cmd/sqlc@latest
|
||||
RUN go install github.com/go-task/task/v3/cmd/task@latest
|
||||
RUN go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
||||
|
||||
FROM alpine
|
||||
RUN apk add go
|
||||
RUN apk add nodejs yarn bash curl git git-perl tmux
|
||||
RUN apk --no-cache add go nodejs yarn bash curl git git-perl tmux
|
||||
ADD docker/build.sh /
|
||||
RUN yarn global add @vue/cli
|
||||
ENV PATH="/root/.yarn/bin/:${PATH}"
|
||||
WORKDIR /src
|
||||
ADD web/package.json /src/web/
|
||||
ADD web/package.json web/yarn.lock /src/web/
|
||||
RUN yarn
|
||||
COPY --from=godeps /root/go/bin/task /root/go/bin/sqlc /root/go/bin/golangci-lint /usr/local/bin/
|
||||
CMD /build.sh
|
||||
|
@ -30,6 +30,16 @@ type CategoryWithBalance struct {
|
||||
Assigned postgres.Numeric
|
||||
}
|
||||
|
||||
func NewCategoryWithBalance(category *postgres.GetCategoriesRow) CategoryWithBalance {
|
||||
return CategoryWithBalance{
|
||||
GetCategoriesRow: category,
|
||||
Available: postgres.NewZeroNumeric(),
|
||||
AvailableLastMonth: postgres.NewZeroNumeric(),
|
||||
Activity: postgres.NewZeroNumeric(),
|
||||
Assigned: postgres.NewZeroNumeric(),
|
||||
}
|
||||
}
|
||||
|
||||
func getDate(c *gin.Context) (time.Time, error) {
|
||||
var year, month int
|
||||
yearString := c.Param("year")
|
||||
@ -153,30 +163,13 @@ func (h *Handler) calculateBalances(budget postgres.Budget,
|
||||
firstOfNextMonth time.Time, firstOfMonth time.Time, categories []postgres.GetCategoriesRow,
|
||||
cumultativeBalances []postgres.GetCumultativeBalancesRow) ([]CategoryWithBalance, postgres.Numeric) {
|
||||
categoriesWithBalance := []CategoryWithBalance{}
|
||||
hiddenCategory := CategoryWithBalance{
|
||||
GetCategoriesRow: &postgres.GetCategoriesRow{
|
||||
Name: "",
|
||||
Group: "Hidden Categories",
|
||||
},
|
||||
Available: postgres.NewZeroNumeric(),
|
||||
AvailableLastMonth: postgres.NewZeroNumeric(),
|
||||
Activity: postgres.NewZeroNumeric(),
|
||||
Assigned: postgres.NewZeroNumeric(),
|
||||
}
|
||||
|
||||
moneyUsed := postgres.NewZeroNumeric()
|
||||
for i := range categories {
|
||||
cat := &categories[i]
|
||||
// do not show hidden categories
|
||||
categoryWithBalance := h.CalculateCategoryBalances(cat, cumultativeBalances,
|
||||
firstOfNextMonth, &moneyUsed, firstOfMonth, hiddenCategory, budget)
|
||||
if cat.Group == "Hidden Categories" {
|
||||
hiddenCategory.Available = hiddenCategory.Available.Add(categoryWithBalance.Available)
|
||||
hiddenCategory.AvailableLastMonth = hiddenCategory.AvailableLastMonth.Add(categoryWithBalance.AvailableLastMonth)
|
||||
hiddenCategory.Activity = hiddenCategory.Activity.Add(categoryWithBalance.Activity)
|
||||
hiddenCategory.Assigned = hiddenCategory.Assigned.Add(categoryWithBalance.Assigned)
|
||||
continue
|
||||
}
|
||||
firstOfNextMonth, &moneyUsed, firstOfMonth, budget)
|
||||
|
||||
if cat.ID == budget.IncomeCategoryID {
|
||||
continue
|
||||
@ -185,22 +178,13 @@ func (h *Handler) calculateBalances(budget postgres.Budget,
|
||||
categoriesWithBalance = append(categoriesWithBalance, categoryWithBalance)
|
||||
}
|
||||
|
||||
categoriesWithBalance = append(categoriesWithBalance, hiddenCategory)
|
||||
|
||||
return categoriesWithBalance, moneyUsed
|
||||
}
|
||||
|
||||
func (*Handler) CalculateCategoryBalances(cat *postgres.GetCategoriesRow,
|
||||
cumultativeBalances []postgres.GetCumultativeBalancesRow, firstOfNextMonth time.Time,
|
||||
moneyUsed *postgres.Numeric, firstOfMonth time.Time, hiddenCategory CategoryWithBalance,
|
||||
budget postgres.Budget) CategoryWithBalance {
|
||||
categoryWithBalance := CategoryWithBalance{
|
||||
GetCategoriesRow: cat,
|
||||
Available: postgres.NewZeroNumeric(),
|
||||
AvailableLastMonth: postgres.NewZeroNumeric(),
|
||||
Activity: postgres.NewZeroNumeric(),
|
||||
Assigned: postgres.NewZeroNumeric(),
|
||||
}
|
||||
moneyUsed *postgres.Numeric, firstOfMonth time.Time, budget postgres.Budget) CategoryWithBalance {
|
||||
categoryWithBalance := NewCategoryWithBalance(cat)
|
||||
for _, bal := range cumultativeBalances {
|
||||
if bal.CategoryID != cat.ID {
|
||||
continue
|
||||
|
@ -19,17 +19,45 @@ function newBudget() {
|
||||
<p class="w-24 text-center text-6xl">+</p>
|
||||
<button class="text-lg" dark @click="newBudget">New Budget</button>
|
||||
</Card>
|
||||
<div v-if="dialog" justify="center">
|
||||
<div>
|
||||
<div>
|
||||
<span class="text-h5">New Budget</span>
|
||||
</div>
|
||||
<div>
|
||||
<input type="text" v-model="budgetName" label="Budget name" required />
|
||||
</div>
|
||||
<div>
|
||||
<button @click="dialog = false">Close</button>
|
||||
<button @click="saveBudget">Save</button>
|
||||
<div v-if="dialog" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full">
|
||||
<div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
|
||||
<div class="mt-3 text-center">
|
||||
<!--<div class="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-green-100">
|
||||
<svg
|
||||
class="h-6 w-6 text-green-600"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
</div>-->
|
||||
<h3 class="mt-3 text-lg leading-6 font-medium text-gray-900">New Budget</h3>
|
||||
<div class="mt-2 px-7 py-3">
|
||||
<input
|
||||
class="border-2"
|
||||
type="text"
|
||||
v-model="budgetName"
|
||||
placeholder="Budget name"
|
||||
required
|
||||
/>
|
||||
<!--<p class="text-sm text-gray-500">Account has been successfully registered!</p>-->
|
||||
</div>
|
||||
<div class="items-center px-4 py-3">
|
||||
<button
|
||||
@click="dialog = false"
|
||||
class="px-4 py-2 bg-red-500 text-white text-base font-medium rounded-md w-1/2 shadow-sm hover:bg-green-600 focus:outline-none focus:ring-2 focus:ring-green-300"
|
||||
>Close</button>
|
||||
<button
|
||||
class="px-4 py-2 bg-green-500 text-white text-base font-medium rounded-md w-1/2 shadow-sm hover:bg-green-600 focus:outline-none focus:ring-2 focus:ring-green-300"
|
||||
@click="saveBudget"
|
||||
>Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -14,11 +14,19 @@ const props = defineProps<{
|
||||
const budgetsStore = useBudgetsStore();
|
||||
const CurrentBudgetID = computed(() => budgetsStore.CurrentBudgetID);
|
||||
|
||||
const categoriesForMonth = useAccountStore().CategoriesForMonth;
|
||||
const Categories = computed(() => {
|
||||
return [...categoriesForMonth(selected.value.Year, selected.value.Month)];
|
||||
const accountStore = useAccountStore();
|
||||
const categoriesForMonth = accountStore.CategoriesForMonthAndGroup;
|
||||
|
||||
function GetCategories(group : string) {
|
||||
return [...categoriesForMonth(selected.value.Year, selected.value.Month, group)];
|
||||
};
|
||||
|
||||
const groupsForMonth = accountStore.CategoryGroupsForMonth;
|
||||
const GroupsForMonth = computed(() => {
|
||||
return [...groupsForMonth(selected.value.Year, selected.value.Month)];
|
||||
});
|
||||
|
||||
|
||||
const previous = computed(() => ({
|
||||
Year: new Date(selected.value.Year, selected.value.Month - 1, 1).getFullYear(),
|
||||
Month: new Date(selected.value.Year, selected.value.Month - 1, 1).getMonth(),
|
||||
@ -61,7 +69,6 @@ onMounted(() => {
|
||||
</div>
|
||||
<table class="container col-lg-12" id="content">
|
||||
<tr>
|
||||
<th>Group</th>
|
||||
<th>Category</th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
@ -70,23 +77,25 @@ onMounted(() => {
|
||||
<th>Activity</th>
|
||||
<th>Available</th>
|
||||
</tr>
|
||||
<tr v-for="category in Categories">
|
||||
<td>{{ category.Group }}</td>
|
||||
<td>{{ category.Name }}</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td class="text-right">
|
||||
<Currency :value="category.AvailableLastMonth" />
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<Currency :value="category.Assigned" />
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<Currency :value="category.Activity" />
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<Currency :value="category.Available" />
|
||||
</td>
|
||||
</tr>
|
||||
<tbody v-for="group in GroupsForMonth">
|
||||
<p class="text-lg font-bold">{{ group }}</p>
|
||||
<tr v-for="category in GetCategories(group)">
|
||||
<td>{{ category.Name }}</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td class="text-right">
|
||||
<Currency :value="category.AvailableLastMonth" />
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<Currency :value="category.Assigned" />
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<Currency :value="category.Activity" />
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<Currency :value="category.Available" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
@ -4,6 +4,7 @@ import { useRouter } from "vue-router";
|
||||
import { DELETE, POST } from "../api";
|
||||
import { useBudgetsStore } from "../stores/budget";
|
||||
import { useSessionStore } from "../stores/session";
|
||||
import Card from "../components/Card.vue";
|
||||
|
||||
const transactionsFile = ref<File | undefined>(undefined);
|
||||
const assignmentsFile = ref<File | undefined>(undefined);
|
||||
@ -54,72 +55,40 @@ function ynabImport() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-container>
|
||||
<div>
|
||||
<h1>Danger Zone</h1>
|
||||
<v-row>
|
||||
<v-col cols="12" md="6" xl="3">
|
||||
<v-card>
|
||||
<v-card-header>
|
||||
<v-card-header-text>
|
||||
<v-card-title>Clear Budget</v-card-title>
|
||||
<v-card-subtitle>This removes transactions and assignments to start from scratch. Accounts and categories are kept. Not undoable!</v-card-subtitle>
|
||||
</v-card-header-text>
|
||||
</v-card-header>
|
||||
<v-card-actions class="justify-center">
|
||||
<v-btn @click="clearBudget">Clear budget</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6" xl="3">
|
||||
<v-card>
|
||||
<v-card-header>
|
||||
<v-card-header-text>
|
||||
<v-card-title>Delete Budget</v-card-title>
|
||||
<v-card-subtitle>This deletes the whole bugdet including all transactions, assignments, accounts and categories. Not undoable!</v-card-subtitle>
|
||||
</v-card-header-text>
|
||||
</v-card-header>
|
||||
<v-card-actions class="justify-center">
|
||||
<v-btn @click="deleteBudget">Delete budget</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6" xl="3">
|
||||
<v-card>
|
||||
<v-card-header>
|
||||
<v-card-header-text>
|
||||
<v-card-title>Fix all historic negative category-balances</v-card-title>
|
||||
<v-card-subtitle>This restores YNABs functionality, that would substract any overspent categories' balances from next months inflows.</v-card-subtitle>
|
||||
</v-card-header-text>
|
||||
</v-card-header>
|
||||
<v-card-actions class="justify-center">
|
||||
<v-btn @click="cleanNegative">Fix negative</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-col>
|
||||
<v-col cols="12" xl="6">
|
||||
<v-card>
|
||||
<v-card-header>
|
||||
<v-card-header-text>
|
||||
<v-card-title>Import YNAB Budget</v-card-title>
|
||||
</v-card-header-text>
|
||||
</v-card-header>
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<Card>
|
||||
<h2>Clear Budget</h2>
|
||||
<p>This removes transactions and assignments to start from scratch. Accounts and categories are kept. Not undoable!</p>
|
||||
|
||||
<label for="transactions_file">
|
||||
Transaktionen:
|
||||
<input type="file" @change="gotTransactions" accept="text/*" />
|
||||
</label>
|
||||
<br />
|
||||
<label for="assignments_file">
|
||||
Budget:
|
||||
<input type="file" @change="gotAssignments" accept="text/*" />
|
||||
</label>
|
||||
<button @click="clearBudget">Clear budget</button>
|
||||
</Card>
|
||||
<Card>
|
||||
<h2>Delete Budget</h2>
|
||||
<p>This deletes the whole bugdet including all transactions, assignments, accounts and categories. Not undoable!</p>
|
||||
<button @click="deleteBudget">Delete budget</button>
|
||||
</Card>
|
||||
<Card>
|
||||
<h2>Fix all historic negative category-balances</h2>
|
||||
<p>This restores YNABs functionality, that would substract any overspent categories' balances from next months inflows.</p>
|
||||
<button @click="cleanNegative">Fix negative</button>
|
||||
</Card>
|
||||
<Card>
|
||||
<h2>Import YNAB Budget</h2>
|
||||
|
||||
<v-card-actions class="justify-center">
|
||||
<v-btn :disabled="filesIncomplete" @click="ynabImport">Importieren</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-card></v-card>
|
||||
</v-container>
|
||||
<label for="transactions_file">
|
||||
Transaktionen:
|
||||
<input type="file" @change="gotTransactions" accept="text/*" />
|
||||
</label>
|
||||
<br />
|
||||
<label for="assignments_file">
|
||||
Budget:
|
||||
<input type="file" @change="gotAssignments" accept="text/*" />
|
||||
</label>
|
||||
|
||||
<button :disabled="filesIncomplete" @click="ynabImport">Importieren</button>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
@ -54,12 +54,30 @@ export const useAccountStore = defineStore("budget/account", {
|
||||
AccountsList(state) {
|
||||
return [...state.Accounts.values()];
|
||||
},
|
||||
CategoriesForMonth: (state) => (year: number, month: number) => {
|
||||
AllCategoriesForMonth: (state) => (year: number, month: number) => {
|
||||
const yearMap = state.Months.get(year);
|
||||
const monthMap = yearMap?.get(month);
|
||||
console.log("MTH", monthMap)
|
||||
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(category.Group);
|
||||
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;
|
||||
@ -102,6 +120,8 @@ export const useAccountStore = defineStore("budget/account", {
|
||||
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);
|
||||
},
|
||||
addCategoriesForMonth(year: number, month: number, categories: Category[]): void {
|
||||
|
Loading…
x
Reference in New Issue
Block a user