From 3daff4381f2e78fd97d54aa7f93c8bff3b354b4c Mon Sep 17 00:00:00 2001
From: DHRUV RAMDEV <pythonbrilliant@gmail.com>
Date: Sat, 3 Feb 2024 01:40:36 +0530
Subject: [PATCH] feat: Don't allow duplicate cat-groups in budget (#2262)

* feat: Don't allow duplicate cat-groups in budget

* Add release notes

* fix: error message

* pass group instead of name for accurate error message

* improve error message
---
 .../src/components/budget/index.tsx             | 17 +++++++++++++++++
 packages/loot-core/src/server/db/index.ts       | 11 +++++++++++
 upcoming-release-notes/2262.md                  |  6 ++++++
 3 files changed, 34 insertions(+)
 create mode 100644 upcoming-release-notes/2262.md

diff --git a/packages/desktop-client/src/components/budget/index.tsx b/packages/desktop-client/src/components/budget/index.tsx
index 3363140e6..f22090b1f 100644
--- a/packages/desktop-client/src/components/budget/index.tsx
+++ b/packages/desktop-client/src/components/budget/index.tsx
@@ -313,7 +313,24 @@ function BudgetInner(props: BudgetProps) {
     }
   };
 
+  const groupNameAlreadyExistsNotification = group => {
+    props.addNotification({
+      type: 'error',
+      message: `A ${group.hidden ? 'hidden ' : ''}’${group.name}’ category group already exists.`,
+    });
+  };
+
   const onSaveGroup = async group => {
+    const categories = await props.getCategories();
+    const matchingGroups = categories.grouped
+      .filter(g => g.name.toUpperCase() === group.name.toUpperCase())
+      .filter(g => group.id === 'new' || group.id !== g.id);
+
+    if (matchingGroups.length > 0) {
+      groupNameAlreadyExistsNotification(matchingGroups[0]);
+      return;
+    }
+
     if (group.id === 'new') {
       const id = await props.createGroup(group.name);
       setIsAddingGroup(false);
diff --git a/packages/loot-core/src/server/db/index.ts b/packages/loot-core/src/server/db/index.ts
index 3a359f065..65cae417c 100644
--- a/packages/loot-core/src/server/db/index.ts
+++ b/packages/loot-core/src/server/db/index.ts
@@ -304,6 +304,17 @@ export async function getCategoriesGrouped(): Promise<
 }
 
 export async function insertCategoryGroup(group) {
+  // Don't allow duplicate group
+  const existingGroup = await first(
+    `SELECT id, name, hidden FROM category_groups WHERE UPPER(name) = ? and tombstone = 0 LIMIT 1`,
+    [group.name.toUpperCase()],
+  );
+  if (existingGroup) {
+    throw new Error(
+      `A ${existingGroup.hidden ? 'hidden ' : ''}’${existingGroup.name}’ category group already exists.`,
+    );
+  }
+
   const lastGroup = await first(`
     SELECT sort_order FROM category_groups WHERE tombstone = 0 ORDER BY sort_order DESC, id DESC LIMIT 1
   `);
diff --git a/upcoming-release-notes/2262.md b/upcoming-release-notes/2262.md
new file mode 100644
index 000000000..e9bdf2490
--- /dev/null
+++ b/upcoming-release-notes/2262.md
@@ -0,0 +1,6 @@
+---
+category: Features
+authors: [dhruvramdev]
+---
+
+Don't allow duplicate category groups
\ No newline at end of file
-- 
GitLab