This is an automated email from the ASF dual-hosted git repository.
arnold pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/develop by this push:
new 507e63435c FINERACT-2497: Vertical repayment alignment fix
507e63435c is described below
commit 507e63435ca912ae9d5d2b927f6b317905379d38
Author: Adam Saghy <[email protected]>
AuthorDate: Tue Feb 24 13:58:55 2026 +0100
FINERACT-2497: Vertical repayment alignment fix
---
.../test/data/loanproduct/DefaultLoanProduct.java | 1 +
.../fineract/test/factory/LoanRequestFactory.java | 2 +-
.../fineract/test/support/TestContextKey.java | 1 +
.../global/LoanProductGlobalInitializerStep.java | 30 ++++
.../test/resources/features/LoanRepayment.feature | 174 +++++++++++++++++++++
.../domain/LoanRepaymentScheduleInstallment.java | 10 ++
.../loanschedule/domain/LoanApplicationTerms.java | 2 +-
.../loanproduct/data/LoanConfigurationDetails.java | 7 +-
.../domain/ILoanConfigurationDetails.java | 3 +
...dvancedPaymentScheduleTransactionProcessor.java | 107 ++++++++++---
.../mapper/LoanConfigurationDetailsMapper.java | 2 +-
.../loanproduct/calc/ProgressiveEMICalculator.java | 12 +-
12 files changed, 318 insertions(+), 33 deletions(-)
diff --git
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/loanproduct/DefaultLoanProduct.java
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/loanproduct/DefaultLoanProduct.java
index 331dba0206..6bbe66d4fb 100644
---
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/loanproduct/DefaultLoanProduct.java
+++
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/loanproduct/DefaultLoanProduct.java
@@ -191,6 +191,7 @@ public enum DefaultLoanProduct implements LoanProduct {
LP2_ADV_PYMNT_INT_DAILY_EMI_360_30_INT_RECALC_DAILY_MULTIDISB_FULL_TERM_TRANCHE_ZERO_INT_CHARGE_OFF,
//
LP2_ADV_PYMNT_INT_DAILY_EMI_360_30_INT_RECALC_DAILY_MULTIDISB_FULL_TERM_TRANCHE_ACCELERATE_MATURITY,
//
LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME, //
+
LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL_INTEREST_RECALC,
//
;
@Override
diff --git
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/LoanRequestFactory.java
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/LoanRequestFactory.java
index 77c3089fe1..f3c2d4385e 100644
---
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/LoanRequestFactory.java
+++
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/LoanRequestFactory.java
@@ -120,7 +120,7 @@ public class LoanRequestFactory {
.transactionProcessingStrategyCode(DEFAULT_TRANSACTION_PROCESSING_STRATEGY_CODE)//
.dateFormat(DATE_FORMAT)//
.graceOnArrearsAgeing(3)//
- .maxOutstandingLoanBalance(new BigDecimal(10000));
+ ;
}
public PostLoansRequest defaultProgressiveLoansRequest(final Long
clientId) {
diff --git
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java
index af71aeeefe..7820e74d0a 100644
---
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java
+++
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java
@@ -313,4 +313,5 @@ public abstract class TestContextKey {
public static final String
DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INT_DAILY_EMI_360_30_INT_RECALC_DAILY_MULTIDISB_FULL_TERM_TRANCHE_ZERO_CHARGE_OFF
=
"loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030InterestRecalculationDailyMultidisburseFullTermTrancheZeroIntChargeOff";
public static final String
DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INT_DAILY_EMI_360_30_INT_RECALC_DAILY_MULTIDISB_FULL_TERM_TRANCHE_ACCELERATE_MATURITY
=
"loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030InterestRecalculationDailyMultidisburseFullTermTrancheAccelerateMaturity";
public static final String OFFICE_CREATE_RESPONSE = "officeCreateResponse";
+ public static final String
DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_DOWNPAYMENT_ADVANCED_PAYMENT_ALLOCATION_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL_INTEREST_RECALC
=
"loanProductCreateResponseLP2DownPaymentAdvancedPaymentAllocationProgressiveLoanScheduleVerticalInterestRecalc";
}
diff --git
a/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java
b/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java
index 04fdb78c46..687762fb7b 100644
---
a/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java
+++
b/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java
@@ -4609,6 +4609,36 @@ public class LoanProductGlobalInitializerStep implements
FineractGlobalInitializ
loanProductsRequestLP2ProgressiveAdvPaymentBuyDownFeesFeeIncome);
TestContext.INSTANCE.set(TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_PROGRESSIVE_ADV_PYMNT_BUYDOWN_FEES_FEE_INCOME,
responseLoanProductsRequestLP2ProgressiveAdvPaymentBuyDownFeesFeeIncome);
+
+ // LP2 with Down-payment + advanced payment allocation + progressive
loan schedule + vertical
+ // (LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL)
+ String name179 =
DefaultLoanProduct.LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL_INTEREST_RECALC.getName();
+ PostLoanProductsRequest
loanProductsRequestDownPaymentAdvPaymentAllocationProgressiveLoanScheduleVerticalInterestRecalc
= loanProductsRequestFactory
+ .defaultLoanProductsRequestLP2Emi()//
+ .name(name179)//
+
.loanScheduleProcessingType("VERTICAL").daysInYearType(DaysInYearType.ACTUAL.value)//
+ .daysInMonthType(DaysInMonthType.ACTUAL.value)//
+ .isInterestRecalculationEnabled(true)//
+ .preClosureInterestCalculationStrategy(1)//
+ .rescheduleStrategyMethod(4)//
+ .interestRecalculationCompoundingMethod(0)//
+ .recalculationRestFrequencyType(2)//
+ .recalculationRestFrequencyInterval(1)//
+ .paymentAllocation(List.of(//
+ createPaymentAllocation("DEFAULT",
"NEXT_INSTALLMENT"), //
+ createPaymentAllocation("REPAYMENT",
"NEXT_INSTALLMENT"))) //
+ .chargeOffBehaviour(ACCELERATE_MATURITY.value)//
+ .multiDisburseLoan(true)//
+ .disallowExpectedDisbursements(true)//
+ .allowFullTermForTranche(true)//
+ .maxTrancheCount(10)//
+ .maxPrincipal(25000000.0)//
+ .outstandingLoanBalance(25000000.0);//
+ PostLoanProductsResponse
responseLoanProductsRequestDownPaymentAdvPaymentAllocationProgressiveLoanScheduleVerticalInterestRecalc
= createLoanProductIdempotent(
+
loanProductsRequestDownPaymentAdvPaymentAllocationProgressiveLoanScheduleVerticalInterestRecalc);
+ TestContext.INSTANCE.set(
+
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_DOWNPAYMENT_ADVANCED_PAYMENT_ALLOCATION_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL_INTEREST_RECALC,
+
responseLoanProductsRequestDownPaymentAdvPaymentAllocationProgressiveLoanScheduleVerticalInterestRecalc);
}
public static AdvancedPaymentData createPaymentAllocation(String
transactionType, String futureInstallmentAllocationRule,
diff --git
a/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment.feature
b/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment.feature
index 53e9ffee7e..bd1f438376 100644
---
a/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment.feature
+++
b/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment.feature
@@ -6751,3 +6751,177 @@ Feature: LoanRepayment
Then Customer undo "1"th transaction made on "01 February 2024" results a
403 error and "update not allowed as loan transaction is linked to other
transactions" error message
When Loan Pay-off is made on "15 March 2024"
Then Loan is closed with zero outstanding balance and it's all
installments have obligations met
+
+ @TestRailId:C4683 @AdvancedPaymentAllocation @ProgressiveLoanSchedule
+ Scenario: Verify AdvancedPaymentAllocation behaviour:
loanScheduleProcessingType-vertical, prepayment with NEXT_INSTALLMENT strategy
+ When Admin sets the business date to "23 February 2026"
+ When Admin creates a client with random data
+ When Admin set
"LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL_INTEREST_RECALC"
loan product "REPAYMENT" transaction type to "NEXT_INSTALLMENT" future
installment allocation rule
+ When Admin creates a fully customized loan with the following data:
+ | LoanProduct
| submitted on date | with Principal | ANNUAL interest rate % |
interest type | interest calculation period | amortization type |
loanTermFrequency | loanTermFrequencyType | repaymentEvery |
repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment |
graceOnInterestPayment | interest free period | Payment strategy |
+ |
LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL_INTEREST_RECALC
| 01 January 2026 | 25000000 | 12 |
DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 12
| MONTHS | 1 | MONTHS |
12 | 0 | 0 | 0
| ADVANCED_PAYMENT_ALLOCATION |
+ And Admin successfully approves the loan on "01 January 2026" with
"25000000" amount and expected disbursement date on "01 January 2026"
+ When Admin successfully disburse the loan on "01 January 2026" with
"25000000" EUR transaction amount
+ Then Loan Repayment schedule has 12 periods, with the following data for
periods:
+ | Nr | Days | Date | Paid date | Balance of loan |
Principal due | Interest | Fees | Penalties | Due | Paid
| In advance | Late | Outstanding |
+ | | | 01 January 2026 | | 25000000.0 |
| | 0.0 | | 0.0 | 0.0 |
| | |
+ | 1 | 31 | 01 February 2026 | | 23034153.81 |
1965846.19 | 254794.52 | 0.0 | 0.0 | 2220640.71 | 0.0
| 0.0 | 0.0 | 2220640.71 |
+ | 2 | 28 | 01 March 2026 | | 21039772.25 |
1994381.56 | 226259.15 | 0.0 | 0.0 | 2220640.71 | 0.0
| 0.0 | 0.0 | 2220640.71 |
+ | 3 | 31 | 01 April 2026 | | 19033564.29 |
2006207.96 | 214432.75 | 0.0 | 0.0 | 2220640.71 | 0.0
| 0.0 | 0.0 | 2220640.71 |
+ | 4 | 30 | 01 May 2026 | | 17000651.89 |
2032912.4 | 187728.31 | 0.0 | 0.0 | 2220640.71 | 0.0
| 0.0 | 0.0 | 2220640.71 |
+ | 5 | 31 | 01 June 2026 | | 14953278.1 |
2047373.79 | 173266.92 | 0.0 | 0.0 | 2220640.71 | 0.0
| 0.0 | 0.0 | 2220640.71 |
+ | 6 | 30 | 01 July 2026 | | 12880121.78 |
2073156.32 | 147484.39 | 0.0 | 0.0 | 2220640.71 | 0.0
| 0.0 | 0.0 | 2220640.71 |
+ | 7 | 31 | 01 August 2026 | | 10790752.45 |
2089369.33 | 131271.38 | 0.0 | 0.0 | 2220640.71 | 0.0
| 0.0 | 0.0 | 2220640.71 |
+ | 8 | 31 | 01 September 2026 | | 8680088.72 |
2110663.73 | 109976.98 | 0.0 | 0.0 | 2220640.71 | 0.0
| 0.0 | 0.0 | 2220640.71 |
+ | 9 | 30 | 01 October 2026 | | 6545059.84 |
2135028.88 | 85611.83 | 0.0 | 0.0 | 2220640.71 | 0.0
| 0.0 | 0.0 | 2220640.71 |
+ | 10 | 31 | 01 November 2026 | | 4391124.95 |
2153934.89 | 66705.82 | 0.0 | 0.0 | 2220640.71 | 0.0
| 0.0 | 0.0 | 2220640.71 |
+ | 11 | 30 | 01 December 2026 | | 2213793.97 |
2177330.98 | 43309.73 | 0.0 | 0.0 | 2220640.71 | 0.0
| 0.0 | 0.0 | 2220640.71 |
+ | 12 | 31 | 01 January 2027 | | 0.0 |
2213793.97 | 22562.5 | 0.0 | 0.0 | 2236356.47 | 0.0
| 0.0 | 0.0 | 2236356.47 |
+ Then Loan Repayment schedule has the following data in Total row:
+ | Principal due | Interest | Fees | Penalties | Due |
Paid | In advance | Late | Outstanding |
+ | 25000000.0 | 1663404.28 | 0.0 | 0.0 | 26663404.28 |
0.0 | 0.0 | 0.0 | 26663404.28 |
+ Then Loan Transactions tab has the following data:
+ | Transaction date | Transaction Type | Amount | Principal |
Interest | Fees | Penalties | Loan Balance |
+ | 01 January 2026 | Disbursement | 25000000.0 | 0.0 | 0.0
| 0.0 | 0.0 | 25000000.0 |
+ When Loan Pay-off is made on "23 February 2026"
+ Then Loan Repayment schedule has 12 periods, with the following data for
periods:
+ | Nr | Days | Date | Paid date | Balance of loan |
Principal due | Interest | Fees | Penalties | Due | Paid
| In advance | Late | Outstanding |
+ | | | 01 January 2026 | | 25000000.0 |
| | 0.0 | | 0.0 | 0.0 |
| | |
+ | 1 | 31 | 01 February 2026 | 23 February 2026 | 23034153.81 |
1965846.19 | 254794.52 | 0.0 | 0.0 | 2220640.71 | 2220640.71
| 0.0 | 2220640.71 | 0.0 |
+ | 2 | 28 | 01 March 2026 | 23 February 2026 | 20813513.1 |
2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71
| 2220640.71 | 0.0 | 0.0 |
+ | 3 | 31 | 01 April 2026 | 23 February 2026 | 18592872.39 |
2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71
| 2220640.71 | 0.0 | 0.0 |
+ | 4 | 30 | 01 May 2026 | 23 February 2026 | 16372231.68 |
2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71
| 2220640.71 | 0.0 | 0.0 |
+ | 5 | 31 | 01 June 2026 | 23 February 2026 | 14151590.97 |
2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71
| 2220640.71 | 0.0 | 0.0 |
+ | 6 | 30 | 01 July 2026 | 23 February 2026 | 11930950.26 |
2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71
| 2220640.71 | 0.0 | 0.0 |
+ | 7 | 31 | 01 August 2026 | 23 February 2026 | 9710309.55 |
2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71
| 2220640.71 | 0.0 | 0.0 |
+ | 8 | 31 | 01 September 2026 | 23 February 2026 | 7489668.84 |
2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71
| 2220640.71 | 0.0 | 0.0 |
+ | 9 | 30 | 01 October 2026 | 23 February 2026 | 5269028.13 |
2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71
| 2220640.71 | 0.0 | 0.0 |
+ | 10 | 31 | 01 November 2026 | 23 February 2026 | 3048387.42 |
2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71
| 2220640.71 | 0.0 | 0.0 |
+ | 11 | 30 | 01 December 2026 | 23 February 2026 | 827746.71 |
2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71
| 2220640.71 | 0.0 | 0.0 |
+ | 12 | 31 | 01 January 2027 | 23 February 2026 | 0.0 |
827746.71 | 180821.92 | 0.0 | 0.0 | 1008568.63 | 1008568.63 |
1008568.63 | 0.0 | 0.0 |
+ Then Loan Repayment schedule has the following data in Total row:
+ | Principal due | Interest | Fees | Penalties | Due |
Paid | In advance | Late | Outstanding |
+ | 25000000.0 | 435616.44 | 0.0 | 0.0 | 25435616.44 |
25435616.44 | 23214975.73 | 2220640.71 | 0.0 |
+ Then Loan Transactions tab has the following data:
+ | Transaction date | Transaction Type | Amount | Principal |
Interest | Fees | Penalties | Loan Balance |
+ | 01 January 2026 | Disbursement | 25000000.0 | 0.0 | 0.0
| 0.0 | 0.0 | 25000000.0 |
+ | 23 February 2026 | Repayment | 25435616.44 | 25000000.0 |
435616.44 | 0.0 | 0.0 | 0.0 |
+ | 23 February 2026 | Accrual | 435616.44 | 0.0 |
435616.44 | 0.0 | 0.0 | 0.0 |
+ Then Loan is closed with zero outstanding balance and it's all
installments have obligations met
+ When Admin set
"LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL_INTEREST_RECALC"
loan product "REPAYMENT" transaction type to "NEXT_INSTALLMENT" future
installment allocation rule
+
+ @TestRailId:C4684 @AdvancedPaymentAllocation @ProgressiveLoanSchedule
+ Scenario: Verify AdvancedPaymentAllocation behaviour:
loanScheduleProcessingType-vertical, prepayment with LAST_INSTALLMENT strategy
+ When Admin sets the business date to "23 February 2026"
+ When Admin creates a client with random data
+ When Admin set
"LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL_INTEREST_RECALC"
loan product "REPAYMENT" transaction type to "LAST_INSTALLMENT" future
installment allocation rule
+ When Admin creates a fully customized loan with the following data:
+ | LoanProduct
| submitted on date | with Principal | ANNUAL interest rate % |
interest type | interest calculation period | amortization type |
loanTermFrequency | loanTermFrequencyType | repaymentEvery |
repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment |
graceOnInterestPayment | interest free period | Payment strategy |
+ |
LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL_INTEREST_RECALC
| 01 January 2026 | 25000000 | 12 |
DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 12
| MONTHS | 1 | MONTHS |
12 | 0 | 0 | 0
| ADVANCED_PAYMENT_ALLOCATION |
+ And Admin successfully approves the loan on "01 January 2026" with
"25000000" amount and expected disbursement date on "01 January 2026"
+ When Admin successfully disburse the loan on "01 January 2026" with
"25000000" EUR transaction amount
+ Then Loan Repayment schedule has 12 periods, with the following data for
periods:
+ | Nr | Days | Date | Paid date | Balance of loan |
Principal due | Interest | Fees | Penalties | Due | Paid
| In advance | Late | Outstanding |
+ | | | 01 January 2026 | | 25000000.0 |
| | 0.0 | | 0.0 | 0.0 |
| | |
+ | 1 | 31 | 01 February 2026 | | 23034153.81 |
1965846.19 | 254794.52 | 0.0 | 0.0 | 2220640.71 | 0.0
| 0.0 | 0.0 | 2220640.71 |
+ | 2 | 28 | 01 March 2026 | | 21039772.25 |
1994381.56 | 226259.15 | 0.0 | 0.0 | 2220640.71 | 0.0
| 0.0 | 0.0 | 2220640.71 |
+ | 3 | 31 | 01 April 2026 | | 19033564.29 |
2006207.96 | 214432.75 | 0.0 | 0.0 | 2220640.71 | 0.0
| 0.0 | 0.0 | 2220640.71 |
+ | 4 | 30 | 01 May 2026 | | 17000651.89 |
2032912.4 | 187728.31 | 0.0 | 0.0 | 2220640.71 | 0.0
| 0.0 | 0.0 | 2220640.71 |
+ | 5 | 31 | 01 June 2026 | | 14953278.1 |
2047373.79 | 173266.92 | 0.0 | 0.0 | 2220640.71 | 0.0
| 0.0 | 0.0 | 2220640.71 |
+ | 6 | 30 | 01 July 2026 | | 12880121.78 |
2073156.32 | 147484.39 | 0.0 | 0.0 | 2220640.71 | 0.0
| 0.0 | 0.0 | 2220640.71 |
+ | 7 | 31 | 01 August 2026 | | 10790752.45 |
2089369.33 | 131271.38 | 0.0 | 0.0 | 2220640.71 | 0.0
| 0.0 | 0.0 | 2220640.71 |
+ | 8 | 31 | 01 September 2026 | | 8680088.72 |
2110663.73 | 109976.98 | 0.0 | 0.0 | 2220640.71 | 0.0
| 0.0 | 0.0 | 2220640.71 |
+ | 9 | 30 | 01 October 2026 | | 6545059.84 |
2135028.88 | 85611.83 | 0.0 | 0.0 | 2220640.71 | 0.0
| 0.0 | 0.0 | 2220640.71 |
+ | 10 | 31 | 01 November 2026 | | 4391124.95 |
2153934.89 | 66705.82 | 0.0 | 0.0 | 2220640.71 | 0.0
| 0.0 | 0.0 | 2220640.71 |
+ | 11 | 30 | 01 December 2026 | | 2213793.97 |
2177330.98 | 43309.73 | 0.0 | 0.0 | 2220640.71 | 0.0
| 0.0 | 0.0 | 2220640.71 |
+ | 12 | 31 | 01 January 2027 | | 0.0 |
2213793.97 | 22562.5 | 0.0 | 0.0 | 2236356.47 | 0.0
| 0.0 | 0.0 | 2236356.47 |
+ Then Loan Repayment schedule has the following data in Total row:
+ | Principal due | Interest | Fees | Penalties | Due |
Paid | In advance | Late | Outstanding |
+ | 25000000.0 | 1663404.28 | 0.0 | 0.0 | 26663404.28 |
0.0 | 0.0 | 0.0 | 26663404.28 |
+ Then Loan Transactions tab has the following data:
+ | Transaction date | Transaction Type | Amount | Principal |
Interest | Fees | Penalties | Loan Balance |
+ | 01 January 2026 | Disbursement | 25000000.0 | 0.0 | 0.0
| 0.0 | 0.0 | 25000000.0 |
+ When Loan Pay-off is made on "23 February 2026"
+ Then Loan Repayment schedule has 12 periods, with the following data for
periods:
+ | Nr | Days | Date | Paid date | Balance of loan |
Principal due | Interest | Fees | Penalties | Due | Paid
| In advance | Late | Outstanding |
+ | | | 01 January 2026 | | 25000000.0 |
| | 0.0 | | 0.0 | 0.0 |
| | |
+ | 1 | 31 | 01 February 2026 | 23 February 2026 | 23034153.81 |
1965846.19 | 254794.52 | 0.0 | 0.0 | 2220640.71 | 2220640.71
| 0.0 | 2220640.71 | 0.0 |
+ | 2 | 28 | 01 March 2026 | 23 February 2026 | 22206407.14 |
827746.67 | 180821.92 | 0.0 | 0.0 | 1008568.59 | 1008568.59 |
1008568.59 | 0.0 | 0.0 |
+ | 3 | 31 | 01 April 2026 | 23 February 2026 | 19985766.43 |
2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71
| 2220640.71 | 0.0 | 0.0 |
+ | 4 | 30 | 01 May 2026 | 23 February 2026 | 17765125.72 |
2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71
| 2220640.71 | 0.0 | 0.0 |
+ | 5 | 31 | 01 June 2026 | 23 February 2026 | 15544485.01 |
2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71
| 2220640.71 | 0.0 | 0.0 |
+ | 6 | 30 | 01 July 2026 | 23 February 2026 | 13323844.3 |
2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71
| 2220640.71 | 0.0 | 0.0 |
+ | 7 | 31 | 01 August 2026 | 23 February 2026 | 11103203.59 |
2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71
| 2220640.71 | 0.0 | 0.0 |
+ | 8 | 31 | 01 September 2026 | 23 February 2026 | 8882562.88 |
2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71
| 2220640.71 | 0.0 | 0.0 |
+ | 9 | 30 | 01 October 2026 | 23 February 2026 | 6661922.17 |
2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71
| 2220640.71 | 0.0 | 0.0 |
+ | 10 | 31 | 01 November 2026 | 23 February 2026 | 4441281.46 |
2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71
| 2220640.71 | 0.0 | 0.0 |
+ | 11 | 30 | 01 December 2026 | 23 February 2026 | 2220640.75 |
2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71
| 2220640.71 | 0.0 | 0.0 |
+ | 12 | 31 | 01 January 2027 | 23 February 2026 | 0.0 |
2220640.75 | 0.0 | 0.0 | 0.0 | 2220640.75 | 2220640.75
| 2220640.75 | 0.0 | 0.0 |
+ Then Loan Repayment schedule has the following data in Total row:
+ | Principal due | Interest | Fees | Penalties | Due |
Paid | In advance | Late | Outstanding |
+ | 25000000.0 | 435616.44 | 0.0 | 0.0 | 25435616.44 |
25435616.44 | 23214975.73 | 2220640.71 | 0.0 |
+ Then Loan Transactions tab has the following data:
+ | Transaction date | Transaction Type | Amount | Principal |
Interest | Fees | Penalties | Loan Balance |
+ | 01 January 2026 | Disbursement | 25000000.0 | 0.0 | 0.0
| 0.0 | 0.0 | 25000000.0 |
+ | 23 February 2026 | Repayment | 25435616.44 | 25000000.0 |
435616.44 | 0.0 | 0.0 | 0.0 |
+ | 23 February 2026 | Accrual | 435616.44 | 0.0 |
435616.44 | 0.0 | 0.0 | 0.0 |
+ Then Loan is closed with zero outstanding balance and it's all
installments have obligations met
+ When Admin set
"LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL_INTEREST_RECALC"
loan product "REPAYMENT" transaction type to "NEXT_INSTALLMENT" future
installment allocation rule
+
+ @TestRailId:C4685 @AdvancedPaymentAllocation @ProgressiveLoanSchedule
+ Scenario: Verify AdvancedPaymentAllocation behaviour:
loanScheduleProcessingType-vertical, prepayment with NEXT_LAST_INSTALLMENT
strategy
+ When Admin sets the business date to "23 February 2026"
+ When Admin creates a client with random data
+ When Admin set
"LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL_INTEREST_RECALC"
loan product "REPAYMENT" transaction type to "NEXT_LAST_INSTALLMENT" future
installment allocation rule
+ When Admin creates a fully customized loan with the following data:
+ | LoanProduct
| submitted on date | with Principal | ANNUAL interest rate % |
interest type | interest calculation period | amortization type |
loanTermFrequency | loanTermFrequencyType | repaymentEvery |
repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment |
graceOnInterestPayment | interest free period | Payment strategy |
+ |
LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL_INTEREST_RECALC
| 01 January 2026 | 25000000 | 12 |
DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 12
| MONTHS | 1 | MONTHS |
12 | 0 | 0 | 0
| ADVANCED_PAYMENT_ALLOCATION |
+ And Admin successfully approves the loan on "01 January 2026" with
"25000000" amount and expected disbursement date on "01 January 2026"
+ When Admin successfully disburse the loan on "01 January 2026" with
"25000000" EUR transaction amount
+ Then Loan Repayment schedule has 12 periods, with the following data for
periods:
+ | Nr | Days | Date | Paid date | Balance of loan |
Principal due | Interest | Fees | Penalties | Due | Paid
| In advance | Late | Outstanding |
+ | | | 01 January 2026 | | 25000000.0 |
| | 0.0 | | 0.0 | 0.0 |
| | |
+ | 1 | 31 | 01 February 2026 | | 23034153.81 |
1965846.19 | 254794.52 | 0.0 | 0.0 | 2220640.71 | 0.0
| 0.0 | 0.0 | 2220640.71 |
+ | 2 | 28 | 01 March 2026 | | 21039772.25 |
1994381.56 | 226259.15 | 0.0 | 0.0 | 2220640.71 | 0.0
| 0.0 | 0.0 | 2220640.71 |
+ | 3 | 31 | 01 April 2026 | | 19033564.29 |
2006207.96 | 214432.75 | 0.0 | 0.0 | 2220640.71 | 0.0
| 0.0 | 0.0 | 2220640.71 |
+ | 4 | 30 | 01 May 2026 | | 17000651.89 |
2032912.4 | 187728.31 | 0.0 | 0.0 | 2220640.71 | 0.0
| 0.0 | 0.0 | 2220640.71 |
+ | 5 | 31 | 01 June 2026 | | 14953278.1 |
2047373.79 | 173266.92 | 0.0 | 0.0 | 2220640.71 | 0.0
| 0.0 | 0.0 | 2220640.71 |
+ | 6 | 30 | 01 July 2026 | | 12880121.78 |
2073156.32 | 147484.39 | 0.0 | 0.0 | 2220640.71 | 0.0
| 0.0 | 0.0 | 2220640.71 |
+ | 7 | 31 | 01 August 2026 | | 10790752.45 |
2089369.33 | 131271.38 | 0.0 | 0.0 | 2220640.71 | 0.0
| 0.0 | 0.0 | 2220640.71 |
+ | 8 | 31 | 01 September 2026 | | 8680088.72 |
2110663.73 | 109976.98 | 0.0 | 0.0 | 2220640.71 | 0.0
| 0.0 | 0.0 | 2220640.71 |
+ | 9 | 30 | 01 October 2026 | | 6545059.84 |
2135028.88 | 85611.83 | 0.0 | 0.0 | 2220640.71 | 0.0
| 0.0 | 0.0 | 2220640.71 |
+ | 10 | 31 | 01 November 2026 | | 4391124.95 |
2153934.89 | 66705.82 | 0.0 | 0.0 | 2220640.71 | 0.0
| 0.0 | 0.0 | 2220640.71 |
+ | 11 | 30 | 01 December 2026 | | 2213793.97 |
2177330.98 | 43309.73 | 0.0 | 0.0 | 2220640.71 | 0.0
| 0.0 | 0.0 | 2220640.71 |
+ | 12 | 31 | 01 January 2027 | | 0.0 |
2213793.97 | 22562.5 | 0.0 | 0.0 | 2236356.47 | 0.0
| 0.0 | 0.0 | 2236356.47 |
+ Then Loan Repayment schedule has the following data in Total row:
+ | Principal due | Interest | Fees | Penalties | Due |
Paid | In advance | Late | Outstanding |
+ | 25000000.0 | 1663404.28 | 0.0 | 0.0 | 26663404.28 |
0.0 | 0.0 | 0.0 | 26663404.28 |
+ Then Loan Transactions tab has the following data:
+ | Transaction date | Transaction Type | Amount | Principal |
Interest | Fees | Penalties | Loan Balance |
+ | 01 January 2026 | Disbursement | 25000000.0 | 0.0 | 0.0
| 0.0 | 0.0 | 25000000.0 |
+ When Loan Pay-off is made on "23 February 2026"
+ Then Loan Repayment schedule has 12 periods, with the following data for
periods:
+ | Nr | Days | Date | Paid date | Balance of loan |
Principal due | Interest | Fees | Penalties | Due | Paid
| In advance | Late | Outstanding |
+ | | | 01 January 2026 | | 25000000.0 |
| | 0.0 | | 0.0 | 0.0 |
| | |
+ | 1 | 31 | 01 February 2026 | 23 February 2026 | 23034153.81 |
1965846.19 | 254794.52 | 0.0 | 0.0 | 2220640.71 | 2220640.71
| 0.0 | 2220640.71 | 0.0 |
+ | 2 | 28 | 01 March 2026 | 23 February 2026 | 20813513.1 |
2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71
| 2220640.71 | 0.0 | 0.0 |
+ | 3 | 31 | 01 April 2026 | 23 February 2026 | 19985766.43 |
827746.67 | 180821.92 | 0.0 | 0.0 | 1008568.59 | 1008568.59 |
1008568.59 | 0.0 | 0.0 |
+ | 4 | 30 | 01 May 2026 | 23 February 2026 | 17765125.72 |
2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71
| 2220640.71 | 0.0 | 0.0 |
+ | 5 | 31 | 01 June 2026 | 23 February 2026 | 15544485.01 |
2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71
| 2220640.71 | 0.0 | 0.0 |
+ | 6 | 30 | 01 July 2026 | 23 February 2026 | 13323844.3 |
2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71
| 2220640.71 | 0.0 | 0.0 |
+ | 7 | 31 | 01 August 2026 | 23 February 2026 | 11103203.59 |
2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71
| 2220640.71 | 0.0 | 0.0 |
+ | 8 | 31 | 01 September 2026 | 23 February 2026 | 8882562.88 |
2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71
| 2220640.71 | 0.0 | 0.0 |
+ | 9 | 30 | 01 October 2026 | 23 February 2026 | 6661922.17 |
2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71
| 2220640.71 | 0.0 | 0.0 |
+ | 10 | 31 | 01 November 2026 | 23 February 2026 | 4441281.46 |
2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71
| 2220640.71 | 0.0 | 0.0 |
+ | 11 | 30 | 01 December 2026 | 23 February 2026 | 2220640.75 |
2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71
| 2220640.71 | 0.0 | 0.0 |
+ | 12 | 31 | 01 January 2027 | 23 February 2026 | 0.0 |
2220640.75 | 0.0 | 0.0 | 0.0 | 2220640.75 | 2220640.75
| 2220640.75 | 0.0 | 0.0 |
+ Then Loan Repayment schedule has the following data in Total row:
+ | Principal due | Interest | Fees | Penalties | Due |
Paid | In advance | Late | Outstanding |
+ | 25000000.0 | 435616.44 | 0.0 | 0.0 | 25435616.44 |
25435616.44 | 23214975.73 | 2220640.71 | 0.0 |
+ Then Loan Transactions tab has the following data:
+ | Transaction date | Transaction Type | Amount | Principal |
Interest | Fees | Penalties | Loan Balance |
+ | 01 January 2026 | Disbursement | 25000000.0 | 0.0 | 0.0
| 0.0 | 0.0 | 25000000.0 |
+ | 23 February 2026 | Repayment | 25435616.44 | 25000000.0 |
435616.44 | 0.0 | 0.0 | 0.0 |
+ | 23 February 2026 | Accrual | 435616.44 | 0.0 |
435616.44 | 0.0 | 0.0 | 0.0 |
+ Then Loan is closed with zero outstanding balance and it's all
installments have obligations met
+ When Admin set
"LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL_INTEREST_RECALC"
loan product "REPAYMENT" transaction type to "NEXT_INSTALLMENT" future
installment allocation rule
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
index d66c6bb2c6..eed2f08dc3 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
@@ -1126,6 +1126,16 @@ public class LoanRepaymentScheduleInstallment extends
AbstractAuditableWithUTCDa
MathUtil.nullToZero(MathUtil.add(getPrincipal(),
getInterestCharged(), getFeeChargesCharged(), getPenaltyCharges())));
}
+ public boolean isOutstandingBalanceNotZero(AllocationType allocationType,
MonetaryCurrency currency) {
+ Money balance = switch (allocationType) {
+ case PENALTY -> this.getPenaltyChargesOutstanding(currency);
+ case FEE -> this.getFeeChargesOutstanding(currency);
+ case PRINCIPAL -> this.getPrincipalOutstanding(currency);
+ case INTEREST -> this.getInterestOutstanding(currency);
+ };
+ return MathUtil.isGreaterThanZero(balance);
+ }
+
public void copyFrom(final LoanScheduleModelPeriod period) {
// Reset fields and relations
resetBalances();
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
index 297ce1b0e4..5538605d6e 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
@@ -1715,7 +1715,7 @@ public final class LoanApplicationTerms {
repaymentEvery, numberOfRepayments,
isInterestChargedFromDateSameAsDisbursalDateEnabled != null &&
isInterestChargedFromDateSameAsDisbursalDateEnabled,
daysInYearCustomStrategy,
allowPartialPeriodInterestCalculation, interestRecalculationEnabled,
recalculationFrequencyType,
- preClosureInterestCalculationStrategy,
allowFullTermForTranche);
+ preClosureInterestCalculationStrategy,
allowFullTermForTranche, loanScheduleProcessingType);
}
public Integer getLoanTermFrequency() {
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanConfigurationDetails.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanConfigurationDetails.java
index 977e9a513c..3fdede017f 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanConfigurationDetails.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanConfigurationDetails.java
@@ -25,6 +25,7 @@ import
org.apache.fineract.portfolio.common.domain.DaysInMonthType;
import
org.apache.fineract.portfolio.common.domain.DaysInYearCustomStrategyType;
import org.apache.fineract.portfolio.common.domain.DaysInYearType;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod;
import
org.apache.fineract.portfolio.loanproduct.domain.ILoanConfigurationDetails;
import
org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
@@ -60,6 +61,8 @@ public class LoanConfigurationDetails implements
ILoanConfigurationDetails {
private final LoanPreCloseInterestCalculationStrategy
preCloseInterestCalculationStrategy;
@Getter
private final boolean allowFullTermForTranche;
+ @Getter
+ private final LoanScheduleProcessingType loanScheduleProcessingType;
public LoanConfigurationDetails(CurrencyData currency, BigDecimal
interestRatePerPeriod, BigDecimal annualNominalInterestRate,
Integer interestChargingGrace, Integer interestPaymentGrace,
Integer principalGrace,
@@ -69,7 +72,8 @@ public class LoanConfigurationDetails implements
ILoanConfigurationDetails {
Integer numberOfRepayments, boolean
interestRecognitionOnDisbursementDate,
DaysInYearCustomStrategyType daysInYearCustomStrategy, boolean
allowPartialPeriodInterestCalculation,
boolean isInterestRecalculationEnabled, RecalculationFrequencyType
restFrequencyType,
- LoanPreCloseInterestCalculationStrategy
preCloseInterestCalculationStrategy, boolean allowFullTermForTranche) {
+ LoanPreCloseInterestCalculationStrategy
preCloseInterestCalculationStrategy, boolean allowFullTermForTranche,
+ LoanScheduleProcessingType loanScheduleProcessingType) {
this.currency = currency;
this.interestRatePerPeriod = interestRatePerPeriod;
this.annualNominalInterestRate = annualNominalInterestRate;
@@ -92,6 +96,7 @@ public class LoanConfigurationDetails implements
ILoanConfigurationDetails {
this.restFrequencyType = restFrequencyType;
this.preCloseInterestCalculationStrategy =
preCloseInterestCalculationStrategy;
this.allowFullTermForTranche = allowFullTermForTranche;
+ this.loanScheduleProcessingType = loanScheduleProcessingType;
}
private Integer defaultToNullIfZero(final Integer value) {
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/ILoanConfigurationDetails.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/ILoanConfigurationDetails.java
index 4ec89f0fd0..79fe75bc9e 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/ILoanConfigurationDetails.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/ILoanConfigurationDetails.java
@@ -22,6 +22,7 @@ import java.math.BigDecimal;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
import
org.apache.fineract.portfolio.common.domain.DaysInYearCustomStrategyType;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
/**
* Represents the bare minimum repayment details needed for activities related
to generating repayment schedules.
@@ -75,4 +76,6 @@ public interface ILoanConfigurationDetails {
LoanPreCloseInterestCalculationStrategy
getPreCloseInterestCalculationStrategy();
boolean isAllowFullTermForTranche();
+
+ LoanScheduleProcessingType getLoanScheduleProcessingType();
}
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
index ea0aeedb42..5a93999d7c 100644
---
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
@@ -2917,6 +2917,7 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
private Money processPeriodsVertically(LoanTransaction loanTransaction,
TransactionCtx ctx, Money transactionAmountUnprocessed,
LoanPaymentAllocationRule paymentAllocationRule,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings,
Balances balances) {
+ Loan loan = loanTransaction.getLoan();
VerticalPaymentAllocationContext paymentAllocationContext = new
VerticalPaymentAllocationContext(ctx, loanTransaction,
paymentAllocationRule.getFutureInstallmentAllocationRule(),
transactionMappings, balances);
paymentAllocationContext.setTransactionAmountUnprocessed(transactionAmountUnprocessed);
@@ -2924,6 +2925,18 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
paymentAllocationContext.setAllocatedAmount(Money.zero(ctx.getCurrency()));
paymentAllocationContext.setInstallment(null);
paymentAllocationContext.setPaymentAllocationType(paymentAllocationType);
+ if (isInterestRecalculationSupported(ctx,
loanTransaction.getLoan())) {
+ // Clear any previously skipped installments before
re-evaluating
+ ProgressiveTransactionCtx progressiveTransactionCtx =
(ProgressiveTransactionCtx) ctx;
+
progressiveTransactionCtx.getSkipRepaymentScheduleInstallments().clear();
+ paymentAllocationContext
+ .setInAdvanceInstallmentsFilteringRules(installment ->
loanTransaction.isBefore(installment.getDueDate())
+ &&
installment.isOutstandingBalanceNotZero(paymentAllocationType.getAllocationType(),
ctx.getCurrency())
+ &&
!progressiveTransactionCtx.getSkipRepaymentScheduleInstallments().contains(installment));
+ } else {
+
paymentAllocationContext.setInAdvanceInstallmentsFilteringRules(getFilterPredicate(
+ paymentAllocationContext.getPaymentAllocationType(),
paymentAllocationContext.getCtx().getCurrency()));
+ }
LoopGuard.runSafeDoWhileLoop(paymentAllocationContext.getCtx().getInstallments().size()
* 100, //
paymentAllocationContext, //
(VerticalPaymentAllocationContext context) ->
context.getInstallment() != null
@@ -2944,11 +2957,20 @@ public class
AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
LoanTransactionToRepaymentScheduleMapping
loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
context.getTransactionMappings(),
context.getLoanTransaction(), context.getInstallment(),
context.getCtx().getCurrency());
- context.setAllocatedAmount(
-
processPaymentAllocation(context.getPaymentAllocationType(),
context.getInstallment(),
-
context.getLoanTransaction(), context.getTransactionAmountUnprocessed(),
-
loanTransactionToRepaymentScheduleMapping, oldestPastDueInstallmentCharges,
- context.getBalances(),
LoanRepaymentScheduleInstallment.PaymentAction.PAY));
+
+ if
(isInterestRecalculationSupported(context.getCtx(), loan)) {
+
context.setAllocatedAmount(handlingPaymentAllocationForInterestBearingProgressiveLoan(
+ context.getLoanTransaction(),
context.getTransactionAmountUnprocessed(),
+ context.getBalances(),
paymentAllocationType, context.getInstallment(),
+ (ProgressiveTransactionCtx)
context.getCtx(), loanTransactionToRepaymentScheduleMapping,
+
oldestPastDueInstallmentCharges));
+ } else {
+ context.setAllocatedAmount(
+
processPaymentAllocation(context.getPaymentAllocationType(),
context.getInstallment(),
+
context.getLoanTransaction(), context.getTransactionAmountUnprocessed(),
+
loanTransactionToRepaymentScheduleMapping, oldestPastDueInstallmentCharges,
+ context.getBalances(),
LoanRepaymentScheduleInstallment.PaymentAction.PAY));
+ }
context.setTransactionAmountUnprocessed(
context.getTransactionAmountUnprocessed().minus(context.getAllocatedAmount()));
}
@@ -2963,11 +2985,19 @@ public class
AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
LoanTransactionToRepaymentScheduleMapping
loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
context.getTransactionMappings(),
context.getLoanTransaction(), context.getInstallment(),
context.getCtx().getCurrency());
- context.setAllocatedAmount(
-
processPaymentAllocation(context.getPaymentAllocationType(),
context.getInstallment(),
-
context.getLoanTransaction(), context.getTransactionAmountUnprocessed(),
-
loanTransactionToRepaymentScheduleMapping, dueInstallmentCharges,
context.getBalances(),
-
LoanRepaymentScheduleInstallment.PaymentAction.PAY));
+ if
(isInterestRecalculationSupported(context.getCtx(), loan)) {
+
context.setAllocatedAmount(handlingPaymentAllocationForInterestBearingProgressiveLoan(
+ context.getLoanTransaction(),
context.getTransactionAmountUnprocessed(),
+ context.getBalances(),
paymentAllocationType, context.getInstallment(),
+ (ProgressiveTransactionCtx)
context.getCtx(), loanTransactionToRepaymentScheduleMapping,
+ dueInstallmentCharges));
+ } else {
+ context.setAllocatedAmount(
+
processPaymentAllocation(context.getPaymentAllocationType(),
context.getInstallment(),
+
context.getLoanTransaction(), context.getTransactionAmountUnprocessed(),
+
loanTransactionToRepaymentScheduleMapping, dueInstallmentCharges,
+ context.getBalances(),
LoanRepaymentScheduleInstallment.PaymentAction.PAY));
+ }
context.setTransactionAmountUnprocessed(
context.getTransactionAmountUnprocessed().minus(context.getAllocatedAmount()));
}
@@ -2979,17 +3009,20 @@ public class
AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
// element.
List<LoanRepaymentScheduleInstallment>
currentInstallments = new ArrayList<>();
if
(FutureInstallmentAllocationRule.REAMORTIZATION.equals(context.getFutureInstallmentAllocationRule()))
{
- currentInstallments =
context.getCtx().getInstallments().stream().filter(predicate)
+ currentInstallments =
context.getCtx().getInstallments().stream()
+
.filter(paymentAllocationContext.inAdvanceInstallmentsFilteringRules)
.filter(e ->
context.getLoanTransaction().isBefore(e.getDueDate())).toList();
} else if
(FutureInstallmentAllocationRule.NEXT_INSTALLMENT
.equals(context.getFutureInstallmentAllocationRule())) {
- currentInstallments =
context.getCtx().getInstallments().stream().filter(predicate)
+ currentInstallments =
context.getCtx().getInstallments().stream()
+
.filter(paymentAllocationContext.inAdvanceInstallmentsFilteringRules)
.filter(e ->
context.getLoanTransaction().isBefore(e.getDueDate()))
.min(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).stream()
.toList();
} else if
(FutureInstallmentAllocationRule.LAST_INSTALLMENT
.equals(context.getFutureInstallmentAllocationRule())) {
- currentInstallments =
context.getCtx().getInstallments().stream().filter(predicate)
+ currentInstallments =
context.getCtx().getInstallments().stream()
+
.filter(paymentAllocationContext.inAdvanceInstallmentsFilteringRules)
.filter(e ->
context.getLoanTransaction().isBefore(e.getDueDate()))
.max(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).stream()
.toList();
@@ -2998,14 +3031,16 @@ public class
AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
// get current installment where from date
< transaction date < to date OR
// transaction date
// is on first installment's first day (
from day )
- currentInstallments =
context.getCtx().getInstallments().stream().filter(predicate)
+ currentInstallments =
context.getCtx().getInstallments().stream()
+
.filter(paymentAllocationContext.inAdvanceInstallmentsFilteringRules)
.filter(e ->
context.getLoanTransaction().isBefore(e.getDueDate()))
.filter(f ->
context.getLoanTransaction().isAfter(f.getFromDate())
||
context.getLoanTransaction().isOn(f.getFromDate()))
.toList();
// if there is no current in advance
installment resolve similar to LAST_INSTALLMENT
if (currentInstallments.isEmpty()) {
- currentInstallments =
context.getCtx().getInstallments().stream().filter(predicate)
+ currentInstallments =
context.getCtx().getInstallments().stream()
+
.filter(paymentAllocationContext.inAdvanceInstallmentsFilteringRules)
.filter(e ->
context.getLoanTransaction().isBefore(e.getDueDate()))
.max(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).stream()
.toList();
@@ -3034,18 +3069,37 @@ public class
AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
LoanTransactionToRepaymentScheduleMapping
loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
context.getTransactionMappings(), context.getLoanTransaction(),
context.getInstallment(),
context.getCtx().getCurrency());
- Money internalPaidPortion =
processPaymentAllocation(context.getPaymentAllocationType(),
- context.getInstallment(),
context.getLoanTransaction(), evenPortion,
-
loanTransactionToRepaymentScheduleMapping, inAdvanceInstallmentCharges,
- context.getBalances(),
LoanRepaymentScheduleInstallment.PaymentAction.PAY);
- // Some extra logic to allocate as
much as possible across the installments if
- // the
- // outstanding balances are different
- if
(internalPaidPortion.isGreaterThanZero()) {
-
context.setAllocatedAmount(internalPaidPortion);
+ if
(isInterestRecalculationSupported(context.getCtx(), loan)) {
+ Money internalPaidPortion =
handlingPaymentAllocationForInterestBearingProgressiveLoan(
+
context.getLoanTransaction(), context.getTransactionAmountUnprocessed(),
+ context.getBalances(),
paymentAllocationType, context.getInstallment(),
+
(ProgressiveTransactionCtx) context.getCtx(),
loanTransactionToRepaymentScheduleMapping,
+
inAdvanceInstallmentCharges);
+ // Some extra logic to allocate as
much as possible across the installments
+ // if
+ // the
+ // outstanding balances are
different
+ if
(internalPaidPortion.isGreaterThanZero()) {
+
context.setAllocatedAmount(internalPaidPortion);
+ }
+
context.setTransactionAmountUnprocessed(
+
context.getTransactionAmountUnprocessed().minus(internalPaidPortion));
+ } else {
+ Money internalPaidPortion =
processPaymentAllocation(context.getPaymentAllocationType(),
+ context.getInstallment(),
context.getLoanTransaction(), evenPortion,
+
loanTransactionToRepaymentScheduleMapping, inAdvanceInstallmentCharges,
+ context.getBalances(),
LoanRepaymentScheduleInstallment.PaymentAction.PAY);
+
+ // Some extra logic to allocate as
much as possible across the installments
+ // if
+ // the
+ // outstanding balances are
different
+ if
(internalPaidPortion.isGreaterThanZero()) {
+
context.setAllocatedAmount(internalPaidPortion);
+ }
+
context.setTransactionAmountUnprocessed(
+
context.getTransactionAmountUnprocessed().minus(internalPaidPortion));
}
-
context.setTransactionAmountUnprocessed(
-
context.getTransactionAmountUnprocessed().minus(internalPaidPortion));
}
} else {
context.setInstallment(null);
@@ -3569,6 +3623,7 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
private Money transactionAmountUnprocessed;
private Money allocatedAmount;
private PaymentAllocationType paymentAllocationType;
+ private Predicate<LoanRepaymentScheduleInstallment>
inAdvanceInstallmentsFilteringRules;
VerticalPaymentAllocationContext(TransactionCtx ctx, LoanTransaction
loanTransaction,
FutureInstallmentAllocationRule
futureInstallmentAllocationRule,
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanConfigurationDetailsMapper.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanConfigurationDetailsMapper.java
index 487c7069ed..0254a6a51a 100644
---
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanConfigurationDetailsMapper.java
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanConfigurationDetailsMapper.java
@@ -57,7 +57,7 @@ public final class LoanConfigurationDetailsMapper {
loanProductRelatedDetail.getNumberOfRepayments(),
loanProductRelatedDetail.isInterestRecognitionOnDisbursementDate(),
loanProductRelatedDetail.getDaysInYearCustomStrategy(),
loanProductRelatedDetail.isAllowPartialPeriodInterestCalculation(),
loan.isInterestRecalculationEnabled(),
getRestFrequencyType(loan), getPreCloseInterestCalculationStrategy(loan),
- loan.isAllowFullTermForTranche());
+ loan.isAllowFullTermForTranche(),
loan.getLoanProductRelatedDetail().getLoanScheduleProcessingType());
}
private static RecalculationFrequencyType getRestFrequencyType(Loan loan) {
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java
index cf7cd8c34f..b5de8612bb 100644
---
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java
@@ -54,6 +54,7 @@ import
org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeInteres
import
org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter;
import
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
import
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelRepaymentPeriod;
+import
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
import
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.ScheduledDateGenerator;
import org.apache.fineract.portfolio.loanproduct.calc.data.EmiAdjustment;
import org.apache.fineract.portfolio.loanproduct.calc.data.EmiChangeOperation;
@@ -524,10 +525,15 @@ public final class ProgressiveEMICalculator implements
EMICalculator {
false)); //
}
}
-
+ Money duePrincipal = repaymentPeriod.getDuePrincipal();
+ Money dueInterest = repaymentPeriod.getDueInterest();
+ if
(scheduleModel.loanProductRelatedDetail().getLoanScheduleProcessingType() ==
LoanScheduleProcessingType.VERTICAL
+ && notFullyRepaidRepaymentPeriodCount > 1) {
+ duePrincipal =
repaymentPeriod.getEmiPlusCreditedAmountsPlusFutureUnrecognizedInterest();
+ }
return new PeriodDueDetails(repaymentPeriod.getEmi(), //
- repaymentPeriod.getDuePrincipal(), //
- repaymentPeriod.getDueInterest()); //
+ duePrincipal, //
+ dueInterest); //
}
@Override