FINERACT-1734: Refactoring around Loan status changes as a preparation to emit Loan Status change business events

This commit is contained in:
Arnold Galovics
2022-09-09 13:13:37 +02:00
committed by Arnold Gálovics
parent 8bb2e9230c
commit d003326e48
4 changed files with 109 additions and 30 deletions
@@ -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<LoanStatus> allowedLoanStatuses;
public DefaultLoanLifecycleStateMachine(final List<LoanStatus> 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<LoanStatus> allowedLoanStatuses) {
LoanStatus match = null;
for (final LoanStatus loanStatus : allowedLoanStatuses) {
@@ -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<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
public ChangedTransactionDetail undoWrittenOff(LoanLifecycleStateMachine loanLifecycleStateMachine,
final List<Long> existingTransactionIds, final List<Long> 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<LoanTransaction> 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();
}
}
@@ -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;
}
@@ -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<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
this.loanTransactionRepository.save(mapEntry.getValue());