From e0330778ade77e123a0fd0bc0b08961cbd262aaf Mon Sep 17 00:00:00 2001 From: matt Date: Thu, 12 Dec 2024 18:15:54 +0000 Subject: [PATCH 01/10] log mismatched split amounts --- .../src/components/settings/FixSplits.tsx | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/packages/desktop-client/src/components/settings/FixSplits.tsx b/packages/desktop-client/src/components/settings/FixSplits.tsx index 2897ee431a0..a304887fc27 100644 --- a/packages/desktop-client/src/components/settings/FixSplits.tsx +++ b/packages/desktop-client/src/components/settings/FixSplits.tsx @@ -1,7 +1,9 @@ import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { runQuery } from 'loot-core/src/client/query-helpers'; import { send } from 'loot-core/src/platform/client/fetch'; +import { q } from 'loot-core/src/shared/query'; import { type Handlers } from 'loot-core/src/types/handlers'; import { theme } from '../../style'; @@ -61,6 +63,29 @@ export function FixSplits() { async function onFix() { setLoading(true); const res = await send('tools/fix-split-transactions'); + + const allTransactions = ( + await runQuery( + q('transactions') + .options({ splits: 'grouped' }) + .filter({ + is_parent: true, + }) + .select('*'), + ) + ).data; + + const mismatchedSplits = allTransactions.filter(t => { + const subValue = t.subtransactions.reduce( + (acc, st) => acc + st.amount, + 0, + ); + + return subValue !== t.amount; + }); + + console.log(mismatchedSplits); + setResults(res); setLoading(false); } From caa96a368a9531934847050f1c477cd9459f00bf Mon Sep 17 00:00:00 2001 From: matt Date: Thu, 12 Dec 2024 19:09:50 +0000 Subject: [PATCH 02/10] improve error log --- .../desktop-client/src/components/settings/FixSplits.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/desktop-client/src/components/settings/FixSplits.tsx b/packages/desktop-client/src/components/settings/FixSplits.tsx index a304887fc27..9a08439b565 100644 --- a/packages/desktop-client/src/components/settings/FixSplits.tsx +++ b/packages/desktop-client/src/components/settings/FixSplits.tsx @@ -84,7 +84,12 @@ export function FixSplits() { return subValue !== t.amount; }); - console.log(mismatchedSplits); + if (mismatchedSplits.length > 0) { + console.log( + `Found ${mismatchedSplits.length} split transaction${mismatchedSplits.length > 1 ? 's' : ''} with mismatched amounts:\n` + + mismatchedSplits.map(t => `- ${t.date}`).join('\n'), + ); + } setResults(res); setLoading(false); From 47cba2e4c9d61a2415bda3ab27587121a52b5bcc Mon Sep 17 00:00:00 2001 From: matt Date: Fri, 13 Dec 2024 15:29:50 +0000 Subject: [PATCH 03/10] add log for none --- packages/desktop-client/src/components/settings/FixSplits.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/desktop-client/src/components/settings/FixSplits.tsx b/packages/desktop-client/src/components/settings/FixSplits.tsx index 9a08439b565..ba4091ccc4e 100644 --- a/packages/desktop-client/src/components/settings/FixSplits.tsx +++ b/packages/desktop-client/src/components/settings/FixSplits.tsx @@ -89,6 +89,8 @@ export function FixSplits() { `Found ${mismatchedSplits.length} split transaction${mismatchedSplits.length > 1 ? 's' : ''} with mismatched amounts:\n` + mismatchedSplits.map(t => `- ${t.date}`).join('\n'), ); + } else { + console.log('No mismatched splits found'); } setResults(res); From 1e5dd4a2b9f04f15989bd70a5345a772278f0bf2 Mon Sep 17 00:00:00 2001 From: matt Date: Sun, 15 Dec 2024 15:55:15 +0000 Subject: [PATCH 04/10] add to UI --- .../src/components/settings/FixSplits.tsx | 64 ++++++------------- packages/loot-core/src/server/tools/app.ts | 21 ++++++ .../src/server/tools/types/handlers.d.ts | 3 + 3 files changed, 43 insertions(+), 45 deletions(-) diff --git a/packages/desktop-client/src/components/settings/FixSplits.tsx b/packages/desktop-client/src/components/settings/FixSplits.tsx index ba4091ccc4e..7dbac3b9c83 100644 --- a/packages/desktop-client/src/components/settings/FixSplits.tsx +++ b/packages/desktop-client/src/components/settings/FixSplits.tsx @@ -1,9 +1,7 @@ import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { runQuery } from 'loot-core/src/client/query-helpers'; import { send } from 'loot-core/src/platform/client/fetch'; -import { q } from 'loot-core/src/shared/query'; import { type Handlers } from 'loot-core/src/types/handlers'; import { theme } from '../../style'; @@ -17,26 +15,31 @@ import { Setting } from './UI'; type Results = Awaited>; function renderResults(results: Results) { - const { numBlankPayees, numCleared, numDeleted } = results; - let result = ''; + const { numBlankPayees, numCleared, numDeleted, mismatchedSplits } = results; + const result: string[] = []; - if (numBlankPayees === 0 && numCleared === 0 && numDeleted === 0) { - result = 'No split transactions found needing repair.'; + if ( + numBlankPayees === 0 && + numCleared === 0 && + numDeleted === 0 && + mismatchedSplits.length === 0 + ) { + result.push('No split transactions found needing repair.'); } else { if (numBlankPayees > 0) { - result += `Fixed ${numBlankPayees} splits with a blank payee.`; + result.push(`Fixed ${numBlankPayees} splits with a blank payee.`); } if (numCleared > 0) { - if (result !== '') { - result += '\n'; - } - result += `Fixed ${numCleared} splits with the wrong cleared flag.`; + result.push(`Fixed ${numCleared} splits with the wrong cleared flag.`); } if (numDeleted > 0) { - if (result !== '') { - result += '\n'; - } - result += `Fixed ${numDeleted} splits that weren’t properly deleted.`; + result.push(`Fixed ${numDeleted} splits that weren’t properly deleted.`); + } + if (mismatchedSplits.length > 0) { + result.push( + `Found ${mismatchedSplits.length} split transaction${mismatchedSplits.length > 1 ? 's' : ''} with mismatched amounts:\n` + + mismatchedSplits.map(t => `- ${t.date}`).join('\n'), + ); } } @@ -50,7 +53,7 @@ function renderResults(results: Results) { whiteSpace: 'pre-wrap', }} > - {result} + {result.join('\n')} ); } @@ -64,35 +67,6 @@ export function FixSplits() { setLoading(true); const res = await send('tools/fix-split-transactions'); - const allTransactions = ( - await runQuery( - q('transactions') - .options({ splits: 'grouped' }) - .filter({ - is_parent: true, - }) - .select('*'), - ) - ).data; - - const mismatchedSplits = allTransactions.filter(t => { - const subValue = t.subtransactions.reduce( - (acc, st) => acc + st.amount, - 0, - ); - - return subValue !== t.amount; - }); - - if (mismatchedSplits.length > 0) { - console.log( - `Found ${mismatchedSplits.length} split transaction${mismatchedSplits.length > 1 ? 's' : ''} with mismatched amounts:\n` + - mismatchedSplits.map(t => `- ${t.date}`).join('\n'), - ); - } else { - console.log('No mismatched splits found'); - } - setResults(res); setLoading(false); } diff --git a/packages/loot-core/src/server/tools/app.ts b/packages/loot-core/src/server/tools/app.ts index 31b48447a83..5736020be30 100644 --- a/packages/loot-core/src/server/tools/app.ts +++ b/packages/loot-core/src/server/tools/app.ts @@ -4,6 +4,9 @@ import { createApp } from '../app'; import * as db from '../db'; import { runMutator } from '../mutators'; +import { runQuery } from '../../client/query-helpers'; +import { q } from '../../shared/query'; + import { ToolsHandlers } from './types/handlers'; export const app = createApp(); @@ -54,9 +57,27 @@ app.method('tools/fix-split-transactions', async () => { await batchUpdateTransactions({ updated }); }); + const splitTransactions = ( + await runQuery( + q('transactions') + .options({ splits: 'grouped' }) + .filter({ + is_parent: true, + }) + .select('*'), + ) + ).data; + + const mismatchedSplits = splitTransactions.filter(t => { + const subValue = t.subtransactions.reduce((acc, st) => acc + st.amount, 0); + + return subValue !== t.amount; + }); + return { numBlankPayees: blankPayeeRows.length, numCleared: clearedRows.length, numDeleted: deletedRows.length, + mismatchedSplits, }; }); diff --git a/packages/loot-core/src/server/tools/types/handlers.d.ts b/packages/loot-core/src/server/tools/types/handlers.d.ts index 841db08c084..12d94a4105e 100644 --- a/packages/loot-core/src/server/tools/types/handlers.d.ts +++ b/packages/loot-core/src/server/tools/types/handlers.d.ts @@ -1,7 +1,10 @@ +import { TransactionEntity } from 'loot-core/types/models'; + export interface ToolsHandlers { 'tools/fix-split-transactions': () => Promise<{ numBlankPayees: number; numCleared: number; numDeleted: number; + mismatchedSplits: TransactionEntity[]; }>; } From 1d3362bea8db815a11a7a2ff27ad8389f6457df4 Mon Sep 17 00:00:00 2001 From: matt Date: Sun, 15 Dec 2024 15:57:34 +0000 Subject: [PATCH 05/10] note --- upcoming-release-notes/3970.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 upcoming-release-notes/3970.md diff --git a/upcoming-release-notes/3970.md b/upcoming-release-notes/3970.md new file mode 100644 index 00000000000..6c7cda26645 --- /dev/null +++ b/upcoming-release-notes/3970.md @@ -0,0 +1,6 @@ +--- +category: Enhancement +authors: [matt-fidd] +--- + +Extend fix splits tool to report splits with mismatched amounts From 578890c5e6bdd0c97bd6b6fdda5109712ec0cd35 Mon Sep 17 00:00:00 2001 From: matt Date: Sun, 15 Dec 2024 16:03:26 +0000 Subject: [PATCH 06/10] fix --- packages/loot-core/src/server/tools/app.ts | 5 ++--- packages/loot-core/src/server/tools/types/handlers.d.ts | 2 +- upcoming-release-notes/3970.md | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/loot-core/src/server/tools/app.ts b/packages/loot-core/src/server/tools/app.ts index 5736020be30..95cb254f9f3 100644 --- a/packages/loot-core/src/server/tools/app.ts +++ b/packages/loot-core/src/server/tools/app.ts @@ -1,12 +1,11 @@ // @ts-strict-ignore +import { q } from '../../shared/query'; import { batchUpdateTransactions } from '../accounts/transactions'; import { createApp } from '../app'; +import { runQuery } from '../aql'; import * as db from '../db'; import { runMutator } from '../mutators'; -import { runQuery } from '../../client/query-helpers'; -import { q } from '../../shared/query'; - import { ToolsHandlers } from './types/handlers'; export const app = createApp(); diff --git a/packages/loot-core/src/server/tools/types/handlers.d.ts b/packages/loot-core/src/server/tools/types/handlers.d.ts index 12d94a4105e..8a31465a369 100644 --- a/packages/loot-core/src/server/tools/types/handlers.d.ts +++ b/packages/loot-core/src/server/tools/types/handlers.d.ts @@ -1,4 +1,4 @@ -import { TransactionEntity } from 'loot-core/types/models'; +import { TransactionEntity } from './models'; export interface ToolsHandlers { 'tools/fix-split-transactions': () => Promise<{ diff --git a/upcoming-release-notes/3970.md b/upcoming-release-notes/3970.md index 6c7cda26645..ec541471f6b 100644 --- a/upcoming-release-notes/3970.md +++ b/upcoming-release-notes/3970.md @@ -1,5 +1,5 @@ --- -category: Enhancement +category: Enhancements authors: [matt-fidd] --- From 2b2f2067678a9540ab5da62ee8312f158fe5389d Mon Sep 17 00:00:00 2001 From: matt Date: Sun, 15 Dec 2024 16:13:10 +0000 Subject: [PATCH 07/10] alter styling --- .../src/components/settings/FixSplits.tsx | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/desktop-client/src/components/settings/FixSplits.tsx b/packages/desktop-client/src/components/settings/FixSplits.tsx index 7dbac3b9c83..aa33f81b5ef 100644 --- a/packages/desktop-client/src/components/settings/FixSplits.tsx +++ b/packages/desktop-client/src/components/settings/FixSplits.tsx @@ -37,7 +37,9 @@ function renderResults(results: Results) { } if (mismatchedSplits.length > 0) { result.push( - `Found ${mismatchedSplits.length} split transaction${mismatchedSplits.length > 1 ? 's' : ''} with mismatched amounts:\n` + + `Found ${mismatchedSplits.length} split transaction${mismatchedSplits.length > 1 ? 's' : ''} ` + + `with mismatched amounts on the below date${mismatchedSplits.length > 1 ? 's' : ''}. ` + + `Please fix ${mismatchedSplits.length > 1 ? 'these' : 'this'} manually:\n` + mismatchedSplits.map(t => `- ${t.date}`).join('\n'), ); } @@ -46,10 +48,10 @@ function renderResults(results: Results) { return ( @@ -76,11 +78,9 @@ export function FixSplits() { primaryAction={ From 43fab40118c50c9e71a7e87ff6d775039b6ccd74 Mon Sep 17 00:00:00 2001 From: Matt Fiddaman Date: Mon, 23 Dec 2024 13:52:10 +0000 Subject: [PATCH 08/10] Update packages/desktop-client/src/components/settings/FixSplits.tsx Co-authored-by: Julian Dominguez-Schatz --- packages/desktop-client/src/components/settings/FixSplits.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/desktop-client/src/components/settings/FixSplits.tsx b/packages/desktop-client/src/components/settings/FixSplits.tsx index aa33f81b5ef..d62f093a417 100644 --- a/packages/desktop-client/src/components/settings/FixSplits.tsx +++ b/packages/desktop-client/src/components/settings/FixSplits.tsx @@ -39,7 +39,7 @@ function renderResults(results: Results) { result.push( `Found ${mismatchedSplits.length} split transaction${mismatchedSplits.length > 1 ? 's' : ''} ` + `with mismatched amounts on the below date${mismatchedSplits.length > 1 ? 's' : ''}. ` + - `Please fix ${mismatchedSplits.length > 1 ? 'these' : 'this'} manually:\n` + + `Please review ${mismatchedSplits.length > 1 ? 'them' : 'it'} manually:\n` + mismatchedSplits.map(t => `- ${t.date}`).join('\n'), ); } From f7057d525469e8bf54310e34334f8ba118940a53 Mon Sep 17 00:00:00 2001 From: matt Date: Mon, 23 Dec 2024 14:06:56 +0000 Subject: [PATCH 09/10] translations --- .../src/components/settings/FixSplits.tsx | 163 +++++++++++------- 1 file changed, 102 insertions(+), 61 deletions(-) diff --git a/packages/desktop-client/src/components/settings/FixSplits.tsx b/packages/desktop-client/src/components/settings/FixSplits.tsx index d62f093a417..b49ef8b8925 100644 --- a/packages/desktop-client/src/components/settings/FixSplits.tsx +++ b/packages/desktop-client/src/components/settings/FixSplits.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { useTranslation } from 'react-i18next'; +import { useTranslation, Trans } from 'react-i18next'; import { send } from 'loot-core/src/platform/client/fetch'; import { type Handlers } from 'loot-core/src/types/handlers'; @@ -14,57 +14,88 @@ import { Setting } from './UI'; type Results = Awaited>; -function renderResults(results: Results) { - const { numBlankPayees, numCleared, numDeleted, mismatchedSplits } = results; - const result: string[] = []; +function useRenderResults() { + const { t } = useTranslation(); - if ( - numBlankPayees === 0 && - numCleared === 0 && - numDeleted === 0 && - mismatchedSplits.length === 0 - ) { - result.push('No split transactions found needing repair.'); - } else { - if (numBlankPayees > 0) { - result.push(`Fixed ${numBlankPayees} splits with a blank payee.`); - } - if (numCleared > 0) { - result.push(`Fixed ${numCleared} splits with the wrong cleared flag.`); - } - if (numDeleted > 0) { - result.push(`Fixed ${numDeleted} splits that weren’t properly deleted.`); - } - if (mismatchedSplits.length > 0) { - result.push( - `Found ${mismatchedSplits.length} split transaction${mismatchedSplits.length > 1 ? 's' : ''} ` + - `with mismatched amounts on the below date${mismatchedSplits.length > 1 ? 's' : ''}. ` + - `Please review ${mismatchedSplits.length > 1 ? 'them' : 'it'} manually:\n` + - mismatchedSplits.map(t => `- ${t.date}`).join('\n'), - ); + function renderResults(results: Results) { + const { numBlankPayees, numCleared, numDeleted, mismatchedSplits } = + results; + const result: string[] = []; + + if ( + numBlankPayees === 0 && + numCleared === 0 && + numDeleted === 0 && + mismatchedSplits.length === 0 + ) { + result.push(t('No split transactions found needing repair.')); + } else { + if (numBlankPayees > 0) { + result.push( + t('Fixed {{numBlankPayees}} splits with a blank payee.', { + numBlankPayees, + }), + ); + } + if (numCleared > 0) { + result.push( + t('Fixed {{numCleared}} splits with the wrong cleared flag.', { + numCleared, + }), + ); + } + if (numDeleted > 0) { + result.push( + t('Fixed {{numDeleted}} splits that weren’t properly deleted.', { + numDeleted, + }), + ); + } + if (mismatchedSplits.length > 0) { + const mismatchedSplitInfo = mismatchedSplits + .map(t => `- ${t.date}`) + .join('\n'); + if (mismatchedSplits.length === 1) { + result.push( + t( + 'Found 1 split transaction with mismatched amounts on the below date. Please review it manually:', + ) + `\n${mismatchedSplitInfo}`, + ); + } else { + result.push( + t( + 'Found {{num}} split transactions with mismatched amounts on the below dates. Please review them manually:', + { num: mismatchedSplits.length }, + ) + `\n${mismatchedSplitInfo}`, + ); + } + } } + + return ( + + {result.join('\n')} + + ); } - return ( - - {result.join('\n')} - - ); + return { renderResults }; } export function FixSplits() { - const { t } = useTranslation(); const [loading, setLoading] = useState(false); const [results, setResults] = useState(null); + const { renderResults } = useRenderResults(); + async function onFix() { setLoading(true); const res = await send('tools/fix-split-transactions'); @@ -84,30 +115,40 @@ export function FixSplits() { }} > - {t('Repair split transactions')} + Repair split transactions {results && renderResults(results)} } > - - {t('Repair split transactions')} - {t( - ' if you are experiencing bugs relating to split transactions and the “Reset budget cache” button above does not help, this tool may fix them. Some examples of bugs include seeing blank payees on splits or incorrect account balances. This tool does two things:', - )} - -
    -
  • - {t( - 'Ensures that deleted split transactions are fully deleted. In previous versions of the app, certain split transactions may appear deleted but not all of them are actually deleted. This causes the transactions list to look correct, but certain balances may be incorrect when filtering.', - )} -
  • -
  • - {t( - 'Sync the payee and cleared flag of a split transaction to the main or “parent” transaction, if appropriate. The payee will only be set if it currently doesn’t have one.', - )} -
  • -
+ + + Repair split transactions if you are experiencing + bugs relating to split transactions and the “Reset budget cache” + button above does not help, this tool may fix them. Some examples of + bugs include seeing blank payees on splits or incorrect account + balances. This tool does three things: + +
    +
  • + Ensures that deleted split transactions are fully deleted. In + previous versions of the app, certain split transactions may appear + deleted but not all of them are actually deleted. This causes the + transactions list to look correct, but certain balances may be + incorrect when filtering. +
  • +
  • + Sync the payee and cleared flag of a split transaction to the main + or “parent” transaction, if appropriate. The payee will only be set + if it currently doesn’t have one. +
  • +
  • + Checks that the sum of all child transactions adds up to the total + amount. If not, these will be flagged below to allow you to easily + locate and fix the amounts. +
  • +
+
); } From 366449f48bf25f78ab02fe35f2fc50124056a2a8 Mon Sep 17 00:00:00 2001 From: matt Date: Mon, 23 Dec 2024 23:39:07 +0000 Subject: [PATCH 10/10] pluralisation --- .../src/components/settings/FixSplits.tsx | 33 ++++++++----------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/packages/desktop-client/src/components/settings/FixSplits.tsx b/packages/desktop-client/src/components/settings/FixSplits.tsx index b49ef8b8925..fa87cecfee8 100644 --- a/packages/desktop-client/src/components/settings/FixSplits.tsx +++ b/packages/desktop-client/src/components/settings/FixSplits.tsx @@ -32,22 +32,22 @@ function useRenderResults() { } else { if (numBlankPayees > 0) { result.push( - t('Fixed {{numBlankPayees}} splits with a blank payee.', { - numBlankPayees, + t('Fixed {{count}} splits with a blank payee.', { + count: numBlankPayees, }), ); } if (numCleared > 0) { result.push( - t('Fixed {{numCleared}} splits with the wrong cleared flag.', { - numCleared, + t('Fixed {{count}} splits with the wrong cleared flag.', { + count: numCleared, }), ); } if (numDeleted > 0) { result.push( - t('Fixed {{numDeleted}} splits that weren’t properly deleted.', { - numDeleted, + t('Fixed {{count}} splits that weren’t properly deleted.', { + count: numDeleted, }), ); } @@ -55,20 +55,13 @@ function useRenderResults() { const mismatchedSplitInfo = mismatchedSplits .map(t => `- ${t.date}`) .join('\n'); - if (mismatchedSplits.length === 1) { - result.push( - t( - 'Found 1 split transaction with mismatched amounts on the below date. Please review it manually:', - ) + `\n${mismatchedSplitInfo}`, - ); - } else { - result.push( - t( - 'Found {{num}} split transactions with mismatched amounts on the below dates. Please review them manually:', - { num: mismatchedSplits.length }, - ) + `\n${mismatchedSplitInfo}`, - ); - } + + result.push( + t( + 'Found {{count}} split transactions with mismatched amounts on the below dates. Please review them manually:', + { count: mismatchedSplits.length }, + ) + `\n${mismatchedSplitInfo}`, + ); } }