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