FINERACT-1981: Progressive Loan schedule handling - Installment charges
This commit is contained in:
committed by
Adam Saghy
parent
f76a65ed99
commit
d165a3feda
+5
-2
@@ -28,12 +28,15 @@ public enum ChargeProductType {
|
||||
LOAN_NSF_FEE(6L), //
|
||||
LOAN_DISBURSEMENT_PERCENTAGE_FEE(7L), //
|
||||
LOAN_TRANCHE_DISBURSEMENT_PERCENTAGE_FEE(8L), //
|
||||
LOAN_INSTALLMENT_PERCENTAGE_FEE(9L), //
|
||||
LOAN_INSTALLMENT_FEE_PERCENTAGE_AMOUNT_PLUS_INTEREST(9L), //
|
||||
LOAN_PERCENTAGE_LATE_FEE_AMOUNT_PLUS_INTEREST(10L), //
|
||||
CLIENT_TEST_CHARGE_FEE(11L), //
|
||||
LOAN_DISBURSEMENT_CHARGE(12L), //
|
||||
CHARGE_LOAN_TRANCHE_DISBURSEMENT_CHARGE_AMOUNT(13L), //
|
||||
CHARGE_LOAN_TRANCHE_DISBURSEMENT_CHARGE_PERCENT(14L); //
|
||||
CHARGE_LOAN_TRANCHE_DISBURSEMENT_CHARGE_PERCENT(14L), //
|
||||
LOAN_INSTALLMENT_FEE_FLAT(15L), //
|
||||
LOAN_INSTALLMENT_FEE_PERCENTAGE_AMOUNT(16L), //
|
||||
LOAN_INSTALLMENT_FEE_PERCENTAGE_INTEREST(17L); //
|
||||
|
||||
public final Long value;
|
||||
|
||||
|
||||
+6
@@ -138,6 +138,12 @@ public enum DefaultLoanProduct implements LoanProduct {
|
||||
LP2_ADV_PYMNT_INTEREST_DAILY_RECALC_EMI_360_30_APPROVED_OVER_APPLIED_FLAT_CAPITALIZED_INCOME, //
|
||||
LP2_ADV_PYMNT_INTEREST_DAILY_RECALC_EMI_360_30_MULTIDISB_CAPITALIZED_INCOME_ADJ_CUSTOM_ALLOC, //
|
||||
LP2_ADV_PYMNT_360_30_INTEREST_RECALC_AUTO_DOWNPAYMENT_ZERO_INTEREST_CHARGE_OFF_ACCRUAL_ACTIVITY, //
|
||||
LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALC_DAILY_INSTALLMENT_FEE_FLAT_CHARGES, //
|
||||
LP2_ADV_PYMNT_INTEREST_DAILY_INSTALLMENT_FEE_PERCENT_AMOUNT_CHARGES, //
|
||||
LP2_ADV_PYMNT_INTEREST_DAILY_INSTALLMENT_FEE_PERCENT_INTEREST_CHARGES, //
|
||||
LP2_ADV_PYMNT_INTEREST_DAILY_INSTALLMENT_FEE_PERCENT_AMOUNT_INTEREST_CHARGES, //
|
||||
LP2_ADV_PYMNT_INTEREST_DAILY_INSTALLMENT_FEE_ALL_CHARGES, //
|
||||
LP2_ADV_PYMNT_INTEREST_DAILY_INSTALLMENT_FEE_FLAT_INTEREST_CHARGES_TRANCHE, //
|
||||
;
|
||||
|
||||
@Override
|
||||
|
||||
+29
@@ -60,6 +60,8 @@ public class LoanRequestFactory {
|
||||
public static final String DATE_FORMAT = "dd MMMM yyyy";
|
||||
public static final String DEFAULT_LOCALE = "en";
|
||||
public static final DefaultLoanProduct DEFAULT_LOAN_PRODUCT = DefaultLoanProduct.valueOf("LP1");
|
||||
public static final DefaultLoanProduct DEFAULT_PROGRESSIVE_LOAN_PRODUCT = DefaultLoanProduct
|
||||
.valueOf("LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL");
|
||||
public static final Double DEFAULT_PAYMENT_TRANSACTION_AMOUNT = 200.00;
|
||||
public static final Double DEFAULT_UNDO_TRANSACTION_AMOUNT = 0.0;
|
||||
public static final Double DEFAULT_REPAYMENT_TRANSACTION_AMOUNT = 200.00;
|
||||
@@ -78,13 +80,16 @@ public class LoanRequestFactory {
|
||||
public static final String DEFAULT_REAGING_FREQUENCY_TYPE = "MONTHS";
|
||||
public static final BigDecimal DEFAULT_INTEREST_RATE_PER_PERIOD = new BigDecimal(0);
|
||||
public static final Integer DEFAULT_INTEREST_TYPE = InterestType.FLAT.value;
|
||||
public static final Integer DEFAULT_PROGRESSIVE_INTEREST_TYPE = InterestType.DECLINING_BALANCE.value;
|
||||
public static final Integer DEFAULT_INTEREST_CALCULATION_PERIOD_TYPE_SAME_AS_REPAYMENT_PERIOD = InterestCalculationPeriodTime.SAME_AS_REPAYMENT_PERIOD.value;
|
||||
public static final Integer DEFAULT_PROGRESSIVE_INTEREST_CALCULATION_PERIOD_TYPE_SAME_AS_REPAYMENT_PERIOD = InterestCalculationPeriodTime.DAILY.value;
|
||||
public static final Integer DEFAULT_AMORTIZATION_TYPE = 1;
|
||||
public static final String DEFAULT_LOAN_TYPE = "individual";
|
||||
public static final Integer DEFAULT_NUMBER_OF_REPAYMENTS = 1;
|
||||
public static final Integer DEFAULT_NUMBER_OF_INSTALLMENTS = 5;
|
||||
public static final Integer DEFAULT_REPAYMENT_FREQUENCY = 30;
|
||||
public static final String DEFAULT_TRANSACTION_PROCESSING_STRATEGY_CODE = TransactionProcessingStrategyCode.PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER.value;
|
||||
public static final String DEFAULT_PROGRESSIVE_TRANSACTION_PROCESSING_STRATEGY_CODE = TransactionProcessingStrategyCode.ADVANCED_PAYMENT_ALLOCATION.value;
|
||||
|
||||
public static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern(DATE_FORMAT);
|
||||
public static final String DATE_SUBMIT_STRING = FORMATTER.format(Utils.now().minusMonths(1L));
|
||||
@@ -118,6 +123,30 @@ public class LoanRequestFactory {
|
||||
.maxOutstandingLoanBalance(new BigDecimal(10000));
|
||||
}
|
||||
|
||||
public PostLoansRequest defaultProgressiveLoansRequest(final Long clientId) {
|
||||
return new PostLoansRequest()//
|
||||
.clientId(clientId)//
|
||||
.productId(loanProductResolver.resolve(DEFAULT_PROGRESSIVE_LOAN_PRODUCT))//
|
||||
.submittedOnDate(DATE_SUBMIT_STRING)//
|
||||
.expectedDisbursementDate(DATE_SUBMIT_STRING)//
|
||||
.principal(DEFAULT_PRINCIPAL)//
|
||||
.locale(DEFAULT_LOCALE)//
|
||||
.loanTermFrequency(DEFAULT_LOAN_TERM_FREQUENCY)//
|
||||
.loanTermFrequencyType(DEFAULT_LOAN_TERM_FREQUENCY_TYPE)//
|
||||
.loanType(DEFAULT_LOAN_TYPE)//
|
||||
.numberOfRepayments(DEFAULT_NUMBER_OF_REPAYMENTS)//
|
||||
.repaymentEvery(DEFAULT_REPAYMENT_FREQUENCY)//
|
||||
.repaymentFrequencyType(DEFAULT_REPAYMENT_FREQUENCY_TYPE)//
|
||||
.interestRatePerPeriod(DEFAULT_INTEREST_RATE_PER_PERIOD)//
|
||||
.interestType(DEFAULT_PROGRESSIVE_INTEREST_TYPE)//
|
||||
.interestCalculationPeriodType(DEFAULT_PROGRESSIVE_INTEREST_CALCULATION_PERIOD_TYPE_SAME_AS_REPAYMENT_PERIOD)//
|
||||
.amortizationType(DEFAULT_AMORTIZATION_TYPE)//
|
||||
.transactionProcessingStrategyCode(DEFAULT_PROGRESSIVE_TRANSACTION_PROCESSING_STRATEGY_CODE)//
|
||||
.dateFormat(DATE_FORMAT)//
|
||||
.graceOnArrearsAgeing(3)//
|
||||
.maxOutstandingLoanBalance(new BigDecimal(10000));
|
||||
}
|
||||
|
||||
public PutLoansLoanIdRequest modifySubmittedOnDateOnLoan(Long clientId, String newSubmittedOnDate) {
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT);
|
||||
String dateDisburseStr = formatter.format(Utils.now());
|
||||
|
||||
+8
@@ -972,4 +972,12 @@ public final class ErrorMessageHelper {
|
||||
public static String addInterestPauseForNotInactiveLoanFailure() {
|
||||
return "Operations on interest pauses are restricted to active loans.";
|
||||
}
|
||||
|
||||
public static String addInstallmentFeeInterestPercentageChargeFailure() {
|
||||
return "Failed data validation due to: installment.loancharge.with.calculation.type.interest.not.allowed.";
|
||||
}
|
||||
|
||||
public static String addInstallmentFeePrincipalPercentageChargeFailure() {
|
||||
return "Failed data validation due to: installment.loancharge.with.calculation.type.principal.not.allowed.";
|
||||
}
|
||||
}
|
||||
|
||||
+39
-7
@@ -52,12 +52,17 @@ public class ChargeGlobalInitializerStep implements FineractGlobalInitializerSte
|
||||
public static final String CHARGE_LOAN_NSF_FEE = "NSF fee";
|
||||
public static final String CHARGE_LOAN_DISBURSEMENT_PERCENT_FEE = "Disbursement percentage fee";
|
||||
public static final String CHARGE_LOAN_TRANCHE_DISBURSEMENT_PERCENT_FEE = "Tranche Disbursement percentage fee";
|
||||
public static final String CHARGE_LOAN_INSTALLMENT_PERCENT_FEE = "Installment percentage fee";
|
||||
public static final String CHARGE_LOAN_INSTALLMENT_FEE_FLAT = "Installment flat fee";
|
||||
public static final String CHARGE_LOAN_INSTALLMENT_FEE_PERCENT_AMOUNT = "Installment percentage amount fee";
|
||||
public static final String CHARGE_LOAN_INSTALLMENT_FEE_PERCENT_INTEREST = "Installment percentage interest fee";
|
||||
public static final String CHARGE_LOAN_INSTALLMENT_FEE_PERCENT_AMOUNT_PLUS_INTEREST = "Installment percentage amount + interest fee";
|
||||
public static final String CHARGE_CLIENT_FIXED_FEE = "Fixed fee for Client";
|
||||
public static final String CHARGE_DISBURSEMENT_CHARGE = "Disbursement Charge";
|
||||
public static final String CHARGE_LOAN_TRANCHE_DISBURSEMENT_CHARGE_AMOUNT = "Tranche Disbursement Charge Amount";
|
||||
public static final String CHARGE_LOAN_TRANCHE_DISBURSEMENT_CHARGE_PERCENT = "Tranche Disbursement Charge Percent";
|
||||
public static final Double CHARGE_AMOUNT_FLAT = 25D;
|
||||
public static final Double CHARGE_INSTALLMENT_FEE_AMOUNT_FLAT = 10D;
|
||||
public static final Double CHARGE_INSTALLMENT_FEE_AMOUNT_PERCENTAGE = 1D;
|
||||
public static final Double CHARGE_AMOUNT_PERCENTAGE = 5D;
|
||||
public static final Double CHARGE_AMOUNT_DISBURSEMENT_PERCENTAGE = 1.5D;
|
||||
public static final Double CHARGE_AMOUNT_INSTALLMENT_PERCENTAGE = 1.5D;
|
||||
@@ -69,6 +74,7 @@ public class ChargeGlobalInitializerStep implements FineractGlobalInitializerSte
|
||||
public static final Integer CHARGE_TIME_TYPE_INSTALLMENT = ChargeTimeType.INSTALLMENT_FEE.value;
|
||||
public static final Integer CHARGE_CALCULATION_TYPE_FLAT = ChargeCalculationType.FLAT.value;
|
||||
public static final Integer CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT = ChargeCalculationType.PERCENTAGE_AMOUNT.value;
|
||||
public static final Integer CHARGE_CALCULATION_TYPE_PERCENTAGE_INTEREST = ChargeCalculationType.PERCENTAGE_INTEREST.value;
|
||||
public static final Integer CHARGE_CALCULATION_TYPE_PERCENTAGE_DISBURSEMENT_AMOUNT = ChargeCalculationType.PERCENTAGE_DISBURSEMENT_AMOUNT.value;
|
||||
public static final Integer CHARGE_CALCULATION_TYPE_PERCENTAGE_LOAN_AMOUNT_PLUS_INTEREST = ChargeCalculationType.PERCENTAGE_LOAN_AMOUNT_PLUS_INTEREST.value;
|
||||
|
||||
@@ -130,12 +136,14 @@ public class ChargeGlobalInitializerStep implements FineractGlobalInitializerSte
|
||||
TestContext.INSTANCE.set(TestContextKey.CHARGE_FOR_LOAN_TRANCHE_DISBURSEMENT_PERCENT_CREATE_RESPONSE,
|
||||
responseLoanTrancheDisbursePercent);
|
||||
|
||||
// Loan - Installment % fee
|
||||
ChargeRequest requestLoanInstallmentPercent = defaultChargesRequest(CHARGE_APPLIES_TO_LOAN, CHARGE_LOAN_INSTALLMENT_PERCENT_FEE,
|
||||
CHARGE_TIME_TYPE_INSTALLMENT, CHARGE_CALCULATION_TYPE_PERCENTAGE_LOAN_AMOUNT_PLUS_INTEREST,
|
||||
CHARGE_AMOUNT_INSTALLMENT_PERCENTAGE, true, false);
|
||||
Response<PostChargesResponse> responseLoanInstallmentPercent = chargesApi.createCharge(requestLoanInstallmentPercent).execute();
|
||||
TestContext.INSTANCE.set(TestContextKey.CHARGE_FOR_LOAN_INSTALLMENT_FEE_CREATE_RESPONSE, responseLoanInstallmentPercent);
|
||||
// Loan - Installment % fee Amount + Interest
|
||||
final ChargeRequest requestLoanInstallmentPercentAmountPlusInterest = defaultChargesRequest(CHARGE_APPLIES_TO_LOAN,
|
||||
CHARGE_LOAN_INSTALLMENT_FEE_PERCENT_AMOUNT_PLUS_INTEREST, CHARGE_TIME_TYPE_INSTALLMENT,
|
||||
CHARGE_CALCULATION_TYPE_PERCENTAGE_LOAN_AMOUNT_PLUS_INTEREST, CHARGE_INSTALLMENT_FEE_AMOUNT_PERCENTAGE, true, false);
|
||||
final Response<PostChargesResponse> responseLoanInstallmentPercentAmountPlusInterest = chargesApi
|
||||
.createCharge(requestLoanInstallmentPercentAmountPlusInterest).execute();
|
||||
TestContext.INSTANCE.set(TestContextKey.CHARGE_FOR_LOAN_INSTALLMENT_FEE_PERCENTAGE_AMOUNT_PLUS_INTEREST_CREATE_RESPONSE,
|
||||
responseLoanInstallmentPercentAmountPlusInterest);
|
||||
|
||||
// Loan - % late (overdue) fee amount+interest
|
||||
ChargeRequest requestLoanPercentAmountPlusInterestLate = defaultChargesRequest(CHARGE_APPLIES_TO_LOAN,
|
||||
@@ -175,6 +183,30 @@ public class ChargeGlobalInitializerStep implements FineractGlobalInitializerSte
|
||||
.createCharge(requestTrancheDisbursementChargePercent).execute();
|
||||
TestContext.INSTANCE.set(TestContextKey.CHARGE_FOR_LOAN_TRANCHE_DISBURSEMENT_CHARGE_PERCENT_CREATE_RESPONSE,
|
||||
responseTrancheDisbursementChargePercent);
|
||||
|
||||
// Loan - Installment flat fee
|
||||
final ChargeRequest requestLoanInstallmentFlat = defaultChargesRequest(CHARGE_APPLIES_TO_LOAN, CHARGE_LOAN_INSTALLMENT_FEE_FLAT,
|
||||
CHARGE_TIME_TYPE_INSTALLMENT, CHARGE_CALCULATION_TYPE_FLAT, CHARGE_INSTALLMENT_FEE_AMOUNT_FLAT, true, false);
|
||||
final Response<PostChargesResponse> responseLoanInstallmentFlat = chargesApi.createCharge(requestLoanInstallmentFlat).execute();
|
||||
TestContext.INSTANCE.set(TestContextKey.CHARGE_FOR_LOAN_INSTALLMENT_FEE_FLAT_CREATE_RESPONSE, responseLoanInstallmentFlat);
|
||||
|
||||
// Loan - Installment % fee Amount
|
||||
final ChargeRequest requestLoanInstallmentPercentAmount = defaultChargesRequest(CHARGE_APPLIES_TO_LOAN,
|
||||
CHARGE_LOAN_INSTALLMENT_FEE_PERCENT_AMOUNT, CHARGE_TIME_TYPE_INSTALLMENT, CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT,
|
||||
CHARGE_INSTALLMENT_FEE_AMOUNT_PERCENTAGE, true, false);
|
||||
final Response<PostChargesResponse> responseLoanInstallmentPercentAmount = chargesApi
|
||||
.createCharge(requestLoanInstallmentPercentAmount).execute();
|
||||
TestContext.INSTANCE.set(TestContextKey.CHARGE_FOR_LOAN_INSTALLMENT_FEE_PERCENTAGE_AMOUNT_CREATE_RESPONSE,
|
||||
responseLoanInstallmentPercentAmount);
|
||||
|
||||
// Loan - Installment % fee Interest
|
||||
final ChargeRequest requestLoanInstallmentPercentInterest = defaultChargesRequest(CHARGE_APPLIES_TO_LOAN,
|
||||
CHARGE_LOAN_INSTALLMENT_FEE_PERCENT_INTEREST, CHARGE_TIME_TYPE_INSTALLMENT, CHARGE_CALCULATION_TYPE_PERCENTAGE_INTEREST,
|
||||
CHARGE_AMOUNT_PERCENTAGE, true, false);
|
||||
final Response<PostChargesResponse> responseLoanInstallmentPercentInterest = chargesApi
|
||||
.createCharge(requestLoanInstallmentPercentInterest).execute();
|
||||
TestContext.INSTANCE.set(TestContextKey.CHARGE_FOR_LOAN_INSTALLMENT_FEE_PERCENTAGE_INTEREST_CREATE_RESPONSE,
|
||||
responseLoanInstallmentPercentInterest);
|
||||
}
|
||||
|
||||
public static ChargeRequest defaultChargesRequest(Enum<ChargeProductAppliesTo> appliesTo, String name, Integer chargeTimeType,
|
||||
|
||||
+165
@@ -2982,6 +2982,171 @@ public class LoanProductGlobalInitializerStep implements FineractGlobalInitializ
|
||||
TestContext.INSTANCE.set(
|
||||
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_360_30_INTEREST_RECALC_AUTO_DOWNPAYMENT_ZERO_INTEREST_CHARGE_OFF_ACCRUAL_ACTIVITY,
|
||||
responseLoanProductsRequestAdvInterestRecalculationAutoDownpaymentZeroInterestChargeOffBehaviourAccrualActivity);
|
||||
|
||||
// LP2 with progressive loan schedule + horizontal + interest recalculation
|
||||
// charges - Installment Fee Flat
|
||||
// (LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALC_DAILY_INSTALLMENT_FEE_FLAT_CHARGES)
|
||||
final String name116 = DefaultLoanProduct.LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALC_DAILY_INSTALLMENT_FEE_FLAT_CHARGES.getName();
|
||||
final List<LoanProductChargeData> chargesInstallmentFeeFlat = new ArrayList<>();
|
||||
chargesInstallmentFeeFlat.add(new LoanProductChargeData().id(ChargeProductType.LOAN_INSTALLMENT_FEE_FLAT.value));
|
||||
final PostLoanProductsRequest loanProductsRequestLP2AdvPaymentInterestRecalcDailyInstallmentFeeFlatCharges = loanProductsRequestFactory
|
||||
.defaultLoanProductsRequestLP2InterestDailyRecalculation()//
|
||||
.name(name116)//
|
||||
.charges(chargesInstallmentFeeFlat)//
|
||||
.paymentAllocation(List.of(//
|
||||
createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"), //
|
||||
createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT"), //
|
||||
createPaymentAllocation("MERCHANT_ISSUED_REFUND", "REAMORTIZATION"), //
|
||||
createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT")));//
|
||||
final Response<PostLoanProductsResponse> responseLoanProductsRequestLP2AdvPaymentInterestRecalcDailyInstallmentFeeFlatCharges = loanProductsApi
|
||||
.createLoanProduct(loanProductsRequestLP2AdvPaymentInterestRecalcDailyInstallmentFeeFlatCharges).execute();
|
||||
TestContext.INSTANCE.set(
|
||||
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALC_DAILY_INSTALLMENT_FEE_FLAT_CHARGES,
|
||||
responseLoanProductsRequestLP2AdvPaymentInterestRecalcDailyInstallmentFeeFlatCharges);
|
||||
|
||||
// LP2 with progressive loan schedule + horizontal
|
||||
// charges - Installment Fee Percentage Amount
|
||||
// (LP2_ADV_PYMNT_INTEREST_DAILY_INSTALLMENT_FEE_PERCENT_AMOUNT_CHARGES)
|
||||
final String name117 = DefaultLoanProduct.LP2_ADV_PYMNT_INTEREST_DAILY_INSTALLMENT_FEE_PERCENT_AMOUNT_CHARGES.getName();
|
||||
final List<LoanProductChargeData> chargesInstallmentFeePercentAmount = new ArrayList<>();
|
||||
chargesInstallmentFeePercentAmount
|
||||
.add(new LoanProductChargeData().id(ChargeProductType.LOAN_INSTALLMENT_FEE_PERCENTAGE_AMOUNT.value));
|
||||
final PostLoanProductsRequest loanProductsRequestLP2AdvPaymentInstallmentFeePercentAmountCharges = loanProductsRequestFactory
|
||||
.defaultLoanProductsRequestLP2()//
|
||||
.name(name117)//
|
||||
.charges(chargesInstallmentFeePercentAmount)//
|
||||
.enableDownPayment(false)//
|
||||
.enableAutoRepaymentForDownPayment(null)//
|
||||
.disbursedAmountPercentageForDownPayment(null)//
|
||||
.transactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION.getValue())//
|
||||
.loanScheduleType("PROGRESSIVE") //
|
||||
.loanScheduleProcessingType("HORIZONTAL")//
|
||||
.interestRateFrequencyType(3)//
|
||||
.maxInterestRatePerPeriod(10.0)//
|
||||
.paymentAllocation(List.of(//
|
||||
createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"), //
|
||||
createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT"), //
|
||||
createPaymentAllocation("MERCHANT_ISSUED_REFUND", "REAMORTIZATION"), //
|
||||
createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT")));//
|
||||
final Response<PostLoanProductsResponse> responseLoanProductsRequestLP2AdvPaymentInstallmentFeePercentAmountCharges = loanProductsApi
|
||||
.createLoanProduct(loanProductsRequestLP2AdvPaymentInstallmentFeePercentAmountCharges).execute();
|
||||
TestContext.INSTANCE.set(
|
||||
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_INSTALLMENT_FEE_PERCENT_AMOUNT_CHARGES,
|
||||
responseLoanProductsRequestLP2AdvPaymentInstallmentFeePercentAmountCharges);
|
||||
|
||||
// LP2 with progressive loan schedule + horizontal
|
||||
// charges - Installment Fee Percentage Interest
|
||||
// (LP2_ADV_PYMNT_INTEREST_DAILY_INSTALLMENT_FEE_PERCENT_INTEREST_CHARGES)
|
||||
final String name118 = DefaultLoanProduct.LP2_ADV_PYMNT_INTEREST_DAILY_INSTALLMENT_FEE_PERCENT_INTEREST_CHARGES.getName();
|
||||
final List<LoanProductChargeData> chargesInstallmentFeePercentInterest = new ArrayList<>();
|
||||
chargesInstallmentFeePercentInterest
|
||||
.add(new LoanProductChargeData().id(ChargeProductType.LOAN_INSTALLMENT_FEE_PERCENTAGE_INTEREST.value));
|
||||
final PostLoanProductsRequest loanProductsRequestLP2AdvPaymentInstallmentFeePercentInterestCharges = loanProductsRequestFactory
|
||||
.defaultLoanProductsRequestLP2()//
|
||||
.name(name118)//
|
||||
.charges(chargesInstallmentFeePercentInterest)//
|
||||
.enableDownPayment(false)//
|
||||
.enableAutoRepaymentForDownPayment(null)//
|
||||
.disbursedAmountPercentageForDownPayment(null)//
|
||||
.transactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION.getValue())//
|
||||
.loanScheduleType("PROGRESSIVE") //
|
||||
.loanScheduleProcessingType("HORIZONTAL")//
|
||||
.interestRateFrequencyType(3)//
|
||||
.maxInterestRatePerPeriod(10.0)//
|
||||
.paymentAllocation(List.of(//
|
||||
createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"), //
|
||||
createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT"), //
|
||||
createPaymentAllocation("MERCHANT_ISSUED_REFUND", "REAMORTIZATION"), //
|
||||
createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT")));//
|
||||
final Response<PostLoanProductsResponse> responseLoanProductsRequestLP2AdvPaymentInstallmentFeePercentInterestCharges = loanProductsApi
|
||||
.createLoanProduct(loanProductsRequestLP2AdvPaymentInstallmentFeePercentInterestCharges).execute();
|
||||
TestContext.INSTANCE.set(
|
||||
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_INSTALLMENT_FEE_PERCENT_INTEREST_CHARGES,
|
||||
responseLoanProductsRequestLP2AdvPaymentInstallmentFeePercentInterestCharges);
|
||||
|
||||
// LP2 with progressive loan schedule + horizontal
|
||||
// charges - Installment Fee Percentage Amount + Interest
|
||||
// (LP2_ADV_PYMNT_INTEREST_DAILY_INSTALLMENT_FEE_PERCENT_AMOUNT_INTEREST_CHARGES)
|
||||
final String name119 = DefaultLoanProduct.LP2_ADV_PYMNT_INTEREST_DAILY_INSTALLMENT_FEE_PERCENT_AMOUNT_INTEREST_CHARGES.getName();
|
||||
final List<LoanProductChargeData> chargesInstallmentFeePercentAmountPlusInterest = new ArrayList<>();
|
||||
chargesInstallmentFeePercentAmountPlusInterest
|
||||
.add(new LoanProductChargeData().id(ChargeProductType.LOAN_INSTALLMENT_FEE_PERCENTAGE_AMOUNT_PLUS_INTEREST.value));
|
||||
final PostLoanProductsRequest loanProductsRequestLP2AdvPaymentInstallmentFeePercentAmountPlusInterestCharges = loanProductsRequestFactory
|
||||
.defaultLoanProductsRequestLP2()//
|
||||
.name(name119)//
|
||||
.charges(chargesInstallmentFeePercentAmountPlusInterest)//
|
||||
.enableDownPayment(false)//
|
||||
.enableAutoRepaymentForDownPayment(null)//
|
||||
.disbursedAmountPercentageForDownPayment(null)//
|
||||
.transactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION.getValue())//
|
||||
.loanScheduleType("PROGRESSIVE") //
|
||||
.loanScheduleProcessingType("HORIZONTAL")//
|
||||
.interestRateFrequencyType(3)//
|
||||
.maxInterestRatePerPeriod(10.0)//
|
||||
.paymentAllocation(List.of(//
|
||||
createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"), //
|
||||
createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT"), //
|
||||
createPaymentAllocation("MERCHANT_ISSUED_REFUND", "REAMORTIZATION"), //
|
||||
createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT")));//
|
||||
final Response<PostLoanProductsResponse> responseLoanProductsRequestLP2AdvPaymentInstallmentFeePercentAmountPlusInterestCharges = loanProductsApi
|
||||
.createLoanProduct(loanProductsRequestLP2AdvPaymentInstallmentFeePercentAmountPlusInterestCharges).execute();
|
||||
TestContext.INSTANCE.set(
|
||||
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_INSTALLMENT_FEE_PERCENT_AMOUNT_INTEREST_CHARGES,
|
||||
responseLoanProductsRequestLP2AdvPaymentInstallmentFeePercentAmountPlusInterestCharges);
|
||||
|
||||
// LP2 with progressive loan schedule + horizontal
|
||||
// charges - Installment Fee All
|
||||
// (LP2_ADV_PYMNT_INTEREST_DAILY_INSTALLMENT_FEE_ALL_CHARGES)
|
||||
final String name120 = DefaultLoanProduct.LP2_ADV_PYMNT_INTEREST_DAILY_INSTALLMENT_FEE_ALL_CHARGES.getName();
|
||||
final List<LoanProductChargeData> chargesInstallmentFeeAll = new ArrayList<>();
|
||||
chargesInstallmentFeeAll.add(new LoanProductChargeData().id(ChargeProductType.LOAN_INSTALLMENT_FEE_FLAT.value));
|
||||
chargesInstallmentFeeAll.add(new LoanProductChargeData().id(ChargeProductType.LOAN_INSTALLMENT_FEE_PERCENTAGE_AMOUNT.value));
|
||||
chargesInstallmentFeeAll.add(new LoanProductChargeData().id(ChargeProductType.LOAN_INSTALLMENT_FEE_PERCENTAGE_INTEREST.value));
|
||||
chargesInstallmentFeeAll
|
||||
.add(new LoanProductChargeData().id(ChargeProductType.LOAN_INSTALLMENT_FEE_PERCENTAGE_AMOUNT_PLUS_INTEREST.value));
|
||||
final PostLoanProductsRequest loanProductsRequestLP2AdvPaymentInstallmentFeeAllCharges = loanProductsRequestFactory
|
||||
.defaultLoanProductsRequestLP2()//
|
||||
.name(name120)//
|
||||
.charges(chargesInstallmentFeeAll)//
|
||||
.enableDownPayment(false)//
|
||||
.enableAutoRepaymentForDownPayment(null)//
|
||||
.disbursedAmountPercentageForDownPayment(null)//
|
||||
.transactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION.getValue())//
|
||||
.loanScheduleType("PROGRESSIVE") //
|
||||
.loanScheduleProcessingType("HORIZONTAL")//
|
||||
.interestRateFrequencyType(3)//
|
||||
.maxInterestRatePerPeriod(10.0)//
|
||||
.paymentAllocation(List.of(//
|
||||
createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"), //
|
||||
createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT"), //
|
||||
createPaymentAllocation("MERCHANT_ISSUED_REFUND", "REAMORTIZATION"), //
|
||||
createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT")));//
|
||||
final Response<PostLoanProductsResponse> responseLoanProductsRequestLP2AdvPaymentInstallmentFeeAllCharges = loanProductsApi
|
||||
.createLoanProduct(loanProductsRequestLP2AdvPaymentInstallmentFeeAllCharges).execute();
|
||||
TestContext.INSTANCE.set(
|
||||
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_INSTALLMENT_FEE_ALL_CHARGES,
|
||||
responseLoanProductsRequestLP2AdvPaymentInstallmentFeeAllCharges);
|
||||
|
||||
// LP2 with progressive loan schedule + horizontal + multidisbursal
|
||||
// charges - Installment Fee Flat + Interest %
|
||||
// (LP2_ADV_PYMNT_INTEREST_DAILY_INSTALLMENT_FEE_FLAT_INTEREST_CHARGES_TRANCHE)
|
||||
final String name121 = DefaultLoanProduct.LP2_ADV_PYMNT_INTEREST_DAILY_INSTALLMENT_FEE_FLAT_INTEREST_CHARGES_TRANCHE.getName();
|
||||
final List<LoanProductChargeData> chargesInstallmentFeeFlatPlusInterest = new ArrayList<>();
|
||||
chargesInstallmentFeeFlatPlusInterest.add(new LoanProductChargeData().id(ChargeProductType.LOAN_INSTALLMENT_FEE_FLAT.value));
|
||||
chargesInstallmentFeeFlatPlusInterest
|
||||
.add(new LoanProductChargeData().id(ChargeProductType.LOAN_INSTALLMENT_FEE_PERCENTAGE_INTEREST.value));
|
||||
final PostLoanProductsRequest loanProductsRequestLP2AdvPaymentInstallmentFeeFlatPlusInterestChargesMultiDisburse = loanProductsRequestLP2AdvPaymentInstallmentFeePercentInterestCharges//
|
||||
.name(name121)//
|
||||
.charges(chargesInstallmentFeeFlatPlusInterest)//
|
||||
.shortName(Utils.randomNameGenerator(SHORT_NAME_PREFIX_EMI, 3))//
|
||||
.disallowExpectedDisbursements(true)//
|
||||
.maxTrancheCount(10)//
|
||||
.outstandingLoanBalance(10000.0);//
|
||||
final Response<PostLoanProductsResponse> responseLoanProductsRequestLP2AdvPaymentInstallmentFeeFlatPlusInterestChargesMultiDisburse = loanProductsApi
|
||||
.createLoanProduct(loanProductsRequestLP2AdvPaymentInstallmentFeeFlatPlusInterestChargesMultiDisburse).execute();
|
||||
TestContext.INSTANCE.set(
|
||||
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_INSTALLMENT_FEE_FLAT_INTEREST_CHARGES_TRANCHE,
|
||||
responseLoanProductsRequestLP2AdvPaymentInstallmentFeeFlatPlusInterestChargesMultiDisburse);
|
||||
}
|
||||
|
||||
public static AdvancedPaymentData createPaymentAllocation(String transactionType, String futureInstallmentAllocationRule,
|
||||
|
||||
+52
-3
@@ -90,7 +90,7 @@ public class LoanChargeStepDef extends AbstractStepDef {
|
||||
Long chargeTypeId = chargeProductType.getValue();
|
||||
if (chargeTypeId.equals(ChargeProductType.LOAN_DISBURSEMENT_PERCENTAGE_FEE.getValue())
|
||||
|| chargeTypeId.equals(ChargeProductType.LOAN_TRANCHE_DISBURSEMENT_PERCENTAGE_FEE.getValue())
|
||||
|| chargeTypeId.equals(ChargeProductType.LOAN_INSTALLMENT_PERCENTAGE_FEE.getValue())) {
|
||||
|| chargeTypeId.equals(ChargeProductType.LOAN_INSTALLMENT_FEE_PERCENTAGE_AMOUNT_PLUS_INTEREST.getValue())) {
|
||||
throw new IllegalStateException(String.format("The requested %s charge is NOT due date type, cannot be used here", chargeType));
|
||||
}
|
||||
|
||||
@@ -115,7 +115,8 @@ public class LoanChargeStepDef extends AbstractStepDef {
|
||||
Long chargeTypeId = chargeProductType.getValue();
|
||||
if (!chargeTypeId.equals(ChargeProductType.LOAN_DISBURSEMENT_PERCENTAGE_FEE.getValue())
|
||||
&& !chargeTypeId.equals(ChargeProductType.LOAN_TRANCHE_DISBURSEMENT_PERCENTAGE_FEE.getValue())
|
||||
&& !chargeTypeId.equals(ChargeProductType.LOAN_INSTALLMENT_PERCENTAGE_FEE.getValue())) {
|
||||
&& !chargeTypeId.equals(ChargeProductType.LOAN_INSTALLMENT_FEE_PERCENTAGE_AMOUNT.getValue())
|
||||
&& !chargeTypeId.equals(ChargeProductType.LOAN_INSTALLMENT_FEE_PERCENTAGE_AMOUNT_PLUS_INTEREST.getValue())) {
|
||||
throw new IllegalStateException(String.format("The requested %s charge is due date type, cannot be used here", chargeType));
|
||||
}
|
||||
|
||||
@@ -128,6 +129,54 @@ public class LoanChargeStepDef extends AbstractStepDef {
|
||||
testContext().set(TestContextKey.ADD_DUE_DATE_CHARGE_RESPONSE, loanChargeResponse);
|
||||
}
|
||||
|
||||
@When("Admin adds {string} installment charge with {double} amount")
|
||||
public void addInstallmentFeeCharge(final String chargeType, final double amount) throws IOException {
|
||||
final Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
|
||||
assert loanResponse.body() != null;
|
||||
final long loanId = loanResponse.body().getLoanId();
|
||||
|
||||
final ChargeProductType chargeProductType = ChargeProductType.valueOf(chargeType);
|
||||
final Long chargeTypeId = chargeProductType.getValue();
|
||||
if (!chargeTypeId.equals(ChargeProductType.LOAN_INSTALLMENT_FEE_FLAT.getValue())
|
||||
&& !chargeTypeId.equals(ChargeProductType.LOAN_INSTALLMENT_FEE_PERCENTAGE_AMOUNT.getValue())
|
||||
&& !chargeTypeId.equals(ChargeProductType.LOAN_INSTALLMENT_FEE_PERCENTAGE_INTEREST.getValue())
|
||||
&& !chargeTypeId.equals(ChargeProductType.LOAN_INSTALLMENT_FEE_PERCENTAGE_AMOUNT_PLUS_INTEREST.getValue())) {
|
||||
throw new IllegalStateException(
|
||||
String.format("The requested %s charge is not installment fee type, cannot be used here", chargeType));
|
||||
}
|
||||
|
||||
final PostLoansLoanIdChargesRequest loanIdChargesRequest = LoanChargeRequestFactory.defaultLoanChargeRequest()
|
||||
.chargeId(chargeTypeId).amount(amount);
|
||||
|
||||
final Response<PostLoansLoanIdChargesResponse> loanChargeResponse = loanChargesApi
|
||||
.executeLoanCharge(loanId, loanIdChargesRequest, "").execute();
|
||||
ErrorHelper.checkSuccessfulApiCall(loanChargeResponse);
|
||||
testContext().set(TestContextKey.ADD_INSTALLMENT_FEE_CHARGE_RESPONSE, loanChargeResponse);
|
||||
}
|
||||
|
||||
@Then("Admin fails to add {string} installment charge with {double} amount because of wrong charge calculation type")
|
||||
public void addInstallmentFeeChargeFails(final String chargeType, final double amount) throws IOException {
|
||||
final Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
|
||||
assert loanResponse.body() != null;
|
||||
|
||||
final long loanId = loanResponse.body().getLoanId();
|
||||
final ChargeProductType chargeProductType = ChargeProductType.valueOf(chargeType);
|
||||
final Long chargeTypeId = chargeProductType.getValue();
|
||||
|
||||
final PostLoansLoanIdChargesRequest loanIdChargesRequest = LoanChargeRequestFactory.defaultLoanChargeRequest()
|
||||
.chargeId(chargeTypeId).amount(amount);
|
||||
|
||||
final Response<PostLoansLoanIdChargesResponse> loanChargeResponse = loanChargesApi
|
||||
.executeLoanCharge(loanId, loanIdChargesRequest, "").execute();
|
||||
testContext().set(TestContextKey.ADD_INSTALLMENT_FEE_CHARGE_RESPONSE, loanChargeResponse);
|
||||
final ErrorResponse errorDetails = ErrorResponse.from(loanChargeResponse);
|
||||
assertThat(errorDetails.getHttpStatusCode()).isEqualTo(400);
|
||||
assertThat(errorDetails.getSingleError().getDeveloperMessage())
|
||||
.isEqualTo(chargeProductType.equals(ChargeProductType.LOAN_INSTALLMENT_FEE_PERCENTAGE_INTEREST)
|
||||
? ErrorMessageHelper.addInstallmentFeeInterestPercentageChargeFailure()
|
||||
: ErrorMessageHelper.addInstallmentFeePrincipalPercentageChargeFailure());
|
||||
}
|
||||
|
||||
@Then("Admin is not able to add {string} due date charge with {string} due date and {double} EUR transaction amount because the of charged-off account")
|
||||
public void addChargeDueDateOnChargedOff(String chargeType, String transactionDate, double transactionAmount) throws IOException {
|
||||
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
|
||||
@@ -284,7 +333,7 @@ public class LoanChargeStepDef extends AbstractStepDef {
|
||||
Long chargeTypeId = chargeProductType.getValue();
|
||||
if (chargeTypeId.equals(ChargeProductType.LOAN_DISBURSEMENT_PERCENTAGE_FEE.getValue())
|
||||
|| chargeTypeId.equals(ChargeProductType.LOAN_TRANCHE_DISBURSEMENT_PERCENTAGE_FEE.getValue())
|
||||
|| chargeTypeId.equals(ChargeProductType.LOAN_INSTALLMENT_PERCENTAGE_FEE.getValue())) {
|
||||
|| chargeTypeId.equals(ChargeProductType.LOAN_INSTALLMENT_FEE_PERCENTAGE_AMOUNT_PLUS_INTEREST.getValue())) {
|
||||
throw new IllegalStateException(String.format("The requested %s charge is NOT due date type, cannot be used here", chargeType));
|
||||
}
|
||||
|
||||
|
||||
+92
@@ -82,6 +82,7 @@ import org.apache.fineract.client.models.GetLoansLoanIdTransactions;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdTransactionsResponse;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdTransactionsTransactionIdResponse;
|
||||
import org.apache.fineract.client.models.IsCatchUpRunningDTO;
|
||||
import org.apache.fineract.client.models.LoanProductChargeData;
|
||||
import org.apache.fineract.client.models.OldestCOBProcessedLoanDTO;
|
||||
import org.apache.fineract.client.models.PaymentAllocationOrder;
|
||||
import org.apache.fineract.client.models.PostAddAndDeleteDisbursementDetailRequest;
|
||||
@@ -258,6 +259,20 @@ public class LoanStepDef extends AbstractStepDef {
|
||||
eventCheckHelper.createLoanEventCheck(response);
|
||||
}
|
||||
|
||||
@When("Admin creates a new default Progressive Loan with date: {string}")
|
||||
public void createProgressiveLoanWithDate(final String date) throws IOException {
|
||||
final Response<PostClientsResponse> clientResponse = testContext().get(TestContextKey.CLIENT_CREATE_RESPONSE);
|
||||
final Long clientId = clientResponse.body().getClientId();
|
||||
final PostLoansRequest loansRequest = loanRequestFactory.defaultProgressiveLoansRequest(clientId).submittedOnDate(date)
|
||||
.expectedDisbursementDate(date);
|
||||
|
||||
final Response<PostLoansResponse> response = loansApi.calculateLoanScheduleOrSubmitLoanApplication(loansRequest, "").execute();
|
||||
testContext().set(TestContextKey.LOAN_CREATE_RESPONSE, response);
|
||||
ErrorHelper.checkSuccessfulApiCall(response);
|
||||
|
||||
eventCheckHelper.createLoanEventCheck(response);
|
||||
}
|
||||
|
||||
@When("Admin crates a second default loan with date: {string}")
|
||||
public void createSecondLoanWithDate(String date) throws IOException {
|
||||
Response<PostClientsResponse> clientResponse = testContext().get(TestContextKey.CLIENT_CREATE_RESPONSE);
|
||||
@@ -483,6 +498,12 @@ public class LoanStepDef extends AbstractStepDef {
|
||||
createCustomizedLoan(data.get(1), false);
|
||||
}
|
||||
|
||||
@When("Admin creates a fully customized loan with loan product`s charges and following data:")
|
||||
public void createFullyCustomizedLoanWithProductCharges(final DataTable table) throws IOException {
|
||||
final List<List<String>> data = table.asLists();
|
||||
createCustomizedLoanWithProductCharges(data.get(1));
|
||||
}
|
||||
|
||||
@When("Admin creates a fully customized loan with emi and the following data:")
|
||||
public void createFullyCustomizedLoanWithEmi(final DataTable table) throws IOException {
|
||||
final List<List<String>> data = table.asLists();
|
||||
@@ -3279,6 +3300,77 @@ public class LoanStepDef extends AbstractStepDef {
|
||||
eventCheckHelper.createLoanEventCheck(response);
|
||||
}
|
||||
|
||||
private void createCustomizedLoanWithProductCharges(final List<String> loanData) throws IOException {
|
||||
final String loanProduct = loanData.get(0);
|
||||
final String submitDate = loanData.get(1);
|
||||
final String principal = loanData.get(2);
|
||||
final BigDecimal interestRate = new BigDecimal(loanData.get(3));
|
||||
final String interestTypeStr = loanData.get(4);
|
||||
final String interestCalculationPeriodStr = loanData.get(5);
|
||||
final String amortizationTypeStr = loanData.get(6);
|
||||
final Integer loanTermFrequency = Integer.valueOf(loanData.get(7));
|
||||
final String loanTermFrequencyType = loanData.get(8);
|
||||
final Integer repaymentFrequency = Integer.valueOf(loanData.get(9));
|
||||
final String repaymentFrequencyTypeStr = loanData.get(10);
|
||||
final Integer numberOfRepayments = Integer.valueOf(loanData.get(11));
|
||||
final Integer graceOnPrincipalPayment = Integer.valueOf(loanData.get(12));
|
||||
final Integer graceOnInterestPayment = Integer.valueOf(loanData.get(13));
|
||||
final Integer graceOnInterestCharged = Integer.valueOf(loanData.get(14));
|
||||
final String transactionProcessingStrategyCode = loanData.get(15);
|
||||
|
||||
final Response<PostClientsResponse> clientResponse = testContext().get(TestContextKey.CLIENT_CREATE_RESPONSE);
|
||||
final Long clientId = clientResponse.body().getClientId();
|
||||
|
||||
final DefaultLoanProduct product = DefaultLoanProduct.valueOf(loanProduct);
|
||||
final Long loanProductId = loanProductResolver.resolve(product);
|
||||
final Response<GetLoanProductsProductIdResponse> loanProductDetails = loanProductsApi.retrieveLoanProductDetails(loanProductId)
|
||||
.execute();
|
||||
|
||||
final List<PostLoansRequestChargeData> loanCharges = new ArrayList<>();
|
||||
|
||||
assert loanProductDetails.body() != null;
|
||||
if (loanProductDetails.body().getCharges() != null) {
|
||||
for (final LoanProductChargeData chargeData : loanProductDetails.body().getCharges()) {
|
||||
loanCharges.add(new PostLoansRequestChargeData().chargeId(chargeData.getId()).amount(chargeData.getAmount()));
|
||||
}
|
||||
}
|
||||
|
||||
final LoanTermFrequencyType termFrequencyType = LoanTermFrequencyType.valueOf(loanTermFrequencyType);
|
||||
final Integer loanTermFrequencyTypeValue = termFrequencyType.getValue();
|
||||
|
||||
final RepaymentFrequencyType repaymentFrequencyType = RepaymentFrequencyType.valueOf(repaymentFrequencyTypeStr);
|
||||
final Integer repaymentFrequencyTypeValue = repaymentFrequencyType.getValue();
|
||||
|
||||
final InterestType interestType = InterestType.valueOf(interestTypeStr);
|
||||
final Integer interestTypeValue = interestType.getValue();
|
||||
|
||||
final InterestCalculationPeriodTime interestCalculationPeriod = InterestCalculationPeriodTime.valueOf(interestCalculationPeriodStr);
|
||||
final Integer interestCalculationPeriodValue = interestCalculationPeriod.getValue();
|
||||
|
||||
final AmortizationType amortizationType = AmortizationType.valueOf(amortizationTypeStr);
|
||||
final Integer amortizationTypeValue = amortizationType.getValue();
|
||||
|
||||
final TransactionProcessingStrategyCode processingStrategyCode = TransactionProcessingStrategyCode
|
||||
.valueOf(transactionProcessingStrategyCode);
|
||||
final String transactionProcessingStrategyCodeValue = processingStrategyCode.getValue();
|
||||
|
||||
final PostLoansRequest loansRequest = loanRequestFactory.defaultLoansRequest(clientId).productId(loanProductId)
|
||||
.principal(new BigDecimal(principal)).interestRatePerPeriod(interestRate).interestType(interestTypeValue)
|
||||
.interestCalculationPeriodType(interestCalculationPeriodValue).amortizationType(amortizationTypeValue)
|
||||
.loanTermFrequency(loanTermFrequency).loanTermFrequencyType(loanTermFrequencyTypeValue)
|
||||
.numberOfRepayments(numberOfRepayments).repaymentEvery(repaymentFrequency)
|
||||
.repaymentFrequencyType(repaymentFrequencyTypeValue).submittedOnDate(submitDate).expectedDisbursementDate(submitDate)
|
||||
.graceOnPrincipalPayment(graceOnPrincipalPayment).graceOnInterestPayment(graceOnInterestPayment)
|
||||
.graceOnInterestPayment(graceOnInterestCharged).transactionProcessingStrategyCode(transactionProcessingStrategyCodeValue)
|
||||
.charges(loanCharges);
|
||||
|
||||
final Response<PostLoansResponse> response = loansApi.calculateLoanScheduleOrSubmitLoanApplication(loansRequest, "").execute();
|
||||
testContext().set(TestContextKey.LOAN_CREATE_RESPONSE, response);
|
||||
ErrorHelper.checkSuccessfulApiCall(response);
|
||||
|
||||
eventCheckHelper.createLoanEventCheck(response);
|
||||
}
|
||||
|
||||
public void createFullyCustomizedLoanWithInterestRateFrequency(final List<String> loanData) throws IOException {
|
||||
final String loanProduct = loanData.get(0);
|
||||
final String submitDate = loanData.get(1);
|
||||
|
||||
+11
-1
@@ -30,6 +30,7 @@ public abstract class TestContextKey {
|
||||
public static final String LOAN_CREATE_SECOND_LOAN_RESPONSE = "loanCreateSecondLoanResponse";
|
||||
public static final String LOAN_MODIFY_RESPONSE = "loanModifyResponse";
|
||||
public static final String ADD_DUE_DATE_CHARGE_RESPONSE = "addDueDateChargeResponse";
|
||||
public static final String ADD_INSTALLMENT_FEE_CHARGE_RESPONSE = "addInstallmentFeeChargeResponse";
|
||||
public static final String ADD_PROCESSING_FEE_RESPONSE = "addProcessingFeeResponse";
|
||||
public static final String ADD_NSF_FEE_RESPONSE = "addNsfFeeResponse";
|
||||
public static final String WAIVE_CHARGE_RESPONSE = "waiveChargeResponse";
|
||||
@@ -178,7 +179,10 @@ public abstract class TestContextKey {
|
||||
public static final String CHARGE_FOR_LOAN_DISBURSEMENET_FEE_CREATE_RESPONSE = "ChargeForLoanDisbursementCreateResponse";
|
||||
public static final String CHARGE_FOR_LOAN_TRANCHE_DISBURSEMENT_PERCENT_CREATE_RESPONSE = "ChargeForLoanTrancheDisbursementPercentCreateResponse";
|
||||
public static final String CHARGE_FOR_LOAN_TRANCHE_DISBURSEMENT_CHARGE_PERCENT_CREATE_RESPONSE = "ChargeForLoanTrancheDisbursementChargePercentCreateResponse";
|
||||
public static final String CHARGE_FOR_LOAN_INSTALLMENT_FEE_CREATE_RESPONSE = "ChargeForLoanInstallmentCreateResponse";
|
||||
public static final String CHARGE_FOR_LOAN_INSTALLMENT_FEE_PERCENTAGE_AMOUNT_PLUS_INTEREST_CREATE_RESPONSE = "ChargeForLoanInstallmentFeePercentageAmountPlusInterestCreateResponse";
|
||||
public static final String CHARGE_FOR_LOAN_INSTALLMENT_FEE_PERCENTAGE_AMOUNT_CREATE_RESPONSE = "ChargeForLoanInstallmentFeePercentageAmountCreateResponse";
|
||||
public static final String CHARGE_FOR_LOAN_INSTALLMENT_FEE_PERCENTAGE_INTEREST_CREATE_RESPONSE = "ChargeForLoanInstallmentFeePercentageInterestCreateResponse";
|
||||
public static final String CHARGE_FOR_LOAN_INSTALLMENT_FEE_FLAT_CREATE_RESPONSE = "ChargeForLoanInstallmentFeeFlatCreateResponse";
|
||||
public static final String CHARGE_FOR_CLIENT_FIXED_FEE_CREATE_RESPONSE = "ChargeForClientFixedFeeCreateResponse";
|
||||
public static final String CHARGE_FOR_LOAN_DISBURSEMENT_CHARGE_CREATE_RESPONSE = "ChargeForLoanDisbursementChargeCreateResponse";
|
||||
public static final String LOAN_RESPONSE = "loanResponse";
|
||||
@@ -235,4 +239,10 @@ public abstract class TestContextKey {
|
||||
public static final String LOAN_BUY_DOWN_FEE_ADJUSTMENT_RESPONSE = "loanBuyDownFeeAdjustmentResponse";
|
||||
public static final String MANUAL_JOURNAL_ENTRIES_REQUEST = "manualJournalEntriesRequest";
|
||||
public static final String MANUAL_JOURNAL_ENTRIES_RESPONSE = "manualJournalEntriesResponse";
|
||||
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALC_DAILY_INSTALLMENT_FEE_FLAT_CHARGES = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyInterestRecalculationInstallmentFeeFlatCharges";
|
||||
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_INSTALLMENT_FEE_PERCENT_AMOUNT_CHARGES = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyInstallmentFeePercentageAmountCharges";
|
||||
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_INSTALLMENT_FEE_PERCENT_INTEREST_CHARGES = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyInstallmentFeePercentageInterestCharges";
|
||||
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_INSTALLMENT_FEE_PERCENT_AMOUNT_INTEREST_CHARGES = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyInstallmentFeeAmountPlusPercentageInterestCharges";
|
||||
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_INSTALLMENT_FEE_ALL_CHARGES = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyInstallmentFeeAllCharges";
|
||||
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_INSTALLMENT_FEE_FLAT_INTEREST_CHARGES_TRANCHE = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyInstallmentFeeFlatPlusPercentageInterestChargesMultidisbursal";
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -1517,7 +1517,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
return repaymetsForAdjust;
|
||||
}
|
||||
|
||||
public int fetchNumberOfInstallmensAfterExceptions() {
|
||||
public int fetchNumberOfInstallmentsAfterExceptions() {
|
||||
if (!this.repaymentScheduleInstallments.isEmpty()) {
|
||||
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
|
||||
int numberOfInstallments = 0;
|
||||
|
||||
+4
@@ -721,4 +721,8 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
.chargePaymentMode(chargePaymentModeData).paid(paid).waived(waived).loanId(loan.getId()).minCap(minCap).maxCap(maxCap)
|
||||
.installmentChargeData(loanInstallmentChargeDataList).externalId(externalId).build();
|
||||
}
|
||||
|
||||
public boolean hasInstallmentFor(final LoanRepaymentScheduleInstallment installment) {
|
||||
return this.getInstallmentLoanCharge(installment.getInstallmentNumber()) != null;
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -51,6 +51,7 @@ import org.apache.fineract.portfolio.charge.exception.LoanChargeNotFoundExceptio
|
||||
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
|
||||
import org.apache.fineract.portfolio.loanproduct.exception.LinkedAccountRequiredException;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -280,6 +281,8 @@ public final class LoanChargeApiJsonValidator {
|
||||
case PERCENT_OF_INTEREST:
|
||||
if (loanCharge.isSpecifiedDueDate()) {
|
||||
errorcode = "specific." + LoanApiConstants.LOAN_CHARGE_CAN_NOT_BE_ADDED_WITH_INTEREST_CALCULATION_TYPE;
|
||||
} else if (loanCharge.isInstalmentFee() && loanCharge.getLoan().isProgressiveSchedule()) {
|
||||
errorcode = "installment." + LoanApiConstants.LOAN_CHARGE_CAN_NOT_BE_ADDED_WITH_INTEREST_CALCULATION_TYPE;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -438,6 +441,9 @@ public final class LoanChargeApiJsonValidator {
|
||||
case PERCENT_OF_INTEREST:
|
||||
if (chargeTime.isSpecifiedDueDate()) {
|
||||
errorcode = "specific." + LoanApiConstants.LOAN_CHARGE_CAN_NOT_BE_ADDED_WITH_INTEREST_CALCULATION_TYPE;
|
||||
} else if (chargeTime.isInstalmentFee()
|
||||
&& loanProduct.getLoanProductRelatedDetail().getLoanScheduleType().equals(LoanScheduleType.PROGRESSIVE)) {
|
||||
errorcode = "installment." + LoanApiConstants.LOAN_CHARGE_CAN_NOT_BE_ADDED_WITH_INTEREST_CALCULATION_TYPE;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
+185
-34
@@ -22,7 +22,7 @@ import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
@@ -75,6 +75,14 @@ public class LoanChargeService {
|
||||
loan.updateSummaryWithTotalFeeChargesDueAtDisbursement(loan.deriveSumTotalOfChargesDueAtDisbursement());
|
||||
}
|
||||
|
||||
public void recalculateParticularChargesAfterTransactionOccurs(final Loan loan, final List<LoanCharge> loanCharges,
|
||||
final LocalDate transactionDate) {
|
||||
for (final LoanCharge loanCharge : loanCharges) {
|
||||
recalculateLoanCharge(loan, loanCharge, 0, transactionDate);
|
||||
}
|
||||
loan.updateSummaryWithTotalFeeChargesDueAtDisbursement(loan.deriveSumTotalOfChargesDueAtDisbursement());
|
||||
}
|
||||
|
||||
public void recalculateLoanCharge(final Loan loan, final LoanCharge loanCharge, final int penaltyWaitPeriod) {
|
||||
BigDecimal amount = BigDecimal.ZERO;
|
||||
BigDecimal chargeAmt;
|
||||
@@ -94,12 +102,38 @@ public class LoanChargeService {
|
||||
}
|
||||
if (loanCharge.isActive()) {
|
||||
clearLoanInstallmentChargesBeforeRegeneration(loanCharge);
|
||||
update(loanCharge, chargeAmt, loanCharge.getDueLocalDate(), amount, loan.fetchNumberOfInstallmensAfterExceptions(),
|
||||
update(loanCharge, chargeAmt, loanCharge.getDueLocalDate(), amount, loan.fetchNumberOfInstallmentsAfterExceptions(),
|
||||
totalChargeAmt);
|
||||
loanChargeValidator.validateChargeHasValidSpecifiedDateIfApplicable(loan, loanCharge, loan.getDisbursementDate());
|
||||
}
|
||||
}
|
||||
|
||||
public void recalculateLoanCharge(final Loan loan, final LoanCharge loanCharge, final int penaltyWaitPeriod,
|
||||
final LocalDate transactionDate) {
|
||||
BigDecimal amount = BigDecimal.ZERO;
|
||||
BigDecimal chargeAmt;
|
||||
BigDecimal totalChargeAmt = BigDecimal.ZERO;
|
||||
if (loanCharge.getChargeCalculation().isPercentageBased()) {
|
||||
if (loanCharge.isOverdueInstallmentCharge()) {
|
||||
amount = calculateOverdueAmountPercentageAppliedTo(loan, loanCharge, penaltyWaitPeriod);
|
||||
} else {
|
||||
amount = calculateAmountPercentageAppliedTo(loan, loanCharge);
|
||||
}
|
||||
chargeAmt = loanCharge.getPercentage();
|
||||
if (loanCharge.isInstalmentFee()) {
|
||||
totalChargeAmt = calculatePerInstallmentChargeAmount(loan, loanCharge);
|
||||
}
|
||||
} else {
|
||||
chargeAmt = loanCharge.amountOrPercentage();
|
||||
}
|
||||
if (loanCharge.isActive()) {
|
||||
clearLoanInstallmentChargesBeforeRegeneration(loanCharge);
|
||||
update(loanCharge, chargeAmt, loanCharge.getDueLocalDate(), amount, loan.fetchNumberOfInstallmentsAfterExceptions(),
|
||||
totalChargeAmt, transactionDate);
|
||||
loanChargeValidator.validateChargeHasValidSpecifiedDateIfApplicable(loan, loanCharge, loan.getDisbursementDate());
|
||||
}
|
||||
}
|
||||
|
||||
public void makeChargePayment(final Loan loan, final Long chargeId, final List<Long> existingTransactionIds,
|
||||
final List<Long> existingReversedTransactionIds, final LoanTransaction paymentTransaction, final Integer installmentNumber) {
|
||||
loanChargeValidator.validateChargePaymentNotInFuture(paymentTransaction);
|
||||
@@ -191,7 +225,8 @@ public class LoanChargeService {
|
||||
} else {
|
||||
chargeAmt = loanCharge.amountOrPercentage();
|
||||
}
|
||||
update(loanCharge, chargeAmt, loanCharge.getDueLocalDate(), amount, loan.fetchNumberOfInstallmensAfterExceptions(), totalChargeAmt);
|
||||
update(loanCharge, chargeAmt, loanCharge.getDueLocalDate(), amount, loan.fetchNumberOfInstallmentsAfterExceptions(),
|
||||
totalChargeAmt);
|
||||
|
||||
// NOTE: must add new loan charge to set of loan charges before
|
||||
// reprocessing the repayment schedule.
|
||||
@@ -275,7 +310,7 @@ public class LoanChargeService {
|
||||
chargeAmt = loanCharge.amountOrPercentage();
|
||||
}
|
||||
if (charge != null) {
|
||||
update(charge, chargeAmt, loanCharge.getDueLocalDate(), amount, loan.fetchNumberOfInstallmensAfterExceptions(),
|
||||
update(charge, chargeAmt, loanCharge.getDueLocalDate(), amount, loan.fetchNumberOfInstallmentsAfterExceptions(),
|
||||
totalChargeAmt);
|
||||
}
|
||||
}
|
||||
@@ -325,7 +360,7 @@ public class LoanChargeService {
|
||||
case FLAT:
|
||||
if (loanCharge.isInstalmentFee()) {
|
||||
loanCharge.setAmount(
|
||||
newValue.multiply(BigDecimal.valueOf(loanCharge.getLoan().fetchNumberOfInstallmensAfterExceptions())));
|
||||
newValue.multiply(BigDecimal.valueOf(loanCharge.getLoan().fetchNumberOfInstallmentsAfterExceptions())));
|
||||
} else {
|
||||
loanCharge.setAmount(newValue);
|
||||
}
|
||||
@@ -375,7 +410,7 @@ public class LoanChargeService {
|
||||
loanCharge.setAmountPaid(null);
|
||||
if (loanCharge.isInstalmentFee()) {
|
||||
if (numberOfRepayments == null) {
|
||||
numberOfRepayments = loanCharge.getLoan().fetchNumberOfInstallmensAfterExceptions();
|
||||
numberOfRepayments = loanCharge.getLoan().fetchNumberOfInstallmentsAfterExceptions();
|
||||
}
|
||||
loanCharge.setAmount(chargeAmount.multiply(BigDecimal.valueOf(numberOfRepayments)));
|
||||
} else {
|
||||
@@ -574,40 +609,47 @@ public class LoanChargeService {
|
||||
return calculatePerInstallmentChargeAmount(loan, loanCharge.getChargeCalculation(), loanCharge.getPercentage());
|
||||
}
|
||||
|
||||
private void updateInstallmentCharges(final LoanCharge loanCharge) {
|
||||
final Collection<LoanInstallmentCharge> remove = new HashSet<>();
|
||||
public void updateInstallmentCharges(final LoanCharge loanCharge) {
|
||||
final List<LoanInstallmentCharge> newChargeInstallments = generateInstallmentLoanCharges(loanCharge.getLoan(), loanCharge);
|
||||
|
||||
if (loanCharge.getLoanInstallmentCharge().isEmpty()) {
|
||||
loanCharge.getLoanInstallmentCharge().addAll(newChargeInstallments);
|
||||
} else {
|
||||
int index = 0;
|
||||
final List<LoanInstallmentCharge> oldChargeInstallments = new ArrayList<>();
|
||||
if (loanCharge.getLoanInstallmentCharge() != null && !loanCharge.getLoanInstallmentCharge().isEmpty()) {
|
||||
oldChargeInstallments.addAll(loanCharge.getLoanInstallmentCharge());
|
||||
}
|
||||
Collections.sort(oldChargeInstallments);
|
||||
final LoanInstallmentCharge[] loanChargePerInstallmentArray = newChargeInstallments
|
||||
.toArray(new LoanInstallmentCharge[newChargeInstallments.size()]);
|
||||
for (final LoanInstallmentCharge chargePerInstallment : oldChargeInstallments) {
|
||||
if (index == loanChargePerInstallmentArray.length) {
|
||||
remove.add(chargePerInstallment);
|
||||
chargePerInstallment.getInstallment().getInstallmentCharges().remove(chargePerInstallment);
|
||||
} else {
|
||||
LoanInstallmentCharge newLoanInstallmentCharge = loanChargePerInstallmentArray[index++];
|
||||
newLoanInstallmentCharge.getInstallment().getInstallmentCharges().remove(newLoanInstallmentCharge);
|
||||
chargePerInstallment.copyFrom(newLoanInstallmentCharge);
|
||||
final Map<Integer, LoanInstallmentCharge> newChargeMap = new HashMap<>();
|
||||
for (final LoanInstallmentCharge newCharge : newChargeInstallments) {
|
||||
if (newCharge.getInstallment() != null && newCharge.getInstallment().getInstallmentNumber() != null) {
|
||||
newChargeMap.put(newCharge.getInstallment().getInstallmentNumber(), newCharge);
|
||||
}
|
||||
}
|
||||
loanCharge.getLoanInstallmentCharge().removeAll(remove);
|
||||
while (index < loanChargePerInstallmentArray.length) {
|
||||
loanCharge.getLoanInstallmentCharge().add(loanChargePerInstallmentArray[index++]);
|
||||
|
||||
final Collection<LoanInstallmentCharge> chargesToRemoveFromLoanCharge = new HashSet<>();
|
||||
final Collection<LoanInstallmentCharge> chargesToAddIntoLoanCharge = new HashSet<>();
|
||||
|
||||
for (final LoanInstallmentCharge oldCharge : loanCharge.getLoanInstallmentCharge()) {
|
||||
final Integer oldInstallmentNumber = oldCharge.getInstallment().getInstallmentNumber();
|
||||
|
||||
if (newChargeMap.containsKey(oldInstallmentNumber)) {
|
||||
chargesToRemoveFromLoanCharge.add(oldCharge);
|
||||
oldCharge.getInstallment().getInstallmentCharges().remove(oldCharge);
|
||||
chargesToAddIntoLoanCharge.add(newChargeMap.get(oldInstallmentNumber));
|
||||
newChargeMap.remove(oldInstallmentNumber);
|
||||
} else {
|
||||
chargesToRemoveFromLoanCharge.add(oldCharge);
|
||||
oldCharge.getInstallment().getInstallmentCharges().remove(oldCharge);
|
||||
}
|
||||
}
|
||||
|
||||
chargesToAddIntoLoanCharge.addAll(newChargeMap.values());
|
||||
|
||||
loanCharge.getLoanInstallmentCharge().removeAll(chargesToRemoveFromLoanCharge);
|
||||
loanCharge.getLoanInstallmentCharge().addAll(chargesToAddIntoLoanCharge);
|
||||
}
|
||||
Money amount = Money.zero(loanCharge.getLoan().getCurrency());
|
||||
|
||||
Money totalAmount = Money.zero(loanCharge.getLoan().getCurrency());
|
||||
for (LoanInstallmentCharge charge : loanCharge.getLoanInstallmentCharge()) {
|
||||
amount = amount.plus(charge.getAmount());
|
||||
totalAmount = totalAmount.plus(charge.getAmount());
|
||||
}
|
||||
loanCharge.setAmount(amount.getAmount());
|
||||
loanCharge.setAmount(totalAmount.getAmount());
|
||||
}
|
||||
|
||||
private List<LoanInstallmentCharge> generateInstallmentLoanCharges(final Loan loan, final LoanCharge loanCharge) {
|
||||
@@ -615,9 +657,6 @@ public class LoanChargeService {
|
||||
if (loanCharge.isInstalmentFee()) {
|
||||
List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
|
||||
for (final LoanRepaymentScheduleInstallment installment : installments) {
|
||||
if (installment.isRecalculatedInterestComponent()) {
|
||||
continue;
|
||||
}
|
||||
BigDecimal amount;
|
||||
if (loanCharge.getChargeCalculation().isFlat()) {
|
||||
amount = loanCharge.amountOrPercentage();
|
||||
@@ -633,6 +672,78 @@ public class LoanChargeService {
|
||||
return loanChargePerInstallments;
|
||||
}
|
||||
|
||||
private List<LoanInstallmentCharge> generateInstallmentLoanCharges(final Loan loan, final LoanCharge loanCharge,
|
||||
final LocalDate transactionDate) {
|
||||
final List<LoanInstallmentCharge> loanChargePerInstallments = new ArrayList<>();
|
||||
if (loanCharge.isInstalmentFee()) {
|
||||
final List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments().stream()
|
||||
.filter(i -> i != null && i.isNotFullyPaidOff() && i.getDueDate() != null && !i.getDueDate().isBefore(transactionDate))
|
||||
.toList();
|
||||
for (final LoanRepaymentScheduleInstallment installment : installments) {
|
||||
BigDecimal amount;
|
||||
if (loanCharge.getChargeCalculation().isFlat()) {
|
||||
amount = loanCharge.amountOrPercentage();
|
||||
} else {
|
||||
amount = calculateInstallmentChargeAmount(loan, loanCharge.getChargeCalculation(), loanCharge.getPercentage(),
|
||||
installment).getAmount();
|
||||
}
|
||||
final LoanInstallmentCharge loanInstallmentCharge = new LoanInstallmentCharge(amount, loanCharge, installment);
|
||||
installment.getInstallmentCharges().add(loanInstallmentCharge);
|
||||
loanChargePerInstallments.add(loanInstallmentCharge);
|
||||
}
|
||||
}
|
||||
return loanChargePerInstallments;
|
||||
}
|
||||
|
||||
public void updateInstallmentCharges(final LoanCharge loanCharge, final LocalDate transactionDate) {
|
||||
final List<LoanInstallmentCharge> newChargeInstallments = generateInstallmentLoanCharges(loanCharge.getLoan(), loanCharge,
|
||||
transactionDate);
|
||||
|
||||
if (loanCharge.getLoanInstallmentCharge().isEmpty()) {
|
||||
loanCharge.getLoanInstallmentCharge().addAll(newChargeInstallments);
|
||||
} else {
|
||||
final List<LoanInstallmentCharge> oldLoanInstallmentCharges = loanCharge
|
||||
.getLoanInstallmentCharge().stream().filter(i -> i != null && !i.isPaid() && i.getInstallment() != null
|
||||
&& i.getInstallment().getDueDate() != null && !i.getInstallment().getDueDate().isBefore(transactionDate))
|
||||
.toList();
|
||||
|
||||
final Map<Integer, LoanInstallmentCharge> newChargeMap = new HashMap<>();
|
||||
for (final LoanInstallmentCharge newCharge : newChargeInstallments) {
|
||||
if (newCharge.getInstallment() != null && newCharge.getInstallment().getInstallmentNumber() != null) {
|
||||
newChargeMap.put(newCharge.getInstallment().getInstallmentNumber(), newCharge);
|
||||
}
|
||||
}
|
||||
|
||||
final Collection<LoanInstallmentCharge> chargesToRemoveFromLoanCharge = new HashSet<>();
|
||||
final Collection<LoanInstallmentCharge> chargesToAddIntoLoanCharge = new HashSet<>();
|
||||
|
||||
for (final LoanInstallmentCharge oldCharge : oldLoanInstallmentCharges) {
|
||||
final Integer oldInstallmentNumber = oldCharge.getInstallment().getInstallmentNumber();
|
||||
|
||||
if (newChargeMap.containsKey(oldInstallmentNumber)) {
|
||||
chargesToRemoveFromLoanCharge.add(oldCharge);
|
||||
oldCharge.getInstallment().getInstallmentCharges().remove(oldCharge);
|
||||
chargesToAddIntoLoanCharge.add(newChargeMap.get(oldInstallmentNumber));
|
||||
newChargeMap.remove(oldInstallmentNumber);
|
||||
} else {
|
||||
chargesToRemoveFromLoanCharge.add(oldCharge);
|
||||
oldCharge.getInstallment().getInstallmentCharges().remove(oldCharge);
|
||||
}
|
||||
}
|
||||
|
||||
chargesToAddIntoLoanCharge.addAll(newChargeMap.values());
|
||||
|
||||
loanCharge.getLoanInstallmentCharge().removeAll(chargesToRemoveFromLoanCharge);
|
||||
loanCharge.getLoanInstallmentCharge().addAll(chargesToAddIntoLoanCharge);
|
||||
}
|
||||
|
||||
Money totalAmount = Money.zero(loanCharge.getLoan().getCurrency());
|
||||
for (LoanInstallmentCharge charge : loanCharge.getLoanInstallmentCharge()) {
|
||||
totalAmount = totalAmount.plus(charge.getAmount());
|
||||
}
|
||||
loanCharge.setAmount(totalAmount.getAmount());
|
||||
}
|
||||
|
||||
private BigDecimal calculateOverdueAmountPercentageAppliedTo(final Loan loan, final LoanCharge loanCharge,
|
||||
final int penaltyWaitPeriod) {
|
||||
LoanRepaymentScheduleInstallment installment = loanCharge.getOverdueInstallmentCharge().getInstallment();
|
||||
@@ -674,7 +785,7 @@ public class LoanChargeService {
|
||||
case FLAT:
|
||||
if (loanCharge.isInstalmentFee()) {
|
||||
if (numberOfRepayments == null) {
|
||||
numberOfRepayments = loanCharge.getLoan().fetchNumberOfInstallmensAfterExceptions();
|
||||
numberOfRepayments = loanCharge.getLoan().fetchNumberOfInstallmentsAfterExceptions();
|
||||
}
|
||||
loanCharge.setAmount(amount.multiply(BigDecimal.valueOf(numberOfRepayments)));
|
||||
} else {
|
||||
@@ -701,6 +812,46 @@ public class LoanChargeService {
|
||||
}
|
||||
}
|
||||
|
||||
private void update(final LoanCharge loanCharge, final BigDecimal amount, final LocalDate dueDate, final BigDecimal loanPrincipal,
|
||||
Integer numberOfRepayments, BigDecimal loanChargeAmount, final LocalDate transactionDate) {
|
||||
if (dueDate != null) {
|
||||
loanCharge.setDueDate(dueDate);
|
||||
}
|
||||
|
||||
if (amount != null) {
|
||||
switch (loanCharge.getChargeCalculation()) {
|
||||
case INVALID:
|
||||
break;
|
||||
case FLAT:
|
||||
if (loanCharge.isInstalmentFee()) {
|
||||
if (numberOfRepayments == null) {
|
||||
numberOfRepayments = loanCharge.getLoan().fetchNumberOfInstallmentsAfterExceptions();
|
||||
}
|
||||
loanCharge.setAmount(amount.multiply(BigDecimal.valueOf(numberOfRepayments)));
|
||||
} else {
|
||||
loanCharge.setAmount(amount);
|
||||
}
|
||||
break;
|
||||
case PERCENT_OF_AMOUNT:
|
||||
case PERCENT_OF_AMOUNT_AND_INTEREST:
|
||||
case PERCENT_OF_INTEREST:
|
||||
case PERCENT_OF_DISBURSEMENT_AMOUNT:
|
||||
loanCharge.setPercentage(amount);
|
||||
loanCharge.setAmountPercentageAppliedTo(loanPrincipal);
|
||||
if (loanChargeAmount.compareTo(BigDecimal.ZERO) == 0) {
|
||||
loanChargeAmount = loanCharge.percentageOf(loanCharge.getAmountPercentageAppliedTo());
|
||||
}
|
||||
loanCharge.setAmount(loanCharge.minimumAndMaximumCap(loanChargeAmount));
|
||||
break;
|
||||
}
|
||||
loanCharge.setAmountOrPercentage(amount);
|
||||
loanCharge.setAmountOutstanding(loanCharge.calculateOutstanding());
|
||||
if (loanCharge.getLoan() != null && loanCharge.isInstalmentFee()) {
|
||||
updateInstallmentCharges(loanCharge, transactionDate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Money getTotalAllTrancheDisbursementAmount(final Loan loan) {
|
||||
Money amount = Money.zero(loan.getCurrency());
|
||||
if (loan.isMultiDisburmentLoan()) {
|
||||
|
||||
+3
-1
@@ -42,6 +42,8 @@ public final class LoanProductsApiResourceSwagger {
|
||||
|
||||
@Schema(example = "1")
|
||||
public Long id;
|
||||
@Schema(example = "60.0")
|
||||
public BigDecimal amount;
|
||||
}
|
||||
|
||||
@Schema(description = "LoanProductChargeToGLAccountMapper")
|
||||
@@ -1421,7 +1423,7 @@ public final class LoanProductsApiResourceSwagger {
|
||||
@Schema(example = "false")
|
||||
public Boolean canDefineInstallmentAmount;
|
||||
@Schema(example = "[]")
|
||||
public List<Integer> charges;
|
||||
public List<LoanProductChargeData> charges;
|
||||
public Set<GetLoanProductsPrincipalVariationsForBorrowerCycle> productsPrincipalVariationsForBorrowerCycle;
|
||||
@Schema(example = "[]")
|
||||
public List<Integer> interestRateVariationsForBorrowerCycle;
|
||||
|
||||
+48
-8
@@ -76,6 +76,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeOffBehaviour;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCreditAllocationRule;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanInstallmentCharge;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalculationDetails;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanPaymentAllocationRule;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||
@@ -97,6 +98,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanSchedul
|
||||
import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.InterestRefundService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.schedule.LoanScheduleComponent;
|
||||
import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
|
||||
import org.apache.fineract.portfolio.loanproduct.calc.data.OutstandingDetails;
|
||||
@@ -125,17 +127,20 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
private final InterestRefundService interestRefundService;
|
||||
private final LoanScheduleComponent loanSchedule;
|
||||
private final LoanTransactionRepository loanTransactionRepository;
|
||||
private final LoanChargeService loanChargeService;
|
||||
|
||||
public AdvancedPaymentScheduleTransactionProcessor(final EMICalculator emiCalculator, final LoanRepositoryWrapper loanRepositoryWrapper,
|
||||
final InterestRefundService interestRefundService, final ExternalIdFactory externalIdFactory,
|
||||
final LoanScheduleComponent loanSchedule, final LoanTransactionRepository loanTransactionRepository,
|
||||
final LoanChargeValidator loanChargeValidator, final LoanBalanceService loanBalanceService) {
|
||||
final LoanChargeValidator loanChargeValidator, final LoanBalanceService loanBalanceService,
|
||||
final LoanChargeService loanChargeService) {
|
||||
super(externalIdFactory, loanChargeValidator, loanBalanceService);
|
||||
this.emiCalculator = emiCalculator;
|
||||
this.loanRepositoryWrapper = loanRepositoryWrapper;
|
||||
this.interestRefundService = interestRefundService;
|
||||
this.loanTransactionRepository = loanTransactionRepository;
|
||||
this.loanSchedule = loanSchedule;
|
||||
this.loanChargeService = loanChargeService;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -372,6 +377,10 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
createMissingAccrualTransactionDuringChargeOffIfNeeded(newInterest, loanTransaction, loanTransaction.getTransactionDate(),
|
||||
transactionCtx);
|
||||
|
||||
if (!loanTransaction.getLoan().isInterestBearingAndInterestRecalculationEnabled()) {
|
||||
recalculateInstallmentFeeCharges(loanTransaction);
|
||||
}
|
||||
|
||||
loanTransaction.resetDerivedComponents();
|
||||
// determine how much is outstanding total and breakdown for principal, interest and charges
|
||||
for (final LoanRepaymentScheduleInstallment currentInstallment : transactionCtx.getInstallments()) {
|
||||
@@ -1215,11 +1224,37 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
|
||||
private void handleDisbursement(LoanTransaction disbursementTransaction, TransactionCtx transactionCtx) {
|
||||
// TODO: Fix this and enhance EMICalculator to support reamortization and reaging
|
||||
if (disbursementTransaction.getLoan().isInterestBearing()) {
|
||||
final Loan loan = disbursementTransaction.getLoan();
|
||||
if (loan.isInterestBearing()) {
|
||||
handleDisbursementWithEMICalculator(disbursementTransaction, transactionCtx);
|
||||
} else {
|
||||
handleDisbursementWithoutEMICalculator(disbursementTransaction, transactionCtx);
|
||||
}
|
||||
if (!disbursementTransaction.getLoan().isInterestBearingAndInterestRecalculationEnabled()) {
|
||||
recalculateInstallmentFeeCharges(disbursementTransaction);
|
||||
}
|
||||
}
|
||||
|
||||
private void recalculateInstallmentFeeCharges(final LoanTransaction loanTransaction) {
|
||||
final Loan loan = loanTransaction.getLoan();
|
||||
final List<LoanCharge> loanInstallmentFeeCharges = loan.getActiveCharges().stream()
|
||||
.filter(c -> c.isInstalmentFee() && c.getSubmittedOnDate().isBefore(loanTransaction.getTransactionDate())).toList();
|
||||
loanChargeService.recalculateParticularChargesAfterTransactionOccurs(loan, loanInstallmentFeeCharges,
|
||||
loanTransaction.getTransactionDate());
|
||||
if (!loanInstallmentFeeCharges.isEmpty()) {
|
||||
final List<LoanRepaymentScheduleInstallment> installmentsToUpdate = loan.getRepaymentScheduleInstallments().stream()
|
||||
.filter(i -> i.isNotFullyPaidOff() && !i.isAdditional()).toList();
|
||||
for (LoanRepaymentScheduleInstallment installment : installmentsToUpdate) {
|
||||
if (installment.isDownPayment()) {
|
||||
continue;
|
||||
}
|
||||
final BigDecimal newFee = installment.getInstallmentCharges().stream().map(LoanInstallmentCharge::getAmount)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
if (newFee.compareTo(BigDecimal.ZERO) != 0) {
|
||||
installment.setFeeChargesCharged(newFee);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleDisbursementWithEMICalculator(LoanTransaction disbursementTransaction, TransactionCtx transactionCtx) {
|
||||
@@ -1670,6 +1705,10 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
createMissingAccrualTransactionDuringChargeOffIfNeeded(newInterest, loanTransaction, loanTransaction.getTransactionDate(),
|
||||
transactionCtx);
|
||||
|
||||
if (!loanTransaction.getLoan().isInterestBearingAndInterestRecalculationEnabled()) {
|
||||
recalculateInstallmentFeeCharges(loanTransaction);
|
||||
}
|
||||
|
||||
loanTransaction.resetDerivedComponents();
|
||||
// determine how much is outstanding total and breakdown for principal, interest and charges
|
||||
for (final LoanRepaymentScheduleInstallment currentInstallment : transactionCtx.getInstallments()) {
|
||||
@@ -1765,8 +1804,8 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
|
||||
if (futureFee.compareTo(BigDecimal.ZERO) > 0 || futurePenalty.compareTo(BigDecimal.ZERO) > 0) {
|
||||
final Optional<LocalDate> latestDueDate = loan.getCharges().stream()
|
||||
.filter(loanCharge -> loanCharge.isActive() && loanCharge.isNotFullyPaid()).map(LoanCharge::getDueDate)
|
||||
.max(LocalDate::compareTo);
|
||||
.filter(loanCharge -> loanCharge.isActive() && loanCharge.isNotFullyPaid() && loanCharge.getDueDate() != null)
|
||||
.map(LoanCharge::getDueDate).max(LocalDate::compareTo);
|
||||
|
||||
if (latestDueDate.isPresent()) {
|
||||
final LoanRepaymentScheduleInstallment lastInstallment = installmentsUpToTransactionDate.getLast();
|
||||
@@ -2526,11 +2565,12 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Set<LoanCharge> getLoanChargesOfInstallment(Set<LoanCharge> charges, LoanRepaymentScheduleInstallment installment,
|
||||
int firstInstallmentNumber) {
|
||||
boolean isFirstInstallment = installment.getInstallmentNumber().equals(firstInstallmentNumber);
|
||||
private Set<LoanCharge> getLoanChargesOfInstallment(final Set<LoanCharge> charges, final LoanRepaymentScheduleInstallment installment,
|
||||
final int firstInstallmentNumber) {
|
||||
final boolean isFirstInstallment = installment.getInstallmentNumber().equals(firstInstallmentNumber);
|
||||
return charges.stream()
|
||||
.filter(loanCharge -> loanCharge.isDueInPeriod(installment.getFromDate(), installment.getDueDate(), isFirstInstallment))
|
||||
.filter(loanCharge -> (loanCharge.isInstalmentFee() && loanCharge.hasInstallmentFor(installment))
|
||||
|| loanCharge.isDueInPeriod(installment.getFromDate(), installment.getDueDate(), isFirstInstallment))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
|
||||
+19
-1
@@ -66,12 +66,17 @@ public class ChangeOperation implements Comparable<ChangeOperation> {
|
||||
return loanCharge.isPresent();
|
||||
}
|
||||
|
||||
public boolean isInstallmentFee() {
|
||||
return loanCharge.map(LoanCharge::isInstalmentFee).orElse(false);
|
||||
}
|
||||
|
||||
private boolean isAccrualActivity() {
|
||||
return isTransaction() && loanTransaction.get().isAccrualActivity();
|
||||
}
|
||||
|
||||
private boolean isBackdatedCharge() {
|
||||
return isCharge() && DateUtils.isBefore(loanCharge.get().getDueDate(), loanCharge.get().getSubmittedOnDate());
|
||||
return isCharge() && loanCharge.isPresent() && loanCharge.get().getDueDate() != null
|
||||
&& DateUtils.isBefore(loanCharge.get().getDueDate(), loanCharge.get().getSubmittedOnDate());
|
||||
}
|
||||
|
||||
private LocalDate getEffectiveDate() {
|
||||
@@ -80,6 +85,8 @@ public class ChangeOperation implements Comparable<ChangeOperation> {
|
||||
} else if (loanCharge.isPresent()) {
|
||||
if (isBackdatedCharge()) {
|
||||
return loanCharge.get().getDueDate();
|
||||
} else if (isInstallmentFee()) {
|
||||
return loanCharge.get().getLoan().isDisbursed() ? loanCharge.get().getSubmittedOnDate() : loanCharge.get().getDueDate();
|
||||
} else {
|
||||
return loanCharge.get().getSubmittedOnDate();
|
||||
}
|
||||
@@ -119,6 +126,17 @@ public class ChangeOperation implements Comparable<ChangeOperation> {
|
||||
public int compareTo(@NonNull ChangeOperation o) {
|
||||
int datePortion = DateUtils.compareWithNullsLast(this.getEffectiveDate(), o.getEffectiveDate());
|
||||
if (datePortion == 0) {
|
||||
boolean thisIsDisb = this.isTransaction() && this.getLoanTransaction().isPresent()
|
||||
&& this.getLoanTransaction().get().isDisbursement();
|
||||
boolean otherIsDisb = o.isTransaction() && o.getLoanTransaction().isPresent() && o.getLoanTransaction().get().isDisbursement();
|
||||
|
||||
if (thisIsDisb && o.isCharge() && o.isInstallmentFee()) {
|
||||
return -1;
|
||||
}
|
||||
if (this.isCharge() && this.isInstallmentFee() && otherIsDisb) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
final boolean isAccrual = isAccrualActivity();
|
||||
if (isAccrual != o.isAccrualActivity()) {
|
||||
return isAccrual ? 1 : -1;
|
||||
|
||||
+2
-1
@@ -75,6 +75,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanSchedul
|
||||
import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.InterestRefundService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.schedule.LoanScheduleComponent;
|
||||
import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
|
||||
import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
|
||||
@@ -121,7 +122,7 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
||||
underTest = new AdvancedPaymentScheduleTransactionProcessor(emiCalculator, Mockito.mock(LoanRepositoryWrapper.class),
|
||||
Mockito.mock(InterestRefundService.class), Mockito.mock(ExternalIdFactory.class), Mockito.mock(LoanScheduleComponent.class),
|
||||
Mockito.mock(LoanTransactionRepository.class), Mockito.mock(LoanChargeValidator.class),
|
||||
Mockito.mock(LoanBalanceService.class));
|
||||
Mockito.mock(LoanBalanceService.class), Mockito.mock(LoanChargeService.class));
|
||||
|
||||
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
|
||||
ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT);
|
||||
|
||||
-17
@@ -202,18 +202,6 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
|
||||
final Long chargeDefinitionId = command.longValueOfParameterNamed("chargeId");
|
||||
final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeDefinitionId);
|
||||
|
||||
/*
|
||||
* TODO: remove this check once handling for Installment fee charges is implemented for Advanced Payment
|
||||
* strategy
|
||||
*/
|
||||
if (ChargeTimeType.fromInt(chargeDefinition.getChargeTimeType()).isInstalmentFee()
|
||||
&& AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY
|
||||
.equals(loan.transactionProcessingStrategy())) {
|
||||
final String errorMessageInstallmentChargeNotSupported = "Charge with identifier " + chargeDefinition.getId()
|
||||
+ " cannot be applied: Installment fee charges are not supported for Advanced payment allocation strategy";
|
||||
throw new ChargeCannotBeAppliedToException("loan", errorMessageInstallmentChargeNotSupported, chargeDefinition.getId());
|
||||
}
|
||||
|
||||
if (loan.isDisbursed() && chargeDefinition.isDisbursementCharge()) {
|
||||
// validates whether any pending disbursements are available to
|
||||
// apply this charge
|
||||
@@ -1079,11 +1067,6 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
|
||||
chargeDefinition.getName());
|
||||
}
|
||||
} else if (loan.isInterestBearingAndInterestRecalculationEnabled()) {
|
||||
if (loanCharge.isInstalmentFee() && loan.getStatus().isActive()) {
|
||||
final String defaultUserMessage = "installment charge addition not allowed after disbursement";
|
||||
throw new LoanChargeCannotBeAddedException("loanCharge", "installment.charge", defaultUserMessage, null,
|
||||
chargeDefinition.getName());
|
||||
}
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final Set<LoanCharge> loanCharges = new HashSet<>(1);
|
||||
loanCharges.add(loanCharge);
|
||||
|
||||
+3
-2
@@ -36,6 +36,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.imp
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.RBILoanRepaymentScheduleTransactionProcessor;
|
||||
import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.ProgressiveLoanInterestRefundServiceImpl;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.schedule.LoanScheduleComponent;
|
||||
import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
|
||||
@@ -139,8 +140,8 @@ public class LoanAccountAutoStarter {
|
||||
final @Lazy ProgressiveLoanInterestRefundServiceImpl progressiveLoanInterestRefundService,
|
||||
final ExternalIdFactory externalIdFactory, final LoanScheduleComponent loanSchedule,
|
||||
final LoanTransactionRepository loanTransactionRepository, final LoanChargeValidator loanChargeValidator,
|
||||
final LoanBalanceService loanBalanceService) {
|
||||
final LoanBalanceService loanBalanceService, @Lazy final LoanChargeService loanChargeService) {
|
||||
return new AdvancedPaymentScheduleTransactionProcessor(emiCalculator, loanRepositoryWrapper, progressiveLoanInterestRefundService,
|
||||
externalIdFactory, loanSchedule, loanTransactionRepository, loanChargeValidator, loanBalanceService);
|
||||
externalIdFactory, loanSchedule, loanTransactionRepository, loanChargeValidator, loanBalanceService, loanChargeService);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -199,7 +199,7 @@ class LoanChargeWritePlatformServiceImplTest {
|
||||
when(loan.getDisbursementDate()).thenReturn(LocalDate.now(ZoneId.systemDefault()));
|
||||
when(loan.getRepaymentScheduleInstallments()).thenReturn(new ArrayList<>());
|
||||
when(loanChargeService.calculateAmountPercentageAppliedTo(any(Loan.class), any(LoanCharge.class))).thenReturn(BigDecimal.TEN);
|
||||
when(loan.fetchNumberOfInstallmensAfterExceptions()).thenReturn(5);
|
||||
when(loan.fetchNumberOfInstallmentsAfterExceptions()).thenReturn(5);
|
||||
when(loan.updateSummaryWithTotalFeeChargesDueAtDisbursement(any(BigDecimal.class))).thenReturn(null);
|
||||
when(loan.deriveSumTotalOfChargesDueAtDisbursement()).thenReturn(BigDecimal.ZERO);
|
||||
when(loanCharge.getDueLocalDate()).thenReturn(LocalDate.now(ZoneId.systemDefault()));
|
||||
|
||||
+2
-3
@@ -46,6 +46,7 @@ import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
|
||||
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class LoanChargeTypeInstallmentFeeErrorHandlingWithAdvancedPaymentAllocationTest extends BaseLoanIntegrationTest {
|
||||
@@ -67,9 +68,7 @@ public class LoanChargeTypeInstallmentFeeErrorHandlingWithAdvancedPaymentAllocat
|
||||
ACCOUNT_HELPER = new AccountHelper(REQUEST_SPEC, RESPONSE_SPEC);
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: To be disabled when Installment Fee Charges are handled for Advanced Payment Allocation
|
||||
*/
|
||||
@Disabled
|
||||
@Test
|
||||
public void addingLoanChargeTypeInstallmentFeeForAdvancedPaymentAllocationGivesErrorTest() {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user