From d0caf9f521edc7709966ba6d0c3f6fc46c19fde9 Mon Sep 17 00:00:00 2001 From: Austin Pearce Date: Thu, 3 Oct 2024 14:49:21 -0400 Subject: [PATCH] fix yearly schedule templates (#3511) Co-authored-by: Austin Pearce --- .../server/budget/goals/goalsSchedule.test.ts | 64 +++++++++++++++++++ .../src/server/budget/goals/goalsSchedule.ts | 6 +- upcoming-release-notes/3511.md | 6 ++ 3 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 upcoming-release-notes/3511.md diff --git a/packages/loot-core/src/server/budget/goals/goalsSchedule.test.ts b/packages/loot-core/src/server/budget/goals/goalsSchedule.test.ts index abfa9c035d8..29eefba3895 100644 --- a/packages/loot-core/src/server/budget/goals/goalsSchedule.test.ts +++ b/packages/loot-core/src/server/budget/goals/goalsSchedule.test.ts @@ -82,4 +82,68 @@ describe('goalsSchedule', () => { expect(result.remainder).toBe(0); expect(result.scheduleFlag).toBe(true); }); + + it('should return correct budget when yearly recurring schedule set and balance is greater than target', async () => { + // Given + const scheduleFlag = false; + const template_lines = [{ type: 'schedule', name: 'Test Schedule' }]; + const current_month = '2024-09-01'; + const balance = 12000; + const remainder = 0; + const last_month_balance = 12000; + const to_budget = 0; + const errors: string[] = []; + const category = { id: 1, name: 'Test Category' }; + + (db.first as jest.Mock).mockResolvedValue({ id: 1, completed: 0 }); + (getRuleForSchedule as jest.Mock).mockResolvedValue({ + serialize: () => ({ + conditions: [ + { + op: 'is', + field: 'date', + value: { + start: '2024-08-01', + interval: 1, + frequency: 'yearly', + patterns: [], + skipWeekend: false, + weekendSolveMode: 'before', + endMode: 'never', + endOccurrences: 1, + endDate: '2024-08-04', + }, + type: 'date', + }, + { + op: 'is', + field: 'amount', + value: -12000, + type: 'number', + }, + ], + }), + execActions: () => ({ amount: -12000, subtransactions: [] }), + }); + (isReflectBudget as jest.Mock).mockReturnValue(false); + + // When + const result = await goalsSchedule( + scheduleFlag, + template_lines, + current_month, + balance, + remainder, + last_month_balance, + to_budget, + errors, + category, + ); + + // Then + expect(result.to_budget).toBe(1000); + expect(result.errors).toHaveLength(0); + expect(result.remainder).toBe(0); + expect(result.scheduleFlag).toBe(true); + }); }); diff --git a/packages/loot-core/src/server/budget/goals/goalsSchedule.ts b/packages/loot-core/src/server/budget/goals/goalsSchedule.ts index 8b2162845cc..6abeec39b94 100644 --- a/packages/loot-core/src/server/budget/goals/goalsSchedule.ts +++ b/packages/loot-core/src/server/budget/goals/goalsSchedule.ts @@ -166,7 +166,11 @@ async function getSinkingBaseContributionTotal(t) { //return only the base contribution of each schedule let total = 0; for (let ll = 0; ll < t.length; ll++) { - total += t[ll].target / t[ll].target_interval; + let monthlyAmount = t[ll].target / t[ll].target_interval; + if (t[ll].target_frequency === 'yearly') { + monthlyAmount /= 12; + } + total += monthlyAmount; } return total; } diff --git a/upcoming-release-notes/3511.md b/upcoming-release-notes/3511.md new file mode 100644 index 00000000000..9cdd896d030 --- /dev/null +++ b/upcoming-release-notes/3511.md @@ -0,0 +1,6 @@ +--- +category: Bugfix +authors: [JukeboxRhino] +--- + +Fix yearly schedule templates not behaving correctly when budgeting ahead of the transaction date