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 be0a5cd3c..35afd16e9 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 @@ -19,15 +19,13 @@ package org.apache.fineract.portfolio.loanaccount.domain; import java.util.List; +import lombok.RequiredArgsConstructor; +@RequiredArgsConstructor public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachine { private final List allowedLoanStatuses; - public DefaultLoanLifecycleStateMachine(final List allowedLoanStatuses) { - this.allowedLoanStatuses = allowedLoanStatuses; - } - @Override public LoanStatus transition(final LoanEvent loanEvent, final LoanStatus from) { @@ -36,77 +34,94 @@ public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachi switch (loanEvent) { case LOAN_CREATED: if (from == null) { - newState = stateOf(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL, this.allowedLoanStatuses); + newState = submittedTransition(); } break; case LOAN_REJECTED: if (from.hasStateOf(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL)) { - newState = stateOf(LoanStatus.REJECTED, this.allowedLoanStatuses); + newState = rejectedTransition(); } break; case LOAN_APPROVED: if (from.hasStateOf(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL)) { - newState = stateOf(LoanStatus.APPROVED, this.allowedLoanStatuses); + newState = approvedTransition(); } break; case LOAN_WITHDRAWN: if (anyOfAllowedWhenComingFrom(from, LoanStatus.SUBMITTED_AND_PENDING_APPROVAL)) { - newState = stateOf(LoanStatus.WITHDRAWN_BY_CLIENT, this.allowedLoanStatuses); + newState = withdrawnByClientTransition(); } break; case LOAN_DISBURSED: if (anyOfAllowedWhenComingFrom(from, LoanStatus.APPROVED, LoanStatus.CLOSED_OBLIGATIONS_MET, LoanStatus.OVERPAID)) { - newState = stateOf(LoanStatus.ACTIVE, this.allowedLoanStatuses); + newState = activeTransition(); } break; case LOAN_APPROVAL_UNDO: if (from.hasStateOf(LoanStatus.APPROVED)) { - newState = stateOf(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL, this.allowedLoanStatuses); + newState = submittedTransition(); } break; case LOAN_DISBURSAL_UNDO: if (anyOfAllowedWhenComingFrom(from, LoanStatus.ACTIVE)) { - newState = stateOf(LoanStatus.APPROVED, this.allowedLoanStatuses); + newState = approvedTransition(); } break; case LOAN_CHARGE_PAYMENT: case LOAN_REPAYMENT_OR_WAIVER: if (anyOfAllowedWhenComingFrom(from, LoanStatus.ACTIVE, LoanStatus.CLOSED_OBLIGATIONS_MET, LoanStatus.OVERPAID)) { - newState = stateOf(LoanStatus.ACTIVE, this.allowedLoanStatuses); - } else { - newState = from; + newState = activeTransition(); } break; case REPAID_IN_FULL: if (anyOfAllowedWhenComingFrom(from, LoanStatus.ACTIVE, LoanStatus.OVERPAID)) { - newState = stateOf(LoanStatus.CLOSED_OBLIGATIONS_MET, this.allowedLoanStatuses); + newState = closeObligationsMetTransition(); } break; case WRITE_OFF_OUTSTANDING: if (anyOfAllowedWhenComingFrom(from, LoanStatus.ACTIVE)) { - newState = stateOf(LoanStatus.CLOSED_WRITTEN_OFF, this.allowedLoanStatuses); + newState = closedWrittenOffTransition(); } break; case LOAN_RESCHEDULE: if (anyOfAllowedWhenComingFrom(from, LoanStatus.ACTIVE)) { - newState = stateOf(LoanStatus.CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT, this.allowedLoanStatuses); + newState = closedRescheduleOutstandingAmountTransition(); } break; case INTERST_REBATE_OWED: if (anyOfAllowedWhenComingFrom(from, LoanStatus.CLOSED_OBLIGATIONS_MET)) { - newState = stateOf(LoanStatus.CLOSED_OBLIGATIONS_MET, this.allowedLoanStatuses); + newState = closeObligationsMetTransition(); } break; case LOAN_OVERPAYMENT: if (anyOfAllowedWhenComingFrom(from, LoanStatus.CLOSED_OBLIGATIONS_MET, LoanStatus.ACTIVE)) { - newState = stateOf(LoanStatus.OVERPAID, this.allowedLoanStatuses); + newState = overpaidTransition(); } break; + case LOAN_ADJUST_TRANSACTION: + if (anyOfAllowedWhenComingFrom(from, LoanStatus.CLOSED_OBLIGATIONS_MET, LoanStatus.CLOSED_WRITTEN_OFF, + LoanStatus.CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT)) { + newState = activeTransition(); + } + break; + case LOAN_INITIATE_TRANSFER: + newState = transferInProgress(); + break; + case LOAN_REJECT_TRANSFER: + newState = transferOnHold(); + break; + case LOAN_WITHDRAW_TRANSFER: + newState = activeTransition(); + break; case LOAN_CLOSED: break; case WRITE_OFF_OUTSTANDING_UNDO: + if (anyOfAllowedWhenComingFrom(from, LoanStatus.CLOSED_WRITTEN_OFF)) { + newState = activeTransition(); + } break; case LOAN_CREDIT_BALANCE_REFUND: + newState = closeObligationsMetTransition(); break; default: break; @@ -115,6 +130,50 @@ public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachi return newState; } + private LoanStatus transferOnHold() { + return stateOf(LoanStatus.TRANSFER_ON_HOLD, this.allowedLoanStatuses); + } + + private LoanStatus transferInProgress() { + return stateOf(LoanStatus.TRANSFER_IN_PROGRESS, this.allowedLoanStatuses); + } + + private LoanStatus overpaidTransition() { + return stateOf(LoanStatus.OVERPAID, this.allowedLoanStatuses); + } + + private LoanStatus closedRescheduleOutstandingAmountTransition() { + return stateOf(LoanStatus.CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT, this.allowedLoanStatuses); + } + + private LoanStatus closedWrittenOffTransition() { + return stateOf(LoanStatus.CLOSED_WRITTEN_OFF, this.allowedLoanStatuses); + } + + private LoanStatus closeObligationsMetTransition() { + return stateOf(LoanStatus.CLOSED_OBLIGATIONS_MET, this.allowedLoanStatuses); + } + + private LoanStatus activeTransition() { + return stateOf(LoanStatus.ACTIVE, this.allowedLoanStatuses); + } + + private LoanStatus withdrawnByClientTransition() { + return stateOf(LoanStatus.WITHDRAWN_BY_CLIENT, this.allowedLoanStatuses); + } + + private LoanStatus approvedTransition() { + return stateOf(LoanStatus.APPROVED, this.allowedLoanStatuses); + } + + private LoanStatus rejectedTransition() { + return stateOf(LoanStatus.REJECTED, this.allowedLoanStatuses); + } + + private LoanStatus submittedTransition() { + return stateOf(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL, this.allowedLoanStatuses); + } + private LoanStatus stateOf(final LoanStatus state, final List allowedLoanStatuses) { LoanStatus match = null; for (final LoanStatus loanStatus : allowedLoanStatuses) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java index 95088b15e..cb41f4d1c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java @@ -3635,7 +3635,9 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { } if (isClosedObligationsMet() || isClosedWrittenOff() || isClosedWithOutsandingAmountMarkedForReschedule()) { - this.loanStatus = LoanStatus.ACTIVE.getValue(); + LoanStatus loanStatusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_ADJUST_TRANSACTION, + LoanStatus.fromInt(this.loanStatus)); + this.loanStatus = loanStatusEnum.getValue(); } if (newTransactionDetail.isRepaymentType() || newTransactionDetail.isInterestWaiver()) { @@ -3646,7 +3648,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { return changedTransactionDetail; } - public ChangedTransactionDetail undoWrittenOff(final List existingTransactionIds, final List existingReversedTransactionIds, + public ChangedTransactionDetail undoWrittenOff(LoanLifecycleStateMachine loanLifecycleStateMachine, + final List existingTransactionIds, final List existingReversedTransactionIds, final ScheduleGeneratorDTO scheduleGeneratorDTO) { validateAccountStatus(LoanEvent.WRITE_OFF_OUTSTANDING_UNDO); @@ -3654,7 +3657,9 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); final LoanTransaction writeOffTransaction = findWriteOffTransaction(); writeOffTransaction.reverse(); - this.loanStatus = LoanStatus.ACTIVE.getValue(); + LoanStatus loanStatusEnum = loanLifecycleStateMachine.transition(LoanEvent.WRITE_OFF_OUTSTANDING_UNDO, + LoanStatus.fromInt(this.loanStatus)); + this.loanStatus = loanStatusEnum.getValue(); final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory .determineProcessor(this.transactionProcessingStrategy); final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); @@ -6068,7 +6073,9 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { updateLoanSummaryDerivedFields(); if (this.totalOverpaid == null || BigDecimal.ZERO.compareTo(this.totalOverpaid) == 0) { - this.loanStatus = LoanStatus.CLOSED_OBLIGATIONS_MET.getValue(); + LoanStatus statusEnum = defaultLoanLifecycleStateMachine.transition(LoanEvent.LOAN_CREDIT_BALANCE_REFUND, + LoanStatus.fromInt(this.loanStatus)); + this.loanStatus = statusEnum.getValue(); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanEvent.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanEvent.java index 7db08e3b1..9d7f1dfba 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanEvent.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanEvent.java @@ -44,5 +44,9 @@ public enum LoanEvent { LOAN_EDIT_MULTI_DISBURSE_DATE, // LOAN_REFUND, // LOAN_FORECLOSURE, // + LOAN_ADJUST_TRANSACTION, // + LOAN_INITIATE_TRANSFER, // + LOAN_REJECT_TRANSFER, // + LOAN_WITHDRAW_TRANSFER, // LOAN_CREDIT_BALANCE_REFUND; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 2d576c805..248f7e3d4 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -2260,7 +2260,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final LoanTransaction newTransferTransaction = LoanTransaction.initiateTransfer(loan.getOffice(), loan, transferDate); loan.addLoanTransaction(newTransferTransaction); - loan.setLoanStatus(LoanStatus.TRANSFER_IN_PROGRESS.getValue()); + LoanLifecycleStateMachine loanLifecycleStateMachine = defaultLoanLifecycleStateMachine(); + LoanStatus loanStatusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_INITIATE_TRANSFER, loan.status()); + loan.setLoanStatus(loanStatusEnum.getValue()); this.loanTransactionRepository.saveAndFlush(newTransferTransaction); saveLoanWithDataIntegrityViolationChecks(loan); @@ -2281,11 +2283,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final LoanTransaction newTransferAcceptanceTransaction = LoanTransaction.approveTransfer(acceptedInOffice, loan, transferDate); loan.addLoanTransaction(newTransferAcceptanceTransaction); + LoanLifecycleStateMachine loanLifecycleStateMachine = defaultLoanLifecycleStateMachine(); + LoanStatus loanStatusEnum; if (loan.getTotalOverpaid() != null) { - loan.setLoanStatus(LoanStatus.OVERPAID.getValue()); + loanStatusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_OVERPAYMENT, loan.status()); } else { - loan.setLoanStatus(LoanStatus.ACTIVE.getValue()); + loanStatusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_REPAYMENT_OR_WAIVER, loan.status()); } + loan.setLoanStatus(loanStatusEnum.getValue()); if (loanOfficer != null) { loan.reassignLoanOfficer(loanOfficer, transferDate); } @@ -2310,7 +2315,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final LoanTransaction newTransferAcceptanceTransaction = LoanTransaction.withdrawTransfer(loan.getOffice(), loan, transferDate); loan.addLoanTransaction(newTransferAcceptanceTransaction); - loan.setLoanStatus(LoanStatus.ACTIVE.getValue()); + LoanLifecycleStateMachine loanLifecycleStateMachine = defaultLoanLifecycleStateMachine(); + LoanStatus loanStatusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_WITHDRAW_TRANSFER, loan.status()); + loan.setLoanStatus(loanStatusEnum.getValue()); this.loanTransactionRepository.saveAndFlush(newTransferAcceptanceTransaction); saveLoanWithDataIntegrityViolationChecks(loan); @@ -2326,7 +2333,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf public void rejectLoanTransfer(final Loan loan) { this.loanAssembler.setHelpers(loan); businessEventNotifierService.notifyPreBusinessEvent(new LoanRejectTransferBusinessEvent(loan)); - loan.setLoanStatus(LoanStatus.TRANSFER_ON_HOLD.getValue()); + LoanLifecycleStateMachine loanLifecycleStateMachine = defaultLoanLifecycleStateMachine(); + LoanStatus loanStatusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_REJECT_TRANSFER, loan.status()); + loan.setLoanStatus(loanStatusEnum.getValue()); saveLoanWithDataIntegrityViolationChecks(loan); businessEventNotifierService.notifyPostBusinessEvent(new LoanRejectTransferBusinessEvent(loan)); } @@ -2857,8 +2866,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom); - ChangedTransactionDetail changedTransactionDetail = loan.undoWrittenOff(existingTransactionIds, existingReversedTransactionIds, - scheduleGeneratorDTO); + ChangedTransactionDetail changedTransactionDetail = loan.undoWrittenOff(defaultLoanLifecycleStateMachine(), existingTransactionIds, + existingReversedTransactionIds, scheduleGeneratorDTO); if (changedTransactionDetail != null) { for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { this.loanTransactionRepository.save(mapEntry.getValue());