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));
+ });
+ }
+}