FINERACT-2317: Include capitalized income and buy down fee in available disbursement calculation
This commit is contained in:
committed by
Adam Saghy
parent
9f53440543
commit
25b440b842
+18
-4
@@ -58,6 +58,7 @@ import org.apache.fineract.portfolio.loanaccount.data.InstallmentLevelDelinquenc
|
|||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanSummary;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
@@ -149,10 +150,7 @@ public class DelinquencyReadPlatformServiceImpl implements DelinquencyReadPlatfo
|
|||||||
// Overpaid
|
// Overpaid
|
||||||
// loans
|
// loans
|
||||||
collectionData = loanDelinquencyDomainService.getOverdueCollectionData(loan, effectiveDelinquencyList);
|
collectionData = loanDelinquencyDomainService.getOverdueCollectionData(loan, effectiveDelinquencyList);
|
||||||
// Ensure availableDisbursementAmount is never negative
|
collectionData.setAvailableDisbursementAmount(calculateAvailableDisbursementAmount(loan));
|
||||||
final BigDecimal availableAmount = loan.getApprovedPrincipal().subtract(loan.getDisbursedAmount());
|
|
||||||
collectionData
|
|
||||||
.setAvailableDisbursementAmount(availableAmount.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : availableAmount);
|
|
||||||
collectionData.setNextPaymentDueDate(possibleNextRepaymentDate(nextPaymentDueDateConfig, loan));
|
collectionData.setNextPaymentDueDate(possibleNextRepaymentDate(nextPaymentDueDateConfig, loan));
|
||||||
|
|
||||||
final LoanTransaction lastPayment = loan.getLastPaymentTransaction();
|
final LoanTransaction lastPayment = loan.getLastPaymentTransaction();
|
||||||
@@ -177,6 +175,22 @@ public class DelinquencyReadPlatformServiceImpl implements DelinquencyReadPlatfo
|
|||||||
return collectionData;
|
return collectionData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private BigDecimal calculateAvailableDisbursementAmount(@NonNull final Loan loan) {
|
||||||
|
BigDecimal availableDisbursementAmount = loan.getApprovedPrincipal().subtract(loan.getDisbursedAmount());
|
||||||
|
if (loan.getLoanRepaymentScheduleDetail().isEnableIncomeCapitalization()) {
|
||||||
|
final LoanSummary loanSummary = loan.getSummary();
|
||||||
|
if (loanSummary != null) {
|
||||||
|
final BigDecimal totalCapitalizedIncome = MathUtil.nullToZero(loanSummary.getTotalCapitalizedIncome());
|
||||||
|
final BigDecimal totalCapitalizedIncomeAdjustment = MathUtil.nullToZero(loanSummary.getTotalCapitalizedIncomeAdjustment());
|
||||||
|
final BigDecimal netCapitalizedIncome = totalCapitalizedIncome.subtract(totalCapitalizedIncomeAdjustment);
|
||||||
|
availableDisbursementAmount = availableDisbursementAmount.subtract(netCapitalizedIncome);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure availableDisbursementAmount is never negative
|
||||||
|
return availableDisbursementAmount.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : availableDisbursementAmount;
|
||||||
|
}
|
||||||
|
|
||||||
private void addInstallmentLevelDelinquencyData(CollectionData collectionData, Long loanId) {
|
private void addInstallmentLevelDelinquencyData(CollectionData collectionData, Long loanId) {
|
||||||
Collection<LoanInstallmentDelinquencyTagData> loanInstallmentDelinquencyTagData = retrieveLoanInstallmentsCurrentDelinquencyTag(
|
Collection<LoanInstallmentDelinquencyTagData> loanInstallmentDelinquencyTagData = retrieveLoanInstallmentsCurrentDelinquencyTag(
|
||||||
loanId);
|
loanId);
|
||||||
|
|||||||
+1
-1
@@ -164,7 +164,7 @@ public class CapitalizedIncomeWritePlatformServiceImpl implements CapitalizedInc
|
|||||||
.setAmountAdjustment(MathUtil.nullToZero(capitalizedIncomeBalance.getAmountAdjustment()).add(transactionAmount));
|
.setAmountAdjustment(MathUtil.nullToZero(capitalizedIncomeBalance.getAmountAdjustment()).add(transactionAmount));
|
||||||
capitalizedIncomeBalance.setUnrecognizedAmount(
|
capitalizedIncomeBalance.setUnrecognizedAmount(
|
||||||
MathUtil.negativeToZero(capitalizedIncomeBalance.getUnrecognizedAmount().subtract(transactionAmount)));
|
MathUtil.negativeToZero(capitalizedIncomeBalance.getUnrecognizedAmount().subtract(transactionAmount)));
|
||||||
capitalizedIncomeBalanceRepository.save(capitalizedIncomeBalance);
|
capitalizedIncomeBalanceRepository.saveAndFlush(capitalizedIncomeBalance);
|
||||||
|
|
||||||
loanLifecycleStateMachine.determineAndTransition(loan, transactionDate);
|
loanLifecycleStateMachine.determineAndTransition(loan, transactionDate);
|
||||||
|
|
||||||
|
|||||||
+8
-13
@@ -22,6 +22,7 @@ import java.math.BigDecimal;
|
|||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanStateTransitionException;
|
import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanStateTransitionException;
|
||||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanDisbursalException;
|
import org.apache.fineract.portfolio.loanaccount.exception.LoanDisbursalException;
|
||||||
@@ -35,15 +36,18 @@ public final class LoanDisbursementValidator {
|
|||||||
|
|
||||||
public void compareDisbursedToApprovedOrProposedPrincipal(final Loan loan, final BigDecimal disbursedAmount,
|
public void compareDisbursedToApprovedOrProposedPrincipal(final Loan loan, final BigDecimal disbursedAmount,
|
||||||
final BigDecimal totalDisbursed) {
|
final BigDecimal totalDisbursed) {
|
||||||
final BigDecimal capitalizedIncome = loan.getSummary().getTotalCapitalizedIncome();
|
final BigDecimal totalCapitalizedIncome = loan.getSummary().getTotalCapitalizedIncome();
|
||||||
|
final BigDecimal totalCapitalizedIncomeAdjustment = MathUtil.nullToZero(loan.getSummary().getTotalCapitalizedIncomeAdjustment());
|
||||||
|
final BigDecimal netCapitalizedIncome = totalCapitalizedIncome.subtract(totalCapitalizedIncomeAdjustment);
|
||||||
|
|
||||||
if (loan.loanProduct().isDisallowExpectedDisbursements() && loan.loanProduct().isAllowApprovedDisbursedAmountsOverApplied()) {
|
if (loan.loanProduct().isDisallowExpectedDisbursements() && loan.loanProduct().isAllowApprovedDisbursedAmountsOverApplied()) {
|
||||||
validateOverMaximumAmount(loan, totalDisbursed, capitalizedIncome);
|
validateOverMaximumAmount(loan, totalDisbursed, netCapitalizedIncome);
|
||||||
} else {
|
} else {
|
||||||
if (loan.loanProduct().isAllowApprovedDisbursedAmountsOverApplied()) {
|
if (loan.loanProduct().isAllowApprovedDisbursedAmountsOverApplied()) {
|
||||||
validateOverMaximumAmount(loan, disbursedAmount, capitalizedIncome);
|
validateOverMaximumAmount(loan, disbursedAmount, netCapitalizedIncome);
|
||||||
} else {
|
} else {
|
||||||
if ((totalDisbursed.compareTo(loan.getApprovedPrincipal()) > 0)
|
if ((totalDisbursed.compareTo(loan.getApprovedPrincipal()) > 0)
|
||||||
|| (totalDisbursed.add(capitalizedIncome).compareTo(loan.getApprovedPrincipal()) > 0)) {
|
|| (totalDisbursed.add(netCapitalizedIncome).compareTo(loan.getApprovedPrincipal()) > 0)) {
|
||||||
final String errorMsg = "Loan can't be disbursed, disburse amount is exceeding approved principal.";
|
final String errorMsg = "Loan can't be disbursed, disburse amount is exceeding approved principal.";
|
||||||
throw new LoanDisbursalException(errorMsg, "disburse.amount.must.be.less.than.approved.principal", totalDisbursed,
|
throw new LoanDisbursalException(errorMsg, "disburse.amount.must.be.less.than.approved.principal", totalDisbursed,
|
||||||
loan.getApprovedPrincipal());
|
loan.getApprovedPrincipal());
|
||||||
@@ -65,15 +69,6 @@ public final class LoanDisbursementValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void validateDisburseAmountNotExceedingApprovedAmount(final Loan loan, final BigDecimal diff,
|
|
||||||
final BigDecimal principalDisbursed) {
|
|
||||||
if (!loan.loanProduct().isMultiDisburseLoan() && diff.compareTo(BigDecimal.ZERO) < 0) {
|
|
||||||
final String errorMsg = "Loan can't be disbursed,disburse amount is exceeding approved amount ";
|
|
||||||
throw new LoanDisbursalException(errorMsg, "disburse.amount.must.be.less.than.approved.amount", principalDisbursed,
|
|
||||||
loan.getLoanRepaymentScheduleDetail().getPrincipal().getAmount());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void validateDisburseDate(final Loan loan, final LocalDate disbursedOn, final LocalDate expectedDate) {
|
public void validateDisburseDate(final Loan loan, final LocalDate disbursedOn, final LocalDate expectedDate) {
|
||||||
if (expectedDate != null
|
if (expectedDate != null
|
||||||
&& (DateUtils.isAfter(disbursedOn, loan.fetchRepaymentScheduleInstallment(1).getDueDate())
|
&& (DateUtils.isAfter(disbursedOn, loan.fetchRepaymentScheduleInstallment(1).getDueDate())
|
||||||
|
|||||||
+6
@@ -100,6 +100,7 @@ import org.apache.fineract.portfolio.loanaccount.service.LoanChargeService;
|
|||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionProcessingService;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionProcessingService;
|
||||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
|
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
|
||||||
|
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@@ -367,9 +368,14 @@ public class LoanAccountDelinquencyRangeEventSerializerTest {
|
|||||||
|
|
||||||
LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
|
LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
|
||||||
when(loanProduct.isMultiDisburseLoan()).thenReturn(false);
|
when(loanProduct.isMultiDisburseLoan()).thenReturn(false);
|
||||||
|
|
||||||
|
LoanProductRelatedDetail loanProductRelatedDetail = Mockito.mock(LoanProductRelatedDetail.class);
|
||||||
|
when(loanProductRelatedDetail.isEnableIncomeCapitalization()).thenReturn(false);
|
||||||
|
|
||||||
Loan loan = Mockito.spy(Loan.class);
|
Loan loan = Mockito.spy(Loan.class);
|
||||||
ReflectionTestUtils.setField(loan, "loanProduct", loanProduct);
|
ReflectionTestUtils.setField(loan, "loanProduct", loanProduct);
|
||||||
ReflectionTestUtils.setField(loan, "loanStatus", LoanStatus.ACTIVE);
|
ReflectionTestUtils.setField(loan, "loanStatus", LoanStatus.ACTIVE);
|
||||||
|
when(loan.getLoanRepaymentScheduleDetail()).thenReturn(loanProductRelatedDetail);
|
||||||
LoanTransaction transaction1 = Mockito.mock(LoanTransaction.class);
|
LoanTransaction transaction1 = Mockito.mock(LoanTransaction.class);
|
||||||
LoanTransaction transaction2 = Mockito.mock(LoanTransaction.class);
|
LoanTransaction transaction2 = Mockito.mock(LoanTransaction.class);
|
||||||
CollectionData collectionData = Mockito.mock(CollectionData.class);
|
CollectionData collectionData = Mockito.mock(CollectionData.class);
|
||||||
|
|||||||
Reference in New Issue
Block a user