FINERACT-2384: Incorrect reage transaction date and repayment schedule

This commit is contained in:
Soma Sörös
2025-10-01 12:12:43 +02:00
committed by Adam Saghy
parent d6323be7dd
commit d50906bef2
5 changed files with 186 additions and 84 deletions
@@ -1017,6 +1017,25 @@ public class DataValidatorBuilder {
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) {
if (this.value == null && this.ignoreNullValue) {
return this;
@@ -2139,3 +2139,59 @@ Feature: LoanReAging
Then Loan Charges tab has the following data:
| 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 |
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 |
@@ -2842,23 +2842,58 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
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) {
loanTransaction.resetDerivedComponents();
MonetaryCurrency currency = ctx.getCurrency();
List<LoanRepaymentScheduleInstallment> installments = ctx.getInstallments();
final LocalDate startDate = loanTransaction.getLoanReAgeParameter().getStartDate();
final LocalDate endDate = calculateReAgedInstallmentEndDate(loanTransaction.getLoanReAgeParameter());
final AtomicReference<Money> outstandingPrincipalBalance = new AtomicReference<>(Money.zero(currency));
AtomicReference<Money> outstandingPrincipalBalance = new AtomicReference<>(Money.zero(currency));
installments.forEach(i -> {
final boolean shouldInclude = !i.isAdditional() || i.getDueDate().isBefore(startDate)
|| (!i.getDueDate().isBefore(startDate) && i.getDueDate().isBefore(endDate));
if (shouldInclude) {
Money principalOutstanding = i.getPrincipalOutstanding(currency);
if (principalOutstanding.isGreaterThanZero()) {
outstandingPrincipalBalance.set(outstandingPrincipalBalance.get().add(principalOutstanding));
i.addToPrincipal(loanTransaction.getTransactionDate(), principalOutstanding.negated());
}
Money principalOutstanding = i.getPrincipalOutstanding(currency);
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()
.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,
loanTransaction.getLoanReAgeParameter(), lastNormalInstallment.getLoan(), lastNormalInstallment.getInstallmentNumber() + 1,
lastNormalInstallment.getDueDate(), calculatedPrincipal.getAmount());
reAgedInstallment.updateObligationsMet(currency, loanTransaction.getTransactionDate());
Optional<LoanRepaymentScheduleInstallment> lastNormalInstallmentOptional = installments.stream().filter(i -> !i.isDownPayment())
.filter(i -> i.getDueDate().isBefore(loanTransaction.getTransactionDate())).reduce((first, second) -> second);
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++) {
final LocalDate calculatedDueDate = calculateReAgedInstallmentDueDate(loanTransaction.getLoanReAgeParameter(),
LocalDate calculatedDueDate = calculateReAgedInstallmentDueDate(loanTransaction.getLoanReAgeParameter(),
reAgedInstallment.getDueDate());
reAgedInstallment = createOrConvertReAgedInstallment(installments, calculatedDueDate, loanTransaction.getLoanReAgeParameter(),
reAgedInstallment.getLoan(), reAgedInstallment.getInstallmentNumber() + 1, reAgedInstallment.getDueDate(),
calculatedPrincipal.getAmount());
reAgedInstallment.updateObligationsMet(currency, loanTransaction.getTransactionDate());
int nextReAgedInstallmentNumber = reAgedInstallment.getInstallmentNumber() + 1;
reAgedInstallment = LoanRepaymentScheduleInstallment.newReAgedInstallment(reAgedInstallment.getLoan(),
nextReAgedInstallmentNumber, reAgedInstallment.getDueDate(), calculatedDueDate, calculatedPrincipal.getAmount());
if (i + 1 == loanTransaction.getLoanReAgeParameter().getNumberOfInstallments()) {
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);
}
@@ -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) {
return calculateReAgedNextDate(reAgeParameter.getFrequencyType(), dueDate, reAgeParameter.getFrequencyNumber(), 1);
return calculateReAgedNextDate(reAgeParameter.getFrequencyType(), dueDate, reAgeParameter.getFrequencyNumber());
}
private LocalDate calculateReAgedNextDate(final PeriodFrequencyType frequencyType, final LocalDate dueDate,
final Integer frequencyNumber, final Integer numberOfInstallments) {
final Integer frequencyNumber) {
return switch (frequencyType) {
case DAYS -> dueDate.plusDays((long) frequencyNumber * numberOfInstallments);
case WEEKS -> dueDate.plusWeeks((long) frequencyNumber * numberOfInstallments);
case MONTHS -> dueDate.plusMonths((long) frequencyNumber * numberOfInstallments);
case YEARS -> dueDate.plusYears((long) frequencyNumber * numberOfInstallments);
case DAYS -> dueDate.plusDays((long) frequencyNumber);
case WEEKS -> dueDate.plusWeeks((long) frequencyNumber);
case MONTHS -> dueDate.plusMonths((long) frequencyNumber);
case YEARS -> dueDate.plusYears((long) frequencyNumber);
default -> throw new UnsupportedOperationException();
};
}
@@ -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.LoanUndoReAgeTransactionBusinessEvent;
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.portfolio.common.domain.PeriodFrequencyType;
import org.apache.fineract.portfolio.loanaccount.api.LoanReAgingApiConstants;
@@ -77,6 +78,7 @@ public class LoanReAgingServiceImpl {
private final LoanUtilService loanUtilService;
private final LoanScheduleService loanScheduleService;
private final ReprocessLoanTransactionsService reprocessLoanTransactionsService;
private final TransactionHelper transactionHelper;
public CommandProcessingResult reAge(Long loanId, JsonCommand command) {
Loan loan = loanAssembler.assembleFrom(loanId);
@@ -89,14 +91,18 @@ public class LoanReAgingServiceImpl {
LoanTransaction reAgeTransaction = createReAgeTransaction(loan, command);
LoanReAgeParameter reAgeParameter = createReAgeParameter(reAgeTransaction, command);
reAgeTransaction.setLoanReAgeParameter(reAgeParameter);
loanTransactionRepository.saveAndFlush(reAgeTransaction);
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanRepaymentScheduleTransactionProcessorFactory
.determineProcessor(loan.transactionProcessingStrategy());
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(reAgeTransaction, new TransactionCtx(loan.getCurrency(),
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
if (reAgeTransaction.getTransactionDate().isBefore(reAgeTransaction.getSubmittedOnDate())) {
reprocessLoanTransactionsService.reprocessTransactionsWithPostTransactionChecks(loan, reAgeTransaction.getTransactionDate());
} else {
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();
persistNote(loan, command, changes);
@@ -172,7 +178,10 @@ public class LoanReAgingServiceImpl {
// reaging transaction date is always the current business date
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
Money txPrincipal = loan.getTotalPrincipalOutstandingUntil(transactionDate);
BigDecimal txPrincipalAmount = txPrincipal.getAmount();
@@ -182,7 +191,6 @@ public class LoanReAgingServiceImpl {
}
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.class);
LocalDate startDate = command.dateValueOfParameterNamed(LoanReAgingApiConstants.startDate);
@@ -62,9 +62,13 @@ public class LoanReAgingValidator {
.notExceedingLengthOf(100);
LocalDate startDate = command.localDateValueOfParameterNamed(LoanReAgingApiConstants.startDate);
baseDataValidator.reset().parameter(LoanReAgingApiConstants.startDate).value(startDate).notNull()
.validateDateAfter(loan.getMaturityDate());
if (loan.isProgressiveSchedule() && !loan.isInterestBearing()) {
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);
baseDataValidator.reset().parameter(LoanReAgingApiConstants.frequencyType).value(frequencyType).notNull();
@@ -81,7 +85,9 @@ public class LoanReAgingValidator {
private void validateReAgeBusinessRules(Loan loan) {
// 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",
"Loan cannot be re-aged before maturity", loan.getId());
}