FINERACT-2081: fix loan status on CBR reverse

This commit is contained in:
Kristof Jozsa
2024-08-11 15:13:36 +02:00
committed by Adam Saghy
parent c70007e30a
commit 22b10d933b
3 changed files with 48 additions and 13 deletions
@@ -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 @@ import org.springframework.stereotype.Component;
@RequiredArgsConstructor
public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachine {
private static Logger LOG = LoggerFactory.getLogger(DefaultLoanLifecycleStateMachine.class);
private static final List<LoanStatus> ALLOWED_LOAN_STATUSES = List.of(LoanStatus.values());
private final BusinessEventNotifierService businessEventNotifierService;
@@ -40,6 +45,9 @@ public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachi
@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 class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachi
}
// 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 ACTIVE -> {
if (loan.getClosedOnDate() != null) {
loan.setClosedOnDate(null);
}
if (loan.getOverpaidOnDate() != null) {
loan.setOverpaidOnDate(null);
}
case SUBMITTED_AND_PENDING_APPROVAL -> {
loan.setApprovedOnDate(null);
loan.setApprovedBy(null);
}
default -> {
case APPROVED -> {
loan.setDisbursedBy(null);
loan.setActualDisbursementDate(null);
}
case ACTIVE -> {
loan.setClosedBy(null);
loan.setClosedOnDate(null);
loan.setOverpaidOnDate(null);
}
default -> { // no fields need to get cleared
}
}
}
@@ -145,7 +159,12 @@ public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachi
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:
@@ -1300,7 +1300,9 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
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 class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
}
public LoanStatus getStatus() {
return LoanStatus.fromInt(this.loanStatus);
return this.loanStatus == null ? null : LoanStatus.fromInt(this.loanStatus);
}
public Integer getPlainStatus() {
@@ -245,7 +245,8 @@ public class ClientLoanCreditBalanceRefundandRepaymentTypeIntegrationTest extend
@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 class ClientLoanCreditBalanceRefundandRepaymentTypeIntegrationTest extend
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 class ClientLoanCreditBalanceRefundandRepaymentTypeIntegrationTest extend
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