From 717de22f583dbeb79f3ad5c8d7b8fe9d8a1701d5 Mon Sep 17 00:00:00 2001 From: shall0pass <20625555+shall0pass@users.noreply.github.com> Date: Wed, 19 Jul 2023 08:12:41 -0500 Subject: [PATCH] Goals: Implement goals in Report Budget (#1329) --- .../budget/report/BudgetSummary.tsx | 22 +++-- .../loot-core/src/server/budget/actions.ts | 2 +- .../src/server/budget/goaltemplates.ts | 81 +++++++++++-------- upcoming-release-notes/1329.md | 6 ++ 4 files changed, 70 insertions(+), 41 deletions(-) create mode 100644 upcoming-release-notes/1329.md diff --git a/packages/desktop-client/src/components/budget/report/BudgetSummary.tsx b/packages/desktop-client/src/components/budget/report/BudgetSummary.tsx index d99386bfa..8c7f140e0 100644 --- a/packages/desktop-client/src/components/budget/report/BudgetSummary.tsx +++ b/packages/desktop-client/src/components/budget/report/BudgetSummary.tsx @@ -1,7 +1,6 @@ import React, { type ComponentProps, type ComponentType, - memo, type ReactNode, useState, type SVGProps, @@ -12,6 +11,7 @@ import { css, type CSSProperties } from 'glamor'; import { reportBudget } from 'loot-core/src/client/queries'; import * as monthUtils from 'loot-core/src/shared/months'; +import useFeatureFlag from '../../../hooks/useFeatureFlag'; import DotsHorizontalTriple from '../../../icons/v1/DotsHorizontalTriple'; import ArrowButtonDown1 from '../../../icons/v2/ArrowButtonDown1'; import ArrowButtonUp1 from '../../../icons/v2/ArrowButtonUp1'; @@ -294,9 +294,7 @@ function Saved({ projected, style }: SavedProps) { type BudgetSummaryProps = { month?: string; }; -export const BudgetSummary = memo(function BudgetSummary({ - month, -}: BudgetSummaryProps) { +export function BudgetSummary({ month }: BudgetSummaryProps) { let { currentMonth, summaryCollapsed: collapsed, @@ -304,6 +302,8 @@ export const BudgetSummary = memo(function BudgetSummary({ onToggleSummaryCollapse, } = useReport(); + const isGoalTemplatesEnabled = useFeatureFlag('goalTemplatesEnabled'); + let [menuOpen, setMenuOpen] = useState(false); function onMenuOpen() { setMenuOpen(true); @@ -425,6 +425,18 @@ export const BudgetSummary = memo(function BudgetSummary({ name: 'set-3-avg', text: 'Set budgets to 3 month avg', }, + isGoalTemplatesEnabled && { + name: 'check-templates', + text: 'Check templates', + }, + isGoalTemplatesEnabled && { + name: 'apply-goal-template', + text: 'Apply budget template', + }, + isGoalTemplatesEnabled && { + name: 'overwrite-goal-template', + text: 'Overwrite with budget template', + }, ]} /> </Tooltip> @@ -470,4 +482,4 @@ export const BudgetSummary = memo(function BudgetSummary({ </NamespaceContext.Provider> </View> ); -}); +} diff --git a/packages/loot-core/src/server/budget/actions.ts b/packages/loot-core/src/server/budget/actions.ts index 2b0c527e4..23e68fee3 100644 --- a/packages/loot-core/src/server/budget/actions.ts +++ b/packages/loot-core/src/server/budget/actions.ts @@ -24,7 +24,7 @@ function getBudgetTable() { return budgetType === 'report' ? 'reflect_budgets' : 'zero_budgets'; } -function isReflectBudget() { +export function isReflectBudget() { let { budgetType } = prefs.getPrefs(); return budgetType === 'report'; } diff --git a/packages/loot-core/src/server/budget/goaltemplates.ts b/packages/loot-core/src/server/budget/goaltemplates.ts index 2be354437..cdfe1b48c 100644 --- a/packages/loot-core/src/server/budget/goaltemplates.ts +++ b/packages/loot-core/src/server/budget/goaltemplates.ts @@ -8,7 +8,7 @@ import { amountToInteger, integerToAmount } from '../../shared/util'; import * as db from '../db'; import { getRuleForSchedule, getNextDate } from '../schedules/app'; -import { setBudget, setZero, getSheetValue } from './actions'; +import { setBudget, setZero, getSheetValue, isReflectBudget } from './actions'; import { parse } from './goal-template.pegjs'; export function applyTemplate({ month }) { @@ -324,12 +324,13 @@ async function applyCategoryTemplate( } }); } - let sheetName = monthUtils.sheetForMonth(month); let budgeted = await getSheetValue(sheetName, `budget-${category.id}`); let spent = await getSheetValue(sheetName, `sum-amount-${category.id}`); let balance = await getSheetValue(sheetName, `leftover-${category.id}`); - let budgetAvailable = await getSheetValue(sheetName, `to-budget`); + let budgetAvailable = isReflectBudget() + ? await getSheetValue(sheetName, `total-saved`) + : await getSheetValue(sheetName, `to-budget`); let to_budget = budgeted; let limit; let hold; @@ -366,40 +367,45 @@ async function applyCategoryTemplate( } case 'by': { // by has 'amount' and 'month' params - let target = 0; - let target_month = `${template_lines[l].month}-01`; - let num_months = monthUtils.differenceInCalendarMonths( - target_month, - current_month, - ); - let repeat = - template.type === 'by' - ? template.repeat - : (template.repeat || 1) * 12; - while (num_months < 0 && repeat) { - target_month = monthUtils.addMonths(target_month, repeat); - num_months = monthUtils.differenceInCalendarMonths( - template_lines[l].month, + if (!isReflectBudget()) { + let target = 0; + let target_month = `${template_lines[l].month}-01`; + let num_months = monthUtils.differenceInCalendarMonths( + target_month, current_month, ); - } - if (l === 0) remainder = last_month_balance; - remainder = amountToInteger(template_lines[l].amount) - remainder; - if (remainder >= 0) { - target = remainder; - remainder = 0; - } else { - target = 0; - remainder = Math.abs(remainder); - } - let diff = num_months >= 0 ? Math.round(target / (num_months + 1)) : 0; - if (diff >= 0) { - if (to_budget + diff < budgetAvailable || !priority) { - to_budget += diff; + let repeat = + template.type === 'by' + ? template.repeat + : (template.repeat || 1) * 12; + while (num_months < 0 && repeat) { + target_month = monthUtils.addMonths(target_month, repeat); + num_months = monthUtils.differenceInCalendarMonths( + template_lines[l].month, + current_month, + ); + } + if (l === 0) remainder = last_month_balance; + remainder = amountToInteger(template_lines[l].amount) - remainder; + if (remainder >= 0) { + target = remainder; + remainder = 0; } else { - if (budgetAvailable > 0) to_budget += budgetAvailable; - errors.push(`Insufficient funds.`); + target = 0; + remainder = Math.abs(remainder); + } + let diff = + num_months >= 0 ? Math.round(target / (num_months + 1)) : 0; + if (diff >= 0) { + if (to_budget + diff < budgetAvailable || !priority) { + to_budget += diff; + } else { + if (budgetAvailable > 0) to_budget += budgetAvailable; + errors.push(`Insufficient funds.`); + } } + } else { + errors.push(`by templates are not supported in Report budgets`); } break; } @@ -563,10 +569,15 @@ async function applyCategoryTemplate( amountCond.value = monthlyTarget; } - if (template.full === true) { - if (num_months === 1) { + if (template.full === true || isReflectBudget()) { + if (num_months === 0) { to_budget = -getScheduledAmount(amountCond.value); } + if (isReflectBudget() && !template.full) { + errors.push( + `Report budgets require the full option for Schedules.`, + ); + } break; } diff --git a/upcoming-release-notes/1329.md b/upcoming-release-notes/1329.md new file mode 100644 index 000000000..3b3789021 --- /dev/null +++ b/upcoming-release-notes/1329.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [shall0pass] +--- + +Goals: Enable goal templates in Report Budget \ No newline at end of file -- GitLab