This is an automated email from the ASF dual-hosted git repository.

bagrijp pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git


The following commit(s) were added to refs/heads/develop by this push:
     new c504996c1 FINERACT-2042 Credit Allocation for Principal and Interest 
components
c504996c1 is described below

commit c504996c1d6cc4dd73a6eff2a91f43eec6e42bcf
Author: Peter Bagrij <[email protected]>
AuthorDate: Wed Feb 7 11:56:01 2024 +0100

    FINERACT-2042 Credit Allocation for Principal and Interest components
---
 .../domain/LoanTransactionRelation.java            |   4 +-
 ...tLoanRepaymentScheduleTransactionProcessor.java |   2 +-
 ...dvancedPaymentScheduleTransactionProcessor.java | 189 ++++++++++
 .../AccrualBasedAccountingProcessorForLoan.java    |   5 +-
 .../loanaccount/service/LoanAssembler.java         |   7 +
 ...cedPaymentScheduleTransactionProcessorTest.java | 249 +++++++++++-
 .../integrationtests/BaseLoanIntegrationTest.java  |  37 +-
 ...ebackWithCreditAllocationsIntegrationTests.java | 418 +++++++++++++++++++++
 .../common/loans/LoanTransactionHelper.java        |   5 +
 9 files changed, 891 insertions(+), 25 deletions(-)

diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRelation.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRelation.java
index 84ee76108..c76f73b81 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRelation.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRelation.java
@@ -62,7 +62,9 @@ public class LoanTransactionRelation extends 
AbstractAuditableWithUTCDateTimeCus
 
     public static LoanTransactionRelation linkToTransaction(@NotNull 
LoanTransaction fromTransaction,
             @NotNull LoanTransaction toTransaction, 
LoanTransactionRelationTypeEnum relation) {
-        return new LoanTransactionRelation(fromTransaction, toTransaction, 
null, relation);
+        LoanTransactionRelation loanTransactionRelation = new 
LoanTransactionRelation(fromTransaction, toTransaction, null, relation);
+        
fromTransaction.getLoanTransactionRelations().add(loanTransactionRelation);
+        return loanTransactionRelation;
     }
 
     public static LoanTransactionRelation linkToCharge(@NotNull 
LoanTransaction fromTransaction, @NotNull LoanCharge loanCharge,
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 89a2ec7b4..583dc130b 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
@@ -474,7 +474,7 @@ public abstract class 
AbstractLoanRepaymentScheduleTransactionProcessor implemen
 
     }
 
-    private void processCreditTransaction(LoanTransaction loanTransaction, 
MoneyHolder overpaymentHolder, MonetaryCurrency currency,
+    protected void processCreditTransaction(LoanTransaction loanTransaction, 
MoneyHolder overpaymentHolder, MonetaryCurrency currency,
             List<LoanRepaymentScheduleInstallment> installments) {
         loanTransaction.resetDerivedComponents();
         List<LoanTransactionToRepaymentScheduleMapping> transactionMappings = 
new ArrayList<>();
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 37d43be4d..a8cc27c0d 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
@@ -20,16 +20,23 @@ package 
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.im
 
 import static java.util.stream.Collectors.mapping;
 import static java.util.stream.Collectors.toList;
+import static 
org.apache.fineract.portfolio.loanproduct.domain.AllocationType.FEE;
+import static 
org.apache.fineract.portfolio.loanproduct.domain.AllocationType.INTEREST;
+import static 
org.apache.fineract.portfolio.loanproduct.domain.AllocationType.PENALTY;
+import static 
org.apache.fineract.portfolio.loanproduct.domain.AllocationType.PRINCIPAL;
 
 import java.math.BigDecimal;
 import java.math.MathContext;
 import java.time.LocalDate;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 import java.util.function.Function;
@@ -41,22 +48,28 @@ import lombok.Setter;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.NotImplementedException;
 import org.apache.commons.lang3.ObjectUtils;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
 import org.apache.fineract.infrastructure.core.service.MathUtil;
 import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
 import org.apache.fineract.organisation.monetary.domain.Money;
 import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
 import 
org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy;
+import 
org.apache.fineract.portfolio.loanaccount.domain.LoanCreditAllocationRule;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanPaymentAllocationRule;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
 import 
org.apache.fineract.portfolio.loanaccount.domain.SingleLoanChargeRepaymentScheduleProcessingWrapper;
 import 
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
 import 
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
 import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
+import org.apache.fineract.portfolio.loanproduct.domain.AllocationType;
+import 
org.apache.fineract.portfolio.loanproduct.domain.CreditAllocationTransactionType;
 import org.apache.fineract.portfolio.loanproduct.domain.DueType;
 import 
org.apache.fineract.portfolio.loanproduct.domain.FutureInstallmentAllocationRule;
 import 
org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
@@ -172,6 +185,182 @@ public class AdvancedPaymentScheduleTransactionProcessor 
extends AbstractLoanRep
         }
     }
 
+    private boolean hasNoCustomCreditAllocationRule(LoanTransaction 
loanTransaction) {
+        return (loanTransaction.getLoan().getCreditAllocationRules() == null 
|| !loanTransaction.getLoan().getCreditAllocationRules()
+                .stream().anyMatch(e -> 
e.getTransactionType().getLoanTransactionType().equals(loanTransaction.getTypeOf())));
+    }
+
+    @Override
+    protected void processCreditTransaction(LoanTransaction loanTransaction, 
MoneyHolder overpaymentHolder, MonetaryCurrency currency,
+            List<LoanRepaymentScheduleInstallment> installments) {
+        if (hasNoCustomCreditAllocationRule(loanTransaction)) {
+            super.processCreditTransaction(loanTransaction, overpaymentHolder, 
currency, installments);
+        } 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);
+            installments.sort(byDate);
+            final Money zeroMoney = Money.zero(currency);
+            Money transactionAmount = loanTransaction.getAmount(currency);
+            Money amountToDistribute = MathUtil
+                    
.negativeToZero(loanTransaction.getAmount(currency).minus(overpaymentHolder.getMoneyObject()));
+            Money repaidAmount = 
MathUtil.negativeToZero(transactionAmount.minus(amountToDistribute));
+            loanTransaction.setOverPayments(repaidAmount);
+            
overpaymentHolder.setMoneyObject(overpaymentHolder.getMoneyObject().minus(repaidAmount));
+
+            if (amountToDistribute.isGreaterThanZero()) {
+                if (loanTransaction.isChargeback()) {
+                    Optional<LoanTransaction> originalTransaction = 
loanTransaction.getLoan().getLoanTransactions(
+                            tr -> 
tr.getLoanTransactionRelations().stream().anyMatch(this.hasMatchingToLoanTransaction(loanTransaction)))
+                            .stream().findFirst();
+                    if (originalTransaction.isEmpty()) {
+                        throw new RuntimeException("Chargeback transaction 
must have an original transaction");
+                    }
+
+                    Map<AllocationType, BigDecimal> originalAllocation = 
getOriginalAllocation(originalTransaction.get());
+                    LoanCreditAllocationRule chargeBackAllocationRule = 
getChargebackAllocationRules(loanTransaction);
+                    Map<AllocationType, Money> chargebackAllocation = 
calculateChargebackAllocationMap(originalAllocation,
+                            amountToDistribute.getAmount(), 
chargeBackAllocationRule.getAllocationTypes(), currency);
+
+                    
loanTransaction.updateComponents(chargebackAllocation.get(PRINCIPAL), 
chargebackAllocation.get(INTEREST),
+                            chargebackAllocation.get(FEE), 
chargebackAllocation.get(PENALTY));
+
+                    final LocalDate transactionDate = 
loanTransaction.getTransactionDate();
+                    boolean loanTransactionMapped = false;
+                    LocalDate pastDueDate = null;
+                    for (final LoanRepaymentScheduleInstallment 
currentInstallment : installments) {
+                        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(currency);
+                            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));
+                            }
+                            loanTransactionMapped = true;
+                            break;
+
+                            // If already exists an additional installment 
just update the due date and
+                            // principal from the Loan chargeback / CBR 
transaction
+                        } else if (currentInstallment.isAdditional()) {
+                            if (DateUtils.isAfter(transactionDate, 
currentInstallment.getDueDate())) {
+                                
currentInstallment.updateDueDate(transactionDate);
+                            }
+                            
currentInstallment.addToCredits(transactionAmount.getAmount());
+                            currentInstallment.addToPrincipal(transactionDate, 
chargebackAllocation.get(PRINCIPAL));
+                            Money originalInterest = 
currentInstallment.getInterestCharged(currency);
+                            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));
+                            }
+                            loanTransactionMapped = true;
+                            break;
+                        }
+                    }
+
+                    // New installment will be added (N+1 scenario)
+                    if (!loanTransactionMapped) {
+                        if 
(loanTransaction.getTransactionDate().equals(pastDueDate)) {
+                            LoanRepaymentScheduleInstallment 
currentInstallment = installments.get(installments.size() - 1);
+                            
currentInstallment.addToCredits(transactionAmount.getAmount());
+                            currentInstallment.addToPrincipal(transactionDate, 
chargebackAllocation.get(PRINCIPAL));
+                            Money originalInterest = 
currentInstallment.getInterestCharged(currency);
+                            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));
+                            }
+                        } else {
+                            Loan loan = loanTransaction.getLoan();
+                            LoanRepaymentScheduleInstallment installment = new 
LoanRepaymentScheduleInstallment(loan,
+                                    (installments.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(currency);
+                            installment.updateInterestCharged(
+                                    
originalInterest.plus(chargebackAllocation.get(INTEREST)).getAmountDefaultedToNullIfZero());
+                            
loan.addLoanRepaymentScheduleInstallment(installment);
+                            if (repaidAmount.isGreaterThanZero()) {
+                                
installment.payPrincipalComponent(loanTransaction.getTransactionDate(), 
repaidAmount);
+                                
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction,
 installment,
+                                        repaidAmount, zeroMoney, zeroMoney, 
zeroMoney));
+                            }
+                        }
+                    }
+
+                    
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings);
+                }
+            }
+        }
+    }
+
+    @NotNull
+    private LoanCreditAllocationRule 
getChargebackAllocationRules(LoanTransaction loanTransaction) {
+        LoanCreditAllocationRule chargeBackAllocationRule = 
loanTransaction.getLoan().getCreditAllocationRules().stream()
+                .filter(tr -> 
tr.getTransactionType().equals(CreditAllocationTransactionType.CHARGEBACK)).findFirst().orElseThrow();
+        return chargeBackAllocationRule;
+    }
+
+    @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());
+        return originalAllocation;
+    }
+
+    protected Map<AllocationType, Money> 
calculateChargebackAllocationMap(Map<AllocationType, BigDecimal> 
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);
+                if (originalAmount != null && 
remainingAmount.compareTo(originalAmount) > 0
+                        && originalAmount.compareTo(BigDecimal.ZERO) > 0) {
+                    result.put(allocationType, Money.of(currency, 
originalAmount));
+                    remainingAmount = remainingAmount.subtract(originalAmount);
+                } else if (originalAmount != null && 
remainingAmount.compareTo(originalAmount) <= 0
+                        && originalAmount.compareTo(BigDecimal.ZERO) > 0) {
+                    result.put(allocationType, Money.of(currency, 
remainingAmount));
+                    remainingAmount = BigDecimal.ZERO;
+                }
+            }
+        }
+        return result;
+    }
+
+    private Predicate<LoanTransactionRelation> 
hasMatchingToLoanTransaction(LoanTransaction loanTransaction) {
+        return relation -> {
+            if (loanTransaction.getId() != null && 
relation.getToTransaction().getId() != null) {
+                return Objects.equals(relation.getToTransaction().getId(), 
loanTransaction.getId());
+            } else {
+                return 
relation.getToTransaction().getTypeOf().equals(loanTransaction.getTypeOf())
+                        && 
relation.getToTransaction().getAmount().compareTo(loanTransaction.getAmount()) 
== 0
+                        && relation.getToTransaction().isReversed() == 
loanTransaction.isReversed()
+                        && 
relation.getToTransaction().getTransactionDate().compareTo(loanTransaction.getTransactionDate())
 == 0;
+            }
+        };
+    }
+
     @Override
     protected void handleRefund(LoanTransaction loanTransaction, 
MonetaryCurrency currency,
             List<LoanRepaymentScheduleInstallment> installments, 
Set<LoanCharge> charges) {
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java
 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java
index d9351deee..8ba14d78f 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java
@@ -430,11 +430,12 @@ public class AccrualBasedAccountingProcessorForLoan 
implements AccountingProcess
         final BigDecimal overpaidAmount = 
Objects.isNull(loanTransactionDTO.getOverPayment()) ? BigDecimal.ZERO
                 : loanTransactionDTO.getOverPayment();
 
-        if (BigDecimal.ZERO.compareTo(overpaidAmount) == 0) {
+        if (BigDecimal.ZERO.compareTo(overpaidAmount) == 0) { // when no 
overpay
             helper.createJournalEntriesAndReversalsForLoan(office, 
currencyCode, AccrualAccountsForLoan.LOAN_PORTFOLIO.getValue(),
                     AccrualAccountsForLoan.FUND_SOURCE.getValue(), 
loanProductId, paymentTypeId, loanId, transactionId, transactionDate,
                     amount, isReversal);
-        } else if (overpaidAmount.compareTo(amount) >= 0) {
+        } else if (overpaidAmount.compareTo(amount) >= 0) { // when the 
overpay amount is matching with the normal
+                                                            // amount
             helper.createJournalEntriesAndReversalsForLoan(office, 
currencyCode, AccrualAccountsForLoan.OVERPAYMENT.getValue(),
                     AccrualAccountsForLoan.FUND_SOURCE.getValue(), 
loanProductId, paymentTypeId, loanId, transactionId, transactionDate,
                     amount, isReversal);
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java
index 0201eaea9..36b9e86de 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java
@@ -66,6 +66,7 @@ import 
org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
 import org.apache.fineract.portfolio.loanaccount.domain.Loan;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanCollateralManagement;
+import 
org.apache.fineract.portfolio.loanaccount.domain.LoanCreditAllocationRule;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanPaymentAllocationRule;
@@ -390,6 +391,12 @@ public class LoanAssembler {
                             r.getFutureInstallmentAllocationRule()))
                     .toList();
             
loanApplication.setPaymentAllocationRules(loanPaymentAllocationRules);
+
+            if (loanProduct.getCreditAllocationRules() != null && 
loanProduct.getCreditAllocationRules().size() > 0) {
+                List<LoanCreditAllocationRule> loanCreditAllocationRules = 
loanProduct.getCreditAllocationRules().stream()
+                        .map(r -> new 
LoanCreditAllocationRule(loanApplication, r.getTransactionType(), 
r.getAllocationTypes())).toList();
+                
loanApplication.setCreditAllocationRules(loanCreditAllocationRules);
+            }
         }
     }
 }
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 e2ae5fa40..6facec110 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
@@ -18,13 +18,25 @@
  */
 package 
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl;
 
+import static 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.CHARGEBACK;
+import static 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.REPAYMENT;
+import static 
org.apache.fineract.portfolio.loanproduct.domain.AllocationType.FEE;
+import static 
org.apache.fineract.portfolio.loanproduct.domain.AllocationType.INTEREST;
+import static 
org.apache.fineract.portfolio.loanproduct.domain.AllocationType.PENALTY;
+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.times;
 
 import java.math.BigDecimal;
 import java.math.RoundingMode;
 import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -39,21 +51,27 @@ import 
org.apache.fineract.organisation.monetary.domain.MoneyHelper;
 import org.apache.fineract.portfolio.loanaccount.domain.Loan;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy;
+import 
org.apache.fineract.portfolio.loanaccount.domain.LoanCreditAllocationRule;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanPaymentAllocationRule;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
 import 
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
 import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
+import org.apache.fineract.portfolio.loanproduct.domain.AllocationType;
+import 
org.apache.fineract.portfolio.loanproduct.domain.CreditAllocationTransactionType;
 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.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.MockedStatic;
 import org.mockito.Mockito;
 import org.mockito.junit.jupiter.MockitoExtension;
@@ -95,13 +113,13 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
         final BigDecimal chargeAmount = BigDecimal.valueOf(100);
         LocalDate disbursementDate = LocalDate.of(2023, 1, 1);
         LocalDate transactionDate = LocalDate.of(2023, 1, 5);
-        LoanTransaction loanTransaction = Mockito.mock(LoanTransaction.class);
+        LoanTransaction loanTransaction = mock(LoanTransaction.class);
         MonetaryCurrency currency = MONETARY_CURRENCY;
-        LoanCharge charge = Mockito.mock(LoanCharge.class);
-        LoanChargePaidBy chargePaidBy = Mockito.mock(LoanChargePaidBy.class);
+        LoanCharge charge = mock(LoanCharge.class);
+        LoanChargePaidBy chargePaidBy = mock(LoanChargePaidBy.class);
         Money overpaidAmount = Money.zero(currency);
         Money zero = Money.zero(currency);
-        Loan loan = Mockito.mock(Loan.class);
+        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),
@@ -138,13 +156,13 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
         BigDecimal chargeAmount = BigDecimal.valueOf(100.00);
         LocalDate disbursementDate = LocalDate.of(2023, 1, 1);
         LocalDate transactionDate = LocalDate.of(2023, 1, 5);
-        LoanTransaction loanTransaction = Mockito.mock(LoanTransaction.class);
+        LoanTransaction loanTransaction = mock(LoanTransaction.class);
         MonetaryCurrency currency = MONETARY_CURRENCY;
-        LoanCharge charge = Mockito.mock(LoanCharge.class);
-        LoanChargePaidBy chargePaidBy = Mockito.mock(LoanChargePaidBy.class);
+        LoanCharge charge = mock(LoanCharge.class);
+        LoanChargePaidBy chargePaidBy = mock(LoanChargePaidBy.class);
         Money overpaidAmount = Money.zero(currency);
         Money zero = Money.zero(currency);
-        Loan loan = Mockito.mock(Loan.class);
+        Loan loan = mock(Loan.class);
         Money chargeAmountMoney = Money.of(currency, chargeAmount);
         BigDecimal transactionAmount = BigDecimal.valueOf(20.00);
         Money transactionAmountMoney = Money.of(currency, transactionAmount);
@@ -184,18 +202,18 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
         BigDecimal chargeAmount = BigDecimal.valueOf(100.00);
         LocalDate disbursementDate = LocalDate.of(2023, 1, 1);
         LocalDate transactionDate = disbursementDate.plusMonths(1);
-        LoanTransaction loanTransaction = Mockito.mock(LoanTransaction.class);
+        LoanTransaction loanTransaction = mock(LoanTransaction.class);
         MonetaryCurrency currency = MONETARY_CURRENCY;
-        LoanCharge charge = Mockito.mock(LoanCharge.class);
-        LoanChargePaidBy chargePaidBy = Mockito.mock(LoanChargePaidBy.class);
+        LoanCharge charge = mock(LoanCharge.class);
+        LoanChargePaidBy chargePaidBy = mock(LoanChargePaidBy.class);
         Money overpaidAmount = Money.zero(currency);
         Money zero = Money.zero(currency);
-        Loan loan = Mockito.mock(Loan.class);
-        LoanProductRelatedDetail loanProductRelatedDetail = 
Mockito.mock(LoanProductRelatedDetail.class);
+        Loan loan = mock(Loan.class);
+        LoanProductRelatedDetail loanProductRelatedDetail = 
mock(LoanProductRelatedDetail.class);
         Money chargeAmountMoney = Money.of(currency, chargeAmount);
         BigDecimal transactionAmount = BigDecimal.valueOf(120.00);
         Money transactionAmountMoney = Money.of(currency, transactionAmount);
-        LoanPaymentAllocationRule loanPaymentAllocationRule = 
Mockito.mock(LoanPaymentAllocationRule.class);
+        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));
@@ -232,4 +250,207 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
         assertEquals(0, 
BigDecimal.valueOf(80).compareTo(installment.getPrincipalOutstanding(currency).getAmount()));
         Mockito.verify(loan, Mockito.times(1)).getPaymentAllocationRules();
     }
+
+    @Test
+    public void 
testProcessCreditTransactionWithAllocationRuleInterestAndPrincipal() {
+        // given
+        Loan loan = mock(Loan.class);
+        LoanCreditAllocationRule mockCreditAllocationRule = 
createMockCreditAllocationRule(INTEREST, PRINCIPAL, PENALTY, FEE);
+        
Mockito.when(loan.getCreditAllocationRules()).thenReturn(List.of(mockCreditAllocationRule));
+        LoanTransaction repayment = createRepayment(loan);
+        
lenient().when(loan.getLoanTransactions(any())).thenReturn(List.of(repayment));
+
+        LoanTransaction chargeBackTransaction = 
createChargebackTransaction(loan);
+        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
+        underTest.processCreditTransaction(chargeBackTransaction, 
overpaymentHolder, MONETARY_CURRENCY, installments);
+
+        // 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);
+        LoanCreditAllocationRule mockCreditAllocationRule = 
createMockCreditAllocationRule(PRINCIPAL, INTEREST, PENALTY, FEE);
+        
Mockito.when(loan.getCreditAllocationRules()).thenReturn(List.of(mockCreditAllocationRule));
+        LoanTransaction repayment = createRepayment(loan);
+        
lenient().when(loan.getLoanTransactions(any())).thenReturn(List.of(repayment));
+
+        LoanTransaction chargeBackTransaction = 
createChargebackTransaction(loan);
+        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
+        underTest.processCreditTransaction(chargeBackTransaction, 
overpaymentHolder, MONETARY_CURRENCY, installments);
+
+        // then
+        Mockito.verify(installment, Mockito.times(1)).addToCredits(new 
BigDecimal("25.00"));
+        Mockito.verify(installment, 
Mockito.times(1)).updateInterestCharged(new BigDecimal("15.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(10.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(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));
+    }
+
+    @Test
+    public void 
testProcessCreditTransactionWithAllocationRulePrincipalAndInterestWithAdditionalInstallment()
 {
+        // given
+        Loan loan = mock(Loan.class);
+        LoanCreditAllocationRule mockCreditAllocationRule = 
createMockCreditAllocationRule(PRINCIPAL, INTEREST, PENALTY, FEE);
+        
Mockito.when(loan.getCreditAllocationRules()).thenReturn(List.of(mockCreditAllocationRule));
+        LoanTransaction repayment = createRepayment(loan);
+        
lenient().when(loan.getLoanTransactions(any())).thenReturn(List.of(repayment));
+
+        LoanTransaction chargeBackTransaction = 
createChargebackTransaction(loan);
+        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);
+        installments.add(installment2);
+
+        // when
+        underTest.processCreditTransaction(chargeBackTransaction, 
overpaymentHolder, MONETARY_CURRENCY, installments);
+
+        // then
+        Mockito.verify(installment2, Mockito.times(1)).addToCredits(new 
BigDecimal("25.00"));
+        Mockito.verify(installment2, 
Mockito.times(1)).updateInterestCharged(new BigDecimal("15.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());
+        Assertions.assertEquals(LocalDate.of(2023, 1, 1), 
localDateArgumentCaptor.getValue());
+        assertEquals(0, 
moneyCaptor.getValue().getAmount().compareTo(BigDecimal.valueOf(10.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(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));
+    }
+
+    private LoanRepaymentScheduleInstallment createMockInstallment(LocalDate 
localDate, boolean isAdditional) {
+        LoanRepaymentScheduleInstallment installment = 
mock(LoanRepaymentScheduleInstallment.class);
+        lenient().when(installment.isAdditional()).thenReturn(isAdditional);
+        lenient().when(installment.getDueDate()).thenReturn(localDate);
+        Money interestCharged = Money.of(MONETARY_CURRENCY, BigDecimal.ZERO);
+        
lenient().when(installment.getInterestCharged(MONETARY_CURRENCY)).thenReturn(interestCharged);
+        return installment;
+    }
+
+    @NotNull
+    private LoanCreditAllocationRule 
createMockCreditAllocationRule(AllocationType... allocationTypes) {
+        LoanCreditAllocationRule mockCreditAllocationRule = 
mock(LoanCreditAllocationRule.class);
+        
lenient().when(mockCreditAllocationRule.getTransactionType()).thenReturn(CreditAllocationTransactionType.CHARGEBACK);
+        
lenient().when(mockCreditAllocationRule.getAllocationTypes()).thenReturn(Arrays.asList(allocationTypes));
+        return mockCreditAllocationRule;
+    }
+
+    private LoanTransaction createRepayment(Loan loan) {
+        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);
+        return repayment;
+    }
+
+    private LoanTransaction createChargebackTransaction(Loan loan) {
+        LoanTransaction chargeback = mock(LoanTransaction.class);
+        lenient().when(chargeback.isChargeback()).thenReturn(true);
+        lenient().when(chargeback.getTypeOf()).thenReturn(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.getTransactionDate()).thenReturn(LocalDate.of(2023, 
1, 1));
+        return chargeback;
+    }
+
+    @Test
+    public void calculateChargebackAllocationMap() {
+        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),
+                List.of(PRINCIPAL, INTEREST, FEE, PENALTY), currency);
+        verify(allocationMap(50.0, 0, 0, 0), result);
+
+        result = 
underTest.calculateChargebackAllocationMap(allocationMap(40.0, 100.0, 200.0, 
12.0), BigDecimal.valueOf(50.0),
+                List.of(PRINCIPAL, INTEREST, FEE, PENALTY), currency);
+        verify(allocationMap(40.0, 10, 0, 0), result);
+
+        result = 
underTest.calculateChargebackAllocationMap(allocationMap(40.0, 100.0, 200.0, 
12.0), BigDecimal.valueOf(50.0),
+                List.of(PRINCIPAL, FEE, PENALTY, INTEREST), currency);
+        verify(allocationMap(40.0, 0, 10, 0), result);
+
+        result = 
underTest.calculateChargebackAllocationMap(allocationMap(40.0, 100.0, 200.0, 
12.0), BigDecimal.valueOf(340.0),
+                List.of(PRINCIPAL, FEE, PENALTY, INTEREST), currency);
+        verify(allocationMap(40.0, 88.0, 200.0, 12.0), result);
+
+        result = 
underTest.calculateChargebackAllocationMap(allocationMap(40.0, 100.0, 200.0, 
12.0), BigDecimal.valueOf(352.0),
+                List.of(PRINCIPAL, FEE, PENALTY, INTEREST), currency);
+        verify(allocationMap(40.0, 100.0, 200.0, 12.0), result);
+    }
+
+    private void verify(Map<AllocationType, BigDecimal> 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);
+        });
+    }
+
+    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));
+        return allocationMap;
+    }
+
 }
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 a406ca770..1352ff0cb 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
@@ -61,6 +61,8 @@ import 
org.apache.fineract.client.models.PostLoansLoanIdChargesResponse;
 import org.apache.fineract.client.models.PostLoansLoanIdRequest;
 import org.apache.fineract.client.models.PostLoansLoanIdResponse;
 import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
+import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
+import 
org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest;
 import org.apache.fineract.client.models.PostLoansRequest;
 import org.apache.fineract.client.models.PostLoansResponse;
 import org.apache.fineract.client.util.CallFailedRuntimeException;
@@ -335,7 +337,7 @@ public abstract class BaseLoanIntegrationTest {
                         && Objects.equals(item.getPenaltyChargesPortion(), 
tr.penaltyPortion) //
                         && Objects.equals(item.getUnrecognizedIncomePortion(), 
tr.unrecognizedPortion) //
                 );
-                Assertions.assertTrue(found, "Required transaction  not found: 
" + tr);
+                Assertions.assertTrue(found, "Required transaction not found: 
" + tr);
             });
         }
     }
@@ -414,6 +416,14 @@ public abstract class BaseLoanIntegrationTest {
                             "%d. installment's fee charges due is different, 
expected: %.2f, actual: %.2f".formatted(i, feeAmount, feeDue));
                 }
 
+                Double penaltyAmount = installments[i].penaltyAmount;
+                Double penaltyDue = period.getPenaltyChargesDue();
+                if (penaltyAmount != null) {
+                    Assertions.assertEquals(penaltyAmount, penaltyDue,
+                            "%d. installment's penalty charges due is 
different, expected: %.2f, actual: %.2f".formatted(i, penaltyAmount,
+                                    penaltyDue));
+                }
+
                 Double outstandingAmount = 
installments[i].totalOutstandingAmount;
                 Double totalOutstanding = 
period.getTotalOutstandingForPeriod();
                 if (outstandingAmount != null) {
@@ -497,10 +507,17 @@ public abstract class BaseLoanIntegrationTest {
         return applyAndApproveLoan(clientId, loanProductId, 
loanDisbursementDate, amount, 1);
     }
 
-    protected void addRepaymentForLoan(Long loanId, Double amount, String 
date) {
+    protected Long addRepaymentForLoan(Long loanId, Double amount, String 
date) {
         String firstRepaymentUUID = UUID.randomUUID().toString();
-        loanTransactionHelper.makeLoanRepayment(loanId, new 
PostLoansLoanIdTransactionsRequest().dateFormat(DATETIME_PATTERN)
-                
.transactionDate(date).locale("en").transactionAmount(amount).externalId(firstRepaymentUUID));
+        PostLoansLoanIdTransactionsResponse response = 
loanTransactionHelper.makeLoanRepayment(loanId,
+                new 
PostLoansLoanIdTransactionsRequest().dateFormat(DATETIME_PATTERN).transactionDate(date).locale("en")
+                        
.transactionAmount(amount).externalId(firstRepaymentUUID));
+        return response.getResourceId();
+    }
+
+    protected void addChargebackForLoan(Long loanId, Long transactionId, 
Double amount) {
+        loanTransactionHelper.chargebackLoanTransaction(loanId, transactionId,
+                new 
PostLoansLoanIdTransactionsTransactionIdRequest().locale("en").transactionAmount(amount).paymentTypeId(1L));
     }
 
     protected PostChargesResponse createCharge(Double amount) {
@@ -538,17 +555,22 @@ public abstract class BaseLoanIntegrationTest {
     }
 
     protected Installment installment(double principalAmount, Boolean 
completed, String dueDate) {
-        return new Installment(principalAmount, null, null, null, completed, 
dueDate);
+        return new Installment(principalAmount, null, null, null, null, 
completed, dueDate);
     }
 
     protected Installment installment(double principalAmount, double 
interestAmount, double totalOutstandingAmount, Boolean completed,
             String dueDate) {
-        return new Installment(principalAmount, interestAmount, null, 
totalOutstandingAmount, completed, dueDate);
+        return new Installment(principalAmount, interestAmount, null, null, 
totalOutstandingAmount, completed, dueDate);
     }
 
     protected Installment installment(double principalAmount, double 
interestAmount, double feeAmount, double totalOutstandingAmount,
             Boolean completed, String dueDate) {
-        return new Installment(principalAmount, interestAmount, feeAmount, 
totalOutstandingAmount, completed, dueDate);
+        return new Installment(principalAmount, interestAmount, feeAmount, 
null, totalOutstandingAmount, completed, dueDate);
+    }
+
+    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);
     }
 
     protected BatchRequestBuilder batchRequest() {
@@ -655,6 +677,7 @@ public abstract class BaseLoanIntegrationTest {
         Double principalAmount;
         Double interestAmount;
         Double feeAmount;
+        Double penaltyAmount;
         Double totalOutstandingAmount;
         Boolean completed;
         String dueDate;
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
new file mode 100644
index 000000000..620a1bb0d
--- /dev/null
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargebackWithCreditAllocationsIntegrationTests.java
@@ -0,0 +1,418 @@
+/**
+ * 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.
+ */
+package org.apache.fineract.integrationtests;
+
+import java.math.BigDecimal;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import lombok.extern.slf4j.Slf4j;
+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.PaymentAllocationOrder;
+import org.apache.fineract.client.models.PostLoanProductsRequest;
+import org.apache.fineract.client.models.PostLoanProductsResponse;
+import org.apache.fineract.client.models.PostLoansLoanIdResponse;
+import org.apache.fineract.client.models.PostLoansRequest;
+import org.apache.fineract.client.models.PostLoansResponse;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import 
org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
+import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
+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.Disabled;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+@Slf4j
+@ExtendWith(LoanTestLifecycleExtension.class)
+public class LoanChargebackWithCreditAllocationsIntegrationTests extends 
BaseLoanIntegrationTest {
+
+    @Test
+    public void 
createLoanWithCreditAllocationAndChargebackPenaltyFeeInterestAndPrincipal() {
+        runAt("01 January 2023", () -> {
+            // Create Client
+            Long clientId = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+            // Create Loan Product
+            Long loanProductId = 
createLoanProduct(charbackAllocation("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", 
1037.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(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 
createLoanWithCreditAllocationAndChargebackPenaltyFeeInterestAndPrincipalOnNPlusOneInstallment()
 {
+        runAt("01 January 2023", () -> {
+            // Create Client
+            Long clientId = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+            // Create Loan Product
+            Long loanProductId = 
createLoanProduct(charbackAllocation("PENALTY", "FEE", "INTEREST", 
"PRINCIPAL"));
+
+            // Apply and Approve Loan
+            Long loanId = applyAndApproveLoan(clientId, loanProductId);
+
+            // Disburse Loan
+            disburseLoan(loanId, BigDecimal.valueOf(1250.0), "01 January 
2023");
+
+            verifyRepaymentSchedule(loanId, //
+                    installment(0, null, "01 January 2023"), //
+                    installment(313.0, 0, 0, 0, 313.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 + and make a full repayment for the first 
installment
+            updateBusinessDate("20 January 2023");
+            addRepaymentForLoan(loanId, 313.0, "20 January 2023");
+
+            // Update Business Date + and make a full repayment for the second 
installment
+            updateBusinessDate("20 February 2023");
+            addRepaymentForLoan(loanId, 313.0, "20 February 2023");
+
+            // Update Business Date + and make a full repayment for the third 
installment
+            updateBusinessDate("20 March 2023");
+            addRepaymentForLoan(loanId, 313.0, "20 March 2023");
+
+            // Add some charges Update Business Date + and make a full 
repayment for the fourth installment
+            updateBusinessDate("20 April 2023");
+            addCharge(loanId, false, 50, "20 April 2023");
+            addCharge(loanId, true, 20, "20 April 2023");
+            Long repaymentTransaction = addRepaymentForLoan(loanId, 381.0, "20 
April 2023");
+
+            verifyRepaymentSchedule(loanId, //
+                    installment(0, null, "01 January 2023"), //
+                    installment(313.0, 0, 0, 0, 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, 50, 20, 0.0, true, "01 May 2023") //
+            );
+
+            // Let's move over the maturity date and chargeback some money
+            updateBusinessDate("02 May 2023");
+
+            // Add Chargeback, 20 penalty + 50 fee + 0 interest + 30 principal
+            addChargebackForLoan(loanId, repaymentTransaction, 100.0);
+
+            verifyTransactions(loanId, //
+                    transaction(1250.0, "Disbursement", "01 January 2023", 
1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(313.0, "Repayment", "20 January 2023", 937.0, 
313.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(313.0, "Repayment", "20 February 2023", 624.0, 
313.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    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) //
+            );
+
+            verifyRepaymentSchedule(loanId, //
+                    installment(0, null, "01 January 2023"), //
+                    installment(313.0, 0, 0, 0, 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, 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
+            );
+        });
+    }
+
+    @Test
+    @Disabled
+    public void 
createLoanWithCreditAllocationAndChargebackReverseReplayWithBackdatedPayment() {
+        runAt("01 January 2023", () -> {
+            // Create Client
+            Long clientId = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+            // Create Loan Product
+            Long loanProductId = 
createLoanProduct(charbackAllocation("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") //
+            );
+
+            updateBusinessDate("21 January 2023");
+
+            // Add Chargeback20 penalty + 50 fee + 0 interest + 30 principal
+            addChargebackForLoan(loanId, repaymentTransaction, 100.0);
+
+            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) //
+            );
+
+            // 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(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 a backdated repayment on 19th of January reverse 
replaying the chargeback
+            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", "21 January 2023", 937.0, 
100.0, 0.0, 0.0, 0.0, 0.0, 0.0) //
+            );
+        });
+    }
+
+    @Test
+    public void 
createLoanWithCreditAllocationAndChargebackPrincipalInterestFeePenalty() {
+        runAt("01 January 2023", () -> {
+            // Create Client
+            Long clientId = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+            // Create Loan Product
+            Long loanProductId = 
createLoanProduct(charbackAllocation("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, 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); // 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(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, 100.0, 0.0, 0.0, 0.0, 0.0, 0.0) //
+            );
+
+            // 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(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") //
+            );
+        });
+    }
+
+    @Nullable
+    private Long applyAndApproveLoan(Long clientId, Long loanProductId) {
+        PostLoansRequest applicationRequest = applyLoanRequest(clientId, 
loanProductId, "01 January 2023", 1250.0, 4)//
+                .repaymentEvery(1)//
+                .loanTermFrequency(4)//
+                .repaymentFrequencyType(RepaymentFrequencyType.MONTHS)//
+                .loanTermFrequencyType(RepaymentFrequencyType.MONTHS)//
+                
.transactionProcessingStrategyCode("advanced-payment-allocation-strategy");
+
+        PostLoansResponse postLoansResponse = 
loanTransactionHelper.applyLoan(applicationRequest);
+
+        PostLoansLoanIdResponse approvedLoanResult = 
loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(),
+                approveLoanRequest(1250.0, "01 January 2023"));
+
+        Long loanId = approvedLoanResult.getLoanId();
+        return loanId;
+    }
+
+    public Long createLoanProduct(CreditAllocationData... 
creditAllocationData) {
+        PostLoanProductsRequest postLoanProductsRequest = 
loanProductWithAdvancedPaymentAllocationWith4Installments(creditAllocationData);
+        PostLoanProductsResponse loanProductResponse = 
loanProductHelper.createLoanProduct(postLoanProductsRequest);
+        return loanProductResponse.getResourceId();
+    }
+
+    private PostLoanProductsRequest 
loanProductWithAdvancedPaymentAllocationWith4Installments(
+            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));
+    }
+
+    private AdvancedPaymentData createDefaultPaymentAllocation() {
+        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_PENALTY, 
PaymentAllocationType.DUE_FEE, PaymentAllocationType.DUE_PRINCIPAL,
+                PaymentAllocationType.DUE_INTEREST, 
PaymentAllocationType.IN_ADVANCE_PENALTY, PaymentAllocationType.IN_ADVANCE_FEE,
+                PaymentAllocationType.IN_ADVANCE_PRINCIPAL, 
PaymentAllocationType.IN_ADVANCE_INTEREST);
+
+        advancedPaymentData.setPaymentAllocationOrder(paymentAllocationOrders);
+        return advancedPaymentData;
+    }
+
+    private AdvancedPaymentData createRepaymentPaymentAllocation() {
+        AdvancedPaymentData advancedPaymentData = new AdvancedPaymentData();
+        advancedPaymentData.setTransactionType("REPAYMENT");
+        
advancedPaymentData.setFutureInstallmentAllocationRule("NEXT_INSTALLMENT");
+
+        List<PaymentAllocationOrder> paymentAllocationOrders = 
getPaymentAllocationOrder(PaymentAllocationType.PAST_DUE_PENALTY,
+                PaymentAllocationType.PAST_DUE_FEE, 
PaymentAllocationType.PAST_DUE_INTEREST, 
PaymentAllocationType.PAST_DUE_PRINCIPAL,
+                PaymentAllocationType.DUE_PENALTY, 
PaymentAllocationType.DUE_FEE, PaymentAllocationType.DUE_INTEREST,
+                PaymentAllocationType.DUE_PRINCIPAL, 
PaymentAllocationType.IN_ADVANCE_PENALTY, PaymentAllocationType.IN_ADVANCE_FEE,
+                PaymentAllocationType.IN_ADVANCE_PRINCIPAL, 
PaymentAllocationType.IN_ADVANCE_INTEREST);
+
+        advancedPaymentData.setPaymentAllocationOrder(paymentAllocationOrders);
+        return advancedPaymentData;
+    }
+
+    private CreditAllocationData charbackAllocation(String... allocationRules) 
{
+        CreditAllocationData creditAllocationData = new CreditAllocationData();
+        creditAllocationData.setTransactionType("CHARGEBACK");
+        
creditAllocationData.setCreditAllocationOrder(createCreditAllocationOrders(allocationRules));
+        return creditAllocationData;
+    }
+
+    public List<CreditAllocationOrder> createCreditAllocationOrders(String... 
allocationRule) {
+        AtomicInteger integer = new AtomicInteger(1);
+        return Arrays.stream(allocationRule).map(allocation -> {
+            CreditAllocationOrder creditAllocationOrder = new 
CreditAllocationOrder();
+            creditAllocationOrder.setCreditAllocationRule(allocation);
+            creditAllocationOrder.setOrder(integer.getAndIncrement());
+            return creditAllocationOrder;
+        }).toList();
+    }
+
+    private List<PaymentAllocationOrder> 
getPaymentAllocationOrder(PaymentAllocationType... paymentAllocationTypes) {
+        AtomicInteger integer = new AtomicInteger(1);
+        return Arrays.stream(paymentAllocationTypes).map(pat -> {
+            PaymentAllocationOrder paymentAllocationOrder = new 
PaymentAllocationOrder();
+            paymentAllocationOrder.setPaymentAllocationRule(pat.name());
+            paymentAllocationOrder.setOrder(integer.getAndIncrement());
+            return paymentAllocationOrder;
+        }).toList();
+    }
+
+}
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
index 3d3de0628..f34191726 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
@@ -744,6 +744,11 @@ public class LoanTransactionHelper extends IntegrationTest 
{
         return ok(fineract().loanTransactions.adjustLoanTransaction1(loanId, 
transactionExternalId, request, "undo"));
     }
 
+    public PostLoansLoanIdTransactionsResponse chargebackLoanTransaction(final 
Long loanId, final Long transactionId,
+            final PostLoansLoanIdTransactionsTransactionIdRequest request) {
+        return ok(fineract().loanTransactions.adjustLoanTransaction(loanId, 
transactionId, request, "chargeback"));
+    }
+
     public PostLoansLoanIdTransactionsResponse chargebackLoanTransaction(final 
String loanExternalId, final Long transactionId,
             final PostLoansLoanIdTransactionsTransactionIdRequest request) {
         return 
ok(fineract().loanTransactions.adjustLoanTransaction2(loanExternalId, 
transactionId, request, "chargeback"));

Reply via email to