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

adamsaghy 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 379983541 [FINERACT-1968] Refund for active loan transaction
379983541 is described below

commit 379983541469573dfcb8e0b9335f19961998e041
Author: taskain7 <[email protected]>
AuthorDate: Fri Nov 10 00:39:03 2023 +0100

    [FINERACT-1968] Refund for active loan transaction
---
 .../domain/PaymentAllocationTransactionType.java   |   5 +-
 ...dvancedPaymentScheduleTransactionProcessor.java | 167 +++++++++-
 ...tiveLoansWithAdvancedPaymentAllocationTest.java | 340 +++++++++++++++++++++
 3 files changed, 504 insertions(+), 8 deletions(-)

diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/PaymentAllocationTransactionType.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/PaymentAllocationTransactionType.java
index 85b3d865d..014b04214 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/PaymentAllocationTransactionType.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/PaymentAllocationTransactionType.java
@@ -37,8 +37,9 @@ public enum PaymentAllocationTransactionType {
     GOODWILL_CREDIT(LoanTransactionType.GOODWILL_CREDIT, "Goodwill credit"), //
     CHARGE_REFUND(LoanTransactionType.CHARGE_REFUND, "Charge refund"), //
     CHARGE_ADJUSTMENT(LoanTransactionType.CHARGE_ADJUSTMENT, "Charge 
adjustment"), //
-    WAIVE_INTEREST(LoanTransactionType.WAIVE_INTEREST, "Waive interest"), 
CHARGE_PAYMENT(LoanTransactionType.CHARGE_PAYMENT,
-            "Charge payment");//
+    WAIVE_INTEREST(LoanTransactionType.WAIVE_INTEREST, "Waive interest"), //
+    CHARGE_PAYMENT(LoanTransactionType.CHARGE_PAYMENT, "Charge payment"), //
+    REFUND_FOR_ACTIVE_LOAN(LoanTransactionType.REFUND_FOR_ACTIVE_LOAN, "Refund 
for active loan");
 
     private final LoanTransactionType loanTransactionType;
     private final String humanReadableName;
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
index aeca0d77d..709e14f2b 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
@@ -25,6 +25,7 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
+import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
 import lombok.AllArgsConstructor;
@@ -97,6 +98,12 @@ public class AdvancedPaymentScheduleTransactionProcessor 
extends AbstractLoanRep
         throw new NotImplementedException();
     }
 
+    @Override
+    public Money handleRepaymentSchedule(List<LoanTransaction> 
transactionsPostDisbursement, MonetaryCurrency currency,
+            List<LoanRepaymentScheduleInstallment> installments, 
Set<LoanCharge> loanCharges) {
+        throw new NotImplementedException();
+    }
+
     private void processSingleTransaction(LoanTransaction loanTransaction, 
MonetaryCurrency currency,
             List<LoanRepaymentScheduleInstallment> installments, 
Set<LoanCharge> charges,
             ChangedTransactionDetail changedTransactionDetail) {
@@ -240,12 +247,6 @@ public class AdvancedPaymentScheduleTransactionProcessor 
extends AbstractLoanRep
                 .addToPrincipal(disbursementTransaction.getTransactionDate(), 
remainingAmount);
     }
 
-    @Override
-    public Money handleRepaymentSchedule(List<LoanTransaction> 
transactionsPostDisbursement, MonetaryCurrency currency,
-            List<LoanRepaymentScheduleInstallment> installments, 
Set<LoanCharge> loanCharges) {
-        throw new NotImplementedException();
-    }
-
     private void handleRepayment(LoanTransaction loanTransaction, 
MonetaryCurrency currency,
             List<LoanRepaymentScheduleInstallment> installments, 
Set<LoanCharge> charges) {
         if (loanTransaction.isRepaymentLikeType() || 
loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) {
@@ -303,6 +304,39 @@ public class AdvancedPaymentScheduleTransactionProcessor 
extends AbstractLoanRep
         };
     }
 
+    private Money unpayAllocation(PaymentAllocationType paymentAllocationType, 
LoanRepaymentScheduleInstallment currentInstallment,
+            LoanTransaction loanTransaction, Money 
transactionAmountUnprocessed,
+            LoanTransactionToRepaymentScheduleMapping 
loanTransactionToRepaymentScheduleMapping, Balances balances) {
+        LocalDate transactionDate = loanTransaction.getTransactionDate();
+        Money zero = transactionAmountUnprocessed.zero();
+        return switch (paymentAllocationType.getAllocationType()) {
+            case PENALTY -> {
+                Money unpaidPenaltyPortion = 
currentInstallment.unpayPenaltyChargesComponent(transactionDate, 
transactionAmountUnprocessed);
+                
balances.setAggregatedPenaltyChargesPortion(balances.getAggregatedPenaltyChargesPortion().minus(unpaidPenaltyPortion));
+                
addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, zero, zero, 
zero, unpaidPenaltyPortion);
+                yield unpaidPenaltyPortion;
+            }
+            case FEE -> {
+                Money unpaidFeePortion = 
currentInstallment.unpayFeeChargesComponent(transactionDate, 
transactionAmountUnprocessed);
+                
balances.setAggregatedFeeChargesPortion(balances.getAggregatedFeeChargesPortion().minus(unpaidFeePortion));
+                
addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, zero, zero, 
unpaidFeePortion, zero);
+                yield unpaidFeePortion;
+            }
+            case INTEREST -> {
+                Money unpaidInterestPortion = 
currentInstallment.unpayInterestComponent(transactionDate, 
transactionAmountUnprocessed);
+                
balances.setAggregatedInterestPortion(balances.getAggregatedInterestPortion().minus(unpaidInterestPortion));
+                
addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, zero, 
unpaidInterestPortion, zero, zero);
+                yield unpaidInterestPortion;
+            }
+            case PRINCIPAL -> {
+                Money unpaidPrincipalPortion = 
currentInstallment.unpayPrincipalComponent(transactionDate, 
transactionAmountUnprocessed);
+                
balances.setAggregatedPrincipalPortion(balances.getAggregatedPrincipalPortion().minus(unpaidPrincipalPortion));
+                
addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, 
unpaidPrincipalPortion, zero, zero, zero);
+                yield unpaidPrincipalPortion;
+            }
+        };
+    }
+
     private void 
addToTransactionMapping(LoanTransactionToRepaymentScheduleMapping 
loanTransactionToRepaymentScheduleMapping,
             Money principalPortion, Money interestPortion, Money feePortion, 
Money penaltyPortion) {
         BigDecimal aggregatedPenalty = ObjectUtils
@@ -397,6 +431,127 @@ public class AdvancedPaymentScheduleTransactionProcessor 
extends AbstractLoanRep
         }
     }
 
+    @Override
+    protected void handleRefund(LoanTransaction loanTransaction, 
MonetaryCurrency currency,
+            List<LoanRepaymentScheduleInstallment> installments, 
Set<LoanCharge> charges) {
+        Money zero = Money.zero(currency);
+        List<LoanTransactionToRepaymentScheduleMapping> transactionMappings = 
new ArrayList<>();
+        Money transactionAmountUnprocessed = 
loanTransaction.getAmount(currency);
+
+        List<LoanPaymentAllocationRule> paymentAllocationRules = 
loanTransaction.getLoan().getPaymentAllocationRules();
+        LoanPaymentAllocationRule defaultPaymentAllocationRule = 
paymentAllocationRules.stream()
+                .filter(e -> 
PaymentAllocationTransactionType.DEFAULT.equals(e.getTransactionType())).findFirst().orElseThrow();
+        Optional<LoanPaymentAllocationRule> paymentAllocationRuleOptional = 
paymentAllocationRules.stream()
+                .filter(e -> 
loanTransaction.getTypeOf().equals(e.getTransactionType().getLoanTransactionType())).findFirst();
+        Balances balances = new Balances(zero, zero, zero, zero);
+        if (paymentAllocationRuleOptional.isPresent()) {
+            LoanPaymentAllocationRule paymentAllocationRule = 
paymentAllocationRuleOptional.get();
+            List<PaymentAllocationType> paymentAllocationTypes = 
paymentAllocationRule.getAllocationTypes();
+            FutureInstallmentAllocationRule futureInstallmentAllocationRule = 
paymentAllocationRule.getFutureInstallmentAllocationRule();
+            for (PaymentAllocationType paymentAllocationType : 
paymentAllocationTypes) {
+                transactionAmountUnprocessed = 
refundTransaction(loanTransaction, currency, installments, zero, 
transactionMappings,
+                        transactionAmountUnprocessed, 
futureInstallmentAllocationRule, balances, paymentAllocationType);
+                if (!transactionAmountUnprocessed.isGreaterThanZero()) {
+                    break;
+                }
+            }
+        } else {
+            // if the allocation rule is not defined then the reverse order of 
the default allocation rule will be used
+            LoanPaymentAllocationRule paymentAllocationRule = 
defaultPaymentAllocationRule;
+            List<PaymentAllocationType> paymentAllocationTypes = 
paymentAllocationRule.getAllocationTypes();
+            FutureInstallmentAllocationRule futureInstallmentAllocationRule = 
FutureInstallmentAllocationRule.LAST_INSTALLMENT;
+            for (int i = paymentAllocationTypes.size() - 1; i >= 0; i--) {
+                PaymentAllocationType paymentAllocationType = 
paymentAllocationTypes.get(i);
+                transactionAmountUnprocessed = 
refundTransaction(loanTransaction, currency, installments, zero, 
transactionMappings,
+                        transactionAmountUnprocessed, 
futureInstallmentAllocationRule, balances, paymentAllocationType);
+                if (!transactionAmountUnprocessed.isGreaterThanZero()) {
+                    break;
+                }
+            }
+        }
+        
loanTransaction.updateComponents(balances.getAggregatedPrincipalPortion(), 
balances.getAggregatedInterestPortion(),
+                balances.getAggregatedFeeChargesPortion(), 
balances.getAggregatedPenaltyChargesPortion());
+        
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings);
+    }
+
+    private Money refundTransaction(LoanTransaction loanTransaction, 
MonetaryCurrency currency,
+            List<LoanRepaymentScheduleInstallment> installments, Money zero,
+            List<LoanTransactionToRepaymentScheduleMapping> 
transactionMappings, Money transactionAmountUnprocessed,
+            FutureInstallmentAllocationRule futureInstallmentAllocationRule, 
Balances balances,
+            PaymentAllocationType paymentAllocationType) {
+        LoanRepaymentScheduleInstallment currentInstallment = null;
+        Money refundedPortion = zero;
+        do {
+            switch (paymentAllocationType.getDueType()) {
+                case PAST_DUE -> {
+                    currentInstallment = 
installments.stream().filter(installment -> 
installment.getTotalPaid(currency).isGreaterThan(zero))
+                            .filter(e -> 
loanTransaction.isAfter(e.getDueDate()))
+                            
.max(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).orElse(null);
+                    if (currentInstallment != null) {
+                        LoanTransactionToRepaymentScheduleMapping 
loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
+                                transactionMappings, loanTransaction, 
currentInstallment, currency);
+                        refundedPortion = 
unpayAllocation(paymentAllocationType, currentInstallment, loanTransaction,
+                                transactionAmountUnprocessed, 
loanTransactionToRepaymentScheduleMapping, balances);
+                        transactionAmountUnprocessed = 
transactionAmountUnprocessed.minus(refundedPortion);
+                    }
+                }
+                case DUE -> {
+                    currentInstallment = 
installments.stream().filter(installment -> 
installment.getTotalPaid(currency).isGreaterThan(zero))
+                            .filter(installment -> 
loanTransaction.isOn(installment.getDueDate()))
+                            
.max(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).orElse(null);
+                    if (currentInstallment != null) {
+                        LoanTransactionToRepaymentScheduleMapping 
loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
+                                transactionMappings, loanTransaction, 
currentInstallment, currency);
+                        refundedPortion = 
unpayAllocation(paymentAllocationType, currentInstallment, loanTransaction,
+                                transactionAmountUnprocessed, 
loanTransactionToRepaymentScheduleMapping, balances);
+                        transactionAmountUnprocessed = 
transactionAmountUnprocessed.minus(refundedPortion);
+                    }
+                }
+                case IN_ADVANCE -> {
+                    List<LoanRepaymentScheduleInstallment> currentInstallments 
= new ArrayList<>();
+                    if 
(FutureInstallmentAllocationRule.REAMORTIZATION.equals(futureInstallmentAllocationRule))
 {
+                        currentInstallments = installments.stream()
+                                .filter(installment -> 
installment.getTotalPaid(currency).isGreaterThan(zero))
+                                .filter(e -> 
loanTransaction.isBefore(e.getDueDate())).toList();
+                    } else if 
(FutureInstallmentAllocationRule.NEXT_INSTALLMENT.equals(futureInstallmentAllocationRule))
 {
+                        currentInstallments = installments.stream()
+                                .filter(installment -> 
installment.getTotalPaid(currency).isGreaterThan(zero))
+                                .filter(e -> 
loanTransaction.isBefore(e.getDueDate()))
+                                
.min(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).stream().toList();
+                    } else if 
(FutureInstallmentAllocationRule.LAST_INSTALLMENT.equals(futureInstallmentAllocationRule))
 {
+                        currentInstallments = installments.stream()
+                                .filter(installment -> 
installment.getTotalPaid(currency).isGreaterThan(zero))
+                                .filter(e -> 
loanTransaction.isBefore(e.getDueDate()))
+                                
.max(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).stream().toList();
+                    }
+                    int numberOfInstallments = currentInstallments.size();
+                    refundedPortion = zero;
+                    if (numberOfInstallments > 0) {
+                        Money evenPortion = 
transactionAmountUnprocessed.dividedBy(numberOfInstallments, 
MoneyHelper.getRoundingMode());
+                        Money balanceAdjustment = 
transactionAmountUnprocessed.minus(evenPortion.multipliedBy(numberOfInstallments));
+                        for (LoanRepaymentScheduleInstallment 
internalCurrentInstallment : currentInstallments) {
+                            currentInstallment = internalCurrentInstallment;
+                            if 
(internalCurrentInstallment.equals(currentInstallments.get(numberOfInstallments 
- 1))) {
+                                evenPortion = 
evenPortion.add(balanceAdjustment);
+                            }
+                            LoanTransactionToRepaymentScheduleMapping 
loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
+                                    transactionMappings, loanTransaction, 
currentInstallment, currency);
+                            Money internalUnpaidPortion = 
unpayAllocation(paymentAllocationType, currentInstallment, loanTransaction,
+                                    evenPortion, 
loanTransactionToRepaymentScheduleMapping, balances);
+                            if (internalUnpaidPortion.isGreaterThanZero()) {
+                                refundedPortion = internalUnpaidPortion;
+                            }
+                            transactionAmountUnprocessed = 
transactionAmountUnprocessed.minus(internalUnpaidPortion);
+                        }
+                    } else {
+                        currentInstallment = null;
+                    }
+                }
+            }
+        } while (currentInstallment != null && 
transactionAmountUnprocessed.isGreaterThanZero() && 
refundedPortion.isGreaterThanZero());
+        return transactionAmountUnprocessed;
+    }
+
     private void processTransaction(LoanTransaction loanTransaction, 
MonetaryCurrency currency,
             List<LoanRepaymentScheduleInstallment> installments, Money 
transactionAmountUnprocessed, Set<LoanCharge> charges) {
         Money zero = Money.zero(currency);
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/RefundForActiveLoansWithAdvancedPaymentAllocationTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/RefundForActiveLoansWithAdvancedPaymentAllocationTest.java
new file mode 100644
index 000000000..cbe683a6e
--- /dev/null
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/RefundForActiveLoansWithAdvancedPaymentAllocationTest.java
@@ -0,0 +1,340 @@
+/**
+ * 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 static org.junit.jupiter.api.Assertions.assertEquals;
+
+import io.restassured.builder.RequestSpecBuilder;
+import io.restassured.builder.ResponseSpecBuilder;
+import io.restassured.http.ContentType;
+import io.restassured.specification.RequestSpecification;
+import io.restassured.specification.ResponseSpecification;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
+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.BusinessDateRequest;
+import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod;
+import org.apache.fineract.client.models.GetLoansLoanIdResponse;
+import org.apache.fineract.client.models.PaymentAllocationOrder;
+import org.apache.fineract.client.models.PostClientsResponse;
+import org.apache.fineract.client.models.PostLoansLoanIdRequest;
+import org.apache.fineract.client.models.PostLoansRequest;
+import org.apache.fineract.client.models.PostLoansResponse;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
+import org.apache.fineract.integrationtests.common.BusinessDateHelper;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.accounting.Account;
+import org.apache.fineract.integrationtests.common.accounting.AccountHelper;
+import org.apache.fineract.integrationtests.common.charges.ChargesHelper;
+import 
org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
+import 
org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import 
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
+import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+@ExtendWith(LoanTestLifecycleExtension.class)
+@Slf4j
+public class RefundForActiveLoansWithAdvancedPaymentAllocationTest {
+
+    private static final String DATETIME_PATTERN = "dd MMMM yyyy";
+    private static final DateTimeFormatter DATE_FORMATTER = new 
DateTimeFormatterBuilder().appendPattern(DATETIME_PATTERN).toFormatter();
+    private static RequestSpecification requestSpec;
+    private static ResponseSpecification responseSpec;
+    private static LoanTransactionHelper loanTransactionHelper;
+    private static PostClientsResponse client;
+    private static BusinessDateHelper businessDateHelper;
+    private static AccountHelper accountHelper;
+
+    @BeforeAll
+    public static void setup() {
+        Utils.initializeRESTAssured();
+        ClientHelper clientHelper = new ClientHelper(requestSpec, 
responseSpec);
+        requestSpec = new 
RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        requestSpec.header("Authorization", "Basic " + 
Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        requestSpec.header("Fineract-Platform-TenantId", "default");
+        responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+
+        loanTransactionHelper = new LoanTransactionHelper(requestSpec, 
responseSpec);
+        client = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
+        businessDateHelper = new BusinessDateHelper();
+        accountHelper = new AccountHelper(requestSpec, responseSpec);
+    }
+
+    @Test
+    public void refundForActiveLoanWithDefaultPaymentAllocation() {
+        try {
+
+            GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, 
responseSpec, Boolean.TRUE);
+            businessDateHelper.updateBusinessDate(new 
BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
+                    .date("2023.02.15").dateFormat("yyyy.MM.dd").locale("en"));
+
+            final Account assetAccount = accountHelper.createAssetAccount();
+            final Account incomeAccount = accountHelper.createIncomeAccount();
+            final Account expenseAccount = 
accountHelper.createExpenseAccount();
+            final Account overpaymentAccount = 
accountHelper.createLiabilityAccount();
+
+            Integer loanProductId = createLoanProduct("1000", "30", "4", 
assetAccount, incomeAccount, expenseAccount, overpaymentAccount);
+
+            final PostLoansResponse loanResponse = 
applyForLoanApplication(client.getClientId(), loanProductId, 1000L, 90, 30, 3, 
0,
+                    "01 January 2023", "01 January 2023");
+
+            int loanId = loanResponse.getLoanId().intValue();
+
+            loanTransactionHelper.approveLoan(loanResponse.getLoanId(),
+                    new 
PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)).dateFormat(DATETIME_PATTERN)
+                            .approvedOnDate("01 January 2023").locale("en"));
+
+            loanTransactionHelper.disburseLoan(loanResponse.getLoanId(),
+                    new PostLoansLoanIdRequest().actualDisbursementDate("01 
January 2023").dateFormat(DATETIME_PATTERN)
+                            
.transactionAmount(BigDecimal.valueOf(1000.00)).locale("en"));
+
+            final float feePortion = 50.0f;
+            final float penaltyPortion = 100.0f;
+
+            Integer fee = ChargesHelper.createCharges(requestSpec, 
responseSpec, ChargesHelper
+                    
.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, 
String.valueOf(feePortion), false));
+
+            Integer penalty = ChargesHelper.createCharges(requestSpec, 
responseSpec, ChargesHelper
+                    
.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, 
String.valueOf(penaltyPortion), true));
+
+            final String firstInstallmentChargeAddedDate = 
DATE_FORMATTER.format(LocalDate.of(2023, 1, 3));
+            loanTransactionHelper.addChargesForLoan(loanId, 
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(
+                    String.valueOf(fee), firstInstallmentChargeAddedDate, 
String.valueOf(feePortion)));
+
+            loanTransactionHelper.addChargesForLoan(loanId, 
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(
+                    String.valueOf(penalty), firstInstallmentChargeAddedDate, 
String.valueOf(penaltyPortion)));
+
+            final String secondInstallmentChargeAddedDate = 
DATE_FORMATTER.format(LocalDate.of(2023, 2, 3));
+            loanTransactionHelper.addChargesForLoan(loanId, 
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(
+                    String.valueOf(fee), secondInstallmentChargeAddedDate, 
String.valueOf(feePortion)));
+
+            loanTransactionHelper.addChargesForLoan(loanId, 
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(
+                    String.valueOf(penalty), secondInstallmentChargeAddedDate, 
String.valueOf(penaltyPortion)));
+
+            GetLoansLoanIdResponse loanDetails = 
loanTransactionHelper.getLoanDetails((long) loanId);
+
+            GetLoansLoanIdRepaymentPeriod firstRepaymentInstallment = 
loanDetails.getRepaymentSchedule().getPeriods().get(2);
+            GetLoansLoanIdRepaymentPeriod secondRepaymentInstallment = 
loanDetails.getRepaymentSchedule().getPeriods().get(3);
+            GetLoansLoanIdRepaymentPeriod thirdRepaymentInstallment = 
loanDetails.getRepaymentSchedule().getPeriods().get(4);
+
+            assertEquals(5, 
loanDetails.getRepaymentSchedule().getPeriods().size());
+            assertEquals(feePortion, 
firstRepaymentInstallment.getFeeChargesDue());
+            assertEquals(feePortion, 
firstRepaymentInstallment.getFeeChargesOutstanding());
+            assertEquals(penaltyPortion, 
firstRepaymentInstallment.getPenaltyChargesDue());
+            assertEquals(penaltyPortion, 
firstRepaymentInstallment.getPenaltyChargesOutstanding());
+            assertEquals(250.0f, firstRepaymentInstallment.getPrincipalDue());
+            assertEquals(250.0f, 
firstRepaymentInstallment.getPrincipalOutstanding());
+            assertEquals(400.0f, 
firstRepaymentInstallment.getTotalDueForPeriod());
+            assertEquals(400.0f, 
firstRepaymentInstallment.getTotalOutstandingForPeriod());
+            assertEquals(LocalDate.of(2023, 1, 31), 
firstRepaymentInstallment.getDueDate());
+
+            assertEquals(feePortion, 
secondRepaymentInstallment.getFeeChargesDue());
+            assertEquals(feePortion, 
secondRepaymentInstallment.getFeeChargesOutstanding());
+            assertEquals(penaltyPortion, 
secondRepaymentInstallment.getPenaltyChargesDue());
+            assertEquals(penaltyPortion, 
secondRepaymentInstallment.getPenaltyChargesOutstanding());
+            assertEquals(400.0f, 
secondRepaymentInstallment.getTotalDueForPeriod());
+            assertEquals(400.0f, 
secondRepaymentInstallment.getTotalOutstandingForPeriod());
+            assertEquals(LocalDate.of(2023, 3, 2), 
secondRepaymentInstallment.getDueDate());
+
+            assertEquals(0.0f, thirdRepaymentInstallment.getFeeChargesDue());
+            assertEquals(0.0f, 
thirdRepaymentInstallment.getFeeChargesOutstanding());
+            assertEquals(0.0f, 
thirdRepaymentInstallment.getPenaltyChargesDue());
+            assertEquals(0.0f, 
thirdRepaymentInstallment.getPenaltyChargesOutstanding());
+            assertEquals(250.0f, 
thirdRepaymentInstallment.getTotalDueForPeriod());
+            assertEquals(250.0f, 
thirdRepaymentInstallment.getTotalOutstandingForPeriod());
+            assertEquals(LocalDate.of(2023, 4, 1), 
thirdRepaymentInstallment.getDueDate());
+
+            businessDateHelper.updateBusinessDate(new 
BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
+                    .date("2023.03.01").dateFormat("yyyy.MM.dd").locale("en"));
+            loanTransactionHelper.makeRepayment("01 March 2023", 810.0f, 
loanId);
+
+            loanDetails = loanTransactionHelper.getLoanDetails((long) loanId);
+
+            firstRepaymentInstallment = 
loanDetails.getRepaymentSchedule().getPeriods().get(2);
+            secondRepaymentInstallment = 
loanDetails.getRepaymentSchedule().getPeriods().get(3);
+            thirdRepaymentInstallment = 
loanDetails.getRepaymentSchedule().getPeriods().get(4);
+
+            assertEquals(5, 
loanDetails.getRepaymentSchedule().getPeriods().size());
+            assertEquals(feePortion, 
firstRepaymentInstallment.getFeeChargesDue());
+            assertEquals(0.0f, 
firstRepaymentInstallment.getFeeChargesOutstanding());
+            assertEquals(penaltyPortion, 
firstRepaymentInstallment.getPenaltyChargesDue());
+            assertEquals(0.0f, 
firstRepaymentInstallment.getPenaltyChargesOutstanding());
+            assertEquals(250.0f, firstRepaymentInstallment.getPrincipalDue());
+            assertEquals(0.0f, 
firstRepaymentInstallment.getPrincipalOutstanding());
+            assertEquals(400.0f, 
firstRepaymentInstallment.getTotalDueForPeriod());
+            assertEquals(0.0f, 
firstRepaymentInstallment.getTotalOutstandingForPeriod());
+            assertEquals(LocalDate.of(2023, 1, 31), 
firstRepaymentInstallment.getDueDate());
+
+            assertEquals(feePortion, 
secondRepaymentInstallment.getFeeChargesDue());
+            assertEquals(0.0f, 
secondRepaymentInstallment.getFeeChargesOutstanding());
+            assertEquals(penaltyPortion, 
secondRepaymentInstallment.getPenaltyChargesDue());
+            assertEquals(0.0f, 
secondRepaymentInstallment.getPenaltyChargesOutstanding());
+            assertEquals(400.0f, 
secondRepaymentInstallment.getTotalDueForPeriod());
+            assertEquals(0.0f, 
secondRepaymentInstallment.getTotalOutstandingForPeriod());
+            assertEquals(LocalDate.of(2023, 3, 2), 
secondRepaymentInstallment.getDueDate());
+
+            assertEquals(0.0f, thirdRepaymentInstallment.getFeeChargesDue());
+            assertEquals(0.0f, 
thirdRepaymentInstallment.getFeeChargesOutstanding());
+            assertEquals(0.0f, 
thirdRepaymentInstallment.getPenaltyChargesDue());
+            assertEquals(0.0f, 
thirdRepaymentInstallment.getPenaltyChargesOutstanding());
+            assertEquals(250.0f, 
thirdRepaymentInstallment.getTotalDueForPeriod());
+            assertEquals(240.0f, 
thirdRepaymentInstallment.getTotalOutstandingForPeriod());
+            assertEquals(LocalDate.of(2023, 4, 1), 
thirdRepaymentInstallment.getDueDate());
+
+            loanTransactionHelper.makeRefundByCash("01 March 2023", 15.0f, 
loanId);
+
+            loanDetails = loanTransactionHelper.getLoanDetails((long) loanId);
+
+            firstRepaymentInstallment = 
loanDetails.getRepaymentSchedule().getPeriods().get(2);
+            secondRepaymentInstallment = 
loanDetails.getRepaymentSchedule().getPeriods().get(3);
+            thirdRepaymentInstallment = 
loanDetails.getRepaymentSchedule().getPeriods().get(4);
+
+            assertEquals(5, 
loanDetails.getRepaymentSchedule().getPeriods().size());
+            assertEquals(feePortion, 
firstRepaymentInstallment.getFeeChargesDue());
+            assertEquals(0.0f, 
firstRepaymentInstallment.getFeeChargesOutstanding());
+            assertEquals(penaltyPortion, 
firstRepaymentInstallment.getPenaltyChargesDue());
+            assertEquals(0.0f, 
firstRepaymentInstallment.getPenaltyChargesOutstanding());
+            assertEquals(250.0f, firstRepaymentInstallment.getPrincipalDue());
+            assertEquals(0.0f, 
firstRepaymentInstallment.getPrincipalOutstanding());
+            assertEquals(400.0f, 
firstRepaymentInstallment.getTotalDueForPeriod());
+            assertEquals(0.0f, 
firstRepaymentInstallment.getTotalOutstandingForPeriod());
+            assertEquals(LocalDate.of(2023, 1, 31), 
firstRepaymentInstallment.getDueDate());
+
+            assertEquals(feePortion, 
secondRepaymentInstallment.getFeeChargesDue());
+            assertEquals(0.0f, 
secondRepaymentInstallment.getFeeChargesOutstanding());
+            assertEquals(penaltyPortion, 
secondRepaymentInstallment.getPenaltyChargesDue());
+            assertEquals(0.0f, 
secondRepaymentInstallment.getPenaltyChargesOutstanding());
+            assertEquals(250.0f, secondRepaymentInstallment.getPrincipalDue());
+            assertEquals(5.0f, 
secondRepaymentInstallment.getPrincipalOutstanding());
+            assertEquals(400.0f, 
secondRepaymentInstallment.getTotalDueForPeriod());
+            assertEquals(5.0f, 
secondRepaymentInstallment.getTotalOutstandingForPeriod());
+            assertEquals(LocalDate.of(2023, 3, 2), 
secondRepaymentInstallment.getDueDate());
+
+            assertEquals(0.0f, thirdRepaymentInstallment.getFeeChargesDue());
+            assertEquals(0.0f, 
thirdRepaymentInstallment.getFeeChargesOutstanding());
+            assertEquals(0.0f, 
thirdRepaymentInstallment.getPenaltyChargesDue());
+            assertEquals(0.0f, 
thirdRepaymentInstallment.getPenaltyChargesOutstanding());
+            assertEquals(250.0f, 
thirdRepaymentInstallment.getTotalDueForPeriod());
+            assertEquals(250.0f, 
thirdRepaymentInstallment.getTotalOutstandingForPeriod());
+            assertEquals(LocalDate.of(2023, 4, 1), 
thirdRepaymentInstallment.getDueDate());
+
+            loanTransactionHelper.makeRefundByCash("01 March 2023", 265.0f, 
loanId);
+
+            loanDetails = loanTransactionHelper.getLoanDetails((long) loanId);
+
+            firstRepaymentInstallment = 
loanDetails.getRepaymentSchedule().getPeriods().get(2);
+            secondRepaymentInstallment = 
loanDetails.getRepaymentSchedule().getPeriods().get(3);
+            thirdRepaymentInstallment = 
loanDetails.getRepaymentSchedule().getPeriods().get(4);
+
+            assertEquals(5, 
loanDetails.getRepaymentSchedule().getPeriods().size());
+            assertEquals(feePortion, 
firstRepaymentInstallment.getFeeChargesDue());
+            assertEquals(0.0f, 
firstRepaymentInstallment.getFeeChargesOutstanding());
+            assertEquals(penaltyPortion, 
firstRepaymentInstallment.getPenaltyChargesDue());
+            assertEquals(0.0f, 
firstRepaymentInstallment.getPenaltyChargesOutstanding());
+            assertEquals(250.0f, firstRepaymentInstallment.getPrincipalDue());
+            assertEquals(0.0f, 
firstRepaymentInstallment.getPrincipalOutstanding());
+            assertEquals(400.0f, 
firstRepaymentInstallment.getTotalDueForPeriod());
+            assertEquals(0.0f, 
firstRepaymentInstallment.getTotalOutstandingForPeriod());
+            assertEquals(LocalDate.of(2023, 1, 31), 
firstRepaymentInstallment.getDueDate());
+
+            assertEquals(feePortion, 
secondRepaymentInstallment.getFeeChargesDue());
+            assertEquals(20.0f, 
secondRepaymentInstallment.getFeeChargesOutstanding());
+            assertEquals(penaltyPortion, 
secondRepaymentInstallment.getPenaltyChargesDue());
+            assertEquals(0.0f, 
secondRepaymentInstallment.getPenaltyChargesOutstanding());
+            assertEquals(250.0f, secondRepaymentInstallment.getPrincipalDue());
+            assertEquals(250.0f, 
secondRepaymentInstallment.getPrincipalOutstanding());
+            assertEquals(400.0f, 
secondRepaymentInstallment.getTotalDueForPeriod());
+            assertEquals(270.0f, 
secondRepaymentInstallment.getTotalOutstandingForPeriod());
+            assertEquals(LocalDate.of(2023, 3, 2), 
secondRepaymentInstallment.getDueDate());
+
+            assertEquals(0.0f, thirdRepaymentInstallment.getFeeChargesDue());
+            assertEquals(0.0f, 
thirdRepaymentInstallment.getFeeChargesOutstanding());
+            assertEquals(0.0f, 
thirdRepaymentInstallment.getPenaltyChargesDue());
+            assertEquals(0.0f, 
thirdRepaymentInstallment.getPenaltyChargesOutstanding());
+            assertEquals(250.0f, 
thirdRepaymentInstallment.getTotalDueForPeriod());
+            assertEquals(250.0f, 
thirdRepaymentInstallment.getTotalOutstandingForPeriod());
+            assertEquals(LocalDate.of(2023, 4, 1), 
thirdRepaymentInstallment.getDueDate());
+        } finally {
+            GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, 
responseSpec, Boolean.FALSE);
+        }
+    }
+
+    private Integer createLoanProduct(final String principal, final String 
repaymentAfterEvery, final String numberOfRepayments,
+            final Account... accounts) {
+        AdvancedPaymentData defaultAllocation = 
createDefaultPaymentAllocation();
+        log.info("------------------------------CREATING NEW LOAN PRODUCT 
---------------------------------------");
+        final String loanProductJSON = new 
LoanProductTestBuilder().withMinPrincipal(principal).withPrincipal(principal)
+                
.withRepaymentTypeAsDays().withRepaymentAfterEvery(repaymentAfterEvery).withNumberOfRepayments(numberOfRepayments)
+                .withEnableDownPayment(true, "25", 
true).withinterestRatePerPeriod("0").withInterestRateFrequencyTypeAsMonths()
+                
.withRepaymentStrategy(AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)
+                
.withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsFlat().withAccountingRulePeriodicAccrual(accounts)
+                
.addAdvancedPaymentAllocation(defaultAllocation).withDaysInMonth("30").withDaysInYear("365").withMoratorium("0",
 "0")
+                .build(null);
+        return loanTransactionHelper.getLoanProductId(loanProductJSON);
+    }
+
+    private static 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_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_INTEREST, 
PaymentAllocationType.IN_ADVANCE_PRINCIPAL);
+
+        advancedPaymentData.setPaymentAllocationOrder(paymentAllocationOrders);
+        return advancedPaymentData;
+    }
+
+    private static 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();
+    }
+
+    private static PostLoansResponse applyForLoanApplication(final Long 
clientId, final Integer loanProductId, final Long principal,
+            final int loanTermFrequency, final int repaymentAfterEvery, final 
int numberOfRepayments, final int interestRate,
+            final String expectedDisbursementDate, final String 
submittedOnDate) {
+        log.info("--------------------------------APPLYING FOR LOAN 
APPLICATION--------------------------------");
+        return loanTransactionHelper.applyLoan(new 
PostLoansRequest().clientId(clientId).productId(loanProductId.longValue())
+                
.expectedDisbursementDate(expectedDisbursementDate).dateFormat(DATETIME_PATTERN)
+                
.transactionProcessingStrategyCode(AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)
+                
.locale("en").submittedOnDate(submittedOnDate).amortizationType(1).interestRatePerPeriod(interestRate)
+                
.interestCalculationPeriodType(1).interestType(0).repaymentFrequencyType(0).repaymentEvery(repaymentAfterEvery)
+                
.repaymentFrequencyType(0).numberOfRepayments(numberOfRepayments).loanTermFrequency(loanTermFrequency)
+                
.loanTermFrequencyType(0).principal(BigDecimal.valueOf(principal)).loanType("individual"));
+    }
+}


Reply via email to