Merge pull request 'Show sums for category groups' (#40) from category-group-summary into master
All checks were successful
continuous-integration/drone/push Build is passing

Reviewed-on: #40
This commit is contained in:
Jan Bader 2022-03-14 20:15:20 +01:00
commit 984a2aa296
6 changed files with 68 additions and 19 deletions

View File

@ -148,3 +148,22 @@ func (q *Queries) UpdateAssignment(ctx context.Context, arg UpdateAssignmentPara
_, err := q.db.ExecContext(ctx, updateAssignment, arg.CategoryID, arg.Date, arg.Amount) _, err := q.db.ExecContext(ctx, updateAssignment, arg.CategoryID, arg.Date, arg.Amount)
return err return err
} }
const updateAssignmentWithDifference = `-- name: UpdateAssignmentWithDifference :exec
INSERT INTO assignments (category_id, date, amount)
VALUES($1, $2, $3)
ON CONFLICT (category_id, date)
DO
UPDATE SET amount = assignments.amount + $3
`
type UpdateAssignmentWithDifferenceParams struct {
CategoryID uuid.UUID
Date time.Time
Amount numeric.Numeric
}
func (q *Queries) UpdateAssignmentWithDifference(ctx context.Context, arg UpdateAssignmentWithDifferenceParams) error {
_, err := q.db.ExecContext(ctx, updateAssignmentWithDifference, arg.CategoryID, arg.Date, arg.Amount)
return err
}

View File

@ -30,3 +30,10 @@ VALUES($1, $2, $3)
ON CONFLICT (category_id, date) ON CONFLICT (category_id, date)
DO DO
UPDATE SET amount = $3; UPDATE SET amount = $3;
-- name: UpdateAssignmentWithDifference :exec
INSERT INTO assignments (category_id, date, amount)
VALUES($1, $2, $3)
ON CONFLICT (category_id, date)
DO
UPDATE SET amount = assignments.amount + $3;

View File

@ -91,12 +91,12 @@ func (ynab *YNABImport) ImportAssignments(context context.Context, r io.Reader)
continue continue
} }
assignment := CreateAssignmentParams{ assignment := UpdateAssignmentWithDifferenceParams{
Date: date, Date: date,
CategoryID: category.UUID, CategoryID: category.UUID,
Amount: amount, Amount: amount,
} }
_, err = ynab.queries.CreateAssignment(context, assignment) err = ynab.queries.UpdateAssignmentWithDifference(context, assignment)
if err != nil { if err != nil {
return fmt.Errorf("save assignment %v: %w", assignment, err) return fmt.Errorf("save assignment %v: %w", assignment, err)
} }
@ -226,7 +226,8 @@ func (ynab *YNABImport) GetTransaction(context context.Context, record []string)
} }
func (ynab *YNABImport) ImportRegularTransaction(context context.Context, payeeName string, func (ynab *YNABImport) ImportRegularTransaction(context context.Context, payeeName string,
transaction CreateTransactionParams) error { transaction CreateTransactionParams,
) error {
payeeID, err := ynab.GetPayee(context, payeeName) payeeID, err := ynab.GetPayee(context, payeeName)
if err != nil { if err != nil {
return fmt.Errorf("get payee %s: %w", payeeName, err) return fmt.Errorf("get payee %s: %w", payeeName, err)
@ -242,7 +243,8 @@ func (ynab *YNABImport) ImportRegularTransaction(context context.Context, payeeN
func (ynab *YNABImport) ImportTransferTransaction(context context.Context, payeeName string, func (ynab *YNABImport) ImportTransferTransaction(context context.Context, payeeName string,
transaction CreateTransactionParams, openTransfers *[]Transfer, transaction CreateTransactionParams, openTransfers *[]Transfer,
account *Account, amount numeric.Numeric) error { account *Account, amount numeric.Numeric,
) error {
transferToAccountName := payeeName[11:] transferToAccountName := payeeName[11:]
transferToAccount, err := ynab.GetAccount(context, transferToAccountName) transferToAccount, err := ynab.GetAccount(context, transferToAccountName)
if err != nil { if err != nil {

View File

@ -1,7 +1,11 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from 'vue'; import { computed } from 'vue';
const props = defineProps<{ value: number | undefined }>(); const props = defineProps<{
value: number | undefined
negativeClass?: string
positiveClass?: string
}>();
const internalValue = computed(() => Number(props.value ?? 0)); const internalValue = computed(() => Number(props.value ?? 0));
@ -11,5 +15,5 @@ const formattedValue = computed(() => internalValue.value.toLocaleString(undefin
</script> </script>
<template> <template>
<span class="text-right" :class="internalValue < 0 ? 'negative' : ''">{{ formattedValue }} </span> <span class="text-right" :class="internalValue < 0 ? (negativeClass ?? 'negative') : positiveClass">{{ formattedValue }} </span>
</template> </template>

View File

@ -95,10 +95,14 @@ function assignedChanged(e : Event, category : Category){
<span class="hidden sm:block text-right">Activity</span> <span class="hidden sm:block text-right">Activity</span>
<span class="hidden sm:block text-right">Available</span> <span class="hidden sm:block text-right">Available</span>
<template v-for="group in GroupsForMonth"> <template v-for="group in GroupsForMonth">
<a <span
class="text-lg font-bold col-span-2 sm:col-span-4 lg:col-span-5" class="text-lg font-bold mt-2"
@click="toggleGroup(group)" @click="toggleGroup(group)"
>{{ (getGroupState(group) ? "" : "+") + " " + group.Name }}</a> >{{ (getGroupState(group) ? "" : "+") + " " + group.Name }}</span>
<Currency :value="group.AvailableLastMonth" class="hidden lg:block mt-2" positive-class="text-slate-500" negative-class="text-red-700 dark:text-red-400" />
<Currency :value="group.Assigned" class="hidden sm:block mx-2 mt-2 text-right" positive-class="text-slate-500" negative-class="text-red-700 dark:text-red-400" />
<Currency :value="group.Activity" class="hidden sm:block mt-2" positive-class="text-slate-500" negative-class="text-red-700 dark:text-red-400" />
<Currency :value="group.Available" class="mt-2" positive-class="text-slate-500" negative-class="text-red-700 dark:text-red-400" />
<template v-for="category in GetCategories(group.Name)" v-if="getGroupState(group)"> <template v-for="category in GetCategories(group.Name)" v-if="getGroupState(group)">
<span class="whitespace-nowrap overflow-hidden">{{ category.Name }}</span> <span class="whitespace-nowrap overflow-hidden">{{ category.Name }}</span>
<Currency :value="category.AvailableLastMonth" class="hidden lg:block" /> <Currency :value="category.AvailableLastMonth" class="hidden lg:block" />

View File

@ -51,7 +51,7 @@ export const useAccountStore = defineStore("budget/account", {
return [...monthMap?.values() || []]; return [...monthMap?.values() || []];
}, },
GetCategoryAvailable(state) { GetCategoryAvailable(state) {
return (category : Category) : number => { return (category: Category): number => {
return category.AvailableLastMonth + Number(category.Assigned) + category.Activity; return category.AvailableLastMonth + Number(category.Assigned) + category.Activity;
} }
}, },
@ -62,7 +62,7 @@ export const useAccountStore = defineStore("budget/account", {
GetIncomeAvailable(state) { GetIncomeAvailable(state) {
return (year: number, month: number) => { return (year: number, month: number) => {
const IncomeCategoryID = this.GetIncomeCategoryID; const IncomeCategoryID = this.GetIncomeCategoryID;
if(IncomeCategoryID == null) if (IncomeCategoryID == null)
return 0; return 0;
const categories = this.AllCategoriesForMonth(year, month); const categories = this.AllCategoriesForMonth(year, month);
@ -81,12 +81,25 @@ export const useAccountStore = defineStore("budget/account", {
if (category.ID == this.GetIncomeCategoryID) if (category.ID == this.GetIncomeCategoryID)
continue; continue;
if (category.Group != prev) if (prev == undefined || category.Group != prev.Name) {
categoryGroups.push({ prev = {
Name: category.Group, Name: category.Group,
Expand: category.Group != "Hidden Categories", Available: this.GetCategoryAvailable(category),
AvailableLastMonth: category.AvailableLastMonth,
Activity: category.Activity,
Assigned: category.Assigned,
}
categoryGroups.push({
...prev,
Expand: prev.Name != "Hidden Categories",
}); });
prev = category.Group; } else {
categoryGroups[categoryGroups.length-1].Available += this.GetCategoryAvailable(category);
categoryGroups[categoryGroups.length-1].AvailableLastMonth += category.AvailableLastMonth;
categoryGroups[categoryGroups.length-1].Activity += category.Activity;
categoryGroups[categoryGroups.length-1].Assigned += category.Assigned;
continue;
}
} }
return categoryGroups; return categoryGroups;
} }
@ -142,7 +155,7 @@ export const useAccountStore = defineStore("budget/account", {
account.Transactions = transactions; account.Transactions = transactions;
}, },
async FetchMonthBudget(budgetid: string, year: number, month: number) { async FetchMonthBudget(budgetid: string, year: number, month: number) {
const result = await GET("/budget/" + budgetid + "/" + year + "/" + (month+1)); const result = await GET("/budget/" + budgetid + "/" + year + "/" + (month + 1));
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;
@ -153,7 +166,7 @@ export const useAccountStore = defineStore("budget/account", {
const response = await result.json(); const response = await result.json();
useBudgetsStore().MergeBudgetingData(response); useBudgetsStore().MergeBudgetingData(response);
if(!isOpen) { if (!isOpen) {
this.Accounts.delete(accountid); this.Accounts.delete(accountid);
} }
}, },