Add flag to control whether to remind about maturity of rec. transaction

This commit is contained in:
2019-06-10 02:29:43 +02:00
parent 4ae49df145
commit 52525ff0d1
11 changed files with 104 additions and 31 deletions

2
.gitignore vendored
View File

@@ -1,2 +0,0 @@
financer-server.log*
.attach*

View File

@@ -53,7 +53,7 @@ public class RecurringTransactionController {
public ResponseEntity createRecurringTransaction(String fromAccountKey, String toAccountKey, Long amount, public ResponseEntity createRecurringTransaction(String fromAccountKey, String toAccountKey, Long amount,
String description, String holidayWeekendType, String description, String holidayWeekendType,
String intervalType, String firstOccurrence, String intervalType, String firstOccurrence,
String lastOccurrence String lastOccurrence, Boolean remind
) { ) {
final String decodedFrom = ControllerUtil.urlDecode(fromAccountKey); final String decodedFrom = ControllerUtil.urlDecode(fromAccountKey);
final String decodedTo = ControllerUtil.urlDecode(toAccountKey); final String decodedTo = ControllerUtil.urlDecode(toAccountKey);
@@ -62,13 +62,13 @@ public class RecurringTransactionController {
if (LOGGER.isDebugEnabled()) { if (LOGGER.isDebugEnabled()) {
LOGGER.debug(String LOGGER.debug(String
.format("/recurringTransactions/createRecurringTransaction got parameters: %s, %s, %s, %s, %s, " + .format("/recurringTransactions/createRecurringTransaction got parameters: %s, %s, %s, %s, %s, " +
"%s, %s, %s", decodedFrom, decodedTo, amount, decodedDesc, holidayWeekendType, "%s, %s, %s, %s", decodedFrom, decodedTo, amount, decodedDesc, holidayWeekendType,
intervalType, firstOccurrence, lastOccurrence)); intervalType, firstOccurrence, lastOccurrence, remind));
} }
final ResponseReason responseReason = this.recurringTransactionService final ResponseReason responseReason = this.recurringTransactionService
.createRecurringTransaction(decodedFrom, decodedTo, amount, decodedDesc, holidayWeekendType, .createRecurringTransaction(decodedFrom, decodedTo, amount, decodedDesc, holidayWeekendType,
intervalType, firstOccurrence, lastOccurrence); intervalType, firstOccurrence, lastOccurrence, remind);
if (LOGGER.isDebugEnabled()) { if (LOGGER.isDebugEnabled()) {
LOGGER.debug(String LOGGER.debug(String

View File

@@ -21,6 +21,7 @@ public class RecurringTransaction {
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
private HolidayWeekendType holidayWeekendType; private HolidayWeekendType holidayWeekendType;
private boolean deleted; private boolean deleted;
private boolean remind;
public Long getId() { public Long getId() {
return id; return id;
@@ -97,4 +98,12 @@ public class RecurringTransaction {
public void setDeleted(boolean deleted) { public void setDeleted(boolean deleted) {
this.deleted = deleted; this.deleted = deleted;
} }
public boolean isRemind() {
return remind;
}
public void setRemind(boolean remind) {
this.remind = remind;
}
} }

View File

@@ -8,6 +8,7 @@ import de.financer.model.HolidayWeekendType;
import de.financer.model.IntervalType; import de.financer.model.IntervalType;
import de.financer.model.RecurringTransaction; import de.financer.model.RecurringTransaction;
import org.apache.commons.collections4.IterableUtils; import org.apache.commons.collections4.IterableUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils; import org.apache.commons.lang3.math.NumberUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -261,16 +262,18 @@ public class RecurringTransactionService {
return due; return due;
} }
@Transactional(propagation = Propagation.REQUIRED) @Transactional(propagation = Propagation.REQUIRED)
public ResponseReason createRecurringTransaction(String fromAccountKey, String toAccountKey, Long amount, public ResponseReason createRecurringTransaction(String fromAccountKey, String toAccountKey, Long amount,
String description, String holidayWeekendType, String description, String holidayWeekendType,
String intervalType, String firstOccurrence, String intervalType, String firstOccurrence,
String lastOccurrence String lastOccurrence, Boolean remind
) { ) {
final Account fromAccount = this.accountService.getAccountByKey(fromAccountKey); final Account fromAccount = this.accountService.getAccountByKey(fromAccountKey);
final Account toAccount = this.accountService.getAccountByKey(toAccountKey); final Account toAccount = this.accountService.getAccountByKey(toAccountKey);
ResponseReason response = validateParameters(fromAccount, toAccount, amount, holidayWeekendType, intervalType, ResponseReason response = validateParameters(fromAccount, toAccount, amount, holidayWeekendType, intervalType,
firstOccurrence, lastOccurrence); firstOccurrence, lastOccurrence); // no validation of 'remind' as it's completely optional
// If we detected an issue with the given parameters return the first error found to the caller // If we detected an issue with the given parameters return the first error found to the caller
if (response != null) { if (response != null) {
@@ -279,7 +282,7 @@ public class RecurringTransactionService {
try { try {
final RecurringTransaction transaction = buildRecurringTransaction(fromAccount, toAccount, amount, final RecurringTransaction transaction = buildRecurringTransaction(fromAccount, toAccount, amount,
description, holidayWeekendType, intervalType, firstOccurrence, lastOccurrence); description, holidayWeekendType, intervalType, firstOccurrence, lastOccurrence, remind);
this.recurringTransactionRepository.save(transaction); this.recurringTransactionRepository.save(transaction);
@@ -303,13 +306,14 @@ public class RecurringTransactionService {
* @param intervalType the interval type * @param intervalType the interval type
* @param firstOccurrence the first occurrence * @param firstOccurrence the first occurrence
* @param lastOccurrence the last occurrence, may be <code>null</code> * @param lastOccurrence the last occurrence, may be <code>null</code>
* @param remind the remind flag
* *
* @return the build {@link RecurringTransaction} instance * @return the build {@link RecurringTransaction} instance
*/ */
private RecurringTransaction buildRecurringTransaction(Account fromAccount, Account toAccount, Long amount, private RecurringTransaction buildRecurringTransaction(Account fromAccount, Account toAccount, Long amount,
String description, String holidayWeekendType, String description, String holidayWeekendType,
String intervalType, String firstOccurrence, String intervalType, String firstOccurrence,
String lastOccurrence String lastOccurrence, Boolean remind
) { ) {
final RecurringTransaction recurringTransaction = new RecurringTransaction(); final RecurringTransaction recurringTransaction = new RecurringTransaction();
@@ -321,6 +325,9 @@ public class RecurringTransactionService {
recurringTransaction.setIntervalType(IntervalType.valueOf(intervalType)); recurringTransaction.setIntervalType(IntervalType.valueOf(intervalType));
recurringTransaction.setFirstOccurrence(LocalDate recurringTransaction.setFirstOccurrence(LocalDate
.parse(firstOccurrence, DateTimeFormatter.ofPattern(this.financerConfig.getDateFormat()))); .parse(firstOccurrence, DateTimeFormatter.ofPattern(this.financerConfig.getDateFormat())));
// See 'resources/database/postgres/readme_V1_0_0__init.txt'
recurringTransaction.setDeleted(false);
recurringTransaction.setRemind(BooleanUtils.toBooleanDefaultIfNull(remind, true));
// lastOccurrence is optional // lastOccurrence is optional
if (StringUtils.isNotEmpty(lastOccurrence)) { if (StringUtils.isNotEmpty(lastOccurrence)) {

View File

@@ -13,6 +13,8 @@ import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.stream.Collectors;
@Component @Component
public class SendRecurringTransactionReminderTask { public class SendRecurringTransactionReminderTask {
@@ -33,7 +35,7 @@ public class SendRecurringTransactionReminderTask {
LOGGER.debug("Enter recurring transaction reminder task"); LOGGER.debug("Enter recurring transaction reminder task");
} }
final Iterable<RecurringTransaction> recurringTransactions = this.recurringTransactionService.getAllDueToday(); Iterable<RecurringTransaction> recurringTransactions = this.recurringTransactionService.getAllDueToday();
// If no recurring transaction is due today we don't need to send a reminder // If no recurring transaction is due today we don't need to send a reminder
if (IterableUtils.isEmpty(recurringTransactions)) { if (IterableUtils.isEmpty(recurringTransactions)) {
@@ -42,6 +44,12 @@ public class SendRecurringTransactionReminderTask {
return; // early return return; // early return
} }
// TODO Filtering currently happens in memory but should be done via SQL
recurringTransactions = IterableUtils.toList(recurringTransactions)
.stream()
.filter((rt) -> rt.isRemind())
.collect(Collectors.toList());
LOGGER.info(String LOGGER.info(String
.format("%s recurring transaction are due today and are about to be included in the reminder email", .format("%s recurring transaction are due today and are about to be included in the reminder email",
IterableUtils.size(recurringTransactions))); IterableUtils.size(recurringTransactions)));

View File

@@ -0,0 +1,4 @@
-- Add a new column to the recurring transaction table that controls whether
-- a reminder about the maturity should be send
ALTER TABLE recurring_transaction
ADD COLUMN remind BOOLEAN DEFAULT TRUE NOT NULL;

View File

@@ -0,0 +1,4 @@
-- Add a new column to the recurring transaction table that controls whether
-- a reminder about the maturity should be send
ALTER TABLE recurring_transaction
ADD COLUMN remind BOOLEAN DEFAULT 'TRUE' NOT NULL

View File

@@ -0,0 +1,25 @@
The recurring transaction table is defined like this (at least for postgres):
-- Recurring transaction table
CREATE TABLE recurring_transaction (
id BIGINT NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
from_account_id BIGINT NOT NULL,
to_account_id BIGINT NOT NULL,
description VARCHAR(1000),
amount BIGINT NOT NULL,
interval_type VARCHAR(255) NOT NULL,
first_occurrence DATE NOT NULL,
last_occurrence DATE,
holiday_weekend_type VARCHAR(255) NOT NULL,
deleted BOOLEAN DEFAULT 'TRUE' NOT NULL,
CONSTRAINT fk_recurring_transaction_from_account FOREIGN KEY (from_account_id) REFERENCES account (id),
CONSTRAINT fk_recurring_transaction_to_account FOREIGN KEY (to_account_id) REFERENCES account (id)
);
Note the
deleted BOOLEAN DEFAULT 'TRUE' NOT NULL,
column definition. Not sure why the default is TRUE here is it doesn't make sense.
It was probably a mistake, however fixing it here _WILL_ break existing installations
as Flyway uses a checksum for scripts. So there is no easy fix, except for effectively
overwriting this default in Java code when creating a new recurring transaction.
See RecurringTransactionService.createRecurringTransaction()

View File

@@ -42,7 +42,8 @@ public class RecurringTransactionService_createRecurringTransactionIntegrationTe
.param("description", "Monthly rent") .param("description", "Monthly rent")
.param("holidayWeekendType", "SAME_DAY") .param("holidayWeekendType", "SAME_DAY")
.param("intervalType", "MONTHLY") .param("intervalType", "MONTHLY")
.param("firstOccurrence", "07.03.2019")) .param("firstOccurrence", "07.03.2019")
.param("remind", "true"))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andReturn(); .andReturn();

View File

@@ -51,7 +51,8 @@ public class RecurringTransactionService_createRecurringTransactionTest {
"HOLIDAY_WEEKEND_TYPE", "HOLIDAY_WEEKEND_TYPE",
"INTERVAL_TYPE", "INTERVAL_TYPE",
"FIRST_OCCURRENCE", "FIRST_OCCURRENCE",
"LAST_OCCURRENCE"); "LAST_OCCURRENCE",
Boolean.TRUE);
// Assert // Assert
Assert.assertEquals(ResponseReason.FROM_AND_TO_ACCOUNT_NOT_FOUND, response); Assert.assertEquals(ResponseReason.FROM_AND_TO_ACCOUNT_NOT_FOUND, response);
@@ -70,7 +71,8 @@ public class RecurringTransactionService_createRecurringTransactionTest {
"HOLIDAY_WEEKEND_TYPE", "HOLIDAY_WEEKEND_TYPE",
"INTERVAL_TYPE", "INTERVAL_TYPE",
"FIRST_OCCURRENCE", "FIRST_OCCURRENCE",
"LAST_OCCURRENCE"); "LAST_OCCURRENCE",
Boolean.TRUE);
// Assert // Assert
Assert.assertEquals(ResponseReason.TO_ACCOUNT_NOT_FOUND, response); Assert.assertEquals(ResponseReason.TO_ACCOUNT_NOT_FOUND, response);
@@ -89,7 +91,8 @@ public class RecurringTransactionService_createRecurringTransactionTest {
"HOLIDAY_WEEKEND_TYPE", "HOLIDAY_WEEKEND_TYPE",
"INTERVAL_TYPE", "INTERVAL_TYPE",
"FIRST_OCCURRENCE", "FIRST_OCCURRENCE",
"LAST_OCCURRENCE"); "LAST_OCCURRENCE",
Boolean.TRUE);
// Assert // Assert
Assert.assertEquals(ResponseReason.FROM_ACCOUNT_NOT_FOUND, response); Assert.assertEquals(ResponseReason.FROM_ACCOUNT_NOT_FOUND, response);
@@ -109,7 +112,8 @@ public class RecurringTransactionService_createRecurringTransactionTest {
"HOLIDAY_WEEKEND_TYPE", "HOLIDAY_WEEKEND_TYPE",
"INTERVAL_TYPE", "INTERVAL_TYPE",
"FIRST_OCCURRENCE", "FIRST_OCCURRENCE",
"LAST_OCCURRENCE"); "LAST_OCCURRENCE",
Boolean.TRUE);
// Assert // Assert
Assert.assertEquals(ResponseReason.INVALID_BOOKING_ACCOUNTS, response); Assert.assertEquals(ResponseReason.INVALID_BOOKING_ACCOUNTS, response);
@@ -129,7 +133,8 @@ public class RecurringTransactionService_createRecurringTransactionTest {
"HOLIDAY_WEEKEND_TYPE", "HOLIDAY_WEEKEND_TYPE",
"INTERVAL_TYPE", "INTERVAL_TYPE",
"FIRST_OCCURRENCE", "FIRST_OCCURRENCE",
"LAST_OCCURRENCE"); "LAST_OCCURRENCE",
Boolean.TRUE);
// Assert // Assert
Assert.assertEquals(ResponseReason.MISSING_AMOUNT, response); Assert.assertEquals(ResponseReason.MISSING_AMOUNT, response);
@@ -149,7 +154,8 @@ public class RecurringTransactionService_createRecurringTransactionTest {
"HOLIDAY_WEEKEND_TYPE", "HOLIDAY_WEEKEND_TYPE",
"INTERVAL_TYPE", "INTERVAL_TYPE",
"FIRST_OCCURRENCE", "FIRST_OCCURRENCE",
"LAST_OCCURRENCE"); "LAST_OCCURRENCE",
Boolean.TRUE);
// Assert // Assert
Assert.assertEquals(ResponseReason.AMOUNT_ZERO, response); Assert.assertEquals(ResponseReason.AMOUNT_ZERO, response);
@@ -169,7 +175,8 @@ public class RecurringTransactionService_createRecurringTransactionTest {
null, null,
"INTERVAL_TYPE", "INTERVAL_TYPE",
"FIRST_OCCURRENCE", "FIRST_OCCURRENCE",
"LAST_OCCURRENCE"); "LAST_OCCURRENCE",
Boolean.TRUE);
// Assert // Assert
Assert.assertEquals(ResponseReason.MISSING_HOLIDAY_WEEKEND_TYPE, response); Assert.assertEquals(ResponseReason.MISSING_HOLIDAY_WEEKEND_TYPE, response);
@@ -189,7 +196,8 @@ public class RecurringTransactionService_createRecurringTransactionTest {
"HOLIDAY_WEEKEND_TYPE", "HOLIDAY_WEEKEND_TYPE",
"INTERVAL_TYPE", "INTERVAL_TYPE",
"FIRST_OCCURRENCE", "FIRST_OCCURRENCE",
"LAST_OCCURRENCE"); "LAST_OCCURRENCE",
Boolean.TRUE);
// Assert // Assert
Assert.assertEquals(ResponseReason.INVALID_HOLIDAY_WEEKEND_TYPE, response); Assert.assertEquals(ResponseReason.INVALID_HOLIDAY_WEEKEND_TYPE, response);
@@ -209,7 +217,8 @@ public class RecurringTransactionService_createRecurringTransactionTest {
HolidayWeekendType.SAME_DAY.name(), HolidayWeekendType.SAME_DAY.name(),
null, null,
"FIRST_OCCURRENCE", "FIRST_OCCURRENCE",
"LAST_OCCURRENCE"); "LAST_OCCURRENCE",
Boolean.TRUE);
// Assert // Assert
Assert.assertEquals(ResponseReason.MISSING_INTERVAL_TYPE, response); Assert.assertEquals(ResponseReason.MISSING_INTERVAL_TYPE, response);
@@ -229,7 +238,8 @@ public class RecurringTransactionService_createRecurringTransactionTest {
HolidayWeekendType.SAME_DAY.name(), HolidayWeekendType.SAME_DAY.name(),
"INTERVAL_TYPE", "INTERVAL_TYPE",
"FIRST_OCCURRENCE", "FIRST_OCCURRENCE",
"LAST_OCCURRENCE"); "LAST_OCCURRENCE",
Boolean.TRUE);
// Assert // Assert
Assert.assertEquals(ResponseReason.INVALID_INTERVAL_TYPE, response); Assert.assertEquals(ResponseReason.INVALID_INTERVAL_TYPE, response);
@@ -249,7 +259,8 @@ public class RecurringTransactionService_createRecurringTransactionTest {
HolidayWeekendType.SAME_DAY.name(), HolidayWeekendType.SAME_DAY.name(),
IntervalType.DAILY.name(), IntervalType.DAILY.name(),
null, null,
"LAST_OCCURRENCE"); "LAST_OCCURRENCE",
Boolean.TRUE);
// Assert // Assert
Assert.assertEquals(ResponseReason.MISSING_FIRST_OCCURRENCE, response); Assert.assertEquals(ResponseReason.MISSING_FIRST_OCCURRENCE, response);
@@ -269,7 +280,8 @@ public class RecurringTransactionService_createRecurringTransactionTest {
HolidayWeekendType.SAME_DAY.name(), HolidayWeekendType.SAME_DAY.name(),
IntervalType.DAILY.name(), IntervalType.DAILY.name(),
"FIRST_OCCURRENCE", "FIRST_OCCURRENCE",
"LAST_OCCURRENCE"); "LAST_OCCURRENCE",
Boolean.TRUE);
// Assert // Assert
Assert.assertEquals(ResponseReason.INVALID_FIRST_OCCURRENCE_FORMAT, response); Assert.assertEquals(ResponseReason.INVALID_FIRST_OCCURRENCE_FORMAT, response);
@@ -289,7 +301,8 @@ public class RecurringTransactionService_createRecurringTransactionTest {
HolidayWeekendType.SAME_DAY.name(), HolidayWeekendType.SAME_DAY.name(),
IntervalType.DAILY.name(), IntervalType.DAILY.name(),
"07.03.2019", "07.03.2019",
"LAST_OCCURRENCE"); "LAST_OCCURRENCE",
Boolean.TRUE);
// Assert // Assert
Assert.assertEquals(ResponseReason.INVALID_LAST_OCCURRENCE_FORMAT, response); Assert.assertEquals(ResponseReason.INVALID_LAST_OCCURRENCE_FORMAT, response);
@@ -310,7 +323,8 @@ public class RecurringTransactionService_createRecurringTransactionTest {
HolidayWeekendType.SAME_DAY.name(), HolidayWeekendType.SAME_DAY.name(),
IntervalType.DAILY.name(), IntervalType.DAILY.name(),
"07.03.2019", "07.03.2019",
null); null,
Boolean.TRUE);
// Assert // Assert
Assert.assertEquals(ResponseReason.UNKNOWN_ERROR, response); Assert.assertEquals(ResponseReason.UNKNOWN_ERROR, response);
@@ -330,7 +344,8 @@ public class RecurringTransactionService_createRecurringTransactionTest {
HolidayWeekendType.SAME_DAY.name(), HolidayWeekendType.SAME_DAY.name(),
IntervalType.DAILY.name(), IntervalType.DAILY.name(),
"07.03.2019", "07.03.2019",
null); null,
Boolean.TRUE);
// Assert // Assert
Assert.assertEquals(ResponseReason.OK, response); Assert.assertEquals(ResponseReason.OK, response);

View File

@@ -36,9 +36,10 @@ public class SendRecurringTransactionReminderTaskTest {
public void test_sendReminder() { public void test_sendReminder() {
// Arrange // Arrange
final Collection<RecurringTransaction> recurringTransactions = Arrays.asList( final Collection<RecurringTransaction> recurringTransactions = Arrays.asList(
createRecurringTransaction("Test booking 1", "Income", "accounts.bank", Long.valueOf(250000)), createRecurringTransaction("Test booking 1", "Income", "accounts.bank", Long.valueOf(250000), true),
createRecurringTransaction("Test booking 2", "Bank", "accounts.rent", Long.valueOf(41500)), createRecurringTransaction("Test booking 2", "Bank", "accounts.rent", Long.valueOf(41500), true),
createRecurringTransaction("Test booking 3", "Bank", "accounts.cash", Long.valueOf(5000)) createRecurringTransaction("Test booking 3", "Bank", "accounts.cash", Long.valueOf(5000), true),
createRecurringTransaction("Test booking 4", "Car", "accounts.car", Long.valueOf(1234), false)
); );
Mockito.when(this.recurringTransactionService.getAllDueToday()).thenReturn(recurringTransactions); Mockito.when(this.recurringTransactionService.getAllDueToday()).thenReturn(recurringTransactions);
@@ -51,13 +52,14 @@ public class SendRecurringTransactionReminderTaskTest {
Mockito.verify(this.mailSender, Mockito.times(1)).send(Mockito.any(SimpleMailMessage.class)); Mockito.verify(this.mailSender, Mockito.times(1)).send(Mockito.any(SimpleMailMessage.class));
} }
private RecurringTransaction createRecurringTransaction(String description, String fromAccountKey, String toAccountKey, Long amount) { private RecurringTransaction createRecurringTransaction(String description, String fromAccountKey, String toAccountKey, Long amount, boolean remind) {
final RecurringTransaction recurringTransaction = new RecurringTransaction(); final RecurringTransaction recurringTransaction = new RecurringTransaction();
recurringTransaction.setDescription(description); recurringTransaction.setDescription(description);
recurringTransaction.setFromAccount(createAccount(fromAccountKey)); recurringTransaction.setFromAccount(createAccount(fromAccountKey));
recurringTransaction.setToAccount(createAccount(toAccountKey)); recurringTransaction.setToAccount(createAccount(toAccountKey));
recurringTransaction.setAmount(amount); recurringTransaction.setAmount(amount);
recurringTransaction.setRemind(remind);
return recurringTransaction; return recurringTransaction;
} }