This is an automated email from the ASF dual-hosted git repository.

bagrijp 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 c34f2b4f5 FINERACT-2068: Loan Status Change History
c34f2b4f5 is described below

commit c34f2b4f59db708bf99490cb78af52df313e8f5f
Author: Peter Bagrij <[email protected]>
AuthorDate: Wed Mar 20 12:22:06 2024 +0100

    FINERACT-2068: Loan Status Change History
---
 .../core/config/FineractProperties.java            |   1 +
 .../domain/LoanStatusChangeHistory.java            |  55 ++++++
 .../domain/LoanStatusChangeHistoryRepository.java  |  25 +++
 .../tenant/module/loan/module-changelog-master.xml |   1 +
 .../parts/1021_add_loan_status_change_history.xml  |  89 +++++++++
 .../service/LoanStatusChangeHistoryListener.java   |  94 +++++++++
 .../src/main/resources/application.properties      |   4 +
 .../LoanStatusChangeHistoryListenerTest.java       | 210 +++++++++++++++++++++
 .../src/test/resources/application-test.properties |   1 +
 9 files changed, 480 insertions(+)

diff --git 
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java
 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java
index 7a5d647c8..beb28d0f9 100644
--- 
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java
+++ 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java
@@ -449,6 +449,7 @@ public class FineractProperties {
     public static class FineractLoanProperties {
 
         private FineractTransactionProcessorProperties transactionProcessor;
+        private String statusChangeHistoryStatuses;
     }
 
     @Getter
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanStatusChangeHistory.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanStatusChangeHistory.java
new file mode 100644
index 000000000..371a0915e
--- /dev/null
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanStatusChangeHistory.java
@@ -0,0 +1,55 @@
+/**
+ * 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.domain;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.EnumType;
+import jakarta.persistence.Enumerated;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.ManyToOne;
+import jakarta.persistence.Table;
+import java.time.LocalDate;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import 
org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
+
+@Entity
+@Table(name = "m_loan_status_change_history")
+@Getter
+@AllArgsConstructor
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+public class LoanStatusChangeHistory extends 
AbstractAuditableWithUTCDateTimeCustom {
+
+    @ManyToOne
+    @JoinColumn(name = "loan_id", nullable = false)
+    private Loan loan;
+
+    @Enumerated(EnumType.STRING)
+    @Column(name = "status_code", nullable = false)
+    @Setter
+    private LoanStatus status;
+
+    @Column(name = "status_change_business_date", nullable = false)
+    private LocalDate businessDate;
+
+}
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanStatusChangeHistoryRepository.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanStatusChangeHistoryRepository.java
new file mode 100644
index 000000000..45560d202
--- /dev/null
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanStatusChangeHistoryRepository.java
@@ -0,0 +1,25 @@
+/**
+ * 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.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface LoanStatusChangeHistoryRepository
+        extends JpaRepository<LoanStatusChangeHistory, Long>, 
JpaSpecificationExecutor<LoanStatusChangeHistory> {}
diff --git 
a/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml
 
b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml
index 9655538f0..4fffd9b3e 100644
--- 
a/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml
+++ 
b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml
@@ -43,4 +43,5 @@
   <include relativeToChangelogFile="true" 
file="parts/1018_rename_credited_principal_back_to_credits_amount.xml"/>
   <include relativeToChangelogFile="true" 
file="parts/1019_add_fixed_length.xml"/>
   <include relativeToChangelogFile="true" 
file="parts/1020_add_re_aged_flag_to_loan_installment.xml"/>
+  <include relativeToChangelogFile="true" 
file="parts/1021_add_loan_status_change_history.xml"/>
 </databaseChangeLog>
diff --git 
a/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1021_add_loan_status_change_history.xml
 
b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1021_add_loan_status_change_history.xml
new file mode 100644
index 000000000..413bcf353
--- /dev/null
+++ 
b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1021_add_loan_status_change_history.xml
@@ -0,0 +1,89 @@
+<?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="1021-1">
+        <createTable tableName="m_loan_status_change_history">
+            <column autoIncrement="true" name="id" type="BIGINT">
+                <constraints nullable="false" primaryKey="true"/>
+            </column>
+            <column name="loan_id" type="BIGINT">
+                <constraints nullable="false" 
foreignKeyName="m_loan_status_change_history_fk" referencedTableName="m_loan" 
referencedColumnNames="id"/>
+            </column>
+            <column name="status_code" type="VARCHAR(255)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="status_change_business_date" type="date">
+                <constraints nullable="false"/>
+            </column>
+            <column name="created_by" type="BIGINT">
+                <constraints nullable="false"/>
+            </column>
+            <column name="last_modified_by" type="BIGINT">
+                <constraints nullable="false"/>
+            </column>
+        </createTable>
+    </changeSet>
+    <changeSet author="fineract" id="1021-2" context="mysql">
+        <addColumn tableName="m_loan_status_change_history">
+            <column name="created_on_utc" type="DATETIME(6)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="last_modified_on_utc" type="DATETIME(6)">
+                <constraints nullable="false"/>
+            </column>
+        </addColumn>
+    </changeSet>
+    <changeSet author="fineract" id="1021-2" context="postgresql">
+        <addColumn tableName="m_loan_status_change_history">
+            <column name="created_on_utc" type="TIMESTAMP WITH TIME ZONE">
+                <constraints nullable="false"/>
+            </column>
+            <column name="last_modified_on_utc" type="TIMESTAMP WITH TIME 
ZONE">
+                <constraints nullable="false"/>
+            </column>
+        </addColumn>
+    </changeSet>
+    <changeSet author="fineract" id="1021-3">
+        <addForeignKeyConstraint baseColumnNames="created_by" 
baseTableName="m_loan_status_change_history"
+                                 
constraintName="FK_oan_status_change_history_created_by" deferrable="false" 
initiallyDeferred="false"
+                                 onDelete="RESTRICT" onUpdate="RESTRICT" 
referencedColumnNames="id"
+                                 referencedTableName="m_appuser" 
validate="true"/>
+        <addForeignKeyConstraint baseColumnNames="last_modified_by" 
baseTableName="m_loan_status_change_history"
+                                 
constraintName="FK_loan_status_change_history_last_modified_by" 
deferrable="false"
+                                 initiallyDeferred="false"
+                                 onDelete="RESTRICT" onUpdate="RESTRICT" 
referencedColumnNames="id"
+                                 referencedTableName="m_appuser" 
validate="true"/>
+    </changeSet>
+    <changeSet author="fineract" id="1021-4">
+        <createIndex tableName="m_loan_status_change_history" 
indexName="IND_loan_status_change_history_loan_id">
+            <column name="loan_id"/>
+        </createIndex>
+        <createIndex tableName="m_loan_status_change_history" 
indexName="IND_loan_status_change_hist_status_code">
+            <column name="status_code"/>
+        </createIndex>
+        <createIndex tableName="m_loan_status_change_history" 
indexName="IND_loan_status_change_hist_s_change_bus_date">
+            <column name="status_change_business_date"/>
+        </createIndex>
+    </changeSet>
+</databaseChangeLog>
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanStatusChangeHistoryListener.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanStatusChangeHistoryListener.java
new file mode 100644
index 000000000..1048d620a
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanStatusChangeHistoryListener.java
@@ -0,0 +1,94 @@
+/**
+ * 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 com.google.common.base.Splitter;
+import jakarta.annotation.PostConstruct;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.fineract.infrastructure.core.config.FineractProperties;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.event.business.BusinessEventListener;
+import 
org.apache.fineract.infrastructure.event.business.domain.loan.LoanStatusChangedBusinessEvent;
+import 
org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
+import 
org.apache.fineract.portfolio.loanaccount.domain.LoanStatusChangeHistory;
+import 
org.apache.fineract.portfolio.loanaccount.domain.LoanStatusChangeHistoryRepository;
+import org.springframework.stereotype.Service;
+
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class LoanStatusChangeHistoryListener {
+
+    private final Set<LoanStatus> loanStatuses = new HashSet<>();
+
+    private final BusinessEventNotifierService businessEventNotifierService;
+    private final LoanStatusChangeHistoryRepository 
loanStatusChangeHistoryRepository;
+    private final FineractProperties fineractProperties;
+
+    @PostConstruct
+    public void addListeners() {
+        
loanStatuses.addAll(getLoanStatuses(fineractProperties.getLoan().getStatusChangeHistoryStatuses()));
+        if (loanStatuses.size() > 0) {
+            
businessEventNotifierService.addPostBusinessEventListener(LoanStatusChangedBusinessEvent.class,
+                    new LoanStatusChangedListener());
+        }
+    }
+
+    Set<LoanStatus> getLoanStatuses(String str) {
+        Set<LoanStatus> result = new HashSet<>();
+        if ("NONE".equals(StringUtils.trim(str))) {
+            return result;
+        } else if ("ALL".equals(StringUtils.trim(str))) {
+            return 
Arrays.stream(LoanStatus.values()).collect(Collectors.toSet());
+        } else {
+            List<String> split = 
Splitter.on(",").trimResults().omitEmptyStrings().splitToList(str);
+            for (int i = 0; i < split.size(); i++) {
+                try {
+                    result.add(Enum.valueOf(LoanStatus.class, split.get(i)));
+                } catch (IllegalArgumentException iae) {
+                    throw new RuntimeException("Invalid loan status: " + 
split.get(i), iae);
+                }
+            }
+        }
+        return result;
+    }
+
+    protected final class LoanStatusChangedListener implements 
BusinessEventListener<LoanStatusChangedBusinessEvent> {
+
+        @Override
+        public void onBusinessEvent(LoanStatusChangedBusinessEvent event) {
+            final Loan loan = event.get();
+            log.debug("Loan Status change for loan {} with status {}", 
loan.getId(), loan.getStatus());
+            if (loanStatuses.contains(loan.getStatus())) {
+                LoanStatusChangeHistory loanStatusChangeHistory = new 
LoanStatusChangeHistory(loan, loan.getStatus(),
+                        DateUtils.getBusinessLocalDate());
+                
loanStatusChangeHistoryRepository.saveAndFlush(loanStatusChangeHistory);
+            }
+        }
+    }
+}
diff --git a/fineract-provider/src/main/resources/application.properties 
b/fineract-provider/src/main/resources/application.properties
index 77624ba83..a49c6403c 100644
--- a/fineract-provider/src/main/resources/application.properties
+++ b/fineract-provider/src/main/resources/application.properties
@@ -142,6 +142,10 @@ 
fineract.loan.transactionprocessor.due-penalty-interest-principal-fee-in-advance
 
fineract.loan.transactionprocessor.advanced-payment-strategy.enabled=${FINERACT_LOAN_TRANSACTIONPROCESSOR_ADVANCED_PAYMENT_STRATEGY_ENABLED:true}
 
fineract.loan.transactionprocessor.error-not-found-fail=${FINERACT_LOAN_TRANSACTIONPROCESSOR_ERROR_NOT_FOUND_FAIL:true}
 
+# Comma separated list of loan statuses which will be recorded on change. 
There are two extra values: "NONE" and "ALL".
+# "NONE" disables the feature and no entries will be created, "ALL" enables 
the feature for all loan statuses.
+fineract.loan.status-change-history-statuses=${FINERACT_LOAN_STATUS_CHANGE_HISTORY_STATUSES:NONE}
+
 
fineract.content.regex-whitelist-enabled=${FINERACT_CONTENT_REGEX_WHITELIST_ENABLED:true}
 
fineract.content.regex-whitelist=${FINERACT_CONTENT_REGEX_WHITELIST:.*\\.pdf$,.*\\.doc,.*\\.docx,.*\\.xls,.*\\.xlsx,.*\\.jpg,.*\\.jpeg,.*\\.png}
 
fineract.content.mime-whitelist-enabled=${FINERACT_CONTENT_MIME_WHITELIST_ENABLED:true}
diff --git 
a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanStatusChangeHistoryListenerTest.java
 
b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanStatusChangeHistoryListenerTest.java
new file mode 100644
index 000000000..43c6e575a
--- /dev/null
+++ 
b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanStatusChangeHistoryListenerTest.java
@@ -0,0 +1,210 @@
+/**
+ * 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 java.time.LocalDate;
+import java.time.ZoneId;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
+import org.apache.fineract.infrastructure.core.config.FineractProperties;
+import org.apache.fineract.infrastructure.core.domain.ActionContext;
+import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
+import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.apache.fineract.infrastructure.event.business.BusinessEventListener;
+import 
org.apache.fineract.infrastructure.event.business.domain.loan.LoanStatusChangedBusinessEvent;
+import 
org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
+import 
org.apache.fineract.portfolio.loanaccount.domain.LoanStatusChangeHistory;
+import 
org.apache.fineract.portfolio.loanaccount.domain.LoanStatusChangeHistoryRepository;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+public class LoanStatusChangeHistoryListenerTest {
+
+    @Mock
+    private BusinessEventNotifierService businessEventNotifierService;
+    @Mock
+    private LoanStatusChangeHistoryRepository 
loanStatusChangeHistoryRepository;
+    @Mock
+    private FineractProperties fineractProperties;
+
+    @Captor
+    private ArgumentCaptor<Class<LoanStatusChangedBusinessEvent>> 
classArgumentCaptor;
+
+    @Captor
+    private 
ArgumentCaptor<BusinessEventListener<LoanStatusChangedBusinessEvent>> 
listenerCaptor;
+
+    @Captor
+    private ArgumentCaptor<LoanStatusChangeHistory> 
loanStatusChangeHistoryArgumentCaptor;
+
+    @InjectMocks
+    private LoanStatusChangeHistoryListener underTest;
+
+    private final LocalDate actualDate = LocalDate.now(ZoneId.systemDefault());
+
+    @BeforeEach
+    public void setUp() {
+        ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, 
"default", "Default", "Asia/Kolkata", null));
+        ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT);
+        ThreadLocalContextUtil.setBusinessDates(new 
HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, actualDate)));
+    }
+
+    @Test
+    public void testGetLoanStatusesParseOK() {
+        Assertions.assertEquals(Set.of(), underTest.getLoanStatuses("NONE"));
+        
Assertions.assertEquals(Arrays.stream(LoanStatus.values()).collect(Collectors.toSet()),
 underTest.getLoanStatuses("ALL"));
+        Assertions.assertEquals(Set.of(LoanStatus.OVERPAID, 
LoanStatus.REJECTED, LoanStatus.CLOSED_OBLIGATIONS_MET),
+                
underTest.getLoanStatuses("OVERPAID,REJECTED,CLOSED_OBLIGATIONS_MET"));
+        Assertions.assertEquals(Set.of(LoanStatus.OVERPAID, 
LoanStatus.REJECTED, LoanStatus.CLOSED_OBLIGATIONS_MET),
+                underTest.getLoanStatuses(" OVERPAID,  REJECTED 
,CLOSED_OBLIGATIONS_MET   "));
+    }
+
+    @Test
+    public void testGetLoanStatusesParseNOK() {
+        RuntimeException runtimeException = 
Assertions.assertThrows(RuntimeException.class, () -> 
underTest.getLoanStatuses("MISSING"));
+        Assertions.assertEquals("Invalid loan status: MISSING", 
runtimeException.getMessage());
+
+        runtimeException = Assertions.assertThrows(RuntimeException.class, () 
-> underTest.getLoanStatuses("OVERPAID,MISSING"));
+        Assertions.assertEquals("Invalid loan status: MISSING", 
runtimeException.getMessage());
+
+        runtimeException = Assertions.assertThrows(RuntimeException.class, () 
-> underTest.getLoanStatuses("ACTIVE,ALL"));
+        Assertions.assertEquals("Invalid loan status: ALL", 
runtimeException.getMessage());
+
+        runtimeException = Assertions.assertThrows(RuntimeException.class, () 
-> underTest.getLoanStatuses("APPROVED,NONE"));
+        Assertions.assertEquals("Invalid loan status: NONE", 
runtimeException.getMessage());
+    }
+
+    @Test
+    public void testEventListenerShouldNotBeRegisteredWhenNONE() {
+        // given
+        FineractProperties.FineractLoanProperties loanProperties = 
Mockito.mock(FineractProperties.FineractLoanProperties.class);
+        Mockito.when(fineractProperties.getLoan()).thenReturn(loanProperties);
+        
Mockito.when(loanProperties.getStatusChangeHistoryStatuses()).thenReturn("NONE");
+
+        // when
+        underTest.addListeners();
+
+        // then
+        Mockito.verifyNoInteractions(businessEventNotifierService);
+    }
+
+    @Test
+    public void testEventListenerShouldBeRegisteredWhenAll() {
+        // given
+        FineractProperties.FineractLoanProperties loanProperties = 
Mockito.mock(FineractProperties.FineractLoanProperties.class);
+        Mockito.when(fineractProperties.getLoan()).thenReturn(loanProperties);
+        
Mockito.when(loanProperties.getStatusChangeHistoryStatuses()).thenReturn("ALL");
+
+        // when
+        underTest.addListeners();
+
+        // then
+        Mockito.verify(businessEventNotifierService, 
Mockito.times(1)).addPostBusinessEventListener(classArgumentCaptor.capture(),
+                listenerCaptor.capture());
+        Mockito.verifyNoMoreInteractions(businessEventNotifierService);
+        Assertions.assertNotNull(listenerCaptor.getValue());
+    }
+
+    @Test
+    public void 
testEventListenerShouldBeRegisteredWhenValidStatusesAreProvided() {
+        // given
+        FineractProperties.FineractLoanProperties loanProperties = 
Mockito.mock(FineractProperties.FineractLoanProperties.class);
+        Mockito.when(fineractProperties.getLoan()).thenReturn(loanProperties);
+        
Mockito.when(loanProperties.getStatusChangeHistoryStatuses()).thenReturn("ACTIVE,
 REJECTED");
+
+        // when
+        underTest.addListeners();
+
+        // then
+        Mockito.verify(businessEventNotifierService, 
Mockito.times(1)).addPostBusinessEventListener(classArgumentCaptor.capture(),
+                listenerCaptor.capture());
+        Mockito.verifyNoMoreInteractions(businessEventNotifierService);
+        Assertions.assertNotNull(listenerCaptor.getValue());
+    }
+
+    @Test
+    public void testHistoryIsSavedWhenLoansStateIsConfigured() {
+        // given
+        FineractProperties.FineractLoanProperties loanProperties = 
Mockito.mock(FineractProperties.FineractLoanProperties.class);
+        Mockito.when(fineractProperties.getLoan()).thenReturn(loanProperties);
+        
Mockito.when(loanProperties.getStatusChangeHistoryStatuses()).thenReturn("ACTIVE,
 REJECTED");
+        underTest.addListeners();
+        Mockito.verify(businessEventNotifierService, 
Mockito.times(1)).addPostBusinessEventListener(classArgumentCaptor.capture(),
+                listenerCaptor.capture());
+        Mockito.verifyNoMoreInteractions(businessEventNotifierService);
+        BusinessEventListener<LoanStatusChangedBusinessEvent> listener = 
listenerCaptor.getValue();
+
+        LoanStatusChangedBusinessEvent mockEvent = 
Mockito.mock(LoanStatusChangedBusinessEvent.class);
+        Loan loan = Mockito.mock(Loan.class);
+        Mockito.when(loan.getId()).thenReturn(123L);
+        Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE);
+        Mockito.when(mockEvent.get()).thenReturn(loan);
+
+        // when
+        listener.onBusinessEvent(mockEvent);
+
+        // then
+        Mockito.verify(loanStatusChangeHistoryRepository, 
Mockito.times(1)).saveAndFlush(loanStatusChangeHistoryArgumentCaptor.capture());
+        Mockito.verifyNoMoreInteractions(loanStatusChangeHistoryRepository);
+        LoanStatusChangeHistory loanStatusChangeHistory = 
loanStatusChangeHistoryArgumentCaptor.getValue();
+        Assertions.assertEquals(loan, loanStatusChangeHistory.getLoan());
+        Assertions.assertEquals(LoanStatus.ACTIVE, 
loanStatusChangeHistory.getStatus());
+        Assertions.assertEquals(actualDate, 
loanStatusChangeHistory.getBusinessDate());
+    }
+
+    @Test
+    public void testHistoryIsNotSavedWhenLoansStateIsNotConfigured() {
+        // given
+        FineractProperties.FineractLoanProperties loanProperties = 
Mockito.mock(FineractProperties.FineractLoanProperties.class);
+        Mockito.when(fineractProperties.getLoan()).thenReturn(loanProperties);
+        
Mockito.when(loanProperties.getStatusChangeHistoryStatuses()).thenReturn("ACTIVE,
 REJECTED");
+        underTest.addListeners();
+        Mockito.verify(businessEventNotifierService, 
Mockito.times(1)).addPostBusinessEventListener(classArgumentCaptor.capture(),
+                listenerCaptor.capture());
+        Mockito.verifyNoMoreInteractions(businessEventNotifierService);
+        BusinessEventListener<LoanStatusChangedBusinessEvent> listener = 
listenerCaptor.getValue();
+
+        LoanStatusChangedBusinessEvent mockEvent = 
Mockito.mock(LoanStatusChangedBusinessEvent.class);
+        Loan loan = Mockito.mock(Loan.class);
+        Mockito.when(loan.getId()).thenReturn(123L);
+        Mockito.when(loan.getStatus()).thenReturn(LoanStatus.OVERPAID);
+        Mockito.when(mockEvent.get()).thenReturn(loan);
+
+        // when
+        listener.onBusinessEvent(mockEvent);
+
+        // then
+        Mockito.verifyNoInteractions(loanStatusChangeHistoryRepository);
+    }
+
+}
diff --git a/fineract-provider/src/test/resources/application-test.properties 
b/fineract-provider/src/test/resources/application-test.properties
index 36c7231ae..9f9eb43ce 100644
--- a/fineract-provider/src/test/resources/application-test.properties
+++ b/fineract-provider/src/test/resources/application-test.properties
@@ -73,6 +73,7 @@ 
fineract.loan.transactionprocessor.due-penalty-fee-interest-principal-in-advance
 
fineract.loan.transactionprocessor.due-penalty-interest-principal-fee-in-advance-penalty-interest-principal-fee.enabled=true
 fineract.loan.transactionprocessor.advanced-payment-strategy.enabled=true
 fineract.loan.transactionprocessor.error-not-found-fail=true
+fineract.loan.status-change-history-statuses=NONE
 
 fineract.content.regex-whitelist-enabled=true
 
fineract.content.regex-whitelist=.*\\.pdf$,.*\\.doc,.*\\.docx,.*\\.xls,.*\\.xlsx,.*\\.jpg,.*\\.jpeg,.*\\.png

Reply via email to