FINERACT-2326: Add additional field which would return available disbursement amount with over applied amount
This commit is contained in:
committed by
Adam Saghy
parent
8bd9444689
commit
2cba4430c7
+5
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.delinquency.service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
|
||||
@@ -26,6 +27,8 @@ import org.apache.fineract.portfolio.delinquency.data.LoanDelinquencyTagHistoryD
|
||||
import org.apache.fineract.portfolio.delinquency.data.LoanInstallmentDelinquencyTagData;
|
||||
import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyAction;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.CollectionData;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
public interface DelinquencyReadPlatformService {
|
||||
|
||||
@@ -43,6 +46,8 @@ public interface DelinquencyReadPlatformService {
|
||||
|
||||
CollectionData calculateLoanCollectionData(Long loanId);
|
||||
|
||||
BigDecimal calculateAvailableDisbursementAmountWithOverApplied(@NonNull Loan loan);
|
||||
|
||||
Collection<LoanInstallmentDelinquencyTagData> retrieveLoanInstallmentsCurrentDelinquencyTag(Long loanId);
|
||||
|
||||
List<LoanDelinquencyAction> retrieveLoanDelinquencyActions(Long loanId);
|
||||
|
||||
+59
-1
@@ -34,6 +34,7 @@ import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDoma
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
|
||||
import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
|
||||
import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData;
|
||||
import org.apache.fineract.portfolio.delinquency.data.LoanDelinquencyTagHistoryData;
|
||||
@@ -56,11 +57,14 @@ import org.apache.fineract.portfolio.loanaccount.data.CollectionData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.DelinquencyPausePeriod;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.InstallmentLevelDelinquency;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||
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.LoanTransactionRepository;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
|
||||
import org.apache.fineract.portfolio.loanproduct.exception.LoanProductGeneralRuleException;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@@ -138,7 +142,12 @@ public class DelinquencyReadPlatformServiceImpl implements DelinquencyReadPlatfo
|
||||
|
||||
// If the Loan is not Active yet or is cancelled (rejected or withdrawn), return template data
|
||||
if (loan.isSubmittedAndPendingApproval() || loan.isApproved() || loan.isCancelled()) {
|
||||
return CollectionData.template();
|
||||
if (loan.getLoanProduct() != null && !loan.getLoanProduct().isAllowApprovedDisbursedAmountsOverApplied()) {
|
||||
return collectionData;
|
||||
} else {
|
||||
collectionData.setAvailableDisbursementAmountWithOverApplied(calculateAvailableDisbursementAmountWithOverApplied(loan));
|
||||
return collectionData;
|
||||
}
|
||||
}
|
||||
|
||||
final List<LoanDelinquencyAction> savedDelinquencyList = retrieveLoanDelinquencyActions(loanId);
|
||||
@@ -152,6 +161,7 @@ public class DelinquencyReadPlatformServiceImpl implements DelinquencyReadPlatfo
|
||||
// loans
|
||||
collectionData = loanDelinquencyDomainService.getOverdueCollectionData(loan, effectiveDelinquencyList);
|
||||
collectionData.setAvailableDisbursementAmount(calculateAvailableDisbursementAmount(loan));
|
||||
collectionData.setAvailableDisbursementAmountWithOverApplied(calculateAvailableDisbursementAmountWithOverApplied(loan));
|
||||
collectionData.setNextPaymentDueDate(possibleNextRepaymentDate(nextPaymentDueDateConfig, loan));
|
||||
PossibleNextRepaymentCalculationService possibleNextRepaymentCalculationService = possibleNextRepaymentCalculationServiceDiscovery
|
||||
.getService(loan);
|
||||
@@ -182,6 +192,54 @@ public class DelinquencyReadPlatformServiceImpl implements DelinquencyReadPlatfo
|
||||
return collectionData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal calculateAvailableDisbursementAmountWithOverApplied(@NonNull final Loan loan) {
|
||||
final LoanProduct loanProduct = loan.getLoanProduct();
|
||||
|
||||
// Start with approved principal
|
||||
BigDecimal approvedWithOverApplied = loan.getApprovedPrincipal();
|
||||
|
||||
// If over applied amount is enabled, calculate the maximum allowed amount
|
||||
if (loanProduct.isAllowApprovedDisbursedAmountsOverApplied()) {
|
||||
if (loanProduct.getOverAppliedCalculationType() != null) {
|
||||
if ("percentage".equalsIgnoreCase(loanProduct.getOverAppliedCalculationType())) {
|
||||
final BigDecimal overAppliedNumber = BigDecimal.valueOf(loanProduct.getOverAppliedNumber());
|
||||
final BigDecimal totalPercentage = BigDecimal.valueOf(1)
|
||||
.add(overAppliedNumber.divide(BigDecimal.valueOf(100), MoneyHelper.getMathContext()));
|
||||
approvedWithOverApplied = loan.getProposedPrincipal().multiply(totalPercentage);
|
||||
} else {
|
||||
approvedWithOverApplied = loan.getProposedPrincipal().add(BigDecimal.valueOf(loanProduct.getOverAppliedNumber()));
|
||||
}
|
||||
} else {
|
||||
throw new LoanProductGeneralRuleException("overAppliedCalculationType.must.be.percentage.or.flat",
|
||||
"Over Applied Calculation Type Must Be 'percentage' or 'flat'");
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate available amount: (approved + over applied) - expected tranches - disbursed - capitalized income
|
||||
if (loan.isMultiDisburmentLoan() && loan.getDisbursementDetails() != null) {
|
||||
final BigDecimal expectedDisbursementAmount = loan.getDisbursementDetails().stream()
|
||||
.filter(detail -> detail.actualDisbursementDate() == null).map(LoanDisbursementDetails::principal)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
approvedWithOverApplied = approvedWithOverApplied.subtract(expectedDisbursementAmount);
|
||||
}
|
||||
|
||||
BigDecimal availableDisbursementAmount = approvedWithOverApplied.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 BigDecimal calculateAvailableDisbursementAmount(@NonNull final Loan loan) {
|
||||
BigDecimal availableDisbursementAmount = loan.getApprovedPrincipal().subtract(loan.getDisbursedAmount());
|
||||
if (loan.getLoanRepaymentScheduleDetail().isEnableIncomeCapitalization()) {
|
||||
|
||||
+2
-1
@@ -29,6 +29,7 @@ import lombok.Data;
|
||||
public final class CollectionData {
|
||||
|
||||
private BigDecimal availableDisbursementAmount;
|
||||
private BigDecimal availableDisbursementAmountWithOverApplied;
|
||||
private Long pastDueDays;
|
||||
private LocalDate nextPaymentDueDate;
|
||||
private BigDecimal nextPaymentAmount;
|
||||
@@ -51,7 +52,7 @@ public final class CollectionData {
|
||||
|
||||
public static CollectionData template() {
|
||||
final BigDecimal zero = BigDecimal.ZERO;
|
||||
return new CollectionData(zero, 0L, null, zero, 0L, null, zero, null, zero, null, zero, null, null, zero, zero, zero, zero);
|
||||
return new CollectionData(zero, zero, 0L, null, zero, 0L, null, zero, null, zero, null, zero, null, null, zero, zero, zero, zero);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+4
-1
@@ -32,6 +32,7 @@ public class LoanApprovalData {
|
||||
private final LocalDate approvalDate;
|
||||
private final BigDecimal approvalAmount;
|
||||
private final BigDecimal netDisbursalAmount;
|
||||
private final BigDecimal availableDisbursementAmountWithOverApplied;
|
||||
|
||||
// import fields
|
||||
private LocalDate approvedOnDate;
|
||||
@@ -54,14 +55,16 @@ public class LoanApprovalData {
|
||||
this.approvalAmount = null;
|
||||
this.approvalDate = null;
|
||||
this.netDisbursalAmount = null;
|
||||
this.availableDisbursementAmountWithOverApplied = null;
|
||||
}
|
||||
|
||||
public LoanApprovalData(final BigDecimal approvalAmount, final LocalDate approvalDate, final BigDecimal netDisbursalAmount,
|
||||
final CurrencyData currency) {
|
||||
final CurrencyData currency, final BigDecimal availableDisbursementAmountWithOverApplied) {
|
||||
this.approvalDate = approvalDate;
|
||||
this.approvalAmount = approvalAmount;
|
||||
this.netDisbursalAmount = netDisbursalAmount;
|
||||
this.currency = currency;
|
||||
this.availableDisbursementAmountWithOverApplied = availableDisbursementAmountWithOverApplied;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+15
-12
@@ -25,6 +25,7 @@ import java.time.LocalDate;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
|
||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||
import org.apache.fineract.organisation.monetary.data.CurrencyData;
|
||||
@@ -70,6 +71,7 @@ public class LoanTransactionData implements Serializable {
|
||||
private final LocalDate submittedOnDate;
|
||||
private final boolean manuallyReversed;
|
||||
private final LocalDate possibleNextRepaymentDate;
|
||||
private final BigDecimal availableDisbursementAmountWithOverApplied;
|
||||
|
||||
private Collection<LoanChargePaidByData> loanChargePaidByList;
|
||||
|
||||
@@ -78,6 +80,7 @@ public class LoanTransactionData implements Serializable {
|
||||
|
||||
private Collection<CodeValueData> writeOffReasonOptions = null;
|
||||
|
||||
@Setter
|
||||
private Integer numberOfRepayments = 0;
|
||||
|
||||
// import fields
|
||||
@@ -94,6 +97,7 @@ public class LoanTransactionData implements Serializable {
|
||||
private Integer bankNumber;
|
||||
private transient Long accountId;
|
||||
private transient String transactionType;
|
||||
@Setter
|
||||
private List<LoanRepaymentScheduleInstallmentData> loanRepaymentScheduleInstallments;
|
||||
|
||||
// Reverse Data
|
||||
@@ -144,6 +148,7 @@ public class LoanTransactionData implements Serializable {
|
||||
this.paymentTypeOptions = null;
|
||||
this.writeOffReasonOptions = null;
|
||||
this.reversalExternalId = ExternalId.empty();
|
||||
this.availableDisbursementAmountWithOverApplied = null;
|
||||
}
|
||||
|
||||
public static LoanTransactionData importInstance(BigDecimal repaymentAmount, LocalDate repaymentDate, Long repaymentTypeId,
|
||||
@@ -196,14 +201,7 @@ public class LoanTransactionData implements Serializable {
|
||||
this.paymentTypeOptions = null;
|
||||
this.writeOffReasonOptions = null;
|
||||
this.reversalExternalId = ExternalId.empty();
|
||||
}
|
||||
|
||||
public void setNumberOfRepayments(Integer numberOfRepayments) {
|
||||
this.numberOfRepayments = numberOfRepayments;
|
||||
}
|
||||
|
||||
public void setLoanRepaymentScheduleInstallments(final List<LoanRepaymentScheduleInstallmentData> loanRepaymentScheduleInstallments) {
|
||||
this.loanRepaymentScheduleInstallments = loanRepaymentScheduleInstallments;
|
||||
this.availableDisbursementAmountWithOverApplied = null;
|
||||
}
|
||||
|
||||
public static LoanTransactionData templateOnTop(final LoanTransactionData loanTransactionData,
|
||||
@@ -301,6 +299,7 @@ public class LoanTransactionData implements Serializable {
|
||||
this.possibleNextRepaymentDate = null;
|
||||
this.reversalExternalId = reversalExternalId;
|
||||
this.reversedOnDate = reversedOnDate;
|
||||
this.availableDisbursementAmountWithOverApplied = null;
|
||||
}
|
||||
|
||||
public LoanTransactionData(Long id, LoanTransactionEnumData transactionType, LocalDate date, BigDecimal totalAmount,
|
||||
@@ -336,17 +335,20 @@ public class LoanTransactionData implements Serializable {
|
||||
final LocalDate submittedOnDate = null;
|
||||
final LocalDate possibleNextRepaymentDate = null;
|
||||
final boolean manuallyReversed = false;
|
||||
final BigDecimal availableDisbursementAmountWithOverApplied = null;
|
||||
return new LoanTransactionData(id, officeId, officeName, transactionType, paymentDetailData, currency, transactionDate,
|
||||
transactionAmount, netDisbursalAmount, principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion,
|
||||
overpaymentPortion, unrecognizedIncomePortion, paymentOptions, transfer, externalId, fixedEmiAmount, outstandingLoanBalance,
|
||||
submittedOnDate, manuallyReversed, possibleNextRepaymentDate, loanId, externalLoanId);
|
||||
submittedOnDate, manuallyReversed, possibleNextRepaymentDate, loanId, externalLoanId,
|
||||
availableDisbursementAmountWithOverApplied);
|
||||
|
||||
}
|
||||
|
||||
public static LoanTransactionData loanTransactionDataForDisbursalTemplate(final LoanTransactionEnumData transactionType,
|
||||
final LocalDate expectedDisbursedOnLocalDateForTemplate, final BigDecimal disburseAmountForTemplate,
|
||||
final BigDecimal netDisbursalAmount, final Collection<PaymentTypeData> paymentOptions, final BigDecimal retriveLastEmiAmount,
|
||||
final LocalDate possibleNextRepaymentDate, final CurrencyData currency) {
|
||||
final LocalDate possibleNextRepaymentDate, final CurrencyData currency,
|
||||
final BigDecimal availableDisbursementAmountWithOverApplied) {
|
||||
final Long id = null;
|
||||
final Long loanId = null;
|
||||
final ExternalId externalLoanId = ExternalId.empty();
|
||||
@@ -368,7 +370,7 @@ public class LoanTransactionData implements Serializable {
|
||||
expectedDisbursedOnLocalDateForTemplate, disburseAmountForTemplate, netDisbursalAmount, principalPortion, interestPortion,
|
||||
feeChargesPortion, penaltyChargesPortion, overpaymentPortion, unrecognizedIncomePortion, paymentOptions, transfer,
|
||||
externalId, retriveLastEmiAmount, outstandingLoanBalance, submittedOnDate, manuallyReversed, possibleNextRepaymentDate,
|
||||
loanId, externalLoanId);
|
||||
loanId, externalLoanId, availableDisbursementAmountWithOverApplied);
|
||||
|
||||
}
|
||||
|
||||
@@ -379,7 +381,7 @@ public class LoanTransactionData implements Serializable {
|
||||
BigDecimal unrecognizedIncomePortion, Collection<PaymentTypeData> paymentOptions, final AccountTransferData transfer,
|
||||
final ExternalId externalId, final BigDecimal fixedEmiAmount, BigDecimal outstandingLoanBalance,
|
||||
final LocalDate submittedOnDate, final boolean manuallyReversed, final LocalDate possibleNextRepaymentDate, Long loanId,
|
||||
ExternalId externalLoanId) {
|
||||
ExternalId externalLoanId, final BigDecimal availableDisbursementAmountWithOverApplied) {
|
||||
this.id = id;
|
||||
this.loanId = loanId;
|
||||
this.externalLoanId = externalLoanId;
|
||||
@@ -406,6 +408,7 @@ public class LoanTransactionData implements Serializable {
|
||||
this.manuallyReversed = manuallyReversed;
|
||||
this.possibleNextRepaymentDate = possibleNextRepaymentDate;
|
||||
this.reversalExternalId = ExternalId.empty();
|
||||
this.availableDisbursementAmountWithOverApplied = availableDisbursementAmountWithOverApplied;
|
||||
}
|
||||
|
||||
public boolean isNotDisbursement() {
|
||||
|
||||
+5
-1
@@ -814,7 +814,11 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
}
|
||||
return principal;
|
||||
} else {
|
||||
return getNetDisbursalAmount();
|
||||
if (this.actualDisbursementDate == null) {
|
||||
return BigDecimal.ZERO;
|
||||
} else {
|
||||
return getNetDisbursalAmount();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user