From aff5bba4ec2d528501e49cd48bf6cdb8372e114f Mon Sep 17 00:00:00 2001
From: Julian Dominguez-Schatz <julian.dominguezschatz@gmail.com>
Date: Tue, 21 May 2024 10:29:39 -0700
Subject: [PATCH] Template only the relevant amount in a split-schedule
 category (#2652)

* Template only the relevant amount in a split-schedule category

* Add release notes

* Adjust sign correctly depending on category type
---
 .../loot-core/src/server/accounts/rules.ts    |  5 ++--
 .../src/server/budget/goals/goalsSchedule.ts  | 27 ++++++++++++++-----
 .../loot-core/src/server/schedules/app.ts     |  2 +-
 upcoming-release-notes/2652.md                |  6 +++++
 4 files changed, 29 insertions(+), 11 deletions(-)
 create mode 100644 upcoming-release-notes/2652.md

diff --git a/packages/loot-core/src/server/accounts/rules.ts b/packages/loot-core/src/server/accounts/rules.ts
index ab7ccd11d..96e9cd1d8 100644
--- a/packages/loot-core/src/server/accounts/rules.ts
+++ b/packages/loot-core/src/server/accounts/rules.ts
@@ -673,17 +673,16 @@ export class Rule {
     });
   }
 
-  execActions(object) {
+  execActions<T>(object: T): Partial<T> {
     const result = execActions(this.actions, {
       ...object,
-      subtransactions: object.subtransactions,
     });
     const changes = Object.keys(result).reduce((prev, cur) => {
       if (result[cur] !== object[cur]) {
         prev[cur] = result[cur];
       }
       return prev;
-    }, {});
+    }, {} as T);
     return changes;
   }
 
diff --git a/packages/loot-core/src/server/budget/goals/goalsSchedule.ts b/packages/loot-core/src/server/budget/goals/goalsSchedule.ts
index 6efdd323c..8d9045325 100644
--- a/packages/loot-core/src/server/budget/goals/goalsSchedule.ts
+++ b/packages/loot-core/src/server/budget/goals/goalsSchedule.ts
@@ -22,15 +22,28 @@ async function createScheduleList(template, current_month, category) {
     const conditions = rule.serialize().conditions;
     const { date: dateConditions, amount: amountCondition } =
       extractScheduleConds(conditions);
-    const sign = category.is_income ? 1 : -1;
-    const target =
+    const scheduleAmount =
       amountCondition.op === 'isbetween'
-        ? (sign *
-            Math.round(
-              amountCondition.value.num1 + amountCondition.value.num2,
-            )) /
+        ? Math.round(amountCondition.value.num1 + amountCondition.value.num2) /
           2
-        : sign * amountCondition.value;
+        : amountCondition.value;
+    const { amount: postRuleAmount, subtransactions } = rule.execActions({
+      amount: scheduleAmount,
+      category: category.id,
+      subtransactions: [],
+    });
+    const categorySubtransactions = subtransactions?.filter(
+      t => t.category === category.id,
+    );
+
+    // Unless the current category is relevant to the schedule, target the post-rule amount.
+    const sign = category.is_income ? 1 : -1;
+    const target =
+      sign *
+      (categorySubtransactions?.length
+        ? categorySubtransactions.reduce((acc, t) => acc + t.amount, 0)
+        : postRuleAmount ?? scheduleAmount);
+
     const next_date_string = getNextDate(
       dateConditions,
       monthUtils._parse(current_month),
diff --git a/packages/loot-core/src/server/schedules/app.ts b/packages/loot-core/src/server/schedules/app.ts
index 1bd4c0eb0..ea99443d4 100644
--- a/packages/loot-core/src/server/schedules/app.ts
+++ b/packages/loot-core/src/server/schedules/app.ts
@@ -107,7 +107,7 @@ export function getNextDate(
   return null;
 }
 
-export async function getRuleForSchedule(id) {
+export async function getRuleForSchedule(id: string | null): Promise<Rule> {
   if (id == null) {
     throw new Error('Schedule not attached to a rule');
   }
diff --git a/upcoming-release-notes/2652.md b/upcoming-release-notes/2652.md
new file mode 100644
index 000000000..3196e7ddd
--- /dev/null
+++ b/upcoming-release-notes/2652.md
@@ -0,0 +1,6 @@
+---
+category: Enhancements
+authors: [jfdoming]
+---
+
+Template only the relevant amount in a split-schedule category
-- 
GitLab