Introduce expenseNeutral flag

This commit is contained in:
2020-04-10 16:37:06 +02:00
parent aa7168ac3c
commit 3d4fb73424
7 changed files with 20 additions and 252 deletions

View File

@@ -14,15 +14,15 @@ public interface TransactionRepository extends CrudRepository<Transaction, Long>
@Query("SELECT t FROM Transaction t WHERE t.toAccount = :toAccount OR t.fromAccount = :fromAccount")
Iterable<Transaction> findTransactionsByFromAccountOrToAccount(Account fromAccount, Account toAccount);
@Query("SELECT SUM(t.amount) FROM Transaction t JOIN t.periods p JOIN t.toAccount a WHERE a.type IN :expenseTypes AND p = :period")
@Query("SELECT SUM(t.amount) FROM Transaction t JOIN t.periods p JOIN t.toAccount a WHERE a.type IN :expenseTypes AND p = :period AND t.expenseNeutral = false")
Long getExpensesForPeriod(Period period, AccountType... expenseTypes);
@Query("SELECT SUM(t.amount) FROM Transaction t JOIN t.periods p JOIN t.toAccount a JOIN t.fromAccount f WHERE a.type IN :expenseTypes AND f.type != :startType AND p.type = :type GROUP BY p ORDER BY p.start ASC")
@Query("SELECT SUM(t.amount) FROM Transaction t JOIN t.periods p JOIN t.toAccount a JOIN t.fromAccount f WHERE a.type IN :expenseTypes AND f.type != :startType AND p.type = :type AND t.expenseNeutral = false GROUP BY p ORDER BY p.start ASC")
List<Long> getExpensesForAllPeriods(PeriodType type, AccountType startType, AccountType... expenseTypes);
// The HQL contains a hack because Hibernate can't resolve the alias of the CASE column in the GROUP BY clause
// That's why the generated alias is used directly in the HQL. It will break if the columns in the SELECT clause get reordered
// col_2_0_ instead of AccType
@Query("SELECT new de.financer.dto.ExpensePeriodTotal(p, SUM(t.amount), CASE WHEN fa.type = :incomeType THEN fa.type ELSE ta.type END AS AccType) FROM Transaction t JOIN t.toAccount ta JOIN t.fromAccount fa JOIN t.periods p WHERE ((ta.type IN :expenseTypes AND fa.type <> :startType) OR (fa.type = :incomeType)) AND p IN :periods GROUP BY p, col_2_0_")
@Query("SELECT new de.financer.dto.ExpensePeriodTotal(p, SUM(t.amount), CASE WHEN fa.type = :incomeType THEN fa.type ELSE ta.type END AS AccType) FROM Transaction t JOIN t.toAccount ta JOIN t.fromAccount fa JOIN t.periods p WHERE ((ta.type IN :expenseTypes AND fa.type <> :startType) OR (fa.type = :incomeType)) AND p IN :periods AND t.expenseNeutral = false GROUP BY p, col_2_0_")
List<ExpensePeriodTotal> getAccountExpenseTotals(Iterable<Period> periods, AccountType incomeType, AccountType startType, AccountType... expenseTypes);
}

View File

@@ -0,0 +1,2 @@
ALTER TABLE "transaction"
ADD COLUMN expense_neutral BOOLEAN DEFAULT FALSE NOT NULL;

View File

@@ -0,0 +1,2 @@
ALTER TABLE "transaction"
ADD COLUMN expense_neutral BOOLEAN DEFAULT 'FALSE' NOT NULL;

View File

@@ -1,174 +0,0 @@
package de.financer.service;
import de.financer.ResponseReason;
import de.financer.config.FinancerConfig;
import de.financer.dba.TransactionRepository;
import de.financer.model.Account;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class TransactionService_createTransactionTest {
@InjectMocks
private TransactionService classUnderTest;
@Mock
private AccountService accountService;
@Mock
private RuleService ruleService;
@Mock
private PeriodService periodService;
@Mock
private AccountStatisticService accountStatisticService;
@Mock
private TransactionRepository transactionRepository;
@Mock
private FinancerConfig financerConfig;
@Before
public void setUp() {
Mockito.when(this.financerConfig.getDateFormat()).thenReturn("dd.MM.yyyy");
}
@Test
public void test_createTransaction_FROM_AND_TO_ACCOUNT_NOT_FOUND() {
// Arrange
// Nothing to do, if we do not instruct the account service instance to return anything the accounts
// will not be found.
// Act
final ResponseReason response = this.classUnderTest.createTransaction("account.invalid", "account.invalid", 150L, "24.02.2019", "XXX", false, null, null);
// Assert
Assert.assertEquals(ResponseReason.FROM_AND_TO_ACCOUNT_NOT_FOUND, response);
}
@Test
public void test_createTransaction_TO_ACCOUNT_NOT_FOUND() {
// Arrange
Mockito.when(this.accountService.getAccountByKey(Mockito.anyString())).thenReturn(createAccount(), null);
// Act
final ResponseReason response = this.classUnderTest.createTransaction("account.from", "account.invalid", 150L, "24.02.2019", "XXX", false, null, null);
// Assert
Assert.assertEquals(ResponseReason.TO_ACCOUNT_NOT_FOUND, response);
}
@Test
public void test_createTransaction_FROM_ACCOUNT_NOT_FOUND() {
// Arrange
Mockito.when(this.accountService.getAccountByKey(Mockito.anyString())).thenReturn(null, createAccount());
// Act
final ResponseReason response = this.classUnderTest.createTransaction("account.invalid", "account.to", 150L, "24.02.2019", "XXX", false, null, null);
// Assert
Assert.assertEquals(ResponseReason.FROM_ACCOUNT_NOT_FOUND, response);
}
@Test
public void test_createTransaction_INVALID_BOOKING_ACCOUNTS() {
// Arrange
Mockito.when(this.accountService.getAccountByKey(Mockito.anyString())).thenReturn(createAccount(), createAccount());
Mockito.when(this.ruleService.isValidBooking(Mockito.any(Account.class), Mockito.any(Account.class))).thenReturn(Boolean.FALSE);
// Act
final ResponseReason response = this.classUnderTest.createTransaction("account.from", "account.to", 150L, "24.02.2019", "XXX", false, null, null);
// Assert
Assert.assertEquals(ResponseReason.INVALID_BOOKING_ACCOUNTS, response);
}
@Test
public void test_createTransaction_MISSING_AMOUNT() {
// Arrange
Mockito.when(this.accountService.getAccountByKey(Mockito.anyString())).thenReturn(createAccount(), createAccount());
Mockito.when(this.ruleService.isValidBooking(Mockito.any(Account.class), Mockito.any(Account.class))).thenReturn(Boolean.TRUE);
// Act
final ResponseReason response = this.classUnderTest.createTransaction("account.from", "account.to", null, "24.02.2019", "XXX", false, null, null);
// Assert
Assert.assertEquals(ResponseReason.MISSING_AMOUNT, response);
}
@Test
public void test_createTransaction_AMOUNT_ZERO() {
// Arrange
Mockito.when(this.accountService.getAccountByKey(Mockito.anyString())).thenReturn(createAccount(), createAccount());
Mockito.when(this.ruleService.isValidBooking(Mockito.any(Account.class), Mockito.any(Account.class))).thenReturn(Boolean.TRUE);
// Act
final ResponseReason response = this.classUnderTest.createTransaction("account.from", "account.to", 0L, "24.02.2019", "XXX", false, null, null);
// Assert
Assert.assertEquals(ResponseReason.AMOUNT_ZERO, response);
}
@Test
public void test_createTransaction_MISSING_DATE() {
// Arrange
Mockito.when(this.accountService.getAccountByKey(Mockito.anyString())).thenReturn(createAccount(), createAccount());
Mockito.when(this.ruleService.isValidBooking(Mockito.any(Account.class), Mockito.any(Account.class))).thenReturn(Boolean.TRUE);
// Act
final ResponseReason response = this.classUnderTest.createTransaction("account.from", "account.to", 125L, null, "XXX", false, null, null);
// Assert
Assert.assertEquals(ResponseReason.MISSING_DATE, response);
}
@Test
public void test_createTransaction_INVALID_DATE_FORMAT() {
// Arrange
Mockito.when(this.accountService.getAccountByKey(Mockito.anyString())).thenReturn(createAccount(), createAccount());
Mockito.when(this.ruleService.isValidBooking(Mockito.any(Account.class), Mockito.any(Account.class))).thenReturn(Boolean.TRUE);
// Act
final ResponseReason response = this.classUnderTest.createTransaction("account.from", "account.to", 125L, "2019-01-01", "XXX", false, null, null);
// Assert
Assert.assertEquals(ResponseReason.INVALID_DATE_FORMAT, response);
}
@Test
public void test_createTransaction_OK() {
// Arrange
final Account fromAccount = Mockito.mock(Account.class);
final Account toAccount = Mockito.mock(Account.class);
Mockito.when(this.accountService.getAccountByKey(Mockito.anyString())).thenReturn(fromAccount, toAccount);
Mockito.when(this.ruleService.isValidBooking(Mockito.any(), Mockito.any())).thenReturn(Boolean.TRUE);
Mockito.when(this.ruleService.getMultiplierFromAccount(Mockito.any())).thenReturn(-1L);
Mockito.when(this.ruleService.getMultiplierToAccount(Mockito.any())).thenReturn(1L);
Mockito.when(fromAccount.getCurrentBalance()).thenReturn(0L);
Mockito.when(toAccount.getCurrentBalance()).thenReturn(0L);
// Act
final ResponseReason response = this.classUnderTest.createTransaction("account.from", "account.to", 125L, "24.02.2019", "XXX", false, null, null);
// Assert
Assert.assertEquals(ResponseReason.CREATED, response);
Mockito.verify(fromAccount, Mockito.times(1)).setCurrentBalance((long) -125);
Mockito.verify(toAccount, Mockito.times(1)).setCurrentBalance(125L);
}
private Account createAccount() {
final Account account = new Account();
account.setCurrentBalance(0L);
return account;
}
}