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 32cc83fdb FINERACT-2153: Enable immediate charge accrual creation post 
maturity
32cc83fdb is described below

commit 32cc83fdbfe45b991d383892eb14ed904f3e5c5e
Author: leksinomi <[email protected]>
AuthorDate: Tue Nov 26 13:31:30 2024 +0200

    FINERACT-2153: Enable immediate charge accrual creation post maturity
---
 .../api/GlobalConfigurationConstants.java          |   1 +
 .../domain/ConfigurationDomainService.java         |   2 +
 .../domain/ConfigurationDomainServiceJpa.java      |   4 +
 .../LoanChargeWritePlatformServiceImpl.java        |   7 +-
 .../db/changelog/tenant/changelog-tenant.xml       |   1 +
 ...able_immediate_charge_accrual_post_maturity.xml |  41 +++++
 .../LoanChargeWritePlatformServiceImplTest.java    | 169 +++++++++++++++++++++
 .../common/GlobalConfigurationHelper.java          |  11 +-
 8 files changed, 233 insertions(+), 3 deletions(-)

diff --git 
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/api/GlobalConfigurationConstants.java
 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/api/GlobalConfigurationConstants.java
index e2ab1715a..82d91b498 100644
--- 
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/api/GlobalConfigurationConstants.java
+++ 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/api/GlobalConfigurationConstants.java
@@ -75,6 +75,7 @@ public final class GlobalConfigurationConstants {
     public static final String ENABLE_SAME_MAKER_CHECKER = 
"enable-same-maker-checker";
     public static final String NEXT_PAYMENT_DUE_DATE = "next-payment-due-date";
     public static final String ENABLE_PAYMENT_HUB_INTEGRATION = 
"enable-payment-hub-integration";
+    public static final String ENABLE_IMMEDIATE_CHARGE_ACCRUAL_POST_MATURITY = 
"enable-immediate-charge-accrual-post-maturity";
 
     private GlobalConfigurationConstants() {}
 }
diff --git 
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java
 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java
index 164986e87..e7482d16a 100644
--- 
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java
+++ 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java
@@ -143,4 +143,6 @@ public interface ConfigurationDomainService {
 
     String getNextPaymentDateConfigForLoan();
 
+    boolean isImmediateChargeAccrualPostMaturityEnabled();
+
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java
index 386fc6635..2e8558184 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java
@@ -523,4 +523,8 @@ public class ConfigurationDomainServiceJpa implements 
ConfigurationDomainService
         return value;
     }
 
+    @Override
+    public boolean isImmediateChargeAccrualPostMaturityEnabled() {
+        return 
getGlobalConfigurationPropertyData(GlobalConfigurationConstants.ENABLE_IMMEDIATE_CHARGE_ACCRUAL_POST_MATURITY).isEnabled();
+    }
 }
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 75f86c50a..ed0203718 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
@@ -55,6 +55,7 @@ import 
org.apache.fineract.infrastructure.event.business.domain.loan.charge.Loan
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.charge.LoanUpdateChargeBusinessEvent;
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.charge.LoanWaiveChargeBusinessEvent;
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.charge.LoanWaiveChargeUndoBusinessEvent;
+import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanAccrualTransactionCreatedBusinessEvent;
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanChargeAdjustmentPostBusinessEvent;
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanChargeAdjustmentPreBusinessEvent;
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanChargeRefundBusinessEvent;
@@ -1033,9 +1034,13 @@ public class LoanChargeWritePlatformServiceImpl 
implements LoanChargeWritePlatfo
         // we want to apply charge transactions only for those loans charges 
that are applied when a loan is active and
         // the loan product uses Upfront Accruals, or only when the loan are 
closed too,
         if ((loan.getStatus().isActive() && 
loan.isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct())
-                || loan.getStatus().isOverpaid() || 
loan.getStatus().isClosedObligationsMet()) {
+                || loan.getStatus().isOverpaid() || 
loan.getStatus().isClosedObligationsMet()
+                || 
(configurationDomainService.isImmediateChargeAccrualPostMaturityEnabled()
+                        && 
DateUtils.getBusinessLocalDate().isAfter(loan.getMaturityDate()))) {
             final LoanTransaction applyLoanChargeTransaction = 
loan.handleChargeAppliedTransaction(loanCharge, null);
             
this.loanTransactionRepository.saveAndFlush(applyLoanChargeTransaction);
+            businessEventNotifierService
+                    .notifyPostBusinessEvent(new 
LoanAccrualTransactionCreatedBusinessEvent(applyLoanChargeTransaction));
         }
         return DateUtils.isBeforeBusinessDate(loanCharge.getDueLocalDate());
     }
diff --git 
a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml 
b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
index 340df505e..af723c51a 100644
--- 
a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
+++ 
b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
@@ -173,4 +173,5 @@
     <include file="parts/0152_update_password_validation_policy.xml" 
relativeToChangelogFile="true" />
     <include 
file="parts/0153_add_charge_off_reason_id_to_acc_product_mapping.xml" 
relativeToChangelogFile="true" />
     <include file="parts/0154_add_interest_refund_to_r_enum_value.xml" 
relativeToChangelogFile="true" />
+    <include 
file="parts/0155_add_configuration_enable_immediate_charge_accrual_post_maturity.xml"
 relativeToChangelogFile="true" />
 </databaseChangeLog>
diff --git 
a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0155_add_configuration_enable_immediate_charge_accrual_post_maturity.xml
 
b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0155_add_configuration_enable_immediate_charge_accrual_post_maturity.xml
new file mode 100644
index 000000000..3080942d9
--- /dev/null
+++ 
b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0155_add_configuration_enable_immediate_charge_accrual_post_maturity.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog";
+                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+                   
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog 
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.3.xsd";>
+    <changeSet author="fineract" id="1" context="postgresql">
+        <sql>
+            SELECT SETVAL('c_configuration_id_seq', COALESCE(MAX(id), 0)+1, 
false ) FROM c_configuration;
+        </sql>
+    </changeSet>
+    <changeSet author="fineract" id="2">
+        <insert tableName="c_configuration">
+            <column name="name" 
value="enable-immediate-charge-accrual-post-maturity"/>
+            <column name="value"/>
+            <column name="date_value"/>
+            <column name="string_value"/>
+            <column name="enabled" valueBoolean="false"/>
+            <column name="is_trap_door" valueBoolean="false"/>
+            <column name="description" value="Whether the system creates 
accruals immediately for charge creation after the maturity date"/>
+        </insert>
+    </changeSet>
+</databaseChangeLog>
diff --git 
a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImplTest.java
 
b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImplTest.java
new file mode 100644
index 000000000..f2d74fbb9
--- /dev/null
+++ 
b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImplTest.java
@@ -0,0 +1,169 @@
+/**
+ * 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.portfolio.loanaccount.service;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.time.LocalDate;
+import java.util.stream.Stream;
+import 
org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
+import 
org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanAccrualTransactionCreatedBusinessEvent;
+import 
org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.portfolio.charge.domain.Charge;
+import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
+import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import 
org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeRepository;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
+import 
org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeApiJsonValidator;
+import 
org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.junit.jupiter.MockitoSettings;
+import org.mockito.quality.Strictness;
+
+@ExtendWith(MockitoExtension.class)
+@MockitoSettings(strictness = Strictness.LENIENT)
+class LoanChargeWritePlatformServiceImplTest {
+
+    private static final Long LOAN_ID = 1L;
+    private static final Integer SPECIFIED_DUE_DATE = 2;
+    private static final LocalDate MATURITY_DATE = LocalDate.of(2024, 2, 15);
+    private static final LocalDate BUSINESS_DATE_AFTER = LocalDate.of(2024, 2, 
26);
+    private static final LocalDate BUSINESS_DATE_ON = MATURITY_DATE;
+    private static final LocalDate BUSINESS_DATE_BEFORE = LocalDate.of(2024, 
2, 14);
+
+    @InjectMocks
+    private LoanChargeWritePlatformServiceImpl loanChargeWritePlatformService;
+
+    @Mock
+    private JsonCommand jsonCommand;
+
+    @Mock
+    private LoanChargeApiJsonValidator loanChargeApiJsonValidator;
+
+    @Mock
+    private LoanAssembler loanAssembler;
+
+    @Mock
+    private Loan loan;
+
+    @Mock
+    private ChargeRepositoryWrapper chargeRepository;
+
+    @Mock
+    private Charge chargeDefinition;
+
+    @Mock
+    private LoanChargeAssembler loanChargeAssembler;
+
+    @Mock
+    private LoanCharge loanCharge;
+
+    @Mock
+    private BusinessEventNotifierService businessEventNotifierService;
+
+    @Mock
+    private LoanProductRelatedDetail loanRepaymentScheduleDetail;
+
+    @Mock
+    private LoanChargeRepository loanChargeRepository;
+
+    @Mock
+    private ConfigurationDomainService configurationDomainService;
+
+    @Mock
+    private LoanTransactionRepository loanTransactionRepository;
+
+    @Mock
+    private LoanTransaction loanTransaction;
+
+    @Mock
+    private LoanAccountDomainService loanAccountDomainService;
+
+    @Mock
+    private MonetaryCurrency monetaryCurrency;
+
+    @Mock
+    private JournalEntryWritePlatformService journalEntryWritePlatformService;
+
+    @BeforeEach
+    void setUp() {
+        when(loanAssembler.assembleFrom(LOAN_ID)).thenReturn(loan);
+        
when(chargeRepository.findOneWithNotFoundDetection(anyLong())).thenReturn(chargeDefinition);
+        
when(chargeDefinition.getChargeTimeType()).thenReturn(SPECIFIED_DUE_DATE);
+        when(loanChargeAssembler.createNewFromJson(loan, chargeDefinition, 
jsonCommand)).thenReturn(loanCharge);
+        
when(loan.repaymentScheduleDetail()).thenReturn(loanRepaymentScheduleDetail);
+        when(loan.hasCurrencyCodeOf(any())).thenReturn(true);
+        
when(loanCharge.getChargePaymentMode()).thenReturn(ChargePaymentMode.REGULAR);
+        when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE);
+        
when(loanChargeRepository.saveAndFlush(any(LoanCharge.class))).thenReturn(loanCharge);
+        when(loan.getCurrency()).thenReturn(monetaryCurrency);
+        
when(loanAccountDomainService.saveAndFlushLoanWithDataIntegrityViolationChecks(any())).thenReturn(loan);
+    }
+
+    @ParameterizedTest
+    @MethodSource("loanChargeAccrualTestCases")
+    void shouldHandleAccrualBasedOnConfigurationAndDates(boolean 
isAccrualEnabled, LocalDate businessDate, LocalDate maturityDate, boolean 
isAccrualExpected) {
+        
when(configurationDomainService.isImmediateChargeAccrualPostMaturityEnabled()).thenReturn(isAccrualEnabled);
+        when(loan.getMaturityDate()).thenReturn(maturityDate);
+        when(loan.handleChargeAppliedTransaction(loanCharge, 
null)).thenReturn(loanTransaction);
+
+        try (MockedStatic<DateUtils> mockedDateUtils = 
mockStatic(DateUtils.class)) {
+            
mockedDateUtils.when(DateUtils::getBusinessLocalDate).thenReturn(businessDate);
+
+            loanChargeWritePlatformService.addLoanCharge(LOAN_ID, jsonCommand);
+        }
+
+        if (isAccrualExpected) {
+            verify(loanTransactionRepository, 
times(1)).saveAndFlush(any(LoanTransaction.class));
+            verify(businessEventNotifierService, 
times(1)).notifyPostBusinessEvent(any(LoanAccrualTransactionCreatedBusinessEvent.class));
+        } else {
+            verify(loanTransactionRepository, 
never()).saveAndFlush(any(LoanTransaction.class));
+            verify(businessEventNotifierService, 
never()).notifyPostBusinessEvent(any(LoanAccrualTransactionCreatedBusinessEvent.class));
+        }
+    }
+
+    private static Stream<Arguments> loanChargeAccrualTestCases() {
+        return Stream.of(Arguments.of(true, BUSINESS_DATE_AFTER, 
MATURITY_DATE, true),
+                Arguments.of(false, BUSINESS_DATE_AFTER, MATURITY_DATE, 
false), Arguments.of(true, BUSINESS_DATE_ON, MATURITY_DATE, false),
+                Arguments.of(true, BUSINESS_DATE_BEFORE, MATURITY_DATE, 
false));
+    }
+}
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java
index 51f6fb0ae..2e88b4967 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java
@@ -100,8 +100,8 @@ public class GlobalConfigurationHelper extends 
IntegrationTest {
         ArrayList<HashMap> expectedGlobalConfigurations = 
getAllDefaultGlobalConfigurations();
         GetGlobalConfigurationsResponse actualGlobalConfigurations = 
getAllGlobalConfigurations();
 
-        Assertions.assertEquals(55, expectedGlobalConfigurations.size());
-        Assertions.assertEquals(55, 
actualGlobalConfigurations.getGlobalConfiguration().size());
+        Assertions.assertEquals(56, expectedGlobalConfigurations.size());
+        Assertions.assertEquals(56, 
actualGlobalConfigurations.getGlobalConfiguration().size());
 
         for (int i = 0; i < expectedGlobalConfigurations.size(); i++) {
 
@@ -528,6 +528,13 @@ public class GlobalConfigurationHelper extends 
IntegrationTest {
         enablePaymentHubIntegrationConfig.put("string_value", "enable payment 
hub integration");
         defaults.add(enablePaymentHubIntegrationConfig);
 
+        HashMap<String, Object> enableImmediateChargeAccrualPostMaturity = new 
HashMap<>();
+        enableImmediateChargeAccrualPostMaturity.put("name", 
GlobalConfigurationConstants.ENABLE_IMMEDIATE_CHARGE_ACCRUAL_POST_MATURITY);
+        enableImmediateChargeAccrualPostMaturity.put("value", 0L);
+        enableImmediateChargeAccrualPostMaturity.put("enabled", false);
+        enableImmediateChargeAccrualPostMaturity.put("trapDoor", false);
+        defaults.add(enableImmediateChargeAccrualPostMaturity);
+
         return defaults;
     }
 

Reply via email to