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
@@ -11,6 +11,14 @@
|
||||
"bigdecimal"
|
||||
]
|
||||
},
|
||||
{
|
||||
"default": null,
|
||||
"name": "availableDisbursementAmountWithOverApplied",
|
||||
"type": [
|
||||
"null",
|
||||
"bigdecimal"
|
||||
]
|
||||
},
|
||||
{
|
||||
"default": null,
|
||||
"name": "pastDueDays",
|
||||
|
||||
+1
@@ -122,6 +122,7 @@ public enum DefaultLoanProduct implements LoanProduct {
|
||||
LP2_ADV_DP_IR_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL, //
|
||||
LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_DISBURSEMENT_CHARGES, //
|
||||
LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_MULTIDISBURSE_EXPECT_TRANCHE, //
|
||||
LP2_ADV_PYMNT_INT_DAILY_EMI_360_30_INT_RECALC_DAILY_MULTIDISB_EXPECT_TRANCHE_APPROVED_OVER_APPLIED, //
|
||||
LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_MULTIDISBURSE, //
|
||||
LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_CASH_ACCOUNTING_DISBURSEMENT_CHARGES, //
|
||||
LP2_ADV_PYMNT_INT_DAILY_EMI_ACTUAL_ACTUAL_INT_REFUND_FULL_ZERO_INT_CHARGE_OFF, //
|
||||
|
||||
+6
@@ -1066,4 +1066,10 @@ public final class ErrorMessageHelper {
|
||||
return String.format("%nNumber of lines does not match in Deferred Income tab and expected datatable of resource %s." //
|
||||
+ "%nNumber of transaction tab lines: %s %nNumber of expected datatable lines: %s%n", resourceId, actual, expected);
|
||||
}
|
||||
|
||||
public static String wrongAvailableDisbursementAmountWithOverApplied(final double actual, final double expected) {
|
||||
return String.format(
|
||||
"Wrong value in LoanDetails/availableDisbursementAmountWithOverApplied. %nActual value is: %s %nExpected Value is: %s",
|
||||
actual, expected);
|
||||
}
|
||||
}
|
||||
|
||||
+36
@@ -3658,6 +3658,42 @@ public class LoanProductGlobalInitializerStep implements FineractGlobalInitializ
|
||||
TestContext.INSTANCE.set(
|
||||
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_PROGRESSIVE_ADV_PYMNT_BUYDOWN_FEES_NON_MERCHANT_CHARGE_OFF_REASON,
|
||||
responseLoanProductsRequestLP2ProgressiveAdvPaymentBuyDownFeesNonMerchantWithChargeOffReason);
|
||||
|
||||
// LP2 with progressive loan schedule + horizontal + interest recalculation daily EMI + 360/30 +
|
||||
// multidisbursement
|
||||
// Frequency for recalculate Outstanding Principal: Daily, Frequency Interval for recalculation: 1
|
||||
// allow approved/disbursed amount over applied amount is enabled with percentage type
|
||||
// (LP2_ADV_PYMNT_INT_DAILY_EMI_360_30_INT_RECALC_DAILY_MULTIDISB_EXPECT_TRANCHE_APPROVED_OVER_APPLIED)
|
||||
final String name138 = DefaultLoanProduct.LP2_ADV_PYMNT_INT_DAILY_EMI_360_30_INT_RECALC_DAILY_MULTIDISB_EXPECT_TRANCHE_APPROVED_OVER_APPLIED
|
||||
.getName();
|
||||
final PostLoanProductsRequest loanProductsRequestLP2AdvEmi36030IntRecalcDailyMultiDisbApprovedOverApplied = loanProductsRequestFactory
|
||||
.defaultLoanProductsRequestLP2Emi()//
|
||||
.name(name138)//
|
||||
.daysInYearType(DaysInYearType.DAYS360.value)//
|
||||
.daysInMonthType(DaysInMonthType.DAYS30.value)//
|
||||
.isInterestRecalculationEnabled(true)//
|
||||
.preClosureInterestCalculationStrategy(1)//
|
||||
.rescheduleStrategyMethod(4)//
|
||||
.interestRecalculationCompoundingMethod(0)//
|
||||
.recalculationRestFrequencyType(2)//
|
||||
.recalculationRestFrequencyInterval(1)//
|
||||
.paymentAllocation(List.of(//
|
||||
createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"), //
|
||||
createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT"), //
|
||||
createPaymentAllocation("MERCHANT_ISSUED_REFUND", "REAMORTIZATION"), //
|
||||
createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT")))//
|
||||
.multiDisburseLoan(true)//
|
||||
.disallowExpectedDisbursements(false)//
|
||||
.allowApprovedDisbursedAmountsOverApplied(true)//
|
||||
.overAppliedCalculationType(OverAppliedCalculationType.PERCENTAGE.value)//
|
||||
.overAppliedNumber(50)//
|
||||
.maxTrancheCount(10)//
|
||||
.outstandingLoanBalance(10000.0);//
|
||||
final Response<PostLoanProductsResponse> responseLoanProductsRequestLP2AdvEmi36030IntRecalcDailyMultiDisbApprovedOverApplied = loanProductsApi
|
||||
.createLoanProduct(loanProductsRequestLP2AdvEmi36030IntRecalcDailyMultiDisbApprovedOverApplied).execute();
|
||||
TestContext.INSTANCE.set(
|
||||
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INT_DAILY_EMI_360_30_INT_RECALC_DAILY_MULTIDISB_EXPECT_TRANCHE_APPROVED_OVER_APPLIED,
|
||||
responseLoanProductsRequestLP2AdvEmi36030IntRecalcDailyMultiDisbApprovedOverApplied);
|
||||
}
|
||||
|
||||
public static AdvancedPaymentData createPaymentAllocation(String transactionType, String futureInstallmentAllocationRule,
|
||||
|
||||
+19
@@ -3293,6 +3293,25 @@ public class LoanStepDef extends AbstractStepDef {
|
||||
assertThat(fixedLengthactual).as(ErrorMessageHelper.wrongfixedLength(fixedLengthactual, fieldValue)).isEqualTo(fieldValue);
|
||||
}
|
||||
|
||||
@Then("Loan has availableDisbursementAmountWithOverApplied field with value: {double}")
|
||||
public void checkLoanDetailsAvailableDisbursementAmountWithOverAppliedField(final double fieldValue) throws IOException {
|
||||
final Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
|
||||
assert loanResponse.body() != null;
|
||||
final long loanId = loanResponse.body().getLoanId();
|
||||
|
||||
final Response<GetLoansLoanIdResponse> loanDetails = loansApi.retrieveLoan(loanId, false, "", "", "").execute();
|
||||
ErrorHelper.checkSuccessfulApiCall(loanDetails);
|
||||
|
||||
assert loanDetails.body() != null;
|
||||
assert loanDetails.body().getDelinquent() != null;
|
||||
assert loanDetails.body().getDelinquent().getAvailableDisbursementAmountWithOverApplied() != null;
|
||||
final Double availableDisbursementAmountWithOverApplied = loanDetails.body().getDelinquent()
|
||||
.getAvailableDisbursementAmountWithOverApplied().doubleValue();
|
||||
assertThat(availableDisbursementAmountWithOverApplied).as(
|
||||
ErrorMessageHelper.wrongAvailableDisbursementAmountWithOverApplied(availableDisbursementAmountWithOverApplied, fieldValue))
|
||||
.isEqualTo(fieldValue);
|
||||
}
|
||||
|
||||
@Then("Loan emi amount variations has {int} variation, with the following data:")
|
||||
public void loanEmiAmountVariationsCheck(final int linesExpected, final DataTable table) throws IOException {
|
||||
final Response<PostLoansResponse> loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
|
||||
|
||||
+1
@@ -152,6 +152,7 @@ public abstract class TestContextKey {
|
||||
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALC_EMI_360_30_CHARGEBACK_PRINCIPAL_INTEREST_FEE = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyInterestRecalcEmi36030ChargebackPrincipalInterestFee";
|
||||
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_DISBURSEMENT_CHARGES = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030InterestRecalculationDailyDisbursementCharge";
|
||||
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_MULTIDISBURSE_EXPECT_TRANCHE = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030InterestRecalculationDailyMultidisburseExpectTranche";
|
||||
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INT_DAILY_EMI_360_30_INT_RECALC_DAILY_MULTIDISB_EXPECT_TRANCHE_APPROVED_OVER_APPLIED = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030InterestRecalculationDailyMultidisburseExpectTrancheApprovedOVerAppliedPercentage";
|
||||
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_MULTIDISBURSE = "loanProductCreateResponseLP2AdvancedPaymentInterestRecalculationDailyEmi36030Multidisburse";
|
||||
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_CASH_ACCOUNTING_DISBURSEMENT_CHARGES = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030InterestRecalculationDailyCashBasedDisbursementCharge";
|
||||
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_PROGRESSIVE_ADV_PYMNT_CAPITALIZED_INCOME = "loanProductCreateResponseLP2ProgressiveAdvancedPaymentCapitalizedIncome";
|
||||
|
||||
@@ -7958,3 +7958,66 @@ Feature: Loan
|
||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance |
|
||||
| 01 June 2024 | Disbursement | 1200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1200.0 |
|
||||
| 01 July 2024 | Repayment | 417.03 | 405.03 | 12.0 | 0.0 | 0.0 | 794.97 |
|
||||
|
||||
@TestRailId:C3999
|
||||
Scenario: Verify availableDisbursementAmountWithOverApplied field calculation
|
||||
When Admin sets the business date to "1 January 2025"
|
||||
And Admin creates a client with random data
|
||||
And Admin creates a fully customized loan with the following data:
|
||||
| LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy |
|
||||
| LP2_ADV_PYMNT_INTEREST_DAILY_RECALC_EMI_360_30_MULTIDISB_APPROVED_OVER_APPLIED_CAPITALIZED_INCOME | 01 January 2025 | 1000 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION |
|
||||
Then Loan has availableDisbursementAmountWithOverApplied field with value: 1500
|
||||
And Admin successfully approves the loan on "1 January 2025" with "1000" amount and expected disbursement date on "1 January 2025"
|
||||
Then Loan has availableDisbursementAmountWithOverApplied field with value: 1500
|
||||
And Admin successfully disburse the loan on "1 January 2025" with "900" EUR transaction amount
|
||||
Then Loan has availableDisbursementAmountWithOverApplied field with value: 600
|
||||
And Admin successfully disburse the loan on "1 January 2025" with "300" EUR transaction amount
|
||||
Then Loan has availableDisbursementAmountWithOverApplied field with value: 300
|
||||
And Admin adds capitalized income with "AUTOPAY" payment type to the loan on "1 January 2025" with "100" EUR transaction amount
|
||||
Then Loan has availableDisbursementAmountWithOverApplied field with value: 200
|
||||
And Admin adds capitalized income with "AUTOPAY" payment type to the loan on "1 January 2025" with "150" EUR transaction amount
|
||||
Then Loan has availableDisbursementAmountWithOverApplied field with value: 50
|
||||
When Loan Pay-off is made on "01 January 2025"
|
||||
Then Loan's all installments have obligations met
|
||||
|
||||
@TestRailId:C4003
|
||||
Scenario: Verify availableDisbursementAmountWithOverApplied field calculation for progressive multidisbursal loan that expects tranches
|
||||
When Admin sets the business date to "01 January 2025"
|
||||
When Admin creates a client with random data
|
||||
When Admin creates a fully customized loan with three expected disbursements details and following data:
|
||||
| LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | 1st_tranche_disb_expected_date | 1st_tranche_disb_principal | 2nd_tranche_disb_expected_date | 2nd_tranche_disb_principal | 3rd_tranche_disb_expected_date | 3rd_tranche_disb_principal |
|
||||
| LP2_ADV_PYMNT_INT_DAILY_EMI_360_30_INT_RECALC_DAILY_MULTIDISB_EXPECT_TRANCHE_APPROVED_OVER_APPLIED | 01 January 2025 | 1000 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | 01 January 2025 | 300.0 | 02 January 2025 | 200.0 | 03 January 2025 | 500.0 |
|
||||
Then Loan Tranche Details tab has the following data:
|
||||
| Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount |
|
||||
| 01 January 2025 | | 300.0 | |
|
||||
| 02 January 2025 | | 200.0 | |
|
||||
| 03 January 2025 | | 500.0 | |
|
||||
Then Loan has availableDisbursementAmountWithOverApplied field with value: 500
|
||||
And Admin successfully approves the loan on "01 January 2025" with "1000" amount and expected disbursement date on "01 January 2025"
|
||||
Then Loan Tranche Details tab has the following data:
|
||||
| Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount |
|
||||
| 01 January 2025 | | 300.0 | |
|
||||
| 02 January 2025 | | 200.0 | |
|
||||
| 03 January 2025 | | 500.0 | |
|
||||
Then Loan has availableDisbursementAmountWithOverApplied field with value: 500
|
||||
When Admin successfully disburse the loan on "01 January 2025" with "300" EUR transaction amount
|
||||
And Loan Transactions tab has the following data:
|
||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
|
||||
| 01 January 2025 | Disbursement | 300.0 | 0.0 | 0.0 | 0.0 | 0.0 | 300.0 | false | false |
|
||||
Then Loan has availableDisbursementAmountWithOverApplied field with value: 500
|
||||
When Admin sets the business date to "02 January 2025"
|
||||
When Admin successfully disburse the loan on "02 January 2025" with "200" EUR transaction amount
|
||||
Then Loan has availableDisbursementAmountWithOverApplied field with value: 500
|
||||
When Admin sets the business date to "03 January 2025"
|
||||
When Admin successfully disburse the loan on "03 January 2025" with "1000" EUR transaction amount
|
||||
Then Loan has availableDisbursementAmountWithOverApplied field with value: 0
|
||||
Then Loan Tranche Details tab has the following data:
|
||||
| Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount |
|
||||
| 01 January 2025 | 01 January 2025 | 300.0 | |
|
||||
| 02 January 2025 | 02 January 2025 | 200.0 | |
|
||||
| 03 January 2025 | 03 January 2025 | 1000.0 | |
|
||||
And Loan Transactions tab has the following data:
|
||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
|
||||
| 01 January 2025 | Disbursement | 300.0 | 0.0 | 0.0 | 0.0 | 0.0 | 300.0 | false | false |
|
||||
| 02 January 2025 | Disbursement | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | false |
|
||||
| 03 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1500.0 | false | false |
|
||||
+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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
@@ -1020,6 +1020,8 @@ final class LoansApiResourceSwagger {
|
||||
|
||||
@Schema(example = "100.000000")
|
||||
public BigDecimal availableDisbursementAmount;
|
||||
@Schema(example = "150.000000")
|
||||
public BigDecimal availableDisbursementAmountWithOverApplied;
|
||||
@Schema(example = "12")
|
||||
public Integer pastDueDays;
|
||||
@Schema(example = "[2022, 07, 01]")
|
||||
|
||||
+7
-2
@@ -643,8 +643,10 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
|
||||
public LoanApprovalData retrieveApprovalTemplate(final Long loanId) {
|
||||
final Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId, true);
|
||||
final ApplicationCurrency appCurrency = applicationCurrencyRepository.findOneWithNotFoundDetection(loan.getCurrency());
|
||||
final BigDecimal availableDisbursementAmountWithOverApplied = delinquencyReadPlatformService
|
||||
.calculateAvailableDisbursementAmountWithOverApplied(loan);
|
||||
return new LoanApprovalData(loan.getProposedPrincipal(), DateUtils.getBusinessLocalDate(), loan.getNetDisbursalAmount(),
|
||||
appCurrency.toData());
|
||||
appCurrency.toData(), availableDisbursementAmountWithOverApplied);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -656,9 +658,12 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
|
||||
paymentOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
|
||||
}
|
||||
final ApplicationCurrency appCurrency = applicationCurrencyRepository.findOneWithNotFoundDetection(loan.getCurrency());
|
||||
final BigDecimal availableDisbursementAmountWithOverApplied = delinquencyReadPlatformService
|
||||
.calculateAvailableDisbursementAmountWithOverApplied(loan);
|
||||
return LoanTransactionData.loanTransactionDataForDisbursalTemplate(transactionType,
|
||||
loan.getExpectedDisbursedOnLocalDateForTemplate(), loan.getDisburseAmountForTemplate(), loan.getNetDisbursalAmount(),
|
||||
paymentOptions, loan.retriveLastEmiAmount(), loan.getNextPossibleRepaymentDateForRescheduling(), appCurrency.toData());
|
||||
paymentOptions, loan.retriveLastEmiAmount(), loan.getNextPossibleRepaymentDateForRescheduling(), appCurrency.toData(),
|
||||
availableDisbursementAmountWithOverApplied);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+17
-16
@@ -186,8 +186,8 @@ public class DelinquencyWritePlatformServiceRangeChangeEventTest {
|
||||
LoanScheduleDelinquencyData loanScheduleDelinquencyData = new LoanScheduleDelinquencyData(1L, overDueSinceDate, 1L,
|
||||
loanForProcessing);
|
||||
final BigDecimal zero = BigDecimal.ZERO;
|
||||
CollectionData collectionData = new CollectionData(zero, 2L, null, zero, 2L, overDueSinceDate, zero, null, null, null, null, null,
|
||||
null, zero, zero, zero, zero);
|
||||
CollectionData collectionData = new CollectionData(zero, zero, 2L, null, zero, 2L, overDueSinceDate, zero, null, null, null, null,
|
||||
null, null, zero, zero, zero, zero);
|
||||
|
||||
Map<Long, CollectionData> installmentsCollection = new HashMap<>();
|
||||
|
||||
@@ -240,10 +240,10 @@ public class DelinquencyWritePlatformServiceRangeChangeEventTest {
|
||||
LocalDate overDueSinceDate = DateUtils.getBusinessLocalDate().minusDays(2);
|
||||
LoanScheduleDelinquencyData loanScheduleDelinquencyData = new LoanScheduleDelinquencyData(1L, overDueSinceDate, 1L,
|
||||
loanForProcessing);
|
||||
CollectionData collectionData = new CollectionData(zeroAmount, 2L, null, zeroAmount, 2L, overDueSinceDate, zeroAmount, null, null,
|
||||
null, null, null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount);
|
||||
CollectionData collectionData = new CollectionData(zeroAmount, zeroAmount, 2L, null, zeroAmount, 2L, overDueSinceDate, zeroAmount,
|
||||
null, null, null, null, null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount);
|
||||
|
||||
CollectionData installmentCollectionData = new CollectionData(zeroAmount, 2L, null, zeroAmount, 2L, overDueSinceDate,
|
||||
CollectionData installmentCollectionData = new CollectionData(zeroAmount, zeroAmount, 2L, null, zeroAmount, 2L, overDueSinceDate,
|
||||
installmentPrincipalAmount, null, null, null, null, null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount);
|
||||
|
||||
Map<Long, CollectionData> installmentsCollection = new HashMap<>();
|
||||
@@ -369,10 +369,10 @@ public class DelinquencyWritePlatformServiceRangeChangeEventTest {
|
||||
LoanScheduleDelinquencyData loanScheduleDelinquencyData = new LoanScheduleDelinquencyData(1L, overDueSinceDate, 1L,
|
||||
loanForProcessing);
|
||||
|
||||
CollectionData collectionData = new CollectionData(zeroAmount, 2L, null, zeroAmount, 2L, overDueSinceDate, zeroAmount, null, null,
|
||||
null, null, null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount);
|
||||
CollectionData collectionData = new CollectionData(zeroAmount, zeroAmount, 2L, null, zeroAmount, 2L, overDueSinceDate, zeroAmount,
|
||||
null, null, null, null, null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount);
|
||||
|
||||
CollectionData installmentCollectionData = new CollectionData(zeroAmount, 2L, null, zeroAmount, 2L, overDueSinceDate,
|
||||
CollectionData installmentCollectionData = new CollectionData(zeroAmount, zeroAmount, 2L, null, zeroAmount, 2L, overDueSinceDate,
|
||||
installmentPrincipalAmount, null, null, null, null, null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount);
|
||||
|
||||
Map<Long, CollectionData> installmentsCollection = new HashMap<>();
|
||||
@@ -446,10 +446,10 @@ public class DelinquencyWritePlatformServiceRangeChangeEventTest {
|
||||
LocalDate overDueSinceDate = DateUtils.getBusinessLocalDate().minusDays(29);
|
||||
LoanScheduleDelinquencyData loanScheduleDelinquencyData = new LoanScheduleDelinquencyData(1L, overDueSinceDate, 1L,
|
||||
loanForProcessing);
|
||||
CollectionData collectionData = new CollectionData(BigDecimal.ZERO, 29L, null, zeroAmount, 29L, overDueSinceDate, zeroAmount, null,
|
||||
null, null, null, null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount);
|
||||
CollectionData collectionData = new CollectionData(zeroAmount, zeroAmount, 29L, null, zeroAmount, 29L, overDueSinceDate, zeroAmount,
|
||||
null, null, null, null, null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount);
|
||||
|
||||
CollectionData installmentCollectionData = new CollectionData(zeroAmount, 29L, null, zeroAmount, 29L, overDueSinceDate,
|
||||
CollectionData installmentCollectionData = new CollectionData(zeroAmount, zeroAmount, 29L, null, zeroAmount, 29L, overDueSinceDate,
|
||||
installmentPrincipalAmount, null, null, null, null, null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount);
|
||||
|
||||
Map<Long, CollectionData> installmentsCollection = new HashMap<>();
|
||||
@@ -534,13 +534,14 @@ public class DelinquencyWritePlatformServiceRangeChangeEventTest {
|
||||
LocalDate overDueSinceDate = DateUtils.getBusinessLocalDate().minusDays(29);
|
||||
LoanScheduleDelinquencyData loanScheduleDelinquencyData = new LoanScheduleDelinquencyData(1L, overDueSinceDate, 1L,
|
||||
loanForProcessing);
|
||||
CollectionData collectionData = new CollectionData(zeroAmount, 29L, null, zeroAmount, 29L, overDueSinceDate, zeroAmount, null, null,
|
||||
null, null, null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount);
|
||||
CollectionData collectionData = new CollectionData(zeroAmount, zeroAmount, 29L, null, zeroAmount, 29L, overDueSinceDate, zeroAmount,
|
||||
null, null, null, null, null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount);
|
||||
|
||||
CollectionData installmentCollectionData_1 = new CollectionData(zeroAmount, 29L, null, zeroAmount, 29L, overDueSinceDate,
|
||||
installmentPrincipalAmount, null, null, null, null, null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount);
|
||||
CollectionData installmentCollectionData_1 = new CollectionData(zeroAmount, zeroAmount, 29L, null, zeroAmount, 29L,
|
||||
overDueSinceDate, installmentPrincipalAmount, null, null, null, null, null, null, zeroAmount, zeroAmount, zeroAmount,
|
||||
zeroAmount);
|
||||
|
||||
CollectionData installmentCollectionData_2 = new CollectionData(zeroAmount, 0L, null, zeroAmount, 0L, null,
|
||||
CollectionData installmentCollectionData_2 = new CollectionData(zeroAmount, zeroAmount, 0L, null, zeroAmount, 0L, null,
|
||||
installmentPrincipalAmount, null, null, null, null, null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount);
|
||||
|
||||
Map<Long, CollectionData> installmentsCollection = new HashMap<>();
|
||||
|
||||
Reference in New Issue
Block a user