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 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 existingTransactionIds = new ArrayList<>(); + final List 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 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) {