This is an automated email from the ASF dual-hosted git repository.
arnold 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 27c50305f FINERACT-1724: Making sure that Loan account status change
business events are only triggered when an actual change happens
27c50305f is described below
commit 27c50305f0ae90b7b0abb5b79f2eff46a0fa39ba
Author: Arnold Galovics <[email protected]>
AuthorDate: Thu Oct 20 14:42:17 2022 +0200
FINERACT-1724: Making sure that Loan account status change business events
are only triggered when an actual change happens
---
.../domain/DefaultLoanLifecycleStateMachine.java | 25 +-
.../DefaultLoanLifecycleStateMachineTest.java | 362 +++++++++++++++++++++
2 files changed, 378 insertions(+), 9 deletions(-)
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachine.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachine.java
index a66ee09f6..be9a71fcd 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachine.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachine.java
@@ -37,15 +37,16 @@ public class DefaultLoanLifecycleStateMachine implements
LoanLifecycleStateMachi
@Override
public LoanStatus dryTransition(final LoanEvent loanEvent, final Loan
loan) {
- LoanStatus nextStatus = getNextState(loanEvent, loan);
- return nextStatus != null ? nextStatus : loan.getStatus();
+ LoanStatus newStatus = getNextStatus(loanEvent, loan);
+ return newStatus != null ? newStatus : loan.getStatus();
}
@Override
public void transition(final LoanEvent loanEvent, final Loan loan) {
- LoanStatus newState = getNextState(loanEvent, loan);
- if (newState != null) {
- loan.setLoanStatus(newState.getValue());
+ LoanStatus newStatus = getNextStatus(loanEvent, loan);
+ if (newStatus != null) {
+ Integer newPlainStatus = newStatus.getValue();
+ loan.setLoanStatus(newPlainStatus);
if (isNotLoanCreation(loanEvent)) {
// in case of Loan creation, a LoanCreatedBusinessEvent is
also raised, no need to send a status change
@@ -58,7 +59,7 @@ public class DefaultLoanLifecycleStateMachine implements
LoanLifecycleStateMachi
return !LoanEvent.LOAN_CREATED.equals(loanEvent);
}
- private LoanStatus getNextState(LoanEvent loanEvent, Loan loan) {
+ private LoanStatus getNextStatus(LoanEvent loanEvent, Loan loan) {
Integer plainFrom = loan.getPlainStatus();
if (loanEvent.equals(LoanEvent.LOAN_CREATED) && plainFrom == null) {
return submittedTransition();
@@ -134,10 +135,14 @@ public class DefaultLoanLifecycleStateMachine implements
LoanLifecycleStateMachi
newState = transferInProgress();
break;
case LOAN_REJECT_TRANSFER:
- newState = transferOnHold();
+ if (anyOfAllowedWhenComingFrom(from,
LoanStatus.TRANSFER_IN_PROGRESS)) {
+ newState = transferOnHold();
+ }
break;
case LOAN_WITHDRAW_TRANSFER:
- newState = activeTransition();
+ if (anyOfAllowedWhenComingFrom(from,
LoanStatus.TRANSFER_IN_PROGRESS)) {
+ newState = activeTransition();
+ }
break;
case WRITE_OFF_OUTSTANDING_UNDO:
if (anyOfAllowedWhenComingFrom(from,
LoanStatus.CLOSED_WRITTEN_OFF)) {
@@ -145,7 +150,9 @@ public class DefaultLoanLifecycleStateMachine implements
LoanLifecycleStateMachi
}
break;
case LOAN_CREDIT_BALANCE_REFUND:
- newState = closeObligationsMetTransition();
+ if (anyOfAllowedWhenComingFrom(from, LoanStatus.OVERPAID)) {
+ newState = closeObligationsMetTransition();
+ }
break;
case LOAN_CHARGE_ADDED:
if (anyOfAllowedWhenComingFrom(from,
LoanStatus.CLOSED_OBLIGATIONS_MET)) {
diff --git
a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachineTest.java
b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachineTest.java
new file mode 100644
index 000000000..56f1443be
--- /dev/null
+++
b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachineTest.java
@@ -0,0 +1,362 @@
+/**
+ * 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 static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+
+import
org.apache.fineract.infrastructure.event.business.domain.loan.LoanStatusChangedBusinessEvent;
+import
org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+class DefaultLoanLifecycleStateMachineTest {
+
+ @Mock
+ private BusinessEventNotifierService businessEventNotifierService;
+
+ private DefaultLoanLifecycleStateMachine underTest;
+
+ @BeforeEach
+ public void setUp() {
+ underTest = new DefaultLoanLifecycleStateMachine(LoanStatus.values(),
businessEventNotifierService);
+ }
+
+ @Test
+ public void testTransitionShouldWorkProperlyForLoanCreation() {
+ // given
+ Loan loan = createLoanWithStatus(null);
+ // when
+ underTest.transition(LoanEvent.LOAN_CREATED, loan);
+ // then
+
assertThat(loan.getStatus()).isEqualTo(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL);
+ verifyNoInteractions(businessEventNotifierService);
+ }
+
+ @Test
+ public void testTransitionShouldWorkProperlyForLoanRejection() {
+ // given
+ Loan loan =
createLoanWithStatus(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL);
+ // when
+ underTest.transition(LoanEvent.LOAN_REJECTED, loan);
+ // then
+ assertThat(loan.getStatus()).isEqualTo(LoanStatus.REJECTED);
+
verify(businessEventNotifierService).notifyPostBusinessEvent(any(LoanStatusChangedBusinessEvent.class));
+ }
+
+ @Test
+ public void testTransitionShouldWorkProperlyForLoanApproval() {
+ // given
+ Loan loan =
createLoanWithStatus(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL);
+ // when
+ underTest.transition(LoanEvent.LOAN_APPROVED, loan);
+ // then
+ assertThat(loan.getStatus()).isEqualTo(LoanStatus.APPROVED);
+
verify(businessEventNotifierService).notifyPostBusinessEvent(any(LoanStatusChangedBusinessEvent.class));
+ }
+
+ @Test
+ public void testTransitionShouldWorkProperlyForLoanWithdraw() {
+ // given
+ Loan loan =
createLoanWithStatus(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL);
+ // when
+ underTest.transition(LoanEvent.LOAN_WITHDRAWN, loan);
+ // then
+ assertThat(loan.getStatus()).isEqualTo(LoanStatus.WITHDRAWN_BY_CLIENT);
+
verify(businessEventNotifierService).notifyPostBusinessEvent(any(LoanStatusChangedBusinessEvent.class));
+ }
+
+ @Test
+ public void
testTransitionShouldWorkProperlyForLoanDisbursementWhenLoanIsApproved() {
+ // given
+ Loan loan = createLoanWithStatus(LoanStatus.APPROVED);
+ // when
+ underTest.transition(LoanEvent.LOAN_DISBURSED, loan);
+ // then
+ assertThat(loan.getStatus()).isEqualTo(LoanStatus.ACTIVE);
+
verify(businessEventNotifierService).notifyPostBusinessEvent(any(LoanStatusChangedBusinessEvent.class));
+ }
+
+ @Test
+ public void
testTransitionShouldWorkProperlyForLoanDisbursementWhenLoanIsClosedObligationsMet()
{
+ // given
+ Loan loan = createLoanWithStatus(LoanStatus.CLOSED_OBLIGATIONS_MET);
+ // when
+ underTest.transition(LoanEvent.LOAN_DISBURSED, loan);
+ // then
+ assertThat(loan.getStatus()).isEqualTo(LoanStatus.ACTIVE);
+
verify(businessEventNotifierService).notifyPostBusinessEvent(any(LoanStatusChangedBusinessEvent.class));
+ }
+
+ @Test
+ public void
testTransitionShouldWorkProperlyForLoanDisbursementWhenLoanIsOverpaid() {
+ // given
+ Loan loan = createLoanWithStatus(LoanStatus.OVERPAID);
+ // when
+ underTest.transition(LoanEvent.LOAN_DISBURSED, loan);
+ // then
+ assertThat(loan.getStatus()).isEqualTo(LoanStatus.ACTIVE);
+
verify(businessEventNotifierService).notifyPostBusinessEvent(any(LoanStatusChangedBusinessEvent.class));
+ }
+
+ @Test
+ public void testTransitionShouldWorkProperlyForLoanApprovalUndo() {
+ // given
+ Loan loan = createLoanWithStatus(LoanStatus.APPROVED);
+ // when
+ underTest.transition(LoanEvent.LOAN_APPROVAL_UNDO, loan);
+ // then
+
assertThat(loan.getStatus()).isEqualTo(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL);
+
verify(businessEventNotifierService).notifyPostBusinessEvent(any(LoanStatusChangedBusinessEvent.class));
+ }
+
+ @Test
+ public void testTransitionShouldWorkProperlyForLoanDisbursementUndo() {
+ // given
+ Loan loan = createLoanWithStatus(LoanStatus.ACTIVE);
+ // when
+ underTest.transition(LoanEvent.LOAN_DISBURSAL_UNDO, loan);
+ // then
+ assertThat(loan.getStatus()).isEqualTo(LoanStatus.APPROVED);
+
verify(businessEventNotifierService).notifyPostBusinessEvent(any(LoanStatusChangedBusinessEvent.class));
+ }
+
+ @Test
+ public void
testTransitionShouldWorkProperlyForLoanChargePaymentWhenClosedObligationsMet() {
+ // given
+ Loan loan = createLoanWithStatus(LoanStatus.CLOSED_OBLIGATIONS_MET);
+ // when
+ underTest.transition(LoanEvent.LOAN_CHARGE_PAYMENT, loan);
+ // then
+ assertThat(loan.getStatus()).isEqualTo(LoanStatus.ACTIVE);
+
verify(businessEventNotifierService).notifyPostBusinessEvent(any(LoanStatusChangedBusinessEvent.class));
+ }
+
+ @Test
+ public void
testTransitionShouldWorkProperlyForLoanChargePaymentWhenOverpaid() {
+ // given
+ Loan loan = createLoanWithStatus(LoanStatus.OVERPAID);
+ // when
+ underTest.transition(LoanEvent.LOAN_CHARGE_PAYMENT, loan);
+ // then
+ assertThat(loan.getStatus()).isEqualTo(LoanStatus.ACTIVE);
+
verify(businessEventNotifierService).notifyPostBusinessEvent(any(LoanStatusChangedBusinessEvent.class));
+ }
+
+ @Test
+ public void
testTransitionShouldWorkProperlyForLoanRepaidInFullWhenActive() {
+ // given
+ Loan loan = createLoanWithStatus(LoanStatus.ACTIVE);
+ // when
+ underTest.transition(LoanEvent.REPAID_IN_FULL, loan);
+ // then
+
assertThat(loan.getStatus()).isEqualTo(LoanStatus.CLOSED_OBLIGATIONS_MET);
+
verify(businessEventNotifierService).notifyPostBusinessEvent(any(LoanStatusChangedBusinessEvent.class));
+ }
+
+ @Test
+ public void
testTransitionShouldWorkProperlyForLoanRepaidInFullWhenOverpaid() {
+ // given
+ Loan loan = createLoanWithStatus(LoanStatus.OVERPAID);
+ // when
+ underTest.transition(LoanEvent.REPAID_IN_FULL, loan);
+ // then
+
assertThat(loan.getStatus()).isEqualTo(LoanStatus.CLOSED_OBLIGATIONS_MET);
+
verify(businessEventNotifierService).notifyPostBusinessEvent(any(LoanStatusChangedBusinessEvent.class));
+ }
+
+ @Test
+ public void testTransitionShouldWorkProperlyForLoanWriteOffOutstanding() {
+ // given
+ Loan loan = createLoanWithStatus(LoanStatus.ACTIVE);
+ // when
+ underTest.transition(LoanEvent.WRITE_OFF_OUTSTANDING, loan);
+ // then
+ assertThat(loan.getStatus()).isEqualTo(LoanStatus.CLOSED_WRITTEN_OFF);
+
verify(businessEventNotifierService).notifyPostBusinessEvent(any(LoanStatusChangedBusinessEvent.class));
+ }
+
+ @Test
+ public void testTransitionShouldWorkProperlyForLoanRescheduled() {
+ // given
+ Loan loan = createLoanWithStatus(LoanStatus.ACTIVE);
+ // when
+ underTest.transition(LoanEvent.LOAN_RESCHEDULE, loan);
+ // then
+
assertThat(loan.getStatus()).isEqualTo(LoanStatus.CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT);
+
verify(businessEventNotifierService).notifyPostBusinessEvent(any(LoanStatusChangedBusinessEvent.class));
+ }
+
+ @Test
+ public void testTransitionShouldWorkProperlyForLoanOverpaymentWhenActive()
{
+ // given
+ Loan loan = createLoanWithStatus(LoanStatus.ACTIVE);
+ // when
+ underTest.transition(LoanEvent.LOAN_OVERPAYMENT, loan);
+ // then
+ assertThat(loan.getStatus()).isEqualTo(LoanStatus.OVERPAID);
+
verify(businessEventNotifierService).notifyPostBusinessEvent(any(LoanStatusChangedBusinessEvent.class));
+ }
+
+ @Test
+ public void
testTransitionShouldWorkProperlyForLoanOverpaymentWhenClosedObligationsMet() {
+ // given
+ Loan loan = createLoanWithStatus(LoanStatus.CLOSED_OBLIGATIONS_MET);
+ // when
+ underTest.transition(LoanEvent.LOAN_OVERPAYMENT, loan);
+ // then
+ assertThat(loan.getStatus()).isEqualTo(LoanStatus.OVERPAID);
+
verify(businessEventNotifierService).notifyPostBusinessEvent(any(LoanStatusChangedBusinessEvent.class));
+ }
+
+ @Test
+ public void
testTransitionShouldWorkProperlyForLoanAdjustTransactionWhenClosedObligationsMet()
{
+ // given
+ Loan loan = createLoanWithStatus(LoanStatus.CLOSED_OBLIGATIONS_MET);
+ // when
+ underTest.transition(LoanEvent.LOAN_ADJUST_TRANSACTION, loan);
+ // then
+ assertThat(loan.getStatus()).isEqualTo(LoanStatus.ACTIVE);
+
verify(businessEventNotifierService).notifyPostBusinessEvent(any(LoanStatusChangedBusinessEvent.class));
+ }
+
+ @Test
+ public void
testTransitionShouldWorkProperlyForLoanAdjustTransactionWhenClosedWrittenOff() {
+ // given
+ Loan loan = createLoanWithStatus(LoanStatus.CLOSED_WRITTEN_OFF);
+ // when
+ underTest.transition(LoanEvent.LOAN_ADJUST_TRANSACTION, loan);
+ // then
+ assertThat(loan.getStatus()).isEqualTo(LoanStatus.ACTIVE);
+
verify(businessEventNotifierService).notifyPostBusinessEvent(any(LoanStatusChangedBusinessEvent.class));
+ }
+
+ @Test
+ public void
testTransitionShouldWorkProperlyForLoanAdjustTransactionWhenClosedRescheduleOutstandingAmount()
{
+ // given
+ Loan loan =
createLoanWithStatus(LoanStatus.CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT);
+ // when
+ underTest.transition(LoanEvent.LOAN_ADJUST_TRANSACTION, loan);
+ // then
+ assertThat(loan.getStatus()).isEqualTo(LoanStatus.ACTIVE);
+
verify(businessEventNotifierService).notifyPostBusinessEvent(any(LoanStatusChangedBusinessEvent.class));
+ }
+
+ @Test
+ public void testTransitionShouldWorkProperlyForLoanInitiateTransfer() {
+ // given
+ Loan loan = createLoanWithStatus(LoanStatus.ACTIVE);
+ // when
+ underTest.transition(LoanEvent.LOAN_INITIATE_TRANSFER, loan);
+ // then
+
assertThat(loan.getStatus()).isEqualTo(LoanStatus.TRANSFER_IN_PROGRESS);
+
verify(businessEventNotifierService).notifyPostBusinessEvent(any(LoanStatusChangedBusinessEvent.class));
+ }
+
+ @Test
+ public void testTransitionShouldWorkProperlyForLoanRejectTransfer() {
+ // given
+ Loan loan = createLoanWithStatus(LoanStatus.TRANSFER_IN_PROGRESS);
+ // when
+ underTest.transition(LoanEvent.LOAN_REJECT_TRANSFER, loan);
+ // then
+ assertThat(loan.getStatus()).isEqualTo(LoanStatus.TRANSFER_ON_HOLD);
+
verify(businessEventNotifierService).notifyPostBusinessEvent(any(LoanStatusChangedBusinessEvent.class));
+ }
+
+ @Test
+ public void testTransitionShouldWorkProperlyForLoanWithdrawTransfer() {
+ // given
+ Loan loan = createLoanWithStatus(LoanStatus.TRANSFER_IN_PROGRESS);
+ // when
+ underTest.transition(LoanEvent.LOAN_WITHDRAW_TRANSFER, loan);
+ // then
+ assertThat(loan.getStatus()).isEqualTo(LoanStatus.ACTIVE);
+
verify(businessEventNotifierService).notifyPostBusinessEvent(any(LoanStatusChangedBusinessEvent.class));
+ }
+
+ @Test
+ public void
testTransitionShouldWorkProperlyForLoanWriteOffOutstandingUndo() {
+ // given
+ Loan loan = createLoanWithStatus(LoanStatus.CLOSED_WRITTEN_OFF);
+ // when
+ underTest.transition(LoanEvent.WRITE_OFF_OUTSTANDING_UNDO, loan);
+ // then
+ assertThat(loan.getStatus()).isEqualTo(LoanStatus.ACTIVE);
+
verify(businessEventNotifierService).notifyPostBusinessEvent(any(LoanStatusChangedBusinessEvent.class));
+ }
+
+ @Test
+ public void testTransitionShouldWorkProperlyForLoanCreditBalanceRefund() {
+ // given
+ Loan loan = createLoanWithStatus(LoanStatus.OVERPAID);
+ // when
+ underTest.transition(LoanEvent.LOAN_CREDIT_BALANCE_REFUND, loan);
+ // then
+
assertThat(loan.getStatus()).isEqualTo(LoanStatus.CLOSED_OBLIGATIONS_MET);
+
verify(businessEventNotifierService).notifyPostBusinessEvent(any(LoanStatusChangedBusinessEvent.class));
+ }
+
+ @Test
+ public void testTransitionShouldWorkProperlyForLoanChargeAdded() {
+ // given
+ Loan loan = createLoanWithStatus(LoanStatus.CLOSED_OBLIGATIONS_MET);
+ // when
+ underTest.transition(LoanEvent.LOAN_CHARGE_ADDED, loan);
+ // then
+ assertThat(loan.getStatus()).isEqualTo(LoanStatus.ACTIVE);
+
verify(businessEventNotifierService).notifyPostBusinessEvent(any(LoanStatusChangedBusinessEvent.class));
+ }
+
+ @Test
+ public void
testTransitionShouldWorkProperlyForLoanChargebackWhenClosedObligationsMet() {
+ // given
+ Loan loan = createLoanWithStatus(LoanStatus.CLOSED_OBLIGATIONS_MET);
+ // when
+ underTest.transition(LoanEvent.LOAN_CHARGEBACK, loan);
+ // then
+ assertThat(loan.getStatus()).isEqualTo(LoanStatus.ACTIVE);
+
verify(businessEventNotifierService).notifyPostBusinessEvent(any(LoanStatusChangedBusinessEvent.class));
+ }
+
+ @Test
+ public void
testTransitionShouldWorkProperlyForLoanChargebackWhenOverpaid() {
+ // given
+ Loan loan = createLoanWithStatus(LoanStatus.OVERPAID);
+ // when
+ underTest.transition(LoanEvent.LOAN_CHARGEBACK, loan);
+ // then
+ assertThat(loan.getStatus()).isEqualTo(LoanStatus.ACTIVE);
+
verify(businessEventNotifierService).notifyPostBusinessEvent(any(LoanStatusChangedBusinessEvent.class));
+ }
+
+ private Loan createLoanWithStatus(LoanStatus status) {
+ Loan result = new Loan();
+ if (status != null) {
+ result.setLoanStatus(status.getValue());
+ }
+ return result;
+ }
+}