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 5ebd3429c FINERACT-2107: Loan transaction Interest Refund GL Entries
5ebd3429c is described below

commit 5ebd3429c6ca781f2af8d082b950022730e93ef7
Author: Jose Alberto Hernandez <[email protected]>
AuthorDate: Tue Aug 13 03:54:08 2024 -0600

    FINERACT-2107: Loan transaction Interest Refund GL Entries
---
 .../loanaccount/data/LoanRefundRequestData.java    |  39 ++++
 .../loanaccount/data/LoanTransactionEnumData.java  |   2 +
 .../domain/LoanAccountDomainService.java           |   3 +
 .../loanaccount/domain/LoanTransaction.java        |   7 +
 .../AccrualBasedAccountingProcessorForLoan.java    |   8 +-
 .../api/InternalLoanInformationApiResource.java    |  25 +++
 .../domain/LoanAccountDomainServiceJpa.java        |  25 ++-
 .../integrationtests/BaseLoanIntegrationTest.java  |   2 +
 .../LoanRefundTransactionTest.java                 | 245 +++++++++++++++++++++
 .../common/loans/LoanTransactionHelper.java        |   8 +
 10 files changed, 359 insertions(+), 5 deletions(-)

diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanRefundRequestData.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanRefundRequestData.java
new file mode 100644
index 000000000..22daadc5c
--- /dev/null
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanRefundRequestData.java
@@ -0,0 +1,39 @@
+/**
+ * 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.portfolio.loanaccount.data;
+
+import java.math.BigDecimal;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+@Data
+@AllArgsConstructor
+public class LoanRefundRequestData {
+
+    private final BigDecimal principal;
+    private final BigDecimal interest;
+    private final BigDecimal feeCharges;
+    private final BigDecimal penaltyCharges;
+    private final BigDecimal overpayment;
+
+    public BigDecimal getTotalAmount() {
+        return principal.add(interest).add(feeCharges).add(penaltyCharges);
+    }
+
+}
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionEnumData.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionEnumData.java
index 78497b620..b3e34c744 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionEnumData.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionEnumData.java
@@ -60,6 +60,7 @@ public class LoanTransactionEnumData {
     private final boolean reAge;
     private final boolean reAmortize;
     private final boolean accrualActivity;
+    private final boolean interestRefund;
 
     public LoanTransactionEnumData(final Long id, final String code, final 
String value) {
         this.id = id;
@@ -94,6 +95,7 @@ public class LoanTransactionEnumData {
         this.accrualActivity = Long.valueOf(32).equals(this.id);
         this.reAge = 
Long.valueOf(LoanTransactionType.REAGE.getValue()).equals(this.id);
         this.reAmortize = 
Long.valueOf(LoanTransactionType.REAMORTIZE.getValue()).equals(this.id);
+        this.interestRefund = 
Long.valueOf(LoanTransactionType.INTEREST_REFUND.getValue()).equals(this.id);
     }
 
     public boolean isRepaymentType() {
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainService.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainService.java
index 51d13848b..965ccf225 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainService.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainService.java
@@ -27,6 +27,7 @@ import 
org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuild
 import org.apache.fineract.infrastructure.core.domain.ExternalId;
 import 
org.apache.fineract.portfolio.delinquency.validator.LoanDelinquencyActionData;
 import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
+import org.apache.fineract.portfolio.loanaccount.data.LoanRefundRequestData;
 import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
 
 public interface LoanAccountDomainService {
@@ -93,4 +94,6 @@ public interface LoanAccountDomainService {
     LoanTransaction creditBalanceRefund(Loan loan, LocalDate transactionDate, 
BigDecimal transactionAmount, String noteText,
             ExternalId externalId, PaymentDetail paymentDetail);
 
+    LoanTransaction applyInterestRefund(Loan loan, LoanRefundRequestData 
loanRefundRequest);
+
 }
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
index 26e7dfddd..f0a8fde00 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
@@ -323,6 +323,13 @@ public class LoanTransaction extends 
AbstractAuditableWithUTCDateTimeCustom<Long
                 externalId);
     }
 
+    public static LoanTransaction interestRefund(final Loan loan, final Office 
office, final BigDecimal amount, final BigDecimal principal,
+            final BigDecimal interest, final BigDecimal feeCharges, final 
BigDecimal penaltyCharges, final PaymentDetail paymentDetail,
+            final LocalDate refundDate, final ExternalId externalId) {
+        return new LoanTransaction(loan, office, 
LoanTransactionType.INTEREST_REFUND.getValue(), refundDate, amount, principal, 
interest,
+                feeCharges, penaltyCharges, amount, false, paymentDetail, 
externalId);
+    }
+
     public static boolean transactionAmountsMatch(final MonetaryCurrency 
currency, final LoanTransaction loanTransaction,
             final LoanTransaction newLoanTransaction) {
         return 
loanTransaction.getAmount(currency).isEqualTo(newLoanTransaction.getAmount(currency))
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 82686cf78..0d7e4902a 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
@@ -111,13 +111,15 @@ public class AccrualBasedAccountingProcessorForLoan 
implements AccountingProcess
                 createJournalEntriesForChargeOff(loanDTO, loanTransactionDTO, 
office);
             }
             // Logic for Interest Payment Waiver
-            else if 
(loanTransactionDTO.getTransactionType().isInterestPaymentWaiver()) {
-                createJournalEntriesForInterestPaymentWaiver(loanDTO, 
loanTransactionDTO, office);
+            else if 
(loanTransactionDTO.getTransactionType().isInterestPaymentWaiver()
+                    || 
loanTransactionDTO.getTransactionType().isInterestRefund()) {
+                
createJournalEntriesForInterestPaymentWaiverOrInterestRefund(loanDTO, 
loanTransactionDTO, office);
             }
         }
     }
 
-    private void createJournalEntriesForInterestPaymentWaiver(LoanDTO loanDTO, 
LoanTransactionDTO loanTransactionDTO, Office office) {
+    private void 
createJournalEntriesForInterestPaymentWaiverOrInterestRefund(LoanDTO loanDTO, 
LoanTransactionDTO loanTransactionDTO,
+            Office office) {
         final Long loanProductId = loanDTO.getLoanProductId();
         final Long loanId = loanDTO.getLoanId();
         final String currencyCode = loanDTO.getCurrencyCode();
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/InternalLoanInformationApiResource.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/InternalLoanInformationApiResource.java
index a15d7f4a1..bef3ac73b 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/InternalLoanInformationApiResource.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/InternalLoanInformationApiResource.java
@@ -23,9 +23,11 @@ import static 
org.apache.fineract.infrastructure.core.domain.AuditableFieldsCons
 import static 
org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.LAST_MODIFIED_BY;
 import static 
org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.LAST_MODIFIED_DATE;
 
+import com.google.gson.Gson;
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 import jakarta.ws.rs.Consumes;
 import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
 import jakarta.ws.rs.Path;
 import jakarta.ws.rs.PathParam;
 import jakarta.ws.rs.Produces;
@@ -41,7 +43,9 @@ import 
org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
 import org.apache.fineract.infrastructure.core.boot.FineractProfiles;
 import 
org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
 import 
org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
+import org.apache.fineract.portfolio.loanaccount.data.LoanRefundRequestData;
 import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import 
org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
@@ -64,6 +68,8 @@ public class InternalLoanInformationApiResource implements 
InitializingBean {
     private final ToApiJsonSerializer<List> toApiJsonSerializerForList;
     private final ApiRequestParameterHelper apiRequestParameterHelper;
     private final AdvancedPaymentDataMapper advancedPaymentDataMapper;
+    private final LoanAccountDomainService loanAccountDomainService;
+    private final Gson gson = new Gson();
 
     @Override
     @SuppressFBWarnings("SLF4J_SIGN_ONLY_FORMAT")
@@ -152,4 +158,23 @@ public class InternalLoanInformationApiResource implements 
InitializingBean {
         final Loan loan = 
loanRepositoryWrapper.findOneWithNotFoundDetection(loanId);
         return 
advancedPaymentDataMapper.mapLoanPaymentAllocationRule(loan.getPaymentAllocationRules());
     }
+
+    @POST
+    @Path("{loanId}/apply-interest-refund")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    @SuppressFBWarnings("SLF4J_SIGN_ONLY_FORMAT")
+    public Long applyInterestRefundToLoan(@Context final UriInfo uriInfo, 
@PathParam("loanId") Long loanId,
+            final String apiRequestBodyAsJson) {
+        
log.warn("------------------------------------------------------------");
+        log.warn("                                                            
");
+        log.warn("    Apply Loan Transaction to Interest Refund loanId {} ", 
loanId);
+        log.warn("                                                            
");
+        
log.warn("------------------------------------------------------------");
+        LoanRefundRequestData loanRefundRequestData = 
gson.fromJson(apiRequestBodyAsJson, LoanRefundRequestData.class);
+        final Loan loan = 
loanRepositoryWrapper.findOneWithNotFoundDetection(loanId);
+        final LoanTransaction loanTransaction = 
loanAccountDomainService.applyInterestRefund(loan, loanRefundRequestData);
+        return loanTransaction.getId();
+    }
+
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
index 5a0f488cc..a83c30591 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
@@ -67,7 +67,6 @@ import 
org.apache.fineract.infrastructure.event.business.service.BusinessEventNo
 import org.apache.fineract.organisation.holiday.domain.Holiday;
 import org.apache.fineract.organisation.holiday.domain.HolidayRepository;
 import org.apache.fineract.organisation.holiday.domain.HolidayStatusType;
-import 
org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
 import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
 import org.apache.fineract.organisation.monetary.domain.Money;
 import org.apache.fineract.organisation.workingdays.domain.WorkingDays;
@@ -87,6 +86,7 @@ import 
org.apache.fineract.portfolio.delinquency.validator.LoanDelinquencyAction
 import org.apache.fineract.portfolio.group.domain.Group;
 import org.apache.fineract.portfolio.group.exception.GroupNotActiveException;
 import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
+import org.apache.fineract.portfolio.loanaccount.data.LoanRefundRequestData;
 import 
org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData;
 import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
 import 
org.apache.fineract.portfolio.loanaccount.service.LoanAccrualTransactionBusinessEventService;
@@ -120,7 +120,6 @@ public class LoanAccountDomainServiceJpa implements 
LoanAccountDomainService {
     private final JournalEntryWritePlatformService 
journalEntryWritePlatformService;
     private final NoteRepository noteRepository;
     private final AccountTransferRepository accountTransferRepository;
-    private final ApplicationCurrencyRepositoryWrapper 
applicationCurrencyRepository;
     private final BusinessEventNotifierService businessEventNotifierService;
     private final LoanUtilService loanUtilService;
     private final StandingInstructionRepository standingInstructionRepository;
@@ -801,4 +800,26 @@ public class LoanAccountDomainServiceJpa implements 
LoanAccountDomainService {
         }
     }
 
+    @Override
+    public LoanTransaction applyInterestRefund(final Loan loan, final 
LoanRefundRequestData loanRefundRequest) {
+        final PaymentDetail paymentDetail = null;
+        final LocalDate transactionDate = DateUtils.getBusinessLocalDate();
+        final BigDecimal refundAmount = loanRefundRequest.getTotalAmount();
+
+        final List<Long> existingTransactionIds = new ArrayList<>();
+        final List<Long> existingReversedTransactionIds = new ArrayList<>();
+        existingTransactionIds.addAll(loan.findExistingTransactionIds());
+        
existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds());
+
+        final LoanTransaction interestRefundTransaction = 
LoanTransaction.interestRefund(loan, loan.getOffice(), refundAmount,
+                loanRefundRequest.getPrincipal(), 
loanRefundRequest.getInterest(), loanRefundRequest.getFeeCharges(),
+                loanRefundRequest.getPenaltyCharges(), paymentDetail, 
transactionDate, externalIdFactory.create());
+        interestRefundTransaction.updateLoan(loan);
+        
saveLoanTransactionWithDataIntegrityViolationChecks(interestRefundTransaction);
+        loan.addLoanTransaction(interestRefundTransaction);
+        postJournalEntries(loan, existingTransactionIds, 
existingReversedTransactionIds, false);
+
+        return interestRefundTransaction;
+    }
+
 }
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 fc0e6f6e6..15d557e88 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
@@ -49,6 +49,7 @@ import java.util.function.Consumer;
 import lombok.AllArgsConstructor;
 import lombok.RequiredArgsConstructor;
 import lombok.ToString;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.fineract.batch.domain.BatchRequest;
 import org.apache.fineract.batch.domain.BatchResponse;
 import org.apache.fineract.client.models.AdvancedPaymentData;
@@ -100,6 +101,7 @@ import org.hamcrest.Matchers;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.extension.ExtendWith;
 
+@Slf4j
 @ExtendWith(LoanTestLifecycleExtension.class)
 public abstract class BaseLoanIntegrationTest {
 
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRefundTransactionTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRefundTransactionTest.java
new file mode 100644
index 000000000..2d2e5bbc2
--- /dev/null
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRefundTransactionTest.java
@@ -0,0 +1,245 @@
+/**
+ * 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.apache.fineract.integrationtests.BaseLoanIntegrationTest.InterestRateFrequencyType.YEARS;
+import static 
org.apache.fineract.integrationtests.BaseLoanIntegrationTest.RepaymentFrequencyType.DAYS;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import com.google.gson.Gson;
+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.util.HashMap;
+import java.util.concurrent.atomic.AtomicLong;
+import org.apache.fineract.client.models.GetLoansLoanIdResponse;
+import org.apache.fineract.client.models.PostLoanProductsRequest;
+import org.apache.fineract.client.models.PostLoanProductsResponse;
+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.integrationtests.common.BusinessDateHelper;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.LoanRescheduleRequestHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.accounting.AccountHelper;
+import 
org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import org.apache.fineract.portfolio.common.domain.DaysInMonthType;
+import org.apache.fineract.portfolio.common.domain.DaysInYearType;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class LoanRefundTransactionTest extends BaseLoanIntegrationTest {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(LoanRefundTransactionTest.class);
+    private static final String DATETIME_PATTERN = "dd MMMM yyyy";
+    private static ResponseSpecification responseSpec;
+    private static RequestSpecification requestSpec;
+    private static BusinessDateHelper businessDateHelper;
+    private static LoanTransactionHelper loanTransactionHelper;
+    private static AccountHelper accountHelper;
+    private static LoanRescheduleRequestHelper loanRescheduleRequestHelper;
+
+    @BeforeAll
+    public static void setup() {
+        Utils.initializeRESTAssured();
+        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);
+        businessDateHelper = new BusinessDateHelper();
+        accountHelper = new AccountHelper(requestSpec, responseSpec);
+        ClientHelper clientHelper = new ClientHelper(requestSpec, 
responseSpec);
+        loanRescheduleRequestHelper = new 
LoanRescheduleRequestHelper(requestSpec, responseSpec);
+    }
+
+    // UC1: (Internal case) Generate a totalInterestRefund using Advanced 
payment allocation with Interest Refund
+    // options and Apply
+    // Interest Refund transaction
+    // 1. Create a Loan product with Adv. Pment. Alloc. and with Accrual and 
Interest Refund
+    // 2. Submit, Approve and Disburse a Loan account
+    // 3. Apply the INTEREST_REFUND transaction to validate the Journal 
Entries generated
+    @Test
+    public void uc1() {
+        final String operationDate = "1 January 2024";
+        AtomicLong createdLoanId = new AtomicLong();
+        runAt(operationDate, () -> {
+            Long clientId = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+            PostLoanProductsRequest product = 
createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
+                    
.interestRatePerPeriod(108.0).interestCalculationPeriodType(RepaymentFrequencyType.DAYS)
+                    
.interestRateFrequencyType(YEARS).daysInMonthType(DaysInMonthType.ACTUAL.getValue())
+                    
.daysInYearType(DaysInYearType.DAYS_360.getValue()).numberOfRepayments(4)//
+                    .maxInterestRatePerPeriod((double) 110)//
+                    .repaymentEvery(1)//
+                    .repaymentFrequencyType(1L)//
+                    .allowPartialPeriodInterestCalcualtion(false)//
+                    .multiDisburseLoan(false)//
+                    .disallowExpectedDisbursements(null)//
+                    .allowApprovedDisbursedAmountsOverApplied(null)//
+                    .overAppliedCalculationType(null)//
+                    .overAppliedNumber(null)//
+                    .installmentAmountInMultiplesOf(null)//
+            ;//
+            PostLoanProductsResponse loanProductResponse = 
loanProductHelper.createLoanProduct(product);
+            PostLoansRequest applicationRequest = applyLoanRequest(clientId, 
loanProductResponse.getResourceId(), operationDate, 1000.0, 4)
+                    .interestRatePerPeriod(BigDecimal.valueOf(108.0));
+
+            applicationRequest = 
applicationRequest.interestCalculationPeriodType(DAYS)
+                    
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY);
+
+            PostLoansResponse loanResponse = 
loanTransactionHelper.applyLoan(applicationRequest);
+            createdLoanId.set(loanResponse.getLoanId());
+
+            loanTransactionHelper.approveLoan(loanResponse.getLoanId(),
+                    new 
PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000.0)).dateFormat(DATETIME_PATTERN)
+                            .approvedOnDate("1 January 2024").locale("en"));
+
+            loanTransactionHelper.disburseLoan(loanResponse.getLoanId(),
+                    new PostLoansLoanIdRequest().actualDisbursementDate("1 
January 2024").dateFormat(DATETIME_PATTERN)
+                            
.transactionAmount(BigDecimal.valueOf(1000.0)).locale("en"));
+
+            // After Disbursement we are expecting zero interest refund
+            GetLoansLoanIdResponse loanDetails = 
loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
+            assertEquals(BigDecimal.ZERO, 
loanDetails.getSummary().getTotalInterestRefund().stripTrailingZeros());
+        });
+
+        runAt("10 February 2024", () -> {
+            // After Interest refund transaction we are expecting non zero 
interest refund
+            GetLoansLoanIdResponse loanDetails = 
loanTransactionHelper.getLoanDetails(createdLoanId.get());
+            LOG.info("value {}", 
loanDetails.getSummary().getTotalInterestRefund().stripTrailingZeros());
+            assertEquals(BigDecimal.ZERO, 
loanDetails.getSummary().getTotalInterestRefund().stripTrailingZeros());
+
+            final BigDecimal inteterestRefund = BigDecimal.valueOf(11);
+            final Long loanTransactionId = 
loanTransactionHelper.applyInterestRefundLoanTransaction(requestSpec, 
responseSpec,
+                    createdLoanId.get(), 
buildJsonBody(BigDecimal.valueOf(5.0), BigDecimal.valueOf(3.0), 
BigDecimal.valueOf(2.0),
+                            BigDecimal.valueOf(1.0), null));
+
+            loanDetails = 
loanTransactionHelper.getLoanDetails(createdLoanId.get());
+            assertEquals(inteterestRefund, 
loanDetails.getSummary().getTotalInterestRefund().stripTrailingZeros());
+
+            verifyTRJournalEntries(loanTransactionId, journalEntry(5.0, 
loansReceivableAccount, "CREDIT"),
+                    journalEntry(3.0, interestReceivableAccount, "CREDIT"), 
journalEntry(2.0, feeReceivableAccount, "CREDIT"),
+                    journalEntry(1.0, penaltyReceivableAccount, "CREDIT"), 
journalEntry(11.0, overpaymentAccount, "CREDIT"),
+                    journalEntry(22.0, interestIncomeAccount, "DEBIT"));
+        });
+    }
+
+    // UC2: (Internal case) Generate a totalInterestRefund using Advanced 
payment allocation with Interest Refund
+    // options and Apply
+    // Interest Refund transaction when the Loan Account is ChargeBack
+    // 1. Create a Loan product with Adv. Pment. Alloc. and with Accrual and 
Interest Refund
+    // 2. Submit, Approve and Disburse a Loan account
+    // 3. Apply the MERCHANT_ISSUED_REFUND transaction to validate 
totalInterestRefund different than Zero
+    // 4- Apply the INTEREST_REFUND transaction to validate the Journal 
Entries generated
+    @Test
+    public void uc2() {
+        final String operationDate = "1 January 2024";
+        AtomicLong createdLoanId = new AtomicLong();
+        runAt(operationDate, () -> {
+            Long clientId = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+            PostLoanProductsRequest product = 
createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
+                    
.interestRatePerPeriod(108.0).interestCalculationPeriodType(RepaymentFrequencyType.DAYS)
+                    
.interestRateFrequencyType(YEARS).daysInMonthType(DaysInMonthType.ACTUAL.getValue())
+                    
.daysInYearType(DaysInYearType.DAYS_360.getValue()).numberOfRepayments(4)//
+                    .maxInterestRatePerPeriod((double) 110)//
+                    .repaymentEvery(1)//
+                    .repaymentFrequencyType(1L)//
+                    .allowPartialPeriodInterestCalcualtion(false)//
+                    .multiDisburseLoan(false)//
+                    .disallowExpectedDisbursements(null)//
+                    .allowApprovedDisbursedAmountsOverApplied(null)//
+                    .overAppliedCalculationType(null)//
+                    .overAppliedNumber(null)//
+                    .installmentAmountInMultiplesOf(null)//
+            ;//
+            PostLoanProductsResponse loanProductResponse = 
loanProductHelper.createLoanProduct(product);
+            PostLoansRequest applicationRequest = applyLoanRequest(clientId, 
loanProductResponse.getResourceId(), operationDate, 1000.0, 4)
+                    .interestRatePerPeriod(BigDecimal.valueOf(108.0));
+
+            applicationRequest = 
applicationRequest.interestCalculationPeriodType(DAYS)
+                    
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY);
+
+            PostLoansResponse loanResponse = 
loanTransactionHelper.applyLoan(applicationRequest);
+            createdLoanId.set(loanResponse.getLoanId());
+
+            loanTransactionHelper.approveLoan(loanResponse.getLoanId(),
+                    new 
PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000.0)).dateFormat(DATETIME_PATTERN)
+                            .approvedOnDate("1 January 2024").locale("en"));
+
+            loanTransactionHelper.disburseLoan(loanResponse.getLoanId(),
+                    new PostLoansLoanIdRequest().actualDisbursementDate("1 
January 2024").dateFormat(DATETIME_PATTERN)
+                            
.transactionAmount(BigDecimal.valueOf(1000.0)).locale("en"));
+
+            // After Disbursement we are expecting zero interest refund
+            GetLoansLoanIdResponse loanDetails = 
loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
+            assertEquals(BigDecimal.ZERO, 
loanDetails.getSummary().getTotalInterestRefund().stripTrailingZeros());
+        });
+
+        runAt("1 February 2024", () -> {
+            // After Interest refund transaction we are expecting non zero 
interest refund
+            GetLoansLoanIdResponse loanDetails = 
loanTransactionHelper.getLoanDetails(createdLoanId.get());
+            LOG.info("value {}", 
loanDetails.getSummary().getTotalInterestRefund().stripTrailingZeros());
+            assertEquals(BigDecimal.ZERO, 
loanDetails.getSummary().getTotalInterestRefund().stripTrailingZeros());
+
+            Long repayment1TransactionId = 
addRepaymentForLoan(createdLoanId.get(), 250.0, "1 February 2024");
+            chargeOffLoan(createdLoanId.get(), "1 February 2024");
+        });
+
+        runAt("10 February 2024", () -> {
+            // After Interest refund transaction we are expecting non zero 
interest refund
+            GetLoansLoanIdResponse loanDetails = 
loanTransactionHelper.getLoanDetails(createdLoanId.get());
+            LOG.info("value {}", 
loanDetails.getSummary().getTotalInterestRefund().stripTrailingZeros());
+            LOG.info("chargedOffOnDate {}", 
loanDetails.getTimeline().getChargedOffOnDate());
+            assertEquals(BigDecimal.ZERO, 
loanDetails.getSummary().getTotalInterestRefund().stripTrailingZeros());
+
+            final BigDecimal inteterestRefund = BigDecimal.valueOf(11);
+            final Long loanTransactionId = 
loanTransactionHelper.applyInterestRefundLoanTransaction(requestSpec, 
responseSpec,
+                    createdLoanId.get(), 
buildJsonBody(BigDecimal.valueOf(5.0), BigDecimal.valueOf(3.0), 
BigDecimal.valueOf(2.0),
+                            BigDecimal.valueOf(1.0), null));
+
+            loanDetails = 
loanTransactionHelper.getLoanDetails(createdLoanId.get());
+            assertEquals(inteterestRefund, 
loanDetails.getSummary().getTotalInterestRefund().stripTrailingZeros());
+
+            verifyTRJournalEntries(loanTransactionId, journalEntry(11.0, 
interestIncomeChargeOffAccount, "CREDIT"),
+                    journalEntry(11.0, overpaymentAccount, "CREDIT"), 
journalEntry(22.0, interestIncomeAccount, "DEBIT"));
+        });
+    }
+
+    private String buildJsonBody(final BigDecimal principal, final BigDecimal 
interest, final BigDecimal feeCharges,
+            final BigDecimal penaltyCharges, final BigDecimal overpayment) {
+        final HashMap<String, BigDecimal> map = new HashMap<>();
+        map.put("principal", principal);
+        map.put("interest", interest);
+        map.put("feeCharges", feeCharges);
+        map.put("penaltyCharges", penaltyCharges);
+        if (overpayment != null) {
+            map.put("overpayment", overpayment);
+        }
+
+        return new Gson().toJson(map);
+    }
+
+}
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 534049b7b..58f04ad23 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
@@ -1700,6 +1700,14 @@ public class LoanTransactionHelper extends 
IntegrationTest {
         return Utils.performServerGet(requestSpec, responseSpec, 
GET_LOAN_TRANSACTION_URL, jsonReturn);
     }
 
+    public Long applyInterestRefundLoanTransaction(final RequestSpecification 
requestSpec, final ResponseSpecification responseSpec,
+            final Long loanId, final String jsonBody) {
+        final String POST_LOAN_TRANSACTION_URL = 
"/fineract-provider/api/v1/internal/loan/" + loanId + "/apply-interest-refund/" 
+ "?"
+                + Utils.TENANT_IDENTIFIER;
+        final String reponse = Utils.performServerPost(requestSpec, 
responseSpec, POST_LOAN_TRANSACTION_URL, jsonBody);
+        return Long.valueOf(reponse);
+    }
+
     public void printRepaymentSchedule(GetLoansLoanIdResponse 
getLoansLoanIdResponse) {
         GetLoansLoanIdRepaymentSchedule getLoanRepaymentSchedule = 
getLoansLoanIdResponse.getRepaymentSchedule();
         if (getLoanRepaymentSchedule != null) {

Reply via email to