From fec2126d77ebb59aa4187df7a4407431f9cde5ec Mon Sep 17 00:00:00 2001
From: Robert Goldmann <deadlocker@gmx.de>
Date: Mon, 7 Apr 2025 23:07:14 +0200
Subject: [PATCH] #774 - account service: add method to get accounts filtered
 by certain criteria

---
 BudgetMasterServer/pom.xml                    |   6 +-
 .../budgetmaster/accounts/AccountService.java |  11 +
 .../accounts/AccountsFilterConfiguration.java |  56 +++++
 .../budgetmaster/unit/AccountServiceTest.java | 195 ++++++++++++++++--
 4 files changed, 249 insertions(+), 19 deletions(-)
 create mode 100644 BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/accounts/AccountsFilterConfiguration.java

diff --git a/BudgetMasterServer/pom.xml b/BudgetMasterServer/pom.xml
index 2fe1f8404..47ee78434 100644
--- a/BudgetMasterServer/pom.xml
+++ b/BudgetMasterServer/pom.xml
@@ -192,7 +192,11 @@
             <artifactId>datatables</artifactId>
             <version>${datatables.version}</version>
         </dependency>
-
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <scope>provided</scope>
+        </dependency>
 
         <!-- selenium -->
         <dependency>
diff --git a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/accounts/AccountService.java b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/accounts/AccountService.java
index fd4f3d1ce..f92a2b6b2 100644
--- a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/accounts/AccountService.java
+++ b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/accounts/AccountService.java
@@ -79,6 +79,17 @@ public class AccountService implements Resettable, AccessAllEntities<Account>, A
 		return accounts;
 	}
 
+	public List<Account> getFilteredEntitiesAsc(AccountsFilterConfiguration accountsFilterConfiguration)
+	{
+		return accountRepository.findAllByTypeOrderByNameAsc(AccountType.CUSTOM).stream()
+				.filter(a -> accountsFilterConfiguration.getIncludedStates().contains(a.getAccountState()))
+				.filter(a -> (accountsFilterConfiguration.isIncludeWithEndDate() && a.getEndDate() != null) || (accountsFilterConfiguration.isIncludeWithoutEndDate() && a.getEndDate() == null))
+				.filter(a -> (!accountsFilterConfiguration.hasName() || (accountsFilterConfiguration.hasName() && a.getName().toLowerCase().contains(accountsFilterConfiguration.getName().toLowerCase()))))
+				.filter(a -> (!accountsFilterConfiguration.hasDescription() || (accountsFilterConfiguration.hasDescription() && a.getDescription().toLowerCase().contains(accountsFilterConfiguration.getDescription().toLowerCase()))))
+				.sorted((a1, a2) -> new NaturalOrderComparator().compare(a1.getName(), a2.getName()))
+				.toList();
+	}
+
 	@Override
 	public Optional<Account> findById(Integer ID)
 	{
diff --git a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/accounts/AccountsFilterConfiguration.java b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/accounts/AccountsFilterConfiguration.java
new file mode 100644
index 000000000..774438a70
--- /dev/null
+++ b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/accounts/AccountsFilterConfiguration.java
@@ -0,0 +1,56 @@
+package de.deadlocker8.budgetmaster.accounts;
+
+import lombok.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Getter
+@Setter
+@ToString
+@AllArgsConstructor
+@NoArgsConstructor
+@EqualsAndHashCode
+public class AccountsFilterConfiguration
+{
+	private boolean includeFullAccess;
+	private boolean includeReadOnly;
+	private boolean includeHidden;
+	private boolean includeWithEndDate;
+	private boolean includeWithoutEndDate;
+	private String name;
+	private String description;
+
+	public static final AccountsFilterConfiguration DEFAULT = new AccountsFilterConfiguration(true, true, false, true, true, "", "");
+
+	public List<AccountState> getIncludedStates()
+	{
+		final List<AccountState> includedStates = new ArrayList<>();
+		if(includeFullAccess)
+		{
+			includedStates.add(AccountState.FULL_ACCESS);
+		}
+
+		if(includeReadOnly)
+		{
+			includedStates.add(AccountState.READ_ONLY);
+		}
+
+		if(includeHidden)
+		{
+			includedStates.add(AccountState.HIDDEN);
+		}
+
+		return includedStates;
+	}
+
+	public boolean hasName()
+	{
+		return name != null && !name.isEmpty();
+	}
+
+	public boolean hasDescription()
+	{
+		return description != null && !description.isEmpty();
+	}
+}
\ No newline at end of file
diff --git a/BudgetMasterServer/src/test/java/de/deadlocker8/budgetmaster/unit/AccountServiceTest.java b/BudgetMasterServer/src/test/java/de/deadlocker8/budgetmaster/unit/AccountServiceTest.java
index 2547e7988..d7f9bd4ba 100644
--- a/BudgetMasterServer/src/test/java/de/deadlocker8/budgetmaster/unit/AccountServiceTest.java
+++ b/BudgetMasterServer/src/test/java/de/deadlocker8/budgetmaster/unit/AccountServiceTest.java
@@ -17,6 +17,7 @@ import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.springframework.test.context.junit.jupiter.SpringExtension;
 
+import java.time.LocalDate;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
@@ -50,6 +51,8 @@ class AccountServiceTest
 	private Account ACCOUNT_DEFAULT;
 	private Account ACCOUNT_PLACEHOLDER;
 	private Account ACCOUNT_NORMAL;
+	private Account ACCOUNT_NORMAL_2;
+	private Account ACCOUNT_NORMAL_3;
 	private Account ACCOUNT_READONLY;
 	private Account ACCOUNT_HIDDEN;
 
@@ -66,6 +69,9 @@ class AccountServiceTest
 		ACCOUNT_NORMAL = new Account("Normal account", "awesome description", AccountType.CUSTOM, null);
 		ACCOUNT_NORMAL.setID(3);
 
+		ACCOUNT_NORMAL_2 = new Account("normal account", "", AccountType.CUSTOM, LocalDate.of(2025, 4, 7));
+		ACCOUNT_NORMAL_3 = new Account("123 account", "", AccountType.CUSTOM, null);
+
 		ACCOUNT_READONLY = new Account("Readonly account", "", AccountType.CUSTOM, null);
 		ACCOUNT_READONLY.setAccountState(AccountState.READ_ONLY);
 
@@ -96,17 +102,14 @@ class AccountServiceTest
 		accounts.add(ACCOUNT_NORMAL);
 		accounts.add(ACCOUNT_READONLY);
 		accounts.add(ACCOUNT_HIDDEN);
-
-		final Account accountNormal2 = new Account("normal account", "", AccountType.CUSTOM, null);
-		accounts.add(accountNormal2);
-		final Account accountNormal3 = new Account("123 account", "", AccountType.CUSTOM, null);
-		accounts.add(accountNormal3);
+		accounts.add(ACCOUNT_NORMAL_2);
+		accounts.add(ACCOUNT_NORMAL_3);
 
 		Mockito.when(accountRepository.findAllByType(AccountType.ALL)).thenReturn(List.of(ACCOUNT_PLACEHOLDER));
 		Mockito.when(accountRepository.findAllByTypeOrderByNameAsc(AccountType.CUSTOM)).thenReturn(accounts);
 
 		assertThat(accountService.getAllEntitiesAsc()).hasSize(6)
-				.containsExactly(ACCOUNT_PLACEHOLDER, accountNormal3, ACCOUNT_HIDDEN, ACCOUNT_NORMAL, accountNormal2, ACCOUNT_READONLY);
+				.containsExactly(ACCOUNT_PLACEHOLDER, ACCOUNT_NORMAL_3, ACCOUNT_HIDDEN, ACCOUNT_NORMAL, ACCOUNT_NORMAL_2, ACCOUNT_READONLY);
 	}
 
 	@Test
@@ -114,17 +117,14 @@ class AccountServiceTest
 	{
 		final List<Account> accounts = new ArrayList<>();
 		accounts.add(ACCOUNT_NORMAL);
-
-		final Account accountNormal2 = new Account("normal account", "", AccountType.CUSTOM, null);
-		accounts.add(accountNormal2);
-		final Account accountNormal3 = new Account("123 account", "", AccountType.CUSTOM, null);
-		accounts.add(accountNormal3);
+		accounts.add(ACCOUNT_NORMAL_2);
+		accounts.add(ACCOUNT_NORMAL_3);
 
 		Mockito.when(accountRepository.findAllByType(AccountType.ALL)).thenReturn(List.of(ACCOUNT_PLACEHOLDER));
 		Mockito.when(accountRepository.findAllByTypeAndAccountStateOrderByNameAsc(AccountType.CUSTOM, AccountState.FULL_ACCESS)).thenReturn(accounts);
 
 		assertThat(accountService.getAllActivatedAccountsAsc()).hasSize(4)
-				.containsExactly(ACCOUNT_PLACEHOLDER, accountNormal3, ACCOUNT_NORMAL, accountNormal2);
+				.containsExactly(ACCOUNT_PLACEHOLDER, ACCOUNT_NORMAL_3, ACCOUNT_NORMAL, ACCOUNT_NORMAL_2);
 	}
 
 	@Test
@@ -132,18 +132,15 @@ class AccountServiceTest
 	{
 		final List<Account> accounts = new ArrayList<>();
 		accounts.add(ACCOUNT_NORMAL);
-
-		final Account accountNormal2 = new Account("normal account", "", AccountType.CUSTOM, null);
-		accounts.add(accountNormal2);
-		final Account accountNormal3 = new Account("123 account", "", AccountType.CUSTOM, null);
-		accounts.add(accountNormal3);
+		accounts.add(ACCOUNT_NORMAL_2);
+		accounts.add(ACCOUNT_NORMAL_3);
 
 		Mockito.when(accountRepository.findAllByType(AccountType.ALL)).thenReturn(List.of(ACCOUNT_PLACEHOLDER));
 		Mockito.when(accountRepository.findAllByTypeAndAccountStateOrderByNameAsc(AccountType.CUSTOM, AccountState.FULL_ACCESS)).thenReturn(accounts);
 		Mockito.when(accountRepository.findAllByTypeAndAccountStateOrderByNameAsc(AccountType.CUSTOM, AccountState.READ_ONLY)).thenReturn(List.of(ACCOUNT_READONLY));
 
 		assertThat(accountService.getAllReadableAccounts()).hasSize(5)
-				.containsExactly(ACCOUNT_PLACEHOLDER, accountNormal3, ACCOUNT_NORMAL, accountNormal2, ACCOUNT_READONLY);
+				.containsExactly(ACCOUNT_PLACEHOLDER, ACCOUNT_NORMAL_3, ACCOUNT_NORMAL, ACCOUNT_NORMAL_2, ACCOUNT_READONLY);
 	}
 
 	@Test
@@ -326,4 +323,166 @@ class AccountServiceTest
 
 		assertThat(accountService.getSelectedAccountOrDefaultAsFallback()).isEqualTo(ACCOUNT_DEFAULT);
 	}
+
+	@Test
+	void test_getFilteredEntitiesAsc_noAccounts()
+	{
+		Mockito.when(accountRepository.findAllByTypeOrderByNameAsc(AccountType.CUSTOM)).thenReturn(List.of());
+
+		final AccountsFilterConfiguration filterConfiguration = new AccountsFilterConfiguration(true, true, true, true, true, "", "");
+		assertThat(accountService.getFilteredEntitiesAsc(filterConfiguration)).isEmpty();
+	}
+
+	@Test
+	void test_getFilteredEntitiesAsc_noFilter()
+	{
+		final List<Account> accounts = new ArrayList<>();
+		accounts.add(ACCOUNT_NORMAL);
+		accounts.add(ACCOUNT_READONLY);
+		accounts.add(ACCOUNT_HIDDEN);
+		accounts.add(ACCOUNT_NORMAL_2);
+		accounts.add(ACCOUNT_NORMAL_3);
+
+		Mockito.when(accountRepository.findAllByTypeOrderByNameAsc(AccountType.CUSTOM)).thenReturn(accounts);
+
+		final AccountsFilterConfiguration filterConfiguration = new AccountsFilterConfiguration(true, true, true, true, true, "", "");
+		assertThat(accountService.getFilteredEntitiesAsc(filterConfiguration))
+				.containsExactly(ACCOUNT_NORMAL_3, ACCOUNT_HIDDEN, ACCOUNT_NORMAL, ACCOUNT_NORMAL_2, ACCOUNT_READONLY);
+	}
+
+	@Test
+	void test_getFilteredEntitiesAsc_onlyFullAccess()
+	{
+		final List<Account> accounts = new ArrayList<>();
+		accounts.add(ACCOUNT_NORMAL);
+		accounts.add(ACCOUNT_READONLY);
+		accounts.add(ACCOUNT_HIDDEN);
+		accounts.add(ACCOUNT_NORMAL_2);
+		accounts.add(ACCOUNT_NORMAL_3);
+
+		Mockito.when(accountRepository.findAllByTypeOrderByNameAsc(AccountType.CUSTOM)).thenReturn(accounts);
+
+		final AccountsFilterConfiguration filterConfiguration = new AccountsFilterConfiguration(true, false, false, true, true, "", "");
+		assertThat(accountService.getFilteredEntitiesAsc(filterConfiguration))
+				.containsExactly(ACCOUNT_NORMAL_3, ACCOUNT_NORMAL, ACCOUNT_NORMAL_2);
+	}
+
+	@Test
+	void test_getFilteredEntitiesAsc_onlyReadOnly()
+	{
+		final List<Account> accounts = new ArrayList<>();
+		accounts.add(ACCOUNT_NORMAL);
+		accounts.add(ACCOUNT_READONLY);
+		accounts.add(ACCOUNT_HIDDEN);
+		accounts.add(ACCOUNT_NORMAL_2);
+		accounts.add(ACCOUNT_NORMAL_3);
+
+		Mockito.when(accountRepository.findAllByTypeOrderByNameAsc(AccountType.CUSTOM)).thenReturn(accounts);
+
+		final AccountsFilterConfiguration filterConfiguration = new AccountsFilterConfiguration(false, true, false, true, true, "", "");
+		assertThat(accountService.getFilteredEntitiesAsc(filterConfiguration))
+				.containsExactly(ACCOUNT_READONLY);
+	}
+
+	@Test
+	void test_getFilteredEntitiesAsc_onlyHidden()
+	{
+		final List<Account> accounts = new ArrayList<>();
+		accounts.add(ACCOUNT_NORMAL);
+		accounts.add(ACCOUNT_READONLY);
+		accounts.add(ACCOUNT_HIDDEN);
+		accounts.add(ACCOUNT_NORMAL_2);
+		accounts.add(ACCOUNT_NORMAL_3);
+
+		Mockito.when(accountRepository.findAllByTypeOrderByNameAsc(AccountType.CUSTOM)).thenReturn(accounts);
+
+		final AccountsFilterConfiguration filterConfiguration = new AccountsFilterConfiguration(false, false, true, true, true, "", "");
+		assertThat(accountService.getFilteredEntitiesAsc(filterConfiguration))
+				.containsExactly(ACCOUNT_HIDDEN);
+	}
+
+	@Test
+	void test_getFilteredEntitiesAsc_onlyWithEndDate()
+	{
+		final List<Account> accounts = new ArrayList<>();
+		accounts.add(ACCOUNT_NORMAL);
+		accounts.add(ACCOUNT_READONLY);
+		accounts.add(ACCOUNT_HIDDEN);
+		accounts.add(ACCOUNT_NORMAL_2);
+		accounts.add(ACCOUNT_NORMAL_3);
+
+		Mockito.when(accountRepository.findAllByTypeOrderByNameAsc(AccountType.CUSTOM)).thenReturn(accounts);
+
+		final AccountsFilterConfiguration filterConfiguration = new AccountsFilterConfiguration(true, true, true, true, false, "", "");
+		assertThat(accountService.getFilteredEntitiesAsc(filterConfiguration))
+				.containsExactly(ACCOUNT_NORMAL_2);
+	}
+
+	@Test
+	void test_getFilteredEntitiesAsc_onlyWithoutEndDate()
+	{
+		final List<Account> accounts = new ArrayList<>();
+		accounts.add(ACCOUNT_NORMAL);
+		accounts.add(ACCOUNT_READONLY);
+		accounts.add(ACCOUNT_HIDDEN);
+		accounts.add(ACCOUNT_NORMAL_2);
+		accounts.add(ACCOUNT_NORMAL_3);
+
+		Mockito.when(accountRepository.findAllByTypeOrderByNameAsc(AccountType.CUSTOM)).thenReturn(accounts);
+
+		final AccountsFilterConfiguration filterConfiguration = new AccountsFilterConfiguration(true, true, true, false, true, "", "");
+		assertThat(accountService.getFilteredEntitiesAsc(filterConfiguration))
+				.containsExactly(ACCOUNT_NORMAL_3, ACCOUNT_HIDDEN, ACCOUNT_NORMAL, ACCOUNT_READONLY);
+	}
+
+	@Test
+	void test_getFilteredEntitiesAsc_neitherWithNorWithoutEndDate()
+	{
+		final List<Account> accounts = new ArrayList<>();
+		accounts.add(ACCOUNT_NORMAL);
+		accounts.add(ACCOUNT_READONLY);
+		accounts.add(ACCOUNT_HIDDEN);
+		accounts.add(ACCOUNT_NORMAL_2);
+		accounts.add(ACCOUNT_NORMAL_3);
+
+		Mockito.when(accountRepository.findAllByTypeOrderByNameAsc(AccountType.CUSTOM)).thenReturn(accounts);
+
+		final AccountsFilterConfiguration filterConfiguration = new AccountsFilterConfiguration(true, true, true, false, false, "", "");
+		assertThat(accountService.getFilteredEntitiesAsc(filterConfiguration))
+				.isEmpty();
+	}
+
+	@Test
+	void test_getFilteredEntitiesAsc_byName()
+	{
+		final List<Account> accounts = new ArrayList<>();
+		accounts.add(ACCOUNT_NORMAL);
+		accounts.add(ACCOUNT_READONLY);
+		accounts.add(ACCOUNT_HIDDEN);
+		accounts.add(ACCOUNT_NORMAL_2);
+		accounts.add(ACCOUNT_NORMAL_3);
+
+		Mockito.when(accountRepository.findAllByTypeOrderByNameAsc(AccountType.CUSTOM)).thenReturn(accounts);
+
+		final AccountsFilterConfiguration filterConfiguration = new AccountsFilterConfiguration(true, true, true, true, true, "123", "");
+		assertThat(accountService.getFilteredEntitiesAsc(filterConfiguration))
+				.containsExactly(ACCOUNT_NORMAL_3);
+	}
+
+	@Test
+	void test_getFilteredEntitiesAsc_byDescription()
+	{
+		final List<Account> accounts = new ArrayList<>();
+		accounts.add(ACCOUNT_NORMAL);
+		accounts.add(ACCOUNT_READONLY);
+		accounts.add(ACCOUNT_HIDDEN);
+		accounts.add(ACCOUNT_NORMAL_2);
+		accounts.add(ACCOUNT_NORMAL_3);
+
+		Mockito.when(accountRepository.findAllByTypeOrderByNameAsc(AccountType.CUSTOM)).thenReturn(accounts);
+
+		final AccountsFilterConfiguration filterConfiguration = new AccountsFilterConfiguration(true, true, true, true, true, "", "awesome");
+		assertThat(accountService.getFilteredEntitiesAsc(filterConfiguration))
+				.containsExactly(ACCOUNT_NORMAL);
+	}
 }
-- 
GitLab