Skip to content

Commit

Permalink
Fix multiple split INCOME cash flow (#862)
Browse files Browse the repository at this point in the history
  • Loading branch information
argaen authored May 15, 2024
1 parent fcdc39c commit 85fdb91
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 18 deletions.
113 changes: 100 additions & 13 deletions src/__tests__/hooks/api/useCashFlow.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ describe('useCashFlow', () => {
*
* This case is simple as we want two rows same as the splits.
*/
it('works with 3 splits in same currency', async () => {
it('works with 3 outflow splits in same currency', async () => {
const account3 = await Account.create({
guid: 'guid3',
name: 'Expense2',
Expand Down Expand Up @@ -389,7 +389,86 @@ describe('useCashFlow', () => {
});

/**
* The above case is simple however, when
* Same as the test before but with receiving money into the ASSET
* account instead of withdrawing
*
* As an example, say we have a tx with the following:
* - 100 in account1
* - -75 in account2
* - -25 in account3
*
* This case is simple as we want two rows same as the splits.
*/
it('works with 3 inflow splits in same currency', async () => {
const account3 = await Account.create({
guid: 'guid3',
name: 'Income1',
fk_commodity: eur,
parent: root,
type: 'INCOME',
}).save();

const account4 = await Account.create({
guid: 'guid4',
name: 'Income2',
fk_commodity: eur,
parent: root,
type: 'INCOME',
}).save();

await Transaction.create({
fk_currency: eur,
description: 'tx1',
date: DateTime.now(),
splits: [
Split.create({
fk_account: account1,
valueNum: 100,
valueDenom: 1,
quantityNum: 100,
quantityDenom: 1,
}),
Split.create({
fk_account: account3,
valueNum: -75,
valueDenom: 1,
quantityNum: -75,
quantityDenom: 1,
}),
Split.create({
fk_account: account4,
valueNum: -25,
valueDenom: 1,
quantityNum: -25,
quantityDenom: 1,
}),
],
}).save();

const { result } = renderHook(
() => useCashFlow(account1.guid),
{ wrapper },
);

await waitFor(() => expect(result.current.status).toEqual('success'));

expect(result.current.data?.[0]).toMatchObject({
name: account3.name,
total: expect.objectContaining({
readable: '-75 EUR',
}),
});

expect(result.current.data?.[1]).toMatchObject({
name: account4.name,
total: expect.objectContaining({
readable: '-25 EUR',
}),
});
});

/**
* The above case are simple however, when
* the accounts have different currencies, this gets a bit more complicated:
*
* - -108 USD/-100 EUR in account1
Expand Down Expand Up @@ -542,10 +621,18 @@ describe('useCashFlow', () => {
it('ignores 0s', async () => {
const account3 = await Account.create({
guid: 'guid3',
name: 'Bank3',
name: 'Investment',
fk_commodity: eur,
parent: account1,
type: 'INVESTMENT',
}).save();

const account4 = await Account.create({
guid: 'guid4',
name: 'Dividends',
fk_commodity: eur,
parent: root,
type: 'ASSET',
type: 'INCOME',
}).save();

await Transaction.create({
Expand All @@ -555,20 +642,20 @@ describe('useCashFlow', () => {
splits: [
Split.create({
fk_account: account1,
valueNum: 0,
valueNum: 100,
valueDenom: 1,
quantityNum: 0,
quantityNum: 100,
quantityDenom: 1,
}),
Split.create({
fk_account: account2,
valueNum: 100,
fk_account: account3,
valueNum: 0,
valueDenom: 1,
quantityNum: 100,
quantityNum: 0,
quantityDenom: 1,
}),
Split.create({
fk_account: account3,
fk_account: account4,
valueNum: -100,
valueDenom: 1,
quantityNum: -100,
Expand All @@ -578,17 +665,17 @@ describe('useCashFlow', () => {
}).save();

const { result } = renderHook(
() => useCashFlow(account3.guid),
() => useCashFlow(account1.guid),
{ wrapper },
);

await waitFor(() => expect(result.current.status).toEqual('success'));

expect(result.current.data).toHaveLength(1);
expect(result.current.data?.[0]).toMatchObject({
name: account2.name,
name: account4.name,
total: expect.objectContaining({
readable: '100 EUR',
readable: '-100 EUR',
}),
});
});
Expand Down
14 changes: 9 additions & 5 deletions src/hooks/api/useCashFlow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,14 @@ export function useCashFlow(
) AS mnemonic,
SUM(
CASE
WHEN splits.guid = credit_split.guid THEN
WHEN splits.guid = asset_split.guid THEN
-1 * cast(account_split.quantity_num AS REAL) / account_split.quantity_denom
ELSE
CASE
WHEN account_split.guid = credit_split.guid THEN
(cast(account_split.quantity_num AS REAL) / account_split.quantity_denom) / (cast(account_split.value_num AS REAL) / account_split.value_denom) * (cast(splits.value_num AS REAL) / splits.value_denom)
WHEN account_split.guid = asset_split.guid THEN
(cast(splits.value_num AS REAL) / splits.value_denom)
* (cast(account_split.quantity_num AS REAL) / account_split.quantity_denom)
/ (cast(account_split.value_num AS REAL) / account_split.value_denom)
ELSE
0
END
Expand All @@ -62,7 +64,9 @@ export function useCashFlow(
JOIN splits ON splits.tx_guid = tx.guid
JOIN accounts ON splits.account_guid = accounts.guid
JOIN splits AS account_split ON account_split.tx_guid = tx.guid AND account_split.account_guid = '${account}'
JOIN splits AS credit_split ON credit_split.tx_guid = tx.guid AND credit_split.value_num < 0
JOIN splits AS asset_split ON asset_split.tx_guid = tx.guid
JOIN accounts AS asset_account ON asset_account.guid = asset_split.account_guid AND asset_account.account_type IN ('ASSET', 'BANK', 'CASH', 'FIXED', 'RECEIVABLE')
WHERE tx.guid IN (
SELECT DISTINCT tx_guid
Expand All @@ -72,7 +76,7 @@ export function useCashFlow(
AND accounts.guid != '${account}'
AND tx.post_date BETWEEN '${((interval as Interval).start as DateTime).toSQLDate()}' AND '${((interval as Interval).end as DateTime).toSQLDate()}'
GROUP BY splits.account_guid, accounts.name, accounts.account_type, mnemonic
GROUP BY accounts.guid, accounts.name, accounts.account_type, mnemonic
HAVING total != 0
`) as { guid: string, type: string, name: string, total: number, mnemonic: string }[];

Expand Down

0 comments on commit 85fdb91

Please sign in to comment.