FINERACT-1981: Pay-off schedule handling
This commit is contained in:
committed by
Adam Saghy
parent
e94ce7f1df
commit
49e9005109
+1
-1
@@ -20,7 +20,7 @@ package org.apache.fineract.test.data.loanproduct;
|
|||||||
|
|
||||||
public enum DefaultLoanProduct implements LoanProduct {
|
public enum DefaultLoanProduct implements LoanProduct {
|
||||||
|
|
||||||
LP1, LP1_DUE_DATE, LP1_INTEREST_FLAT, LP1_INTEREST_DECLINING_BALANCE_PERIOD_SAME_AS_PAYMENT, LP1_INTEREST_DECLINING_BALANCE_PERIOD_DAILY, LP1_1MONTH_INTEREST_DECLINING_BALANCE_DAILY_RECALCULATION_COMPOUNDING_MONTHLY, LP1_INTEREST_DECLINING_BALANCE_DAILY_RECALCULATION_COMPOUNDING_NONE, LP1_INTEREST_DECLINING_BALANCE_DAILY_RECALCULATION_COMPOUNDING_NONE_RESCHEDULE_REDUCE_NR_INST, LP1_INTEREST_DECLINING_BALANCE_DAILY_RECALCULATION_COMPOUNDING_NONE_RESCHEDULE_RESCH_NEXT_REP, LP1_INTEREST_DECLINING_BALANCE_DAILY_RECALCULATION_SAME_AS_REPAYMENT_COMPOUNDING_NONE, LP1_INTEREST_DECLINING_BALANCE_SAR_RECALCULATION_SAME_AS_REPAYMENT_COMPOUNDING_NONE_MULTIDISB, LP1_PAYMENT_STRATEGY_DUE_IN_ADVANCE, LP1_PAYMENT_STRATEGY_DUE_IN_ADVANCE_INTEREST_FLAT, LP1_PAYMENT_STRATEGY_DUE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE, LP1_PAYMENT_STRATEGY_DUE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_INTEREST_FLAT, LP1_INTEREST_FLAT_OVERDUE_FROM_AMOUNT, LP1_INTEREST_FLAT_OVERDUE_FROM_AMOUNT_INTEREST, LP2_DOWNPAYMENT, LP2_DOWNPAYMENT_AUTO, LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION, LP2_DOWNPAYMENT_ADVANCED_PAYMENT_ALLOCATION, LP2_DOWNPAYMENT_INTEREST, LP2_DOWNPAYMENT_INTEREST_AUTO, LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL, LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL, LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL_INSTALLMENT_LEVEL_DELINQUENCY, LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROG_SCHEDULE_HOR_INST_LVL_DELINQUENCY_CREDIT_ALLOCATION, LP2_DOWNPAYMENT_ADV_PMT_ALLOC_FIXED_LENGTH;
|
LP1, LP1_DUE_DATE, LP1_INTEREST_FLAT, LP1_INTEREST_DECLINING_BALANCE_PERIOD_SAME_AS_PAYMENT, LP1_INTEREST_DECLINING_BALANCE_PERIOD_DAILY, LP1_1MONTH_INTEREST_DECLINING_BALANCE_DAILY_RECALCULATION_COMPOUNDING_MONTHLY, LP1_INTEREST_DECLINING_BALANCE_DAILY_RECALCULATION_COMPOUNDING_NONE, LP1_INTEREST_DECLINING_BALANCE_DAILY_RECALCULATION_COMPOUNDING_NONE_RESCHEDULE_REDUCE_NR_INST, LP1_INTEREST_DECLINING_BALANCE_DAILY_RECALCULATION_COMPOUNDING_NONE_RESCHEDULE_RESCH_NEXT_REP, LP1_INTEREST_DECLINING_BALANCE_DAILY_RECALCULATION_SAME_AS_REPAYMENT_COMPOUNDING_NONE, LP1_INTEREST_DECLINING_BALANCE_SAR_RECALCULATION_SAME_AS_REPAYMENT_COMPOUNDING_NONE_MULTIDISB, LP1_PAYMENT_STRATEGY_DUE_IN_ADVANCE, LP1_PAYMENT_STRATEGY_DUE_IN_ADVANCE_INTEREST_FLAT, LP1_PAYMENT_STRATEGY_DUE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE, LP1_PAYMENT_STRATEGY_DUE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_INTEREST_FLAT, LP1_INTEREST_FLAT_OVERDUE_FROM_AMOUNT, LP1_INTEREST_FLAT_OVERDUE_FROM_AMOUNT_INTEREST, LP2_DOWNPAYMENT, LP2_DOWNPAYMENT_AUTO, LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION, LP2_DOWNPAYMENT_ADVANCED_PAYMENT_ALLOCATION, LP2_DOWNPAYMENT_INTEREST, LP2_DOWNPAYMENT_INTEREST_AUTO, LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL, LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL, LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL_INSTALLMENT_LEVEL_DELINQUENCY, LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROG_SCHEDULE_HOR_INST_LVL_DELINQUENCY_CREDIT_ALLOCATION, LP2_DOWNPAYMENT_ADV_PMT_ALLOC_FIXED_LENGTH, LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_TILL_PRECLOSE;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
|
|||||||
+122
-5
@@ -18,6 +18,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.test.factory;
|
package org.apache.fineract.test.factory;
|
||||||
|
|
||||||
|
import static org.apache.fineract.test.data.TransactionProcessingStrategyCode.ADVANCED_PAYMENT_ALLOCATION;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -64,17 +66,20 @@ public class LoanProductsRequestFactory {
|
|||||||
public static final String NAME_PREFIX_INTEREST_FLAT_LP2 = "LP2InterestFlat-";
|
public static final String NAME_PREFIX_INTEREST_FLAT_LP2 = "LP2InterestFlat-";
|
||||||
public static final String NAME_PREFIX_INTEREST_DECLINING = "LP1InterestDeclining-";
|
public static final String NAME_PREFIX_INTEREST_DECLINING = "LP1InterestDeclining-";
|
||||||
public static final String NAME_PREFIX_INTEREST_DECLINING_RECALCULATION = "LP1InterestDecliningRecalculation-";
|
public static final String NAME_PREFIX_INTEREST_DECLINING_RECALCULATION = "LP1InterestDecliningRecalculation-";
|
||||||
|
public static final String NAME_PREFIX_LP2_EMI = "LP2Emi-";
|
||||||
public static final String SHORT_NAME_PREFIX = "p";
|
public static final String SHORT_NAME_PREFIX = "p";
|
||||||
public static final String SHORT_NAME_PREFIX_INTEREST = "i";
|
public static final String SHORT_NAME_PREFIX_INTEREST = "i";
|
||||||
|
public static final String SHORT_NAME_PREFIX_EMI = "e";
|
||||||
public static final String DATE_FORMAT = "dd MMMM yyyy";
|
public static final String DATE_FORMAT = "dd MMMM yyyy";
|
||||||
public static final String LOCALE_EN = "en";
|
public static final String LOCALE_EN = "en";
|
||||||
public static final String DESCRIPTION = "Pay in 30 days product";
|
public static final String DESCRIPTION = "LP1 product";
|
||||||
public static final String DESCRIPTION_LP2 = "Pay in 4 product";
|
public static final String DESCRIPTION_LP2 = "LP2 product";
|
||||||
public static final String DESCRIPTION_INTEREST_FLAT = "Pay in 30 days product with 12% interest - FLAT";
|
public static final String DESCRIPTION_INTEREST_FLAT = "LP1 product with 12% interest - FLAT";
|
||||||
public static final String DESCRIPTION_INTEREST_FLAT_LP2 = "Pay in 4 product with 12% interest - FLAT";
|
public static final String DESCRIPTION_INTEREST_FLAT_LP2 = "LP2 product with 12% interest - FLAT";
|
||||||
public static final String DESCRIPTION_INTEREST_DECLINING = "Pay in 30 days product with 12% interest - DECLINING BALANCE";
|
public static final String DESCRIPTION_INTEREST_DECLINING = "LP1 product with 12% interest - DECLINING BALANCE";
|
||||||
public static final String DESCRIPTION_INTEREST_DECLINING_BALANCE_DAILY_RECALCULATION_COMPOUNDING_MONTHLY = "LP1-1MONTH with 12% DECLINING BALANCE interest, interest period: Daily, Interest recalculation-Monthly, Compounding:Interest";
|
public static final String DESCRIPTION_INTEREST_DECLINING_BALANCE_DAILY_RECALCULATION_COMPOUNDING_MONTHLY = "LP1-1MONTH with 12% DECLINING BALANCE interest, interest period: Daily, Interest recalculation-Monthly, Compounding:Interest";
|
||||||
public static final String DESCRIPTION_INTEREST_DECLINING_BALANCE_DAILY_RECALCULATION_COMPOUNDING_NONE = "LP1 with 12% DECLINING BALANCE interest, interest period: Daily, Interest recalculation-Daily, Compounding:none";
|
public static final String DESCRIPTION_INTEREST_DECLINING_BALANCE_DAILY_RECALCULATION_COMPOUNDING_NONE = "LP1 with 12% DECLINING BALANCE interest, interest period: Daily, Interest recalculation-Daily, Compounding:none";
|
||||||
|
public static final String DESCRIPTION_LP2_EMI = "LP2 product with EMI";
|
||||||
public static final Long FUND_ID = FundId.LENDER_A.value;
|
public static final Long FUND_ID = FundId.LENDER_A.value;
|
||||||
public static final String CURRENCY_CODE = "EUR";
|
public static final String CURRENCY_CODE = "EUR";
|
||||||
public static final Integer INTEREST_RATE_FREQUENCY_TYPE_MONTH = InterestRateFrequencyType.MONTH.value;
|
public static final Integer INTEREST_RATE_FREQUENCY_TYPE_MONTH = InterestRateFrequencyType.MONTH.value;
|
||||||
@@ -87,6 +92,7 @@ public class LoanProductsRequestFactory {
|
|||||||
public static final Integer INTEREST_CALCULATION_PERIOD_TYPE_SAME_AS_REPAYMENT = InterestCalculationPeriodTime.SAME_AS_REPAYMENT_PERIOD.value;
|
public static final Integer INTEREST_CALCULATION_PERIOD_TYPE_SAME_AS_REPAYMENT = InterestCalculationPeriodTime.SAME_AS_REPAYMENT_PERIOD.value;
|
||||||
public static final Integer INTEREST_CALCULATION_PERIOD_TYPE_DAILY = InterestCalculationPeriodTime.DAILY.value;
|
public static final Integer INTEREST_CALCULATION_PERIOD_TYPE_DAILY = InterestCalculationPeriodTime.DAILY.value;
|
||||||
public static final String TRANSACTION_PROCESSING_STRATEGY_CODE = TransactionProcessingStrategyCode.PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER.value;
|
public static final String TRANSACTION_PROCESSING_STRATEGY_CODE = TransactionProcessingStrategyCode.PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER.value;
|
||||||
|
public static final String TRANSACTION_PROCESSING_STRATEGY_CODE_ADVANCED = TransactionProcessingStrategyCode.ADVANCED_PAYMENT_ALLOCATION.value;
|
||||||
public static final Integer DAYS_IN_YEAR_TYPE = DaysInYearType.ACTUAL.value;
|
public static final Integer DAYS_IN_YEAR_TYPE = DaysInYearType.ACTUAL.value;
|
||||||
public static final Integer DAYS_IN_MONTH_TYPE = DaysInMonthType.ACTUAL.value;
|
public static final Integer DAYS_IN_MONTH_TYPE = DaysInMonthType.ACTUAL.value;
|
||||||
public static final Integer LOAN_ACCOUNTING_RULE = AccountingRule.ACCRUAL_PERIODIC.value;
|
public static final Integer LOAN_ACCOUNTING_RULE = AccountingRule.ACCRUAL_PERIODIC.value;
|
||||||
@@ -874,4 +880,115 @@ public class LoanProductsRequestFactory {
|
|||||||
.chargeOffFraudExpenseAccountId(accountTypeResolver.resolve(DefaultAccountType.CREDIT_LOSS_BAD_DEBT_FRAUD))//
|
.chargeOffFraudExpenseAccountId(accountTypeResolver.resolve(DefaultAccountType.CREDIT_LOSS_BAD_DEBT_FRAUD))//
|
||||||
.incomeFromChargeOffPenaltyAccountId(accountTypeResolver.resolve(DefaultAccountType.FEE_CHARGE_OFF));//
|
.incomeFromChargeOffPenaltyAccountId(accountTypeResolver.resolve(DefaultAccountType.FEE_CHARGE_OFF));//
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PostLoanProductsRequest defaultLoanProductsRequestLP2Emi() {
|
||||||
|
String name = Utils.randomNameGenerator(NAME_PREFIX_LP2_EMI, 4);
|
||||||
|
String shortName = Utils.randomNameGenerator(SHORT_NAME_PREFIX_EMI, 3);
|
||||||
|
|
||||||
|
List<Integer> principalVariationsForBorrowerCycle = new ArrayList<>();
|
||||||
|
List<Integer> numberOfRepaymentVariationsForBorrowerCycle = new ArrayList<>();
|
||||||
|
List<Integer> interestRateVariationsForBorrowerCycle = new ArrayList<>();
|
||||||
|
List<ChargeData> charges = new ArrayList<>();
|
||||||
|
List<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings = new ArrayList<>();
|
||||||
|
List<GetLoanFeeToIncomeAccountMappings> feeToIncomeAccountMappings = new ArrayList<>();
|
||||||
|
|
||||||
|
List<GetLoanPaymentChannelToFundSourceMappings> paymentChannelToFundSourceMappings = new ArrayList<>();
|
||||||
|
GetLoanPaymentChannelToFundSourceMappings loanPaymentChannelToFundSourceMappings = new GetLoanPaymentChannelToFundSourceMappings();
|
||||||
|
loanPaymentChannelToFundSourceMappings.fundSourceAccountId(accountTypeResolver.resolve(DefaultAccountType.FUND_RECEIVABLES));
|
||||||
|
loanPaymentChannelToFundSourceMappings.paymentTypeId(paymentTypeResolver.resolve(DefaultPaymentType.MONEY_TRANSFER));
|
||||||
|
paymentChannelToFundSourceMappings.add(loanPaymentChannelToFundSourceMappings);
|
||||||
|
|
||||||
|
return new PostLoanProductsRequest()//
|
||||||
|
.name(name)//
|
||||||
|
.shortName(shortName)//
|
||||||
|
.description(DESCRIPTION_LP2_EMI)//
|
||||||
|
.loanScheduleType("PROGRESSIVE") //
|
||||||
|
.interestCalculationPeriodType(InterestCalculationPeriodTime.DAILY.value)//
|
||||||
|
.transactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION.getValue())//
|
||||||
|
.fundId(FUND_ID)//
|
||||||
|
.startDate(null)//
|
||||||
|
.closeDate(null)//
|
||||||
|
.includeInBorrowerCycle(false)//
|
||||||
|
.currencyCode(CURRENCY_CODE)//
|
||||||
|
.digitsAfterDecimal(2)//
|
||||||
|
.inMultiplesOf(0)//
|
||||||
|
.useBorrowerCycle(false)//
|
||||||
|
.minPrincipal(10.0)//
|
||||||
|
.principal(1000.0)//
|
||||||
|
.maxPrincipal(10000.0)//
|
||||||
|
.minNumberOfRepayments(1)//
|
||||||
|
.numberOfRepayments(4)//
|
||||||
|
.maxNumberOfRepayments(30)//
|
||||||
|
.isLinkedToFloatingInterestRates(false)//
|
||||||
|
.minInterestRatePerPeriod((double) 0)//
|
||||||
|
.interestRatePerPeriod((double) 12)//
|
||||||
|
.maxInterestRatePerPeriod((double) 60)//
|
||||||
|
.interestRateFrequencyType(INTEREST_RATE_FREQUENCY_TYPE_YEAR)//
|
||||||
|
.repaymentEvery(15)//
|
||||||
|
.repaymentStartDateType(1)//
|
||||||
|
.repaymentFrequencyType(REPAYMENT_FREQUENCY_TYPE_DAYS)//
|
||||||
|
.principalVariationsForBorrowerCycle(principalVariationsForBorrowerCycle)//
|
||||||
|
.numberOfRepaymentVariationsForBorrowerCycle(numberOfRepaymentVariationsForBorrowerCycle)//
|
||||||
|
.interestRateVariationsForBorrowerCycle(interestRateVariationsForBorrowerCycle)//
|
||||||
|
.amortizationType(AMORTIZATION_TYPE)//
|
||||||
|
.interestType(INTEREST_TYPE_DECLINING_BALANCE)//
|
||||||
|
.isEqualAmortization(false)//
|
||||||
|
.interestCalculationPeriodType(INTEREST_CALCULATION_PERIOD_TYPE_DAILY)//
|
||||||
|
.transactionProcessingStrategyCode(TRANSACTION_PROCESSING_STRATEGY_CODE_ADVANCED)//
|
||||||
|
.daysInYearType(DAYS_IN_YEAR_TYPE)//
|
||||||
|
.daysInMonthType(DAYS_IN_MONTH_TYPE)//
|
||||||
|
.canDefineInstallmentAmount(true)//
|
||||||
|
.graceOnArrearsAgeing(3)//
|
||||||
|
.overdueDaysForNPA(179)//
|
||||||
|
.accountMovesOutOfNPAOnlyOnArrearsCompletion(false)//
|
||||||
|
.principalThresholdForLastInstallment(50)//
|
||||||
|
.allowVariableInstallments(false)//
|
||||||
|
.canUseForTopup(false)//
|
||||||
|
.isInterestRecalculationEnabled(false)//
|
||||||
|
.holdGuaranteeFunds(false)//
|
||||||
|
.multiDisburseLoan(false)//
|
||||||
|
.allowAttributeOverrides(new AllowAttributeOverrides()//
|
||||||
|
.amortizationType(true)//
|
||||||
|
.interestType(true)//
|
||||||
|
.transactionProcessingStrategyCode(true)//
|
||||||
|
.interestCalculationPeriodType(true)//
|
||||||
|
.inArrearsTolerance(true)//
|
||||||
|
.repaymentEvery(true)//
|
||||||
|
.graceOnPrincipalAndInterestPayment(true)//
|
||||||
|
.graceOnArrearsAgeing(true))//
|
||||||
|
.allowPartialPeriodInterestCalcualtion(false)//
|
||||||
|
.maxTrancheCount(10)//
|
||||||
|
.outstandingLoanBalance(10000.0)//
|
||||||
|
.charges(charges)//
|
||||||
|
.accountingRule(LOAN_ACCOUNTING_RULE)//
|
||||||
|
.fundSourceAccountId(accountTypeResolver.resolve(DefaultAccountType.SUSPENSE_CLEARING_ACCOUNT))//
|
||||||
|
.loanPortfolioAccountId(accountTypeResolver.resolve(DefaultAccountType.LOANS_RECEIVABLE))//
|
||||||
|
.transfersInSuspenseAccountId(accountTypeResolver.resolve(DefaultAccountType.TRANSFER_IN_SUSPENSE_ACCOUNT))//
|
||||||
|
.interestOnLoanAccountId(accountTypeResolver.resolve(DefaultAccountType.INTEREST_INCOME))//
|
||||||
|
.incomeFromFeeAccountId(accountTypeResolver.resolve(DefaultAccountType.FEE_INCOME))//
|
||||||
|
.incomeFromPenaltyAccountId(accountTypeResolver.resolve(DefaultAccountType.FEE_INCOME))//
|
||||||
|
.incomeFromRecoveryAccountId(accountTypeResolver.resolve(DefaultAccountType.RECOVERIES))//
|
||||||
|
.writeOffAccountId(accountTypeResolver.resolve(DefaultAccountType.WRITTEN_OFF))//
|
||||||
|
.overpaymentLiabilityAccountId(accountTypeResolver.resolve(DefaultAccountType.OVERPAYMENT_ACCOUNT))//
|
||||||
|
.receivableInterestAccountId(accountTypeResolver.resolve(DefaultAccountType.INTEREST_FEE_RECEIVABLE))//
|
||||||
|
.receivableFeeAccountId(accountTypeResolver.resolve(DefaultAccountType.INTEREST_FEE_RECEIVABLE))//
|
||||||
|
.receivablePenaltyAccountId(accountTypeResolver.resolve(DefaultAccountType.INTEREST_FEE_RECEIVABLE))//
|
||||||
|
.dateFormat(DATE_FORMAT)//
|
||||||
|
.locale(LOCALE_EN)//
|
||||||
|
.disallowExpectedDisbursements(false)//
|
||||||
|
.delinquencyBucketId(DELINQUENCY_BUCKET_ID.longValue())//
|
||||||
|
.goodwillCreditAccountId(accountTypeResolver.resolve(DefaultAccountType.GOODWILL_EXPENSE_ACCOUNT))//
|
||||||
|
.incomeFromGoodwillCreditInterestAccountId(accountTypeResolver.resolve(DefaultAccountType.INTEREST_INCOME_CHARGE_OFF))//
|
||||||
|
.incomeFromGoodwillCreditFeesAccountId(accountTypeResolver.resolve(DefaultAccountType.FEE_CHARGE_OFF))//
|
||||||
|
.incomeFromGoodwillCreditPenaltyAccountId(accountTypeResolver.resolve(DefaultAccountType.FEE_CHARGE_OFF))//
|
||||||
|
.paymentChannelToFundSourceMappings(paymentChannelToFundSourceMappings)//
|
||||||
|
.penaltyToIncomeAccountMappings(penaltyToIncomeAccountMappings)//
|
||||||
|
.feeToIncomeAccountMappings(feeToIncomeAccountMappings)//
|
||||||
|
.incomeFromChargeOffInterestAccountId(accountTypeResolver.resolve(DefaultAccountType.INTEREST_INCOME_CHARGE_OFF))//
|
||||||
|
.incomeFromChargeOffFeesAccountId(accountTypeResolver.resolve(DefaultAccountType.FEE_CHARGE_OFF))//
|
||||||
|
.chargeOffExpenseAccountId(accountTypeResolver.resolve(DefaultAccountType.CREDIT_LOSS_BAD_DEBT))//
|
||||||
|
.chargeOffFraudExpenseAccountId(accountTypeResolver.resolve(DefaultAccountType.CREDIT_LOSS_BAD_DEBT_FRAUD))//
|
||||||
|
.incomeFromChargeOffPenaltyAccountId(accountTypeResolver.resolve(DefaultAccountType.FEE_CHARGE_OFF));//
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+30
@@ -36,6 +36,8 @@ import org.apache.fineract.client.models.PostLoanProductsResponse;
|
|||||||
import org.apache.fineract.client.services.LoanProductsApi;
|
import org.apache.fineract.client.services.LoanProductsApi;
|
||||||
import org.apache.fineract.test.data.AdvancePaymentsAdjustmentType;
|
import org.apache.fineract.test.data.AdvancePaymentsAdjustmentType;
|
||||||
import org.apache.fineract.test.data.ChargeProductType;
|
import org.apache.fineract.test.data.ChargeProductType;
|
||||||
|
import org.apache.fineract.test.data.DaysInMonthType;
|
||||||
|
import org.apache.fineract.test.data.DaysInYearType;
|
||||||
import org.apache.fineract.test.data.InterestCalculationPeriodTime;
|
import org.apache.fineract.test.data.InterestCalculationPeriodTime;
|
||||||
import org.apache.fineract.test.data.RecalculationRestFrequencyType;
|
import org.apache.fineract.test.data.RecalculationRestFrequencyType;
|
||||||
import org.apache.fineract.test.data.TransactionProcessingStrategyCode;
|
import org.apache.fineract.test.data.TransactionProcessingStrategyCode;
|
||||||
@@ -476,6 +478,34 @@ public class LoanProductGlobalInitializerStep implements FineractGlobalInitializ
|
|||||||
.createLoanProduct(loanProductsRequestDownPaymentAdvPmtAllocFixedLength).execute();
|
.createLoanProduct(loanProductsRequestDownPaymentAdvPmtAllocFixedLength).execute();
|
||||||
TestContext.INSTANCE.set(TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_DOWNPAYMENT_ADV_PMT_ALLOC_FIXED_LENGTH,
|
TestContext.INSTANCE.set(TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_DOWNPAYMENT_ADV_PMT_ALLOC_FIXED_LENGTH,
|
||||||
responseLoanProductsRequestDownPaymentAdvPmtAllocFixedLength);
|
responseLoanProductsRequestDownPaymentAdvPmtAllocFixedLength);
|
||||||
|
|
||||||
|
// LP2 with progressive loan schedule + horizontal + interest EMI + 360/30
|
||||||
|
// + interest recalculation, preClosureInterestCalculationStrategy= till preclose,
|
||||||
|
// interestRecalculationCompoundingMethod = none
|
||||||
|
// Frequency for recalculate Outstanding Principal: Daily, Frequency Interval for recalculation: 1
|
||||||
|
// (LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_TILL_PRECLOSE)
|
||||||
|
String name38 = DefaultLoanProduct.LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_TILL_PRECLOSE.getName();
|
||||||
|
PostLoanProductsRequest loanProductsRequestLP2AdvancedpaymentInterestEmi36030InterestRecalcTillPreclose = loanProductsRequestFactory
|
||||||
|
.defaultLoanProductsRequestLP2Emi()//
|
||||||
|
.name(name38)//
|
||||||
|
.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")));//
|
||||||
|
Response<PostLoanProductsResponse> responseLoanProductsRequestLP2AdvancedpaymentInterest36030InterestRecalcTillPreCloese = loanProductsApi
|
||||||
|
.createLoanProduct(loanProductsRequestLP2AdvancedpaymentInterestEmi36030InterestRecalcTillPreclose).execute();
|
||||||
|
TestContext.INSTANCE.set(
|
||||||
|
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_TILL_PRECLOSE,
|
||||||
|
responseLoanProductsRequestLP2AdvancedpaymentInterest36030InterestRecalcTillPreCloese);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AdvancedPaymentData createPaymentAllocation(String transactionType, String futureInstallmentAllocationRule,
|
public static AdvancedPaymentData createPaymentAllocation(String transactionType, String futureInstallmentAllocationRule,
|
||||||
|
|||||||
+15
@@ -33,12 +33,14 @@ import java.util.HashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.codec.binary.Base64;
|
import org.apache.commons.codec.binary.Base64;
|
||||||
import org.apache.fineract.avro.loan.v1.LoanTransactionAdjustmentDataV1;
|
import org.apache.fineract.avro.loan.v1.LoanTransactionAdjustmentDataV1;
|
||||||
import org.apache.fineract.avro.loan.v1.LoanTransactionDataV1;
|
import org.apache.fineract.avro.loan.v1.LoanTransactionDataV1;
|
||||||
import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod;
|
import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod;
|
||||||
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
||||||
import org.apache.fineract.client.models.GetLoansLoanIdTransactions;
|
import org.apache.fineract.client.models.GetLoansLoanIdTransactions;
|
||||||
|
import org.apache.fineract.client.models.GetLoansLoanIdTransactionsTemplateResponse;
|
||||||
import org.apache.fineract.client.models.GetLoansLoanIdTransactionsTransactionIdResponse;
|
import org.apache.fineract.client.models.GetLoansLoanIdTransactionsTransactionIdResponse;
|
||||||
import org.apache.fineract.client.models.GetUsersUserIdResponse;
|
import org.apache.fineract.client.models.GetUsersUserIdResponse;
|
||||||
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
|
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
|
||||||
@@ -66,6 +68,7 @@ import org.apache.fineract.test.support.TestContextKey;
|
|||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import retrofit2.Response;
|
import retrofit2.Response;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
public class LoanRepaymentStepDef extends AbstractStepDef {
|
public class LoanRepaymentStepDef extends AbstractStepDef {
|
||||||
|
|
||||||
public static final String DATE_FORMAT = "dd MMMM yyyy";
|
public static final String DATE_FORMAT = "dd MMMM yyyy";
|
||||||
@@ -561,4 +564,16 @@ public class LoanRepaymentStepDef extends AbstractStepDef {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@When("Loan Pay-off is made on {string}")
|
||||||
|
public void makeLoanPayOff(String transactionDate) throws IOException {
|
||||||
|
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
|
||||||
|
long loanId1 = loanResponse.body().getLoanId();
|
||||||
|
Response<GetLoansLoanIdTransactionsTemplateResponse> response = loanTransactionsApi
|
||||||
|
.retrieveTransactionTemplate(loanId1, "prepayLoan", DATE_FORMAT, transactionDate, DEFAULT_LOCALE).execute();
|
||||||
|
Double transactionAmount = response.body().getAmount();
|
||||||
|
|
||||||
|
log.info("--- Loan Pay-off with amount: {} ---", transactionAmount);
|
||||||
|
makeRepayment("AUTOPAY", transactionDate, transactionAmount, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -77,6 +77,7 @@ public abstract class TestContextKey {
|
|||||||
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_DOWNPAYMENT_ADVANCED_PAYMENT_ALLOCATION_PROGRESSIVE_LOAN_SCHEDULE_INSTALLMENT_LEVEL_DELINQUENCY = "loanProductCreateResponseLP2DownPaymentProgressiveLoanScheduleInstallmentLevelDelinquency";
|
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_DOWNPAYMENT_ADVANCED_PAYMENT_ALLOCATION_PROGRESSIVE_LOAN_SCHEDULE_INSTALLMENT_LEVEL_DELINQUENCY = "loanProductCreateResponseLP2DownPaymentProgressiveLoanScheduleInstallmentLevelDelinquency";
|
||||||
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROG_SCHEDULE_HOR_INST_LVL_DELINQUENCY_CREDIT_ALLOCATION = "loanProductCreateResponseLP2DownPaymentProgressiveLoanScheduleHorizontalInstallmentLevelDelinquencyCreditAllocation";
|
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROG_SCHEDULE_HOR_INST_LVL_DELINQUENCY_CREDIT_ALLOCATION = "loanProductCreateResponseLP2DownPaymentProgressiveLoanScheduleHorizontalInstallmentLevelDelinquencyCreditAllocation";
|
||||||
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_DOWNPAYMENT_ADV_PMT_ALLOC_FIXED_LENGTH = "loanProductCreateResponseLP2DownPaymentProgressiveLoanScheduleFixedLength";
|
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_DOWNPAYMENT_ADV_PMT_ALLOC_FIXED_LENGTH = "loanProductCreateResponseLP2DownPaymentProgressiveLoanScheduleFixedLength";
|
||||||
|
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_TILL_PRECLOSE = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030InterestRecalculationTillPreClose";
|
||||||
public static final String CHARGE_FOR_LOAN_PERCENT_LATE_CREATE_RESPONSE = "ChargeForLoanPercentLateCreateResponse";
|
public static final String CHARGE_FOR_LOAN_PERCENT_LATE_CREATE_RESPONSE = "ChargeForLoanPercentLateCreateResponse";
|
||||||
public static final String CHARGE_FOR_LOAN_PERCENT_LATE_AMOUNT_PLUS_INTEREST_CREATE_RESPONSE = "ChargeForLoanPercentLateAmountPlusInterestCreateResponse";
|
public static final String CHARGE_FOR_LOAN_PERCENT_LATE_AMOUNT_PLUS_INTEREST_CREATE_RESPONSE = "ChargeForLoanPercentLateAmountPlusInterestCreateResponse";
|
||||||
public static final String CHARGE_FOR_LOAN_PERCENT_PROCESSING_CREATE_RESPONSE = "ChargeForLoanPercentProcessingCreateResponse";
|
public static final String CHARGE_FOR_LOAN_PERCENT_PROCESSING_CREATE_RESPONSE = "ChargeForLoanPercentProcessingCreateResponse";
|
||||||
|
|||||||
@@ -5523,7 +5523,6 @@ Feature: Loan
|
|||||||
When Admin successfully disburse the loan on "01 February 2024" with "1000" EUR transaction amount
|
When Admin successfully disburse the loan on "01 February 2024" with "1000" EUR transaction amount
|
||||||
Then LoanDetails has fixedLength field with int value: 60
|
Then LoanDetails has fixedLength field with int value: 60
|
||||||
|
|
||||||
|
|
||||||
Scenario: Actual disbursement date is in the past with advanced payment allocation product + submitted on date repaymentStartDateType
|
Scenario: Actual disbursement date is in the past with advanced payment allocation product + submitted on date repaymentStartDateType
|
||||||
When Admin sets repaymentStartDateType for "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product to "SUBMITTED_ON_DATE"
|
When Admin sets repaymentStartDateType for "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product to "SUBMITTED_ON_DATE"
|
||||||
When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule
|
When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule
|
||||||
@@ -5536,3 +5535,64 @@ Feature: Loan
|
|||||||
Then Loan status has changed to "Approved"
|
Then Loan status has changed to "Approved"
|
||||||
Then Admin fails to disburse the loan on "31 December 2022" with "500" EUR transaction amount because disbursement date is earlier than "01 January 2023"
|
Then Admin fails to disburse the loan on "31 December 2022" with "500" EUR transaction amount because disbursement date is earlier than "01 January 2023"
|
||||||
When Admin sets repaymentStartDateType for "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product to "DISBURSEMENT_DATE"
|
When Admin sets repaymentStartDateType for "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product to "DISBURSEMENT_DATE"
|
||||||
|
|
||||||
|
Scenario: Early pay-off loan with interest
|
||||||
|
When Admin sets the business date to "01 January 2024"
|
||||||
|
When Admin creates a client with random data
|
||||||
|
When Admin set "LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_TILL_PRECLOSE" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule
|
||||||
|
When 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_EMI_360_30_INTEREST_RECALCULATION_TILL_PRECLOSE | 01 January 2024 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION |
|
||||||
|
And Admin successfully approves the loan on "01 January 2024" with "100" amount and expected disbursement date on "01 January 2024"
|
||||||
|
When Admin successfully disburse the loan on "01 January 2024" with "100" EUR transaction amount
|
||||||
|
Then Loan Repayment schedule has 6 periods, with the following data for periods:
|
||||||
|
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||||
|
| | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | |
|
||||||
|
| 1 | 31 | 01 February 2024 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
|
||||||
|
| 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
|
||||||
|
| 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
|
||||||
|
| 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
|
||||||
|
| 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
|
||||||
|
| 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 |
|
||||||
|
Then Loan Repayment schedule has the following data in Total row:
|
||||||
|
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||||
|
| 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 0.0 | 0.0 | 0.0 | 102.05 |
|
||||||
|
Then Loan Transactions tab has the following data:
|
||||||
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
|
||||||
|
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
|
||||||
|
When Admin sets the business date to "01 February 2024"
|
||||||
|
And Customer makes "AUTOPAY" repayment on "01 February 2024" with 17.01 EUR transaction amount
|
||||||
|
Then Loan Repayment schedule has 6 periods, with the following data for periods:
|
||||||
|
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||||
|
| | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | |
|
||||||
|
| 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 |
|
||||||
|
| 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
|
||||||
|
| 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
|
||||||
|
| 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
|
||||||
|
| 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
|
||||||
|
| 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 |
|
||||||
|
Then Loan Repayment schedule has the following data in Total row:
|
||||||
|
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||||
|
| 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 17.01 | 0.0 | 0.0 | 85.04 |
|
||||||
|
Then Loan Transactions tab has the following data:
|
||||||
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
|
||||||
|
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
|
||||||
|
| 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false |
|
||||||
|
When Admin sets the business date to "15 February 2024"
|
||||||
|
When Loan Pay-off is made on "15 February 2024"
|
||||||
|
# When Customer makes "AUTOPAY" repayment on "15 February 2024" with 83.81 EUR transaction amount
|
||||||
|
Then Loan Repayment schedule has 6 periods, with the following data for periods:
|
||||||
|
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||||
|
| | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | |
|
||||||
|
| 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 |
|
||||||
|
| 2 | 29 | 01 March 2024 | 15 February 2024 | 66.8 | 16.77 | 0.24 | 0.0 | 0.0 | 17.01 | 17.01 | 17.01 | 0.0 | 0.0 |
|
||||||
|
| 3 | 31 | 01 April 2024 | 15 February 2024 | 49.79 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 17.01 | 17.01 | 0.0 | 0.0 |
|
||||||
|
| 4 | 30 | 01 May 2024 | 15 February 2024 | 32.78 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 17.01 | 17.01 | 0.0 | 0.0 |
|
||||||
|
| 5 | 31 | 01 June 2024 | 15 February 2024 | 15.77 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 17.01 | 17.01 | 0.0 | 0.0 |
|
||||||
|
| 6 | 30 | 01 July 2024 | | 0.0 | 15.77 | 0.0 | 0.0 | 0.0 | 15.77 | 15.77 | 15.77 | 0.0 | 0.0 |
|
||||||
|
Then Loan Transactions tab has the following data:
|
||||||
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
|
||||||
|
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
|
||||||
|
| 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false |
|
||||||
|
| 15 February 2024 | Repayment | 83.81 | 83.57 | 0.24 | 0.0 | 0.0 | 0.0 | false |
|
||||||
|
When Admin set "LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_TILL_PRECLOSE" loan product "DEFAULT" transaction type to "LAST_INSTALLMENT" future installment allocation rule
|
||||||
|
|||||||
+8
-4
@@ -46,20 +46,24 @@ public class OutstandingAmountsDTO {
|
|||||||
.plus(penaltyCharges());
|
.plus(penaltyCharges());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void plusPrincipal(Money principal) {
|
public OutstandingAmountsDTO plusPrincipal(Money principal) {
|
||||||
this.principal = this.principal.plus(principal);
|
this.principal = this.principal.plus(principal);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void plusInterest(Money interest) {
|
public OutstandingAmountsDTO plusInterest(Money interest) {
|
||||||
this.interest = this.interest.plus(interest);
|
this.interest = this.interest.plus(interest);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void plusFeeCharges(Money feeCharges) {
|
public OutstandingAmountsDTO plusFeeCharges(Money feeCharges) {
|
||||||
this.feeCharges = this.feeCharges.plus(feeCharges);
|
this.feeCharges = this.feeCharges.plus(feeCharges);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void plusPenaltyCharges(Money penaltyCharges) {
|
public OutstandingAmountsDTO plusPenaltyCharges(Money penaltyCharges) {
|
||||||
this.penaltyCharges = this.penaltyCharges.plus(penaltyCharges);
|
this.penaltyCharges = this.penaltyCharges.plus(penaltyCharges);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+86
-32
@@ -55,6 +55,7 @@ import lombok.Setter;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.NotImplementedException;
|
import org.apache.commons.lang3.NotImplementedException;
|
||||||
import org.apache.commons.lang3.ObjectUtils;
|
import org.apache.commons.lang3.ObjectUtils;
|
||||||
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
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.infrastructure.core.service.MathUtil;
|
||||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||||
@@ -76,6 +77,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParamet
|
|||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLoanInterestRepaymentModel;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLoanInterestScheduleModel;
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLoanInterestScheduleModel;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
|
||||||
import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
|
import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
|
||||||
@@ -140,12 +142,13 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
// only for progressive loans
|
||||||
public ChangedTransactionDetail reprocessLoanTransactions(LocalDate disbursementDate, List<LoanTransaction> loanTransactions,
|
public Pair<ChangedTransactionDetail, ProgressiveLoanInterestScheduleModel> reprocessProgressiveLoanTransactions(
|
||||||
MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> charges) {
|
LocalDate disbursementDate, List<LoanTransaction> loanTransactions, MonetaryCurrency currency,
|
||||||
|
List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> charges) {
|
||||||
final ChangedTransactionDetail changedTransactionDetail = new ChangedTransactionDetail();
|
final ChangedTransactionDetail changedTransactionDetail = new ChangedTransactionDetail();
|
||||||
if (loanTransactions.isEmpty()) {
|
if (loanTransactions.isEmpty()) {
|
||||||
return changedTransactionDetail;
|
return Pair.of(changedTransactionDetail, null);
|
||||||
}
|
}
|
||||||
if (charges != null) {
|
if (charges != null) {
|
||||||
for (final LoanCharge loanCharge : charges) {
|
for (final LoanCharge loanCharge : charges) {
|
||||||
@@ -180,10 +183,18 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
chargeOrTransaction.getLoanCharge()
|
chargeOrTransaction.getLoanCharge()
|
||||||
.ifPresent(loanCharge -> processSingleCharge(loanCharge, currency, installments, disbursementDate));
|
.ifPresent(loanCharge -> processSingleCharge(loanCharge, currency, installments, disbursementDate));
|
||||||
}
|
}
|
||||||
List<LoanTransaction> txs = chargeOrTransactions.stream().map(ChargeOrTransaction::getLoanTransaction).filter(Optional::isPresent)
|
List<LoanTransaction> txs = chargeOrTransactions.stream() //
|
||||||
|
.map(ChargeOrTransaction::getLoanTransaction) //
|
||||||
|
.filter(Optional::isPresent) //
|
||||||
.map(Optional::get).toList();
|
.map(Optional::get).toList();
|
||||||
reprocessInstallments(disbursementDate, txs, installments, currency);
|
reprocessInstallments(disbursementDate, txs, installments, currency);
|
||||||
return changedTransactionDetail;
|
return Pair.of(changedTransactionDetail, scheduleModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChangedTransactionDetail reprocessLoanTransactions(LocalDate disbursementDate, List<LoanTransaction> loanTransactions,
|
||||||
|
MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> charges) {
|
||||||
|
return reprocessProgressiveLoanTransactions(disbursementDate, loanTransactions, currency, installments, charges).getLeft();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -276,7 +287,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
}
|
}
|
||||||
return originalTransaction.get();
|
return originalTransaction.get();
|
||||||
} else { // when there is no id, then it might be that the original transaction is changed, so we need to look
|
} else { // when there is no id, then it might be that the original transaction is changed, so we need to look
|
||||||
// it up from the Ctx.
|
// it up from the Ctx.
|
||||||
Long originalChargebackTransactionId = ctx.getChangedTransactionDetail().getCurrentTransactionToOldId().get(loanTransaction);
|
Long originalChargebackTransactionId = ctx.getChangedTransactionDetail().getCurrentTransactionToOldId().get(loanTransaction);
|
||||||
Collection<LoanTransaction> updatedTransactions = ctx.getChangedTransactionDetail().getNewTransactionMappings().values();
|
Collection<LoanTransaction> updatedTransactions = ctx.getChangedTransactionDetail().getNewTransactionMappings().values();
|
||||||
Optional<LoanTransaction> updatedTransaction = updatedTransactions.stream().filter(tr -> tr.getLoanTransactionRelations()
|
Optional<LoanTransaction> updatedTransaction = updatedTransactions.stream().filter(tr -> tr.getLoanTransactionRelations()
|
||||||
@@ -733,19 +744,18 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
LoanPaymentAllocationRule defaultPaymentAllocationRule = paymentAllocationRules.stream()
|
LoanPaymentAllocationRule defaultPaymentAllocationRule = paymentAllocationRules.stream()
|
||||||
.filter(e -> DEFAULT.equals(e.getTransactionType())).findFirst().orElseThrow();
|
.filter(e -> DEFAULT.equals(e.getTransactionType())).findFirst().orElseThrow();
|
||||||
|
|
||||||
Money transactionAmountUnprocessed = null;
|
Money transactionAmountUnprocessed = transactionCtx.getOverpaymentHolder().getMoneyObject();
|
||||||
Money zero = Money.zero(transactionCtx.getCurrency());
|
Money zero = Money.zero(transactionCtx.getCurrency());
|
||||||
Balances balances = new Balances(zero, zero, zero, zero);
|
Balances balances = new Balances(zero, zero, zero, zero);
|
||||||
if (LoanScheduleProcessingType.HORIZONTAL
|
if (LoanScheduleProcessingType.HORIZONTAL
|
||||||
.equals(loanTransaction.getLoan().getLoanProductRelatedDetail().getLoanScheduleProcessingType())) {
|
.equals(loanTransaction.getLoan().getLoanProductRelatedDetail().getLoanScheduleProcessingType())) {
|
||||||
transactionAmountUnprocessed = processPeriodsHorizontally(loanTransaction, transactionCtx.getCurrency(),
|
transactionAmountUnprocessed = processPeriodsHorizontally(loanTransaction, transactionCtx, transactionAmountUnprocessed,
|
||||||
transactionCtx.getInstallments(), transactionCtx.getOverpaymentHolder().getMoneyObject(),
|
|
||||||
defaultPaymentAllocationRule, transactionMappings, Set.of(), balances);
|
defaultPaymentAllocationRule, transactionMappings, Set.of(), balances);
|
||||||
} else if (LoanScheduleProcessingType.VERTICAL
|
} else if (LoanScheduleProcessingType.VERTICAL
|
||||||
.equals(loanTransaction.getLoan().getLoanProductRelatedDetail().getLoanScheduleProcessingType())) {
|
.equals(loanTransaction.getLoan().getLoanProductRelatedDetail().getLoanScheduleProcessingType())) {
|
||||||
transactionAmountUnprocessed = processPeriodsVertically(loanTransaction, transactionCtx.getCurrency(),
|
transactionAmountUnprocessed = processPeriodsVertically(loanTransaction, transactionCtx.getCurrency(),
|
||||||
transactionCtx.getInstallments(), transactionCtx.getOverpaymentHolder().getMoneyObject(),
|
transactionCtx.getInstallments(), transactionAmountUnprocessed, defaultPaymentAllocationRule, transactionMappings,
|
||||||
defaultPaymentAllocationRule, transactionMappings, Set.of(), balances);
|
Set.of(), balances);
|
||||||
}
|
}
|
||||||
transactionCtx.getOverpaymentHolder().setMoneyObject(transactionAmountUnprocessed);
|
transactionCtx.getOverpaymentHolder().setMoneyObject(transactionAmountUnprocessed);
|
||||||
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings);
|
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings);
|
||||||
@@ -1104,9 +1114,8 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
|
|
||||||
if (LoanScheduleProcessingType.HORIZONTAL
|
if (LoanScheduleProcessingType.HORIZONTAL
|
||||||
.equals(loanTransaction.getLoan().getLoanProductRelatedDetail().getLoanScheduleProcessingType())) {
|
.equals(loanTransaction.getLoan().getLoanProductRelatedDetail().getLoanScheduleProcessingType())) {
|
||||||
transactionAmountUnprocessed = processPeriodsHorizontally(loanTransaction, transactionCtx.getCurrency(),
|
transactionAmountUnprocessed = processPeriodsHorizontally(loanTransaction, transactionCtx, transactionAmountUnprocessed,
|
||||||
transactionCtx.getInstallments(), transactionAmountUnprocessed, paymentAllocationRule, transactionMappings,
|
paymentAllocationRule, transactionMappings, transactionCtx.getCharges(), balances);
|
||||||
transactionCtx.getCharges(), balances);
|
|
||||||
} else if (LoanScheduleProcessingType.VERTICAL
|
} else if (LoanScheduleProcessingType.VERTICAL
|
||||||
.equals(loanTransaction.getLoan().getLoanProductRelatedDetail().getLoanScheduleProcessingType())) {
|
.equals(loanTransaction.getLoan().getLoanProductRelatedDetail().getLoanScheduleProcessingType())) {
|
||||||
transactionAmountUnprocessed = processPeriodsVertically(loanTransaction, transactionCtx.getCurrency(),
|
transactionAmountUnprocessed = processPeriodsVertically(loanTransaction, transactionCtx.getCurrency(),
|
||||||
@@ -1120,26 +1129,31 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
handleOverpayment(transactionAmountUnprocessed, loanTransaction, transactionCtx.getOverpaymentHolder());
|
handleOverpayment(transactionAmountUnprocessed, loanTransaction, transactionCtx.getOverpaymentHolder());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Money processPeriodsHorizontally(LoanTransaction loanTransaction, MonetaryCurrency currency,
|
private Money processPeriodsHorizontally(LoanTransaction loanTransaction, TransactionCtx transactionCtx,
|
||||||
List<LoanRepaymentScheduleInstallment> installments, Money transactionAmountUnprocessed,
|
Money transactionAmountUnprocessed, LoanPaymentAllocationRule paymentAllocationRule,
|
||||||
LoanPaymentAllocationRule paymentAllocationRule, List<LoanTransactionToRepaymentScheduleMapping> transactionMappings,
|
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Set<LoanCharge> charges, Balances balances) {
|
||||||
Set<LoanCharge> charges, Balances balances) {
|
|
||||||
LinkedHashMap<DueType, List<PaymentAllocationType>> paymentAllocationsMap = paymentAllocationRule.getAllocationTypes().stream()
|
LinkedHashMap<DueType, List<PaymentAllocationType>> paymentAllocationsMap = paymentAllocationRule.getAllocationTypes().stream()
|
||||||
.collect(Collectors.groupingBy(PaymentAllocationType::getDueType, LinkedHashMap::new,
|
.collect(Collectors.groupingBy(PaymentAllocationType::getDueType, LinkedHashMap::new,
|
||||||
mapping(Function.identity(), toList())));
|
mapping(Function.identity(), toList())));
|
||||||
|
|
||||||
for (Map.Entry<DueType, List<PaymentAllocationType>> paymentAllocationsEntry : paymentAllocationsMap.entrySet()) {
|
for (Map.Entry<DueType, List<PaymentAllocationType>> paymentAllocationsEntry : paymentAllocationsMap.entrySet()) {
|
||||||
transactionAmountUnprocessed = processAllocationsHorizontally(loanTransaction, currency, installments,
|
transactionAmountUnprocessed = processAllocationsHorizontally(loanTransaction, transactionCtx, transactionAmountUnprocessed,
|
||||||
transactionAmountUnprocessed, paymentAllocationsEntry.getValue(),
|
paymentAllocationsEntry.getValue(), paymentAllocationRule.getFutureInstallmentAllocationRule(), transactionMappings,
|
||||||
paymentAllocationRule.getFutureInstallmentAllocationRule(), transactionMappings, charges, balances);
|
charges, balances);
|
||||||
}
|
}
|
||||||
return transactionAmountUnprocessed;
|
return transactionAmountUnprocessed;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Money processAllocationsHorizontally(LoanTransaction loanTransaction, MonetaryCurrency currency,
|
private Money processAllocationsHorizontally(LoanTransaction loanTransaction, TransactionCtx transactionCtx,
|
||||||
List<LoanRepaymentScheduleInstallment> installments, Money transactionAmountUnprocessed,
|
Money transactionAmountUnprocessed, List<PaymentAllocationType> paymentAllocationTypes,
|
||||||
List<PaymentAllocationType> paymentAllocationTypes, FutureInstallmentAllocationRule futureInstallmentAllocationRule,
|
FutureInstallmentAllocationRule futureInstallmentAllocationRule,
|
||||||
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Set<LoanCharge> charges, Balances balances) {
|
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Set<LoanCharge> charges, Balances balances) {
|
||||||
|
if (transactionAmountUnprocessed.isZero()) {
|
||||||
|
return transactionAmountUnprocessed;
|
||||||
|
}
|
||||||
|
|
||||||
|
MonetaryCurrency currency = transactionCtx.getCurrency();
|
||||||
|
List<LoanRepaymentScheduleInstallment> installments = transactionCtx.getInstallments();
|
||||||
Money paidPortion;
|
Money paidPortion;
|
||||||
boolean exit = false;
|
boolean exit = false;
|
||||||
do {
|
do {
|
||||||
@@ -1209,15 +1223,55 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
for (LoanRepaymentScheduleInstallment inAdvanceInstallment : inAdvanceInstallments) {
|
for (LoanRepaymentScheduleInstallment inAdvanceInstallment : inAdvanceInstallments) {
|
||||||
Set<LoanCharge> inAdvanceInstallmentCharges = getLoanChargesOfInstallment(charges, inAdvanceInstallment,
|
Set<LoanCharge> inAdvanceInstallmentCharges = getLoanChargesOfInstallment(charges, inAdvanceInstallment,
|
||||||
firstNormalInstallmentNumber);
|
firstNormalInstallmentNumber);
|
||||||
// Adjust the portion for the last installment
|
|
||||||
if (inAdvanceInstallment.equals(inAdvanceInstallments.get(numberOfInstallments - 1))) {
|
|
||||||
evenPortion = evenPortion.add(balanceAdjustment);
|
|
||||||
}
|
|
||||||
LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
|
LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
|
||||||
transactionMappings, loanTransaction, inAdvanceInstallment, currency);
|
transactionMappings, loanTransaction, inAdvanceInstallment, currency);
|
||||||
paidPortion = processPaymentAllocation(paymentAllocationType, inAdvanceInstallment, loanTransaction,
|
|
||||||
evenPortion, loanTransactionToRepaymentScheduleMapping, inAdvanceInstallmentCharges, balances,
|
Loan loan = loanTransaction.getLoan();
|
||||||
LoanRepaymentScheduleInstallment.PaymentAction.PAY);
|
if (transactionCtx instanceof ProgressiveTransactionCtx ctx && loan.isInterestBearing()
|
||||||
|
&& loan.getLoanProductRelatedDetail().isInterestRecalculationEnabled()) {
|
||||||
|
ProgressiveLoanInterestScheduleModel model = ctx.getModel();
|
||||||
|
LocalDate transactionDate = loanTransaction.getTransactionDate();
|
||||||
|
LocalDate payDate = inAdvanceInstallment.getFromDate().isAfter(transactionDate)
|
||||||
|
? inAdvanceInstallment.getFromDate()
|
||||||
|
: transactionDate;
|
||||||
|
ProgressiveLoanInterestRepaymentModel payableDetails = emiCalculator
|
||||||
|
.getPayableDetails(model, inAdvanceInstallment.getDueDate(), payDate).orElseThrow();
|
||||||
|
|
||||||
|
switch (paymentAllocationType) {
|
||||||
|
case IN_ADVANCE_INTEREST ->
|
||||||
|
inAdvanceInstallment.updateInterestCharged(payableDetails.getInterestDue().getAmount());
|
||||||
|
case IN_ADVANCE_PRINCIPAL ->
|
||||||
|
inAdvanceInstallment.updatePrincipal(payableDetails.getPrincipalDue().getAmount());
|
||||||
|
default -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
paidPortion = processPaymentAllocation(paymentAllocationType, inAdvanceInstallment, loanTransaction,
|
||||||
|
transactionAmountUnprocessed, loanTransactionToRepaymentScheduleMapping,
|
||||||
|
inAdvanceInstallmentCharges, balances, LoanRepaymentScheduleInstallment.PaymentAction.PAY);
|
||||||
|
|
||||||
|
switch (paymentAllocationType) {
|
||||||
|
case IN_ADVANCE_PRINCIPAL -> {
|
||||||
|
emiCalculator.addBalanceCorrection(model, payDate,
|
||||||
|
payableDetails.getOutstandingBalance().multipliedBy(-1));
|
||||||
|
emiCalculator.addBalanceCorrection(model, payDate,
|
||||||
|
payableDetails.getPrincipalDue().minus(paidPortion));
|
||||||
|
}
|
||||||
|
case IN_ADVANCE_INTEREST -> emiCalculator.addBalanceCorrection(model, payDate,
|
||||||
|
payableDetails.getInterestDue().minus(paidPortion));
|
||||||
|
default -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Adjust the portion for the last installment
|
||||||
|
if (inAdvanceInstallment.equals(inAdvanceInstallments.get(numberOfInstallments - 1))) {
|
||||||
|
evenPortion = evenPortion.add(balanceAdjustment);
|
||||||
|
}
|
||||||
|
paidPortion = processPaymentAllocation(paymentAllocationType, inAdvanceInstallment, loanTransaction,
|
||||||
|
evenPortion, loanTransactionToRepaymentScheduleMapping, inAdvanceInstallmentCharges, balances,
|
||||||
|
LoanRepaymentScheduleInstallment.PaymentAction.PAY);
|
||||||
|
}
|
||||||
transactionAmountUnprocessed = transactionAmountUnprocessed.minus(paidPortion);
|
transactionAmountUnprocessed = transactionAmountUnprocessed.minus(paidPortion);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+28
-69
@@ -18,11 +18,10 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
|
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
|
||||||
|
|
||||||
import static java.time.temporal.ChronoUnit.DAYS;
|
import static org.apache.fineract.portfolio.loanproduct.domain.LoanPreClosureInterestCalculationStrategy.TILL_PRE_CLOSURE_DATE;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.MathContext;
|
import java.math.MathContext;
|
||||||
import java.math.RoundingMode;
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@@ -30,14 +29,12 @@ import java.util.Comparator;
|
|||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||||
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
|
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
|
||||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||||
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
|
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
|
import org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
|
||||||
@@ -46,6 +43,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
|||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO;
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleModelDownPaymentPeriod;
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleModelDownPaymentPeriod;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleParams;
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleParams;
|
||||||
@@ -247,77 +245,38 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
|
|||||||
public OutstandingAmountsDTO calculatePrepaymentAmount(MonetaryCurrency currency, LocalDate onDate,
|
public OutstandingAmountsDTO calculatePrepaymentAmount(MonetaryCurrency currency, LocalDate onDate,
|
||||||
LoanApplicationTerms loanApplicationTerms, MathContext mc, Loan loan, HolidayDetailDTO holidayDetailDTO,
|
LoanApplicationTerms loanApplicationTerms, MathContext mc, Loan loan, HolidayDetailDTO holidayDetailDTO,
|
||||||
LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) {
|
LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) {
|
||||||
return switch (loanApplicationTerms.getPreClosureInterestCalculationStrategy()) {
|
List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
|
||||||
case TILL_PRE_CLOSURE_DATE -> {
|
|
||||||
log.debug("calculating prepayment amount till pre closure date (Strategy A)");
|
|
||||||
OutstandingAmountsDTO outstandingAmounts = new OutstandingAmountsDTO(currency);
|
|
||||||
AtomicBoolean firstAfterPayoff = new AtomicBoolean(true);
|
|
||||||
loan.getRepaymentScheduleInstallments().forEach(installment -> {
|
|
||||||
boolean isInstallmentAfterPayoff = installment.getDueDate().isAfter(onDate);
|
|
||||||
|
|
||||||
outstandingAmounts.plusPrincipal(installment.getPrincipalOutstanding(currency));
|
LocalDate transactionDate = switch (loanApplicationTerms.getPreClosureInterestCalculationStrategy()) {
|
||||||
if (isInstallmentAfterPayoff) {
|
case TILL_PRE_CLOSURE_DATE -> onDate;
|
||||||
if (firstAfterPayoff.getAndSet(false)) {
|
case TILL_REST_FREQUENCY_DATE -> // find due date of current installment
|
||||||
outstandingAmounts.plusInterest(calculatePayableInterest(loan, installment, onDate));
|
installments.stream().filter(it -> it.getFromDate().isBefore(onDate) && it.getDueDate().isAfter(onDate)).findFirst()
|
||||||
} else {
|
.orElseThrow(() -> new IllegalStateException("No installment found for transaction date: " + onDate)).getDueDate();
|
||||||
log.debug("Installment {} - {} is after payoff, not counting interest", installment.getFromDate(),
|
case NONE -> throw new IllegalStateException("Unexpected PreClosureInterestCalculationStrategy: NONE");
|
||||||
installment.getDueDate());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.debug("adding interest for {} - {}: {}", installment.getFromDate(), installment.getDueDate(),
|
|
||||||
installment.getInterestOutstanding(currency));
|
|
||||||
outstandingAmounts.plusInterest(installment.getInterestOutstanding(currency));
|
|
||||||
}
|
|
||||||
outstandingAmounts.plusFeeCharges(installment.getFeeChargesOutstanding(currency));
|
|
||||||
outstandingAmounts.plusPenaltyCharges(installment.getPenaltyChargesOutstanding(currency));
|
|
||||||
});
|
|
||||||
yield outstandingAmounts;
|
|
||||||
}
|
|
||||||
|
|
||||||
case TILL_REST_FREQUENCY_DATE -> {
|
|
||||||
log.debug("calculating prepayment amount till rest frequency date (Strategy B)");
|
|
||||||
OutstandingAmountsDTO outstandingAmounts = new OutstandingAmountsDTO(currency);
|
|
||||||
loan.getRepaymentScheduleInstallments().forEach(installment -> {
|
|
||||||
boolean isPayoffBeforeInstallment = installment.getFromDate().isBefore(onDate);
|
|
||||||
|
|
||||||
outstandingAmounts.plusPrincipal(installment.getPrincipalOutstanding(currency));
|
|
||||||
if (isPayoffBeforeInstallment) {
|
|
||||||
outstandingAmounts.plusInterest(installment.getInterestOutstanding(currency));
|
|
||||||
} else {
|
|
||||||
log.debug("Payoff after installment {}, not counting interest", installment.getDueDate());
|
|
||||||
}
|
|
||||||
outstandingAmounts.plusFeeCharges(installment.getFeeChargesOutstanding(currency));
|
|
||||||
outstandingAmounts.plusPenaltyCharges(installment.getPenaltyChargesOutstanding(currency));
|
|
||||||
});
|
|
||||||
|
|
||||||
yield outstandingAmounts;
|
|
||||||
}
|
|
||||||
case NONE -> throw new UnsupportedOperationException("Pre-closure interest calculation strategy not supported");
|
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
private Money calculatePayableInterest(Loan loan, LoanRepaymentScheduleInstallment installment, LocalDate onDate) {
|
if (!(loanRepaymentScheduleTransactionProcessor instanceof AdvancedPaymentScheduleTransactionProcessor processor)) {
|
||||||
RoundingMode roundingMode = MoneyHelper.getRoundingMode();
|
throw new IllegalStateException("Expected an AdvancedPaymentScheduleTransactionProcessor");
|
||||||
MonetaryCurrency currency = loan.getCurrency();
|
|
||||||
Money originalInterest = installment.getInterestCharged(currency);
|
|
||||||
log.debug("calculating interest for {} from {} to {}", originalInterest, installment.getFromDate(), installment.getDueDate());
|
|
||||||
|
|
||||||
LocalDate start = installment.getFromDate();
|
|
||||||
Money payableInterest = Money.zero(currency);
|
|
||||||
|
|
||||||
while (!start.isEqual(onDate)) {
|
|
||||||
long between = DAYS.between(start, installment.getDueDate());
|
|
||||||
Money dailyInterest = originalInterest.minus(payableInterest).dividedBy(between, roundingMode);
|
|
||||||
log.debug("Daily interest is {}: {} / {}, total: {}", dailyInterest, originalInterest.minus(payableInterest), between,
|
|
||||||
payableInterest.add(dailyInterest));
|
|
||||||
payableInterest = payableInterest.add(dailyInterest);
|
|
||||||
start = start.plusDays(1);
|
|
||||||
}
|
}
|
||||||
|
ProgressiveLoanInterestScheduleModel model = processor.reprocessProgressiveLoanTransactions(loan.getDisbursementDate(),
|
||||||
|
loan.retrieveListOfTransactionsForReprocessing(), currency, installments, loan.getActiveCharges()).getRight();
|
||||||
|
|
||||||
payableInterest = payableInterest.minus(installment.getInterestPaid(currency).minus(installment.getInterestWaived(currency)));
|
LoanRepaymentScheduleInstallment actualInstallment = installments.stream()
|
||||||
|
.filter(it -> transactionDate.isAfter(it.getFromDate()) && !transactionDate.isAfter(it.getDueDate())).findFirst()
|
||||||
|
.orElse(installments.get(0));
|
||||||
|
|
||||||
log.debug("Payable interest is {}", payableInterest);
|
ProgressiveLoanInterestRepaymentModel result = emiCalculator
|
||||||
return payableInterest;
|
.getPayableDetails(model, actualInstallment.getDueDate(), transactionDate).orElseThrow();
|
||||||
|
|
||||||
|
OutstandingAmountsDTO amounts = new OutstandingAmountsDTO(currency) //
|
||||||
|
.principal(result.getOutstandingBalance()) //
|
||||||
|
.interest(result.getInterestDue());
|
||||||
|
|
||||||
|
installments.forEach(installment -> amounts //
|
||||||
|
.plusFeeCharges(installment.getFeeChargesOutstanding(currency))
|
||||||
|
.plusPenaltyCharges(installment.getPenaltyChargesOutstanding(currency)));
|
||||||
|
|
||||||
|
return amounts;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Private, internal methods
|
// Private, internal methods
|
||||||
|
|||||||
-192
@@ -1,192 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
|
|
||||||
|
|
||||||
import static java.math.BigDecimal.ZERO;
|
|
||||||
import static org.apache.fineract.portfolio.loanproduct.domain.LoanPreClosureInterestCalculationStrategy.TILL_PRE_CLOSURE_DATE;
|
|
||||||
import static org.apache.fineract.portfolio.loanproduct.domain.LoanPreClosureInterestCalculationStrategy.TILL_REST_FREQUENCY_DATE;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
import ch.qos.logback.classic.Level;
|
|
||||||
import ch.qos.logback.classic.Logger;
|
|
||||||
import ch.qos.logback.classic.LoggerContext;
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.math.MathContext;
|
|
||||||
import java.math.RoundingMode;
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
|
|
||||||
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.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.OutstandingAmountsDTO;
|
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.junit.jupiter.api.AfterAll;
|
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.test.util.ReflectionTestUtils;
|
|
||||||
|
|
||||||
class ProgressiveLoanScheduleGeneratorTest {
|
|
||||||
|
|
||||||
static class TestRow {
|
|
||||||
|
|
||||||
LocalDate fromDate;
|
|
||||||
LocalDate dueDate;
|
|
||||||
BigDecimal balance;
|
|
||||||
BigDecimal principal;
|
|
||||||
BigDecimal interest;
|
|
||||||
BigDecimal fee;
|
|
||||||
BigDecimal penalty;
|
|
||||||
boolean paid;
|
|
||||||
|
|
||||||
TestRow(LocalDate fromDate, LocalDate dueDate, BigDecimal balance, BigDecimal principal, BigDecimal interest, BigDecimal fee,
|
|
||||||
BigDecimal penalty, boolean paid) {
|
|
||||||
this.fromDate = fromDate;
|
|
||||||
this.dueDate = dueDate;
|
|
||||||
this.balance = balance;
|
|
||||||
this.principal = principal;
|
|
||||||
this.interest = interest;
|
|
||||||
this.fee = fee;
|
|
||||||
this.penalty = penalty;
|
|
||||||
this.paid = paid;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ProgressiveLoanScheduleGenerator generator = new ProgressiveLoanScheduleGenerator(null, null);
|
|
||||||
private MonetaryCurrency usd = new MonetaryCurrency("USD", 2, null);
|
|
||||||
private HolidayDetailDTO holidays = new HolidayDetailDTO(false, null, null);
|
|
||||||
LoanRepaymentScheduleTransactionProcessor processor = mock(LoanRepaymentScheduleTransactionProcessor.class);
|
|
||||||
|
|
||||||
static {
|
|
||||||
ConfigurationDomainService domainService = mock(ConfigurationDomainService.class);
|
|
||||||
when(domainService.getRoundingMode()).thenReturn(RoundingMode.HALF_UP.ordinal());
|
|
||||||
ReflectionTestUtils.setField(MoneyHelper.class, "staticConfigurationDomainService", domainService);
|
|
||||||
}
|
|
||||||
|
|
||||||
@BeforeAll
|
|
||||||
public static void beforeAll() {
|
|
||||||
((LoggerContext) LoggerFactory.getILoggerFactory()).getLogger(Logger.ROOT_LOGGER_NAME).setLevel(ch.qos.logback.classic.Level.DEBUG);
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterAll
|
|
||||||
public static void afterAll() {
|
|
||||||
((LoggerContext) LoggerFactory.getILoggerFactory()).getLogger(Logger.ROOT_LOGGER_NAME).setLevel(Level.INFO);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<TestRow> testRows() {
|
|
||||||
return List.of(
|
|
||||||
new TestRow(LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1), BigDecimal.valueOf(83.57), BigDecimal.valueOf(16.43),
|
|
||||||
BigDecimal.valueOf(0.58), ZERO, ZERO, true),
|
|
||||||
new TestRow(LocalDate.of(2024, 2, 1), LocalDate.of(2024, 3, 1), BigDecimal.valueOf(67.05), BigDecimal.valueOf(16.52),
|
|
||||||
BigDecimal.valueOf(0.49), ZERO, ZERO, false),
|
|
||||||
new TestRow(LocalDate.of(2024, 3, 1), LocalDate.of(2024, 4, 1), BigDecimal.valueOf(50.43), BigDecimal.valueOf(16.62),
|
|
||||||
BigDecimal.valueOf(0.39), ZERO, ZERO, false),
|
|
||||||
new TestRow(LocalDate.of(2024, 4, 1), LocalDate.of(2024, 5, 1), BigDecimal.valueOf(33.71), BigDecimal.valueOf(16.72),
|
|
||||||
BigDecimal.valueOf(0.29), ZERO, ZERO, false),
|
|
||||||
new TestRow(LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1), BigDecimal.valueOf(16.90), BigDecimal.valueOf(16.81),
|
|
||||||
BigDecimal.valueOf(0.20), ZERO, ZERO, false),
|
|
||||||
new TestRow(LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1), BigDecimal.valueOf(00.90), BigDecimal.valueOf(16.90),
|
|
||||||
BigDecimal.valueOf(0.10), ZERO, ZERO, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void calculatePrepaymentAmount_TILL_PRE_CLOSURE_DATE() {
|
|
||||||
LoanApplicationTerms terms = mock(LoanApplicationTerms.class);
|
|
||||||
when(terms.getPreClosureInterestCalculationStrategy()).thenReturn(TILL_PRE_CLOSURE_DATE);
|
|
||||||
Loan loan = prepareLoanWithInstallments(testRows());
|
|
||||||
|
|
||||||
OutstandingAmountsDTO amounts = generator.calculatePrepaymentAmount(usd, LocalDate.of(2024, 2, 15), terms, MathContext.DECIMAL32,
|
|
||||||
loan, holidays, processor);
|
|
||||||
assertEquals(BigDecimal.valueOf(83.84), amounts.getTotalOutstanding().getAmount());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void calculatePrepaymentAmount_TILL_REST_FREQUENCY_DATE() {
|
|
||||||
LoanApplicationTerms terms = mock(LoanApplicationTerms.class);
|
|
||||||
when(terms.getPreClosureInterestCalculationStrategy()).thenReturn(TILL_REST_FREQUENCY_DATE);
|
|
||||||
Loan loan = prepareLoanWithInstallments(testRows());
|
|
||||||
|
|
||||||
OutstandingAmountsDTO amounts = generator.calculatePrepaymentAmount(usd, LocalDate.of(2024, 2, 15), terms, MathContext.DECIMAL32,
|
|
||||||
loan, holidays, processor);
|
|
||||||
assertEquals(BigDecimal.valueOf(84.06), amounts.getTotalOutstanding().getAmount());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void calculateSameDayPayoff_TILL_PRE_CLOSURE_DATE() {
|
|
||||||
LoanApplicationTerms terms = mock(LoanApplicationTerms.class);
|
|
||||||
when(terms.getPreClosureInterestCalculationStrategy()).thenReturn(TILL_PRE_CLOSURE_DATE);
|
|
||||||
|
|
||||||
Loan loan = prepareLoanWithInstallments(List.of(
|
|
||||||
new TestRow(LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1), BigDecimal.valueOf(102), BigDecimal.valueOf(100),
|
|
||||||
BigDecimal.valueOf(2), ZERO, ZERO, false),
|
|
||||||
new TestRow(LocalDate.of(2024, 2, 1), LocalDate.of(2024, 3, 1), BigDecimal.valueOf(102), BigDecimal.valueOf(100),
|
|
||||||
BigDecimal.valueOf(2), ZERO, ZERO, false)));
|
|
||||||
|
|
||||||
OutstandingAmountsDTO amounts = generator.calculatePrepaymentAmount(usd, LocalDate.of(2024, 1, 1), terms, MathContext.DECIMAL32,
|
|
||||||
loan, holidays, processor);
|
|
||||||
assertEquals(BigDecimal.valueOf(200.0).longValue(), amounts.getTotalOutstanding().getAmount().longValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void calculateSameDayPayoff_TILL_REST_FREQUENCY_DATE() {
|
|
||||||
LoanApplicationTerms terms = mock(LoanApplicationTerms.class);
|
|
||||||
when(terms.getPreClosureInterestCalculationStrategy()).thenReturn(TILL_REST_FREQUENCY_DATE);
|
|
||||||
|
|
||||||
Loan loan = prepareLoanWithInstallments(List.of(
|
|
||||||
new TestRow(LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1), BigDecimal.valueOf(102), BigDecimal.valueOf(100),
|
|
||||||
BigDecimal.valueOf(2), ZERO, ZERO, false),
|
|
||||||
new TestRow(LocalDate.of(2024, 2, 1), LocalDate.of(2024, 3, 1), BigDecimal.valueOf(102), BigDecimal.valueOf(100),
|
|
||||||
BigDecimal.valueOf(2), ZERO, ZERO, false)));
|
|
||||||
|
|
||||||
OutstandingAmountsDTO amounts = generator.calculatePrepaymentAmount(usd, LocalDate.of(2024, 1, 1), terms, MathContext.DECIMAL32,
|
|
||||||
loan, holidays, processor);
|
|
||||||
assertEquals(BigDecimal.valueOf(200.0).longValue(), amounts.getTotalOutstanding().getAmount().longValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private Loan prepareLoanWithInstallments(List<TestRow> rows) {
|
|
||||||
Loan loan = mock(Loan.class);
|
|
||||||
List<LoanRepaymentScheduleInstallment> installments = createInstallments(rows, loan, usd);
|
|
||||||
when(loan.getRepaymentScheduleInstallments()).thenReturn(installments);
|
|
||||||
when(loan.getCurrency()).thenReturn(usd);
|
|
||||||
return loan;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<LoanRepaymentScheduleInstallment> createInstallments(List<TestRow> rows, Loan loan, MonetaryCurrency usd) {
|
|
||||||
AtomicInteger count = new AtomicInteger(1);
|
|
||||||
return rows.stream().map(row -> {
|
|
||||||
LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(loan, count.incrementAndGet(), row.fromDate,
|
|
||||||
row.dueDate, row.principal, row.interest, row.fee, row.penalty, true, null, null, row.paid);
|
|
||||||
if (row.paid) {
|
|
||||||
installment.payPrincipalComponent(row.fromDate, Money.of(usd, row.principal));
|
|
||||||
installment.payInterestComponent(row.fromDate, Money.of(usd, row.interest));
|
|
||||||
installment.updateObligationMet(true);
|
|
||||||
}
|
|
||||||
return installment;
|
|
||||||
}).toList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user