FINERACT-2384: Incorrect reage transaction date and repayment schedule
This commit is contained in:
+19
@@ -1017,6 +1017,25 @@ public class DataValidatorBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DataValidatorBuilder validateDateAfterOrEqual(final LocalDate date) {
|
||||||
|
if (this.value == null && this.ignoreNullValue) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.value != null && date != null) {
|
||||||
|
final LocalDate dateVal = (LocalDate) this.value;
|
||||||
|
if (DateUtils.isBefore(dateVal, date)) {
|
||||||
|
String validationErrorCode = "validation.msg." + this.resource + "." + this.parameter + ".is.before.than.date";
|
||||||
|
String defaultEnglishMessage = "The parameter `" + this.parameter + "` must be greater than or equal to the provided date: "
|
||||||
|
+ date;
|
||||||
|
final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode, defaultEnglishMessage, this.parameter,
|
||||||
|
dateVal, date);
|
||||||
|
this.dataValidationErrors.add(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public DataValidatorBuilder validateDateBeforeOrEqual(final LocalDate date) {
|
public DataValidatorBuilder validateDateBeforeOrEqual(final LocalDate date) {
|
||||||
if (this.value == null && this.ignoreNullValue) {
|
if (this.value == null && this.ignoreNullValue) {
|
||||||
return this;
|
return this;
|
||||||
|
|||||||
@@ -2139,3 +2139,59 @@ Feature: LoanReAging
|
|||||||
Then Loan Charges tab has the following data:
|
Then Loan Charges tab has the following data:
|
||||||
| Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding |
|
| Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding |
|
||||||
| NSF fee | true | Specified due date | 03 May 2025 | Flat | 20.0 | 0.0 | 0.0 | 20.0 |
|
| NSF fee | true | Specified due date | 03 May 2025 | Flat | 20.0 | 0.0 | 0.0 | 20.0 |
|
||||||
|
Scenario: Verify that Loan re-aging transaction - can be performed before maturity date and merges the corresponding normal installments
|
||||||
|
When Admin sets the business date to "01 January 2024"
|
||||||
|
When Admin creates a client with random data
|
||||||
|
When Admin creates a fully customized loan with the following data:
|
||||||
|
| LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy |
|
||||||
|
| LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 January 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION |
|
||||||
|
And Admin successfully approves the loan on "01 January 2024" with "1000" amount and expected disbursement date on "01 January 2024"
|
||||||
|
When Admin successfully disburse the loan on "01 January 2024" with "1000" EUR transaction amount
|
||||||
|
When Admin sets the business date to "09 March 2024"
|
||||||
|
When Admin creates a Loan re-aging transaction by Loan external ID with the following data:
|
||||||
|
| frequencyNumber | frequencyType | startDate | numberOfInstallments |
|
||||||
|
| 1 | MONTHS | 15 March 2024 | 6 |
|
||||||
|
|
||||||
|
Scenario: Verify that Loan re-aging transaction - can be performed before maturity date and removes additional normal installments
|
||||||
|
When Admin sets the business date to "01 January 2024"
|
||||||
|
When Admin creates a client with random data
|
||||||
|
When Admin creates a fully customized loan with the following data:
|
||||||
|
| LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy |
|
||||||
|
| LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 January 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION |
|
||||||
|
And Admin successfully approves the loan on "01 January 2024" with "1000" amount and expected disbursement date on "01 January 2024"
|
||||||
|
When Admin successfully disburse the loan on "01 January 2024" with "1000" EUR transaction amount
|
||||||
|
When Admin sets the business date to "09 March 2024"
|
||||||
|
When Admin creates a Loan re-aging transaction by Loan external ID with the following data:
|
||||||
|
| frequencyNumber | frequencyType | startDate | numberOfInstallments |
|
||||||
|
| 1 | MONTHS | 15 March 2024 | 1 |
|
||||||
|
|
||||||
|
Scenario: Verify that Loan re-aging transaction - can be performed before maturity date and removes n+1
|
||||||
|
When Admin sets the business date to "01 January 2024"
|
||||||
|
When Admin creates a client with random data
|
||||||
|
When Admin creates a fully customized loan with the following data:
|
||||||
|
| LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy |
|
||||||
|
| LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 January 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION |
|
||||||
|
And Admin successfully approves the loan on "01 January 2024" with "1000" amount and expected disbursement date on "01 January 2024"
|
||||||
|
When Admin successfully disburse the loan on "01 January 2024" with "1000" EUR transaction amount
|
||||||
|
When Admin adds "LOAN_NSF_FEE" due date charge with "1 October 2024" due date and 20 EUR transaction amount
|
||||||
|
|
||||||
|
When Admin sets the business date to "09 March 2024"
|
||||||
|
When Admin creates a Loan re-aging transaction by Loan external ID with the following data:
|
||||||
|
| frequencyNumber | frequencyType | startDate | numberOfInstallments |
|
||||||
|
| 1 | MONTHS | 15 March 2024 | 10 |
|
||||||
|
|
||||||
|
Scenario: Verify that Loan re-aging transaction - can be performed before maturity date and removes additional normal installments and not modifies n+1
|
||||||
|
When Admin sets the business date to "01 January 2024"
|
||||||
|
When Admin creates a client with random data
|
||||||
|
When Admin creates a fully customized loan with the following data:
|
||||||
|
| LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy |
|
||||||
|
| LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 January 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION |
|
||||||
|
And Admin successfully approves the loan on "01 January 2024" with "1000" amount and expected disbursement date on "01 January 2024"
|
||||||
|
When Admin successfully disburse the loan on "01 January 2024" with "1000" EUR transaction amount
|
||||||
|
When Admin adds "LOAN_NSF_FEE" due date charge with "1 October 2024" due date and 20 EUR transaction amount
|
||||||
|
When Admin adds "LOAN_NSF_FEE" due date charge with "1 November 2024" due date and 30 EUR transaction amount
|
||||||
|
|
||||||
|
When Admin sets the business date to "09 March 2024"
|
||||||
|
When Admin creates a Loan re-aging transaction by Loan external ID with the following data:
|
||||||
|
| frequencyNumber | frequencyType | startDate | numberOfInstallments |
|
||||||
|
| 1 | MONTHS | 15 March 2024 | 2 |
|
||||||
|
|||||||
+86
-73
@@ -2842,23 +2842,58 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
private Money aggregatedPenaltyChargesPortion;
|
private Money aggregatedPenaltyChargesPortion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void mergeReAgedInstallment(final LoanRepaymentScheduleInstallment target,
|
||||||
|
final LoanRepaymentScheduleInstallment reAgedInstallment, MonetaryCurrency currency, LocalDate transactionDate) {
|
||||||
|
target.setAdditional(false);
|
||||||
|
target.setReAged(true);
|
||||||
|
target.setFromDate(reAgedInstallment.getFromDate());
|
||||||
|
target.setDueDate(reAgedInstallment.getDueDate());
|
||||||
|
target.setPrincipal(reAgedInstallment.getPrincipal().add(MathUtil.nullToZero(target.getPrincipalCompleted())));
|
||||||
|
target.setInterestCharged(MathUtil.add(reAgedInstallment.getInterestCharged(), target.getInterestPaid()));
|
||||||
|
target.updateObligationsMet(currency, transactionDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void insertOrReplaceRelatedInstallment(List<LoanRepaymentScheduleInstallment> installments,
|
||||||
|
final LoanRepaymentScheduleInstallment reAgedInstallment, final MonetaryCurrency currency, final LocalDate transactionDate) {
|
||||||
|
Optional<LoanRepaymentScheduleInstallment> first = installments.stream()
|
||||||
|
.filter(installment -> Objects.equals(installment.getInstallmentNumber(), reAgedInstallment.getInstallmentNumber()))
|
||||||
|
.findFirst();
|
||||||
|
|
||||||
|
if (first.isPresent()) {
|
||||||
|
int indexOfReplaceInstallment = installments.indexOf(first.get());
|
||||||
|
LoanRepaymentScheduleInstallment target = installments.get(indexOfReplaceInstallment);
|
||||||
|
|
||||||
|
if (target.isAdditional()) {
|
||||||
|
// additional ( N+1 ) installment due date cannot be earlier than its original due date
|
||||||
|
if (!target.getDueDate().isAfter(reAgedInstallment.getDueDate())) {
|
||||||
|
mergeReAgedInstallment(target, reAgedInstallment, currency, transactionDate);
|
||||||
|
} else {
|
||||||
|
installments.add(reAgedInstallment);
|
||||||
|
reAgedInstallment.updateObligationsMet(currency, transactionDate);
|
||||||
|
installments.stream()
|
||||||
|
.filter(i -> i.getInstallmentNumber() != null && i.getInstallmentNumber() >= target.getInstallmentNumber())
|
||||||
|
.forEach(i -> i.setInstallmentNumber(i.getInstallmentNumber() + 1));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mergeReAgedInstallment(target, reAgedInstallment, currency, transactionDate);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
installments.add(reAgedInstallment);
|
||||||
|
reAgedInstallment.updateObligationsMet(currency, transactionDate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void handleReAge(LoanTransaction loanTransaction, TransactionCtx ctx) {
|
private void handleReAge(LoanTransaction loanTransaction, TransactionCtx ctx) {
|
||||||
loanTransaction.resetDerivedComponents();
|
loanTransaction.resetDerivedComponents();
|
||||||
MonetaryCurrency currency = ctx.getCurrency();
|
MonetaryCurrency currency = ctx.getCurrency();
|
||||||
List<LoanRepaymentScheduleInstallment> installments = ctx.getInstallments();
|
List<LoanRepaymentScheduleInstallment> installments = ctx.getInstallments();
|
||||||
|
|
||||||
final LocalDate startDate = loanTransaction.getLoanReAgeParameter().getStartDate();
|
AtomicReference<Money> outstandingPrincipalBalance = new AtomicReference<>(Money.zero(currency));
|
||||||
final LocalDate endDate = calculateReAgedInstallmentEndDate(loanTransaction.getLoanReAgeParameter());
|
|
||||||
final AtomicReference<Money> outstandingPrincipalBalance = new AtomicReference<>(Money.zero(currency));
|
|
||||||
installments.forEach(i -> {
|
installments.forEach(i -> {
|
||||||
final boolean shouldInclude = !i.isAdditional() || i.getDueDate().isBefore(startDate)
|
Money principalOutstanding = i.getPrincipalOutstanding(currency);
|
||||||
|| (!i.getDueDate().isBefore(startDate) && i.getDueDate().isBefore(endDate));
|
if (principalOutstanding.isGreaterThanZero()) {
|
||||||
if (shouldInclude) {
|
outstandingPrincipalBalance.set(outstandingPrincipalBalance.get().add(principalOutstanding));
|
||||||
Money principalOutstanding = i.getPrincipalOutstanding(currency);
|
i.addToPrincipal(loanTransaction.getTransactionDate(), principalOutstanding.negated());
|
||||||
if (principalOutstanding.isGreaterThanZero()) {
|
|
||||||
outstandingPrincipalBalance.set(outstandingPrincipalBalance.get().add(principalOutstanding));
|
|
||||||
i.addToPrincipal(loanTransaction.getTransactionDate(), principalOutstanding.negated());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2878,27 +2913,45 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
adjustCalculatedPrincipal = outstandingPrincipalBalance.get()
|
adjustCalculatedPrincipal = outstandingPrincipalBalance.get()
|
||||||
.minus(calculatedPrincipal.multipliedBy(loanTransaction.getLoanReAgeParameter().getNumberOfInstallments()));
|
.minus(calculatedPrincipal.multipliedBy(loanTransaction.getLoanReAgeParameter().getNumberOfInstallments()));
|
||||||
}
|
}
|
||||||
final LoanRepaymentScheduleInstallment lastNormalInstallment = installments.stream() //
|
|
||||||
.filter(i -> !i.isDownPayment() && i.getDueDate().isBefore(loanTransaction.getTransactionDate())) //
|
|
||||||
.reduce((first, second) -> second) //
|
|
||||||
.orElseThrow();
|
|
||||||
|
|
||||||
LoanRepaymentScheduleInstallment reAgedInstallment = createOrConvertReAgedInstallment(installments, startDate,
|
Optional<LoanRepaymentScheduleInstallment> lastNormalInstallmentOptional = installments.stream().filter(i -> !i.isDownPayment())
|
||||||
loanTransaction.getLoanReAgeParameter(), lastNormalInstallment.getLoan(), lastNormalInstallment.getInstallmentNumber() + 1,
|
.filter(i -> i.getDueDate().isBefore(loanTransaction.getTransactionDate())).reduce((first, second) -> second);
|
||||||
lastNormalInstallment.getDueDate(), calculatedPrincipal.getAmount());
|
|
||||||
reAgedInstallment.updateObligationsMet(currency, loanTransaction.getTransactionDate());
|
int reAgedInstallmentNumber;
|
||||||
|
LocalDate fromDate;
|
||||||
|
Loan loan;
|
||||||
|
if (lastNormalInstallmentOptional.isEmpty()) {
|
||||||
|
LoanRepaymentScheduleInstallment firstNormalInstallment = installments.stream().filter(i -> !i.isDownPayment())
|
||||||
|
.min(Comparator.comparing(LoanRepaymentScheduleInstallment::getDueDate)).orElseThrow();
|
||||||
|
reAgedInstallmentNumber = firstNormalInstallment.getInstallmentNumber();
|
||||||
|
fromDate = firstNormalInstallment.getFromDate();
|
||||||
|
loan = firstNormalInstallment.getLoan();
|
||||||
|
} else {
|
||||||
|
LoanRepaymentScheduleInstallment lastNormalInstallment = lastNormalInstallmentOptional.get();
|
||||||
|
reAgedInstallmentNumber = lastNormalInstallment.getInstallmentNumber() + 1;
|
||||||
|
fromDate = lastNormalInstallment.getDueDate();
|
||||||
|
loan = lastNormalInstallment.getLoan();
|
||||||
|
}
|
||||||
|
|
||||||
|
LoanRepaymentScheduleInstallment reAgedInstallment = LoanRepaymentScheduleInstallment.newReAgedInstallment(loan,
|
||||||
|
reAgedInstallmentNumber, fromDate, loanTransaction.getLoanReAgeParameter().getStartDate(), calculatedPrincipal.getAmount());
|
||||||
|
insertOrReplaceRelatedInstallment(installments, reAgedInstallment, currency, loanTransaction.getTransactionDate());
|
||||||
|
|
||||||
for (int i = 1; i < loanTransaction.getLoanReAgeParameter().getNumberOfInstallments(); i++) {
|
for (int i = 1; i < loanTransaction.getLoanReAgeParameter().getNumberOfInstallments(); i++) {
|
||||||
final LocalDate calculatedDueDate = calculateReAgedInstallmentDueDate(loanTransaction.getLoanReAgeParameter(),
|
LocalDate calculatedDueDate = calculateReAgedInstallmentDueDate(loanTransaction.getLoanReAgeParameter(),
|
||||||
reAgedInstallment.getDueDate());
|
reAgedInstallment.getDueDate());
|
||||||
|
int nextReAgedInstallmentNumber = reAgedInstallment.getInstallmentNumber() + 1;
|
||||||
reAgedInstallment = createOrConvertReAgedInstallment(installments, calculatedDueDate, loanTransaction.getLoanReAgeParameter(),
|
reAgedInstallment = LoanRepaymentScheduleInstallment.newReAgedInstallment(reAgedInstallment.getLoan(),
|
||||||
reAgedInstallment.getLoan(), reAgedInstallment.getInstallmentNumber() + 1, reAgedInstallment.getDueDate(),
|
nextReAgedInstallmentNumber, reAgedInstallment.getDueDate(), calculatedDueDate, calculatedPrincipal.getAmount());
|
||||||
calculatedPrincipal.getAmount());
|
if (i + 1 == loanTransaction.getLoanReAgeParameter().getNumberOfInstallments()) {
|
||||||
reAgedInstallment.updateObligationsMet(currency, loanTransaction.getTransactionDate());
|
reAgedInstallment.addToPrincipal(loanTransaction.getTransactionDate(), adjustCalculatedPrincipal);
|
||||||
|
}
|
||||||
|
insertOrReplaceRelatedInstallment(installments, reAgedInstallment, currency, loanTransaction.getTransactionDate());
|
||||||
}
|
}
|
||||||
reAgedInstallment.addToPrincipal(loanTransaction.getTransactionDate(), adjustCalculatedPrincipal);
|
int lastReAgedInstallmentNumber = reAgedInstallment.getInstallmentNumber();
|
||||||
|
List<LoanRepaymentScheduleInstallment> toRemove = installments.stream().filter(i -> i != null && !i.isAdditional()
|
||||||
|
&& i.getInstallmentNumber() != null && i.getInstallmentNumber() > lastReAgedInstallmentNumber).toList();
|
||||||
|
toRemove.forEach(installments::remove);
|
||||||
reprocessInstallments(installments);
|
reprocessInstallments(installments);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2915,57 +2968,17 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private LoanRepaymentScheduleInstallment convertAdditionalToReAged(final LoanRepaymentScheduleInstallment installment,
|
|
||||||
final LocalDate fromDate, final LocalDate dueDate, final BigDecimal principalAmount) {
|
|
||||||
installment.setAdditional(false);
|
|
||||||
installment.setReAged(true);
|
|
||||||
installment.updateFromDate(fromDate);
|
|
||||||
installment.updateDueDate(dueDate);
|
|
||||||
installment.updatePrincipal(principalAmount);
|
|
||||||
return installment;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<LoanRepaymentScheduleInstallment> findAdditionalForFrequency(final List<LoanRepaymentScheduleInstallment> installments,
|
|
||||||
final LocalDate targetDueDate, final LoanReAgeParameter reAgeParameter) {
|
|
||||||
final LocalDate nextReAgedDate = calculateReAgedInstallmentDueDate(reAgeParameter, targetDueDate);
|
|
||||||
|
|
||||||
return installments.stream() //
|
|
||||||
.filter(LoanRepaymentScheduleInstallment::isAdditional) //
|
|
||||||
.filter(ai -> !ai.getDueDate().isBefore(targetDueDate) && ai.getDueDate().isBefore(nextReAgedDate)).findAny();
|
|
||||||
}
|
|
||||||
|
|
||||||
private LoanRepaymentScheduleInstallment createOrConvertReAgedInstallment(final List<LoanRepaymentScheduleInstallment> installments,
|
|
||||||
final LocalDate targetDueDate, final LoanReAgeParameter reAgeParameter, final Loan loan, final int installmentNumber,
|
|
||||||
final LocalDate fromDate, final BigDecimal principalAmount) {
|
|
||||||
final Optional<LoanRepaymentScheduleInstallment> additionalInstallment = findAdditionalForFrequency(installments, targetDueDate,
|
|
||||||
reAgeParameter);
|
|
||||||
|
|
||||||
if (additionalInstallment.isPresent()) {
|
|
||||||
return convertAdditionalToReAged(additionalInstallment.get(), fromDate, targetDueDate, principalAmount);
|
|
||||||
} else {
|
|
||||||
final LoanRepaymentScheduleInstallment reAgedInstallment = LoanRepaymentScheduleInstallment.newReAgedInstallment(loan,
|
|
||||||
installmentNumber, fromDate, targetDueDate, principalAmount);
|
|
||||||
installments.add(reAgedInstallment);
|
|
||||||
return reAgedInstallment;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private LocalDate calculateReAgedInstallmentEndDate(final LoanReAgeParameter reAgeParameter) {
|
|
||||||
return calculateReAgedNextDate(reAgeParameter.getFrequencyType(), reAgeParameter.getStartDate(),
|
|
||||||
reAgeParameter.getFrequencyNumber(), reAgeParameter.getNumberOfInstallments());
|
|
||||||
}
|
|
||||||
|
|
||||||
private LocalDate calculateReAgedInstallmentDueDate(final LoanReAgeParameter reAgeParameter, final LocalDate dueDate) {
|
private LocalDate calculateReAgedInstallmentDueDate(final LoanReAgeParameter reAgeParameter, final LocalDate dueDate) {
|
||||||
return calculateReAgedNextDate(reAgeParameter.getFrequencyType(), dueDate, reAgeParameter.getFrequencyNumber(), 1);
|
return calculateReAgedNextDate(reAgeParameter.getFrequencyType(), dueDate, reAgeParameter.getFrequencyNumber());
|
||||||
}
|
}
|
||||||
|
|
||||||
private LocalDate calculateReAgedNextDate(final PeriodFrequencyType frequencyType, final LocalDate dueDate,
|
private LocalDate calculateReAgedNextDate(final PeriodFrequencyType frequencyType, final LocalDate dueDate,
|
||||||
final Integer frequencyNumber, final Integer numberOfInstallments) {
|
final Integer frequencyNumber) {
|
||||||
return switch (frequencyType) {
|
return switch (frequencyType) {
|
||||||
case DAYS -> dueDate.plusDays((long) frequencyNumber * numberOfInstallments);
|
case DAYS -> dueDate.plusDays((long) frequencyNumber);
|
||||||
case WEEKS -> dueDate.plusWeeks((long) frequencyNumber * numberOfInstallments);
|
case WEEKS -> dueDate.plusWeeks((long) frequencyNumber);
|
||||||
case MONTHS -> dueDate.plusMonths((long) frequencyNumber * numberOfInstallments);
|
case MONTHS -> dueDate.plusMonths((long) frequencyNumber);
|
||||||
case YEARS -> dueDate.plusYears((long) frequencyNumber * numberOfInstallments);
|
case YEARS -> dueDate.plusYears((long) frequencyNumber);
|
||||||
default -> throw new UnsupportedOperationException();
|
default -> throw new UnsupportedOperationException();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-7
@@ -37,6 +37,7 @@ import org.apache.fineract.infrastructure.event.business.domain.loan.reaging.Loa
|
|||||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.reaging.LoanReAgeTransactionBusinessEvent;
|
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.reaging.LoanReAgeTransactionBusinessEvent;
|
||||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.reaging.LoanUndoReAgeTransactionBusinessEvent;
|
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.reaging.LoanUndoReAgeTransactionBusinessEvent;
|
||||||
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
|
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
|
||||||
|
import org.apache.fineract.infrastructure.event.business.service.TransactionHelper;
|
||||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||||
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
||||||
import org.apache.fineract.portfolio.loanaccount.api.LoanReAgingApiConstants;
|
import org.apache.fineract.portfolio.loanaccount.api.LoanReAgingApiConstants;
|
||||||
@@ -77,6 +78,7 @@ public class LoanReAgingServiceImpl {
|
|||||||
private final LoanUtilService loanUtilService;
|
private final LoanUtilService loanUtilService;
|
||||||
private final LoanScheduleService loanScheduleService;
|
private final LoanScheduleService loanScheduleService;
|
||||||
private final ReprocessLoanTransactionsService reprocessLoanTransactionsService;
|
private final ReprocessLoanTransactionsService reprocessLoanTransactionsService;
|
||||||
|
private final TransactionHelper transactionHelper;
|
||||||
|
|
||||||
public CommandProcessingResult reAge(Long loanId, JsonCommand command) {
|
public CommandProcessingResult reAge(Long loanId, JsonCommand command) {
|
||||||
Loan loan = loanAssembler.assembleFrom(loanId);
|
Loan loan = loanAssembler.assembleFrom(loanId);
|
||||||
@@ -89,14 +91,18 @@ public class LoanReAgingServiceImpl {
|
|||||||
LoanTransaction reAgeTransaction = createReAgeTransaction(loan, command);
|
LoanTransaction reAgeTransaction = createReAgeTransaction(loan, command);
|
||||||
LoanReAgeParameter reAgeParameter = createReAgeParameter(reAgeTransaction, command);
|
LoanReAgeParameter reAgeParameter = createReAgeParameter(reAgeTransaction, command);
|
||||||
reAgeTransaction.setLoanReAgeParameter(reAgeParameter);
|
reAgeTransaction.setLoanReAgeParameter(reAgeParameter);
|
||||||
|
|
||||||
loanTransactionRepository.saveAndFlush(reAgeTransaction);
|
loanTransactionRepository.saveAndFlush(reAgeTransaction);
|
||||||
|
|
||||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanRepaymentScheduleTransactionProcessorFactory
|
if (reAgeTransaction.getTransactionDate().isBefore(reAgeTransaction.getSubmittedOnDate())) {
|
||||||
.determineProcessor(loan.transactionProcessingStrategy());
|
reprocessLoanTransactionsService.reprocessTransactionsWithPostTransactionChecks(loan, reAgeTransaction.getTransactionDate());
|
||||||
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(reAgeTransaction, new TransactionCtx(loan.getCurrency(),
|
} else {
|
||||||
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
|
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanRepaymentScheduleTransactionProcessorFactory
|
||||||
|
.determineProcessor(loan.transactionProcessingStrategy());
|
||||||
|
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(reAgeTransaction,
|
||||||
|
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
|
||||||
|
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
|
||||||
|
|
||||||
|
}
|
||||||
loan.updateLoanScheduleDependentDerivedFields();
|
loan.updateLoanScheduleDependentDerivedFields();
|
||||||
persistNote(loan, command, changes);
|
persistNote(loan, command, changes);
|
||||||
|
|
||||||
@@ -172,7 +178,10 @@ public class LoanReAgingServiceImpl {
|
|||||||
|
|
||||||
// reaging transaction date is always the current business date
|
// reaging transaction date is always the current business date
|
||||||
LocalDate transactionDate = DateUtils.getBusinessLocalDate();
|
LocalDate transactionDate = DateUtils.getBusinessLocalDate();
|
||||||
|
LocalDate startDate = command.dateValueOfParameterNamed(LoanReAgingApiConstants.startDate);
|
||||||
|
if (transactionDate.isAfter(startDate)) {
|
||||||
|
transactionDate = startDate;
|
||||||
|
}
|
||||||
// in case of a reaging transaction, only the outstanding principal amount until the business date is considered
|
// in case of a reaging transaction, only the outstanding principal amount until the business date is considered
|
||||||
Money txPrincipal = loan.getTotalPrincipalOutstandingUntil(transactionDate);
|
Money txPrincipal = loan.getTotalPrincipalOutstandingUntil(transactionDate);
|
||||||
BigDecimal txPrincipalAmount = txPrincipal.getAmount();
|
BigDecimal txPrincipalAmount = txPrincipal.getAmount();
|
||||||
@@ -182,7 +191,6 @@ public class LoanReAgingServiceImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private LoanReAgeParameter createReAgeParameter(LoanTransaction reAgeTransaction, JsonCommand command) {
|
private LoanReAgeParameter createReAgeParameter(LoanTransaction reAgeTransaction, JsonCommand command) {
|
||||||
// TODO: these parameters should be checked when the validations are implemented
|
|
||||||
PeriodFrequencyType periodFrequencyType = command.enumValueOfParameterNamed(LoanReAgingApiConstants.frequencyType,
|
PeriodFrequencyType periodFrequencyType = command.enumValueOfParameterNamed(LoanReAgingApiConstants.frequencyType,
|
||||||
PeriodFrequencyType.class);
|
PeriodFrequencyType.class);
|
||||||
LocalDate startDate = command.dateValueOfParameterNamed(LoanReAgingApiConstants.startDate);
|
LocalDate startDate = command.dateValueOfParameterNamed(LoanReAgingApiConstants.startDate);
|
||||||
|
|||||||
+10
-4
@@ -62,9 +62,13 @@ public class LoanReAgingValidator {
|
|||||||
.notExceedingLengthOf(100);
|
.notExceedingLengthOf(100);
|
||||||
|
|
||||||
LocalDate startDate = command.localDateValueOfParameterNamed(LoanReAgingApiConstants.startDate);
|
LocalDate startDate = command.localDateValueOfParameterNamed(LoanReAgingApiConstants.startDate);
|
||||||
baseDataValidator.reset().parameter(LoanReAgingApiConstants.startDate).value(startDate).notNull()
|
if (loan.isProgressiveSchedule() && !loan.isInterestBearing()) {
|
||||||
.validateDateAfter(loan.getMaturityDate());
|
baseDataValidator.reset().parameter(LoanReAgingApiConstants.startDate).value(startDate).notNull()
|
||||||
|
.validateDateAfterOrEqual(loan.getDisbursementDate());
|
||||||
|
} else {
|
||||||
|
baseDataValidator.reset().parameter(LoanReAgingApiConstants.startDate).value(startDate).notNull()
|
||||||
|
.validateDateAfter(loan.getMaturityDate());
|
||||||
|
}
|
||||||
String frequencyType = command.stringValueOfParameterNamedAllowingNull(LoanReAgingApiConstants.frequencyType);
|
String frequencyType = command.stringValueOfParameterNamedAllowingNull(LoanReAgingApiConstants.frequencyType);
|
||||||
baseDataValidator.reset().parameter(LoanReAgingApiConstants.frequencyType).value(frequencyType).notNull();
|
baseDataValidator.reset().parameter(LoanReAgingApiConstants.frequencyType).value(frequencyType).notNull();
|
||||||
|
|
||||||
@@ -81,7 +85,9 @@ public class LoanReAgingValidator {
|
|||||||
|
|
||||||
private void validateReAgeBusinessRules(Loan loan) {
|
private void validateReAgeBusinessRules(Loan loan) {
|
||||||
// validate reaging shouldn't happen before maturity
|
// validate reaging shouldn't happen before maturity
|
||||||
if (DateUtils.isBefore(getBusinessLocalDate(), loan.getMaturityDate())) {
|
// on progressive loans it can
|
||||||
|
if (!(loan.isProgressiveSchedule() && !loan.isInterestBearing())
|
||||||
|
&& DateUtils.isBefore(getBusinessLocalDate(), loan.getMaturityDate())) {
|
||||||
throw new GeneralPlatformDomainRuleException("error.msg.loan.reage.cannot.be.submitted.before.maturity",
|
throw new GeneralPlatformDomainRuleException("error.msg.loan.reage.cannot.be.submitted.before.maturity",
|
||||||
"Loan cannot be re-aged before maturity", loan.getId());
|
"Loan cannot be re-aged before maturity", loan.getId());
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user