FINERACT-2059: Re-aging repayment schedule handling
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanReAgingApiConstants.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanReAgingApiConstants.java
index 96411c2..56c18c3 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanReAgingApiConstants.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanReAgingApiConstants.java
@@ -24,7 +24,8 @@
     String dateFormatParameterName = "dateFormat";
     String externalIdParameterName = "externalId";
 
-    String frequency = "frequency";
+    String frequencyType = "frequencyType";
+    String frequencyNumber = "frequencyNumber";
     String startDate = "startDate";
     String numberOfInstallments = "numberOfInstallments";
 }
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
index d9d9c93..e5266b1 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
@@ -2166,8 +2166,9 @@
     }
 
     private LocalDate determineExpectedMaturityDate() {
-        final int numberOfInstallments = this.repaymentScheduleInstallments.size();
-        List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
+        List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments().stream()
+                .filter(i -> !i.isDownPayment() && !i.isAdditional()).toList();
+        final int numberOfInstallments = installments.size();
         LocalDate maturityDate = installments.get(numberOfInstallments - 1).getDueDate();
         ListIterator<LoanRepaymentScheduleInstallment> iterator = installments.listIterator(numberOfInstallments);
         while (iterator.hasPrevious()) {
@@ -3432,7 +3433,8 @@
         final List<LoanTransaction> repaymentsOrWaivers = new ArrayList<>();
         List<LoanTransaction> trans = getLoanTransactions();
         for (final LoanTransaction transaction : trans) {
-            if (transaction.isNotReversed() && (transaction.isChargeOff() || !transaction.isNonMonetaryTransaction())) {
+            if (transaction.isNotReversed() && (transaction.isChargeOff() || transaction.isReAge() || transaction.isReAmortize()
+                    || !transaction.isNonMonetaryTransaction())) {
                 repaymentsOrWaivers.add(transaction);
             }
         }
@@ -3670,10 +3672,10 @@
     }
 
     private LocalDate getNextUnpaidInstallmentDueDate() {
-        LocalDate nextUnpaidInstallmentDate = null;
         List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
         LocalDate currentBusinessDate = DateUtils.getBusinessLocalDate();
         LocalDate expectedMaturityDate = determineExpectedMaturityDate();
+        LocalDate nextUnpaidInstallmentDate = expectedMaturityDate;
 
         for (final LoanRepaymentScheduleInstallment installment : installments) {
             boolean isCurrentDateBeforeInstallmentAndLoanPeriod = DateUtils.isBefore(currentBusinessDate, installment.getDueDate())
@@ -5664,7 +5666,8 @@
                 lastCompoundingDate = compoundingDetail.getEffectiveDate();
             }
             List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
-            LoanRepaymentScheduleInstallment lastInstallment = installments.get(installments.size() - 1);
+            LoanRepaymentScheduleInstallment lastInstallment = LoanRepaymentScheduleInstallment
+                    .getLastNonDownPaymentInstallment(installments);
             reverseTransactionsPostEffectiveDate(incomeTransactions, lastInstallment.getDueDate());
             reverseTransactionsPostEffectiveDate(accrualTransactions, lastInstallment.getDueDate());
         }
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
index 72ffbd7..c1ff292 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
@@ -29,6 +29,7 @@
 import java.math.BigDecimal;
 import java.time.LocalDate;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
 import org.apache.fineract.infrastructure.core.service.DateUtils;
@@ -145,6 +146,9 @@
     @Column(name = "is_down_payment", nullable = false)
     private boolean isDownPayment;
 
+    @Column(name = "is_re_aged", nullable = false)
+    private boolean isReAged;
+
     @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, mappedBy = "loanRepaymentScheduleInstallment")
     private Set<LoanInterestRecalcualtionAdditionalDetails> loanCompoundingDetails = new HashSet<>();
 
@@ -223,6 +227,36 @@
         this.obligationsMet = false;
     }
 
+    public LoanRepaymentScheduleInstallment(Loan loan, Integer installmentNumber, LocalDate fromDate, LocalDate dueDate,
+            BigDecimal principal, BigDecimal interestCharged, BigDecimal feeChargesCharged, BigDecimal penaltyCharges,
+            BigDecimal creditedPrincipal, BigDecimal creditedFee, BigDecimal creditedPenalty, boolean additional, boolean isDownPayment,
+            boolean isReAged) {
+        this.loan = loan;
+        this.installmentNumber = installmentNumber;
+        this.fromDate = fromDate;
+        this.dueDate = dueDate;
+        this.principal = principal;
+        this.interestCharged = interestCharged;
+        this.feeChargesCharged = feeChargesCharged;
+        this.penaltyCharges = penaltyCharges;
+        this.creditedPrincipal = creditedPrincipal;
+        this.creditedFee = creditedFee;
+        this.creditedPenalty = creditedPenalty;
+        this.additional = additional;
+        this.isDownPayment = isDownPayment;
+        this.isReAged = isReAged;
+    }
+
+    public static LoanRepaymentScheduleInstallment newReAgedInstallment(final Loan loan, final Integer installmentNumber,
+            final LocalDate fromDate, final LocalDate dueDate, final BigDecimal principal) {
+        return new LoanRepaymentScheduleInstallment(loan, installmentNumber, fromDate, dueDate, principal, null, null, null, null, null,
+                null, false, false, true);
+    }
+
+    public static LoanRepaymentScheduleInstallment getLastNonDownPaymentInstallment(List<LoanRepaymentScheduleInstallment> installments) {
+        return installments.stream().filter(i -> !i.isDownPayment()).reduce((first, second) -> second).orElseThrow();
+    }
+
     private BigDecimal defaultToNullIfZero(final BigDecimal value) {
         BigDecimal result = value;
         if (BigDecimal.ZERO.compareTo(value) == 0) {
@@ -400,6 +434,10 @@
         return this.installmentNumber.compareTo(o.installmentNumber);
     }
 
+    public int compareToByDueDate(LoanRepaymentScheduleInstallment o) {
+        return this.dueDate.compareTo(o.dueDate);
+    }
+
     public boolean isPrincipalNotCompleted(final MonetaryCurrency currency) {
         return !isPrincipalCompleted(currency);
     }
@@ -1022,4 +1060,8 @@
     public enum PaymentAction {
         PAY, UNPAY
     }
+
+    public boolean isReAged() {
+        return isReAged;
+    }
 }
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
index 1b737fe..a37e32e 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
@@ -865,7 +865,8 @@
                 || LoanTransactionType.MARKED_FOR_RESCHEDULING.equals(getTypeOf())
                 || LoanTransactionType.APPROVE_TRANSFER.equals(getTypeOf()) || LoanTransactionType.INITIATE_TRANSFER.equals(getTypeOf())
                 || LoanTransactionType.REJECT_TRANSFER.equals(getTypeOf()) || LoanTransactionType.WITHDRAW_TRANSFER.equals(getTypeOf())
-                || LoanTransactionType.CHARGE_OFF.equals(getTypeOf()));
+                || LoanTransactionType.CHARGE_OFF.equals(getTypeOf()) || LoanTransactionType.REAMORTIZE.equals(getTypeOf())
+                || LoanTransactionType.REAGE.equals(getTypeOf()));
     }
 
     public void updateOutstandingLoanBalance(BigDecimal outstandingLoanBalance) {
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java
index 3e33114..4a02782 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java
@@ -61,7 +61,9 @@
     CHARGE_ADJUSTMENT(26, "loanTransactionType.chargeAdjustment"), //
     CHARGE_OFF(27, "loanTransactionType.chargeOff"), //
     DOWN_PAYMENT(28, "loanTransactionType.downPayment"), //
-    REAGE(29, "loanTransactionType.reAge"), REAMORTIZE(30, "loanTransactionType.reAmortize");
+    REAGE(29, "loanTransactionType.reAge"), //
+    REAMORTIZE(30, "loanTransactionType.reAmortize"), //
+    ;
 
     private final Integer value;
     private final String code;
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/reaging/LoanReAgeParameter.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/reaging/LoanReAgeParameter.java
index 78198ea..fcba7a0 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/reaging/LoanReAgeParameter.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/reaging/LoanReAgeParameter.java
@@ -40,8 +40,11 @@
     private Long loanTransactionId;
 
     @Enumerated(EnumType.STRING)
-    @Column(name = "frequency", nullable = false)
-    private PeriodFrequencyType frequency;
+    @Column(name = "frequency_type", nullable = false)
+    private PeriodFrequencyType frequencyType;
+
+    @Column(name = "frequency_number", nullable = false)
+    private Integer frequencyNumber;
 
     @Column(name = "start_date", nullable = false)
     private LocalDate startDate;
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
index b05c2ae..ba9eccf 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
@@ -479,7 +479,8 @@
         loanTransaction.resetDerivedComponents();
         List<LoanTransactionToRepaymentScheduleMapping> transactionMappings = new ArrayList<>();
         final Comparator<LoanRepaymentScheduleInstallment> byDate = Comparator.comparing(LoanRepaymentScheduleInstallment::getDueDate);
-        installments.sort(byDate);
+        List<LoanRepaymentScheduleInstallment> installmentToBeProcessed = installments.stream().filter(i -> !i.isDownPayment())
+                .sorted(byDate).toList();
         final Money zeroMoney = Money.zero(currency);
         Money transactionAmount = loanTransaction.getAmount(currency);
         Money principalPortion = MathUtil.negativeToZero(loanTransaction.getAmount(currency).minus(overpaymentHolder.getMoneyObject()));
@@ -492,7 +493,7 @@
             final LocalDate transactionDate = loanTransaction.getTransactionDate();
             boolean loanTransactionMapped = false;
             LocalDate pastDueDate = null;
-            for (final LoanRepaymentScheduleInstallment currentInstallment : installments) {
+            for (final LoanRepaymentScheduleInstallment currentInstallment : installmentToBeProcessed) {
                 pastDueDate = currentInstallment.getDueDate();
                 if (!currentInstallment.isAdditional() && DateUtils.isAfter(currentInstallment.getDueDate(), transactionDate)) {
                     currentInstallment.addToCreditedPrincipal(transactionAmount.getAmount());
@@ -526,7 +527,7 @@
             // New installment will be added (N+1 scenario)
             if (!loanTransactionMapped) {
                 if (loanTransaction.getTransactionDate().equals(pastDueDate)) {
-                    LoanRepaymentScheduleInstallment currentInstallment = installments.get(installments.size() - 1);
+                    LoanRepaymentScheduleInstallment currentInstallment = installmentToBeProcessed.get(installmentToBeProcessed.size() - 1);
                     currentInstallment.addToCreditedPrincipal(transactionAmount.getAmount());
                     currentInstallment.addToPrincipal(transactionDate, transactionAmount);
                     if (repaidAmount.isGreaterThanZero()) {
@@ -848,7 +849,8 @@
     protected void addChargeOnlyRepaymentInstallmentIfRequired(Set<LoanCharge> charges,
             List<LoanRepaymentScheduleInstallment> installments) {
         if (!CollectionUtils.isEmpty(charges) && !CollectionUtils.isEmpty(installments)) {
-            LoanRepaymentScheduleInstallment latestRepaymentScheduleInstalment = installments.get(installments.size() - 1);
+            LoanRepaymentScheduleInstallment latestRepaymentScheduleInstalment = installments.stream().filter(i -> !i.isDownPayment())
+                    .reduce((first, second) -> second).orElseThrow();
             LocalDate installmentDueDate = null;
 
             LoanCharge latestCharge = getLatestLoanChargeWithSpecificDueDate(charges);
@@ -867,7 +869,6 @@
                             BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, false, null);
                     installment.markAsAdditional();
                     loan.addLoanRepaymentScheduleInstallment(installment);
-
                 }
             }
         }
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
index d4a6a9f..30d876e 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
@@ -22,7 +22,6 @@
 import static java.util.stream.Collectors.mapping;
 import static java.util.stream.Collectors.toList;
 import static org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum.CHARGEBACK;
-import static org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.REAMORTIZE;
 import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.FEE;
 import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.INTEREST;
 import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.PENALTY;
@@ -44,11 +43,14 @@
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
+import lombok.RequiredArgsConstructor;
 import lombok.Setter;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.NotImplementedException;
@@ -71,6 +73,8 @@
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
 import org.apache.fineract.portfolio.loanaccount.domain.SingleLoanChargeRepaymentScheduleProcessingWrapper;
+import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter;
+import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgingParameterRepository;
 import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
 import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
 import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
@@ -84,12 +88,15 @@
 import org.jetbrains.annotations.Nullable;
 
 @Slf4j
+@RequiredArgsConstructor
 public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRepaymentScheduleTransactionProcessor {
 
     public static final String ADVANCED_PAYMENT_ALLOCATION_STRATEGY = "advanced-payment-allocation-strategy";
 
     public final SingleLoanChargeRepaymentScheduleProcessingWrapper loanChargeProcessor = new SingleLoanChargeRepaymentScheduleProcessingWrapper();
 
+    private final LoanReAgingParameterRepository reAgingParameterRepository;
+
     @Override
     public String getCode() {
         return ADVANCED_PAYMENT_ALLOCATION_STRATEGY;
@@ -144,6 +151,10 @@
                 }
             }
         }
+        // Remove re-aged and additional (N+1) installments (if applicable), those will be recreated during the
+        // reprocessing
+        installments.removeIf(LoanRepaymentScheduleInstallment::isReAged);
+        installments.removeIf(LoanRepaymentScheduleInstallment::isAdditional);
 
         addChargeOnlyRepaymentInstallmentIfRequired(charges, installments);
 
@@ -185,6 +196,7 @@
                     ctx.getOverpaymentHolder());
             case WAIVE_CHARGES -> log.debug("WAIVE_CHARGES transaction will not be processed.");
             case REAMORTIZE -> handleReAmortization(loanTransaction, ctx.getCurrency(), ctx.getInstallments());
+            case REAGE -> handleReAge(loanTransaction, ctx);
             // TODO: Cover rest of the transaction types
             default -> {
                 log.warn("Unhandled transaction processing for transaction type: {}", loanTransaction.getTypeOf());
@@ -201,7 +213,7 @@
                 .toList();
         List<LoanRepaymentScheduleInstallment> futureInstallments = installments.stream() //
                 .filter(installment -> installment.getDueDate().isAfter(transactionDate)) //
-                .filter(installment -> !installment.isAdditional() && !installment.isDownPayment()) //
+                .filter(installment -> !installment.isAdditional() && !installment.isDownPayment() && !installment.isReAged()) //
                 .toList();
 
         BigDecimal overallOverDuePrincipal = ZERO;
@@ -1279,4 +1291,62 @@
         private Money aggregatedInterestPortion;
         private Money aggregatedPenaltyChargesPortion;
     }
+
+    private void handleReAge(LoanTransaction loanTransaction, TransactionCtx ctx) {
+        MonetaryCurrency currency = ctx.getCurrency();
+        List<LoanRepaymentScheduleInstallment> installments = ctx.getInstallments();
+        // Either we have the transaction id or we need to fetch it from context
+        Long loanTransactionId = loanTransaction.getId() != null ? loanTransaction.getId()
+                : ctx.getChangedTransactionDetail().getCurrentTransactionToOldId().get(loanTransaction);
+        LoanReAgeParameter reAgeParameter = reAgingParameterRepository.findByLoanTransactionId(loanTransactionId).orElseThrow();
+        AtomicReference<Money> outstandingPrincipalBalance = new AtomicReference<>(Money.zero(currency));
+        installments.forEach(i -> {
+            Money principalOutstanding = i.getPrincipalOutstanding(currency);
+            if (principalOutstanding.isGreaterThanZero()) {
+                outstandingPrincipalBalance.set(outstandingPrincipalBalance.get().add(principalOutstanding));
+                i.addToPrincipal(loanTransaction.getTransactionDate(), principalOutstanding.negated());
+            }
+        });
+
+        Money calculatedPrincipal = outstandingPrincipalBalance.get().dividedBy(reAgeParameter.getNumberOfInstallments(),
+                MoneyHelper.getRoundingMode());
+        Integer installmentAmountInMultiplesOf = loanTransaction.getLoan().getLoanProduct().getInstallmentAmountInMultiplesOf();
+        if (installmentAmountInMultiplesOf != null) {
+            calculatedPrincipal = Money.roundToMultiplesOf(calculatedPrincipal, installmentAmountInMultiplesOf);
+        }
+        Money adjustCalculatedPrincipal = outstandingPrincipalBalance.get()
+                .minus(calculatedPrincipal.multipliedBy(reAgeParameter.getNumberOfInstallments()));
+        LoanRepaymentScheduleInstallment lastNormalInstallment = installments.stream().filter(i -> !i.isDownPayment())
+                .reduce((first, second) -> second).orElseThrow();
+        LoanRepaymentScheduleInstallment reAgedInstallment = LoanRepaymentScheduleInstallment.newReAgedInstallment(
+                lastNormalInstallment.getLoan(), lastNormalInstallment.getInstallmentNumber() + 1, lastNormalInstallment.getDueDate(),
+                reAgeParameter.getStartDate(), calculatedPrincipal.getAmount());
+        installments.add(reAgedInstallment);
+        for (int i = 1; i < reAgeParameter.getNumberOfInstallments(); i++) {
+            LocalDate calculatedDueDate = calculateReAgedInstallmentDueDate(reAgeParameter, reAgedInstallment.getDueDate());
+            reAgedInstallment = LoanRepaymentScheduleInstallment.newReAgedInstallment(reAgedInstallment.getLoan(),
+                    reAgedInstallment.getInstallmentNumber() + 1, reAgedInstallment.getDueDate(), calculatedDueDate,
+                    calculatedPrincipal.getAmount());
+            installments.add(reAgedInstallment);
+        }
+        reAgedInstallment.addToPrincipal(loanTransaction.getTransactionDate(), adjustCalculatedPrincipal);
+
+        reprocessInstallmentsOrder(installments);
+    }
+
+    private void reprocessInstallmentsOrder(List<LoanRepaymentScheduleInstallment> installments) {
+        AtomicInteger counter = new AtomicInteger(0);
+        installments.stream().sorted(LoanRepaymentScheduleInstallment::compareToByDueDate)
+                .forEachOrdered(i -> i.updateInstallmentNumber(counter.getAndIncrement()));
+    }
+
+    private LocalDate calculateReAgedInstallmentDueDate(LoanReAgeParameter reAgeParameter, LocalDate dueDate) {
+        return switch (reAgeParameter.getFrequencyType()) {
+            case DAYS -> dueDate.plusDays(reAgeParameter.getFrequencyNumber());
+            case WEEKS -> dueDate.plusWeeks(reAgeParameter.getFrequencyNumber());
+            case MONTHS -> dueDate.plusMonths(reAgeParameter.getFrequencyNumber());
+            case YEARS -> dueDate.plusYears(reAgeParameter.getFrequencyNumber());
+            default -> throw new UnsupportedOperationException(reAgeParameter.getFrequencyType().getCode());
+        };
+    }
 }
diff --git a/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml
index 5304770..9655538 100644
--- a/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml
+++ b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml
@@ -42,4 +42,5 @@
   <include relativeToChangelogFile="true" file="parts/1017_add_fee_and_penalty_adjustments_to_loan.xml"/>
   <include relativeToChangelogFile="true" file="parts/1018_rename_credited_principal_back_to_credits_amount.xml"/>
   <include relativeToChangelogFile="true" file="parts/1019_add_fixed_length.xml"/>
+  <include relativeToChangelogFile="true" file="parts/1020_add_re_aged_flag_to_loan_installment.xml"/>
 </databaseChangeLog>
diff --git a/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1020_add_re_aged_flag_to_loan_installment.xml b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1020_add_re_aged_flag_to_loan_installment.xml
new file mode 100644
index 0000000..df8158a
--- /dev/null
+++ b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1020_add_re_aged_flag_to_loan_installment.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements. See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership. The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License. You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied. See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+-->
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
+    <changeSet author="fineract" id="1">
+        <addColumn tableName="m_loan_repayment_schedule">
+            <column name="is_re_aged" type="boolean" defaultValueBoolean="false">
+                <constraints nullable="false"/>
+            </column>
+        </addColumn>
+    </changeSet>
+</databaseChangeLog>
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResourceSwagger.java
index e427143..445bfe8 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResourceSwagger.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResourceSwagger.java
@@ -285,8 +285,10 @@
         public Long writeoffReasonId;
 
         // command=reAge START
-        @Schema(example = "frequency")
-        public String frequency;
+        @Schema(example = "frequencyType")
+        public String frequencyType;
+        @Schema(example = "frequencyNumber")
+        public Integer frequencyNumber;
         @Schema(example = "startDate")
         public String startDate;
         @Schema(example = "numberOfInstallments")
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractCumulativeLoanScheduleGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractCumulativeLoanScheduleGenerator.java
index d2acafe..7a01ba3 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractCumulativeLoanScheduleGenerator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractCumulativeLoanScheduleGenerator.java
@@ -1854,7 +1854,8 @@
                 holidayDetailDTO);
         updateMapWithAmount(principalPortionMap, unprocessed, applicableDate);
         installment.addPrincipalAmount(unprocessed);
-        LoanRepaymentScheduleInstallment lastInstallment = installments.get(installments.size() - 1);
+        LoanRepaymentScheduleInstallment lastInstallment = installments.stream().filter(i -> !i.isDownPayment())
+                .reduce((first, second) -> second).orElseThrow();
         lastInstallment.updatePrincipal(lastInstallment.getPrincipal(unprocessed.getCurrency()).plus(unprocessed).getAmount());
         lastInstallment.payPrincipalComponent(detail.getTransactionDate(), unprocessed);
     }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractProgressiveLoanScheduleGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractProgressiveLoanScheduleGenerator.java
index 98d75ff..dd2db46 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractProgressiveLoanScheduleGenerator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractProgressiveLoanScheduleGenerator.java
@@ -181,6 +181,12 @@
             // }
         }
 
+        // If the disbursement happened after maturity date
+        if (loanApplicationTerms.isMultiDisburseLoan()) {
+            processDisbursements(loanApplicationTerms, chargesDueAtTimeOfDisbursement, scheduleParams, periods,
+                    DateUtils.getBusinessLocalDate().plusDays(1));
+        }
+
         // determine fees and penalties for charges which depends on total
         // loan interest
         updatePeriodsWithCharges(currency, scheduleParams, periods, nonCompoundingCharges, mc);
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
index 42d4ff6..b8dccde 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
@@ -498,7 +498,7 @@
             }
         }
         if (!changes.isEmpty()) {
-
+            loan.updateLoanScheduleDependentDerivedFields();
             loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
 
             final String noteText = command.stringValueOfParameterNamed("note");
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reaging/LoanReAgingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reaging/LoanReAgingServiceImpl.java
index 39bf599..8b27424 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reaging/LoanReAgingServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reaging/LoanReAgingServiceImpl.java
@@ -24,6 +24,7 @@
 import java.time.LocalDate;
 import java.util.Comparator;
 import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
 import lombok.RequiredArgsConstructor;
 import org.apache.fineract.infrastructure.core.api.JsonCommand;
@@ -41,12 +42,18 @@
 import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
 import org.apache.fineract.portfolio.loanaccount.api.LoanReAgingApiConstants;
 import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionComparator;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
 import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter;
 import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgingParameterRepository;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
 import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
+import org.apache.fineract.portfolio.note.domain.Note;
+import org.apache.fineract.portfolio.note.domain.NoteRepository;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -61,6 +68,8 @@
     private final BusinessEventNotifierService businessEventNotifierService;
     private final LoanTransactionRepository loanTransactionRepository;
     private final LoanReAgingParameterRepository reAgingParameterRepository;
+    private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory;
+    private final NoteRepository noteRepository;
 
     public CommandProcessingResult reAge(Long loanId, JsonCommand command) {
         Loan loan = loanAssembler.assembleFrom(loanId);
@@ -77,6 +86,15 @@
         LoanReAgeParameter reAgeParameter = createReAgeParameter(reAgeTransaction, command);
         reAgingParameterRepository.saveAndFlush(reAgeParameter);
 
+        final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanRepaymentScheduleTransactionProcessorFactory
+                .determineProcessor(loan.transactionProcessingStrategy());
+        loanRepaymentScheduleTransactionProcessor.processLatestTransaction(reAgeTransaction,
+                new LoanRepaymentScheduleTransactionProcessor.TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(),
+                        loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney())));
+
+        loan.updateLoanScheduleDependentDerivedFields();
+        persistNote(loan, command, changes);
+
         // delinquency recalculation will be triggered by the event in a decoupled way via a listener
         businessEventNotifierService.notifyPostBusinessEvent(new LoanReAgeBusinessEvent(loan));
         businessEventNotifierService.notifyPostBusinessEvent(new LoanReAgeTransactionBusinessEvent(reAgeTransaction));
@@ -91,15 +109,6 @@
                 .with(changes).build();
     }
 
-    private LoanReAgeParameter createReAgeParameter(LoanTransaction reAgeTransaction, JsonCommand command) {
-        // TODO: these parameters should be checked when the validations are implemented
-        PeriodFrequencyType periodFrequencyType = command.enumValueOfParameterNamed(LoanReAgingApiConstants.frequency,
-                PeriodFrequencyType.class);
-        LocalDate startDate = command.dateValueOfParameterNamed(LoanReAgingApiConstants.startDate);
-        Integer numberOfInstallments = command.integerValueOfParameterNamed(LoanReAgingApiConstants.numberOfInstallments);
-        return new LoanReAgeParameter(reAgeTransaction.getId(), periodFrequencyType, startDate, numberOfInstallments);
-    }
-
     public CommandProcessingResult undoReAge(Long loanId, JsonCommand command) {
         Loan loan = loanAssembler.assembleFrom(loanId);
         reAgingValidator.validateUndoReAge(loan, command);
@@ -115,6 +124,10 @@
         reverseReAgeTransaction(reAgeTransaction, command);
         loanTransactionRepository.saveAndFlush(reAgeTransaction);
 
+        reProcessLoanTransactions(reAgeTransaction.getLoan());
+        loan.updateLoanScheduleDependentDerivedFields();
+        persistNote(loan, command, changes);
+
         // delinquency recalculation will be triggered by the event in a decoupled way via a listener
         businessEventNotifierService.notifyPostBusinessEvent(new LoanUndoReAgeBusinessEvent(loan));
         businessEventNotifierService.notifyPostBusinessEvent(new LoanUndoReAgeTransactionBusinessEvent(reAgeTransaction));
@@ -156,4 +169,35 @@
         return new LoanTransaction(loan, loan.getOffice(), LoanTransactionType.REAGE.getValue(), transactionDate, txPrincipalAmount,
                 txPrincipalAmount, ZERO, ZERO, ZERO, null, false, null, txExternalId);
     }
+
+    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);
+        Integer numberOfInstallments = command.integerValueOfParameterNamed(LoanReAgingApiConstants.numberOfInstallments);
+        Integer periodFrequencyNumber = command.integerValueOfParameterNamed(LoanReAgingApiConstants.frequencyNumber);
+        return new LoanReAgeParameter(reAgeTransaction.getId(), periodFrequencyType, periodFrequencyNumber, startDate,
+                numberOfInstallments);
+    }
+
+    private void reProcessLoanTransactions(Loan loan) {
+        final List<LoanTransaction> filteredTransactions = loan.getLoanTransactions().stream().filter(LoanTransaction::isNotReversed)
+                .filter(t -> t.isChargeOff() || !t.isNonMonetaryTransaction()).sorted(LoanTransactionComparator.INSTANCE).toList();
+
+        final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanRepaymentScheduleTransactionProcessorFactory
+                .determineProcessor(loan.transactionProcessingStrategy());
+        loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(loan.getDisbursementDate(), filteredTransactions,
+                loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges());
+    }
+
+    private void persistNote(Loan loan, JsonCommand command, Map<String, Object> changes) {
+        if (command.hasParameter("note")) {
+            final String note = command.stringValueOfParameterNamed("note");
+            final Note newNote = Note.loanNote(loan, note);
+            changes.put("note", note);
+
+            this.noteRepository.saveAndFlush(newNote);
+        }
+    }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountAutoStarter.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountAutoStarter.java
index 68b3962..905f4f4 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountAutoStarter.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountAutoStarter.java
@@ -20,6 +20,7 @@
 
 import java.util.List;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
+import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgingParameterRepository;
 import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
 import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
 import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.CreocoreLoanRepaymentScheduleTransactionProcessor;
@@ -103,8 +104,9 @@
 
     @Bean
     @Conditional(AdvancedPaymentScheduleTransactionProcessorCondition.class)
-    public AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor() {
-        return new AdvancedPaymentScheduleTransactionProcessor();
+    public AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor(
+            LoanReAgingParameterRepository reAgingParameterRepository) {
+        return new AdvancedPaymentScheduleTransactionProcessor(reAgingParameterRepository);
     }
 
 }
diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0136_loan_reaging_parameters.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0136_loan_reaging_parameters.xml
index 5539fee..381165a 100644
--- a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0136_loan_reaging_parameters.xml
+++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0136_loan_reaging_parameters.xml
@@ -71,4 +71,12 @@
             </column>
         </addColumn>
     </changeSet>
+    <changeSet id="4" author="fineract">
+        <addColumn tableName="m_loan_reage_parameter">
+            <column name="frequency_number" type="SMALLINT" defaultValueNumeric="1">
+                <constraints nullable="false"/>
+            </column>
+        </addColumn>
+        <renameColumn tableName="m_loan_reage_parameter" oldColumnName="frequency" newColumnName="frequency_type" columnDataType="VARCHAR(100)"/>
+    </changeSet>
 </databaseChangeLog>
diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java
index e4ba465..24979b4 100644
--- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java
+++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java
@@ -61,6 +61,7 @@
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
+import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgingParameterRepository;
 import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor.TransactionCtx;
 import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
 import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
@@ -89,6 +90,7 @@
     private static final MonetaryCurrency MONETARY_CURRENCY = new MonetaryCurrency("USD", 2, 1);
     private static final MockedStatic<MoneyHelper> MONEY_HELPER = mockStatic(MoneyHelper.class);
     private AdvancedPaymentScheduleTransactionProcessor underTest;
+    private LoanReAgingParameterRepository reAgingParameterRepository = Mockito.mock(LoanReAgingParameterRepository.class);
 
     @BeforeAll
     public static void init() {
@@ -102,7 +104,7 @@
 
     @BeforeEach
     public void setUp() {
-        underTest = new AdvancedPaymentScheduleTransactionProcessor();
+        underTest = new AdvancedPaymentScheduleTransactionProcessor(reAgingParameterRepository);
 
         ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
         ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT);
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
index 926ce0c..ccf4d02 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
@@ -378,11 +378,12 @@
         inlineLoanCOBHelper.executeInlineCOB(List.of(loanId));
     }
 
-    protected void reAgeLoan(Long loanId, String frequency, String startDate, Integer numberOfInstallments) {
+    protected void reAgeLoan(Long loanId, String frequencyType, int frequencyNumber, String startDate, Integer numberOfInstallments) {
         PostLoansLoanIdTransactionsRequest request = new PostLoansLoanIdTransactionsRequest();
         request.setDateFormat(DATETIME_PATTERN);
         request.setLocale("en");
-        request.setFrequency(frequency);
+        request.setFrequencyType(frequencyType);
+        request.setFrequencyNumber(frequencyNumber);
         request.setStartDate(startDate);
         request.setNumberOfInstallments(numberOfInstallments);
         loanTransactionHelper.reAge(loanId, request);
@@ -798,6 +799,13 @@
         assertEquals(paidLate, period.getTotalPaidLateForPeriod());
     }
 
+    protected void checkMaturityDates(long loanId, LocalDate expectedMaturityDate, LocalDate actualMaturityDate) {
+        GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
+
+        assertEquals(expectedMaturityDate, loanDetails.getTimeline().getExpectedMaturityDate());
+        assertEquals(actualMaturityDate, loanDetails.getTimeline().getActualMaturityDate());
+    }
+
     @RequiredArgsConstructor
     public static class BatchRequestBuilder {
 
@@ -937,6 +945,7 @@
 
         public static final Integer MONTHS = 2;
         public static final String MONTHS_STRING = "MONTHS";
+        public static final String DAYS_STRING = "DAYS";
     }
 
     public static class InterestCalculationPeriodType {
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanDelinquencyDetailsNextPaymentDateConfigurationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanDelinquencyDetailsNextPaymentDateConfigurationTest.java
index 7dc67f4..656017a 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanDelinquencyDetailsNextPaymentDateConfigurationTest.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanDelinquencyDetailsNextPaymentDateConfigurationTest.java
@@ -120,9 +120,6 @@
                 businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BUSINESS_DATE.getName()).date("25 December 2023")
                         .dateFormat(DATETIME_PATTERN).locale("en"));
 
-                // delinquency null next payment date for date after maturity date
-                verifyLoanDelinquencyNextPaymentDate(loanId, "", true);
-
             } finally {
                 // reset global config
                 GlobalConfigurationHelper.updateLoanNextPaymentDateConfiguration(this.requestSpec, this.responseSpec,
@@ -238,10 +235,6 @@
 
                 businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BUSINESS_DATE.getName()).date("25 December 2023")
                         .dateFormat(DATETIME_PATTERN).locale("en"));
-
-                // delinquency null next payment date for date after maturity date
-                verifyLoanDelinquencyNextPaymentDate(loanId, "", true);
-
             } finally {
                 // reset global config
                 GlobalConfigurationHelper.updateLoanNextPaymentDateConfiguration(this.requestSpec, this.responseSpec,
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reaging/LoanReAgingIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reaging/LoanReAgingIntegrationTest.java
index c5a64bf..e3bebe2 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reaging/LoanReAgingIntegrationTest.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reaging/LoanReAgingIntegrationTest.java
@@ -19,10 +19,15 @@
 package org.apache.fineract.integrationtests.loan.reaging;
 
 import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.UUID;
 import java.util.concurrent.atomic.AtomicLong;
+import org.apache.fineract.client.models.PostChargesResponse;
 import org.apache.fineract.client.models.PostLoanProductsRequest;
 import org.apache.fineract.client.models.PostLoanProductsResponse;
+import org.apache.fineract.client.models.PostLoansLoanIdChargesResponse;
 import org.apache.fineract.client.models.PostLoansLoanIdResponse;
+import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
 import org.apache.fineract.client.models.PostLoansRequest;
 import org.apache.fineract.client.models.PostLoansResponse;
 import org.apache.fineract.integrationtests.BaseLoanIntegrationTest;
@@ -40,13 +45,17 @@
             // Create Client
             Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
 
-            int numberOfRepayments = 1;
+            int numberOfRepayments = 3;
             int repaymentEvery = 1;
 
             // Create Loan Product
             PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation() //
                     .numberOfRepayments(numberOfRepayments) //
                     .repaymentEvery(repaymentEvery) //
+                    .installmentAmountInMultiplesOf(null) //
+                    .enableDownPayment(true) //
+                    .disbursedAmountPercentageForDownPayment(BigDecimal.valueOf(25)) //
+                    .enableAutoRepaymentForDownPayment(true) //
                     .repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue()); //
 
             PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product);
@@ -74,102 +83,75 @@
 
             // verify transactions
             verifyTransactions(loanId, //
-                    transaction(1250.0, "Disbursement", "01 January 2023") //
+                    transaction(1250.0, "Disbursement", "01 January 2023"), //
+                    transaction(312.5, "Down Payment", "01 January 2023") //
             );
 
             // verify schedule
             verifyRepaymentSchedule(loanId, //
-                    installment(0, null, "01 January 2023"), //
-                    installment(1250.0, false, "01 February 2023") //
+                    installment(1250, null, "01 January 2023"), //
+                    installment(312.5, true, "01 January 2023"), //
+                    installment(312.5, false, "01 February 2023"), //
+                    installment(312.5, false, "01 March 2023"), //
+                    installment(312.5, false, "01 April 2023") //
             );
-
+            checkMaturityDates(loanId, LocalDate.of(2023, 4, 1), LocalDate.of(2023, 4, 1));
             createdLoanId.set(loanId);
         });
 
-        runAt("02 February 2023", () -> {
+        runAt("11 April 2023", () -> {
+
             long loanId = createdLoanId.get();
 
-            // create re-age transaction
-            reAgeLoan(loanId, RepaymentFrequencyType.MONTHS_STRING, "02 February 2023", 6);
+            // create charge
+            double chargeAmount = 10.0;
+            PostChargesResponse chargeResult = createCharge(chargeAmount);
+            Long chargeId = chargeResult.getResourceId();
 
-            // verify transactions
-            verifyTransactions(loanId, //
-                    transaction(1250.0, "Disbursement", "01 January 2023"), //
-                    transaction(1250.0, "Re-age", "02 February 2023") //
-            );
-
-            // TODO: verify installments when schedule generation is implemented
-        });
-    }
-
-    @Test
-    public void test_LoanUndoReAgeTransaction_Works() {
-        AtomicLong createdLoanId = new AtomicLong();
-
-        runAt("01 January 2023", () -> {
-            // Create Client
-            Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
-
-            int numberOfRepayments = 1;
-            int repaymentEvery = 1;
-
-            // Create Loan Product
-            PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation() //
-                    .numberOfRepayments(numberOfRepayments) //
-                    .repaymentEvery(repaymentEvery) //
-                    .repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue()); //
-
-            PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product);
-            Long loanProductId = loanProductResponse.getResourceId();
-
-            // Apply and Approve Loan
-            double amount = 1250.0;
-
-            PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductId, "01 January 2023", amount, numberOfRepayments)//
-                    .transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)//
-                    .repaymentEvery(repaymentEvery)//
-                    .loanTermFrequency(numberOfRepayments)//
-                    .repaymentFrequencyType(RepaymentFrequencyType.MONTHS)//
-                    .loanTermFrequencyType(RepaymentFrequencyType.MONTHS);
-
-            PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applicationRequest);
-
-            PostLoansLoanIdResponse approvedLoanResult = loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(),
-                    approveLoanRequest(amount, "01 January 2023"));
-
-            Long loanId = approvedLoanResult.getLoanId();
-
-            // disburse Loan
-            disburseLoan(loanId, BigDecimal.valueOf(1250.0), "01 January 2023");
-
-            // verify transactions
-            verifyTransactions(loanId, //
-                    transaction(1250.0, "Disbursement", "01 January 2023") //
-            );
+            // add charge after maturity
+            PostLoansLoanIdChargesResponse loanChargeResult = addLoanCharge(loanId, chargeId, "11 April 2023", chargeAmount);
 
             // verify schedule
             verifyRepaymentSchedule(loanId, //
-                    installment(0, null, "01 January 2023"), //
-                    installment(1250.0, false, "01 February 2023") //
+                    installment(1250, null, "01 January 2023"), //
+                    installment(312.5, true, "01 January 2023"), //
+                    installment(312.5, false, "01 February 2023"), //
+                    installment(312.5, false, "01 March 2023"), //
+                    installment(312.5, false, "01 April 2023"), //
+                    installment(0.0, 0.0, 10.0, 10.0, false, "11 April 2023") //
             );
-
-            createdLoanId.set(loanId);
+            checkMaturityDates(loanId, LocalDate.of(2023, 4, 1), LocalDate.of(2023, 4, 1));
         });
 
-        runAt("02 February 2023", () -> {
+        runAt("12 April 2023", () -> {
             long loanId = createdLoanId.get();
 
             // create re-age transaction
-            reAgeLoan(loanId, RepaymentFrequencyType.MONTHS_STRING, "02 February 2023", 6);
+            reAgeLoan(loanId, RepaymentFrequencyType.MONTHS_STRING, 1, "12 April 2023", 4);
 
             // verify transactions
             verifyTransactions(loanId, //
                     transaction(1250.0, "Disbursement", "01 January 2023"), //
-                    transaction(1250.0, "Re-age", "02 February 2023") //
+                    transaction(312.5, "Down Payment", "01 January 2023"), //
+                    transaction(937.5, "Re-age", "12 April 2023") //
             );
+
+            verifyRepaymentSchedule(loanId, //
+                    installment(1250, null, "01 January 2023"), //
+                    installment(312.5, true, "01 January 2023"), //
+                    installment(0, true, "01 February 2023"), //
+                    installment(0, true, "01 March 2023"), //
+                    installment(0, true, "01 April 2023"), //
+                    installment(0.0, 0.0, 10.0, 10.0, false, "11 April 2023"), //
+                    installment(234.38, false, "12 April 2023"), //
+                    installment(234.38, false, "12 May 2023"), //
+                    installment(234.38, false, "12 June 2023"), //
+                    installment(234.36, false, "12 July 2023") //
+            );
+            checkMaturityDates(loanId, LocalDate.of(2023, 7, 12), LocalDate.of(2023, 7, 12));
         });
 
-        runAt("03 February 2023", () -> {
+        runAt("13 April 2023", () -> {
             long loanId = createdLoanId.get();
 
             // create re-age transaction
@@ -178,10 +160,106 @@
             // verify transactions
             verifyTransactions(loanId, //
                     transaction(1250.0, "Disbursement", "01 January 2023"), //
-                    reversedTransaction(1250.0, "Re-age", "02 February 2023") //
+                    transaction(312.5, "Down Payment", "01 January 2023"), //
+                    reversedTransaction(937.5, "Re-age", "12 April 2023") //
             );
 
-            // TODO: verify installments when schedule generation is implemented
+            // verify schedule
+            verifyRepaymentSchedule(loanId, //
+                    installment(1250, null, "01 January 2023"), //
+                    installment(312.5, true, "01 January 2023"), //
+                    installment(312.5, false, "01 February 2023"), //
+                    installment(312.5, false, "01 March 2023"), //
+                    installment(312.5, false, "01 April 2023"), //
+                    installment(0.0, 0.0, 10.0, 10.0, false, "11 April 2023") //
+            );
+            checkMaturityDates(loanId, LocalDate.of(2023, 4, 1), LocalDate.of(2023, 4, 1));
+        });
+        String repaymentExternalId = UUID.randomUUID().toString();
+        runAt("13 April 2023", () -> {
+            long loanId = createdLoanId.get();
+
+            loanTransactionHelper.makeLoanRepayment(loanId, new PostLoansLoanIdTransactionsRequest().dateFormat(DATETIME_PATTERN)
+                    .transactionDate("13 April 2023").locale("en").transactionAmount(100.0).externalId(repaymentExternalId));
+
+            // verify transactions
+            verifyTransactions(loanId, //
+                    transaction(1250.0, "Disbursement", "01 January 2023"), //
+                    transaction(312.5, "Down Payment", "01 January 2023"), //
+                    reversedTransaction(937.5, "Re-age", "12 April 2023"), //
+                    transaction(100.0, "Repayment", "13 April 2023") //
+            );
+
+            // verify schedule
+            verifyRepaymentSchedule(loanId, //
+                    installment(1250, null, "01 January 2023"), //
+                    installment(312.5, 0, 0, 0, 0.0, true, "01 January 2023"), //
+                    installment(312.5, 0, 0, 0, 212.5, false, "01 February 2023"), //
+                    installment(312.5, 0, 0, 0, 312.5, false, "01 March 2023"), //
+                    installment(312.5, 0, 0, 0, 312.5, false, "01 April 2023"), //
+                    installment(0.0, 0.0, 10.0, 10.0, false, "11 April 2023") //
+            );
+
+            // create re-age transaction
+            reAgeLoan(loanId, RepaymentFrequencyType.DAYS_STRING, 30, "13 April 2023", 3);
+
+            // verify transactions
+            verifyTransactions(loanId, //
+                    transaction(1250.0, "Disbursement", "01 January 2023"), //
+                    transaction(312.5, "Down Payment", "01 January 2023"), //
+                    reversedTransaction(937.5, "Re-age", "12 April 2023"), //
+                    transaction(100.0, "Repayment", "13 April 2023"), //
+                    transaction(837.5, "Re-age", "13 April 2023") //
+            );
+
+            // verify schedule
+            verifyRepaymentSchedule(loanId, //
+                    installment(1250, null, "01 January 2023"), //
+                    installment(312.5, 0, 0, 0, 0.0, true, "01 January 2023"), //
+                    installment(100.0, 0, 0, 0, 0.0, true, "01 February 2023"), //
+                    installment(0, 0, 0, 0, 0.0, true, "01 March 2023"), //
+                    installment(0, 0, 0, 0, 0.0, true, "01 April 2023"), //
+                    installment(0.0, 0.0, 10.0, 10.0, false, "11 April 2023"), //
+                    installment(279.17, 0, 0, 0, 279.17, false, "13 April 2023"), //
+                    installment(279.17, 0, 0, 0, 279.17, false, "13 May 2023"), //
+                    installment(279.16, 0, 0, 0, 279.16, false, "12 June 2023") //
+            );
+            checkMaturityDates(loanId, LocalDate.of(2023, 6, 12), LocalDate.of(2023, 6, 12));
+        });
+
+        runAt("14 April 2023", () -> {
+            long loanId = createdLoanId.get();
+
+            // disburse Loan
+            disburseLoan(loanId, BigDecimal.valueOf(100.0), "14 April 2023");
+
+            // verify transactions
+            verifyTransactions(loanId, //
+                    transaction(1250.0, "Disbursement", "01 January 2023"), //
+                    transaction(312.5, "Down Payment", "01 January 2023"), //
+                    reversedTransaction(937.5, "Re-age", "12 April 2023"), //
+                    transaction(100.0, "Repayment", "13 April 2023"), //
+                    transaction(837.5, "Re-age", "13 April 2023"), //
+                    transaction(100.0, "Disbursement", "14 April 2023"), //
+                    transaction(25.0, "Down Payment", "14 April 2023") //
+            );
+
+            // verify schedule
+            verifyRepaymentSchedule(loanId, //
+                    installment(1250, null, "01 January 2023"), //
+                    installment(312.5, 0, 0, 0, 0.0, true, "01 January 2023"), //
+                    installment(100.0, 0, 0, 0, 0.0, true, "01 February 2023"), //
+                    installment(0, 0, 0, 0, 0.0, true, "01 March 2023"), //
+                    installment(0, 0, 0, 0, 0.0, true, "01 April 2023"), //
+                    installment(0.0, 0.0, 10.0, 0.0, true, "11 April 2023"), //
+                    installment(279.17, 0, 0, 0, 264.17, false, "13 April 2023"), //
+                    installment(100, null, "14 April 2023"), //
+                    installment(25.0, 0, 0, 0, 25.0, false, "14 April 2023"), //
+                    installment(316.67, 0, 0, 0, 316.67, false, "13 May 2023"), //
+                    installment(316.66, 0, 0, 0, 316.66, false, "12 June 2023") //
+            );
+            checkMaturityDates(loanId, LocalDate.of(2023, 6, 12), LocalDate.of(2023, 6, 12));
         });
     }
+
 }