Various stuff all over the tree again
- RecurringTransactions can now be used to create Transactions - Improve JavaDoc - Add unit tests and fix various small bugs because of them - Add integration test
This commit is contained in:
@@ -22,7 +22,10 @@ public enum ResponseReason {
|
||||
INVALID_INTERVAL_TYPE(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
MISSING_FIRST_OCCURRENCE(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
INVALID_FIRST_OCCURRENCE_FORMAT(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
INVALID_LAST_OCCURRENCE_FORMAT(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
INVALID_LAST_OCCURRENCE_FORMAT(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
MISSING_RECURRING_TRANSACTION_ID(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
INVALID_RECURRING_TRANSACTION_ID(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
RECURRING_TRANSACTION_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
|
||||
private HttpStatus httpStatus;
|
||||
|
||||
|
||||
@@ -40,4 +40,9 @@ public class RecurringTransactionController {
|
||||
lastOccurrence)
|
||||
.toResponseEntity();
|
||||
}
|
||||
|
||||
@RequestMapping("createTransaction")
|
||||
public ResponseEntity createTransaction(String recurringTransactionId) {
|
||||
return this.recurringTransactionService.createTransaction(recurringTransactionId).toResponseEntity();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ public enum HolidayWeekendType {
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Indicates that the action should be made earlier at the previous day
|
||||
* Indicates that the action should preponed to the previous day
|
||||
* </p>
|
||||
* <pre>
|
||||
* Example 1:
|
||||
|
||||
@@ -8,6 +8,8 @@ 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.StringUtils;
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
@@ -17,6 +19,7 @@ import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@@ -34,6 +37,9 @@ public class RecurringTransactionService {
|
||||
@Autowired
|
||||
private FinancerConfig financerConfig;
|
||||
|
||||
@Autowired
|
||||
private TransactionService transactionService;
|
||||
|
||||
public Iterable<RecurringTransaction> getAll() {
|
||||
return this.recurringTransactionRepository.findAll();
|
||||
}
|
||||
@@ -68,8 +74,8 @@ public class RecurringTransactionService {
|
||||
//@formatter:off
|
||||
return IterableUtils.toList(allRecurringTransactions).stream()
|
||||
.filter((rt) -> checkRecurringTransactionDueToday(rt, now) ||
|
||||
checkRecurringTransactionDuePast(rt, now))
|
||||
// TODO checkRecurringTransactionDueFuture for HolidayWeekendType.PREVIOUS_WORKDAY
|
||||
checkRecurringTransactionDuePast(rt, now) ||
|
||||
checkRecurringTransactionDueFuture(rt, now))
|
||||
.collect(Collectors.toList());
|
||||
//@formatter:on
|
||||
}
|
||||
@@ -167,6 +173,59 @@ public class RecurringTransactionService {
|
||||
return due;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks whether the given {@link RecurringTransaction} will actually be due in the close future will
|
||||
* be preponed to <i>maybe</i> today because the actual due day will be a holiday or weekend day and the {@link
|
||||
* RecurringTransaction#getHolidayWeekendType() holiday weekend type} is {@link
|
||||
* HolidayWeekendType#PREVIOUS_WORKDAY}. The period this method considers starts with today and ends with the next
|
||||
* workday (no {@link RuleService#isHoliday(LocalDate) holiday}, not a {@link RuleService#isWeekend(LocalDate)
|
||||
* weekend day}) whereas the end is exclusive, because if the recurring transaction will due at the next workday day
|
||||
* it does not need to be preponed.
|
||||
*
|
||||
* @param recurringTransaction to check whether it is due today
|
||||
* @param now today's date
|
||||
*
|
||||
* @return <code>true</code> if the recurring transaction is due today, <code>false</code> otherwise
|
||||
*/
|
||||
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
|
||||
if (!HolidayWeekendType.PREVIOUS_WORKDAY.equals(recurringTransaction.getHolidayWeekendType())) {
|
||||
return false; // early return
|
||||
}
|
||||
|
||||
boolean weekend;
|
||||
boolean holiday;
|
||||
LocalDate tomorrow = now;
|
||||
boolean due = false;
|
||||
|
||||
// Go forth in time until we hit the first non-holiday, non-weekend day
|
||||
// and check for every day in between if the given recurring transaction will be due on this day
|
||||
do {
|
||||
tomorrow = tomorrow.plusDays(1);
|
||||
holiday = this.ruleService.isHoliday(tomorrow);
|
||||
weekend = this.ruleService.isWeekend(tomorrow);
|
||||
|
||||
if (holiday || weekend) {
|
||||
// Lambdas require final local variables
|
||||
final LocalDate finalTomorrow = tomorrow;
|
||||
|
||||
// For an explanation of the expression see the ...DueToday method
|
||||
due = recurringTransaction.getFirstOccurrence()
|
||||
.datesUntil(tomorrow.plusDays(1), this.ruleService
|
||||
.getPeriodForInterval(recurringTransaction
|
||||
.getIntervalType()))
|
||||
.anyMatch((d) -> d.equals(finalTomorrow));
|
||||
|
||||
if (due) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
while (holiday || weekend);
|
||||
|
||||
return due;
|
||||
}
|
||||
|
||||
@Transactional(propagation = Propagation.REQUIRED)
|
||||
public ResponseReason createRecurringTransaction(String fromAccountKey, String toAccountKey, Long amount,
|
||||
String description, String holidayWeekendType,
|
||||
@@ -192,6 +251,7 @@ public class RecurringTransactionService {
|
||||
response = ResponseReason.OK;
|
||||
} catch (Exception e) {
|
||||
// TODO log
|
||||
e.printStackTrace();
|
||||
|
||||
response = ResponseReason.UNKNOWN_ERROR;
|
||||
}
|
||||
@@ -227,8 +287,12 @@ public class RecurringTransactionService {
|
||||
recurringTransaction.setIntervalType(IntervalType.valueOf(intervalType));
|
||||
recurringTransaction.setFirstOccurrence(LocalDate
|
||||
.parse(firstOccurrence, DateTimeFormatter.ofPattern(this.financerConfig.getDateFormat())));
|
||||
recurringTransaction.setLastOccurrence(LocalDate
|
||||
.parse(lastOccurrence, DateTimeFormatter.ofPattern(this.financerConfig.getDateFormat())));
|
||||
|
||||
// lastOccurrence is optional
|
||||
if (StringUtils.isNotEmpty(lastOccurrence)) {
|
||||
recurringTransaction.setLastOccurrence(LocalDate
|
||||
.parse(lastOccurrence, DateTimeFormatter.ofPattern(this.financerConfig.getDateFormat())));
|
||||
}
|
||||
|
||||
return recurringTransaction;
|
||||
}
|
||||
@@ -276,13 +340,15 @@ public class RecurringTransactionService {
|
||||
response = ResponseReason.MISSING_FIRST_OCCURRENCE;
|
||||
}
|
||||
|
||||
try {
|
||||
LocalDate.parse(firstOccurrence, DateTimeFormatter.ofPattern(this.financerConfig.getDateFormat()));
|
||||
} catch (DateTimeParseException e) {
|
||||
response = ResponseReason.INVALID_FIRST_OCCURRENCE_FORMAT;
|
||||
if (response == null && firstOccurrence != null) {
|
||||
try {
|
||||
LocalDate.parse(firstOccurrence, DateTimeFormatter.ofPattern(this.financerConfig.getDateFormat()));
|
||||
} catch (DateTimeParseException e) {
|
||||
response = ResponseReason.INVALID_FIRST_OCCURRENCE_FORMAT;
|
||||
}
|
||||
}
|
||||
|
||||
if (lastOccurrence != null) {
|
||||
if (response == null && lastOccurrence != null) {
|
||||
try {
|
||||
LocalDate.parse(lastOccurrence, DateTimeFormatter.ofPattern(this.financerConfig.getDateFormat()));
|
||||
} catch (DateTimeParseException e) {
|
||||
@@ -292,4 +358,28 @@ public class RecurringTransactionService {
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
@Transactional(propagation = Propagation.REQUIRED)
|
||||
public ResponseReason createTransaction(String recurringTransactionId) {
|
||||
if (recurringTransactionId == null) {
|
||||
return ResponseReason.MISSING_RECURRING_TRANSACTION_ID;
|
||||
} else if (!NumberUtils.isCreatable(recurringTransactionId)) {
|
||||
return ResponseReason.INVALID_RECURRING_TRANSACTION_ID;
|
||||
}
|
||||
|
||||
final Optional<RecurringTransaction> optionalRecurringTransaction = this.recurringTransactionRepository
|
||||
.findById(Long.valueOf(recurringTransactionId));
|
||||
|
||||
if (!optionalRecurringTransaction.isPresent()) {
|
||||
return ResponseReason.RECURRING_TRANSACTION_NOT_FOUND;
|
||||
}
|
||||
|
||||
final RecurringTransaction recurringTransaction = optionalRecurringTransaction.get();
|
||||
|
||||
return this.transactionService.createTransaction(recurringTransaction.getFromAccount().getKey(),
|
||||
recurringTransaction.getToAccount().getKey(),
|
||||
recurringTransaction.getAmount(),
|
||||
LocalDate.now().format(DateTimeFormatter.ofPattern(this.financerConfig.getDateFormat())),
|
||||
recurringTransaction.getDescription());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ public class TransactionService {
|
||||
|
||||
/**
|
||||
* @param accountKey the key of the account to get the transactions for
|
||||
*
|
||||
* @return all transactions for the given account, for all time. Returns an empty list if the given key does not
|
||||
* match any account.
|
||||
*/
|
||||
@@ -94,6 +95,7 @@ public class TransactionService {
|
||||
* @param amount the transaction amount
|
||||
* @param description the description of the transaction
|
||||
* @param date the date of the transaction
|
||||
*
|
||||
* @return the build {@link Transaction} instance
|
||||
*/
|
||||
private Transaction buildTransaction(Account fromAccount, Account toAccount, Long amount, String description, String date) {
|
||||
@@ -115,6 +117,7 @@ public class TransactionService {
|
||||
* @param toAccount the to account
|
||||
* @param amount the transaction amount
|
||||
* @param date the transaction date
|
||||
*
|
||||
* @return the first error found or <code>null</code> if all parameters are valid
|
||||
*/
|
||||
private ResponseReason validateParameters(Account fromAccount, Account toAccount, Long amount, String date) {
|
||||
@@ -134,16 +137,14 @@ public class TransactionService {
|
||||
response = ResponseReason.AMOUNT_ZERO;
|
||||
} else if (date == null) {
|
||||
response = ResponseReason.MISSING_DATE;
|
||||
} else if (date != null) {
|
||||
try {
|
||||
LocalDate.parse(date, DateTimeFormatter.ofPattern(this.financerConfig.getDateFormat()));
|
||||
} catch (DateTimeParseException e) {
|
||||
response = ResponseReason.INVALID_DATE_FORMAT;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
LocalDate.parse(date, DateTimeFormatter.ofPattern(this.financerConfig.getDateFormat()));
|
||||
}
|
||||
catch (DateTimeParseException e) {
|
||||
response = ResponseReason.INVALID_DATE_FORMAT;
|
||||
}
|
||||
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user