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 31a7e3644 FINERACT-1968: Adding Installment percentage fee Adv Payment 
allocation
31a7e3644 is described below

commit 31a7e3644d80374fc9fc373bc7892476f8e5ab2f
Author: Ruchi Dhamankar <[email protected]>
AuthorDate: Thu Dec 7 18:50:14 2023 +0530

    FINERACT-1968: Adding Installment percentage fee Adv Payment allocation
---
 .../LoanChargeWritePlatformServiceImpl.java        |  13 ++
 ...rHandlingWithAdvancedPaymentAllocationTest.java | 180 +++++++++++++++++++++
 .../common/loans/LoanTransactionHelper.java        |   6 +
 3 files changed, 199 insertions(+)

diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java
index 5769ca3f4..5e96c43d9 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java
@@ -72,6 +72,7 @@ import 
org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatfo
 import org.apache.fineract.portfolio.accountdetails.domain.AccountType;
 import org.apache.fineract.portfolio.charge.domain.Charge;
 import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
+import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
 import 
org.apache.fineract.portfolio.charge.exception.ChargeCannotBeAppliedToException;
 import 
org.apache.fineract.portfolio.charge.exception.ChargeCannotBeUpdatedException;
 import 
org.apache.fineract.portfolio.charge.exception.LoanChargeCannotBeAddedException;
@@ -183,6 +184,18 @@ public class LoanChargeWritePlatformServiceImpl implements 
LoanChargeWritePlatfo
         final Long chargeDefinitionId = 
command.longValueOfParameterNamed("chargeId");
         final Charge chargeDefinition = 
this.chargeRepository.findOneWithNotFoundDetection(chargeDefinitionId);
 
+        /*
+         * TODO: remove this check once handling for Installment fee charges 
is implemented for Advanced Payment
+         * strategy
+         */
+        if 
(ChargeTimeType.fromInt(chargeDefinition.getChargeTimeType()).isInstalmentFee()
+                && 
AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY
+                        .equals(loan.transactionProcessingStrategy())) {
+            final String errorMessageInstallmentChargeNotSupported = "Charge 
with identifier " + chargeDefinition.getId()
+                    + " cannot be applied: Installment fee charges are not 
supported for Advanced payment allocation strategy";
+            throw new ChargeCannotBeAppliedToException("loan", 
errorMessageInstallmentChargeNotSupported, chargeDefinition.getId());
+        }
+
         if (loan.isDisbursed() && chargeDefinition.isDisbursementCharge()) {
             // validates whether any pending disbursements are available to
             // apply this charge
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeTypeInstallmentFeeErrorHandlingWithAdvancedPaymentAllocationTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeTypeInstallmentFeeErrorHandlingWithAdvancedPaymentAllocationTest.java
new file mode 100644
index 000000000..af8a09846
--- /dev/null
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeTypeInstallmentFeeErrorHandlingWithAdvancedPaymentAllocationTest.java
@@ -0,0 +1,180 @@
+/**
+ * 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 static org.junit.jupiter.api.Assertions.assertNotNull;
+
+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.time.LocalDate;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.apache.fineract.client.models.AdvancedPaymentData;
+import org.apache.fineract.client.models.PaymentAllocationOrder;
+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.CommonConstants;
+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.LoanApplicationTestBuilder;
+import 
org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
+import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+public class 
LoanChargeTypeInstallmentFeeErrorHandlingWithAdvancedPaymentAllocationTest {
+
+    private static LoanTransactionHelper LOAN_TRANSACTION_HELPER;
+    private static ResponseSpecification RESPONSE_SPEC;
+    private static RequestSpecification REQUEST_SPEC;
+    private static ClientHelper CLIENT_HELPER;
+    private static AccountHelper ACCOUNT_HELPER;
+
+    @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);
+        CLIENT_HELPER = new ClientHelper(REQUEST_SPEC, RESPONSE_SPEC);
+        ACCOUNT_HELPER = new AccountHelper(REQUEST_SPEC, RESPONSE_SPEC);
+    }
+
+    /*
+     * TODO: To be disabled when Installment Fee Charges are handled for 
Advanced Payment Allocation
+     */
+    @Test
+    public void 
addingLoanChargeTypeInstallmentFeeForAdvancedPaymentAllocationGivesErrorTest() {
+        try {
+            // Set business date
+            LocalDate businessDate = LocalDate.of(2023, 3, 15);
+
+            
GlobalConfigurationHelper.updateIsBusinessDateEnabled(REQUEST_SPEC, 
RESPONSE_SPEC, Boolean.TRUE);
+            BusinessDateHelper.updateBusinessDate(REQUEST_SPEC, RESPONSE_SPEC, 
BusinessDateType.BUSINESS_DATE, businessDate);
+
+            // Accounts oof periodic accrual
+            final Account assetAccount = ACCOUNT_HELPER.createAssetAccount();
+            final Account incomeAccount = ACCOUNT_HELPER.createIncomeAccount();
+            final Account expenseAccount = 
ACCOUNT_HELPER.createExpenseAccount();
+            final Account overpaymentAccount = 
ACCOUNT_HELPER.createLiabilityAccount();
+
+            final ResponseSpecification errorResponse = new 
ResponseSpecBuilder().expectStatusCode(403).build();
+            final LoanTransactionHelper validationErrorHelper = new 
LoanTransactionHelper(REQUEST_SPEC, errorResponse);
+
+            // Loan ExternalId
+            String loanExternalIdStr = UUID.randomUUID().toString();
+
+            final Integer clientId = 
CLIENT_HELPER.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue();
+
+            final Integer loanProductId = createLoanProduct(assetAccount, 
incomeAccount, expenseAccount, overpaymentAccount);
+
+            final Integer loanId = createLoanAccount(clientId, loanProductId, 
loanExternalIdStr);
+
+            // disburse principal amount
+            LOAN_TRANSACTION_HELPER.disburseLoanWithTransactionAmount("15 
February 2023", loanId, "1000");
+
+            // add loan charge
+            // apply Installment fee
+            Integer installmentFeeCharge = 
ChargesHelper.createCharges(REQUEST_SPEC, RESPONSE_SPEC,
+                    
ChargesHelper.getLoanInstallmentJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT,
 "50", false));
+
+            List<HashMap<String, Object>> loanChargeErrorData = 
(List<HashMap<String, Object>>) validationErrorHelper
+                    .addChargesForLoanWithError(loanId,
+                            
LoanTransactionHelper.getInstallmentChargesForLoanAsJSON(String.valueOf(installmentFeeCharge),
 "50"),
+                            CommonConstants.RESPONSE_ERROR);
+            assertNotNull(loanChargeErrorData);
+
+            assertEquals(
+                    "Charge with identifier %d cannot be applied: Installment 
fee charges are not supported for Advanced payment allocation strategy"
+                            .formatted(installmentFeeCharge),
+                    loanChargeErrorData.get(0).get("defaultUserMessage"));
+            assertEquals("error.msg.charge.cannot.be.applied.toloan",
+                    
loanChargeErrorData.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
+
+        } finally {
+            
GlobalConfigurationHelper.updateIsBusinessDateEnabled(REQUEST_SPEC, 
RESPONSE_SPEC, Boolean.FALSE);
+        }
+    }
+
+    private Integer createLoanProduct(final Account... accounts) {
+        String futureInstallmentAllocationRule = "NEXT_INSTALLMENT";
+        AdvancedPaymentData defaultAllocation = 
createDefaultPaymentAllocation(futureInstallmentAllocationRule);
+        String loanProductCreateJSON = new 
LoanProductTestBuilder().withPrincipal("15,000.00").withNumberOfRepayments("4")
+                
.withRepaymentAfterEvery("1").withRepaymentTypeAsMonth().withinterestRatePerPeriod("0")
+                
.withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualInstallments().withInterestTypeAsDecliningBalance()
+                
.withAccountingRulePeriodicAccrual(accounts).withInterestCalculationPeriodTypeAsRepaymentPeriod(true)
+                
.addAdvancedPaymentAllocation(defaultAllocation).withLoanScheduleType(LoanScheduleType.PROGRESSIVE).withMultiDisburse()
+                .withDisallowExpectedDisbursements(true).build();
+        return LOAN_TRANSACTION_HELPER.getLoanProductId(loanProductCreateJSON);
+
+    }
+
+    private AdvancedPaymentData createDefaultPaymentAllocation(String 
futureInstallmentAllocationRule) {
+        AdvancedPaymentData advancedPaymentData = new AdvancedPaymentData();
+        advancedPaymentData.setTransactionType("DEFAULT");
+        
advancedPaymentData.setFutureInstallmentAllocationRule(futureInstallmentAllocationRule);
+
+        List<PaymentAllocationOrder> 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<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 Integer createLoanAccount(final Integer clientID, final Integer 
loanProductID, final String externalId) {
+
+        String loanApplicationJSON = new 
LoanApplicationTestBuilder().withPrincipal("1000").withLoanTermFrequency("60")
+                
.withLoanTermFrequencyAsDays().withNumberOfRepayments("4").withRepaymentEveryAfter("15").withRepaymentFrequencyTypeAsDays()
+                
.withInterestRatePerPeriod("0").withInterestTypeAsFlatBalance().withAmortizationTypeAsEqualPrincipalPayments()
+                
.withInterestCalculationPeriodTypeSameAsRepaymentPeriod().withExpectedDisbursementDate("15
 February 2023")
+                .withSubmittedOnDate("15 February 
2023").withLoanType("individual").withExternalId(externalId)
+                
.withRepaymentStrategy("advanced-payment-allocation-strategy").build(clientID.toString(),
 loanProductID.toString(), null);
+
+        final Integer loanId = 
LOAN_TRANSACTION_HELPER.getLoanId(loanApplicationJSON);
+        LOAN_TRANSACTION_HELPER.approveLoan("15 February 2023", "1000", 
loanId, null);
+        return loanId;
+    }
+}
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 4794baf70..ba30cc35b 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
@@ -1965,4 +1965,10 @@ public class LoanTransactionHelper extends 
IntegrationTest {
             final PostLoansLoanIdTransactionsRequest request) {
         return 
ok(fineract().loanTransactions.executeLoanTransaction1(loanExternalId, request, 
"writeoff"));
     }
+
+    public Object addChargesForLoanWithError(final Integer loanId, final 
String request, final String jsonAttributeToGetBack) {
+        log.info("--------------------------------- ADD CHARGES FOR LOAN 
--------------------------------");
+        final String ADD_CHARGES_URL = LOAN_ACCOUNT_URL + "/" + loanId + 
"/charges?" + Utils.TENANT_IDENTIFIER;
+        return Utils.performServerPost(requestSpec, responseSpec, 
ADD_CHARGES_URL, request, jsonAttributeToGetBack);
+    }
 }

Reply via email to