diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java index 1e3a7eacb..97db300d3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java @@ -2284,6 +2284,8 @@ public final class LoanProductDataValidator { } else { considerPartialPeriodUpdates = considerPartialPeriods; } + } else if (loanProduct != null) { + considerPartialPeriodUpdates = loanProduct.getLoanProductRelatedDetail().isAllowPartialPeriodInterestCalcualtion(); } if (!considerPartialPeriodUpdates) { diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductUpdateApiTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductUpdateApiTest.java new file mode 100644 index 000000000..07118f3f0 --- /dev/null +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductUpdateApiTest.java @@ -0,0 +1,134 @@ +/** + * 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 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.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; +import org.apache.fineract.client.models.AdvancedPaymentData; +import org.apache.fineract.client.models.GetLoanProductsProductIdResponse; +import org.apache.fineract.client.models.PaymentAllocationOrder; +import org.apache.fineract.client.models.PutLoanProductsProductIdRequest; +import org.apache.fineract.integrationtests.common.Utils; +import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder; +import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper; +import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class LoanProductUpdateApiTest { + + private static LoanTransactionHelper LOAN_TRANSACTION_HELPER; + private static ResponseSpecification RESPONSE_SPEC; + private static RequestSpecification REQUEST_SPEC; + + @BeforeAll + public static void setupTests() { + Utils.initializeRESTAssured(); + REQUEST_SPEC = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); + REQUEST_SPEC.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); + RESPONSE_SPEC = new ResponseSpecBuilder().expectStatusCode(200).build(); + LOAN_TRANSACTION_HELPER = new LoanTransactionHelper(REQUEST_SPEC, RESPONSE_SPEC); + } + + @Test + public void loanProductModifyForAdvancedPaymentAllocationRuleTest() { + // create loan product with Advanced Payment Allocation Strategy with default allocation with future installment + // allocation as NEXT_INSTALLMENT + String futureInstallmentAllocationRule = "NEXT_INSTALLMENT"; + AdvancedPaymentData defaultAllocation = createDefaultPaymentAllocation(futureInstallmentAllocationRule); + + Integer loanProductId = createLoanProduct(defaultAllocation); + Assertions.assertNotNull(loanProductId); + + // verify allocation rule + GetLoanProductsProductIdResponse loanProduct = LOAN_TRANSACTION_HELPER.getLoanProduct(loanProductId); + Assertions.assertNotNull(loanProduct.getPaymentAllocation()); + + Optional defaultAllocationAfterCreate = loanProduct.getPaymentAllocation().stream() + .filter(advancedPaymentData -> "DEFAULT".equals(advancedPaymentData.getTransactionType())).findFirst(); + Assertions.assertTrue(defaultAllocationAfterCreate.isPresent()); + Assertions.assertEquals(futureInstallmentAllocationRule, defaultAllocationAfterCreate.get().getFutureInstallmentAllocationRule()); + + // Change future installment allocation rule to "LAST_INSTALLMENT" and update loan product + futureInstallmentAllocationRule = "LAST_INSTALLMENT"; + defaultAllocation = createDefaultPaymentAllocation(futureInstallmentAllocationRule); + + loanProductId = updateLoanProduct(loanProductId, defaultAllocation); + Assertions.assertNotNull(loanProductId); + + loanProduct = LOAN_TRANSACTION_HELPER.getLoanProduct(loanProductId); + Assertions.assertNotNull(loanProduct.getPaymentAllocation()); + + // verify allocation rule + Optional defaultAllocationAfterUpdate = loanProduct.getPaymentAllocation().stream() + .filter(advancedPaymentData -> "DEFAULT".equals(advancedPaymentData.getTransactionType())).findFirst(); + Assertions.assertTrue(defaultAllocationAfterUpdate.isPresent()); + Assertions.assertEquals(futureInstallmentAllocationRule, defaultAllocationAfterUpdate.get().getFutureInstallmentAllocationRule()); + + } + + private Integer updateLoanProduct(Integer loanProductId, AdvancedPaymentData... advancedPaymentData) { + final PutLoanProductsProductIdRequest requestModifyLoan = new PutLoanProductsProductIdRequest() + .transactionProcessingStrategyCode("advanced-payment-allocation-strategy") + .paymentAllocation(Arrays.stream(advancedPaymentData).toList()).locale("en"); + return LOAN_TRANSACTION_HELPER.updateLoanProduct(loanProductId.longValue(), requestModifyLoan).getResourceId().intValue(); + } + + private Integer createLoanProduct(AdvancedPaymentData... advancedPaymentData) { + String loanProductCreateJSON = new LoanProductTestBuilder().withPrincipal("15,000.00").withNumberOfRepayments("4") + .withRepaymentAfterEvery("1").withRepaymentTypeAsMonth().withinterestRatePerPeriod("1") + .withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualInstallments().withInterestTypeAsDecliningBalance() + .addAdvancedPaymentAllocation(advancedPaymentData).build(); + return LOAN_TRANSACTION_HELPER.getLoanProductId(loanProductCreateJSON); + + } + + private AdvancedPaymentData createDefaultPaymentAllocation(String futureInstallmentAllocationRule) { + AdvancedPaymentData advancedPaymentData = new AdvancedPaymentData(); + advancedPaymentData.setTransactionType("DEFAULT"); + advancedPaymentData.setFutureInstallmentAllocationRule(futureInstallmentAllocationRule); + + List paymentAllocationOrders = getPaymentAllocationOrder(PaymentAllocationType.PAST_DUE_PENALTY, + PaymentAllocationType.PAST_DUE_FEE, PaymentAllocationType.PAST_DUE_PRINCIPAL, PaymentAllocationType.PAST_DUE_INTEREST, + PaymentAllocationType.DUE_PENALTY, PaymentAllocationType.DUE_FEE, PaymentAllocationType.DUE_PRINCIPAL, + PaymentAllocationType.DUE_INTEREST, PaymentAllocationType.IN_ADVANCE_PENALTY, PaymentAllocationType.IN_ADVANCE_FEE, + PaymentAllocationType.IN_ADVANCE_PRINCIPAL, PaymentAllocationType.IN_ADVANCE_INTEREST); + + advancedPaymentData.setPaymentAllocationOrder(paymentAllocationOrders); + return advancedPaymentData; + } + + private List 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(); + } +}