diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachine.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachine.java index 0508768623d..06d2251e043 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachine.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachine.java @@ -18,10 +18,13 @@ */ package org.apache.fineract.portfolio.loanaccount.domain; +import java.math.BigDecimal; import java.util.List; import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.event.business.domain.loan.LoanStatusChangedBusinessEvent; import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; // TODO: introduce tests for the state machine @@ -29,6 +32,8 @@ @RequiredArgsConstructor public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachine { + private static Logger LOG = LoggerFactory.getLogger(DefaultLoanLifecycleStateMachine.class); + private static final List ALLOWED_LOAN_STATUSES = List.of(LoanStatus.values()); private final BusinessEventNotifierService businessEventNotifierService; @@ -40,6 +45,9 @@ public LoanStatus dryTransition(final LoanEvent loanEvent, final Loan loan) { @Override public void transition(final LoanEvent loanEvent, final Loan loan) { + loan.updateLoanSummaryDerivedFields(); + + LoanStatus oldStatus = loan.getStatus(); LoanStatus newStatus = getNextStatus(loanEvent, loan); if (newStatus != null) { Integer newPlainStatus = newStatus.getValue(); @@ -51,16 +59,22 @@ public void transition(final LoanEvent loanEvent, final Loan loan) { } // set mandatory field states based on new status after the transition + LOG.debug("Transitioning loan {} status from {} to {}", loan.getId(), oldStatus, newStatus); switch (newStatus) { + case SUBMITTED_AND_PENDING_APPROVAL -> { + loan.setApprovedOnDate(null); + loan.setApprovedBy(null); + } + case APPROVED -> { + loan.setDisbursedBy(null); + loan.setActualDisbursementDate(null); + } case ACTIVE -> { - if (loan.getClosedOnDate() != null) { - loan.setClosedOnDate(null); - } - if (loan.getOverpaidOnDate() != null) { - loan.setOverpaidOnDate(null); - } + loan.setClosedBy(null); + loan.setClosedOnDate(null); + loan.setOverpaidOnDate(null); } - default -> { + default -> { // no fields need to get cleared } } } @@ -145,7 +159,12 @@ private LoanStatus getNextStatus(LoanEvent loanEvent, Loan loan) { case LOAN_ADJUST_TRANSACTION: if (anyOfAllowedWhenComingFrom(from, LoanStatus.CLOSED_OBLIGATIONS_MET, LoanStatus.CLOSED_WRITTEN_OFF, LoanStatus.CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT)) { - newState = activeTransition(); + boolean isOverpaid = loan.getTotalOverpaid() != null && loan.getTotalOverpaid().compareTo(BigDecimal.ZERO) > 0; + if (isOverpaid) { + newState = overpaidTransition(); + } else { + newState = activeTransition(); + } } break; case LOAN_INITIATE_TRANSFER: diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java index 17c7281e651..80fb57c0452 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java @@ -1300,7 +1300,9 @@ public void updateLoanScheduleDependentDerivedFields() { public void updateLoanSummaryDerivedFields() { if (isNotDisbursed()) { - this.summary.zeroFields(); + if (this.summary != null) { + this.summary.zeroFields(); + } this.totalOverpaid = null; } else { final Money overpaidBy = calculateTotalOverpayment(); @@ -3017,7 +3019,7 @@ public boolean isNotSubmittedAndPendingApproval() { } public LoanStatus getStatus() { - return LoanStatus.fromInt(this.loanStatus); + return this.loanStatus == null ? null : LoanStatus.fromInt(this.loanStatus); } public Integer getPlainStatus() { diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanCreditBalanceRefundandRepaymentTypeIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanCreditBalanceRefundandRepaymentTypeIntegrationTest.java index 38c8843d425..40c8ae4d6db 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanCreditBalanceRefundandRepaymentTypeIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanCreditBalanceRefundandRepaymentTypeIntegrationTest.java @@ -245,7 +245,8 @@ public void cantRefundMoreThanOverpaidTest(LoanProductTestBuilder loanProductTes @ParameterizedTest @MethodSource("loanProductFactory") - public void fullRefundChangesStatusToClosedObligationMetTest(LoanProductTestBuilder loanProductTestBuilder) { + public void fullRefundChangesStatusToClosedObligationMetAndSetBackToOverpayAfterReverseTest( + LoanProductTestBuilder loanProductTestBuilder) { disburseLoanOfAccountingRule(ACCRUAL_PERIODIC, loanProductTestBuilder); HashMap loanStatusHashMap = makeRepayment("06 January 2022", 20000.00f); // overpayment LoanStatusChecker.verifyLoanAccountIsOverPaid(loanStatusHashMap); @@ -255,7 +256,8 @@ public void fullRefundChangesStatusToClosedObligationMetTest(LoanProductTestBuil final String creditBalanceRefundDate = "09 January 2022"; final String externalId = null; - loanTransactionHelper.creditBalanceRefund(creditBalanceRefundDate, totalOverpaid, externalId, disbursedLoanID, null); + Integer resourceId = (Integer) loanTransactionHelper.creditBalanceRefund(creditBalanceRefundDate, totalOverpaid, externalId, + disbursedLoanID, "resourceId"); loanStatusHashMap = (HashMap) this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, disbursedLoanID, "status"); LoanStatusChecker.verifyLoanAccountIsClosed(loanStatusHashMap); @@ -267,6 +269,18 @@ public void fullRefundChangesStatusToClosedObligationMetTest(LoanProductTestBuil totalOverpaidAtEnd = floatZero; } assertEquals(totalOverpaidAtEnd, floatZero); + + loanTransactionHelper.reverseLoanTransaction(disbursedLoanID, resourceId.longValue(), creditBalanceRefundDate, responseSpec); + + loanStatusHashMap = (HashMap) this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, disbursedLoanID, + "status"); + + LoanStatusChecker.verifyLoanAccountIsOverPaid(loanStatusHashMap); + + Float totalOverpaidAfterReverse = (Float) this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, + disbursedLoanID, "totalOverpaid"); + + assertEquals(totalOverpaidAfterReverse, totalOverpaid); } @ParameterizedTest