FINERACT-2042 Handling overpayment of chargeback with credit allocations
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 8c7b424..46f49e8 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
@@ -140,6 +140,7 @@
 import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
 import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
 import org.apache.fineract.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationTransactionType;
 import org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType;
 import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType;
 import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
@@ -5959,10 +5960,18 @@
                         .minus(loanTransaction.getOverPaymentPortion(getCurrency()));
                 loanTransaction.updateOutstandingLoanBalance(outstanding.getAmount());
             } else if (loanTransaction.isChargeback() || loanTransaction.isCreditBalanceRefund()) {
-                Money transactionOutstanding = loanTransaction.getAmount(getCurrency());
+                Money transactionOutstanding = loanTransaction.getPrincipalPortion(getCurrency());
                 if (!loanTransaction.getOverPaymentPortion(getCurrency()).isZero()) {
-                    transactionOutstanding = loanTransaction.getAmount(getCurrency())
-                            .minus(loanTransaction.getOverPaymentPortion(getCurrency()));
+                    // in case of advanced payment strategy and creditAllocations the full amount is recognized first
+                    if (this.getCreditAllocationRules() != null && this.getCreditAllocationRules().size() > 0) {
+                        Money payedPrincipal = loanTransaction.getLoanTransactionToRepaymentScheduleMappings().stream()
+                                .map(mapping -> mapping.getPrincipalPortion(getCurrency())).reduce(Money.zero(getCurrency()), Money::plus);
+                        transactionOutstanding = loanTransaction.getPrincipalPortion(getCurrency()).minus(payedPrincipal);
+                    } else {
+                        // in case legacy payment strategy
+                        transactionOutstanding = loanTransaction.getAmount(getCurrency())
+                                .minus(loanTransaction.getOverPaymentPortion(getCurrency()));
+                    }
                     if (transactionOutstanding.isLessThanZero()) {
                         transactionOutstanding = Money.zero(getCurrency());
                     }
@@ -7198,6 +7207,13 @@
         return paymentAllocationRules;
     }
 
+    public LoanPaymentAllocationRule getPaymentAllocationRuleOrDefault(PaymentAllocationTransactionType transactionType) {
+        Optional<LoanPaymentAllocationRule> paymentAllocationRule = this.getPaymentAllocationRules().stream()
+                .filter(rule -> rule.getTransactionType().equals(transactionType)).findFirst();
+        return paymentAllocationRule.orElse(this.getPaymentAllocationRules().stream()
+                .filter(rule -> rule.getTransactionType().equals(PaymentAllocationTransactionType.DEFAULT)).findFirst().get());
+    }
+
     public void setPaymentAllocationRules(List<LoanPaymentAllocationRule> loanPaymentAllocationRules) {
         this.paymentAllocationRules = loanPaymentAllocationRules;
     }
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 1431c8a..6d64565 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
@@ -130,8 +130,14 @@
     @Column(name = "is_additional", nullable = false)
     private boolean additional;
 
-    @Column(name = "credits_amount", scale = 6, precision = 19, nullable = true)
-    private BigDecimal credits;
+    @Column(name = "credited_principal", scale = 6, precision = 19, nullable = true)
+    private BigDecimal creditedPrincipal;
+
+    @Column(name = "credited_fee", scale = 6, precision = 19, nullable = true)
+    private BigDecimal creditedFee;
+
+    @Column(name = "credited_penalty", scale = 6, precision = 19, nullable = true)
+    private BigDecimal creditedPenalty;
 
     @Column(name = "is_down_payment", nullable = false)
     private boolean isDownPayment;
@@ -246,8 +252,16 @@
         return this.dueDate;
     }
 
-    public Money getCredits(final MonetaryCurrency currency) {
-        return Money.of(currency, this.credits);
+    public Money getCreditedPrincipal(final MonetaryCurrency currency) {
+        return Money.of(currency, this.creditedPrincipal);
+    }
+
+    public Money getCreditedFee(final MonetaryCurrency currency) {
+        return Money.of(currency, this.creditedFee);
+    }
+
+    public Money getCreditedPenalty(final MonetaryCurrency currency) {
+        return Money.of(currency, this.creditedPenalty);
     }
 
     public Money getPrincipal(final MonetaryCurrency currency) {
@@ -408,9 +422,17 @@
 
         this.obligationsMet = false;
         this.obligationsMetOnDate = null;
-        if (this.credits != null) {
-            this.principal = this.principal.subtract(this.credits);
-            this.credits = null;
+        if (this.creditedPrincipal != null) {
+            this.principal = this.principal.subtract(this.creditedPrincipal);
+            this.creditedPrincipal = null;
+        }
+        if (this.creditedFee != null) {
+            this.feeChargesCharged = this.feeChargesCharged.subtract(this.creditedFee);
+            this.creditedFee = null;
+        }
+        if (this.creditedPenalty != null) {
+            this.penaltyCharges = this.penaltyCharges.subtract(this.creditedPenalty);
+            this.creditedPenalty = null;
         }
     }
 
@@ -780,11 +802,27 @@
         checkIfRepaymentPeriodObligationsAreMet(transactionDate, transactionAmount.getCurrency());
     }
 
-    public void addToCredits(final BigDecimal amount) {
-        if (this.credits == null) {
-            this.credits = amount;
+    public void addToCreditedPrincipal(final BigDecimal amount) {
+        if (this.creditedPrincipal == null) {
+            this.creditedPrincipal = amount;
         } else {
-            this.credits = this.credits.add(amount);
+            this.creditedPrincipal = this.creditedPrincipal.add(amount);
+        }
+    }
+
+    public void addToCreditedFee(final BigDecimal amount) {
+        if (this.creditedFee == null) {
+            this.creditedFee = amount;
+        } else {
+            this.creditedFee = this.creditedFee.add(amount);
+        }
+    }
+
+    public void addToCreditedPenalty(final BigDecimal amount) {
+        if (this.creditedPenalty == null) {
+            this.creditedPenalty = amount;
+        } else {
+            this.creditedPenalty = this.creditedPenalty.add(amount);
         }
     }
 
@@ -908,7 +946,7 @@
     }
 
     public void updateCredits(final LocalDate transactionDate, final Money transactionAmount) {
-        addToCredits(transactionAmount.getAmount());
+        addToCreditedPrincipal(transactionAmount.getAmount());
         addToPrincipal(transactionDate, transactionAmount);
     }
 
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanSummary.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanSummary.java
index 2cbb064..271f88f 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanSummary.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanSummary.java
@@ -74,6 +74,9 @@
     @Column(name = "total_charges_due_at_disbursement_derived", scale = 6, precision = 19)
     private BigDecimal totalFeeChargesDueAtDisbursement;
 
+    @Column(name = "fee_adjustments_derived", scale = 6, precision = 19)
+    private BigDecimal totalFeeAdjustments;
+
     @Column(name = "fee_charges_repaid_derived", scale = 6, precision = 19)
     private BigDecimal totalFeeChargesRepaid;
 
@@ -89,6 +92,9 @@
     @Column(name = "penalty_charges_charged_derived", scale = 6, precision = 19)
     private BigDecimal totalPenaltyChargesCharged;
 
+    @Column(name = "penalty_adjustments_derived", scale = 6, precision = 19)
+    private BigDecimal totalPenaltyAdjustments;
+
     @Column(name = "penalty_charges_repaid_derived", scale = 6, precision = 19)
     private BigDecimal totalPenaltyChargesRepaid;
 
@@ -204,6 +210,8 @@
     public void zeroFields() {
         this.totalPrincipalDisbursed = BigDecimal.ZERO;
         this.totalPrincipalAdjustments = BigDecimal.ZERO;
+        this.totalFeeAdjustments = BigDecimal.ZERO;
+        this.totalPenaltyAdjustments = BigDecimal.ZERO;
         this.totalPrincipalRepaid = BigDecimal.ZERO;
         this.totalPrincipalWrittenOff = BigDecimal.ZERO;
         this.totalPrincipalOutstanding = BigDecimal.ZERO;
@@ -238,6 +246,8 @@
         this.totalPrincipalDisbursed = principal.getAmount();
         this.totalPrincipalAdjustments = summaryWrapper.calculateTotalPrincipalAdjusted(repaymentScheduleInstallments, currency)
                 .getAmount();
+        this.totalFeeAdjustments = summaryWrapper.calculateTotalFeeAdjusted(repaymentScheduleInstallments, currency).getAmount();
+        this.totalPenaltyAdjustments = summaryWrapper.calculateTotalPenaltyAdjusted(repaymentScheduleInstallments, currency).getAmount();
         this.totalPrincipalRepaid = summaryWrapper.calculateTotalPrincipalRepaid(repaymentScheduleInstallments, currency).getAmount();
         this.totalPrincipalWrittenOff = summaryWrapper.calculateTotalPrincipalWrittenOff(repaymentScheduleInstallments, currency)
                 .getAmount();
@@ -259,7 +269,9 @@
         this.totalFeeChargesCharged = totalFeeChargesCharged.getAmount();
 
         Money totalFeeChargesRepaidAtDisbursement = summaryWrapper.calculateTotalChargesRepaidAtDisbursement(charges, currency);
-        this.totalFeeChargesRepaid = totalFeeChargesRepaidAtDisbursement.getAmount();
+        Money totalFeeChargesRepaidAfterDisbursement = summaryWrapper.calculateTotalFeeChargesRepaid(repaymentScheduleInstallments,
+                currency);
+        this.totalFeeChargesRepaid = totalFeeChargesRepaidAfterDisbursement.plus(totalFeeChargesRepaidAtDisbursement).getAmount();
 
         if (charges != null) {
             this.totalFeeChargesWaived = summaryWrapper.calculateTotalFeeChargesWaived(charges, currency).getAmount();
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanSummaryWrapper.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanSummaryWrapper.java
index 947d175..8ad10df 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanSummaryWrapper.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanSummaryWrapper.java
@@ -45,7 +45,25 @@
             final MonetaryCurrency currency) {
         Money total = Money.zero(currency);
         for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
-            total = total.plus(installment.getCredits(currency));
+            total = total.plus(installment.getCreditedPrincipal(currency));
+        }
+        return total;
+    }
+
+    public Money calculateTotalFeeAdjusted(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
+            final MonetaryCurrency currency) {
+        Money total = Money.zero(currency);
+        for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
+            total = total.plus(installment.getCreditedFee(currency));
+        }
+        return total;
+    }
+
+    public Money calculateTotalPenaltyAdjusted(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
+            final MonetaryCurrency currency) {
+        Money total = Money.zero(currency);
+        for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
+            total = total.plus(installment.getCreditedPenalty(currency));
         }
         return total;
     }
@@ -247,7 +265,8 @@
             return total;
         }
         for (final LoanCharge loanCharge : charges) {
-            if (!loanCharge.isPenaltyCharge() && loanCharge.getAmountPaid(currency).isGreaterThanZero()) {
+            if (!loanCharge.isPenaltyCharge() && loanCharge.getAmountPaid(currency).isGreaterThanZero()
+                    && loanCharge.isDisbursementCharge()) {
                 total = total.plus(loanCharge.getAmountPaid(currency));
             }
         }
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 596513e..94592fe 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
@@ -494,7 +494,7 @@
             for (final LoanRepaymentScheduleInstallment currentInstallment : installments) {
                 pastDueDate = currentInstallment.getDueDate();
                 if (!currentInstallment.isAdditional() && DateUtils.isAfter(currentInstallment.getDueDate(), transactionDate)) {
-                    currentInstallment.addToCredits(transactionAmount.getAmount());
+                    currentInstallment.addToCreditedPrincipal(transactionAmount.getAmount());
                     currentInstallment.addToPrincipal(transactionDate, transactionAmount);
                     if (repaidAmount.isGreaterThanZero()) {
                         currentInstallment.payPrincipalComponent(loanTransaction.getTransactionDate(), repaidAmount);
@@ -526,7 +526,7 @@
             if (!loanTransactionMapped) {
                 if (loanTransaction.getTransactionDate().equals(pastDueDate)) {
                     LoanRepaymentScheduleInstallment currentInstallment = installments.get(installments.size() - 1);
-                    currentInstallment.addToCredits(transactionAmount.getAmount());
+                    currentInstallment.addToCreditedPrincipal(transactionAmount.getAmount());
                     currentInstallment.addToPrincipal(transactionDate, transactionAmount);
                     if (repaidAmount.isGreaterThanZero()) {
                         currentInstallment.payPrincipalComponent(loanTransaction.getTransactionDate(), repaidAmount);
@@ -539,7 +539,7 @@
                             pastDueDate, transactionDate, transactionAmount.getAmount(), zeroMoney.getAmount(), zeroMoney.getAmount(),
                             zeroMoney.getAmount(), false, null);
                     installment.markAsAdditional();
-                    installment.addToCredits(transactionAmount.getAmount());
+                    installment.addToCreditedPrincipal(transactionAmount.getAmount());
                     loan.addLoanRepaymentScheduleInstallment(installment);
 
                     if (repaidAmount.isGreaterThanZero()) {
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 53de2b0..60fb6ca 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
@@ -25,6 +25,7 @@
 import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.INTEREST;
 import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.PENALTY;
 import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.PRINCIPAL;
+import static org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationTransactionType.DEFAULT;
 
 import java.math.BigDecimal;
 import java.math.MathContext;
@@ -76,7 +77,6 @@
 import org.apache.fineract.portfolio.loanproduct.domain.DueType;
 import org.apache.fineract.portfolio.loanproduct.domain.FutureInstallmentAllocationRule;
 import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
-import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationTransactionType;
 import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -235,27 +235,31 @@
         if (hasNoCustomCreditAllocationRule(loanTransaction)) {
             super.processCreditTransaction(loanTransaction, ctx.getOverpaymentHolder(), ctx.getCurrency(), ctx.getInstallments());
         } else {
-            log.debug("Processing credit transaction with custom credit allocation rules");
-
             loanTransaction.resetDerivedComponents();
-            List<LoanTransactionToRepaymentScheduleMapping> transactionMappings = new ArrayList<>();
             final Comparator<LoanRepaymentScheduleInstallment> byDate = Comparator.comparing(LoanRepaymentScheduleInstallment::getDueDate);
             ctx.getInstallments().sort(byDate);
             final Money zeroMoney = Money.zero(ctx.getCurrency());
             Money transactionAmount = loanTransaction.getAmount(ctx.getCurrency());
-            Money amountToDistribute = MathUtil
-                    .negativeToZero(loanTransaction.getAmount(ctx.getCurrency()).minus(ctx.getOverpaymentHolder().getMoneyObject()));
-            Money repaidAmount = MathUtil.negativeToZero(transactionAmount.minus(amountToDistribute));
-            loanTransaction.setOverPayments(repaidAmount);
-            ctx.getOverpaymentHolder().setMoneyObject(ctx.getOverpaymentHolder().getMoneyObject().minus(repaidAmount));
+            Money totalOverpaid = ctx.getOverpaymentHolder().getMoneyObject();
+            Money amountToDistribute = MathUtil.negativeToZero(loanTransaction.getAmount(ctx.getCurrency()).minus(totalOverpaid));
+            Money overpaymentAmount = MathUtil.negativeToZero(transactionAmount.minus(amountToDistribute));
+            loanTransaction.setOverPayments(overpaymentAmount);
 
-            if (amountToDistribute.isGreaterThanZero()) {
+            if (transactionAmount.isGreaterThanZero()) {
                 if (loanTransaction.isChargeback()) {
                     LoanTransaction originalTransaction = findOriginalTransaction(loanTransaction, ctx);
-                    Map<AllocationType, BigDecimal> originalAllocation = getOriginalAllocation(originalTransaction);
+                    // get the original allocation from the opriginal transaction
+                    Map<AllocationType, Money> originalAllocationNotAdjusted = getOriginalAllocation(originalTransaction,
+                            ctx.getCurrency());
                     LoanCreditAllocationRule chargeBackAllocationRule = getChargebackAllocationRules(loanTransaction);
+
+                    // if there were earlier chargebacks then let's calculate the remaining amounts for each portion
+                    Map<AllocationType, Money> originalAllocation = adjustOriginalAllocationWithFormerChargebacks(originalTransaction,
+                            originalAllocationNotAdjusted, loanTransaction, ctx, chargeBackAllocationRule);
+
+                    // calculate the current chargeback allocation
                     Map<AllocationType, Money> chargebackAllocation = calculateChargebackAllocationMap(originalAllocation,
-                            amountToDistribute.getAmount(), chargeBackAllocationRule.getAllocationTypes(), ctx.getCurrency());
+                            transactionAmount.getAmount(), chargeBackAllocationRule.getAllocationTypes(), ctx.getCurrency());
 
                     loanTransaction.updateComponents(chargebackAllocation.get(PRINCIPAL), chargebackAllocation.get(INTEREST),
                             chargebackAllocation.get(FEE), chargebackAllocation.get(PENALTY));
@@ -266,18 +270,7 @@
                     for (final LoanRepaymentScheduleInstallment currentInstallment : ctx.getInstallments()) {
                         pastDueDate = currentInstallment.getDueDate();
                         if (!currentInstallment.isAdditional() && DateUtils.isAfter(currentInstallment.getDueDate(), transactionDate)) {
-
-                            currentInstallment.addToCredits(transactionAmount.getAmount());
-                            currentInstallment.addToPrincipal(transactionDate, chargebackAllocation.get(PRINCIPAL));
-                            Money originalInterest = currentInstallment.getInterestCharged(ctx.getCurrency());
-                            currentInstallment.updateInterestCharged(
-                                    originalInterest.plus(chargebackAllocation.get(INTEREST)).getAmountDefaultedToNullIfZero());
-
-                            if (repaidAmount.isGreaterThanZero()) {
-                                currentInstallment.payPrincipalComponent(loanTransaction.getTransactionDate(), repaidAmount);
-                                transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction,
-                                        currentInstallment, repaidAmount, zeroMoney, zeroMoney, zeroMoney));
-                            }
+                            recognizeAmountsAfterChargeback(ctx.getCurrency(), transactionDate, currentInstallment, chargebackAllocation);
                             loanTransactionMapped = true;
                             break;
 
@@ -287,16 +280,7 @@
                             if (DateUtils.isAfter(transactionDate, currentInstallment.getDueDate())) {
                                 currentInstallment.updateDueDate(transactionDate);
                             }
-                            currentInstallment.addToCredits(transactionAmount.getAmount());
-                            currentInstallment.addToPrincipal(transactionDate, chargebackAllocation.get(PRINCIPAL));
-                            Money originalInterest = currentInstallment.getInterestCharged(ctx.getCurrency());
-                            currentInstallment.updateInterestCharged(
-                                    originalInterest.plus(chargebackAllocation.get(INTEREST)).getAmountDefaultedToNullIfZero());
-                            if (repaidAmount.isGreaterThanZero()) {
-                                currentInstallment.payPrincipalComponent(loanTransaction.getTransactionDate(), repaidAmount);
-                                transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction,
-                                        currentInstallment, repaidAmount, zeroMoney, zeroMoney, zeroMoney));
-                            }
+                            recognizeAmountsAfterChargeback(ctx.getCurrency(), transactionDate, currentInstallment, chargebackAllocation);
                             loanTransactionMapped = true;
                             break;
                         }
@@ -307,42 +291,99 @@
                         if (loanTransaction.getTransactionDate().equals(pastDueDate)) {
                             LoanRepaymentScheduleInstallment currentInstallment = ctx.getInstallments()
                                     .get(ctx.getInstallments().size() - 1);
-                            currentInstallment.addToCredits(transactionAmount.getAmount());
-                            currentInstallment.addToPrincipal(transactionDate, chargebackAllocation.get(PRINCIPAL));
-                            Money originalInterest = currentInstallment.getInterestCharged(ctx.getCurrency());
-                            currentInstallment.updateInterestCharged(
-                                    originalInterest.plus(chargebackAllocation.get(INTEREST)).getAmountDefaultedToNullIfZero());
-                            if (repaidAmount.isGreaterThanZero()) {
-                                currentInstallment.payPrincipalComponent(loanTransaction.getTransactionDate(), repaidAmount);
-                                transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction,
-                                        currentInstallment, repaidAmount, zeroMoney, zeroMoney, zeroMoney));
-                            }
+                            recognizeAmountsAfterChargeback(ctx.getCurrency(), transactionDate, currentInstallment, chargebackAllocation);
                         } else {
                             Loan loan = loanTransaction.getLoan();
                             LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(loan,
                                     (ctx.getInstallments().size() + 1), pastDueDate, transactionDate, zeroMoney.getAmount(),
                                     zeroMoney.getAmount(), zeroMoney.getAmount(), zeroMoney.getAmount(), false, null);
-                            installment.markAsAdditional();
-                            installment.addToCredits(transactionAmount.getAmount());
-                            installment.addToPrincipal(transactionDate, chargebackAllocation.get(PRINCIPAL));
-                            Money originalInterest = installment.getInterestCharged(ctx.getCurrency());
-                            installment.updateInterestCharged(
-                                    originalInterest.plus(chargebackAllocation.get(INTEREST)).getAmountDefaultedToNullIfZero());
+                            recognizeAmountsAfterChargeback(ctx.getCurrency(), transactionDate, installment, chargebackAllocation);
                             loan.addLoanRepaymentScheduleInstallment(installment);
-                            if (repaidAmount.isGreaterThanZero()) {
-                                installment.payPrincipalComponent(loanTransaction.getTransactionDate(), repaidAmount);
-                                transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction, installment,
-                                        repaidAmount, zeroMoney, zeroMoney, zeroMoney));
-                            }
                         }
                     }
-
-                    loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings);
+                    allocateOverpayment(loanTransaction, ctx.getCurrency(), loanTransaction.getLoan().getRepaymentScheduleInstallments(),
+                            ctx.getOverpaymentHolder());
+                } else {
+                    throw new RuntimeException("Unsupported transaction " + loanTransaction.getTypeOf().name());
                 }
             }
         }
     }
 
+    private Map<AllocationType, Money> adjustOriginalAllocationWithFormerChargebacks(LoanTransaction originalTransaction,
+            Map<AllocationType, Money> originalAllocation, LoanTransaction chargeBackTransaction, TransactionCtx ctx,
+            LoanCreditAllocationRule chargeBackAllocationRule) {
+        // these are the list of existing transactions
+        List<LoanTransaction> allTransactions = new ArrayList<>(chargeBackTransaction.getLoan().getLoanTransactions());
+
+        // Remove the current chargeback from the list
+        if (chargeBackTransaction.getId() != null) {
+            allTransactions.remove(chargeBackTransaction);
+        } else {
+            Long oldId = ctx.getChangedTransactionDetail().getCurrentTransactionToOldId().get(chargeBackTransaction);
+            allTransactions.remove(allTransactions.stream().filter(tr -> Objects.equals(tr.getId(), oldId)).findFirst().get());
+        }
+
+        // Add the replayed transactions and remove their old version before the replay
+        if (ctx.getChangedTransactionDetail() != null && ctx.getChangedTransactionDetail().getNewTransactionMappings() != null) {
+            for (Long id : ctx.getChangedTransactionDetail().getNewTransactionMappings().keySet()) {
+                allTransactions.remove(allTransactions.stream().filter(tr -> Objects.equals(tr.getId(), id)).findFirst().get());
+                allTransactions.add(ctx.getChangedTransactionDetail().getNewTransactionMappings().get(id));
+            }
+        }
+
+        // keep only the chargeback transactions
+        List<LoanTransaction> chargebacks = allTransactions.stream().filter(LoanTransaction::isChargeback).toList();
+
+        // let's figure out the original transaction for these chargebacks, and order them by ascending order
+        List<LoanTransaction> chargebacksForTheSameOriginal = chargebacks.stream()
+                .filter(tr -> findOriginalTransaction(tr, ctx) == originalTransaction).sorted(loanTransactionDateComparator()).toList();
+
+        Map<AllocationType, Money> allocation = new HashMap<>(originalAllocation);
+        for (LoanTransaction loanTransaction : chargebacksForTheSameOriginal) {
+            Map<AllocationType, Money> temp = calculateChargebackAllocationMap(allocation, loanTransaction.getAmount(),
+                    chargeBackAllocationRule.getAllocationTypes(), ctx.getCurrency());
+            allocation.keySet().forEach(k -> allocation.put(k, allocation.get(k).minus(temp.get(k))));
+        }
+        return allocation;
+    }
+
+    @NotNull
+    private Comparator<LoanTransaction> loanTransactionDateComparator() {
+        return (tr1, tr2) -> {
+            if (tr1.getTransactionDate().compareTo(tr2.getTransactionDate()) != 0) {
+                return tr1.getTransactionDate().compareTo(tr2.getTransactionDate());
+            } else if (tr1.getSubmittedOnDate().compareTo(tr2.getSubmittedOnDate()) != 0) {
+                return tr1.getSubmittedOnDate().compareTo(tr2.getSubmittedOnDate());
+            } else {
+                return tr1.getCreatedDateTime().compareTo(tr2.getCreatedDateTime());
+            }
+        };
+    }
+
+    private void recognizeAmountsAfterChargeback(MonetaryCurrency currency, LocalDate localDate,
+            LoanRepaymentScheduleInstallment installment, Map<AllocationType, Money> chargebackAllocation) {
+        Money principal = chargebackAllocation.get(PRINCIPAL);
+        if (principal.isGreaterThanZero()) {
+            installment.addToCreditedPrincipal(principal.getAmount());
+            installment.addToPrincipal(localDate, principal);
+        }
+
+        Money fee = chargebackAllocation.get(FEE);
+        if (fee.isGreaterThanZero()) {
+            installment.addToCreditedFee(fee.getAmount());
+            installment.addToChargePortion(fee, Money.zero(currency), Money.zero(currency), Money.zero(currency), Money.zero(currency),
+                    Money.zero(currency));
+        }
+
+        Money penalty = chargebackAllocation.get(PENALTY);
+        if (penalty.isGreaterThanZero()) {
+            installment.addToCreditedPenalty(penalty.getAmount());
+            installment.addToChargePortion(Money.zero(currency), Money.zero(currency), Money.zero(currency), penalty, Money.zero(currency),
+                    Money.zero(currency));
+        }
+    }
+
     @NotNull
     private LoanCreditAllocationRule getChargebackAllocationRules(LoanTransaction loanTransaction) {
         LoanCreditAllocationRule chargeBackAllocationRule = loanTransaction.getLoan().getCreditAllocationRules().stream()
@@ -351,23 +392,23 @@
     }
 
     @NotNull
-    private Map<AllocationType, BigDecimal> getOriginalAllocation(LoanTransaction originalLoanTransaction) {
-        Map<AllocationType, BigDecimal> originalAllocation = new HashMap<>();
-        originalAllocation.put(PRINCIPAL, originalLoanTransaction.getPrincipalPortion());
-        originalAllocation.put(INTEREST, originalLoanTransaction.getInterestPortion());
-        originalAllocation.put(PENALTY, originalLoanTransaction.getPenaltyChargesPortion());
-        originalAllocation.put(FEE, originalLoanTransaction.getFeeChargesPortion());
+    private Map<AllocationType, Money> getOriginalAllocation(LoanTransaction originalLoanTransaction, MonetaryCurrency currency) {
+        Map<AllocationType, Money> originalAllocation = new HashMap<>();
+        originalAllocation.put(PRINCIPAL, Money.of(currency, originalLoanTransaction.getPrincipalPortion()));
+        originalAllocation.put(INTEREST, Money.of(currency, originalLoanTransaction.getInterestPortion()));
+        originalAllocation.put(PENALTY, Money.of(currency, originalLoanTransaction.getPenaltyChargesPortion()));
+        originalAllocation.put(FEE, Money.of(currency, originalLoanTransaction.getFeeChargesPortion()));
         return originalAllocation;
     }
 
-    protected Map<AllocationType, Money> calculateChargebackAllocationMap(Map<AllocationType, BigDecimal> originalAllocation,
+    protected Map<AllocationType, Money> calculateChargebackAllocationMap(Map<AllocationType, Money> originalAllocation,
             BigDecimal amountToDistribute, List<AllocationType> allocationTypes, MonetaryCurrency currency) {
         BigDecimal remainingAmount = amountToDistribute;
         Map<AllocationType, Money> result = new HashMap<>();
         Arrays.stream(AllocationType.values()).forEach(allocationType -> result.put(allocationType, Money.of(currency, BigDecimal.ZERO)));
         for (AllocationType allocationType : allocationTypes) {
             if (remainingAmount.compareTo(BigDecimal.ZERO) > 0) {
-                BigDecimal originalAmount = originalAllocation.get(allocationType);
+                BigDecimal originalAmount = originalAllocation.get(allocationType).getAmount();
                 if (originalAmount != null && remainingAmount.compareTo(originalAmount) > 0
                         && originalAmount.compareTo(BigDecimal.ZERO) > 0) {
                     result.put(allocationType, Money.of(currency, originalAmount));
@@ -395,14 +436,14 @@
 
         List<LoanPaymentAllocationRule> paymentAllocationRules = loanTransaction.getLoan().getPaymentAllocationRules();
         LoanPaymentAllocationRule defaultPaymentAllocationRule = paymentAllocationRules.stream()
-                .filter(e -> PaymentAllocationTransactionType.DEFAULT.equals(e.getTransactionType())).findFirst().orElseThrow();
+                .filter(e -> DEFAULT.equals(e.getTransactionType())).findFirst().orElseThrow();
         LoanPaymentAllocationRule paymentAllocationRule = paymentAllocationRules.stream()
                 .filter(e -> loanTransaction.getTypeOf().equals(e.getTransactionType().getLoanTransactionType())).findFirst()
                 .orElse(defaultPaymentAllocationRule);
         Balances balances = new Balances(zero, zero, zero, zero);
         List<PaymentAllocationType> paymentAllocationTypes;
         FutureInstallmentAllocationRule futureInstallmentAllocationRule;
-        if (PaymentAllocationTransactionType.DEFAULT.equals(paymentAllocationRule.getTransactionType())) {
+        if (DEFAULT.equals(paymentAllocationRule.getTransactionType())) {
             // if the allocation rule is not defined then the reverse order of the default allocation rule will be used
             paymentAllocationTypes = new ArrayList<>(paymentAllocationRule.getAllocationTypes());
             Collections.reverse(paymentAllocationTypes);
@@ -560,7 +601,7 @@
             List<LoanTransactionToRepaymentScheduleMapping> transactionMappings = new ArrayList<>();
             List<LoanPaymentAllocationRule> paymentAllocationRules = loanTransaction.getLoan().getPaymentAllocationRules();
             LoanPaymentAllocationRule defaultPaymentAllocationRule = paymentAllocationRules.stream()
-                    .filter(e -> PaymentAllocationTransactionType.DEFAULT.equals(e.getTransactionType())).findFirst().orElseThrow();
+                    .filter(e -> DEFAULT.equals(e.getTransactionType())).findFirst().orElseThrow();
 
             Money transactionAmountUnprocessed = null;
             Money zero = Money.zero(currency);
@@ -926,7 +967,7 @@
 
         List<LoanPaymentAllocationRule> paymentAllocationRules = loanTransaction.getLoan().getPaymentAllocationRules();
         LoanPaymentAllocationRule defaultPaymentAllocationRule = paymentAllocationRules.stream()
-                .filter(e -> PaymentAllocationTransactionType.DEFAULT.equals(e.getTransactionType())).findFirst().orElseThrow();
+                .filter(e -> DEFAULT.equals(e.getTransactionType())).findFirst().orElseThrow();
         LoanPaymentAllocationRule paymentAllocationRule = paymentAllocationRules.stream()
                 .filter(e -> loanTransaction.getTypeOf().equals(e.getTransactionType().getLoanTransactionType())).findFirst()
                 .orElse(defaultPaymentAllocationRule);
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 16760fc..71521ef 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
@@ -39,4 +39,5 @@
   <include relativeToChangelogFile="true" file="parts/1014_add_loan_account_custom_snapshot_event.xml"/>
   <include relativeToChangelogFile="true" file="parts/1015_remove_disable_schedule_extension_column.xml"/>
   <include relativeToChangelogFile="true" file="parts/1016_add_credit_allocation_rule.xml"/>
+  <include relativeToChangelogFile="true" file="parts/1017_add_fee_and_penalty_adjustments_to_loan.xml"/>
 </databaseChangeLog>
diff --git a/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1017_add_fee_and_penalty_adjustments_to_loan.xml b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1017_add_fee_and_penalty_adjustments_to_loan.xml
new file mode 100644
index 0000000..faa79a4
--- /dev/null
+++ b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1017_add_fee_and_penalty_adjustments_to_loan.xml
@@ -0,0 +1,43 @@
+<?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">
+            <column name="fee_adjustments_derived" type="DECIMAL(19, 6)" defaultValueNumeric="0.000000">
+                <constraints nullable="false"/>
+            </column>
+            <column name="penalty_adjustments_derived" type="DECIMAL(19, 6)" defaultValueNumeric="0.000000">
+                <constraints nullable="false"/>
+            </column>
+        </addColumn>
+    </changeSet>
+
+    <changeSet author="fineract" id="2">
+        <addColumn tableName="m_loan_repayment_schedule">
+            <column defaultValueComputed="NULL" name="credited_fee" type="DECIMAL(19, 6)"/>
+            <column defaultValueComputed="NULL" name="credited_penalty" type="DECIMAL(19, 6)"/>
+        </addColumn>
+        <renameColumn tableName="m_loan_repayment_schedule" oldColumnName="credits_amount" newColumnName="credited_principal"  columnDataType="DECIMAL(19, 6)"/>
+    </changeSet>
+
+
+</databaseChangeLog>
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java
index d93575a..375d506 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java
@@ -524,6 +524,8 @@
             public Double interestOutstanding;
             @Schema(example = "200000.000000")
             public Double interestOverdue;
+            @Schema(example = "0.00")
+            public Double feeAdjustments;
             @Schema(example = "18000.000000")
             public Double feeChargesCharged;
             @Schema(example = "0.000000")
@@ -538,6 +540,8 @@
             public Double feeChargesOutstanding;
             @Schema(example = "15000.000000")
             public Double feeChargesOverdue;
+            @Schema(example = "0.00")
+            public Double penaltyAdjustments;
             @Schema(example = "0.000000")
             public Double penaltyChargesCharged;
             @Schema(example = "0.000000")
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanScheduleAccrualData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanScheduleAccrualData.java
index 6fcb359..21abaa1 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanScheduleAccrualData.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanScheduleAccrualData.java
@@ -51,12 +51,15 @@
     private BigDecimal dueDatePenaltyIncome;
     private BigDecimal accruableIncome;
 
+    private BigDecimal creditedFee;
+    private BigDecimal creditedPenalty;
+
     public LoanScheduleAccrualData(final Long loanId, final Long officeId, final Integer installmentNumber, final LocalDate accruedTill,
             final PeriodFrequencyType repaymentFrequency, final Integer repayEvery, final LocalDate dueDate, final LocalDate fromDate,
             final Long repaymentScheduleId, final Long loanProductId, final BigDecimal interestIncome, final BigDecimal feeIncome,
             final BigDecimal penaltyIncome, final BigDecimal accruedInterestIncome, final BigDecimal accruedFeeIncome,
             final BigDecimal accruedPenaltyIncome, final CurrencyData currencyData, final LocalDate interestCalculatedFrom,
-            final BigDecimal waivedInterestIncome) {
+            final BigDecimal waivedInterestIncome, BigDecimal creditedFee, BigDecimal creditedPenalty) {
         this.loanId = loanId;
         this.installmentNumber = installmentNumber;
         this.officeId = officeId;
@@ -76,6 +79,8 @@
         this.repayEvery = repayEvery;
         this.interestCalculatedFrom = interestCalculatedFrom;
         this.waivedInterestIncome = waivedInterestIncome;
+        this.creditedFee = creditedFee;
+        this.creditedPenalty = creditedPenalty;
     }
 
     public Long getLoanId() {
@@ -185,4 +190,12 @@
         this.accruableIncome = accruableIncome;
     }
 
+    public BigDecimal getCreditedFee() {
+        return this.creditedFee;
+    }
+
+    public BigDecimal getCreditedPenalty() {
+        return this.creditedPenalty;
+    }
+
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanSummaryData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanSummaryData.java
index 73e7be4..42ebbd6 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanSummaryData.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanSummaryData.java
@@ -21,6 +21,7 @@
 import java.math.BigDecimal;
 import java.time.LocalDate;
 import java.util.Collection;
+import lombok.Builder;
 import lombok.Data;
 import lombok.experimental.Accessors;
 import org.apache.fineract.organisation.monetary.data.CurrencyData;
@@ -31,6 +32,7 @@
  * Immutable data object representing loan summary information.
  */
 @Data
+@Builder
 @Accessors(chain = true)
 public class LoanSummaryData {
 
@@ -48,6 +50,7 @@
     private final BigDecimal interestOutstanding;
     private final BigDecimal interestOverdue;
     private final BigDecimal feeChargesCharged;
+    private final BigDecimal feeAdjustments;
     private final BigDecimal feeChargesDueAtDisbursementCharged;
     private final BigDecimal feeChargesPaid;
     private final BigDecimal feeChargesWaived;
@@ -55,6 +58,7 @@
     private final BigDecimal feeChargesOutstanding;
     private final BigDecimal feeChargesOverdue;
     private final BigDecimal penaltyChargesCharged;
+    private final BigDecimal penaltyAdjustments;
     private final BigDecimal penaltyChargesPaid;
     private final BigDecimal penaltyChargesWaived;
     private final BigDecimal penaltyChargesWrittenOff;
@@ -90,61 +94,6 @@
     private final Long chargeOffReasonId;
     private final String chargeOffReason;
 
-    public LoanSummaryData(final CurrencyData currency, final BigDecimal principalDisbursed, final BigDecimal principalAdjustments,
-            final BigDecimal principalPaid, final BigDecimal principalWrittenOff, final BigDecimal principalOutstanding,
-            final BigDecimal principalOverdue, final BigDecimal interestCharged, final BigDecimal interestPaid,
-            final BigDecimal interestWaived, final BigDecimal interestWrittenOff, final BigDecimal interestOutstanding,
-            final BigDecimal interestOverdue, final BigDecimal feeChargesCharged, final BigDecimal feeChargesDueAtDisbursementCharged,
-            final BigDecimal feeChargesPaid, final BigDecimal feeChargesWaived, final BigDecimal feeChargesWrittenOff,
-            final BigDecimal feeChargesOutstanding, final BigDecimal feeChargesOverdue, final BigDecimal penaltyChargesCharged,
-            final BigDecimal penaltyChargesPaid, final BigDecimal penaltyChargesWaived, final BigDecimal penaltyChargesWrittenOff,
-            final BigDecimal penaltyChargesOutstanding, final BigDecimal penaltyChargesOverdue, final BigDecimal totalExpectedRepayment,
-            final BigDecimal totalRepayment, final BigDecimal totalExpectedCostOfLoan, final BigDecimal totalCostOfLoan,
-            final BigDecimal totalWaived, final BigDecimal totalWrittenOff, final BigDecimal totalOutstanding,
-            final BigDecimal totalOverdue, final LocalDate overdueSinceDate, final Long writeoffReasonId, final String writeoffReason,
-            final BigDecimal totalRecovered, final Long chargeOffReasonId, final String chargeOffReason) {
-        this.currency = currency;
-        this.principalDisbursed = principalDisbursed;
-        this.principalAdjustments = principalAdjustments;
-        this.principalPaid = principalPaid;
-        this.principalWrittenOff = principalWrittenOff;
-        this.principalOutstanding = principalOutstanding;
-        this.principalOverdue = principalOverdue;
-        this.interestCharged = interestCharged;
-        this.interestPaid = interestPaid;
-        this.interestWaived = interestWaived;
-        this.interestWrittenOff = interestWrittenOff;
-        this.interestOutstanding = interestOutstanding;
-        this.interestOverdue = interestOverdue;
-        this.feeChargesCharged = feeChargesCharged;
-        this.feeChargesDueAtDisbursementCharged = feeChargesDueAtDisbursementCharged;
-        this.feeChargesPaid = feeChargesPaid;
-        this.feeChargesWaived = feeChargesWaived;
-        this.feeChargesWrittenOff = feeChargesWrittenOff;
-        this.feeChargesOutstanding = feeChargesOutstanding;
-        this.feeChargesOverdue = feeChargesOverdue;
-        this.penaltyChargesCharged = penaltyChargesCharged;
-        this.penaltyChargesPaid = penaltyChargesPaid;
-        this.penaltyChargesWaived = penaltyChargesWaived;
-        this.penaltyChargesWrittenOff = penaltyChargesWrittenOff;
-        this.penaltyChargesOutstanding = penaltyChargesOutstanding;
-        this.penaltyChargesOverdue = penaltyChargesOverdue;
-        this.totalExpectedRepayment = totalExpectedRepayment;
-        this.totalRepayment = totalRepayment;
-        this.totalExpectedCostOfLoan = totalExpectedCostOfLoan;
-        this.totalCostOfLoan = totalCostOfLoan;
-        this.totalWaived = totalWaived;
-        this.totalWrittenOff = totalWrittenOff;
-        this.totalOutstanding = totalOutstanding;
-        this.totalOverdue = totalOverdue;
-        this.overdueSinceDate = overdueSinceDate;
-        this.writeoffReasonId = writeoffReasonId;
-        this.writeoffReason = writeoffReason;
-        this.totalRecovered = totalRecovered;
-        this.chargeOffReasonId = chargeOffReasonId;
-        this.chargeOffReason = chargeOffReason;
-    }
-
     public static LoanSummaryData withTransactionAmountsSummary(final LoanSummaryData defaultSummaryData,
             final Collection<LoanTransactionData> loanTransactions) {
 
@@ -184,34 +133,41 @@
             totalRepaymentTransactionReversed = computeTotalAmountForReversedTransactions(LoanTransactionType.REPAYMENT, loanTransactions);
         }
 
-        return new LoanSummaryData(defaultSummaryData.currency, defaultSummaryData.principalDisbursed,
-                defaultSummaryData.principalAdjustments, defaultSummaryData.principalPaid, defaultSummaryData.principalWrittenOff,
-                defaultSummaryData.principalOutstanding, defaultSummaryData.principalOverdue, defaultSummaryData.interestCharged,
-                defaultSummaryData.interestPaid, defaultSummaryData.interestWaived, defaultSummaryData.interestWrittenOff,
-                defaultSummaryData.interestOutstanding, defaultSummaryData.interestOverdue, defaultSummaryData.feeChargesCharged,
-                defaultSummaryData.feeChargesDueAtDisbursementCharged, defaultSummaryData.feeChargesPaid,
-                defaultSummaryData.feeChargesWaived, defaultSummaryData.feeChargesWrittenOff, defaultSummaryData.feeChargesOutstanding,
-                defaultSummaryData.feeChargesOverdue, defaultSummaryData.penaltyChargesCharged, defaultSummaryData.penaltyChargesPaid,
-                defaultSummaryData.penaltyChargesWaived, defaultSummaryData.penaltyChargesWrittenOff,
-                defaultSummaryData.penaltyChargesOutstanding, defaultSummaryData.penaltyChargesOverdue,
-                defaultSummaryData.totalExpectedRepayment, defaultSummaryData.totalRepayment, defaultSummaryData.totalExpectedCostOfLoan,
-                defaultSummaryData.totalCostOfLoan, defaultSummaryData.totalWaived, defaultSummaryData.totalWrittenOff,
-                defaultSummaryData.totalOutstanding, defaultSummaryData.totalOverdue, defaultSummaryData.overdueSinceDate,
-                defaultSummaryData.writeoffReasonId, defaultSummaryData.writeoffReason, defaultSummaryData.totalRecovered,
-                defaultSummaryData.chargeOffReasonId, defaultSummaryData.chargeOffReason).setTotalMerchantRefund(totalMerchantRefund)
-                .setTotalMerchantRefundReversed(totalMerchantRefundReversed).setTotalPayoutRefund(totalPayoutRefund)
-                .setTotalPayoutRefundReversed(totalPayoutRefundReversed).setTotalGoodwillCredit(totalGoodwillCredit)
-                .setTotalGoodwillCreditReversed(totalGoodwillCreditReversed).setTotalChargeAdjustment(totalChargeAdjustment)
-                .setTotalChargeAdjustmentReversed(totalChargeAdjustmentReversed).setTotalChargeback(totalChargeback)
-                .setTotalCreditBalanceRefund(totalCreditBalanceRefund).setTotalCreditBalanceRefundReversed(totalCreditBalanceRefundReversed)
-                .setTotalRepaymentTransaction(totalRepaymentTransaction)
-                .setTotalRepaymentTransactionReversed(totalRepaymentTransactionReversed);
+        return LoanSummaryData.builder().currency(defaultSummaryData.currency).principalDisbursed(defaultSummaryData.principalDisbursed)
+                .principalAdjustments(defaultSummaryData.principalAdjustments).principalPaid(defaultSummaryData.principalPaid)
+                .principalWrittenOff(defaultSummaryData.principalWrittenOff).principalOutstanding(defaultSummaryData.principalOutstanding)
+                .principalOverdue(defaultSummaryData.principalOverdue).interestCharged(defaultSummaryData.interestCharged)
+                .interestPaid(defaultSummaryData.interestPaid).interestWaived(defaultSummaryData.interestWaived)
+                .interestWrittenOff(defaultSummaryData.interestWrittenOff).interestOutstanding(defaultSummaryData.interestOutstanding)
+                .interestOverdue(defaultSummaryData.interestOverdue).feeChargesCharged(defaultSummaryData.feeChargesCharged)
+                .feeAdjustments(defaultSummaryData.feeAdjustments)
+                .feeChargesDueAtDisbursementCharged(defaultSummaryData.feeChargesDueAtDisbursementCharged)
+                .feeChargesPaid(defaultSummaryData.feeChargesPaid).feeChargesWaived(defaultSummaryData.feeChargesWaived)
+                .feeChargesWrittenOff(defaultSummaryData.feeChargesWrittenOff)
+                .feeChargesOutstanding(defaultSummaryData.feeChargesOutstanding).feeChargesOverdue(defaultSummaryData.feeChargesOverdue)
+                .penaltyChargesCharged(defaultSummaryData.penaltyChargesCharged).penaltyAdjustments(defaultSummaryData.penaltyAdjustments)
+                .penaltyChargesPaid(defaultSummaryData.penaltyChargesPaid).penaltyChargesWaived(defaultSummaryData.penaltyChargesWaived)
+                .penaltyChargesWrittenOff(defaultSummaryData.penaltyChargesWrittenOff)
+                .penaltyChargesOutstanding(defaultSummaryData.penaltyChargesOutstanding)
+                .penaltyChargesOverdue(defaultSummaryData.penaltyChargesOverdue)
+                .totalExpectedRepayment(defaultSummaryData.totalExpectedRepayment).totalRepayment(defaultSummaryData.totalRepayment)
+                .totalExpectedCostOfLoan(defaultSummaryData.totalExpectedCostOfLoan).totalCostOfLoan(defaultSummaryData.totalCostOfLoan)
+                .totalWaived(defaultSummaryData.totalWaived).totalWrittenOff(defaultSummaryData.totalWrittenOff)
+                .totalOutstanding(defaultSummaryData.totalOutstanding).totalOverdue(defaultSummaryData.totalOverdue)
+                .overdueSinceDate(defaultSummaryData.overdueSinceDate).writeoffReasonId(defaultSummaryData.writeoffReasonId)
+                .writeoffReason(defaultSummaryData.writeoffReason).totalRecovered(defaultSummaryData.totalRecovered)
+                .chargeOffReasonId(defaultSummaryData.chargeOffReasonId).chargeOffReason(defaultSummaryData.chargeOffReason)
+                .totalMerchantRefund(totalMerchantRefund).totalMerchantRefundReversed(totalMerchantRefundReversed)
+                .totalPayoutRefund(totalPayoutRefund).totalPayoutRefundReversed(totalPayoutRefundReversed)
+                .totalGoodwillCredit(totalGoodwillCredit).totalGoodwillCreditReversed(totalGoodwillCreditReversed)
+                .totalChargeAdjustment(totalChargeAdjustment).totalChargeAdjustmentReversed(totalChargeAdjustmentReversed)
+                .totalChargeback(totalChargeback).totalCreditBalanceRefund(totalCreditBalanceRefund)
+                .totalCreditBalanceRefundReversed(totalCreditBalanceRefundReversed).totalRepaymentTransaction(totalRepaymentTransaction)
+                .totalRepaymentTransactionReversed(totalRepaymentTransactionReversed).build();
     }
 
     public static LoanSummaryData withOnlyCurrencyData(CurrencyData currencyData) {
-        return new LoanSummaryData(currencyData, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-                null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-                null, null, null, null);
+        return LoanSummaryData.builder().currency(currencyData).build();
     }
 
     private static BigDecimal computeTotalAmountForReversedTransactions(LoanTransactionType transactionType,
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
index f587857..cc79581 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
@@ -695,7 +695,8 @@
                     installment.getFeeChargesCharged(currency).getAmount(), installment.getPenaltyChargesCharged(currency).getAmount(),
                     installment.getInterestAccrued(currency).getAmount(), installment.getFeeAccrued(currency).getAmount(),
                     installment.getPenaltyAccrued(currency).getAmount(), currencyData, interestCalculatedFrom,
-                    installment.getInterestWaived(currency).getAmount());
+                    installment.getInterestWaived(currency).getAmount(), installment.getCreditedFee(currency).getAmount(),
+                    installment.getCreditedPenalty(currency).getAmount());
             loanScheduleAccrualDatas.add(accrualData);
 
         }
@@ -940,10 +941,12 @@
                         .minus(loanRepaymentScheduleInstallment.getInterestWaived(currency));
                 feePortion = feePortion.add(loanRepaymentScheduleInstallment.getFeeChargesCharged(currency))
                         .minus(loanRepaymentScheduleInstallment.getFeeAccrued(currency))
-                        .minus(loanRepaymentScheduleInstallment.getFeeChargesWaived(currency));
+                        .minus(loanRepaymentScheduleInstallment.getFeeChargesWaived(currency))
+                        .minus(loanRepaymentScheduleInstallment.getCreditedFee(currency));
                 penaltyPortion = penaltyPortion.add(loanRepaymentScheduleInstallment.getPenaltyChargesCharged(currency))
                         .minus(loanRepaymentScheduleInstallment.getPenaltyAccrued(currency))
-                        .minus(loanRepaymentScheduleInstallment.getPenaltyChargesWaived(currency));
+                        .minus(loanRepaymentScheduleInstallment.getPenaltyChargesWaived(currency))
+                        .minus(loanRepaymentScheduleInstallment.getCreditedPenalty(currency));
             }
             Money total = interestPortion.plus(feePortion).plus(penaltyPortion);
 
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java
index 27b4d16..4172f5c 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java
@@ -168,7 +168,9 @@
 
         BigDecimal totalAccInterest = accrualData.getAccruedInterestIncome();
         BigDecimal totalAccPenalty = accrualData.getAccruedPenaltyIncome();
+        BigDecimal totalCreditedPenalty = accrualData.getCreditedPenalty();
         BigDecimal totalAccFee = accrualData.getAccruedFeeIncome();
+        BigDecimal totalCreditedFee = accrualData.getCreditedFee();
 
         if (totalAccInterest == null) {
             totalAccInterest = BigDecimal.ZERO;
@@ -183,7 +185,10 @@
             if (totalAccFee == null) {
                 totalAccFee = BigDecimal.ZERO;
             }
-            feePortion = feePortion.subtract(totalAccFee);
+            if (totalCreditedFee == null) {
+                totalCreditedFee = BigDecimal.ZERO;
+            }
+            feePortion = feePortion.subtract(totalAccFee).subtract(totalCreditedFee);
             amount = amount.add(feePortion);
             totalAccFee = totalAccFee.add(feePortion);
             if (feePortion.compareTo(BigDecimal.ZERO) == 0) {
@@ -195,7 +200,10 @@
             if (totalAccPenalty == null) {
                 totalAccPenalty = BigDecimal.ZERO;
             }
-            penaltyPortion = penaltyPortion.subtract(totalAccPenalty);
+            if (totalCreditedPenalty == null) {
+                totalCreditedPenalty = BigDecimal.ZERO;
+            }
+            penaltyPortion = penaltyPortion.subtract(totalAccPenalty).subtract(totalCreditedPenalty);
             amount = amount.add(penaltyPortion);
             totalAccPenalty = totalAccPenalty.add(penaltyPortion);
             if (penaltyPortion.compareTo(BigDecimal.ZERO) == 0) {
@@ -234,6 +242,9 @@
             if (scheduleAccrualData.getAccruedFeeIncome() != null) {
                 feePortion = feePortion.subtract(scheduleAccrualData.getAccruedFeeIncome());
             }
+            if (scheduleAccrualData.getCreditedFee() != null) {
+                feePortion = feePortion.subtract(scheduleAccrualData.getCreditedFee());
+            }
             amount = amount.add(feePortion);
             if (feePortion.compareTo(BigDecimal.ZERO) == 0) {
                 feePortion = null;
@@ -248,6 +259,9 @@
             if (scheduleAccrualData.getAccruedPenaltyIncome() != null) {
                 penaltyPortion = penaltyPortion.subtract(scheduleAccrualData.getAccruedPenaltyIncome());
             }
+            if (scheduleAccrualData.getCreditedPenalty() != null) {
+                penaltyPortion = penaltyPortion.subtract(scheduleAccrualData.getCreditedPenalty());
+            }
             amount = amount.add(penaltyPortion);
             if (penaltyPortion.compareTo(BigDecimal.ZERO) == 0) {
                 penaltyPortion = null;
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
index c804805..6d6643f 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
@@ -649,6 +649,7 @@
                     + " l.loan_officer_id as loanOfficerId, s.display_name as loanOfficerName, "
                     + " l.principal_disbursed_derived as principalDisbursed, l.principal_repaid_derived as principalPaid,"
                     + " l.principal_adjustments_derived as principalAdjustments, l.principal_writtenoff_derived as principalWrittenOff,"
+                    + " l.fee_adjustments_derived as feeAdjustments, l.penalty_adjustments_derived as penaltyAdjustments,"
                     + " l.principal_outstanding_derived as principalOutstanding, l.interest_charged_derived as interestCharged,"
                     + " l.interest_repaid_derived as interestPaid, l.interest_waived_derived as interestWaived,"
                     + " l.interest_writtenoff_derived as interestWrittenOff, l.interest_outstanding_derived as interestOutstanding,"
@@ -912,6 +913,7 @@
                 final BigDecimal interestOverdue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestOverdue");
 
                 final BigDecimal feeChargesCharged = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeChargesCharged");
+                final BigDecimal feeAdjustments = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeAdjustments");
                 final BigDecimal feeChargesPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeChargesPaid");
                 final BigDecimal feeChargesWaived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeChargesWaived");
                 final BigDecimal feeChargesWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeChargesWrittenOff");
@@ -919,6 +921,7 @@
                 final BigDecimal feeChargesOverdue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeChargesOverdue");
 
                 final BigDecimal penaltyChargesCharged = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penaltyChargesCharged");
+                final BigDecimal penaltyAdjustments = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penaltyAdjustments");
                 final BigDecimal penaltyChargesPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penaltyChargesPaid");
                 final BigDecimal penaltyChargesWaived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penaltyChargesWaived");
                 final BigDecimal penaltyChargesWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penaltyChargesWrittenOff");
@@ -938,14 +941,24 @@
                 final LocalDate overdueSinceDate = JdbcSupport.getLocalDate(rs, "overdueSinceDate");
                 inArrears = (overdueSinceDate != null);
 
-                loanSummary = new LoanSummaryData(currencyData, principalDisbursed, principalAdjustments, principalPaid,
-                        principalWrittenOff, principalOutstanding, principalOverdue, interestCharged, interestPaid, interestWaived,
-                        interestWrittenOff, interestOutstanding, interestOverdue, feeChargesCharged, feeChargesDueAtDisbursementCharged,
-                        feeChargesPaid, feeChargesWaived, feeChargesWrittenOff, feeChargesOutstanding, feeChargesOverdue,
-                        penaltyChargesCharged, penaltyChargesPaid, penaltyChargesWaived, penaltyChargesWrittenOff,
-                        penaltyChargesOutstanding, penaltyChargesOverdue, totalExpectedRepayment, totalRepayment, totalExpectedCostOfLoan,
-                        totalCostOfLoan, totalWaived, totalWrittenOff, totalOutstanding, totalOverdue, overdueSinceDate, writeoffReasonId,
-                        writeoffReason, totalRecovered, chargeOffReasonId, chargeOffReason);
+                loanSummary = LoanSummaryData.builder().currency(currencyData).principalDisbursed(principalDisbursed)
+                        .principalAdjustments(principalAdjustments).principalPaid(principalPaid).principalWrittenOff(principalWrittenOff)
+                        .principalOutstanding(principalOutstanding).principalOverdue(principalOverdue).interestCharged(interestCharged)
+                        .interestPaid(interestPaid).interestWaived(interestWaived).interestWrittenOff(interestWrittenOff)
+                        .interestOutstanding(interestOutstanding).interestOverdue(interestOverdue).feeChargesCharged(feeChargesCharged)
+                        .feeAdjustments(feeAdjustments).feeChargesDueAtDisbursementCharged(feeChargesDueAtDisbursementCharged)
+                        .feeChargesPaid(feeChargesPaid).feeChargesWaived(feeChargesWaived).feeChargesWrittenOff(feeChargesWrittenOff)
+                        .feeChargesOutstanding(feeChargesOutstanding).feeChargesOverdue(feeChargesOverdue)
+                        .penaltyChargesCharged(penaltyChargesCharged).penaltyAdjustments(penaltyAdjustments)
+                        .penaltyChargesPaid(penaltyChargesPaid).penaltyChargesWaived(penaltyChargesWaived)
+                        .penaltyChargesWrittenOff(penaltyChargesWrittenOff).penaltyChargesOutstanding(penaltyChargesOutstanding)
+                        .penaltyChargesOverdue(penaltyChargesOverdue).totalExpectedRepayment(totalExpectedRepayment)
+                        .totalRepayment(totalRepayment).totalExpectedCostOfLoan(totalExpectedCostOfLoan).totalCostOfLoan(totalCostOfLoan)
+                        .totalWaived(totalWaived).totalWrittenOff(totalWrittenOff).totalOutstanding(totalOutstanding)
+                        .totalOverdue(totalOverdue).overdueSinceDate(overdueSinceDate).writeoffReasonId(writeoffReasonId)
+                        .writeoffReason(writeoffReason).totalRecovered(totalRecovered).chargeOffReasonId(chargeOffReasonId)
+                        .chargeOffReason(chargeOffReason).build();
+
             }
 
             GroupGeneralData groupData = null;
@@ -1144,7 +1157,7 @@
                     + " ls.fee_charges_amount as feeChargesDue, ls.fee_charges_completed_derived as feeChargesPaid, ls.fee_charges_waived_derived as feeChargesWaived, ls.fee_charges_writtenoff_derived as feeChargesWrittenOff, "
                     + " ls.penalty_charges_amount as penaltyChargesDue, ls.penalty_charges_completed_derived as penaltyChargesPaid, ls.penalty_charges_waived_derived as penaltyChargesWaived, "
                     + " ls.penalty_charges_writtenoff_derived as penaltyChargesWrittenOff, ls.total_paid_in_advance_derived as totalPaidInAdvanceForPeriod, "
-                    + " ls.total_paid_late_derived as totalPaidLateForPeriod, ls.credits_amount as totalCredits, ls.is_down_payment isDownPayment "
+                    + " ls.total_paid_late_derived as totalPaidLateForPeriod, (coalesce(ls.credited_principal,0) + coalesce(ls.credited_fee,0) + coalesce(ls.credited_penalty,0)) as totalCredits, ls.is_down_payment isDownPayment "
                     + " from m_loan_repayment_schedule ls ";
         }
 
@@ -1912,6 +1925,7 @@
                     .append("ls.duedate as duedate,ls.fromdate as fromdate ,ls.id as scheduleId,loan.product_id as productId,")
                     .append("ls.interest_amount as interest, ls.interest_waived_derived as interestWaived,")
                     .append("ls.penalty_charges_amount as penalty, ").append("ls.fee_charges_amount as charges, ")
+                    .append("ls.credited_penalty as credited_penalty, ").append("ls.credited_fee as credited_fee, ")
                     .append("ls.accrual_interest_derived as accinterest,ls.accrual_fee_charges_derived as accfeecharege,ls.accrual_penalty_charges_derived as accpenalty,")
                     .append(" loan.currency_code as currencyCode,loan.currency_digits as currencyDigits,loan.currency_multiplesof as inMultiplesOf,")
                     .append("curr.display_symbol as currencyDisplaySymbol,curr.name as currencyName,curr.internationalized_name_code as currencyNameCode")
@@ -1946,6 +1960,8 @@
             final BigDecimal accruedInterestIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "accinterest");
             final BigDecimal accruedFeeIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "accfeecharege");
             final BigDecimal accruedPenaltyIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "accpenalty");
+            final BigDecimal creditedFee = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "credited_fee");
+            final BigDecimal creditedPenalty = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "credited_penalty");
 
             final String currencyCode = rs.getString("currencyCode");
             final String currencyName = rs.getString("currencyName");
@@ -1958,7 +1974,7 @@
 
             return new LoanScheduleAccrualData(loanId, officeId, installmentNumber, accruedTill, frequency, repayEvery, dueDate, fromDate,
                     repaymentScheduleId, loanProductId, interestIncome, feeIncome, penaltyIncome, accruedInterestIncome, accruedFeeIncome,
-                    accruedPenaltyIncome, currencyData, interestCalculatedFrom, interestIncomeWaived);
+                    accruedPenaltyIncome, currencyData, interestCalculatedFrom, interestIncomeWaived, creditedFee, creditedPenalty);
         }
 
     }
@@ -1972,6 +1988,7 @@
                     .append("ls.installment as installmentNumber, ")
                     .append("ls.interest_amount as interest, ls.interest_waived_derived as interestWaived,")
                     .append("ls.penalty_charges_amount as penalty, ").append("ls.fee_charges_amount as charges, ")
+                    .append("ls.credited_penalty as credited_penalty, ").append("ls.credited_fee as credited_fee, ")
                     .append("ls.accrual_interest_derived as accinterest,ls.accrual_fee_charges_derived as accfeecharege,ls.accrual_penalty_charges_derived as accpenalty,")
                     .append(" loan.currency_code as currencyCode,loan.currency_digits as currencyDigits,loan.currency_multiplesof as inMultiplesOf,")
                     .append("curr.display_symbol as currencyDisplaySymbol,curr.name as currencyName,curr.internationalized_name_code as currencyNameCode")
@@ -1996,6 +2013,9 @@
             final BigDecimal interestIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "interest");
             final BigDecimal feeIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "charges");
             final BigDecimal penaltyIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "penalty");
+            final BigDecimal creditedFee = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "credited_fee");
+            final BigDecimal creditedPenalty = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "credited_penalty");
+
             final BigDecimal interestIncomeWaived = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "interestWaived");
             final BigDecimal accruedInterestIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "accinterest");
             final BigDecimal accruedFeeIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "accfeecharege");
@@ -2015,7 +2035,7 @@
             final LocalDate interestCalculatedFrom = null;
             return new LoanScheduleAccrualData(loanId, officeId, installmentNumber, accruedTill, frequency, repayEvery, dueDate, fromdate,
                     repaymentScheduleId, loanProductId, interestIncome, feeIncome, penaltyIncome, accruedInterestIncome, accruedFeeIncome,
-                    accruedPenaltyIncome, currencyData, interestCalculatedFrom, interestIncomeWaived);
+                    accruedPenaltyIncome, currencyData, interestCalculatedFrom, interestIncomeWaived, creditedFee, creditedPenalty);
         }
     }
 
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 fb79dc2..e4ba465 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
@@ -24,11 +24,15 @@
 import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.PENALTY;
 import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.PRINCIPAL;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.refEq;
 import static org.mockito.Mockito.lenient;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.when;
 
 import java.math.BigDecimal;
 import java.math.RoundingMode;
@@ -83,7 +87,7 @@
 
     private final LocalDate transactionDate = LocalDate.of(2023, 7, 11);
     private static final MonetaryCurrency MONETARY_CURRENCY = new MonetaryCurrency("USD", 2, 1);
-    private static final MockedStatic<MoneyHelper> MONEY_HELPER = Mockito.mockStatic(MoneyHelper.class);
+    private static final MockedStatic<MoneyHelper> MONEY_HELPER = mockStatic(MoneyHelper.class);
     private AdvancedPaymentScheduleTransactionProcessor underTest;
 
     @BeforeAll
@@ -123,34 +127,33 @@
         Money zero = Money.zero(currency);
         Loan loan = mock(Loan.class);
         Money chargeAmountMoney = Money.of(currency, chargeAmount);
-        LoanRepaymentScheduleInstallment installment = Mockito
-                .spy(new LoanRepaymentScheduleInstallment(loan, 1, disbursementDate, disbursementDate.plusMonths(1), BigDecimal.valueOf(0L),
+        LoanRepaymentScheduleInstallment installment = spy(
+                new LoanRepaymentScheduleInstallment(loan, 1, disbursementDate, disbursementDate.plusMonths(1), BigDecimal.valueOf(0L),
                         BigDecimal.valueOf(0L), chargeAmount, BigDecimal.valueOf(0L), false, null, BigDecimal.ZERO));
 
-        Mockito.when(loanTransaction.getTypeOf()).thenReturn(LoanTransactionType.CHARGE_PAYMENT);
-        Mockito.when(chargePaidBy.getLoanCharge()).thenReturn(charge);
-        Mockito.when(loanTransaction.getLoanChargesPaid()).thenReturn(Set.of(chargePaidBy));
-        Mockito.when(loanTransaction.getAmount()).thenReturn(chargeAmount);
-        Mockito.when(loanTransaction.getAmount(currency)).thenReturn(chargeAmountMoney);
-        Mockito.when(loanTransaction.getTransactionDate()).thenReturn(transactionDate);
-        Mockito.when(charge.getAmountOutstanding(currency)).thenReturn(chargeAmountMoney);
-        Mockito.when(loanTransaction.getLoan()).thenReturn(loan);
-        Mockito.when(loan.getDisbursementDate()).thenReturn(disbursementDate);
-        Mockito.when(charge.isDueForCollectionFromIncludingAndUpToAndIncluding(disbursementDate, installment.getDueDate()))
-                .thenReturn(true);
-        Mockito.when(installment.getInstallmentNumber()).thenReturn(1);
-        Mockito.when(charge.updatePaidAmountBy(refEq(chargeAmountMoney), eq(1), refEq(zero))).thenReturn(chargeAmountMoney);
-        Mockito.when(loanTransaction.isPenaltyPayment()).thenReturn(false);
+        when(loanTransaction.getTypeOf()).thenReturn(LoanTransactionType.CHARGE_PAYMENT);
+        when(chargePaidBy.getLoanCharge()).thenReturn(charge);
+        when(loanTransaction.getLoanChargesPaid()).thenReturn(Set.of(chargePaidBy));
+        when(loanTransaction.getAmount()).thenReturn(chargeAmount);
+        when(loanTransaction.getAmount(currency)).thenReturn(chargeAmountMoney);
+        when(loanTransaction.getTransactionDate()).thenReturn(transactionDate);
+        when(charge.getAmountOutstanding(currency)).thenReturn(chargeAmountMoney);
+        when(loanTransaction.getLoan()).thenReturn(loan);
+        when(loan.getDisbursementDate()).thenReturn(disbursementDate);
+        when(charge.isDueForCollectionFromIncludingAndUpToAndIncluding(disbursementDate, installment.getDueDate())).thenReturn(true);
+        when(installment.getInstallmentNumber()).thenReturn(1);
+        when(charge.updatePaidAmountBy(refEq(chargeAmountMoney), eq(1), refEq(zero))).thenReturn(chargeAmountMoney);
+        when(loanTransaction.isPenaltyPayment()).thenReturn(false);
 
         underTest.processLatestTransaction(loanTransaction,
                 new TransactionCtx(currency, List.of(installment), Set.of(charge), new MoneyHolder(overpaidAmount)));
 
-        Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), eq(chargeAmountMoney));
-        Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(zero), refEq(zero), refEq(chargeAmountMoney), refEq(zero));
+        Mockito.verify(installment, times(1)).payFeeChargesComponent(eq(transactionDate), eq(chargeAmountMoney));
+        Mockito.verify(loanTransaction, times(1)).updateComponents(refEq(zero), refEq(zero), refEq(chargeAmountMoney), refEq(zero));
         assertEquals(zero.getAmount(), loanTransaction.getAmount(currency).minus(chargeAmountMoney).getAmount());
         assertEquals(0, chargeAmount.compareTo(installment.getFeeChargesCharged(currency).getAmount()));
         assertEquals(0, BigDecimal.ZERO.compareTo(installment.getFeeChargesOutstanding(currency).getAmount()));
-        Mockito.verify(loan, Mockito.times(0)).getPaymentAllocationRules();
+        Mockito.verify(loan, times(0)).getPaymentAllocationRules();
     }
 
     @Test
@@ -168,35 +171,33 @@
         Money chargeAmountMoney = Money.of(currency, chargeAmount);
         BigDecimal transactionAmount = BigDecimal.valueOf(20.00);
         Money transactionAmountMoney = Money.of(currency, transactionAmount);
-        LoanRepaymentScheduleInstallment installment = Mockito
-                .spy(new LoanRepaymentScheduleInstallment(loan, 1, disbursementDate, disbursementDate.plusMonths(1), BigDecimal.valueOf(0L),
+        LoanRepaymentScheduleInstallment installment = spy(
+                new LoanRepaymentScheduleInstallment(loan, 1, disbursementDate, disbursementDate.plusMonths(1), BigDecimal.valueOf(0L),
                         BigDecimal.valueOf(0L), chargeAmount, BigDecimal.valueOf(0L), false, null, BigDecimal.ZERO));
 
-        Mockito.when(loanTransaction.getTypeOf()).thenReturn(LoanTransactionType.CHARGE_PAYMENT);
-        Mockito.when(chargePaidBy.getLoanCharge()).thenReturn(charge);
-        Mockito.when(loanTransaction.getLoanChargesPaid()).thenReturn(Set.of(chargePaidBy));
-        Mockito.when(loanTransaction.getAmount()).thenReturn(transactionAmount);
-        Mockito.when(loanTransaction.getAmount(currency)).thenReturn(transactionAmountMoney);
-        Mockito.when(loanTransaction.getTransactionDate()).thenReturn(transactionDate);
-        Mockito.when(charge.getAmountOutstanding(currency)).thenReturn(chargeAmountMoney);
-        Mockito.when(loanTransaction.getLoan()).thenReturn(loan);
-        Mockito.when(loan.getDisbursementDate()).thenReturn(disbursementDate);
-        Mockito.when(charge.isDueForCollectionFromIncludingAndUpToAndIncluding(disbursementDate, installment.getDueDate()))
-                .thenReturn(true);
-        Mockito.when(installment.getInstallmentNumber()).thenReturn(1);
-        Mockito.when(charge.updatePaidAmountBy(refEq(transactionAmountMoney), eq(1), refEq(zero))).thenReturn(transactionAmountMoney);
-        Mockito.when(loanTransaction.isPenaltyPayment()).thenReturn(false);
+        when(loanTransaction.getTypeOf()).thenReturn(LoanTransactionType.CHARGE_PAYMENT);
+        when(chargePaidBy.getLoanCharge()).thenReturn(charge);
+        when(loanTransaction.getLoanChargesPaid()).thenReturn(Set.of(chargePaidBy));
+        when(loanTransaction.getAmount()).thenReturn(transactionAmount);
+        when(loanTransaction.getAmount(currency)).thenReturn(transactionAmountMoney);
+        when(loanTransaction.getTransactionDate()).thenReturn(transactionDate);
+        when(charge.getAmountOutstanding(currency)).thenReturn(chargeAmountMoney);
+        when(loanTransaction.getLoan()).thenReturn(loan);
+        when(loan.getDisbursementDate()).thenReturn(disbursementDate);
+        when(charge.isDueForCollectionFromIncludingAndUpToAndIncluding(disbursementDate, installment.getDueDate())).thenReturn(true);
+        when(installment.getInstallmentNumber()).thenReturn(1);
+        when(charge.updatePaidAmountBy(refEq(transactionAmountMoney), eq(1), refEq(zero))).thenReturn(transactionAmountMoney);
+        when(loanTransaction.isPenaltyPayment()).thenReturn(false);
 
         underTest.processLatestTransaction(loanTransaction,
                 new TransactionCtx(currency, List.of(installment), Set.of(charge), new MoneyHolder(overpaidAmount)));
 
-        Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), eq(transactionAmountMoney));
-        Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(zero), refEq(zero), refEq(transactionAmountMoney),
-                refEq(zero));
+        Mockito.verify(installment, times(1)).payFeeChargesComponent(eq(transactionDate), eq(transactionAmountMoney));
+        Mockito.verify(loanTransaction, times(1)).updateComponents(refEq(zero), refEq(zero), refEq(transactionAmountMoney), refEq(zero));
         assertEquals(zero.getAmount(), loanTransaction.getAmount(currency).minus(transactionAmountMoney).getAmount());
         assertEquals(0, chargeAmount.compareTo(installment.getFeeChargesCharged(currency).getAmount()));
         assertEquals(0, BigDecimal.valueOf(80.00).compareTo(installment.getFeeChargesOutstanding(currency).getAmount()));
-        Mockito.verify(loan, Mockito.times(0)).getPaymentAllocationRules();
+        Mockito.verify(loan, times(0)).getPaymentAllocationRules();
     }
 
     @Test
@@ -216,94 +217,50 @@
         BigDecimal transactionAmount = BigDecimal.valueOf(120.00);
         Money transactionAmountMoney = Money.of(currency, transactionAmount);
         LoanPaymentAllocationRule loanPaymentAllocationRule = mock(LoanPaymentAllocationRule.class);
-        LoanRepaymentScheduleInstallment installment = Mockito
-                .spy(new LoanRepaymentScheduleInstallment(loan, 1, disbursementDate, transactionDate, BigDecimal.valueOf(100L),
-                        BigDecimal.valueOf(0L), chargeAmount, BigDecimal.valueOf(0L), false, null, BigDecimal.ZERO));
+        LoanRepaymentScheduleInstallment installment = spy(new LoanRepaymentScheduleInstallment(loan, 1, disbursementDate, transactionDate,
+                BigDecimal.valueOf(100L), BigDecimal.valueOf(0L), chargeAmount, BigDecimal.valueOf(0L), false, null, BigDecimal.ZERO));
 
-        Mockito.when(loanTransaction.getTypeOf()).thenReturn(LoanTransactionType.CHARGE_PAYMENT);
-        Mockito.when(chargePaidBy.getLoanCharge()).thenReturn(charge);
-        Mockito.when(loanTransaction.getLoanChargesPaid()).thenReturn(Set.of(chargePaidBy));
-        Mockito.when(loanTransaction.getAmount()).thenReturn(transactionAmount);
-        Mockito.when(loanTransaction.getAmount(currency)).thenReturn(transactionAmountMoney);
-        Mockito.when(loanTransaction.getTransactionDate()).thenReturn(transactionDate);
-        Mockito.when(charge.getAmountOutstanding(currency)).thenReturn(chargeAmountMoney);
-        Mockito.when(loanTransaction.getLoan()).thenReturn(loan);
-        Mockito.when(loanTransaction.getLoan().getLoanProductRelatedDetail()).thenReturn(loanProductRelatedDetail);
-        Mockito.when(loanProductRelatedDetail.getLoanScheduleProcessingType()).thenReturn(LoanScheduleProcessingType.HORIZONTAL);
-        Mockito.when(loan.getDisbursementDate()).thenReturn(disbursementDate);
-        Mockito.when(charge.isDueForCollectionFromIncludingAndUpToAndIncluding(disbursementDate, installment.getDueDate()))
-                .thenReturn(true);
-        Mockito.when(installment.getInstallmentNumber()).thenReturn(1);
-        Mockito.when(charge.updatePaidAmountBy(refEq(chargeAmountMoney), eq(1), refEq(zero))).thenReturn(chargeAmountMoney);
-        Mockito.when(loanTransaction.isPenaltyPayment()).thenReturn(false);
-        Mockito.when(loan.getPaymentAllocationRules()).thenReturn(List.of(loanPaymentAllocationRule));
-        Mockito.when(loanPaymentAllocationRule.getTransactionType()).thenReturn(PaymentAllocationTransactionType.DEFAULT);
-        Mockito.when(loanPaymentAllocationRule.getAllocationTypes()).thenReturn(List.of(PaymentAllocationType.DUE_PRINCIPAL));
-        Mockito.when(loanTransaction.isOn(eq(transactionDate))).thenReturn(true);
+        when(loanTransaction.getTypeOf()).thenReturn(LoanTransactionType.CHARGE_PAYMENT);
+        when(chargePaidBy.getLoanCharge()).thenReturn(charge);
+        when(loanTransaction.getLoanChargesPaid()).thenReturn(Set.of(chargePaidBy));
+        when(loanTransaction.getAmount()).thenReturn(transactionAmount);
+        when(loanTransaction.getAmount(currency)).thenReturn(transactionAmountMoney);
+        when(loanTransaction.getTransactionDate()).thenReturn(transactionDate);
+        when(charge.getAmountOutstanding(currency)).thenReturn(chargeAmountMoney);
+        when(loanTransaction.getLoan()).thenReturn(loan);
+        when(loanTransaction.getLoan().getLoanProductRelatedDetail()).thenReturn(loanProductRelatedDetail);
+        when(loanProductRelatedDetail.getLoanScheduleProcessingType()).thenReturn(LoanScheduleProcessingType.HORIZONTAL);
+        when(loan.getDisbursementDate()).thenReturn(disbursementDate);
+        when(charge.isDueForCollectionFromIncludingAndUpToAndIncluding(disbursementDate, installment.getDueDate())).thenReturn(true);
+        when(installment.getInstallmentNumber()).thenReturn(1);
+        when(charge.updatePaidAmountBy(refEq(chargeAmountMoney), eq(1), refEq(zero))).thenReturn(chargeAmountMoney);
+        when(loanTransaction.isPenaltyPayment()).thenReturn(false);
+        when(loan.getPaymentAllocationRules()).thenReturn(List.of(loanPaymentAllocationRule));
+        when(loanPaymentAllocationRule.getTransactionType()).thenReturn(PaymentAllocationTransactionType.DEFAULT);
+        when(loanPaymentAllocationRule.getAllocationTypes()).thenReturn(List.of(PaymentAllocationType.DUE_PRINCIPAL));
+        when(loanTransaction.isOn(eq(transactionDate))).thenReturn(true);
 
         underTest.processLatestTransaction(loanTransaction,
                 new TransactionCtx(currency, List.of(installment), Set.of(charge), new MoneyHolder(overpaidAmount)));
 
-        Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), eq(chargeAmountMoney));
-        Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(zero), refEq(zero), refEq(chargeAmountMoney), refEq(zero));
+        Mockito.verify(installment, times(1)).payFeeChargesComponent(eq(transactionDate), eq(chargeAmountMoney));
+        Mockito.verify(loanTransaction, times(1)).updateComponents(refEq(zero), refEq(zero), refEq(chargeAmountMoney), refEq(zero));
         assertEquals(0, BigDecimal.valueOf(20).compareTo(loanTransaction.getAmount(currency).minus(chargeAmountMoney).getAmount()));
         assertEquals(0, chargeAmount.compareTo(installment.getFeeChargesCharged(currency).getAmount()));
         assertEquals(0, BigDecimal.ZERO.compareTo(installment.getFeeChargesOutstanding(currency).getAmount()));
         assertEquals(0, BigDecimal.valueOf(80).compareTo(installment.getPrincipalOutstanding(currency).getAmount()));
-        Mockito.verify(loan, Mockito.times(1)).getPaymentAllocationRules();
+        Mockito.verify(loan, times(1)).getPaymentAllocationRules();
     }
 
     @Test
-    public void testProcessCreditTransactionWithAllocationRuleInterestAndPrincipal() {
+    public void testProcessCreditTransactionWithAllocationRulePrincipalPenaltyFeeInterest() {
         // given
         Loan loan = mock(Loan.class);
-        LoanTransaction chargeBackTransaction = createChargebackTransaction(loan);
+        LoanTransaction chargeBackTransaction = createChargebackTransaction(loan, 25.0);
 
-        LoanCreditAllocationRule mockCreditAllocationRule = createMockCreditAllocationRule(INTEREST, PRINCIPAL, PENALTY, FEE);
-        Mockito.when(loan.getCreditAllocationRules()).thenReturn(List.of(mockCreditAllocationRule));
-        LoanTransaction repayment = createRepayment(loan, chargeBackTransaction);
-        lenient().when(loan.getLoanTransactions()).thenReturn(List.of(repayment));
-
-        MoneyHolder overpaymentHolder = new MoneyHolder(Money.zero(MONETARY_CURRENCY));
-        List<LoanRepaymentScheduleInstallment> installments = new ArrayList<>();
-        LoanRepaymentScheduleInstallment installment = createMockInstallment(LocalDate.of(2023, 1, 31), false);
-        installments.add(installment);
-
-        // when
-
-        TransactionCtx ctx = new TransactionCtx(MONETARY_CURRENCY, installments, null, overpaymentHolder);
-        underTest.processCreditTransaction(chargeBackTransaction, ctx);
-
-        // then
-        Mockito.verify(installment, Mockito.times(1)).addToCredits(new BigDecimal("25.00"));
-        Mockito.verify(installment, Mockito.times(1)).updateInterestCharged(new BigDecimal("20.00"));
-        ArgumentCaptor<LocalDate> localDateArgumentCaptor = ArgumentCaptor.forClass(LocalDate.class);
-        ArgumentCaptor<Money> moneyCaptor = ArgumentCaptor.forClass(Money.class);
-        Mockito.verify(installment, Mockito.times(1)).addToPrincipal(localDateArgumentCaptor.capture(), moneyCaptor.capture());
-        Assertions.assertEquals(LocalDate.of(2023, 1, 1), localDateArgumentCaptor.getValue());
-        assertEquals(0, moneyCaptor.getValue().getAmount().compareTo(BigDecimal.valueOf(5.0)));
-
-        ArgumentCaptor<Money> principal = ArgumentCaptor.forClass(Money.class);
-        ArgumentCaptor<Money> interest = ArgumentCaptor.forClass(Money.class);
-        ArgumentCaptor<Money> fee = ArgumentCaptor.forClass(Money.class);
-        ArgumentCaptor<Money> penalty = ArgumentCaptor.forClass(Money.class);
-        Mockito.verify(chargeBackTransaction, times(1)).updateComponents(principal.capture(), interest.capture(), fee.capture(),
-                penalty.capture());
-        assertEquals(0, principal.getValue().getAmount().compareTo(BigDecimal.valueOf(5.0)));
-        assertEquals(0, interest.getValue().getAmount().compareTo(BigDecimal.valueOf(20.0)));
-        assertEquals(0, fee.getValue().getAmount().compareTo(BigDecimal.ZERO));
-        assertEquals(0, penalty.getValue().getAmount().compareTo(BigDecimal.ZERO));
-    }
-
-    @Test
-    public void testProcessCreditTransactionWithAllocationRulePrincipalAndInterest() {
-        // given
-        Loan loan = mock(Loan.class);
-        LoanTransaction chargeBackTransaction = createChargebackTransaction(loan);
-
-        LoanCreditAllocationRule mockCreditAllocationRule = createMockCreditAllocationRule(PRINCIPAL, INTEREST, PENALTY, FEE);
-        Mockito.when(loan.getCreditAllocationRules()).thenReturn(List.of(mockCreditAllocationRule));
-        LoanTransaction repayment = createRepayment(loan, chargeBackTransaction);
+        LoanCreditAllocationRule mockCreditAllocationRule = createMockCreditAllocationRule(PRINCIPAL, PENALTY, FEE, INTEREST);
+        when(loan.getCreditAllocationRules()).thenReturn(List.of(mockCreditAllocationRule));
+        LoanTransaction repayment = createRepayment(loan, chargeBackTransaction, 10, 0, 20, 5);
         lenient().when(loan.getLoanTransactions()).thenReturn(List.of(repayment));
 
         MoneyHolder overpaymentHolder = new MoneyHolder(Money.zero(MONETARY_CURRENCY));
@@ -315,15 +272,31 @@
         TransactionCtx ctx = new TransactionCtx(MONETARY_CURRENCY, installments, null, overpaymentHolder);
         underTest.processCreditTransaction(chargeBackTransaction, ctx);
 
-        // then
-        Mockito.verify(installment, Mockito.times(1)).addToCredits(new BigDecimal("25.00"));
-        Mockito.verify(installment, Mockito.times(1)).updateInterestCharged(new BigDecimal("15.00"));
+        // verify principal
+        Mockito.verify(installment, times(1)).addToCreditedPrincipal(new BigDecimal("10.00"));
         ArgumentCaptor<LocalDate> localDateArgumentCaptor = ArgumentCaptor.forClass(LocalDate.class);
         ArgumentCaptor<Money> moneyCaptor = ArgumentCaptor.forClass(Money.class);
-        Mockito.verify(installment, Mockito.times(1)).addToPrincipal(localDateArgumentCaptor.capture(), moneyCaptor.capture());
+        Mockito.verify(installment, times(1)).addToPrincipal(localDateArgumentCaptor.capture(), moneyCaptor.capture());
         Assertions.assertEquals(LocalDate.of(2023, 1, 1), localDateArgumentCaptor.getValue());
         assertEquals(0, moneyCaptor.getValue().getAmount().compareTo(BigDecimal.valueOf(10.0)));
 
+        // verify charges on installment
+        Mockito.verify(installment, times(1)).addToCreditedFee(new BigDecimal("10.00"));
+        Mockito.verify(installment, times(1)).addToCreditedPenalty(new BigDecimal("5.00"));
+
+        ArgumentCaptor<Money> feeCaptor = ArgumentCaptor.forClass(Money.class);
+        ArgumentCaptor<Money> penaltyCaptor = ArgumentCaptor.forClass(Money.class);
+        Mockito.verify(installment, times(2)).addToChargePortion(feeCaptor.capture(), any(), any(), penaltyCaptor.capture(), any(), any());
+        assertEquals(2, feeCaptor.getAllValues().size());
+        assertEquals(2, penaltyCaptor.getAllValues().size());
+
+        assertEquals(0, feeCaptor.getAllValues().get(0).getAmount().compareTo(BigDecimal.valueOf(10.0)));
+        assertEquals(0, feeCaptor.getAllValues().get(1).getAmount().compareTo(BigDecimal.ZERO));
+
+        assertEquals(0, penaltyCaptor.getAllValues().get(0).getAmount().compareTo(BigDecimal.ZERO));
+        assertEquals(0, penaltyCaptor.getAllValues().get(1).getAmount().compareTo(BigDecimal.valueOf(5.0)));
+
+        // verify transaction
         ArgumentCaptor<Money> principal = ArgumentCaptor.forClass(Money.class);
         ArgumentCaptor<Money> interest = ArgumentCaptor.forClass(Money.class);
         ArgumentCaptor<Money> fee = ArgumentCaptor.forClass(Money.class);
@@ -331,24 +304,74 @@
         Mockito.verify(chargeBackTransaction, times(1)).updateComponents(principal.capture(), interest.capture(), fee.capture(),
                 penalty.capture());
         assertEquals(0, principal.getValue().getAmount().compareTo(BigDecimal.valueOf(10.0)));
-        assertEquals(0, interest.getValue().getAmount().compareTo(BigDecimal.valueOf(15.0)));
-        assertEquals(0, fee.getValue().getAmount().compareTo(BigDecimal.ZERO));
-        assertEquals(0, penalty.getValue().getAmount().compareTo(BigDecimal.ZERO));
+        assertEquals(0, fee.getValue().getAmount().compareTo(BigDecimal.valueOf(10.0)));
+        assertEquals(0, penalty.getValue().getAmount().compareTo(BigDecimal.valueOf(5.0)));
+        assertEquals(0, interest.getValue().getAmount().compareTo(BigDecimal.ZERO));
+    }
+
+    @Test
+    public void testProcessCreditTransactionWithAllocationRulePenaltyFeePrincipalInterest() {
+        // given
+        Loan loan = mock(Loan.class);
+        LoanTransaction chargeBackTransaction = createChargebackTransaction(loan, 25.0);
+
+        LoanCreditAllocationRule mockCreditAllocationRule = createMockCreditAllocationRule(PENALTY, FEE, PRINCIPAL, INTEREST);
+        when(loan.getCreditAllocationRules()).thenReturn(List.of(mockCreditAllocationRule));
+        LoanTransaction repayment = createRepayment(loan, chargeBackTransaction, 10, 0, 20, 5);
+        lenient().when(loan.getLoanTransactions()).thenReturn(List.of(repayment));
+
+        MoneyHolder overpaymentHolder = new MoneyHolder(Money.zero(MONETARY_CURRENCY));
+        List<LoanRepaymentScheduleInstallment> installments = new ArrayList<>();
+        LoanRepaymentScheduleInstallment installment = createMockInstallment(LocalDate.of(2023, 1, 31), false);
+        installments.add(installment);
+
+        // when
+        TransactionCtx ctx = new TransactionCtx(MONETARY_CURRENCY, installments, null, overpaymentHolder);
+        underTest.processCreditTransaction(chargeBackTransaction, ctx);
+
+        // verify charges on installment
+        Mockito.verify(installment, times(1)).addToCreditedFee(new BigDecimal("20.00"));
+        Mockito.verify(installment, times(1)).addToCreditedPenalty(new BigDecimal("5.00"));
+
+        ArgumentCaptor<Money> feeCaptor = ArgumentCaptor.forClass(Money.class);
+        ArgumentCaptor<Money> penaltyCaptor = ArgumentCaptor.forClass(Money.class);
+        Mockito.verify(installment, times(2)).addToChargePortion(feeCaptor.capture(), any(), any(), penaltyCaptor.capture(), any(), any());
+        assertEquals(2, feeCaptor.getAllValues().size());
+        assertEquals(2, penaltyCaptor.getAllValues().size());
+
+        assertEquals(0, feeCaptor.getAllValues().get(0).getAmount().compareTo(BigDecimal.valueOf(20.0)));
+        assertEquals(0, feeCaptor.getAllValues().get(1).getAmount().compareTo(BigDecimal.ZERO));
+
+        assertEquals(0, penaltyCaptor.getAllValues().get(0).getAmount().compareTo(BigDecimal.ZERO));
+        assertEquals(0, penaltyCaptor.getAllValues().get(1).getAmount().compareTo(BigDecimal.valueOf(5.0)));
+
+        // verify transaction
+        ArgumentCaptor<Money> principal = ArgumentCaptor.forClass(Money.class);
+        ArgumentCaptor<Money> interest = ArgumentCaptor.forClass(Money.class);
+        ArgumentCaptor<Money> fee = ArgumentCaptor.forClass(Money.class);
+        ArgumentCaptor<Money> penalty = ArgumentCaptor.forClass(Money.class);
+        Mockito.verify(chargeBackTransaction, times(1)).updateComponents(principal.capture(), interest.capture(), fee.capture(),
+                penalty.capture());
+        assertEquals(0, principal.getValue().getAmount().compareTo(BigDecimal.valueOf(0)));
+        assertEquals(0, interest.getValue().getAmount().compareTo(BigDecimal.valueOf(0)));
+        assertEquals(0, fee.getValue().getAmount().compareTo(BigDecimal.valueOf(20.0)));
+        assertEquals(0, penalty.getValue().getAmount().compareTo(BigDecimal.valueOf(5.0)));
     }
 
     @Test
     public void testProcessCreditTransactionWithAllocationRulePrincipalAndInterestWithAdditionalInstallment() {
         // given
         Loan loan = mock(Loan.class);
-        LoanTransaction chargeBackTransaction = createChargebackTransaction(loan);
+        LoanTransaction chargeBackTransaction = createChargebackTransaction(loan, 25.0);
 
-        LoanCreditAllocationRule mockCreditAllocationRule = createMockCreditAllocationRule(PRINCIPAL, INTEREST, PENALTY, FEE);
-        Mockito.when(loan.getCreditAllocationRules()).thenReturn(List.of(mockCreditAllocationRule));
-        LoanTransaction repayment = createRepayment(loan, chargeBackTransaction);
+        LoanCreditAllocationRule mockCreditAllocationRule = createMockCreditAllocationRule(PRINCIPAL, PENALTY, FEE, INTEREST);
+        when(loan.getCreditAllocationRules()).thenReturn(List.of(mockCreditAllocationRule));
+        LoanTransaction repayment = createRepayment(loan, chargeBackTransaction, 10, 0, 20, 5);
         lenient().when(loan.getLoanTransactions()).thenReturn(List.of(repayment));
 
         MoneyHolder overpaymentHolder = new MoneyHolder(Money.zero(MONETARY_CURRENCY));
         List<LoanRepaymentScheduleInstallment> installments = new ArrayList<>();
+
         LoanRepaymentScheduleInstallment installment1 = createMockInstallment(LocalDate.of(2022, 12, 20), false);
         LoanRepaymentScheduleInstallment installment2 = createMockInstallment(LocalDate.of(2022, 12, 27), true);
         installments.add(installment1);
@@ -358,15 +381,31 @@
         TransactionCtx ctx = new TransactionCtx(MONETARY_CURRENCY, installments, null, overpaymentHolder);
         underTest.processCreditTransaction(chargeBackTransaction, ctx);
 
-        // then
-        Mockito.verify(installment2, Mockito.times(1)).addToCredits(new BigDecimal("25.00"));
-        Mockito.verify(installment2, Mockito.times(1)).updateInterestCharged(new BigDecimal("15.00"));
+        // verify principal
+        Mockito.verify(installment2, times(1)).addToCreditedPrincipal(new BigDecimal("10.00"));
         ArgumentCaptor<LocalDate> localDateArgumentCaptor = ArgumentCaptor.forClass(LocalDate.class);
         ArgumentCaptor<Money> moneyCaptor = ArgumentCaptor.forClass(Money.class);
-        Mockito.verify(installment2, Mockito.times(1)).addToPrincipal(localDateArgumentCaptor.capture(), moneyCaptor.capture());
+        Mockito.verify(installment2, times(1)).addToPrincipal(localDateArgumentCaptor.capture(), moneyCaptor.capture());
         Assertions.assertEquals(LocalDate.of(2023, 1, 1), localDateArgumentCaptor.getValue());
         assertEquals(0, moneyCaptor.getValue().getAmount().compareTo(BigDecimal.valueOf(10.0)));
 
+        // verify charges on installment
+        Mockito.verify(installment2, times(1)).addToCreditedFee(new BigDecimal("10.00"));
+        Mockito.verify(installment2, times(1)).addToCreditedPenalty(new BigDecimal("5.00"));
+
+        ArgumentCaptor<Money> feeCaptor = ArgumentCaptor.forClass(Money.class);
+        ArgumentCaptor<Money> penaltyCaptor = ArgumentCaptor.forClass(Money.class);
+        Mockito.verify(installment2, times(2)).addToChargePortion(feeCaptor.capture(), any(), any(), penaltyCaptor.capture(), any(), any());
+        assertEquals(2, feeCaptor.getAllValues().size());
+        assertEquals(2, penaltyCaptor.getAllValues().size());
+
+        assertEquals(0, feeCaptor.getAllValues().get(0).getAmount().compareTo(BigDecimal.valueOf(10.0)));
+        assertEquals(0, feeCaptor.getAllValues().get(1).getAmount().compareTo(BigDecimal.ZERO));
+
+        assertEquals(0, penaltyCaptor.getAllValues().get(0).getAmount().compareTo(BigDecimal.ZERO));
+        assertEquals(0, penaltyCaptor.getAllValues().get(1).getAmount().compareTo(BigDecimal.valueOf(5.0)));
+
+        // verify transaction
         ArgumentCaptor<Money> principal = ArgumentCaptor.forClass(Money.class);
         ArgumentCaptor<Money> interest = ArgumentCaptor.forClass(Money.class);
         ArgumentCaptor<Money> fee = ArgumentCaptor.forClass(Money.class);
@@ -374,9 +413,9 @@
         Mockito.verify(chargeBackTransaction, times(1)).updateComponents(principal.capture(), interest.capture(), fee.capture(),
                 penalty.capture());
         assertEquals(0, principal.getValue().getAmount().compareTo(BigDecimal.valueOf(10.0)));
-        assertEquals(0, interest.getValue().getAmount().compareTo(BigDecimal.valueOf(15.0)));
-        assertEquals(0, fee.getValue().getAmount().compareTo(BigDecimal.ZERO));
-        assertEquals(0, penalty.getValue().getAmount().compareTo(BigDecimal.ZERO));
+        assertEquals(0, fee.getValue().getAmount().compareTo(BigDecimal.valueOf(10.0)));
+        assertEquals(0, penalty.getValue().getAmount().compareTo(BigDecimal.valueOf(5.0)));
+        assertEquals(0, interest.getValue().getAmount().compareTo(BigDecimal.ZERO));
     }
 
     private LoanRepaymentScheduleInstallment createMockInstallment(LocalDate localDate, boolean isAdditional) {
@@ -396,15 +435,16 @@
         return mockCreditAllocationRule;
     }
 
-    private LoanTransaction createRepayment(Loan loan, LoanTransaction toTransaction) {
+    private LoanTransaction createRepayment(Loan loan, LoanTransaction toTransaction, double principalPortion, double interestPortion,
+            double feePortion, double penaltyPortion) {
         LoanTransaction repayment = mock(LoanTransaction.class);
         lenient().when(repayment.getLoan()).thenReturn(loan);
         lenient().when(repayment.isRepayment()).thenReturn(true);
         lenient().when(repayment.getTypeOf()).thenReturn(REPAYMENT);
-        lenient().when(repayment.getPrincipalPortion()).thenReturn(BigDecimal.valueOf(10));
-        lenient().when(repayment.getInterestPortion()).thenReturn(BigDecimal.valueOf(20));
-        lenient().when(repayment.getFeeChargesPortion()).thenReturn(BigDecimal.ZERO);
-        lenient().when(repayment.getPenaltyChargesPortion()).thenReturn(BigDecimal.ZERO);
+        lenient().when(repayment.getPrincipalPortion()).thenReturn(BigDecimal.valueOf(principalPortion));
+        lenient().when(repayment.getInterestPortion()).thenReturn(BigDecimal.valueOf(interestPortion));
+        lenient().when(repayment.getFeeChargesPortion()).thenReturn(BigDecimal.valueOf(feePortion));
+        lenient().when(repayment.getPenaltyChargesPortion()).thenReturn(BigDecimal.valueOf(penaltyPortion));
 
         LoanTransactionRelation relation = mock(LoanTransactionRelation.class);
         lenient().when(relation.getRelationType()).thenReturn(LoanTransactionRelationTypeEnum.CHARGEBACK);
@@ -414,14 +454,14 @@
         return repayment;
     }
 
-    private LoanTransaction createChargebackTransaction(Loan loan) {
+    private LoanTransaction createChargebackTransaction(Loan loan, double transactionAmount) {
         LoanTransaction chargeback = mock(LoanTransaction.class);
         lenient().when(chargeback.isChargeback()).thenReturn(true);
         lenient().when(chargeback.getTypeOf()).thenReturn(LoanTransactionType.CHARGEBACK);
         lenient().when(chargeback.getLoan()).thenReturn(loan);
-        lenient().when(chargeback.getAmount()).thenReturn(BigDecimal.valueOf(25));
-        Money amount = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(25));
-        lenient().when(chargeback.getAmount(MONETARY_CURRENCY)).thenReturn(amount);
+        lenient().when(chargeback.getAmount()).thenReturn(BigDecimal.valueOf(transactionAmount));
+        Money money = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(transactionAmount));
+        lenient().when(chargeback.getAmount(MONETARY_CURRENCY)).thenReturn(money);
         lenient().when(chargeback.getTransactionDate()).thenReturn(LocalDate.of(2023, 1, 1));
         return chargeback;
     }
@@ -431,40 +471,41 @@
         Map<AllocationType, Money> result;
         MonetaryCurrency currency = mock(MonetaryCurrency.class);
 
-        result = underTest.calculateChargebackAllocationMap(allocationMap(50.0, 100.0, 200.0, 12.0), BigDecimal.valueOf(50.0),
+        result = underTest.calculateChargebackAllocationMap(allocationMap(50.0, 100.0, 200.0, 12.0, currency), BigDecimal.valueOf(50.0),
                 List.of(PRINCIPAL, INTEREST, FEE, PENALTY), currency);
-        verify(allocationMap(50.0, 0, 0, 0), result);
+        verify(allocationMap(50.0, 0, 0, 0, currency), result);
 
-        result = underTest.calculateChargebackAllocationMap(allocationMap(40.0, 100.0, 200.0, 12.0), BigDecimal.valueOf(50.0),
+        result = underTest.calculateChargebackAllocationMap(allocationMap(40.0, 100.0, 200.0, 12.0, currency), BigDecimal.valueOf(50.0),
                 List.of(PRINCIPAL, INTEREST, FEE, PENALTY), currency);
-        verify(allocationMap(40.0, 10, 0, 0), result);
+        verify(allocationMap(40.0, 10, 0, 0, currency), result);
 
-        result = underTest.calculateChargebackAllocationMap(allocationMap(40.0, 100.0, 200.0, 12.0), BigDecimal.valueOf(50.0),
+        result = underTest.calculateChargebackAllocationMap(allocationMap(40.0, 100.0, 200.0, 12.0, currency), BigDecimal.valueOf(50.0),
                 List.of(PRINCIPAL, FEE, PENALTY, INTEREST), currency);
-        verify(allocationMap(40.0, 0, 10, 0), result);
+        verify(allocationMap(40.0, 0, 10, 0, currency), result);
 
-        result = underTest.calculateChargebackAllocationMap(allocationMap(40.0, 100.0, 200.0, 12.0), BigDecimal.valueOf(340.0),
+        result = underTest.calculateChargebackAllocationMap(allocationMap(40.0, 100.0, 200.0, 12.0, currency), BigDecimal.valueOf(340.0),
                 List.of(PRINCIPAL, FEE, PENALTY, INTEREST), currency);
-        verify(allocationMap(40.0, 88.0, 200.0, 12.0), result);
+        verify(allocationMap(40.0, 88.0, 200.0, 12.0, currency), result);
 
-        result = underTest.calculateChargebackAllocationMap(allocationMap(40.0, 100.0, 200.0, 12.0), BigDecimal.valueOf(352.0),
+        result = underTest.calculateChargebackAllocationMap(allocationMap(40.0, 100.0, 200.0, 12.0, currency), BigDecimal.valueOf(352.0),
                 List.of(PRINCIPAL, FEE, PENALTY, INTEREST), currency);
-        verify(allocationMap(40.0, 100.0, 200.0, 12.0), result);
+        verify(allocationMap(40.0, 100.0, 200.0, 12.0, currency), result);
     }
 
-    private void verify(Map<AllocationType, BigDecimal> expected, Map<AllocationType, Money> actual) {
+    private void verify(Map<AllocationType, Money> expected, Map<AllocationType, Money> actual) {
         Assertions.assertEquals(expected.size(), actual.size());
         expected.forEach((k, v) -> {
-            Assertions.assertEquals(0, v.compareTo(actual.get(k).getAmount()), "Not matching for " + k);
+            Assertions.assertEquals(0, v.getAmount().compareTo(actual.get(k).getAmount()), "Not matching for " + k);
         });
     }
 
-    private Map<AllocationType, BigDecimal> allocationMap(double principal, double interest, double fee, double penalty) {
-        Map<AllocationType, BigDecimal> allocationMap = new HashMap<>();
-        allocationMap.put(AllocationType.PRINCIPAL, BigDecimal.valueOf(principal));
-        allocationMap.put(AllocationType.INTEREST, BigDecimal.valueOf(interest));
-        allocationMap.put(AllocationType.FEE, BigDecimal.valueOf(fee));
-        allocationMap.put(AllocationType.PENALTY, BigDecimal.valueOf(penalty));
+    private Map<AllocationType, Money> allocationMap(double principal, double interest, double fee, double penalty,
+            MonetaryCurrency currency) {
+        Map<AllocationType, Money> allocationMap = new HashMap<>();
+        allocationMap.put(AllocationType.PRINCIPAL, Money.of(currency, BigDecimal.valueOf(principal)));
+        allocationMap.put(AllocationType.INTEREST, Money.of(currency, BigDecimal.valueOf(interest)));
+        allocationMap.put(AllocationType.FEE, Money.of(currency, BigDecimal.valueOf(fee)));
+        allocationMap.put(AllocationType.PENALTY, Money.of(currency, BigDecimal.valueOf(penalty)));
         return allocationMap;
     }
 
@@ -472,17 +513,17 @@
     public void testFindOriginalTransactionShouldFindOriginalInLoansTransactionWhenIdProvided() {
         // given
         LoanTransaction chargebackTransaction = mock(LoanTransaction.class);
-        Mockito.when(chargebackTransaction.getId()).thenReturn(123L);
+        when(chargebackTransaction.getId()).thenReturn(123L);
         Loan loan = mock(Loan.class);
-        Mockito.when(chargebackTransaction.getLoan()).thenReturn(loan);
+        when(chargebackTransaction.getLoan()).thenReturn(loan);
         LoanTransaction repayment1 = mock(LoanTransaction.class);
         LoanTransaction repayment2 = mock(LoanTransaction.class);
-        Mockito.when(loan.getLoanTransactions()).thenReturn(List.of(chargebackTransaction, repayment1, repayment2));
+        when(loan.getLoanTransactions()).thenReturn(List.of(chargebackTransaction, repayment1, repayment2));
 
         LoanTransactionRelation relation = mock(LoanTransactionRelation.class);
-        Mockito.when(relation.getToTransaction()).thenReturn(chargebackTransaction);
-        Mockito.when(relation.getRelationType()).thenReturn(LoanTransactionRelationTypeEnum.CHARGEBACK);
-        Mockito.when(repayment2.getLoanTransactionRelations()).thenReturn(Set.of(relation));
+        when(relation.getToTransaction()).thenReturn(chargebackTransaction);
+        when(relation.getRelationType()).thenReturn(LoanTransactionRelationTypeEnum.CHARGEBACK);
+        when(repayment2.getLoanTransactionRelations()).thenReturn(Set.of(relation));
         TransactionCtx ctx = mock(TransactionCtx.class);
 
         // when
@@ -496,14 +537,14 @@
     public void testFindOriginalTransactionThrowsRuntimeExceptionWhenIdProvidedAndRelationsAreMissing() {
         // given
         LoanTransaction chargebackTransaction = mock(LoanTransaction.class);
-        Mockito.when(chargebackTransaction.getId()).thenReturn(123L);
+        when(chargebackTransaction.getId()).thenReturn(123L);
         Loan loan = mock(Loan.class);
-        Mockito.when(chargebackTransaction.getLoan()).thenReturn(loan);
+        when(chargebackTransaction.getLoan()).thenReturn(loan);
         LoanTransaction repayment1 = mock(LoanTransaction.class);
         LoanTransaction repayment2 = mock(LoanTransaction.class);
-        Mockito.when(loan.getLoanTransactions()).thenReturn(List.of(chargebackTransaction, repayment1, repayment2));
+        when(loan.getLoanTransactions()).thenReturn(List.of(chargebackTransaction, repayment1, repayment2));
 
-        Mockito.when(repayment2.getLoanTransactionRelations()).thenReturn(Set.of());
+        when(repayment2.getLoanTransactionRelations()).thenReturn(Set.of());
 
         TransactionCtx ctx = mock(TransactionCtx.class);
 
@@ -517,22 +558,22 @@
     public void testFindOriginalTransactionShouldFindOriginalInLoansTransactionFromTransactionCtxWhenIdIsNotProvided() {
         // given
         LoanTransaction chargebackReplayed = mock(LoanTransaction.class);
-        Mockito.when(chargebackReplayed.getId()).thenReturn(null);
+        when(chargebackReplayed.getId()).thenReturn(null);
         LoanTransaction repayment1 = mock(LoanTransaction.class);
         LoanTransaction repayment2 = mock(LoanTransaction.class);
 
         LoanTransaction originalChargeback = mock(LoanTransaction.class);
-        Mockito.when(originalChargeback.getId()).thenReturn(123L);
+        when(originalChargeback.getId()).thenReturn(123L);
         LoanTransactionRelation relation = mock(LoanTransactionRelation.class);
-        Mockito.when(relation.getToTransaction()).thenReturn(originalChargeback);
-        Mockito.when(relation.getRelationType()).thenReturn(LoanTransactionRelationTypeEnum.CHARGEBACK);
-        Mockito.when(repayment2.getLoanTransactionRelations()).thenReturn(Set.of(relation));
+        when(relation.getToTransaction()).thenReturn(originalChargeback);
+        when(relation.getRelationType()).thenReturn(LoanTransactionRelationTypeEnum.CHARGEBACK);
+        when(repayment2.getLoanTransactionRelations()).thenReturn(Set.of(relation));
 
         TransactionCtx ctx = mock(TransactionCtx.class);
         ChangedTransactionDetail changedTransactionDetail = mock(ChangedTransactionDetail.class);
-        Mockito.when(ctx.getChangedTransactionDetail()).thenReturn(changedTransactionDetail);
-        Mockito.when(changedTransactionDetail.getCurrentTransactionToOldId()).thenReturn(Map.of(chargebackReplayed, 123L));
-        Mockito.when(changedTransactionDetail.getNewTransactionMappings()).thenReturn(Map.of(122L, repayment1, 121L, repayment2));
+        when(ctx.getChangedTransactionDetail()).thenReturn(changedTransactionDetail);
+        when(changedTransactionDetail.getCurrentTransactionToOldId()).thenReturn(Map.of(chargebackReplayed, 123L));
+        when(changedTransactionDetail.getNewTransactionMappings()).thenReturn(Map.of(122L, repayment1, 121L, repayment2));
 
         // when
         LoanTransaction originalTransaction = underTest.findOriginalTransaction(chargebackReplayed, ctx);
@@ -545,25 +586,25 @@
     public void testFindOriginalTransactionShouldFindOriginalInLoansTransactionFromTransactionCtxWhenIdIsNotProvidedFallbackToPersistedTransactions() {
         // given
         LoanTransaction chargebackReplayed = mock(LoanTransaction.class);
-        Mockito.when(chargebackReplayed.getId()).thenReturn(null);
+        when(chargebackReplayed.getId()).thenReturn(null);
         LoanTransaction repayment1 = mock(LoanTransaction.class);
         LoanTransaction repayment2 = mock(LoanTransaction.class);
         Loan loan = mock(Loan.class);
-        Mockito.when(chargebackReplayed.getLoan()).thenReturn(loan);
-        Mockito.when(loan.getLoanTransactions()).thenReturn(List.of(repayment1, repayment2));
+        when(chargebackReplayed.getLoan()).thenReturn(loan);
+        when(loan.getLoanTransactions()).thenReturn(List.of(repayment1, repayment2));
 
         LoanTransaction originalChargeback = mock(LoanTransaction.class);
-        Mockito.when(originalChargeback.getId()).thenReturn(123L);
+        when(originalChargeback.getId()).thenReturn(123L);
         LoanTransactionRelation relation = mock(LoanTransactionRelation.class);
-        Mockito.when(relation.getToTransaction()).thenReturn(originalChargeback);
-        Mockito.when(relation.getRelationType()).thenReturn(LoanTransactionRelationTypeEnum.CHARGEBACK);
-        Mockito.when(repayment2.getLoanTransactionRelations()).thenReturn(Set.of(relation));
+        when(relation.getToTransaction()).thenReturn(originalChargeback);
+        when(relation.getRelationType()).thenReturn(LoanTransactionRelationTypeEnum.CHARGEBACK);
+        when(repayment2.getLoanTransactionRelations()).thenReturn(Set.of(relation));
 
         TransactionCtx ctx = mock(TransactionCtx.class);
         ChangedTransactionDetail changedTransactionDetail = mock(ChangedTransactionDetail.class);
-        Mockito.when(ctx.getChangedTransactionDetail()).thenReturn(changedTransactionDetail);
-        Mockito.when(changedTransactionDetail.getCurrentTransactionToOldId()).thenReturn(Map.of(chargebackReplayed, 123L));
-        Mockito.when(changedTransactionDetail.getNewTransactionMappings()).thenReturn(Map.of());
+        when(ctx.getChangedTransactionDetail()).thenReturn(changedTransactionDetail);
+        when(changedTransactionDetail.getCurrentTransactionToOldId()).thenReturn(Map.of(chargebackReplayed, 123L));
+        when(changedTransactionDetail.getNewTransactionMappings()).thenReturn(Map.of());
 
         // when
         LoanTransaction originalTransaction = underTest.findOriginalTransaction(chargebackReplayed, ctx);
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 a3073f7..f0056b2 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
@@ -476,6 +476,46 @@
                                     totalOutstanding));
                 }
 
+                Double outstandingPrincipalExpected = installments[i].outstandingAmounts != null
+                        ? installments[i].outstandingAmounts.principalOutstanding
+                        : null;
+                Double outstandingPrincipal = period.getPrincipalOutstanding();
+                if (outstandingPrincipalExpected != null) {
+                    Assertions.assertEquals(outstandingPrincipalExpected, outstandingPrincipal,
+                            "%d. installment's outstanding principal is different, expected: %.2f, actual: %.2f".formatted(i,
+                                    outstandingPrincipalExpected, outstandingPrincipal));
+                }
+
+                Double outstandingFeeExpected = installments[i].outstandingAmounts != null
+                        ? installments[i].outstandingAmounts.feeOutstanding
+                        : null;
+                Double outstandingFee = period.getFeeChargesOutstanding();
+                if (outstandingFeeExpected != null) {
+                    Assertions.assertEquals(outstandingFeeExpected, outstandingFee,
+                            "%d. installment's outstanding fee is different, expected: %.2f, actual: %.2f".formatted(i,
+                                    outstandingFeeExpected, outstandingFee));
+                }
+
+                Double outstandingPenaltyExpected = installments[i].outstandingAmounts != null
+                        ? installments[i].outstandingAmounts.penaltyOutstanding
+                        : null;
+                Double outstandingPenalty = period.getPenaltyChargesOutstanding();
+                if (outstandingPenaltyExpected != null) {
+                    Assertions.assertEquals(outstandingPenaltyExpected, outstandingPenalty,
+                            "%d. installment's outstanding penalty is different, expected: %.2f, actual: %.2f".formatted(i,
+                                    outstandingPenaltyExpected, outstandingPenalty));
+                }
+
+                Double outstandingTotalExpected = installments[i].outstandingAmounts != null
+                        ? installments[i].outstandingAmounts.totalOutstanding
+                        : null;
+                Double outstandingTotal = period.getTotalOutstandingForPeriod();
+                if (outstandingTotalExpected != null) {
+                    Assertions.assertEquals(outstandingTotalExpected, outstandingTotal,
+                            "%d. installment's total outstanding is different, expected: %.2f, actual: %.2f".formatted(i,
+                                    outstandingTotalExpected, outstandingTotal));
+                }
+
             }
             Assertions.assertEquals(installments[i].completed, period.getComplete());
             Assertions.assertEquals(LocalDate.parse(installments[i].dueDate, dateTimeFormatter), period.getDueDate());
@@ -603,22 +643,31 @@
     }
 
     protected Installment installment(double principalAmount, Boolean completed, String dueDate) {
-        return new Installment(principalAmount, null, null, null, null, completed, dueDate);
+        return new Installment(principalAmount, null, null, null, null, completed, dueDate, null);
     }
 
     protected Installment installment(double principalAmount, double interestAmount, double totalOutstandingAmount, Boolean completed,
             String dueDate) {
-        return new Installment(principalAmount, interestAmount, null, null, totalOutstandingAmount, completed, dueDate);
+        return new Installment(principalAmount, interestAmount, null, null, totalOutstandingAmount, completed, dueDate, null);
     }
 
     protected Installment installment(double principalAmount, double interestAmount, double feeAmount, double totalOutstandingAmount,
             Boolean completed, String dueDate) {
-        return new Installment(principalAmount, interestAmount, feeAmount, null, totalOutstandingAmount, completed, dueDate);
+        return new Installment(principalAmount, interestAmount, feeAmount, null, totalOutstandingAmount, completed, dueDate, null);
     }
 
     protected Installment installment(double principalAmount, double interestAmount, double feeAmount, double penaltyAmount,
             double totalOutstandingAmount, Boolean completed, String dueDate) {
-        return new Installment(principalAmount, interestAmount, feeAmount, penaltyAmount, totalOutstandingAmount, completed, dueDate);
+        return new Installment(principalAmount, interestAmount, feeAmount, penaltyAmount, totalOutstandingAmount, completed, dueDate, null);
+    }
+
+    protected Installment installment(double principalAmount, double interestAmount, double feeAmount, double penaltyAmount,
+            OutstandingAmounts outstandingAmounts, Boolean completed, String dueDate) {
+        return new Installment(principalAmount, interestAmount, feeAmount, penaltyAmount, null, completed, dueDate, outstandingAmounts);
+    }
+
+    protected OutstandingAmounts outstanding(double principal, double fee, double penalty, double total) {
+        return new OutstandingAmounts(principal, fee, penalty, total);
     }
 
     protected BatchRequestBuilder batchRequest() {
@@ -735,6 +784,17 @@
         Double totalOutstandingAmount;
         Boolean completed;
         String dueDate;
+        OutstandingAmounts outstandingAmounts;
+    }
+
+    @AllArgsConstructor
+    @ToString
+    public static class OutstandingAmounts {
+
+        Double principalOutstanding;
+        Double feeOutstanding;
+        Double penaltyOutstanding;
+        Double totalOutstanding;
     }
 
     public static class AmortizationType {
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargebackWithCreditAllocationsIntegrationTests.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargebackWithCreditAllocationsIntegrationTests.java
index 1f76104..63ae1b1 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargebackWithCreditAllocationsIntegrationTests.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargebackWithCreditAllocationsIntegrationTests.java
@@ -26,6 +26,8 @@
 import org.apache.fineract.client.models.AdvancedPaymentData;
 import org.apache.fineract.client.models.CreditAllocationData;
 import org.apache.fineract.client.models.CreditAllocationOrder;
+import org.apache.fineract.client.models.GetLoansLoanIdResponse;
+import org.apache.fineract.client.models.GetLoansLoanIdSummary;
 import org.apache.fineract.client.models.PaymentAllocationOrder;
 import org.apache.fineract.client.models.PostLoanProductsRequest;
 import org.apache.fineract.client.models.PostLoanProductsResponse;
@@ -38,6 +40,7 @@
 import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
 import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
 import org.jetbrains.annotations.Nullable;
+import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 
@@ -46,13 +49,15 @@
 public class LoanChargebackWithCreditAllocationsIntegrationTests extends BaseLoanIntegrationTest {
 
     @Test
-    public void createLoanWithCreditAllocationAndChargebackPenaltyFeeInterestAndPrincipal() {
+    public void simpleChargebackWithCreditAllocationPenaltyFeeInterestAndPrincipal() {
         runAt("01 January 2023", () -> {
             // Create Client
             Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
             // Create Loan Product
-            Long loanProductId = createLoanProduct(chargebackAllocation("PENALTY", "FEE", "INTEREST", "PRINCIPAL"));
-
+            Long loanProductId = createLoanProduct(//
+                    createDefaultPaymentAllocation(), //
+                    chargebackAllocation("PENALTY", "FEE", "INTEREST", "PRINCIPAL")//
+            );
             // Apply and Approve Loan
             Long loanId = applyAndApproveLoan(clientId, loanProductId);
 
@@ -92,15 +97,13 @@
             verifyTransactions(loanId, //
                     transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
                     transaction(383.0, "Repayment", "20 January 2023", 937.0, 313.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
-                    transaction(100.0, "Chargeback", "20 January 2023", 1037.0, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0) //
+                    transaction(100.0, "Chargeback", "20 January 2023", 967.0, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0) //
             );
 
             // Verify Repayment Schedule
             verifyRepaymentSchedule(loanId, //
                     installment(0, null, "01 January 2023"), //
-                    installment(343.0, 0, 50, 20, 30.0, false, "01 February 2023"), // TODO: we still need to add the
-                                                                                    // fee and the penalty to the
-                                                                                    // outstanding
+                    installment(343.0, 0, 100, 40, 100.0, false, "01 February 2023"),
                     installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
                     installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
                     installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
@@ -109,13 +112,145 @@
     }
 
     @Test
-    public void createLoanWithCreditAllocationAndChargebackPenaltyFeeInterestAndPrincipalOnNPlusOneInstallment() {
+    public void simpleChargebackWithCreditAllocationPenaltyFeeInterestAndPrincipalOnTheLastDayOfTheInstallment() {
         runAt("01 January 2023", () -> {
             // Create Client
             Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
             // Create Loan Product
-            Long loanProductId = createLoanProduct(chargebackAllocation("PENALTY", "FEE", "INTEREST", "PRINCIPAL"));
+            Long loanProductId = createLoanProduct(//
+                    createDefaultPaymentAllocation(), //
+                    chargebackAllocation("PENALTY", "FEE", "INTEREST", "PRINCIPAL")//
+            );
+            // Apply and Approve Loan
+            Long loanId = applyAndApproveLoan(clientId, loanProductId);
 
+            // Disburse Loan
+            disburseLoan(loanId, BigDecimal.valueOf(1250.0), "01 January 2023");
+
+            // Add Charges
+            Long feeId = addCharge(loanId, false, 50, "15 January 2023");
+            Long penaltyId = addCharge(loanId, true, 20, "20 January 2023");
+
+            verifyRepaymentSchedule(loanId, //
+                    installment(0, null, "01 January 2023"), //
+                    installment(313.0, 0, 50, 20, 383.0, false, "01 February 2023"), //
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
+                    installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
+            );
+
+            // Update Business Date
+            updateBusinessDate("20 January 2023");
+
+            // Add Repayment
+            Long repaymentTransaction = addRepaymentForLoan(loanId, 383.0, "20 January 2023");
+
+            verifyRepaymentSchedule(loanId, //
+                    installment(0, null, "01 January 2023"), //
+                    installment(313.0, 0, 50, 20, 0.0, true, "01 February 2023"), //
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
+                    installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
+            );
+
+            updateBusinessDate("01 February 2023");
+
+            // Add Chargeback
+            addChargebackForLoan(loanId, repaymentTransaction, 100.0); // 20 penalty + 50 fee + 0 interest + 30
+            // principal
+
+            verifyTransactions(loanId, //
+                    transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(383.0, "Repayment", "20 January 2023", 937.0, 313.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
+                    transaction(100.0, "Chargeback", "01 February 2023", 967.0, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0) //
+            );
+
+            // Verify Repayment Schedule
+            verifyRepaymentSchedule(loanId, //
+                    installment(0, null, "01 January 2023"), //
+                    installment(313.0, 0, 50, 20, 0, true, "01 February 2023"), //
+                    installment(343.0, 0, 50, 20, 413.0, false, "01 March 2023"),
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
+                    installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
+            );
+        });
+    }
+
+    @Test
+    public void simpleChargebackWithCreditAllocationPenaltyFeeInterestAndPrincipalOnTheLastDayOfTheLoan() {
+        runAt("01 January 2023", () -> {
+            // Create Client
+            Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+            // Create Loan Product
+            Long loanProductId = createLoanProduct(//
+                    createDefaultPaymentAllocation(), //
+                    chargebackAllocation("PENALTY", "FEE", "INTEREST", "PRINCIPAL")//
+            );
+            // Apply and Approve Loan
+            Long loanId = applyAndApproveLoan(clientId, loanProductId);
+
+            // Disburse Loan
+            disburseLoan(loanId, BigDecimal.valueOf(1250.0), "01 January 2023");
+
+            // Add Charges
+            Long feeId = addCharge(loanId, false, 50, "15 January 2023");
+            Long penaltyId = addCharge(loanId, true, 20, "20 January 2023");
+
+            verifyRepaymentSchedule(loanId, //
+                    installment(0, null, "01 January 2023"), //
+                    installment(313.0, 0, 50, 20, 383.0, false, "01 February 2023"), //
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
+                    installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
+            );
+
+            // Update Business Date
+            updateBusinessDate("20 January 2023");
+
+            // Add Repayment
+            Long repaymentTransaction = addRepaymentForLoan(loanId, 383.0, "20 January 2023");
+
+            verifyRepaymentSchedule(loanId, //
+                    installment(0, null, "01 January 2023"), //
+                    installment(313.0, 0, 50, 20, 0.0, true, "01 February 2023"), //
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
+                    installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
+            );
+
+            updateBusinessDate("01 May 2023");
+
+            // Add Chargeback
+            addChargebackForLoan(loanId, repaymentTransaction, 100.0); // 20 penalty + 50 fee + 0 interest + 30
+            // principal
+
+            verifyTransactions(loanId, //
+                    transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(383.0, "Repayment", "20 January 2023", 937.0, 313.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
+                    transaction(100.0, "Chargeback", "01 May 2023", 967.0, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0) //
+            );
+
+            // Verify Repayment Schedule
+            verifyRepaymentSchedule(loanId, //
+                    installment(0, null, "01 January 2023"), //
+                    installment(313.0, 0, 50, 20, 0.0, true, "01 February 2023"),
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
+                    installment(341.0, 0, 50, 20, 411.0, false, "01 May 2023") //
+            );
+        });
+    }
+
+    @Test
+    public void chargebackWithCreditAllocationPenaltyFeeInterestAndPrincipalOnNPlusOneInstallment() {
+        runAt("01 January 2023", () -> {
+            // Create Client
+            Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+            // Create Loan Product
+            Long loanProductId = createLoanProduct(//
+                    createDefaultPaymentAllocation(), //
+                    chargebackAllocation("PENALTY", "FEE", "INTEREST", "PRINCIPAL")//
+            );
             // Apply and Approve Loan
             Long loanId = applyAndApproveLoan(clientId, loanProductId);
 
@@ -169,7 +304,7 @@
                     transaction(313.0, "Repayment", "20 March 2023", 311.0, 313.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
                     transaction(381.0, "Repayment", "20 April 2023", 0.0, 311.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
                     transaction(70.0, "Accrual", "20 April 2023", 0.0, 0.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
-                    transaction(100.0, "Chargeback", "02 May 2023", 100.0, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0) //
+                    transaction(100.0, "Chargeback", "02 May 2023", 30.0, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0) //
             );
 
             verifyRepaymentSchedule(loanId, //
@@ -178,20 +313,21 @@
                     installment(313.0, 0, 0, 0, 0.0, true, "01 March 2023"), //
                     installment(313.0, 0, 0, 0, 0.0, true, "01 April 2023"), //
                     installment(311.0, 0, 50, 20, 0.0, true, "01 May 2023"), //
-                    installment(30.0, 0, 0, 0, 30.0, false, "02 May 2023") // TODO: fee and penalty must be added here
-                                                                           // after chargeback
+                    installment(30.0, 0, 50, 20, 100.0, false, "02 May 2023") //
             );
         });
     }
 
     @Test
-    public void createLoanWithCreditAllocationAndChargebackReverseReplayWithBackdatedPayment() {
+    public void chargebackWithCreditAllocationAndReverseReplayWithBackdatedPayment() {
         runAt("01 January 2023", () -> {
             // Create Client
             Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
             // Create Loan Product
-            Long loanProductId = createLoanProduct(chargebackAllocation("PENALTY", "FEE", "INTEREST", "PRINCIPAL"));
-
+            Long loanProductId = createLoanProduct(//
+                    createDefaultPaymentAllocation(), //
+                    chargebackAllocation("PENALTY", "FEE", "INTEREST", "PRINCIPAL")//
+            );
             // Apply and Approve Loan
             Long loanId = applyAndApproveLoan(clientId, loanProductId);
 
@@ -232,15 +368,13 @@
             verifyTransactions(loanId, //
                     transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
                     transaction(383.0, "Repayment", "20 January 2023", 937.0, 313.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
-                    transaction(100.0, "Chargeback", "21 January 2023", 1037.0, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0) //
+                    transaction(100.0, "Chargeback", "21 January 2023", 967.0, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0) //
             );
 
             // Verify Repayment Schedule
             verifyRepaymentSchedule(loanId, //
                     installment(0, null, "01 January 2023"), //
-                    installment(343.0, 0, 50, 20, 30.0, false, "01 February 2023"), // TODO: we still need to add the
-                    // fee and the penalty to the
-                    // outstanding
+                    installment(343.0, 0, 100, 40, 100.0, false, "01 February 2023"), //
                     installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
                     installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
                     installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
@@ -260,13 +394,15 @@
     }
 
     @Test
-    public void createLoanWithCreditAllocationAndOnlyTheChargebackReverseReplayedWithBackdatedPayment() {
+    public void chargebackWithCreditAllocationReverseReplayedWithBackdatedPayment() {
         runAt("01 January 2023", () -> {
             // Create Client
             Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
             // Create Loan Product
-            Long loanProductId = createLoanProduct(chargebackAllocation("PENALTY", "FEE", "INTEREST", "PRINCIPAL"));
-
+            Long loanProductId = createLoanProduct(//
+                    createDefaultPaymentAllocation(), //
+                    chargebackAllocation("PENALTY", "FEE", "INTEREST", "PRINCIPAL")//
+            );
             // Apply and Approve Loan
             Long loanId = applyAndApproveLoan(clientId, loanProductId);
 
@@ -307,15 +443,13 @@
             verifyTransactions(loanId, //
                     transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
                     transaction(383.0, "Repayment", "20 January 2023", 937.0, 313.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
-                    transaction(100.0, "Chargeback", "22 January 2023", 1037.0, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0) //
+                    transaction(100.0, "Chargeback", "22 January 2023", 967.0, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0) //
             );
 
             // Verify Repayment Schedule
             verifyRepaymentSchedule(loanId, //
                     installment(0, null, "01 January 2023"), //
-                    installment(343.0, 0, 50, 20, 30.0, false, "01 February 2023"), // TODO: we still need to add the
-                    // fee and the penalty to the
-                    // outstanding
+                    installment(343.0, 0, 100, 40, 100.0, false, "01 February 2023"),
                     installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
                     installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
                     installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
@@ -330,18 +464,24 @@
                     transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
                     transaction(383.0, "Repayment", "20 January 2023", 937.0, 313.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
                     transaction(200.0, "Repayment", "21 January 2023", 737.0, 200.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
-                    transaction(100.0, "Chargeback", "22 January 2023", 837.0, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0) //
+                    transaction(100.0, "Chargeback", "22 January 2023", 767.0, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0) //
             );
+
+            verifyLoanSummaryAmounts(loanId, 30.0, 50.0, 20.0, 837.0);
         });
     }
 
     @Test
-    public void createLoanWithCreditAllocationAndChargebackPrincipalInterestFeePenalty() {
+    public void chargebackWithCreditAllocationPrincipalInterestFeePenalty() {
         runAt("01 January 2023", () -> {
             // Create Client
             Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+
             // Create Loan Product
-            Long loanProductId = createLoanProduct(chargebackAllocation("PRINCIPAL", "INTEREST", "FEE", "PENALTY"));
+            Long loanProductId = createLoanProduct(//
+                    createDefaultPaymentAllocation(), //
+                    chargebackAllocation("PRINCIPAL", "INTEREST", "FEE", "PENALTY")//
+            );
 
             // Apply and Approve Loan
             Long loanId = applyAndApproveLoan(clientId, loanProductId);
@@ -387,16 +527,501 @@
             // Verify Repayment Schedule
             verifyRepaymentSchedule(loanId, //
                     installment(0, null, "01 January 2023"), //
-                    installment(413.0, 0, 50, 20, 100.0, false, "01 February 2023"), // TODO: we still need to add the
-                    // fee and the penalty to the
-                    // outstanding
+                    installment(413.0, 0, 50, 20, 100.0, false, "01 February 2023"),
                     installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
                     installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
                     installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
             );
+
+            verifyLoanSummaryAmounts(loanId, 100.0, 0.0, 0.0, 1037);
         });
     }
 
+    @Test
+    public void chargebackWithCreditAllocationPrincipalInterestFeePenaltyWhenOverpaid() {
+        runAt("01 January 2023", () -> {
+            // Create Client
+            Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+            // Create Loan Product
+            Long loanProductId = createLoanProduct(//
+                    createDefaultPaymentAllocation(), //
+                    chargebackAllocation("PRINCIPAL", "INTEREST", "FEE", "PENALTY")//
+            );
+            // Apply and Approve Loan
+            Long loanId = applyAndApproveLoan(clientId, loanProductId);
+
+            // Disburse Loan
+            disburseLoan(loanId, BigDecimal.valueOf(1250.0), "01 January 2023");
+
+            // Add Charges
+            Long feeId = addCharge(loanId, false, 50, "15 January 2023");
+            Long penaltyId = addCharge(loanId, true, 20, "20 January 2023");
+
+            verifyRepaymentSchedule(loanId, //
+                    installment(0, null, "01 January 2023"), //
+                    installment(313.0, 0, 50, 20, 383.0, false, "01 February 2023"), //
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
+                    installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
+            );
+
+            // Update Business Date
+            updateBusinessDate("20 January 2023");
+
+            // Add Repayment
+            Long repaymentTransaction = addRepaymentForLoan(loanId, 1370.0, "20 January 2023"); // 1250 + 70 = 1320; 50
+                                                                                                // overpayment
+
+            verifyRepaymentSchedule(loanId, //
+                    installment(0, null, "01 January 2023"), //
+                    installment(313.0, 0, 50, 20, 0.0, true, "01 February 2023"), //
+                    installment(313.0, 0, 0, 0, 0.0, true, "01 March 2023"), //
+                    installment(313.0, 0, 0, 0, 0.0, true, "01 April 2023"), //
+                    installment(311.0, 0, 0, 0, 0.0, true, "01 May 2023") //
+            );
+
+            updateBusinessDate("02 May 2023");
+
+            // Add Chargeback
+            addChargebackForLoan(loanId, repaymentTransaction, 100.0); // 100 principal, 0 interest, 0 fee 0 penalty
+
+            verifyTransactions(loanId, //
+                    transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(1370.0, "Repayment", "20 January 2023", 0, 1250.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
+                    transaction(70.0, "Accrual", "20 January 2023", 0.0, 0.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
+                    transaction(100.0, "Chargeback", "02 May 2023", 50.0, 100.0, 0.0, 0.0, 0.0, 0.0, 50.0) //
+            );
+
+            // Verify Repayment Schedule
+            // DEFAULT payment allocation is ..., DUE_PENALTY, DUE_FEE, DUE_PRINCIPAL, ...
+            verifyRepaymentSchedule(loanId, //
+                    installment(0, null, "01 January 2023"), //
+                    installment(313.0, 0, 50, 20, 0, true, "01 February 2023"), //
+                    installment(313.0, 0, 0, 0, 0, true, "01 March 2023"), //
+                    installment(313.0, 0, 0, 0, 0, true, "01 April 2023"), //
+                    installment(311.0, 0, 0, 0, 0, true, "01 May 2023"), //
+                    installment(100.0, 0, 0, 0, outstanding(50.0, 0d, 0d, 50.0), false, "02 May 2023") //
+            );
+        });
+    }
+
+    @Test
+    public void chargebackWithCreditAllocationFeePenaltyPrincipalInterestWhenOverpaid() {
+        runAt("01 January 2023", () -> {
+            // Create Client
+            Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+            // Create Loan Product
+            Long loanProductId = createLoanProduct(//
+                    createDefaultPaymentAllocation(), //
+                    chargebackAllocation("FEE", "PENALTY", "PRINCIPAL", "INTEREST")//
+            );
+            // Apply and Approve Loan
+            Long loanId = applyAndApproveLoan(clientId, loanProductId);
+
+            // Disburse Loan
+            disburseLoan(loanId, BigDecimal.valueOf(1250.0), "01 January 2023");
+
+            // Add Charges
+            Long feeId = addCharge(loanId, false, 50, "15 January 2023");
+            Long penaltyId = addCharge(loanId, true, 20, "20 January 2023");
+
+            verifyRepaymentSchedule(loanId, //
+                    installment(0, null, "01 January 2023"), //
+                    installment(313.0, 0, 50, 20, 383.0, false, "01 February 2023"), //
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
+                    installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
+            );
+
+            // Update Business Date
+            updateBusinessDate("20 January 2023");
+
+            // Add Repayment
+            Long repaymentTransaction = addRepaymentForLoan(loanId, 1370.0, "20 January 2023"); // 1250 + 70 = 1320; 50
+                                                                                                // overpayment
+
+            verifyRepaymentSchedule(loanId, //
+                    installment(0, null, "01 January 2023"), //
+                    installment(313.0, 0, 50, 20, 0.0, true, "01 February 2023"), //
+                    installment(313.0, 0, 0, 0, 0.0, true, "01 March 2023"), //
+                    installment(313.0, 0, 0, 0, 0.0, true, "01 April 2023"), //
+                    installment(311.0, 0, 0, 0, 0.0, true, "01 May 2023") //
+            );
+
+            // Add Chargeback
+            addChargebackForLoan(loanId, repaymentTransaction, 100.0); // 100 principal, 0 interest, 0 fee 0 penalty
+
+            verifyTransactions(loanId, //
+                    transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(1370.0, "Repayment", "20 January 2023", 0, 1250.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
+                    transaction(70.0, "Accrual", "20 January 2023", 0.0, 0.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
+                    transaction(100.0, "Chargeback", "20 January 2023", 30.0, 30.0, 0.0, 50.0, 20.0, 0.0, 50.0) //
+            );
+
+            // Verify Repayment Schedule,
+            // DEFAULT payment allocation is ..., DUE_PENALTY, DUE_FEE, DUE_PRINCIPAL, ...
+            verifyRepaymentSchedule(loanId, //
+                    installment(0, null, "01 January 2023"), //
+                    installment(343.0, 0, 100, 40, outstanding(30.0, 20.0, 0.0, 50.0), false, "01 February 2023"), //
+                    installment(313.0, 0, 0, 0, 0.0, true, "01 March 2023"), //
+                    installment(313.0, 0, 0, 0, 0.0, true, "01 April 2023"), //
+                    installment(311.0, 0, 0, 0, 0.0, true, "01 May 2023") //
+            );
+
+            verifyLoanSummaryAmounts(loanId, 30.0, 50.0, 20.0, 50.0);
+        });
+    }
+
+    @Test
+    public void chargebackWithCreditAllocationFeePenaltyPrincipalInterestWhenOverpaidDefaultPaymentPrincipalFirst() {
+        runAt("01 January 2023", () -> {
+            // Create Client
+            Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+            // Create Loan Product
+            Long loanProductId = createLoanProduct(//
+                    createDefaultPaymentAllocationPrincipalFirst(), //
+                    chargebackAllocation("FEE", "PENALTY", "PRINCIPAL", "INTEREST")//
+            );
+            // Apply and Approve Loan
+            Long loanId = applyAndApproveLoan(clientId, loanProductId);
+
+            // Disburse Loan
+            disburseLoan(loanId, BigDecimal.valueOf(1250.0), "01 January 2023");
+
+            // Add Charges
+            Long feeId = addCharge(loanId, false, 50, "15 January 2023");
+            Long penaltyId = addCharge(loanId, true, 20, "20 January 2023");
+
+            verifyRepaymentSchedule(loanId, //
+                    installment(0, null, "01 January 2023"), //
+                    installment(313.0, 0, 50, 20, 383.0, false, "01 February 2023"), //
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
+                    installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
+            );
+
+            // Update Business Date
+            updateBusinessDate("20 January 2023");
+
+            // Add Repayment
+            Long repaymentTransaction = addRepaymentForLoan(loanId, 1370.0, "20 January 2023"); // 1250 + 70 = 1320; 50
+            // overpayment
+
+            verifyRepaymentSchedule(loanId, //
+                    installment(0, null, "01 January 2023"), //
+                    installment(313.0, 0, 50, 20, 0.0, true, "01 February 2023"), //
+                    installment(313.0, 0, 0, 0, 0.0, true, "01 March 2023"), //
+                    installment(313.0, 0, 0, 0, 0.0, true, "01 April 2023"), //
+                    installment(311.0, 0, 0, 0, 0.0, true, "01 May 2023") //
+            );
+
+            // Add Chargeback
+            addChargebackForLoan(loanId, repaymentTransaction, 100.0); // 100 principal, 0 interest, 0 fee 0 penalty
+
+            verifyTransactions(loanId, //
+                    transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(1370.0, "Repayment", "20 January 2023", 0, 1250.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
+                    transaction(70.0, "Accrual", "20 January 2023", 0.0, 0.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
+                    transaction(100.0, "Chargeback", "20 January 2023", 0.0, 30.0, 0.0, 50.0, 20.0, 0.0, 50.0) //
+            );
+
+            // Verify Repayment Schedule,
+            // DEFAULT payment allocation is ..., DUE_PRINCIPAL, DUE_FEE, DUE_PENALTY ...
+            verifyRepaymentSchedule(loanId, //
+                    installment(0, null, "01 January 2023"), //
+                    installment(343.0, 0, 100, 40, outstanding(0.0, 30.0, 20.0, 50.0), false, "01 February 2023"), //
+                    installment(313.0, 0, 0, 0, 0.0, true, "01 March 2023"), //
+                    installment(313.0, 0, 0, 0, 0.0, true, "01 April 2023"), //
+                    installment(311.0, 0, 0, 0, 0.0, true, "01 May 2023") //
+            );
+
+            verifyLoanSummaryAmounts(loanId, 30.0, 50.0, 20.0, 50.0);
+        });
+    }
+
+    @Test
+    public void doubleChargebackWithCreditAllocationPenaltyFeeInterestAndPrincipal() {
+        runAt("01 January 2023", () -> {
+            // Create Client
+            Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+            // Create Loan Product
+            Long loanProductId = createLoanProduct(//
+                    createDefaultPaymentAllocation(), //
+                    chargebackAllocation("PENALTY", "FEE", "INTEREST", "PRINCIPAL")//
+            );
+            // Apply and Approve Loan
+            Long loanId = applyAndApproveLoan(clientId, loanProductId);
+
+            // Disburse Loan
+            disburseLoan(loanId, BigDecimal.valueOf(1250.0), "01 January 2023");
+
+            // Add Charges
+            Long feeId = addCharge(loanId, false, 50, "15 January 2023");
+            Long penaltyId = addCharge(loanId, true, 20, "20 January 2023");
+
+            verifyRepaymentSchedule(loanId, //
+                    installment(0, null, "01 January 2023"), //
+                    installment(313.0, 0, 50, 20, 383.0, false, "01 February 2023"), //
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
+                    installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
+            );
+
+            // Update Business Date
+            updateBusinessDate("20 January 2023");
+
+            // Add Repayment
+            Long repaymentTransaction = addRepaymentForLoan(loanId, 383.0, "20 January 2023");
+
+            verifyRepaymentSchedule(loanId, //
+                    installment(0, null, "01 January 2023"), //
+                    installment(313.0, 0, 50, 20, 0.0, true, "01 February 2023"), //
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
+                    installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
+            );
+
+            // Add Chargeback
+            addChargebackForLoan(loanId, repaymentTransaction, 100.0); // 20 penalty + 50 fee + 0 interest + 30
+            // principal
+
+            verifyTransactions(loanId, //
+                    transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(383.0, "Repayment", "20 January 2023", 937.0, 313.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
+                    transaction(100.0, "Chargeback", "20 January 2023", 967.0, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0) //
+            );
+
+            // Verify Repayment Schedule
+            verifyRepaymentSchedule(loanId, //
+                    installment(0, null, "01 January 2023"), //
+                    installment(343.0, 0, 100, 40, 100.0, false, "01 February 2023"),
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
+                    installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
+            );
+
+            updateBusinessDate("21 January 2023");
+
+            addChargebackForLoan(loanId, repaymentTransaction, 100.0); // 100 to principal
+
+            verifyTransactions(loanId, //
+                    transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(383.0, "Repayment", "20 January 2023", 937.0, 313.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
+                    transaction(100.0, "Chargeback", "20 January 2023", 967, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
+                    transaction(100.0, "Chargeback", "21 January 2023", 1067.0, 100.0, 0.0, 0.0, 0.0, 0.0, 0.0) //
+            );
+
+            // Verify Repayment Schedule
+            verifyRepaymentSchedule(loanId, //
+                    installment(0, null, "01 January 2023"), //
+                    installment(443.0, 0, 100, 40, 200.0, false, "01 February 2023"),
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
+                    installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
+            );
+
+        });
+    }
+
+    @Test
+    public void doubleChargebackReverseReplayedBothFeeAndPenaltyPayedWithCreditAllocationPenaltyFeeInterestAndPrincipal() {
+        runAt("01 January 2023", () -> {
+            // Create Client
+            Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+            // Create Loan Product
+            Long loanProductId = createLoanProduct(//
+                    createDefaultPaymentAllocation(), //
+                    chargebackAllocation("PENALTY", "FEE", "INTEREST", "PRINCIPAL")//
+            );
+            // Apply and Approve Loan
+            Long loanId = applyAndApproveLoan(clientId, loanProductId);
+
+            // Disburse Loan
+            disburseLoan(loanId, BigDecimal.valueOf(1250.0), "01 January 2023");
+
+            // Add Charges
+            Long feeId = addCharge(loanId, false, 50, "15 January 2023");
+            Long penaltyId = addCharge(loanId, true, 20, "20 January 2023");
+
+            verifyRepaymentSchedule(loanId, //
+                    installment(0, null, "01 January 2023"), //
+                    installment(313.0, 0, 50, 20, 383.0, false, "01 February 2023"), //
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
+                    installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
+            );
+
+            // Update Business Date
+            updateBusinessDate("20 January 2023");
+
+            // Add Repayment
+            Long repaymentTransaction = addRepaymentForLoan(loanId, 383.0, "20 January 2023");
+
+            verifyRepaymentSchedule(loanId, //
+                    installment(0, null, "01 January 2023"), //
+                    installment(313.0, 0, 50, 20, 0.0, true, "01 February 2023"), //
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
+                    installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
+            );
+
+            // Add Chargeback
+            addChargebackForLoan(loanId, repaymentTransaction, 100.0); // 20 penalty + 50 fee + 0 interest + 30
+            // principal
+
+            verifyTransactions(loanId, //
+                    transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(383.0, "Repayment", "20 January 2023", 937.0, 313.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
+                    transaction(100.0, "Chargeback", "20 January 2023", 967.0, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0) //
+            );
+
+            // Verify Repayment Schedule
+            verifyRepaymentSchedule(loanId, //
+                    installment(0, null, "01 January 2023"), //
+                    installment(343.0, 0, 100, 40, 100.0, false, "01 February 2023"),
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
+                    installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
+            );
+
+            updateBusinessDate("21 January 2023");
+
+            addChargebackForLoan(loanId, repaymentTransaction, 100.0); // 100 to principal
+
+            verifyTransactions(loanId, //
+                    transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(383.0, "Repayment", "20 January 2023", 937.0, 313.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
+                    transaction(100.0, "Chargeback", "20 January 2023", 967.0, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
+                    transaction(100.0, "Chargeback", "21 January 2023", 1067.0, 100.0, 0.0, 0.0, 0.0, 0.0, 0.0) //
+            );
+
+            // Verify Repayment Schedule
+            verifyRepaymentSchedule(loanId, //
+                    installment(0, null, "01 January 2023"), //
+                    installment(443.0, 0, 100, 40, 200.0, false, "01 February 2023"),
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
+                    installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
+            );
+
+            // Let's add repayment to trigger reverse replay for both chargebacks
+            addRepaymentForLoan(loanId, 200.0, "19 January 2023");
+
+            verifyTransactions(loanId, //
+                    transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(200.0, "Repayment", "19 January 2023", 1120.0, 130.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
+                    transaction(383.0, "Repayment", "20 January 2023", 737.0, 383.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(100.0, "Chargeback", "20 January 2023", 837.0, 100.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(100.0, "Chargeback", "21 January 2023", 937.0, 100.0, 0.0, 0.0, 0.0, 0.0, 0.0) //
+            );
+        });
+    }
+
+    @Test
+    public void doubleChargebackReverseReplayedOnlyPenaltyPayedWithCreditAllocationPenaltyFeeInterestAndPrincipal() {
+        runAt("01 January 2023", () -> {
+            // Create Client
+            Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+            // Create Loan Product
+            Long loanProductId = createLoanProduct(//
+                    createDefaultPaymentAllocation(), //
+                    chargebackAllocation("PENALTY", "FEE", "INTEREST", "PRINCIPAL")//
+            );
+            // Apply and Approve Loan
+            Long loanId = applyAndApproveLoan(clientId, loanProductId);
+
+            // Disburse Loan
+            disburseLoan(loanId, BigDecimal.valueOf(1250.0), "01 January 2023");
+
+            // Add Charges
+            Long feeId = addCharge(loanId, false, 50, "15 January 2023");
+            Long penaltyId = addCharge(loanId, true, 20, "15 January 2023");
+
+            verifyRepaymentSchedule(loanId, //
+                    installment(0, null, "01 January 2023"), //
+                    installment(313.0, 0, 50, 20, 383.0, false, "01 February 2023"), //
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
+                    installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
+            );
+
+            // Update Business Date
+            updateBusinessDate("20 January 2023");
+
+            // Add Repayment
+            Long repaymentTransaction = addRepaymentForLoan(loanId, 383.0, "20 January 2023");
+
+            verifyRepaymentSchedule(loanId, //
+                    installment(0, null, "01 January 2023"), //
+                    installment(313.0, 0, 50, 20, 0.0, true, "01 February 2023"), //
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
+                    installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
+            );
+
+            // Add Chargeback
+            addChargebackForLoan(loanId, repaymentTransaction, 100.0); // 20 penalty + 50 fee + 0 interest + 30
+            // principal
+
+            verifyTransactions(loanId, //
+                    transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(383.0, "Repayment", "20 January 2023", 937.0, 313.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
+                    transaction(100.0, "Chargeback", "20 January 2023", 967.0, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0) //
+            );
+
+            // Verify Repayment Schedule
+            verifyRepaymentSchedule(loanId, //
+                    installment(0, null, "01 January 2023"), //
+                    installment(343.0, 0, 100, 40, 100.0, false, "01 February 2023"),
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
+                    installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
+            );
+
+            updateBusinessDate("21 January 2023");
+
+            addChargebackForLoan(loanId, repaymentTransaction, 100.0); // 100 to principal
+
+            verifyTransactions(loanId, //
+                    transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(383.0, "Repayment", "20 January 2023", 937.0, 313.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
+                    transaction(100.0, "Chargeback", "20 January 2023", 967.0, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
+                    transaction(100.0, "Chargeback", "21 January 2023", 1067.0, 100.0, 0.0, 0.0, 0.0, 0.0, 0.0) //
+            );
+
+            // Verify Repayment Schedule
+            verifyRepaymentSchedule(loanId, //
+                    installment(0, null, "01 January 2023"), //
+                    installment(443.0, 0, 100, 40, 200.0, false, "01 February 2023"),
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
+                    installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
+                    installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
+            );
+
+            // Let's add repayment to trigger reverse replay for both chargebacks
+            addRepaymentForLoan(loanId, 20.0, "19 January 2023");
+
+            verifyTransactions(loanId, //
+                    transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(20.0, "Repayment", "19 January 2023", 1250.0, 0.0, 0.0, 0.0, 20.0, 0.0, 0.0), //
+                    transaction(383.0, "Repayment", "20 January 2023", 917.0, 333.0, 0.0, 50.0, 0.0, 0.0, 0.0), //
+                    transaction(100.0, "Chargeback", "20 January 2023", 967.0, 50.0, 0.0, 50.0, 0.0, 0.0, 0.0), //
+                    transaction(100.0, "Chargeback", "21 January 2023", 1067.0, 100.0, 0.0, 0.0, 0.0, 0.0, 0.0) //
+            );
+        });
+    }
+
+    private void verifyLoanSummaryAmounts(Long loanId, double creditedPrincipal, double creditedFee, double creditedPenalty,
+            double totalOutstanding) {
+        GetLoansLoanIdResponse loanResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId.intValue());
+        GetLoansLoanIdSummary summary = loanResponse.getSummary();
+        Assertions.assertNotNull(summary);
+        Assertions.assertEquals(creditedPrincipal, summary.getPrincipalAdjustments());
+        Assertions.assertEquals(creditedFee, summary.getFeeAdjustments());
+        Assertions.assertEquals(creditedPenalty, summary.getPenaltyAdjustments());
+        Assertions.assertEquals(totalOutstanding, summary.getTotalOutstanding());
+    }
+
     @Nullable
     private Long applyAndApproveLoan(Long clientId, Long loanProductId) {
         PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductId, "01 January 2023", 1250.0, 4)//
@@ -415,22 +1040,23 @@
         return loanId;
     }
 
-    public Long createLoanProduct(CreditAllocationData... creditAllocationData) {
-        PostLoanProductsRequest postLoanProductsRequest = loanProductWithAdvancedPaymentAllocationWith4Installments(creditAllocationData);
+    public Long createLoanProduct(AdvancedPaymentData defaultAllocation, CreditAllocationData creditAllocationData) {
+        PostLoanProductsRequest postLoanProductsRequest = loanProductWithAdvancedPaymentAllocationWith4Installments(defaultAllocation,
+                creditAllocationData);
         PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(postLoanProductsRequest);
         return loanProductResponse.getResourceId();
     }
 
-    private PostLoanProductsRequest loanProductWithAdvancedPaymentAllocationWith4Installments(
-            CreditAllocationData... creditAllocationData) {
+    private PostLoanProductsRequest loanProductWithAdvancedPaymentAllocationWith4Installments(AdvancedPaymentData defaultAllocation,
+            CreditAllocationData creditAllocationData) {
         return createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct().numberOfRepayments(4)//
                 .repaymentEvery(1)//
                 .repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue())//
                 .loanScheduleType(LoanScheduleType.PROGRESSIVE.toString()) //
                 .loanScheduleProcessingType(LoanScheduleProcessingType.VERTICAL.toString()) //
                 .transactionProcessingStrategyCode("advanced-payment-allocation-strategy")
-                .paymentAllocation(List.of(createDefaultPaymentAllocation(), createRepaymentPaymentAllocation()))
-                .creditAllocation(Arrays.asList(creditAllocationData));
+                .paymentAllocation(List.of(defaultAllocation, createRepaymentPaymentAllocation()))
+                .creditAllocation(List.of(creditAllocationData));
     }
 
     private AdvancedPaymentData createDefaultPaymentAllocation() {
@@ -448,6 +1074,21 @@
         return advancedPaymentData;
     }
 
+    private AdvancedPaymentData createDefaultPaymentAllocationPrincipalFirst() {
+        AdvancedPaymentData advancedPaymentData = new AdvancedPaymentData();
+        advancedPaymentData.setTransactionType("DEFAULT");
+        advancedPaymentData.setFutureInstallmentAllocationRule("NEXT_INSTALLMENT");
+
+        List<PaymentAllocationOrder> paymentAllocationOrders = getPaymentAllocationOrder(PaymentAllocationType.PAST_DUE_PENALTY,
+                PaymentAllocationType.PAST_DUE_FEE, PaymentAllocationType.PAST_DUE_PRINCIPAL, PaymentAllocationType.PAST_DUE_INTEREST,
+                PaymentAllocationType.DUE_PRINCIPAL, PaymentAllocationType.DUE_FEE, PaymentAllocationType.DUE_PENALTY,
+                PaymentAllocationType.DUE_INTEREST, PaymentAllocationType.IN_ADVANCE_PRINCIPAL, PaymentAllocationType.IN_ADVANCE_FEE,
+                PaymentAllocationType.IN_ADVANCE_PENALTY, PaymentAllocationType.IN_ADVANCE_INTEREST);
+
+        advancedPaymentData.setPaymentAllocationOrder(paymentAllocationOrders);
+        return advancedPaymentData;
+    }
+
     private AdvancedPaymentData createRepaymentPaymentAllocation() {
         AdvancedPaymentData advancedPaymentData = new AdvancedPaymentData();
         advancedPaymentData.setTransactionType("REPAYMENT");