From 08aff05a06f4bca4344f939324080e1fa6511b62 Mon Sep 17 00:00:00 2001
From: Robert Dyer <rdyer@unl.edu>
Date: Thu, 30 May 2024 14:05:52 -0500
Subject: [PATCH] Initial support for linking to off-budget accounts (#2788)

* initial support for linking to off budget

* add release note
---
 .../modals/SelectLinkedAccounts.jsx           | 38 +++++++++++++------
 .../loot-core/src/client/actions/account.ts   |  6 ++-
 packages/loot-core/src/server/main.ts         |  4 ++
 .../loot-core/src/types/server-handlers.d.ts  |  2 +
 upcoming-release-notes/2788.md                |  6 +++
 5 files changed, 42 insertions(+), 14 deletions(-)
 create mode 100644 upcoming-release-notes/2788.md

diff --git a/packages/desktop-client/src/components/modals/SelectLinkedAccounts.jsx b/packages/desktop-client/src/components/modals/SelectLinkedAccounts.jsx
index dc959f3dc..d722c23dd 100644
--- a/packages/desktop-client/src/components/modals/SelectLinkedAccounts.jsx
+++ b/packages/desktop-client/src/components/modals/SelectLinkedAccounts.jsx
@@ -10,7 +10,11 @@ import { View } from '../common/View';
 import { PrivacyFilter } from '../PrivacyFilter';
 import { TableHeader, Table, Row, Field } from '../table';
 
-const addAccountOption = { id: 'new', name: 'Create new account' };
+const addOnBudgetAccountOption = { id: 'new-on', name: 'Create new account' };
+const addOffBudgetAccountOption = {
+  id: 'new-off',
+  name: 'Create new account (off-budget)',
+};
 
 export function SelectLinkedAccounts({
   modalProps,
@@ -45,6 +49,7 @@ export function SelectLinkedAccounts({
         const externalAccount = externalAccounts.find(
           account => account.account_id === chosenExternalAccountId,
         );
+        const offBudget = chosenLocalAccountId === addOffBudgetAccountOption.id;
 
         // Skip linking accounts that were previously linked with
         // a different bank.
@@ -56,17 +61,21 @@ export function SelectLinkedAccounts({
         if (syncSource === 'simpleFin') {
           actions.linkAccountSimpleFin(
             externalAccount,
-            chosenLocalAccountId !== addAccountOption.id
+            chosenLocalAccountId !== addOnBudgetAccountOption.id &&
+              chosenLocalAccountId !== addOffBudgetAccountOption.id
               ? chosenLocalAccountId
               : undefined,
+            offBudget,
           );
         } else {
           actions.linkAccount(
             requisitionId,
             externalAccount,
-            chosenLocalAccountId !== addAccountOption.id
+            chosenLocalAccountId !== addOnBudgetAccountOption.id &&
+              chosenLocalAccountId !== addOffBudgetAccountOption.id
               ? chosenLocalAccountId
               : undefined,
+            offBudget,
           );
         }
       },
@@ -125,11 +134,15 @@ export function SelectLinkedAccounts({
                   <TableRow
                     externalAccount={item}
                     chosenAccount={
-                      chosenAccounts[item.account_id] === addAccountOption.id
-                        ? addAccountOption
-                        : localAccounts.find(
-                            acc => chosenAccounts[item.account_id] === acc.id,
-                          )
+                      chosenAccounts[item.account_id] ===
+                      addOnBudgetAccountOption.id
+                        ? addOnBudgetAccountOption
+                        : chosenAccounts[item.account_id] ===
+                            addOffBudgetAccountOption.id
+                          ? addOffBudgetAccountOption
+                          : localAccounts.find(
+                              acc => chosenAccounts[item.account_id] === acc.id,
+                            )
                     }
                     unlinkedAccounts={unlinkedAccounts}
                     onSetLinkedAccount={onSetLinkedAccount}
@@ -170,8 +183,9 @@ function TableRow({
 
   const availableAccountOptions = [
     ...unlinkedAccounts,
-    chosenAccount?.id !== addAccountOption.id && chosenAccount,
-    addAccountOption,
+    chosenAccount?.id !== addOnBudgetAccountOption.id && chosenAccount,
+    addOnBudgetAccountOption,
+    addOffBudgetAccountOption,
   ].filter(Boolean);
 
   return (
@@ -181,7 +195,7 @@ function TableRow({
         <PrivacyFilter>{externalAccount.balance}</PrivacyFilter>
       </Field>
       <Field
-        width="flex"
+        width="40%"
         truncate={focusedField !== 'account'}
         onClick={() => setFocusedField('account')}
       >
@@ -203,7 +217,7 @@ function TableRow({
           chosenAccount?.name
         )}
       </Field>
-      <Field width="flex">
+      <Field width="20%">
         {chosenAccount ? (
           <Button
             onClick={() => {
diff --git a/packages/loot-core/src/client/actions/account.ts b/packages/loot-core/src/client/actions/account.ts
index c0d89874a..6eafe4edb 100644
--- a/packages/loot-core/src/client/actions/account.ts
+++ b/packages/loot-core/src/client/actions/account.ts
@@ -55,23 +55,25 @@ export function unlinkAccount(id: string) {
   };
 }
 
-export function linkAccount(requisitionId, account, upgradingId) {
+export function linkAccount(requisitionId, account, upgradingId, offBudget) {
   return async (dispatch: Dispatch) => {
     await send('gocardless-accounts-link', {
       requisitionId,
       account,
       upgradingId,
+      offBudget,
     });
     await dispatch(getPayees());
     await dispatch(getAccounts());
   };
 }
 
-export function linkAccountSimpleFin(externalAccount, upgradingId) {
+export function linkAccountSimpleFin(externalAccount, upgradingId, offBudget) {
   return async (dispatch: Dispatch) => {
     await send('simplefin-accounts-link', {
       externalAccount,
       upgradingId,
+      offBudget,
     });
     await dispatch(getPayees());
     await dispatch(getAccounts());
diff --git a/packages/loot-core/src/server/main.ts b/packages/loot-core/src/server/main.ts
index 33d30933c..ea4a001b9 100644
--- a/packages/loot-core/src/server/main.ts
+++ b/packages/loot-core/src/server/main.ts
@@ -596,6 +596,7 @@ handlers['gocardless-accounts-link'] = async function ({
   requisitionId,
   account,
   upgradingId,
+  offBudget,
 }) {
   let id;
   const bank = await link.findOrCreateBank(account.institution, requisitionId);
@@ -620,6 +621,7 @@ handlers['gocardless-accounts-link'] = async function ({
       name: account.name,
       official_name: account.official_name,
       bank: bank.id,
+      offbudget: offBudget ? 1 : 0,
       account_sync_source: 'goCardless',
     });
     await db.insertPayee({
@@ -647,6 +649,7 @@ handlers['gocardless-accounts-link'] = async function ({
 handlers['simplefin-accounts-link'] = async function ({
   externalAccount,
   upgradingId,
+  offBudget,
 }) {
   let id;
 
@@ -678,6 +681,7 @@ handlers['simplefin-accounts-link'] = async function ({
       name: externalAccount.name,
       official_name: externalAccount.name,
       bank: bank.id,
+      offbudget: offBudget ? 1 : 0,
       account_sync_source: 'simpleFin',
     });
     await db.insertPayee({
diff --git a/packages/loot-core/src/types/server-handlers.d.ts b/packages/loot-core/src/types/server-handlers.d.ts
index 8a643803b..0cf51bc85 100644
--- a/packages/loot-core/src/types/server-handlers.d.ts
+++ b/packages/loot-core/src/types/server-handlers.d.ts
@@ -152,11 +152,13 @@ export interface ServerHandlers {
     requisitionId;
     account;
     upgradingId;
+    offBudget;
   }) => Promise<'ok'>;
 
   'simplefin-accounts-link': (arg: {
     externalAccount;
     upgradingId;
+    offBudget;
   }) => Promise<'ok'>;
 
   'account-create': (arg: {
diff --git a/upcoming-release-notes/2788.md b/upcoming-release-notes/2788.md
new file mode 100644
index 000000000..7bc6f22bd
--- /dev/null
+++ b/upcoming-release-notes/2788.md
@@ -0,0 +1,6 @@
+---
+category: Bugfix
+authors: [psybers]
+---
+
+Allow creating a new off-budget account in bank sync modal.
-- 
GitLab