This is an automated email from the ASF dual-hosted git repository.

adamsaghy 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 65b1c45a5 FINERACT-1981: Fix down-payment handling on overpaid loan 
(Advanced payment allocation loan)
65b1c45a5 is described below

commit 65b1c45a5fc988cbb8ba4bcc0f7821310ea3bbd4
Author: Adam Saghy <[email protected]>
AuthorDate: Wed Feb 28 14:05:45 2024 +0100

    FINERACT-1981: Fix down-payment handling on overpaid loan (Advanced payment 
allocation loan)
---
 .../loanaccount/domain/LoanTransaction.java        | 11 ++++--
 .../domain/DefaultLoanLifecycleStateMachine.java   |  4 +-
 .../domain/LoanAccountDomainServiceJpa.java        |  2 +-
 .../LoanWritePlatformServiceJpaRepositoryImpl.java |  4 +-
 .../DefaultLoanLifecycleStateMachineTest.java      | 43 +++++++++++++++++++++-
 5 files changed, 55 insertions(+), 9 deletions(-)

diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
index 7df92edab..f14835273 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
@@ -148,9 +148,14 @@ public class LoanTransaction extends 
AbstractAuditableWithUTCDateTimeCustom {
     }
 
     public static LoanTransaction disbursement(final Office office, final 
Money amount, final PaymentDetail paymentDetail,
-            final LocalDate disbursementDate, final ExternalId externalId) {
-        return new LoanTransaction(null, office, 
LoanTransactionType.DISBURSEMENT, paymentDetail, amount.getAmount(), 
disbursementDate,
-                externalId);
+            final LocalDate disbursementDate, final ExternalId externalId, 
final Money loanTotalOverpaid) {
+        // We need to set the overpayment amount because it could happen the 
transaction got saved before the proper
+        // portion calculation and side effect would be reverse-replay
+        Money overPaymentPortion = amount.isGreaterThan(loanTotalOverpaid) ? 
loanTotalOverpaid : amount;
+        LoanTransaction disbursement = new LoanTransaction(null, office, 
LoanTransactionType.DISBURSEMENT, paymentDetail,
+                amount.getAmount(), disbursementDate, externalId);
+        disbursement.setOverPayments(overPaymentPortion);
+        return disbursement;
     }
 
     public static LoanTransaction repayment(final Office office, final Money 
amount, final PaymentDetail paymentDetail,
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 5b12b72ba..bf2e53f49 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
@@ -82,7 +82,9 @@ public class DefaultLoanLifecycleStateMachine implements 
LoanLifecycleStateMachi
                 }
             break;
             case LOAN_DISBURSED:
-                if (anyOfAllowedWhenComingFrom(from, LoanStatus.APPROVED, 
LoanStatus.CLOSED_OBLIGATIONS_MET, LoanStatus.OVERPAID)) {
+                if (anyOfAllowedWhenComingFrom(from, LoanStatus.APPROVED, 
LoanStatus.CLOSED_OBLIGATIONS_MET)) {
+                    newState = activeTransition();
+                } else if (from.isOverpaid() && 
loan.getTotalOverpaidAsMoney().isZero()) {
                     newState = activeTransition();
                 }
             break;
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
index cc795819b..8039a6479 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
@@ -535,7 +535,7 @@ public class LoanAccountDomainServiceJpa implements 
LoanAccountDomainService {
         final List<Long> existingReversedTransactionIds = new ArrayList<>();
         final Money amount = Money.of(loan.getCurrency(), transactionAmount);
         LoanTransaction disbursementTransaction = 
LoanTransaction.disbursement(loan.getOffice(), amount, paymentDetail, 
transactionDate,
-                txnExternalId);
+                txnExternalId, loan.getTotalOverpaidAsMoney());
 
         // Subtract Previous loan outstanding balance from netDisbursalAmount
         loan.deductFromNetDisbursalAmount(transactionAmount);
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 a34a3071f..5c9759407 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
@@ -436,7 +436,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
                 
existingTransactionIds.addAll(loan.findExistingTransactionIds());
                 
existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds());
                 disbursementTransaction = 
LoanTransaction.disbursement(loan.getOffice(), amountToDisburse, paymentDetail,
-                        actualDisbursementDate, txnExternalId);
+                        actualDisbursementDate, txnExternalId, 
loan.getTotalOverpaidAsMoney());
                 disbursementTransaction.updateLoan(loan);
                 loan.addLoanTransaction(disbursementTransaction);
             }
@@ -752,7 +752,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
                     
existingTransactionIds.addAll(loan.findExistingTransactionIds());
                     
existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds());
                     LoanTransaction disbursementTransaction = 
LoanTransaction.disbursement(loan.getOffice(), disburseAmount, paymentDetail,
-                            actualDisbursementDate, txnExternalId);
+                            actualDisbursementDate, txnExternalId, 
loan.getTotalOverpaidAsMoney());
                     disbursementTransaction.updateLoan(loan);
                     loan.addLoanTransaction(disbursementTransaction);
                     businessEventNotifierService
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
index c901e7d34..c9c5e0c5d 100644
--- 
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
@@ -23,12 +23,21 @@ import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoInteractions;
 
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.LoanStatusChangedBusinessEvent;
 import 
org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
+import org.junit.jupiter.api.AfterEach;
 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.MockedStatic;
+import org.mockito.Mockito;
 import org.mockito.junit.jupiter.MockitoExtension;
 
 @ExtendWith(MockitoExtension.class)
@@ -39,11 +48,22 @@ class DefaultLoanLifecycleStateMachineTest {
 
     private DefaultLoanLifecycleStateMachine underTest;
 
+    private MockedStatic<MoneyHelper> moneyHelperStatic;
+
     @BeforeEach
     public void setUp() {
+
+        moneyHelperStatic = Mockito.mockStatic(MoneyHelper.class);
+        moneyHelperStatic.when(() -> 
MoneyHelper.getRoundingMode()).thenReturn(RoundingMode.UP);
         underTest = new 
DefaultLoanLifecycleStateMachine(businessEventNotifierService);
     }
 
+    @AfterEach
+    public void deregister() {
+        ThreadLocalContextUtil.reset();
+        moneyHelperStatic.close();
+    }
+
     @Test
     public void testTransitionShouldWorkProperlyForLoanCreation() {
         // given
@@ -113,14 +133,33 @@ class DefaultLoanLifecycleStateMachineTest {
     @Test
     public void 
testTransitionShouldWorkProperlyForLoanDisbursementWhenLoanIsOverpaid() {
         // given
-        Loan loan = createLoanWithStatus(LoanStatus.OVERPAID);
+        Money overpayment = Money.of(new MonetaryCurrency("USD", 2, null), 
BigDecimal.ZERO);
+        Loan loan = Mockito.mock(Loan.class);
+        
Mockito.when(loan.getPlainStatus()).thenReturn(LoanStatus.OVERPAID.getValue());
+        Mockito.when(loan.getStatus()).thenReturn(LoanStatus.OVERPAID);
+        Mockito.when(loan.getTotalOverpaidAsMoney()).thenReturn(overpayment);
         // when
         underTest.transition(LoanEvent.LOAN_DISBURSED, loan);
         // then
-        assertThat(loan.getStatus()).isEqualTo(LoanStatus.ACTIVE);
+        verify(loan, 
Mockito.times(1)).setLoanStatus(LoanStatus.ACTIVE.getValue());
         
verify(businessEventNotifierService).notifyPostBusinessEvent(any(LoanStatusChangedBusinessEvent.class));
     }
 
+    @Test
+    public void 
testTransitionShouldWorkProperlyForLoanDisbursementWhenLoanIsOverpaidAndRemainsOverpaid()
 {
+        // given
+        Money overpayment = Money.of(new MonetaryCurrency("USD", 2, null), 
BigDecimal.TEN);
+        Loan loan = Mockito.mock(Loan.class);
+        
Mockito.when(loan.getPlainStatus()).thenReturn(LoanStatus.OVERPAID.getValue());
+        Mockito.when(loan.getStatus()).thenReturn(LoanStatus.OVERPAID);
+        Mockito.when(loan.getTotalOverpaidAsMoney()).thenReturn(overpayment);
+        // when
+        underTest.transition(LoanEvent.LOAN_DISBURSED, loan);
+        // then
+        verify(loan, 
Mockito.never()).setLoanStatus(LoanStatus.ACTIVE.getValue());
+        verify(businessEventNotifierService, 
Mockito.never()).notifyPostBusinessEvent(any(LoanStatusChangedBusinessEvent.class));
+    }
+
     @Test
     public void testTransitionShouldWorkProperlyForLoanApprovalUndo() {
         // given

Reply via email to