diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 9f363e8..0000000 --- a/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -financer-server.log* -.attach* \ No newline at end of file diff --git a/src/main/java/de/financer/controller/RecurringTransactionController.java b/src/main/java/de/financer/controller/RecurringTransactionController.java index 6c36465..91738b7 100644 --- a/src/main/java/de/financer/controller/RecurringTransactionController.java +++ b/src/main/java/de/financer/controller/RecurringTransactionController.java @@ -53,7 +53,7 @@ public class RecurringTransactionController { public ResponseEntity createRecurringTransaction(String fromAccountKey, String toAccountKey, Long amount, String description, String holidayWeekendType, String intervalType, String firstOccurrence, - String lastOccurrence + String lastOccurrence, Boolean remind ) { final String decodedFrom = ControllerUtil.urlDecode(fromAccountKey); final String decodedTo = ControllerUtil.urlDecode(toAccountKey); @@ -62,13 +62,13 @@ public class RecurringTransactionController { if (LOGGER.isDebugEnabled()) { LOGGER.debug(String .format("/recurringTransactions/createRecurringTransaction got parameters: %s, %s, %s, %s, %s, " + - "%s, %s, %s", decodedFrom, decodedTo, amount, decodedDesc, holidayWeekendType, - intervalType, firstOccurrence, lastOccurrence)); + "%s, %s, %s, %s", decodedFrom, decodedTo, amount, decodedDesc, holidayWeekendType, + intervalType, firstOccurrence, lastOccurrence, remind)); } final ResponseReason responseReason = this.recurringTransactionService .createRecurringTransaction(decodedFrom, decodedTo, amount, decodedDesc, holidayWeekendType, - intervalType, firstOccurrence, lastOccurrence); + intervalType, firstOccurrence, lastOccurrence, remind); if (LOGGER.isDebugEnabled()) { LOGGER.debug(String diff --git a/src/main/java/de/financer/model/RecurringTransaction.java b/src/main/java/de/financer/model/RecurringTransaction.java index 3df99c8..135fabe 100644 --- a/src/main/java/de/financer/model/RecurringTransaction.java +++ b/src/main/java/de/financer/model/RecurringTransaction.java @@ -21,6 +21,7 @@ public class RecurringTransaction { @Enumerated(EnumType.STRING) private HolidayWeekendType holidayWeekendType; private boolean deleted; + private boolean remind; public Long getId() { return id; @@ -97,4 +98,12 @@ public class RecurringTransaction { public void setDeleted(boolean deleted) { this.deleted = deleted; } + + public boolean isRemind() { + return remind; + } + + public void setRemind(boolean remind) { + this.remind = remind; + } } diff --git a/src/main/java/de/financer/service/RecurringTransactionService.java b/src/main/java/de/financer/service/RecurringTransactionService.java index b921365..38fd3ad 100644 --- a/src/main/java/de/financer/service/RecurringTransactionService.java +++ b/src/main/java/de/financer/service/RecurringTransactionService.java @@ -8,6 +8,7 @@ import de.financer.model.HolidayWeekendType; import de.financer.model.IntervalType; import de.financer.model.RecurringTransaction; import org.apache.commons.collections4.IterableUtils; +import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import org.slf4j.Logger; @@ -261,16 +262,18 @@ public class RecurringTransactionService { return due; } + + @Transactional(propagation = Propagation.REQUIRED) public ResponseReason createRecurringTransaction(String fromAccountKey, String toAccountKey, Long amount, String description, String holidayWeekendType, String intervalType, String firstOccurrence, - String lastOccurrence + String lastOccurrence, Boolean remind ) { final Account fromAccount = this.accountService.getAccountByKey(fromAccountKey); final Account toAccount = this.accountService.getAccountByKey(toAccountKey); 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 (response != null) { @@ -279,7 +282,7 @@ public class RecurringTransactionService { try { final RecurringTransaction transaction = buildRecurringTransaction(fromAccount, toAccount, amount, - description, holidayWeekendType, intervalType, firstOccurrence, lastOccurrence); + description, holidayWeekendType, intervalType, firstOccurrence, lastOccurrence, remind); this.recurringTransactionRepository.save(transaction); @@ -303,13 +306,14 @@ public class RecurringTransactionService { * @param intervalType the interval type * @param firstOccurrence the first occurrence * @param lastOccurrence the last occurrence, may be null + * @param remind the remind flag * * @return the build {@link RecurringTransaction} instance */ private RecurringTransaction buildRecurringTransaction(Account fromAccount, Account toAccount, Long amount, String description, String holidayWeekendType, String intervalType, String firstOccurrence, - String lastOccurrence + String lastOccurrence, Boolean remind ) { final RecurringTransaction recurringTransaction = new RecurringTransaction(); @@ -321,6 +325,9 @@ public class RecurringTransactionService { recurringTransaction.setIntervalType(IntervalType.valueOf(intervalType)); recurringTransaction.setFirstOccurrence(LocalDate .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 if (StringUtils.isNotEmpty(lastOccurrence)) { diff --git a/src/main/java/de/financer/task/SendRecurringTransactionReminderTask.java b/src/main/java/de/financer/task/SendRecurringTransactionReminderTask.java index f534e7c..48cf729 100644 --- a/src/main/java/de/financer/task/SendRecurringTransactionReminderTask.java +++ b/src/main/java/de/financer/task/SendRecurringTransactionReminderTask.java @@ -13,6 +13,8 @@ import org.springframework.mail.javamail.JavaMailSender; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; +import java.util.stream.Collectors; + @Component public class SendRecurringTransactionReminderTask { @@ -33,7 +35,7 @@ public class SendRecurringTransactionReminderTask { LOGGER.debug("Enter recurring transaction reminder task"); } - final Iterable recurringTransactions = this.recurringTransactionService.getAllDueToday(); + Iterable recurringTransactions = this.recurringTransactionService.getAllDueToday(); // If no recurring transaction is due today we don't need to send a reminder if (IterableUtils.isEmpty(recurringTransactions)) { @@ -42,6 +44,12 @@ public class SendRecurringTransactionReminderTask { 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 .format("%s recurring transaction are due today and are about to be included in the reminder email", IterableUtils.size(recurringTransactions))); diff --git a/src/main/resources/database/hsqldb/V6_0_0__remindFlagRecurringTransaction.sql b/src/main/resources/database/hsqldb/V6_0_0__remindFlagRecurringTransaction.sql new file mode 100644 index 0000000..a9410ad --- /dev/null +++ b/src/main/resources/database/hsqldb/V6_0_0__remindFlagRecurringTransaction.sql @@ -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; \ No newline at end of file diff --git a/src/main/resources/database/postgres/V6_0_0__remindFlagRecurringTransaction.sql b/src/main/resources/database/postgres/V6_0_0__remindFlagRecurringTransaction.sql new file mode 100644 index 0000000..aa29189 --- /dev/null +++ b/src/main/resources/database/postgres/V6_0_0__remindFlagRecurringTransaction.sql @@ -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 \ No newline at end of file diff --git a/src/main/resources/database/postgres/readme_V1_0_0__init.txt b/src/main/resources/database/postgres/readme_V1_0_0__init.txt new file mode 100644 index 0000000..8d853b0 --- /dev/null +++ b/src/main/resources/database/postgres/readme_V1_0_0__init.txt @@ -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() \ No newline at end of file diff --git a/src/test/java/de/financer/controller/integration/RecurringTransactionService_createRecurringTransactionIntegrationTest.java b/src/test/java/de/financer/controller/integration/RecurringTransactionService_createRecurringTransactionIntegrationTest.java index c64e6ae..1b7cf20 100644 --- a/src/test/java/de/financer/controller/integration/RecurringTransactionService_createRecurringTransactionIntegrationTest.java +++ b/src/test/java/de/financer/controller/integration/RecurringTransactionService_createRecurringTransactionIntegrationTest.java @@ -42,7 +42,8 @@ public class RecurringTransactionService_createRecurringTransactionIntegrationTe .param("description", "Monthly rent") .param("holidayWeekendType", "SAME_DAY") .param("intervalType", "MONTHLY") - .param("firstOccurrence", "07.03.2019")) + .param("firstOccurrence", "07.03.2019") + .param("remind", "true")) .andExpect(status().isOk()) .andReturn(); diff --git a/src/test/java/de/financer/service/RecurringTransactionService_createRecurringTransactionTest.java b/src/test/java/de/financer/service/RecurringTransactionService_createRecurringTransactionTest.java index c551797..c34cf0d 100644 --- a/src/test/java/de/financer/service/RecurringTransactionService_createRecurringTransactionTest.java +++ b/src/test/java/de/financer/service/RecurringTransactionService_createRecurringTransactionTest.java @@ -51,7 +51,8 @@ public class RecurringTransactionService_createRecurringTransactionTest { "HOLIDAY_WEEKEND_TYPE", "INTERVAL_TYPE", "FIRST_OCCURRENCE", - "LAST_OCCURRENCE"); + "LAST_OCCURRENCE", + Boolean.TRUE); // Assert Assert.assertEquals(ResponseReason.FROM_AND_TO_ACCOUNT_NOT_FOUND, response); @@ -70,7 +71,8 @@ public class RecurringTransactionService_createRecurringTransactionTest { "HOLIDAY_WEEKEND_TYPE", "INTERVAL_TYPE", "FIRST_OCCURRENCE", - "LAST_OCCURRENCE"); + "LAST_OCCURRENCE", + Boolean.TRUE); // Assert Assert.assertEquals(ResponseReason.TO_ACCOUNT_NOT_FOUND, response); @@ -89,7 +91,8 @@ public class RecurringTransactionService_createRecurringTransactionTest { "HOLIDAY_WEEKEND_TYPE", "INTERVAL_TYPE", "FIRST_OCCURRENCE", - "LAST_OCCURRENCE"); + "LAST_OCCURRENCE", + Boolean.TRUE); // Assert Assert.assertEquals(ResponseReason.FROM_ACCOUNT_NOT_FOUND, response); @@ -109,7 +112,8 @@ public class RecurringTransactionService_createRecurringTransactionTest { "HOLIDAY_WEEKEND_TYPE", "INTERVAL_TYPE", "FIRST_OCCURRENCE", - "LAST_OCCURRENCE"); + "LAST_OCCURRENCE", + Boolean.TRUE); // Assert Assert.assertEquals(ResponseReason.INVALID_BOOKING_ACCOUNTS, response); @@ -129,7 +133,8 @@ public class RecurringTransactionService_createRecurringTransactionTest { "HOLIDAY_WEEKEND_TYPE", "INTERVAL_TYPE", "FIRST_OCCURRENCE", - "LAST_OCCURRENCE"); + "LAST_OCCURRENCE", + Boolean.TRUE); // Assert Assert.assertEquals(ResponseReason.MISSING_AMOUNT, response); @@ -149,7 +154,8 @@ public class RecurringTransactionService_createRecurringTransactionTest { "HOLIDAY_WEEKEND_TYPE", "INTERVAL_TYPE", "FIRST_OCCURRENCE", - "LAST_OCCURRENCE"); + "LAST_OCCURRENCE", + Boolean.TRUE); // Assert Assert.assertEquals(ResponseReason.AMOUNT_ZERO, response); @@ -169,7 +175,8 @@ public class RecurringTransactionService_createRecurringTransactionTest { null, "INTERVAL_TYPE", "FIRST_OCCURRENCE", - "LAST_OCCURRENCE"); + "LAST_OCCURRENCE", + Boolean.TRUE); // Assert Assert.assertEquals(ResponseReason.MISSING_HOLIDAY_WEEKEND_TYPE, response); @@ -189,7 +196,8 @@ public class RecurringTransactionService_createRecurringTransactionTest { "HOLIDAY_WEEKEND_TYPE", "INTERVAL_TYPE", "FIRST_OCCURRENCE", - "LAST_OCCURRENCE"); + "LAST_OCCURRENCE", + Boolean.TRUE); // Assert Assert.assertEquals(ResponseReason.INVALID_HOLIDAY_WEEKEND_TYPE, response); @@ -209,7 +217,8 @@ public class RecurringTransactionService_createRecurringTransactionTest { HolidayWeekendType.SAME_DAY.name(), null, "FIRST_OCCURRENCE", - "LAST_OCCURRENCE"); + "LAST_OCCURRENCE", + Boolean.TRUE); // Assert Assert.assertEquals(ResponseReason.MISSING_INTERVAL_TYPE, response); @@ -229,7 +238,8 @@ public class RecurringTransactionService_createRecurringTransactionTest { HolidayWeekendType.SAME_DAY.name(), "INTERVAL_TYPE", "FIRST_OCCURRENCE", - "LAST_OCCURRENCE"); + "LAST_OCCURRENCE", + Boolean.TRUE); // Assert Assert.assertEquals(ResponseReason.INVALID_INTERVAL_TYPE, response); @@ -249,7 +259,8 @@ public class RecurringTransactionService_createRecurringTransactionTest { HolidayWeekendType.SAME_DAY.name(), IntervalType.DAILY.name(), null, - "LAST_OCCURRENCE"); + "LAST_OCCURRENCE", + Boolean.TRUE); // Assert Assert.assertEquals(ResponseReason.MISSING_FIRST_OCCURRENCE, response); @@ -269,7 +280,8 @@ public class RecurringTransactionService_createRecurringTransactionTest { HolidayWeekendType.SAME_DAY.name(), IntervalType.DAILY.name(), "FIRST_OCCURRENCE", - "LAST_OCCURRENCE"); + "LAST_OCCURRENCE", + Boolean.TRUE); // Assert Assert.assertEquals(ResponseReason.INVALID_FIRST_OCCURRENCE_FORMAT, response); @@ -289,7 +301,8 @@ public class RecurringTransactionService_createRecurringTransactionTest { HolidayWeekendType.SAME_DAY.name(), IntervalType.DAILY.name(), "07.03.2019", - "LAST_OCCURRENCE"); + "LAST_OCCURRENCE", + Boolean.TRUE); // Assert Assert.assertEquals(ResponseReason.INVALID_LAST_OCCURRENCE_FORMAT, response); @@ -310,7 +323,8 @@ public class RecurringTransactionService_createRecurringTransactionTest { HolidayWeekendType.SAME_DAY.name(), IntervalType.DAILY.name(), "07.03.2019", - null); + null, + Boolean.TRUE); // Assert Assert.assertEquals(ResponseReason.UNKNOWN_ERROR, response); @@ -330,7 +344,8 @@ public class RecurringTransactionService_createRecurringTransactionTest { HolidayWeekendType.SAME_DAY.name(), IntervalType.DAILY.name(), "07.03.2019", - null); + null, + Boolean.TRUE); // Assert Assert.assertEquals(ResponseReason.OK, response); diff --git a/src/test/java/de/financer/task/SendRecurringTransactionReminderTaskTest.java b/src/test/java/de/financer/task/SendRecurringTransactionReminderTaskTest.java index 844178d..215f2fc 100644 --- a/src/test/java/de/financer/task/SendRecurringTransactionReminderTaskTest.java +++ b/src/test/java/de/financer/task/SendRecurringTransactionReminderTaskTest.java @@ -36,9 +36,10 @@ public class SendRecurringTransactionReminderTaskTest { public void test_sendReminder() { // Arrange final Collection recurringTransactions = Arrays.asList( - createRecurringTransaction("Test booking 1", "Income", "accounts.bank", Long.valueOf(250000)), - createRecurringTransaction("Test booking 2", "Bank", "accounts.rent", Long.valueOf(41500)), - createRecurringTransaction("Test booking 3", "Bank", "accounts.cash", Long.valueOf(5000)) + createRecurringTransaction("Test booking 1", "Income", "accounts.bank", Long.valueOf(250000), true), + createRecurringTransaction("Test booking 2", "Bank", "accounts.rent", Long.valueOf(41500), true), + 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); @@ -51,13 +52,14 @@ public class SendRecurringTransactionReminderTaskTest { 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(); recurringTransaction.setDescription(description); recurringTransaction.setFromAccount(createAccount(fromAccountKey)); recurringTransaction.setToAccount(createAccount(toAccountKey)); recurringTransaction.setAmount(amount); + recurringTransaction.setRemind(remind); return recurringTransaction; }