Fix a bug in the rec. trx. service if it has HWT 'NW' and LO on a weekend

Adjust it so that recurring transactions that have their last occurrence
(LO) on a weekend or a holiday in the near past and HWT NEXT_WORKDAY (NW)
are also grabbed. Otherwise there would never be a reminder about them.
On the actual due date the reminder is deferred because of the HWT and
for later runs it's not grabbed because of the condition
'...LastOccurrenceGreaterThanEqual(now)'.

Also add a unit test case for this. Further add better logging.
This commit is contained in:
2019-06-16 11:47:45 +02:00
parent 5356bb209f
commit 4182c38656
2 changed files with 65 additions and 1 deletions

View File

@@ -10,6 +10,7 @@ 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.BooleanUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.commons.lang3.math.NumberUtils; import org.apache.commons.lang3.math.NumberUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -79,8 +80,15 @@ public class RecurringTransactionService {
// Visible for unit tests // Visible for unit tests
/* package */ Iterable<RecurringTransaction> getAllDueToday(LocalDate now) { /* package */ Iterable<RecurringTransaction> getAllDueToday(LocalDate now) {
// Subtract one week/seven days from the current date so that recurring transactions that have their last
// occurrence on a weekend or a holiday in the near past and HWT NEXT_WORKDAY are also grabbed. Otherwise
// there would never be a reminder about them. On the actual due date the reminder is deferred because of the
// HWT and for later runs it's not grabbed because of the condition '...LastOccurrenceGreaterThanEqual(now)'
final Iterable<RecurringTransaction> allRecurringTransactions = this.recurringTransactionRepository final Iterable<RecurringTransaction> allRecurringTransactions = this.recurringTransactionRepository
.findByDeletedFalseAndLastOccurrenceIsNullOrLastOccurrenceGreaterThanEqual(now); .findByDeletedFalseAndLastOccurrenceIsNullOrLastOccurrenceGreaterThanEqual(now.minusDays(7));
LOGGER.debug(String.format("Found %s candidate recurring transactions. Checking which are due",
IterableUtils.size(allRecurringTransactions)));
//@formatter:off //@formatter:off
return IterableUtils.toList(allRecurringTransactions).stream() return IterableUtils.toList(allRecurringTransactions).stream()
@@ -115,6 +123,10 @@ public class RecurringTransactionService {
// now.plusDays(1) = 2019-05-15 // now.plusDays(1) = 2019-05-15
// => IllegalArgumentException: 2019-05-15 < 2019-05-27 // => IllegalArgumentException: 2019-05-15 < 2019-05-27
if (recurringTransaction.getFirstOccurrence().isAfter(now)) { if (recurringTransaction.getFirstOccurrence().isAfter(now)) {
LOGGER.debug(String.format("Recurring transaction %s has its first occurrence in the future and thus " +
"cannot be due today",
ReflectionToStringBuilder.toString(recurringTransaction)));
return false; // early return return false; // early return
} }
@@ -141,6 +153,9 @@ public class RecurringTransactionService {
|| recurringTransaction.getHolidayWeekendType() == HolidayWeekendType.PREVIOUS_WORKDAY; || recurringTransaction.getHolidayWeekendType() == HolidayWeekendType.PREVIOUS_WORKDAY;
} }
LOGGER.debug(String.format("Recurring transaction %s due today? %s (defer=%s, dueToday=%s)",
ReflectionToStringBuilder.toString(recurringTransaction), (!defer && dueToday), defer, dueToday));
return !defer && dueToday; return !defer && dueToday;
} }
@@ -162,18 +177,31 @@ public class RecurringTransactionService {
private boolean checkRecurringTransactionDuePast(RecurringTransaction recurringTransaction, LocalDate now) { private boolean checkRecurringTransactionDuePast(RecurringTransaction recurringTransaction, LocalDate now) {
// Recurring transactions with holiday weekend type SAME_DAY or PREVIOUS_WORKDAY can't be due in the past // Recurring transactions with holiday weekend type SAME_DAY or PREVIOUS_WORKDAY can't be due in the past
if (!HolidayWeekendType.NEXT_WORKDAY.equals(recurringTransaction.getHolidayWeekendType())) { if (!HolidayWeekendType.NEXT_WORKDAY.equals(recurringTransaction.getHolidayWeekendType())) {
LOGGER.debug(String.format("Recurring transaction %s has HWT %s and thus cannot be due in the past",
ReflectionToStringBuilder.toString(recurringTransaction),
recurringTransaction.getHolidayWeekendType()));
return false; // early return return false; // early return
} }
// If a recurring transactions first occurrence is in the future it can never be relevant for this // If a recurring transactions first occurrence is in the future it can never be relevant for this
// method, as this method handles recurring transactions due in the past. // method, as this method handles recurring transactions due in the past.
if (recurringTransaction.getFirstOccurrence().isAfter(now)) { if (recurringTransaction.getFirstOccurrence().isAfter(now)) {
LOGGER.debug(String.format("Recurring transaction %s has its first occurrence in the future and thus " +
"cannot be due in the past",
ReflectionToStringBuilder.toString(recurringTransaction)));
return false; // early return return false; // early return
} }
// If today is a weekend day or holiday the recurring transaction cannot be due today, because the // If today is a weekend day or holiday the recurring transaction cannot be due today, because the
// holiday weekend type says NEXT_WORKDAY. // holiday weekend type says NEXT_WORKDAY.
if (this.ruleService.isHoliday(now) || this.ruleService.isWeekend(now)) { if (this.ruleService.isHoliday(now) || this.ruleService.isWeekend(now)) {
LOGGER.debug(String.format("Recurring transaction %s has HWT %s and today is either a holiday or weekend," +
" thus it cannot be due in the past",
ReflectionToStringBuilder.toString(recurringTransaction),
recurringTransaction.getHolidayWeekendType()));
return false; // early return return false; // early return
} }
@@ -207,6 +235,9 @@ public class RecurringTransactionService {
} }
while (holiday || weekend); while (holiday || weekend);
LOGGER.debug(String.format("Recurring transaction %s is due in the past? %s",
ReflectionToStringBuilder.toString(recurringTransaction), due));
return due; return due;
} }
@@ -227,6 +258,10 @@ public class RecurringTransactionService {
private boolean checkRecurringTransactionDueFuture(RecurringTransaction recurringTransaction, LocalDate now) { private boolean checkRecurringTransactionDueFuture(RecurringTransaction recurringTransaction, LocalDate now) {
// Recurring transactions with holiday weekend type SAME_DAY or PREVIOUS_WORKDAY can't be due in the future // Recurring transactions with holiday weekend type SAME_DAY or PREVIOUS_WORKDAY can't be due in the future
if (!HolidayWeekendType.PREVIOUS_WORKDAY.equals(recurringTransaction.getHolidayWeekendType())) { if (!HolidayWeekendType.PREVIOUS_WORKDAY.equals(recurringTransaction.getHolidayWeekendType())) {
LOGGER.debug(String.format("Recurring transaction %s has HWT %s and thus cannot be due in the future",
ReflectionToStringBuilder.toString(recurringTransaction),
recurringTransaction.getHolidayWeekendType()));
return false; // early return return false; // early return
} }
@@ -260,6 +295,9 @@ public class RecurringTransactionService {
} }
while (holiday || weekend); while (holiday || weekend);
LOGGER.debug(String.format("Recurring transaction %s is due in the future? %s",
ReflectionToStringBuilder.toString(recurringTransaction), due));
return due; return due;
} }

View File

@@ -181,6 +181,32 @@ public class RecurringTransactionService_getAllDueToday_MONTHLY_NEXT_WORKDAYTest
Assert.assertEquals(0, IterableUtils.size(recurringDueToday)); Assert.assertEquals(0, IterableUtils.size(recurringDueToday));
} }
@Test
public void test_() {
// Arrange
final LocalDate now = LocalDate.of(2019, 6, 17); // A monday
final RecurringTransaction recurringTransaction = new RecurringTransaction();
recurringTransaction.setLastOccurrence(LocalDate.of(2019, 6, 15)); // a saturday
recurringTransaction.setFirstOccurrence(LocalDate.of(2019, 5, 15)); // a wednesday
recurringTransaction.setHolidayWeekendType(HolidayWeekendType.NEXT_WORKDAY);
recurringTransaction.setIntervalType(IntervalType.MONTHLY);
Mockito.when(this.recurringTransactionRepository
.findByDeletedFalseAndLastOccurrenceIsNullOrLastOccurrenceGreaterThanEqual(Mockito.any()))
.thenReturn(Collections.singletonList(recurringTransaction));
Mockito.when(this.ruleService.isWeekend(Mockito.any()))
.thenReturn(Boolean.FALSE, Boolean.FALSE, Boolean.TRUE, Boolean.TRUE, Boolean.FALSE);
Mockito.when(this.ruleService.isHoliday(Mockito.any()))
.thenReturn(Boolean.FALSE);
// Act
final Iterable<RecurringTransaction> recurringDueToday = this.classUnderTest.getAllDueToday(now);
// Assert
Assert.assertEquals(1, IterableUtils.size(recurringDueToday));
}
private RecurringTransaction createRecurringTransaction(int days) { private RecurringTransaction createRecurringTransaction(int days) {
final RecurringTransaction recurringTransaction = new RecurringTransaction(); final RecurringTransaction recurringTransaction = new RecurringTransaction();