fix: Custom Report Total Mode not showing offbudget transactions #3627 (#3633)

* fix: Custom Report Total Mode not showing offbudget transactions #3627

* chore: release note

* chore: remove debug logging

* fix: table grouped report
This commit is contained in:
Koen van Staveren
2024-10-23 23:55:54 +02:00
committed by GitHub
parent 4d89a9b86a
commit 10ca29e1e9
6 changed files with 81 additions and 153 deletions

View File

@@ -232,49 +232,32 @@ export type QueryDataEntity = {
amount: number;
};
type UncategorizedId = 'off_budget' | 'transfer' | 'other' | 'all';
export type UncategorizedEntity = Pick<
CategoryEntity,
'id' | 'name' | 'hidden'
'id' | 'name' | 'hidden' | 'cat_group'
> & {
/*
When looking at uncategorized and hidden transactions we
need a way to group them. To do this we give them a unique
uncategorized_id. We also need a way to filter the
transctions from our query. For this we use the 3 variables
below.
*/
uncategorized_id?: string;
is_off_budget?: boolean;
is_transfer?: boolean;
has_category?: boolean;
uncategorized_id?: UncategorizedId;
};
const uncategorizedCategory: UncategorizedEntity = {
id: '',
name: 'Uncategorized',
uncategorized_id: '1',
uncategorized_id: 'other',
hidden: false,
is_off_budget: false,
is_transfer: false,
has_category: false,
};
const transferCategory: UncategorizedEntity = {
id: '',
name: 'Transfers',
uncategorized_id: '2',
uncategorized_id: 'transfer',
hidden: false,
is_off_budget: false,
is_transfer: true,
has_category: false,
};
const offBudgetCategory: UncategorizedEntity = {
id: '',
name: 'Off Budget',
uncategorized_id: '3',
uncategorized_id: 'off_budget',
hidden: false,
is_off_budget: true,
is_transfer: false,
has_category: true,
};
type UncategorizedGroupEntity = Pick<
@@ -282,12 +265,14 @@ type UncategorizedGroupEntity = Pick<
'name' | 'id' | 'hidden'
> & {
categories?: UncategorizedEntity[];
uncategorized_id?: UncategorizedId;
};
const uncategorizedGroup: UncategorizedGroupEntity = {
name: 'Uncategorized & Off Budget',
id: 'uncategorized',
hidden: false,
uncategorized_id: 'all',
categories: [uncategorizedCategory, transferCategory, offBudgetCategory],
};
@@ -302,7 +287,7 @@ export const categoryLists = (categories: {
const catGroupB = categories.grouped.find(f => f.id === b.cat_group);
//initial check that both a and b have a sort_order and category group
return a.sort_order && b.sort_order && catGroupA && catGroupB
? /*sorting by "is_income" because sort_order for this group is
? /*sorting by "is_income" because sort_order for this group is
separate from other groups*/
Number(catGroupA.is_income) - Number(catGroupB.is_income) ||
//Next, sorting by group sort_order
@@ -342,7 +327,12 @@ export const groupBySelections = (
break;
case 'Group':
groupByList = categoryGroup.map(group => {
return { id: group.id, name: group.name, hidden: group.hidden };
return {
...group,
id: group.id,
name: group.name,
hidden: group.hidden,
};
});
groupByLabel = 'categoryGroup';
break;

View File

@@ -145,6 +145,8 @@ export function createCustomSpreadsheet({
let netAssets = 0;
let netDebts = 0;
const groupsByCategory =
groupByLabel === 'category' || groupByLabel === 'categoryGroup';
const intervalData = intervals.reduce(
(arr: IntervalEntity[], intervalItem, index) => {
let perIntervalAssets = 0;
@@ -163,11 +165,13 @@ export function createCustomSpreadsheet({
showOffBudget,
showHiddenCategories,
showUncategorized,
groupsByCategory,
)
.filter(
asset =>
asset.date === intervalItem &&
asset[groupByLabel] === (item.id ?? null),
(asset[groupByLabel] === (item.id ?? null) ||
(item.uncategorized_id && groupsByCategory)),
)
.reduce((a, v) => (a = a + v.amount), 0);
perIntervalAssets += intervalAssets;
@@ -178,11 +182,13 @@ export function createCustomSpreadsheet({
showOffBudget,
showHiddenCategories,
showUncategorized,
groupsByCategory,
)
.filter(
debt =>
debt.date === intervalItem &&
debt[groupByLabel] === (item.id ?? null),
(debt[groupByLabel] === (item.id ?? null) ||
(item.uncategorized_id && groupsByCategory)),
)
.reduce((a, v) => (a = a + v.amount), 0);
perIntervalDebts += intervalDebts;

View File

@@ -9,43 +9,42 @@ export function filterHiddenItems(
showOffBudget?: boolean,
showHiddenCategories?: boolean,
showUncategorized?: boolean,
groupByCategory?: boolean,
) {
const showHide = data
.filter(e =>
!showHiddenCategories
? e.categoryHidden === false && e.categoryGroupHidden === false
: true,
.filter(
e =>
showHiddenCategories ||
(e.categoryHidden === false && e.categoryGroupHidden === false),
)
.filter(f =>
showOffBudget
? showUncategorized
? //true,true
true
: //true,false
f.category !== null ||
f.accountOffBudget !== false ||
f.transferAccount !== null
: showUncategorized
? //false, true
f.accountOffBudget === false &&
(f.transferAccount === null || f.category !== null)
: //false false
f.category !== null && f.accountOffBudget === false,
.filter(e => showOffBudget || e.accountOffBudget === false)
.filter(
e =>
showUncategorized || e.category !== null || e.accountOffBudget === true,
);
return showHide.filter(query => {
if (!item.uncategorized_id) {
return true;
if (!groupByCategory) return true;
const hasCategory = !!query.category;
const isOffBudget = query.accountOffBudget;
const isTransfer = !!query.transferAccount;
if (hasCategory && !isOffBudget) {
return item.uncategorized_id == null;
}
const isTransfer = item.is_transfer
? query.transferAccount
: !query.transferAccount;
const isHidden = item.has_category ? true : !query.category;
const isOffBudget = item.is_off_budget
? query.accountOffBudget
: !query.accountOffBudget;
return isTransfer && isHidden && isOffBudget;
switch (item.uncategorized_id) {
case 'off_budget':
return isOffBudget;
case 'transfer':
return isTransfer && !isOffBudget;
case 'other':
return !isOffBudget && !isTransfer;
case 'all':
return true;
default:
return false;
}
});
}

View File

@@ -2,11 +2,7 @@ import { runQuery } from 'loot-core/src/client/query-helpers';
import { type useSpreadsheet } from 'loot-core/src/client/SpreadsheetProvider';
import { send } from 'loot-core/src/platform/client/fetch';
import * as monthUtils from 'loot-core/src/shared/months';
import { integerToAmount } from 'loot-core/src/shared/util';
import {
type IntervalEntity,
type GroupedEntity,
} from 'loot-core/src/types/models/reports';
import { type GroupedEntity } from 'loot-core/src/types/models/reports';
import {
categoryLists,
@@ -16,7 +12,6 @@ import {
import { type createCustomSpreadsheetProps } from './custom-spreadsheet';
import { filterEmptyRows } from './filterEmptyRows';
import { filterHiddenItems } from './filterHiddenItems';
import { makeQuery } from './makeQuery';
import { recalculate } from './recalculate';
@@ -98,85 +93,18 @@ export function createGroupedSpreadsheet({
const groupedData: GroupedEntity[] = categoryGroup.map(
group => {
let totalAssets = 0;
let totalDebts = 0;
let netAssets = 0;
let netDebts = 0;
const intervalData = intervals.reduce(
(arr: IntervalEntity[], intervalItem) => {
let groupedAssets = 0;
let groupedDebts = 0;
let groupedNetAssets = 0;
let groupedNetDebts = 0;
let groupedTotals = 0;
if (!group.categories) {
return [];
}
group.categories.forEach(item => {
const intervalAssets = filterHiddenItems(
item,
assets,
showOffBudget,
showHiddenCategories,
showUncategorized,
)
.filter(
asset =>
asset.date === intervalItem &&
asset.category === (item.id ?? null),
)
.reduce((a, v) => (a = a + v.amount), 0);
groupedAssets += intervalAssets;
const intervalDebts = filterHiddenItems(
item,
debts,
showOffBudget,
showHiddenCategories,
showUncategorized,
)
.filter(
debts =>
debts.date === intervalItem &&
debts.category === (item.id ?? null),
)
.reduce((a, v) => (a = a + v.amount), 0);
groupedDebts += intervalDebts;
const intervalTotals = intervalAssets + intervalDebts;
groupedNetAssets =
intervalTotals > 0
? groupedNetAssets + intervalTotals
: groupedNetAssets;
groupedNetDebts =
intervalTotals < 0
? groupedNetDebts + intervalTotals
: groupedNetDebts;
groupedTotals += intervalTotals;
});
totalAssets += groupedAssets;
totalDebts += groupedDebts;
netAssets += groupedNetAssets;
netDebts += groupedNetDebts;
arr.push({
date: intervalItem,
totalAssets: integerToAmount(groupedAssets),
totalDebts: integerToAmount(groupedDebts),
netAssets: integerToAmount(groupedNetAssets),
netDebts: integerToAmount(groupedNetDebts),
totalTotals: integerToAmount(groupedTotals),
});
return arr;
},
[],
);
const grouped = recalculate({
item: group,
intervals,
assets,
debts,
groupByLabel: 'categoryGroup',
showOffBudget,
showHiddenCategories,
showUncategorized,
startDate,
endDate,
});
const stackedCategories =
group.categories &&
@@ -197,14 +125,7 @@ export function createGroupedSpreadsheet({
});
return {
id: group.id || '',
name: group.name,
totalAssets: integerToAmount(totalAssets),
totalDebts: integerToAmount(totalDebts),
netAssets: integerToAmount(netAssets),
netDebts: integerToAmount(netDebts),
totalTotals: integerToAmount(totalAssets + totalDebts),
intervalData,
...grouped,
categories:
stackedCategories &&
stackedCategories.filter(i =>

View File

@@ -43,17 +43,21 @@ export function recalculate({
(arr: IntervalEntity[], intervalItem, index) => {
const last = arr.length === 0 ? null : arr[arr.length - 1];
const groupsByCategory =
groupByLabel === 'category' || groupByLabel === 'categoryGroup';
const intervalAssets = filterHiddenItems(
item,
assets,
showOffBudget,
showHiddenCategories,
showUncategorized,
groupsByCategory,
)
.filter(
asset =>
asset.date === intervalItem &&
asset[groupByLabel] === (item.id ?? null),
(asset[groupByLabel] === (item.id ?? null) ||
(item.uncategorized_id && groupsByCategory)),
)
.reduce((a, v) => (a = a + v.amount), 0);
totalAssets += intervalAssets;
@@ -64,11 +68,13 @@ export function recalculate({
showOffBudget,
showHiddenCategories,
showUncategorized,
groupsByCategory,
)
.filter(
debt =>
debt.date === intervalItem &&
debt[groupByLabel] === (item.id ?? null),
(debt[groupByLabel] === (item.id ?? null) ||
(item.uncategorized_id && groupsByCategory)),
)
.reduce((a, v) => (a = a + v.amount), 0);
totalDebts += intervalDebts;

View File

@@ -0,0 +1,6 @@
---
category: Bugfix
authors: [UnderKoen]
---
Fix 'show uncategorized' and 'show off budget' for custom reports