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