diff --git a/cypress/factories/settings.ts b/cypress/factories/settings.ts index ac0316f..4f0e1d3 100644 --- a/cypress/factories/settings.ts +++ b/cypress/factories/settings.ts @@ -21,6 +21,7 @@ export default function createSettings( fractionDigits: 2, startDate: 1562450400000, startBalance: 0, + ignorePendingTransactions: false, ...settings, }; } diff --git a/src/budget/Types.ts b/src/budget/Types.ts index 694c61b..f348276 100644 --- a/src/budget/Types.ts +++ b/src/budget/Types.ts @@ -7,6 +7,7 @@ import { } from '../moneymoney'; import { isLeft } from 'fp-ts/lib/Either'; import { ThrowReporter } from 'io-ts/lib/ThrowReporter'; +import semver from 'semver'; /** * ## 0.0.1 -> 0.0.2 @@ -14,8 +15,11 @@ import { ThrowReporter } from 'io-ts/lib/ThrowReporter'; * * ## 0.0.2 -> 0.0.3 * removed setting.numberLocale in favor of always using system + * + * ## 0.0.3 -> 0.0.4 + * added setting.ignorePendingTransactions */ -export const VERSION = '0.0.3'; +export const VERSION = '0.0.4'; const categoryShape = t.partial({ amount: t.number, @@ -39,6 +43,7 @@ const incomeCategoryShape = t.type( const settingsShape = t.type( { fractionDigits: t.number, + ignorePendingTransactions: t.boolean, startDate: t.number, currency: t.string, startBalance: t.number, @@ -76,13 +81,17 @@ export function validateBudgetState(data: unknown): BudgetState { if (typeof data !== 'object' || data === null) { throw new Error('Invalid budget file format'); } - const version: unknown = (data as any).version; - if (!version || version === '0.0.1') { + const version: string = (data as any).version as string; + if (!version || !semver.valid(version) || !semver.satisfies(version, '>0.0.1')){ throw new Error( 'File format not supported. Please use an earlier version of BudgetBudget to open this file', ); } - + // Upgrade to 0.0.4 version format + if (semver.lt(version, '0.0.4')) { + (data as any).settings.ignorePendingTransactions = false; + (data as any).version = '0.0.4'; + } const c = budgetStateShape.decode(data); if (isLeft(c)) { throw ThrowReporter.report(c); diff --git a/src/budget/budgetReducer.ts b/src/budget/budgetReducer.ts index 5a46e27..1cee0a1 100644 --- a/src/budget/budgetReducer.ts +++ b/src/budget/budgetReducer.ts @@ -14,6 +14,9 @@ export const ACTION_SET_NAME = Symbol('SET_NAME'); export const ACTION_SETTINGS_SET_FRACTION_DIGITS = Symbol( 'ACTION_SETTINGS_SET_FRACTION_DIGITS', ); +export const ACTION_SETTINGS_SET_IGNORE_PENDING_TRANSACTIONS = Symbol( + 'ACTION_SETTINGS_SET_IGNORE_PENDING_TRANSACTIONS', +); export const ACTION_SETTINGS_SET_SELECTED_ACCOUNTS = Symbol( 'ACTION_SETTINGS_SET_SELECTED_ACCOUNTS', ); @@ -71,6 +74,10 @@ type SetSettingsFractionDigits = { type: typeof ACTION_SETTINGS_SET_FRACTION_DIGITS; payload: number; }; +type SetSettingsIgnorePendingTransactions = { + type: typeof ACTION_SETTINGS_SET_IGNORE_PENDING_TRANSACTIONS; + payload: boolean; +}; type SetSettingsSelectedAccounts = { type: typeof ACTION_SETTINGS_SET_SELECTED_ACCOUNTS; payload: string[]; @@ -125,6 +132,7 @@ export type Action = | SetCategoryRolloverAction | SetNameAction | SetSettingsFractionDigits + | SetSettingsIgnorePendingTransactions | SetSettingsSelectedAccounts | SetSettingsStartDate | SetSettingsStartBalance @@ -233,6 +241,14 @@ function budgetReducer(state: BudgetState, action: Action): BudgetState { fractionDigits: action.payload, }, }; + case ACTION_SETTINGS_SET_IGNORE_PENDING_TRANSACTIONS: + return { + ...state, + settings: { + ...state.settings, + ignorePendingTransactions: action.payload, + }, + }; case ACTION_SETTINGS_SET_SELECTED_ACCOUNTS: return { ...state, diff --git a/src/budget/useBudgets.ts b/src/budget/useBudgets.ts index 5c32aa7..242448d 100644 --- a/src/budget/useBudgets.ts +++ b/src/budget/useBudgets.ts @@ -15,7 +15,7 @@ export default function useBudgets( defaultCategories: Category[] = [], { budgets, - settings: { incomeCategories, fractionDigits, startBalance, startDate }, + settings: { incomeCategories, fractionDigits, startBalance, startDate, ignorePendingTransactions }, }: BudgetState, ): [MonthData[], (add: number) => void] { const defaultCategoryIds = useMemo( @@ -23,8 +23,8 @@ export default function useBudgets( [defaultCategories], ); const balances = useMemo( - () => calculateBalances(transactions, defaultCategoryIds), - [transactions, defaultCategoryIds], + () => calculateBalances(transactions, defaultCategoryIds, ignorePendingTransactions), + [transactions, defaultCategoryIds, ignorePendingTransactions], ); const getInitial = useMemo(() => { const initial: InterMonthData = { diff --git a/src/lib/initialSettings.ts b/src/lib/initialSettings.ts index 6c5fcf7..b2c11c7 100644 --- a/src/lib/initialSettings.ts +++ b/src/lib/initialSettings.ts @@ -8,6 +8,7 @@ const settings: BudgetState['settings'] = { currency: 'EUR', incomeCategories: [], fractionDigits: 2, + ignorePendingTransactions: false, startDate: startOfMonth(subMonths(getToday(), 1)).getTime(), startBalance: 0, }; diff --git a/src/moneymoney/calculateBalances.ts b/src/moneymoney/calculateBalances.ts index 1486f44..2075961 100644 --- a/src/moneymoney/calculateBalances.ts +++ b/src/moneymoney/calculateBalances.ts @@ -4,8 +4,12 @@ import { formatDateKey } from '../lib'; export default function calculateBalances( transactions: Transaction[], defaultCategoryIds: string[], + ignorePendingTransactions: boolean ): Balances { return transactions.reduce((memo, transaction) => { + if (!transaction.booked && ignorePendingTransactions) { + return memo; + } const key = formatDateKey(transaction.bookingDate); let balance = memo[key]; if (!balance) { diff --git a/src/views/Settings/General/General.tsx b/src/views/Settings/General/General.tsx index be9c24b..7e5763a 100644 --- a/src/views/Settings/General/General.tsx +++ b/src/views/Settings/General/General.tsx @@ -9,6 +9,7 @@ import CurrencySetting from './Currency'; import { Props } from './Types'; import { Loading } from '../../../components'; import { NumberFormatter } from '../../../lib/createNumberFormatter'; +import IgnorePendingTransactionsSetting from './IgnorePendingTransactions'; export default function Settings( props: Props & { numberFormatter: NumberFormatter }, @@ -26,6 +27,8 @@ export default function Settings(

+ +
diff --git a/src/views/Settings/General/IgnorePendingTransactions.tsx b/src/views/Settings/General/IgnorePendingTransactions.tsx new file mode 100644 index 0000000..51a4e1e --- /dev/null +++ b/src/views/Settings/General/IgnorePendingTransactions.tsx @@ -0,0 +1,35 @@ +import React, { useCallback } from 'react'; +import { ACTION_SETTINGS_SET_IGNORE_PENDING_TRANSACTIONS } from '../../../budget'; +import Setting from '../Setting'; +import { Props } from './Types'; + +export default function IgnorePendingTransactionsSetting({ + state: { + settings: { ignorePendingTransactions }, + }, + dispatch, +}: Props) { + const onChange = useCallback( + (ev: React.ChangeEvent) => { + dispatch({ + type: ACTION_SETTINGS_SET_IGNORE_PENDING_TRANSACTIONS, + payload: ev.target.checked, + }); + }, + [dispatch], + ); + + return ( + + + + ); +}