From f1caf21deb9488ede8741ce3ed06a2e584309fee Mon Sep 17 00:00:00 2001 From: Joel Jeremy Marquez <joeljeremy.marquez@gmail.com> Date: Sun, 7 Jul 2024 09:29:27 -0700 Subject: [PATCH] Assign schedule to both transactions if schedule is a transfer (#2990) * Assign schedule to both transactions if schedule is a transfer * Release notes * Migration for old scheduled transfer transactions --- .../src/components/accounts/Account.jsx | 29 +++-------------- .../mobile/accounts/AccountTransactions.jsx | 18 ++++------- .../transactions/TransactionsTable.jsx | 10 +++--- .../1720310586000_link_transfer_schedules.sql | 19 +++++++++++ .../src/client/data-hooks/schedules.tsx | 32 ++++++++++++++++++- .../loot-core/src/server/accounts/transfer.ts | 2 ++ upcoming-release-notes/2990.md | 6 ++++ 7 files changed, 74 insertions(+), 42 deletions(-) create mode 100644 packages/loot-core/migrations/1720310586000_link_transfer_schedules.sql create mode 100644 upcoming-release-notes/2990.md diff --git a/packages/desktop-client/src/components/accounts/Account.jsx b/packages/desktop-client/src/components/accounts/Account.jsx index ef660cbef..3f62365ad 100644 --- a/packages/desktop-client/src/components/accounts/Account.jsx +++ b/packages/desktop-client/src/components/accounts/Account.jsx @@ -7,7 +7,10 @@ import { v4 as uuidv4 } from 'uuid'; import { validForTransfer } from 'loot-core/client/transfer'; import { useFilters } from 'loot-core/src/client/data-hooks/filters'; -import { SchedulesProvider } from 'loot-core/src/client/data-hooks/schedules'; +import { + SchedulesProvider, + useDefaultSchedulesQueryTransform, +} from 'loot-core/src/client/data-hooks/schedules'; import * as queries from 'loot-core/src/client/queries'; import { runQuery, pagedQuery } from 'loot-core/src/client/query-helpers'; import { send, listen } from 'loot-core/src/platform/client/fetch'; @@ -1837,29 +1840,7 @@ export function Account() { const savedFiters = useFilters(); const actionCreators = useActions(); - const transform = useMemo(() => { - const filterByAccount = queries.getAccountFilter(params.id, '_account'); - const filterByPayee = queries.getAccountFilter( - params.id, - '_payee.transfer_acct', - ); - - return q => { - q = q.filter({ - $and: [{ '_account.closed': false }], - }); - if (params.id) { - if (params.id === 'uncategorized') { - q = q.filter({ next_date: null }); - } else { - q = q.filter({ - $or: [filterByAccount, filterByPayee], - }); - } - } - return q.orderBy({ next_date: 'desc' }); - }; - }, [params.id]); + const transform = useDefaultSchedulesQueryTransform(params.id); return ( <SchedulesProvider transform={transform}> diff --git a/packages/desktop-client/src/components/mobile/accounts/AccountTransactions.jsx b/packages/desktop-client/src/components/mobile/accounts/AccountTransactions.jsx index cf01dba15..4711d0523 100644 --- a/packages/desktop-client/src/components/mobile/accounts/AccountTransactions.jsx +++ b/packages/desktop-client/src/components/mobile/accounts/AccountTransactions.jsx @@ -7,7 +7,6 @@ import React, { } from 'react'; import { useDispatch } from 'react-redux'; -import memoizeOne from 'memoize-one'; import { useDebounceCallback } from 'usehooks-ts'; import { @@ -20,7 +19,10 @@ import { syncAndDownload, updateAccount, } from 'loot-core/client/actions'; -import { SchedulesProvider } from 'loot-core/client/data-hooks/schedules'; +import { + SchedulesProvider, + useDefaultSchedulesQueryTransform, +} from 'loot-core/client/data-hooks/schedules'; import * as queries from 'loot-core/client/queries'; import { pagedQuery } from 'loot-core/client/query-helpers'; import { listen, send } from 'loot-core/platform/client/fetch'; @@ -39,6 +41,7 @@ import { AddTransactionButton } from '../transactions/AddTransactionButton'; import { TransactionListWithBalances } from '../transactions/TransactionListWithBalances'; export function AccountTransactions({ account, pending, failed }) { + const schedulesTransform = useDefaultSchedulesQueryTransform(account.id); return ( <Page header={ @@ -52,7 +55,7 @@ export function AccountTransactions({ account, pending, failed }) { } padding={0} > - <SchedulesProvider transform={getSchedulesTransform(account.id)}> + <SchedulesProvider transform={schedulesTransform}> <TransactionListWithPreviews account={account} /> </SchedulesProvider> </Page> @@ -132,15 +135,6 @@ function AccountName({ account, pending, failed }) { ); } -const getSchedulesTransform = memoizeOne(id => { - const filter = queries.getAccountFilter(id, '_account'); - - return q => { - q = q.filter({ $and: [filter, { '_account.closed': false }] }); - return q.orderBy({ next_date: 'desc' }); - }; -}); - function TransactionListWithPreviews({ account }) { const [currentQuery, setCurrentQuery] = useState(); const [isSearching, setIsSearching] = useState(false); diff --git a/packages/desktop-client/src/components/transactions/TransactionsTable.jsx b/packages/desktop-client/src/components/transactions/TransactionsTable.jsx index d3225f371..f0880f907 100644 --- a/packages/desktop-client/src/components/transactions/TransactionsTable.jsx +++ b/packages/desktop-client/src/components/transactions/TransactionsTable.jsx @@ -604,17 +604,17 @@ function PayeeIcons({ transferAccount, onNavigateToTransferAccount, onNavigateToSchedule, - children, }) { const scheduleId = transaction.schedule; const scheduleData = useCachedSchedules(); - const schedule = scheduleData - ? scheduleData.schedules.find(s => s.id === scheduleId) - : null; + const schedule = + scheduleId && scheduleData + ? scheduleData.schedules.find(s => s.id === scheduleId) + : null; if (schedule == null && transferAccount == null) { // Neither a valid scheduled transaction nor a transfer. - return children; + return null; } const buttonStyle = { diff --git a/packages/loot-core/migrations/1720310586000_link_transfer_schedules.sql b/packages/loot-core/migrations/1720310586000_link_transfer_schedules.sql new file mode 100644 index 000000000..f1f6e11d4 --- /dev/null +++ b/packages/loot-core/migrations/1720310586000_link_transfer_schedules.sql @@ -0,0 +1,19 @@ +BEGIN TRANSACTION; + +UPDATE transactions AS t1 +SET schedule = ( + SELECT t2.schedule FROM transactions AS t2 + WHERE t2.id = t1.transferred_id + AND t2.schedule IS NOT NULL + LIMIT 1 +) +WHERE t1.schedule IS NULL +AND t1.transferred_id IS NOT NULL +AND EXISTS ( + SELECT 1 FROM transactions AS t2 + WHERE t2.id = t1.transferred_id + AND t2.schedule IS NOT NULL + LIMIT 1 +); + +COMMIT; diff --git a/packages/loot-core/src/client/data-hooks/schedules.tsx b/packages/loot-core/src/client/data-hooks/schedules.tsx index 2f6117dea..5c18d8779 100644 --- a/packages/loot-core/src/client/data-hooks/schedules.tsx +++ b/packages/loot-core/src/client/data-hooks/schedules.tsx @@ -1,9 +1,16 @@ // @ts-strict-ignore -import React, { createContext, useEffect, useState, useContext } from 'react'; +import React, { + createContext, + useEffect, + useState, + useContext, + useMemo, +} from 'react'; import { q, type Query } from '../../shared/query'; import { getStatus, getHasTransactionsQuery } from '../../shared/schedules'; import { type ScheduleEntity } from '../../types/models'; +import { getAccountFilter } from '../queries'; import { liveQuery } from '../query-helpers'; export type ScheduleStatusType = ReturnType<typeof getStatus>; @@ -84,3 +91,26 @@ export function SchedulesProvider({ transform, children }) { export function useCachedSchedules() { return useContext(SchedulesContext); } + +export function useDefaultSchedulesQueryTransform(accountId) { + return useMemo(() => { + const filterByAccount = getAccountFilter(accountId, '_account'); + const filterByPayee = getAccountFilter(accountId, '_payee.transfer_acct'); + + return (q: Query) => { + q = q.filter({ + $and: [{ '_account.closed': false }], + }); + if (accountId) { + if (accountId === 'uncategorized') { + q = q.filter({ next_date: null }); + } else { + q = q.filter({ + $or: [filterByAccount, filterByPayee], + }); + } + } + return q.orderBy({ next_date: 'desc' }); + }; + }, [accountId]); +} diff --git a/packages/loot-core/src/server/accounts/transfer.ts b/packages/loot-core/src/server/accounts/transfer.ts index f3c41e434..04545bf78 100644 --- a/packages/loot-core/src/server/accounts/transfer.ts +++ b/packages/loot-core/src/server/accounts/transfer.ts @@ -80,6 +80,7 @@ export async function addTransfer(transaction, transferredAccount) { date: transaction.date, transfer_id: transaction.id, notes: transaction.notes || null, + schedule: transaction.schedule, cleared: false, }); @@ -130,6 +131,7 @@ export async function updateTransfer(transaction, transferredAccount) { date: transaction.date, notes: transaction.notes, amount: -transaction.amount, + schedule: transaction.schedule, }); const categoryCleared = await clearCategory(transaction, transferredAccount); diff --git a/upcoming-release-notes/2990.md b/upcoming-release-notes/2990.md new file mode 100644 index 000000000..17da16756 --- /dev/null +++ b/upcoming-release-notes/2990.md @@ -0,0 +1,6 @@ +--- +category: Bugfix +authors: [joel-jeremy] +--- + +Assign schedule to both transactions if schedule is a transfer -- GitLab