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 093054b3d FINERACT-1981: Consolidated accrual activity created instead 
of individual ones
093054b3d is described below

commit 093054b3dbaf90b5dc66cc3d2b929101815902b9
Author: Andrii Kulminskyi <[email protected]>
AuthorDate: Mon Jan 20 14:02:38 2025 +0200

    FINERACT-1981: Consolidated accrual activity created instead of individual 
ones
---
 .../LoanAccrualActivityProcessingServiceImpl.java  |  74 +++++++++-----
 .../LoanTransactionAccrualActivityPostingTest.java |  11 +--
 .../MultiActivityAccrualsTest.java                 | 107 +++++++++++++++++++++
 3 files changed, 161 insertions(+), 31 deletions(-)

diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImpl.java
index f644b9048..4da472e38 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImpl.java
@@ -92,42 +92,66 @@ public class LoanAccrualActivityProcessingServiceImpl 
implements LoanAccrualActi
         if 
(!loan.getLoanProductRelatedDetail().isEnableAccrualActivityPosting()) {
             return;
         }
-        LocalDate transactionDate = loan.isOverPaid() ? 
loan.getOverpaidOnDate() : loan.getClosedOnDate();
-        // reverse after closure activities
-        loan.getLoanTransactions(t -> t.isAccrualActivity() && !t.isReversed() 
&& t.getDateOf().isAfter(transactionDate))
+
+        LocalDate closureDate = loan.isOverPaid() ? loan.getOverpaidOnDate() : 
loan.getClosedOnDate();
+
+        // Reverse accrual activities posted after the closure date
+        loan.getLoanTransactions(t -> t.isAccrualActivity() && !t.isReversed() 
&& t.getDateOf().isAfter(closureDate))
                 .forEach(this::reverseAccrualActivityTransaction);
 
-        // calculate activity amounts
         BigDecimal feeChargesPortion = BigDecimal.ZERO;
         BigDecimal penaltyChargesPortion = BigDecimal.ZERO;
         BigDecimal interestPortion = BigDecimal.ZERO;
-        // collect installment amounts
+
+        // Calculate total portions from all installments
         for (LoanRepaymentScheduleInstallment installment : 
loan.getRepaymentScheduleInstallments()) {
-            feeChargesPortion = MathUtil.add(feeChargesPortion, 
installment.getFeeChargesCharged());
-            penaltyChargesPortion = MathUtil.add(penaltyChargesPortion, 
installment.getPenaltyCharges());
-            interestPortion = MathUtil.add(interestPortion, 
installment.getInterestCharged());
+            if (!installment.isDownPayment()) { // Exclude downpayment 
installments
+                feeChargesPortion = MathUtil.add(feeChargesPortion, 
installment.getFeeChargesCharged());
+                penaltyChargesPortion = MathUtil.add(penaltyChargesPortion, 
installment.getPenaltyCharges());
+                interestPortion = MathUtil.add(interestPortion, 
installment.getInterestCharged());
+            }
         }
+
         List<LoanTransaction> accrualActivities = loan.getLoanTransactions(t 
-> t.isAccrualActivity() && !t.isReversed());
-        // subtract already posted activities
-        for (LoanTransaction accrualActivity : accrualActivities) {
-            if (MathUtil.isLessThan(feeChargesPortion, 
accrualActivity.getFeeChargesPortion())
-                    || MathUtil.isLessThan(penaltyChargesPortion, 
accrualActivity.getPenaltyChargesPortion())
-                    || MathUtil.isLessThan(interestPortion, 
accrualActivity.getInterestPortion())) {
-                reverseAccrualActivityTransaction(accrualActivity);
-            } else {
-                feeChargesPortion = MathUtil.subtract(feeChargesPortion, 
accrualActivity.getFeeChargesPortion());
-                penaltyChargesPortion = 
MathUtil.subtract(penaltyChargesPortion, 
accrualActivity.getPenaltyChargesPortion());
-                interestPortion = MathUtil.subtract(interestPortion, 
accrualActivity.getInterestPortion());
+
+        // Check each past installment for accrual activity
+        for (LoanRepaymentScheduleInstallment installment : 
loan.getRepaymentScheduleInstallments()) {
+            if (!installment.isDownPayment() && !installment.isAdditional() && 
installment.getDueDate().isBefore(closureDate)) {
+                List<LoanTransaction> installmentAccruals = 
accrualActivities.stream()
+                        .filter(t -> 
t.getDateOf().isEqual(installment.getDueDate())).toList();
+
+                if (installmentAccruals.isEmpty()) {
+                    // No AAT for this installment; create one
+                    makeAccrualActivityTransaction(loan, installment, 
installment.getDueDate());
+
+                    // Subtract processed portions
+                } else if (installmentAccruals.size() > 1) {
+                    // Reverse and recreate if inconsistent or duplicate
+                    
installmentAccruals.forEach(this::reverseAccrualActivityTransaction);
+                    makeAccrualActivityTransaction(loan, installment, 
installment.getDueDate());
+                } else if (!validateActivityTransaction(installment, 
installmentAccruals.get(0))) {
+                    reverseReplayAccrualActivityTransaction(loan, 
installmentAccruals.get(0), installment, installment.getDueDate());
+                }
             }
         }
-        BigDecimal transactionAmount = MathUtil.add(feeChargesPortion, 
penaltyChargesPortion, interestPortion);
-        if (!MathUtil.isGreaterThanZero(transactionAmount)) {
-            return;
+
+        // Subtract already posted accrual activities
+        accrualActivities = loan.getLoanTransactions(t -> 
t.isAccrualActivity() && !t.isReversed());
+        for (LoanTransaction accrualActivity : accrualActivities) {
+            feeChargesPortion = MathUtil.subtract(feeChargesPortion, 
accrualActivity.getFeeChargesPortion());
+            penaltyChargesPortion = MathUtil.subtract(penaltyChargesPortion, 
accrualActivity.getPenaltyChargesPortion());
+            interestPortion = MathUtil.subtract(interestPortion, 
accrualActivity.getInterestPortion());
+        }
+
+        // Skip final accrual activity creation if no portions remain
+        if (MathUtil.isGreaterThanZero(feeChargesPortion) || 
MathUtil.isGreaterThanZero(penaltyChargesPortion)
+                || MathUtil.isGreaterThanZero(interestPortion)) {
+            BigDecimal transactionAmount = MathUtil.add(feeChargesPortion, 
penaltyChargesPortion, interestPortion);
+            LoanTransaction newActivity = new LoanTransaction(loan, 
loan.getOffice(), LoanTransactionType.ACCRUAL_ACTIVITY.getValue(),
+                    closureDate, transactionAmount, null, interestPortion, 
feeChargesPortion, penaltyChargesPortion, null, false, null,
+                    externalIdFactory.create());
+            makeAccrualActivityTransaction(loan, newActivity);
         }
-        LoanTransaction newActivity = new LoanTransaction(loan, 
loan.getOffice(), LoanTransactionType.ACCRUAL_ACTIVITY.getValue(),
-                transactionDate, transactionAmount, null, interestPortion, 
feeChargesPortion, penaltyChargesPortion, null, false, null,
-                externalIdFactory.create());
-        makeAccrualActivityTransaction(loan, newActivity);
     }
 
     @Override
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionAccrualActivityPostingTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionAccrualActivityPostingTest.java
index 66301d4ac..e5c9e25d8 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionAccrualActivityPostingTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionAccrualActivityPostingTest.java
@@ -876,17 +876,16 @@ public class LoanTransactionAccrualActivityPostingTest 
extends BaseLoanIntegrati
             chargeFee(loanId.get(), 40.0, repaymentPeriod1DueDate);
             addRepaymentForLoan(loanId.get(), 650.0, repaymentDate1);
             verifyTransactions(loanId.get(),
-                    transaction(650.0, "Repayment", repaymentDate1, 0.0, 
500.0, 39.45, 40.0, 30.0, 0.0, 40.55, false), //
-                    transaction(109.45, "Accrual Activity", repaymentDate1, 
0.0, 0.0, 39.45, 40.0, 30.0, 0.0, 0.0, false), //
-                    transaction(109.45, "Accrual", repaymentDate1, 0.0, 0.0, 
39.45, 40.0, 30.0, 0.0, 0.0, false), //
-                    transaction(500.0, "Disbursement", disbursementDay, 500.0, 
0, 0, 0, 0, 0, 0, false) //
-            );
+                    transaction(650.0, "Repayment", repaymentDate1, 0.0, 
500.0, 39.45, 40.0, 30.0, 0.0, 40.55, false),
+                    transaction(109.45, "Accrual Activity", repaymentDate1, 
0.0, 0.0, 39.45, 40.0, 30.0, 0.0, 0.0, false),
+                    transaction(109.45, "Accrual", repaymentDate1, 0.0, 0.0, 
39.45, 40.0, 30.0, 0.0, 0.0, false),
+                    transaction(500.0, "Disbursement", disbursementDay, 500.0, 
0, 0, 0, 0, 0, 0, false));
         });
         runAt(repaymentPeriod1CloseDate, () -> {
             inlineLoanCOBHelper.executeInlineCOB(List.of(loanId.get()));
             verifyTransactions(loanId.get(),
                     transaction(650.0, "Repayment", repaymentDate1, 0.0, 
500.0, 39.45, 40.0, 30.0, 0.0, 40.55, false), //
-                    transaction(109.45, "Accrual Activity", repaymentDate1, 
0.0, 0.0, 39.45, 40.0, 30.0, 0.0, 0.0, false), //
+                    transaction(109.45, "Accrual Activity", repaymentDate1, 
0.0, 0.0, 39.45, 40.0, 30.0, 0.0, 0.0, false),
                     transaction(109.45, "Accrual", repaymentDate1, 0.0, 0.0, 
39.45, 40.0, 30.0, 0.0, 0.0, false), //
                     transaction(500.0, "Disbursement", disbursementDay, 500.0, 
0, 0, 0, 0, 0, 0, false) //
             );
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/MultiActivityAccrualsTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/MultiActivityAccrualsTest.java
new file mode 100644
index 000000000..954a1e054
--- /dev/null
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/MultiActivityAccrualsTest.java
@@ -0,0 +1,107 @@
+/**
+ * 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.assertFalse;
+
+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.List;
+import org.apache.fineract.client.models.GetLoansLoanIdResponse;
+import org.apache.fineract.client.models.GetLoansLoanIdTransactions;
+import org.apache.fineract.client.models.PostClientsResponse;
+import org.apache.fineract.client.models.PostLoanProductsResponse;
+import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
+import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class MultiActivityAccrualsTest extends BaseLoanIntegrationTest {
+
+    private static final String disbursementDate = "9 August 2024";
+    private static final String repaymentDate = "9 December 2024";
+    private static final Double fullRepaymentAmount = 700.0;
+    private static final Integer expectedNumberOfAccruals = 1;
+    private static final Integer expectedNumberOfActivityAccruals = 4;
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+    private ClientHelper clientHelper;
+    private LoanTransactionHelper loanTransactionHelper;
+    private static Long loanId;
+
+    @BeforeEach
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new 
RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + 
Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new 
ResponseSpecBuilder().expectStatusCode(200).build();
+        this.loanTransactionHelper = new 
LoanTransactionHelper(this.requestSpec, this.responseSpec);
+        this.clientHelper = new ClientHelper(this.requestSpec, 
this.responseSpec);
+
+        PostClientsResponse client = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
+        PostLoanProductsResponse loanProduct = loanProductHelper
+                
.createLoanProduct(create4IProgressive().currencyCode("USD").enableAccrualActivityPosting(true));
+
+        loanId = applyAndApproveProgressiveLoan(client.getClientId(), 
loanProduct.getResourceId(), disbursementDate, 600.0, 9.99, 4, null);
+        Assertions.assertNotNull(loanId);
+        disburseLoan(loanId, BigDecimal.valueOf(600), disbursementDate);
+    }
+
+    @Test
+    public void testMultiAccrualActivityCreated() {
+        runAt(repaymentDate, () -> {
+
+            final PostLoansLoanIdTransactionsResponse repaymentTransaction = 
loanTransactionHelper.makeLoanRepayment(loanId,
+                    new PostLoansLoanIdTransactionsRequest().dateFormat("dd 
MMMM yyyy").transactionDate(repaymentDate).locale("en")
+                            .transactionAmount(fullRepaymentAmount));
+
+            GetLoansLoanIdResponse loanDetails = 
loanTransactionHelper.getLoanDetails(loanId);
+
+            List<GetLoansLoanIdTransactions> accrualTransactional = 
loanDetails.getTransactions().stream()
+                    .filter(transaction -> 
transaction.getType().getCode().equals("loanTransactionType.accrual")).toList();
+
+            List<GetLoansLoanIdTransactions> accrualActivityTransactional = 
loanDetails.getTransactions().stream()
+                    .filter(transaction -> 
transaction.getType().getCode().equals("loanTransactionType.accrualActivity")).toList();
+
+            assertFalse(accrualTransactional.isEmpty());
+            assertEquals(expectedNumberOfAccruals, 
accrualTransactional.size());
+            assertFalse(accrualActivityTransactional.isEmpty());
+            assertEquals(expectedNumberOfActivityAccruals, 
accrualActivityTransactional.size());
+
+            verifyTransactions(loanId, //
+                    transaction(600.0, "Disbursement", "09 August 2024", 
600.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
+                    transaction(20.00, "Accrual", "09 December 2024", 0, 0, 
20.00, 0, 0, 0.0, 0.0),
+                    transaction(5.0, "Accrual Activity", "09 September 2024", 
0, 0, 5.0, 0, 0, 0.0, 0.0),
+                    transaction(5.0, "Accrual Activity", "09 October 2024", 0, 
0, 5.0, 0, 0, 0.0, 0.0),
+                    transaction(5.0, "Accrual Activity", "09 November 2024", 
0, 0, 5.0, 0, 0, 0.0, 0.0),
+                    transaction(5.0, "Accrual Activity", "09 December 2024", 
0, 0, 5.0, 0, 0, 0.0, 0.0),
+                    transaction(700.00, "Repayment", "09 December 2024", 0, 
600.00, 20.0, 0, 0, 0.0, 80.0));
+        });
+    }
+}

Reply via email to