diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanScheduleService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanScheduleService.java index b7fe87bb2bc..a5bf24e662d 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanScheduleService.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanScheduleService.java @@ -29,6 +29,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel; +import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType; @RequiredArgsConstructor public class LoanScheduleService { @@ -98,7 +99,8 @@ public void regenerateRepaymentScheduleWithInterestRecalculation(final Loan loan for (final LoanCharge loanCharge : charges) { if (!loanCharge.isDueAtDisbursement()) { loan.updateOverdueScheduleInstallment(loanCharge); - if (loanCharge.getDueLocalDate() == null || !DateUtils.isBefore(lastRepaymentDate, loanCharge.getDueLocalDate())) { + if (loanCharge.getDueLocalDate() == null || (!DateUtils.isBefore(lastRepaymentDate, loanCharge.getDueLocalDate()) + || loan.getLoanProductRelatedDetail().getLoanScheduleType().equals(LoanScheduleType.PROGRESSIVE))) { if ((loanCharge.isInstalmentFee() || !loanCharge.isWaived()) && (loanCharge.getDueLocalDate() == null || !DateUtils.isAfter(lastTransactionDate, loanCharge.getDueLocalDate()))) { loanChargeService.recalculateLoanCharge(loan, loanCharge, generatorDTO.getPenaltyWaitPeriod()); 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 b097ec2670b..c494a05957b 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 @@ -1004,6 +1004,12 @@ protected PostChargesResponse createCharge(Double amount) { return ChargesHelper.createLoanCharge(requestSpec, responseSpec, payload); } + protected PostChargesResponse createCharge(Double amount, String currencyCode) { + String payload = ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, amount.toString(), false, + currencyCode); + return ChargesHelper.createLoanCharge(requestSpec, responseSpec, payload); + } + protected PostLoansLoanIdChargesResponse addLoanCharge(Long loanId, Long chargeId, String date, Double amount) { String payload = LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(chargeId.toString(), date, amount.toString()); return loanTransactionHelper.addChargeForLoan(loanId.intValue(), payload, responseSpec); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeProgressiveTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeProgressiveTest.java new file mode 100644 index 00000000000..d7babdff4da --- /dev/null +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeProgressiveTest.java @@ -0,0 +1,68 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.integrationtests; + +import java.math.BigDecimal; +import java.time.LocalDate; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.client.models.GetLoansLoanIdResponse; +import org.apache.fineract.client.models.PostChargesResponse; +import org.apache.fineract.client.models.PostLoanProductsResponse; +import org.apache.fineract.client.models.PostLoansResponse; +import org.apache.fineract.integrationtests.common.ClientHelper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +@Slf4j +public class LoanChargeProgressiveTest extends BaseLoanIntegrationTest { + + private Long clientId; + private Long loanId; + + // create client, progressive loan product, loan with disburse limit 1000 for the client, + // and disburse 250 on 01 June 2024 + @BeforeEach + public void beforeEach() { + runAt("01 June 2024", () -> { + clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); + final PostLoanProductsResponse loanProductsResponse = loanProductHelper.createLoanProduct(create4IProgressive()); + PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan( + applyLP2ProgressiveLoanRequest(clientId, loanProductsResponse.getResourceId(), "01 June 2024", 1000.0, 10.0, 4, null)); + loanId = postLoansResponse.getLoanId(); + loanTransactionHelper.approveLoan(loanId, approveLoanRequest(1000.0, "01 June 2024")); + disburseLoan(loanId, BigDecimal.valueOf(250.0), "01 June 2024"); + }); + } + + @Test + public void loanChargeAfterMaturityTest() { + runAt("02 October 2024", () -> { + final PostChargesResponse chargeResponse = createCharge(20.0d, "EUR"); + addLoanCharge(loanId, chargeResponse.getResourceId(), "02 October 2024", 20.0d); + + final GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId); + validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2024, 10, 2), 0, 20, 0, 0); + + executeInlineCOB(loanId); + + final GetLoansLoanIdResponse loanDetails2 = loanTransactionHelper.getLoanDetails(loanId); + validateRepaymentPeriod(loanDetails2, 5, LocalDate.of(2024, 10, 2), 0, 20, 0, 0); + }); + } +} diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/charges/ChargesHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/charges/ChargesHelper.java index d554d3b02d1..f702389914a 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/charges/ChargesHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/charges/ChargesHelper.java @@ -223,14 +223,25 @@ public static String getLoanSpecifiedDueDateJSON(final Integer chargeCalculation return getLoanSpecifiedDueDateJSON(chargeCalculationType, amount, penalty, ChargesHelper.CHARGE_PAYMENT_MODE_REGULAR); } + public static String getLoanSpecifiedDueDateJSON(final Integer chargeCalculationType, final String amount, boolean penalty, + String currencyCode) { + return getLoanSpecifiedDueDateJSON(chargeCalculationType, amount, penalty, ChargesHelper.CHARGE_PAYMENT_MODE_REGULAR, currencyCode); + } + public static String getLoanSpecifiedDueDateJSON(final Integer chargeCalculationType, final String amount, final boolean penalty, final Integer paymentMode) { + return getLoanSpecifiedDueDateJSON(chargeCalculationType, amount, penalty, paymentMode, ChargesHelper.CURRENCY_CODE); + } + + public static String getLoanSpecifiedDueDateJSON(final Integer chargeCalculationType, final String amount, final boolean penalty, + final Integer paymentMode, final String currencyCode) { final HashMap map = populateDefaultsForLoan(); map.put("chargeTimeType", CHARGE_SPECIFIED_DUE_DATE); map.put("chargePaymentMode", paymentMode); map.put("penalty", penalty); map.put("amount", amount); map.put("chargeCalculationType", chargeCalculationType); + map.put("currencyCode", currencyCode); String chargesCreateJson = new Gson().toJson(map); LOG.info("{}", chargesCreateJson);