Skip to content

Commit ff77199

Browse files
FINERACT-2412: Full term tranche - Schedule handling and Calculations
1 parent 5c04216 commit ff77199

File tree

25 files changed

+713
-31
lines changed

25 files changed

+713
-31
lines changed

fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/loanproduct/DefaultLoanProduct.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ public enum DefaultLoanProduct implements LoanProduct {
180180
LP1_INTEREST_FLAT_DAILY_ACTUAL_ACTUAL_MULTIDISB_EXPECT_TRANCHES, //
181181
LP2_ADV_PYMNT_360_30_ZERO_INTEREST_CHARGE_OFF_ACCRUAL_ACTIVITY, //
182182
LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL_PRINCIPAL_FIRST, //
183+
LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_MULTIDISBURSE_FULL_TERM_TRANCHE, //
183184
;
184185

185186
@Override

fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4262,6 +4262,39 @@ public void initialize() throws Exception {
42624262
TestContext.INSTANCE.set(
42634263
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADVANCED_CUSTOM_PAYMENT_ALLOCATION_PROGRESSIVE_LOAN_SCHEDULE_PRINCIPAL_FIRST,
42644264
responseLoanProductsRequestAdvCustomPaymentAllocationProgressiveLoanSchedulePrincipalFirst);
4265+
4266+
// LP2 with progressive loan schedule + horizontal + interest recalculation daily EMI + 360/30 +
4267+
// multidisbursement with full term tranche enabled
4268+
// Frequency for recalculate Outstanding Principal: Daily, Frequency Interval for recalculation: 1
4269+
// (LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_MULTIDISBURSE_FULL_TERM_TRANCHE)
4270+
String name152 = DefaultLoanProduct.LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_MULTIDISBURSE_FULL_TERM_TRANCHE
4271+
.getName();
4272+
PostLoanProductsRequest loanProductsRequestLP2AdvancedpaymentInterestEmi36030InterestRecalcDailyMultiDisburseFullTermTranche = loanProductsRequestFactory
4273+
.defaultLoanProductsRequestLP2Emi()//
4274+
.name(name152)//
4275+
.daysInYearType(DaysInYearType.DAYS360.value)//
4276+
.daysInMonthType(DaysInMonthType.DAYS30.value)//
4277+
.isInterestRecalculationEnabled(true)//
4278+
.preClosureInterestCalculationStrategy(1)//
4279+
.rescheduleStrategyMethod(4)//
4280+
.interestRecalculationCompoundingMethod(0)//
4281+
.recalculationRestFrequencyType(2)//
4282+
.recalculationRestFrequencyInterval(1)//
4283+
.paymentAllocation(List.of(//
4284+
createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"), //
4285+
createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT"), //
4286+
createPaymentAllocation("MERCHANT_ISSUED_REFUND", "REAMORTIZATION"), //
4287+
createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT")))//
4288+
.multiDisburseLoan(true)//
4289+
.disallowExpectedDisbursements(true)//
4290+
.allowFullTermForTranche(true)//
4291+
.maxTrancheCount(10)//
4292+
.outstandingLoanBalance(10000.0);//
4293+
PostLoanProductsResponse responseLoanProductsRequestLP2AdvancedpaymentInterestEmi36030InterestRecalcDailyMultiDisburseFullTermTranche = createLoanProductIdempotent(
4294+
loanProductsRequestLP2AdvancedpaymentInterestEmi36030InterestRecalcDailyMultiDisburseFullTermTranche);
4295+
TestContext.INSTANCE.set(
4296+
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_MULTIDISBURSE_FULL_TERM_TRANCHE,
4297+
responseLoanProductsRequestLP2AdvancedpaymentInterestEmi36030InterestRecalcDailyMultiDisburseFullTermTranche);
42654298
}
42664299

42674300
public static AdvancedPaymentData createPaymentAllocation(String transactionType, String futureInstallmentAllocationRule,

fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,4 +292,5 @@ public abstract class TestContextKey {
292292
public static final String LP1_INTEREST_FLAT_DAILY_ACTUAL_ACTUAL_MULTIDISB_EXPECT_TRANCHES = "loanProductCreateResponseLP1InterestFlatDailyActualActualMultiDisbursementExpectTranches";
293293
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_360_30_ZERO_INTEREST_CHARGE_OFF_ACCRUAL_ACTIVITY = "loanProductCreateResponseLP2AdvancedPaymentZeroInterestChargeOffBehaviourAccrualActivity";
294294
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADVANCED_CUSTOM_PAYMENT_ALLOCATION_PROGRESSIVE_LOAN_SCHEDULE_PRINCIPAL_FIRST = "loanProductCreateResponseLP2AdvancedPaymentHorizontalPrincipalFirst";
295+
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_MULTIDISBURSE_FULL_TERM_TRANCHE = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030InterestRecalculationDailyMultidisburseFullTermTranche";
295296
}

fineract-e2e-tests-runner/src/test/resources/features/LoanDelayedScheduleCaptures.feature

Lines changed: 139 additions & 0 deletions
Large diffs are not rendered by default.

fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/RepaymentScheduleRelatedLoanData.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import java.math.BigDecimal;
2222
import java.time.LocalDate;
23+
import lombok.Getter;
2324
import org.apache.fineract.organisation.monetary.data.CurrencyData;
2425

2526
/**
@@ -34,17 +35,27 @@ public class RepaymentScheduleRelatedLoanData {
3435
private final BigDecimal netDisbursalAmount;
3536
private final BigDecimal inArrearsTolerance;
3637
private final BigDecimal totalFeeChargesAtDisbursement;
38+
@Getter
39+
private final boolean allowFullTermForTranche;
3740

3841
public RepaymentScheduleRelatedLoanData(final LocalDate expectedDisbursementDate, final LocalDate actualDisbursementDate,
3942
final CurrencyData currency, final BigDecimal principal, final BigDecimal inArrearsTolerance,
4043
final BigDecimal totalFeeChargesAtDisbursement) {
44+
this(expectedDisbursementDate, actualDisbursementDate, currency, principal, inArrearsTolerance, totalFeeChargesAtDisbursement,
45+
false);
46+
}
47+
48+
public RepaymentScheduleRelatedLoanData(final LocalDate expectedDisbursementDate, final LocalDate actualDisbursementDate,
49+
final CurrencyData currency, final BigDecimal principal, final BigDecimal inArrearsTolerance,
50+
final BigDecimal totalFeeChargesAtDisbursement, final boolean allowFullTermForTranche) {
4151
this.expectedDisbursementDate = expectedDisbursementDate;
4252
this.actualDisbursementDate = actualDisbursementDate;
4353
this.currency = currency;
4454
this.principal = principal;
4555
this.inArrearsTolerance = inArrearsTolerance;
4656
this.totalFeeChargesAtDisbursement = totalFeeChargesAtDisbursement;
4757
this.netDisbursalAmount = this.principal.subtract(this.totalFeeChargesAtDisbursement);
58+
this.allowFullTermForTranche = allowFullTermForTranche;
4859
}
4960

5061
public LocalDate disbursementDate() {

fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
427427
@Column(name = "enable_installment_level_delinquency", nullable = false)
428428
private boolean enableInstallmentLevelDelinquency = false;
429429

430+
@Getter
430431
@Column(name = "allow_full_term_for_tranche", nullable = false)
431432
private boolean allowFullTermForTranche = false;
432433

@@ -1787,10 +1788,6 @@ public void updateEnableInstallmentLevelDelinquency(boolean enableInstallmentLev
17871788
this.enableInstallmentLevelDelinquency = enableInstallmentLevelDelinquency;
17881789
}
17891790

1790-
public boolean isAllowFullTermForTranche() {
1791-
return this.allowFullTermForTranche;
1792-
}
1793-
17941791
public void updateAllowFullTermForTranche(boolean allowFullTermForTranche) {
17951792
this.allowFullTermForTranche = allowFullTermForTranche;
17961793
}

fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,10 @@ public static boolean isInPeriod(LocalDate targetDate, LocalDate fromDate, Local
253253
: DateUtils.isDateInRangeFromExclusiveToInclusive(targetDate, fromDate, toDate);
254254
}
255255

256+
public static boolean isInPeriodFromInclusiveToExclusive(final LocalDate targetDate, final LocalDate fromDate, final LocalDate toDate) {
257+
return DateUtils.isDateInRangeFromInclusiveToExclusive(fromDate, toDate, targetDate);
258+
}
259+
256260
public static boolean isBeforePeriod(LocalDate targetDate, LoanRepaymentScheduleInstallment installment, boolean isFirstPeriod) {
257261
LocalDate fromDate = installment.getFromDate();
258262
return isFirstPeriod ? DateUtils.isBefore(targetDate, fromDate) : !DateUtils.isAfter(targetDate, fromDate);

fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.util.HashSet;
2828
import java.util.List;
2929
import java.util.Set;
30+
import lombok.Getter;
3031
import org.apache.fineract.infrastructure.core.service.DateUtils;
3132
import org.apache.fineract.infrastructure.core.service.MathUtil;
3233
import org.apache.fineract.organisation.monetary.data.CurrencyData;
@@ -247,6 +248,8 @@ public final class LoanApplicationTerms {
247248
private LoanBuyDownFeeStrategy buyDownFeeStrategy;
248249
private LoanBuyDownFeeIncomeType buyDownFeeIncomeType;
249250
private boolean merchantBuyDownFee;
251+
@Getter
252+
private boolean allowFullTermForTranche = false;
250253

251254
private LoanApplicationTerms(Builder builder) {
252255
this.currency = builder.currency;
@@ -292,6 +295,7 @@ private LoanApplicationTerms(Builder builder) {
292295
this.buyDownFeeStrategy = builder.buyDownFeeStrategy;
293296
this.buyDownFeeIncomeType = builder.buyDownFeeIncomeType;
294297
this.merchantBuyDownFee = builder.merchantBuyDownFee;
298+
this.allowFullTermForTranche = builder.allowFullTermForTranche;
295299
this.interestMethod = builder.interestMethod;
296300
this.allowPartialPeriodInterestCalculation = builder.allowPartialPeriodInterestCalculation;
297301
}
@@ -333,6 +337,7 @@ public static class Builder {
333337
private LoanBuyDownFeeStrategy buyDownFeeStrategy;
334338
private LoanBuyDownFeeIncomeType buyDownFeeIncomeType;
335339
private boolean merchantBuyDownFee;
340+
private boolean allowFullTermForTranche;
336341
private boolean allowPartialPeriodInterestCalculation;
337342

338343
public Builder interestMethod(InterestMethod interestMethod) {
@@ -500,6 +505,11 @@ public Builder merchantBuyDownFee(boolean value) {
500505
return this;
501506
}
502507

508+
public Builder allowFullTermForTranche(boolean value) {
509+
this.allowFullTermForTranche = value;
510+
return this;
511+
}
512+
503513
public LoanApplicationTerms build() {
504514
return new LoanApplicationTerms(this);
505515
}
@@ -542,7 +552,8 @@ public static LoanApplicationTerms assembleFrom(LoanRepaymentScheduleModelData m
542552
.submittedOnDate(modelData.scheduleGenerationStartDate()).seedDate(seedDate)
543553
.interestRecognitionOnDisbursementDate(modelData.interestRecognitionOnDisbursementDate())
544554
.daysInYearCustomStrategy(modelData.daysInYearCustomStrategy()).interestMethod(modelData.interestMethod())
545-
.allowPartialPeriodInterestCalculation(modelData.allowPartialPeriodInterestCalculation()).mc(mc).build();
555+
.allowPartialPeriodInterestCalculation(modelData.allowPartialPeriodInterestCalculation())
556+
.allowFullTermForTranche(modelData.allowFullTermForTranche()).mc(mc).build();
546557
}
547558

548559
public static LoanApplicationTerms assembleFrom(final CurrencyData currency, final Integer loanTermFrequency,
@@ -579,7 +590,7 @@ public static LoanApplicationTerms assembleFrom(final CurrencyData currency, fin
579590
final LoanCapitalizedIncomeStrategy capitalizedIncomeStrategy, final LoanCapitalizedIncomeType capitalizedIncomeType,
580591
final boolean enableBuyDownFee, final LoanBuyDownFeeCalculationType buyDownFeeCalculationType,
581592
final LoanBuyDownFeeStrategy buyDownFeeStrategy, final LoanBuyDownFeeIncomeType buyDownFeeIncomeType,
582-
final boolean merchantBuyDownFee) {
593+
final boolean merchantBuyDownFee, final boolean allowFullTermForTranche) {
583594

584595
final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null;
585596
final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null;
@@ -601,7 +612,7 @@ public static LoanApplicationTerms assembleFrom(final CurrencyData currency, fin
601612
fixedLength, enableAccrualActivityPosting, supportedInterestRefundTypes, chargeOffBehaviour,
602613
interestRecognitionOnDisbursementDate, daysInYearCustomStrategy, enableIncomeCapitalization,
603614
capitalizedIncomeCalculationType, capitalizedIncomeStrategy, capitalizedIncomeType, enableBuyDownFee,
604-
buyDownFeeCalculationType, buyDownFeeStrategy, buyDownFeeIncomeType, merchantBuyDownFee);
615+
buyDownFeeCalculationType, buyDownFeeStrategy, buyDownFeeIncomeType, merchantBuyDownFee, allowFullTermForTranche);
605616

606617
}
607618

@@ -622,7 +633,7 @@ public static LoanApplicationTerms assembleFrom(final CurrencyData currency, fin
622633
final boolean isSkipRepaymentOnFirstDayOfMonth, final HolidayDetailDTO holidayDetailDTO, final boolean allowCompoundingOnEod,
623634
final boolean isFirstRepaymentDateAllowedOnHoliday, final boolean isInterestToBeRecoveredFirstWhenGreaterThanEMI,
624635
final BigDecimal fixedPrincipalPercentagePerInstallment, final boolean isPrincipalCompoundingDisabledForOverdueLoans,
625-
final RepaymentStartDateType repaymentStartDateType, final LocalDate submittedOnDate) {
636+
final RepaymentStartDateType repaymentStartDateType, final LocalDate submittedOnDate, final boolean allowFullTermForTranche) {
626637

627638
final Integer numberOfRepayments = loanProductRelatedDetail.getNumberOfRepayments();
628639
final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery();
@@ -680,7 +691,7 @@ public static LoanApplicationTerms assembleFrom(final CurrencyData currency, fin
680691
loanProductRelatedDetail.getCapitalizedIncomeStrategy(), loanProductRelatedDetail.getCapitalizedIncomeType(),
681692
loanProductRelatedDetail.isEnableBuyDownFee(), loanProductRelatedDetail.getBuyDownFeeCalculationType(),
682693
loanProductRelatedDetail.getBuyDownFeeStrategy(), loanProductRelatedDetail.getBuyDownFeeIncomeType(),
683-
loanProductRelatedDetail.isMerchantBuyDownFee());
694+
loanProductRelatedDetail.isMerchantBuyDownFee(), allowFullTermForTranche);
684695
}
685696

686697
private LoanApplicationTerms(final CurrencyData currency, final Integer loanTermFrequency,
@@ -716,7 +727,7 @@ private LoanApplicationTerms(final CurrencyData currency, final Integer loanTerm
716727
final LoanCapitalizedIncomeStrategy capitalizedIncomeStrategy, final LoanCapitalizedIncomeType capitalizedIncomeType,
717728
final boolean enableBuyDownFee, final LoanBuyDownFeeCalculationType buyDownFeeCalculationType,
718729
final LoanBuyDownFeeStrategy buyDownFeeStrategy, final LoanBuyDownFeeIncomeType buyDownFeeIncomeType,
719-
final boolean merchantBuyDownFee) {
730+
final boolean merchantBuyDownFee, final boolean allowFullTermForTranche) {
720731

721732
this.currency = currency;
722733
this.loanTermFrequency = loanTermFrequency;
@@ -827,6 +838,7 @@ private LoanApplicationTerms(final CurrencyData currency, final Integer loanTerm
827838
this.buyDownFeeStrategy = buyDownFeeStrategy;
828839
this.buyDownFeeIncomeType = buyDownFeeIncomeType;
829840
this.merchantBuyDownFee = merchantBuyDownFee;
841+
this.allowFullTermForTranche = allowFullTermForTranche;
830842
}
831843

832844
public Money adjustPrincipalIfLastRepaymentPeriod(final Money principalForPeriod, final Money totalCumulativePrincipalToDate,
@@ -1703,7 +1715,7 @@ public ILoanConfigurationDetails toLoanConfigurationDetails() {
17031715
repaymentEvery, numberOfRepayments,
17041716
isInterestChargedFromDateSameAsDisbursalDateEnabled != null && isInterestChargedFromDateSameAsDisbursalDateEnabled,
17051717
daysInYearCustomStrategy, allowPartialPeriodInterestCalculation, interestRecalculationEnabled, recalculationFrequencyType,
1706-
preClosureInterestCalculationStrategy);
1718+
preClosureInterestCalculationStrategy, allowFullTermForTranche);
17071719
}
17081720

17091721
public Integer getLoanTermFrequency() {

fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanRepaymentScheduleModelData.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,6 @@ public record LoanRepaymentScheduleModelData(@NotNull LocalDate scheduleGenerati
3535
@NotNull boolean downPaymentEnabled, @NotNull DaysInMonthType daysInMonth, @NotNull DaysInYearType daysInYear,
3636
BigDecimal downPaymentPercentage, Integer installmentAmountInMultiplesOf, Integer fixedLength,
3737
@NotNull Boolean interestRecognitionOnDisbursementDate, @Nullable DaysInYearCustomStrategyType daysInYearCustomStrategy,
38-
@NotNull InterestMethod interestMethod, @NotNull boolean allowPartialPeriodInterestCalculation) {
38+
@NotNull InterestMethod interestMethod, @NotNull boolean allowPartialPeriodInterestCalculation,
39+
@NotNull boolean allowFullTermForTranche) {
3940
}

fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanTermVariationsMapper.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,8 @@ public LoanApplicationTerms constructLoanApplicationTerms(final ScheduleGenerato
120120
scheduleGeneratorDTO.getNumberOfdays(), scheduleGeneratorDTO.isSkipRepaymentOnFirstDayofMonth(), holidayDetailDTO,
121121
allowCompoundingOnEod, scheduleGeneratorDTO.isFirstRepaymentDateAllowedOnHoliday(),
122122
scheduleGeneratorDTO.isInterestToBeRecoveredFirstWhenGreaterThanEMI(), loan.getFixedPrincipalPercentagePerInstallment(),
123-
scheduleGeneratorDTO.isPrincipalCompoundingDisabledForOverdueLoans(), repaymentStartDateType, loan.getSubmittedOnDate());
123+
scheduleGeneratorDTO.isPrincipalCompoundingDisabledForOverdueLoans(), repaymentStartDateType, loan.getSubmittedOnDate(),
124+
loan.isAllowFullTermForTranche());
124125
}
125126

126127
private BigDecimal constructFloatingInterestRates(final BigDecimal annualNominalInterestRate, final FloatingRateDTO floatingRateDTO,

0 commit comments

Comments
 (0)