FINERACT-2354: In case full transaction reprocessing, we need to recalculate the schedule as well

This commit is contained in:
Adam Saghy
2025-11-13 08:44:22 +01:00
parent 051571003e
commit c79c076ee6
39 changed files with 361 additions and 479 deletions
@@ -20,6 +20,7 @@ package org.apache.fineract.organisation.monetary.domain;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Embeddable; import jakarta.persistence.Embeddable;
import lombok.AccessLevel;
import lombok.Getter; import lombok.Getter;
import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.monetary.data.CurrencyData;
@@ -36,6 +37,7 @@ public class MonetaryCurrency {
@Column(name = "currency_multiplesof") @Column(name = "currency_multiplesof")
private Integer inMultiplesOf; private Integer inMultiplesOf;
@Getter(AccessLevel.PRIVATE)
private transient CurrencyData currencyData; private transient CurrencyData currencyData;
protected MonetaryCurrency() { protected MonetaryCurrency() {
@@ -21,6 +21,7 @@ package org.apache.fineract.organisation.monetary.domain;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.MathContext; import java.math.MathContext;
import java.util.Iterator; import java.util.Iterator;
import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.monetary.data.CurrencyData;
public class Money implements Comparable<Money> { public class Money implements Comparable<Money> {
@@ -45,9 +46,8 @@ public class Money implements Comparable<Money> {
// round monetary amounts into multiples of say 20/50. // round monetary amounts into multiples of say 20/50.
if (currency.getInMultiplesOf() != null && currency.getDecimalPlaces() == 0 && currency.getInMultiplesOf() > 0 if (currency.getInMultiplesOf() != null && currency.getDecimalPlaces() == 0 && currency.getInMultiplesOf() > 0
&& amountScaled.doubleValue() > 0) { && MathUtil.isGreaterThanZero(amountScaled)) {
final double existingVal = amountScaled.doubleValue(); amountScaled = roundToMultiplesOf(amountScaled, currency.getInMultiplesOf());
amountScaled = BigDecimal.valueOf(roundToMultiplesOf(existingVal, currency.getInMultiplesOf()));
} }
this.amount = amountScaled.setScale(currency.getDecimalPlaces(), getMc().getRoundingMode()); this.amount = amountScaled.setScale(currency.getDecimalPlaces(), getMc().getRoundingMode());
} }
@@ -8394,13 +8394,13 @@ Feature: Loan
| 2 | 15 | 31 January 2025 | | 200.59 | 49.78 | 0.73 | 0.0 | 0.0 | 50.51 | 0.0 | 0.0 | 0.0 | 50.51 | | 2 | 15 | 31 January 2025 | | 200.59 | 49.78 | 0.73 | 0.0 | 0.0 | 50.51 | 0.0 | 0.0 | 0.0 | 50.51 |
| | | 01 February 2025 | | 500.0 | | | 0.0 | | 0.0 | | | | 0.0 | | | | 01 February 2025 | | 500.0 | | | 0.0 | | 0.0 | | | | 0.0 |
| | | 01 February 2025 | | 200.0 | | | 0.0 | | 0.0 | | | | 0.0 | | | | 01 February 2025 | | 200.0 | | | 0.0 | | 0.0 | | | | 0.0 |
| 3 | 15 | 15 February 2025 | | 850.67 | 49.92 | 0.59 | 0.0 | 0.0 | 50.51 | 0.0 | 0.0 | 0.0 | 50.51 | | 3 | 15 | 15 February 2025 | | 676.32 | 224.27 | 2.49 | 0.0 | 0.0 | 226.76 | 0.0 | 0.0 | 0.0 | 226.76 |
| 4 | 15 | 02 March 2025 | | 800.6 | 50.07 | 0.44 | 0.0 | 0.0 | 50.51 | 0.0 | 0.0 | 0.0 | 50.51 | | 4 | 15 | 02 March 2025 | | 451.53 | 224.79 | 1.97 | 0.0 | 0.0 | 226.76 | 0.0 | 0.0 | 0.0 | 226.76 |
| 5 | 15 | 17 March 2025 | | 750.38 | 50.22 | 0.29 | 0.0 | 0.0 | 50.51 | 0.0 | 0.0 | 0.0 | 50.51 | | 5 | 15 | 17 March 2025 | | 226.09 | 225.44 | 1.32 | 0.0 | 0.0 | 226.76 | 0.0 | 0.0 | 0.0 | 226.76 |
| 6 | 15 | 01 April 2025 | | 700.0 | 50.38 | 0.15 | 0.0 | 0.0 | 50.53 | 0.0 | 0.0 | 0.0 | 50.53 | | 6 | 15 | 01 April 2025 | | 0.0 | 226.09 | 0.66 | 0.0 | 0.0 | 226.75 | 0.0 | 0.0 | 0.0 | 226.75 |
Then Loan Repayment schedule has the following data in Total row: Then Loan Repayment schedule has the following data in Total row:
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| 300.0 | 3.08 | 0.0 | 0.0 | 303.08 | 0.0 | 0.0 | 0.0 | 303.08 | | 1000.00 | 8.05 | 0.0 | 0.0 | 1008.05 | 0.0 | 0.0 | 0.0 | 1008.05 |
Then Loan Tranche Details tab has the following data: Then Loan Tranche Details tab has the following data:
| Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount |
| 01 January 2025 | | 300.0 | | | 01 January 2025 | | 300.0 | |
@@ -8860,4 +8860,4 @@ Feature: Loan
| 21 October 2025 | Accrual Activity | 3.32 | 0.0 | 0.52 | 0.0 | 2.8 | 0.0 | false | true | | 21 October 2025 | Accrual Activity | 3.32 | 0.0 | 0.52 | 0.0 | 2.8 | 0.0 | false | true |
| 06 November 2025 | Accrual | 1.21 | 0.0 | 1.21 | 0.0 | 0.0 | 0.0 | false | false | | 06 November 2025 | Accrual | 1.21 | 0.0 | 1.21 | 0.0 | 0.0 | 0.0 | false | false |
And Customer makes "AUTOPAY" repayment on "06 November 2025" with 35.28 EUR transaction amount And Customer makes "AUTOPAY" repayment on "06 November 2025" with 35.28 EUR transaction amount
Then Loan status will be "CLOSED_OBLIGATIONS_MET" Then Loan status will be "CLOSED_OBLIGATIONS_MET"
@@ -4634,10 +4634,10 @@ Feature: LoanReAging
| 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false |
| 01 March 2024 | Accrual | 1.07 | 0.0 | 1.07 | 0.0 | 0.0 | 0.0 | false | false | | 01 March 2024 | Accrual | 1.07 | 0.0 | 1.07 | 0.0 | 0.0 | 0.0 | false | false |
| 01 March 2024 | Contract Termination | 84.06 | 83.57 | 0.49 | 0.0 | 0.0 | 0.0 | false | false | | 01 March 2024 | Contract Termination | 84.06 | 83.57 | 0.49 | 0.0 | 0.0 | 0.0 | false | false |
When Admin sets the business date to "15 April 2024" # When Admin sets the business date to "15 April 2024"
Then Admin fails to create a Loan re-aging transaction with the following data because loan was contract terminated: # Then Admin fails to create a Loan re-aging transaction with the following data because loan was contract terminated:
| frequencyNumber | frequencyType | startDate | numberOfInstallments | # | frequencyNumber | frequencyType | startDate | numberOfInstallments |
| 1 | MONTHS | 01 May 2024 | 6 | # | 1 | MONTHS | 01 May 2024 | 6 |
@TestRailId:C4090 @AdvancedPaymentAllocation @TestRailId:C4090 @AdvancedPaymentAllocation
Scenario: Verify that Re-aging is forbidden on charged-off loan, interest bearing loan, Interest calculation: Default Behavior, Charge-off scenario (accelerate maturity) - UC11 Scenario: Verify that Re-aging is forbidden on charged-off loan, interest bearing loan, Interest calculation: Default Behavior, Charge-off scenario (accelerate maturity) - UC11
@@ -5014,6 +5014,8 @@ Feature: LoanReAging
| 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false |
| 15 March 2024 | Re-age | 84.28 | 83.57 | 0.71 | 0.0 | 0.0 | 0.0 | true | | 15 March 2024 | Re-age | 84.28 | 83.57 | 0.71 | 0.0 | 0.0 | 0.0 | true |
| 15 March 2024 | Interest waive | 0.71 | 0.0 | 0.71 | 0.0 | 0.0 | 0.0 | true | | 15 March 2024 | Interest waive | 0.71 | 0.0 | 0.71 | 0.0 | 0.0 | 0.0 | true |
When Loan Pay-off is made on "01 April 2024"
Then Loan is closed with zero outstanding balance and it's all installments have obligations met
@Skip @TestRailId:C4198 @AdvancedPaymentAllocation @Skip @TestRailId:C4198 @AdvancedPaymentAllocation
Scenario: Verify Re-aging reversal on interest bearing loan - UC3: Interest handling: EQUAL_AMORTIZATION_PAYABLE_INTEREST Scenario: Verify Re-aging reversal on interest bearing loan - UC3: Interest handling: EQUAL_AMORTIZATION_PAYABLE_INTEREST
@@ -5120,6 +5122,8 @@ Feature: LoanReAging
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
| 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false |
| 15 March 2024 | Re-age | 84.28 | 83.57 | 0.71 | 0.0 | 0.0 | 0.0 | true | | 15 March 2024 | Re-age | 84.28 | 83.57 | 0.71 | 0.0 | 0.0 | 0.0 | true |
When Loan Pay-off is made on "01 April 2024"
Then Loan is closed with zero outstanding balance and it's all installments have obligations met
@Skip @TestRailId:C4199 @AdvancedPaymentAllocation @Skip @TestRailId:C4199 @AdvancedPaymentAllocation
Scenario: Verify Re-aging reversal on interest bearing loan - UC4: Interest handling: EQUAL_AMORTIZATION_FULL_INTEREST Scenario: Verify Re-aging reversal on interest bearing loan - UC4: Interest handling: EQUAL_AMORTIZATION_FULL_INTEREST
@@ -5226,6 +5230,8 @@ Feature: LoanReAging
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
| 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false |
| 15 March 2024 | Re-age | 85.08 | 83.57 | 1.51 | 0.0 | 0.0 | 0.0 | true | | 15 March 2024 | Re-age | 85.08 | 83.57 | 1.51 | 0.0 | 0.0 | 0.0 | true |
When Loan Pay-off is made on "01 April 2024"
Then Loan is closed with zero outstanding balance and it's all installments have obligations met
@TestRailId:C4202 @AdvancedPaymentAllocation @TestRailId:C4202 @AdvancedPaymentAllocation
Scenario: Verify Re-aging reversal on interest bearing loan - UC5: Interest handling: DEFAULT, re-aging is NOT the latest transaction on loan Scenario: Verify Re-aging reversal on interest bearing loan - UC5: Interest handling: DEFAULT, re-aging is NOT the latest transaction on loan
@@ -5331,7 +5337,7 @@ Feature: LoanReAging
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false |
| 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false |
| 15 March 2024 | Re-age | 84.28 | 83.57 | 0.71 | 0.0 | 0.0 | 0.0 | false | true | | 15 March 2024 | Re-age | 84.28 | 83.57 | 0.71 | 0.0 | 0.0 | 0.0 | false | false |
| 16 March 2024 | Repayment | 14.3 | 13.57 | 0.73 | 0.0 | 0.0 | 70.0 | false | false | | 16 March 2024 | Repayment | 14.3 | 13.57 | 0.73 | 0.0 | 0.0 | 70.0 | false | false |
# --- Reversal of re-age transaction --- # --- Reversal of re-age transaction ---
When Admin sets the business date to "01 April 2024" When Admin sets the business date to "01 April 2024"
@@ -5352,7 +5358,7 @@ Feature: LoanReAging
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false |
| 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false |
| 15 March 2024 | Re-age | 84.28 | 83.57 | 0.71 | 0.0 | 0.0 | 0.0 | true | true | | 15 March 2024 | Re-age | 84.28 | 83.57 | 0.71 | 0.0 | 0.0 | 0.0 | true | false |
| 16 March 2024 | Repayment | 14.3 | 13.81 | 0.49 | 0.0 | 0.0 | 69.76 | false | true | | 16 March 2024 | Repayment | 14.3 | 13.81 | 0.49 | 0.0 | 0.0 | 69.76 | false | true |
When Loan Pay-off is made on "01 April 2024" When Loan Pay-off is made on "01 April 2024"
Then Loan is closed with zero outstanding balance and it's all installments have obligations met Then Loan is closed with zero outstanding balance and it's all installments have obligations met
@@ -5416,12 +5422,12 @@ Feature: LoanReAging
| | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | |
| 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 |
| 2 | 29 | 01 March 2024 | 01 March 2024 | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | | 2 | 29 | 01 March 2024 | 01 March 2024 | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 |
| 3 | 31 | 01 April 2024 | | 56.0 | 11.05 | 0.39 | 0.0 | 0.0 | 11.44 | 0.0 | 0.0 | 0.0 | 11.44 | | 3 | 31 | 01 April 2024 | | 56.04 | 11.01 | 0.39 | 0.0 | 0.0 | 11.4 | 0.0 | 0.0 | 0.0 | 11.4 |
| 4 | 30 | 01 May 2024 | | 44.95 | 11.05 | 0.39 | 0.0 | 0.0 | 11.44 | 0.0 | 0.0 | 0.0 | 11.44 | | 4 | 30 | 01 May 2024 | | 45.03 | 11.01 | 0.39 | 0.0 | 0.0 | 11.4 | 0.0 | 0.0 | 0.0 | 11.4 |
| 5 | 31 | 01 June 2024 | | 33.9 | 11.05 | 0.39 | 0.0 | 0.0 | 11.44 | 0.0 | 0.0 | 0.0 | 11.44 | | 5 | 31 | 01 June 2024 | | 34.02 | 11.01 | 0.39 | 0.0 | 0.0 | 11.4 | 0.0 | 0.0 | 0.0 | 11.4 |
| 6 | 30 | 01 July 2024 | | 22.66 | 11.24 | 0.2 | 0.0 | 0.0 | 11.44 | 0.0 | 0.0 | 0.0 | 11.44 | | 6 | 30 | 01 July 2024 | | 22.82 | 11.2 | 0.2 | 0.0 | 0.0 | 11.4 | 0.0 | 0.0 | 0.0 | 11.4 |
| 7 | 31 | 01 August 2024 | | 11.35 | 11.31 | 0.13 | 0.0 | 0.0 | 11.44 | 0.0 | 0.0 | 0.0 | 11.44 | | 7 | 31 | 01 August 2024 | | 11.55 | 11.27 | 0.13 | 0.0 | 0.0 | 11.4 | 0.0 | 0.0 | 0.0 | 11.4 |
| 8 | 31 | 01 September 2024| | 0.0 | 11.35 | 0.07 | 0.0 | 0.0 | 11.42 | 0.0 | 0.0 | 0.0 | 11.42 | | 8 | 31 | 01 September 2024| | 0.0 | 11.55 | 0.07 | 0.0 | 0.0 | 11.62 | 0.0 | 0.0 | 0.0 | 11.62 |
Then Loan Repayment schedule has the following data in Total row: Then Loan Repayment schedule has the following data in Total row:
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| 100.0 | 2.64 | 0.0 | 0.0 | 102.64 | 34.02 | 0.0 | 0.0 | 68.62 | | 100.0 | 2.64 | 0.0 | 0.0 | 102.64 | 34.02 | 0.0 | 0.0 | 68.62 |
@@ -5430,7 +5436,7 @@ Feature: LoanReAging
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false |
| 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false |
| 01 March 2024 | Repayment | 17.01 | 16.52 | 0.49 | 0.0 | 0.0 | 67.05 | false | false | | 01 March 2024 | Repayment | 17.01 | 16.52 | 0.49 | 0.0 | 0.0 | 67.05 | false | false |
| 01 April 2024 | Re-age | 67.44 | 67.05 | 0.39 | 0.0 | 0.0 | 0.0 | false | true | | 01 April 2024 | Re-age | 67.44 | 67.05 | 0.39 | 0.0 | 0.0 | 0.0 | false | false |
@Skip @Skip
@TestRailId:C4154 @AdvancedPaymentAllocation @TestRailId:C4154 @AdvancedPaymentAllocation
@@ -5508,7 +5514,7 @@ Feature: LoanReAging
| frequencyNumber | frequencyType | startDate | numberOfInstallments | | frequencyNumber | frequencyType | startDate | numberOfInstallments |
| 1 | MONTHS | 31 December 2023 | 6 | | 1 | MONTHS | 31 December 2023 | 6 |
@TestRailId:C4156 @AdvancedPaymentAllocation @Skip @TestRailId:C4156 @AdvancedPaymentAllocation
Scenario: Verify allowing Re-aging on interest bearing loan - Interest calculation: Default Behavior - Verify backdated re-aging on the day of repayment with interest recalculation enabled - UC16.3 Scenario: Verify allowing Re-aging on interest bearing loan - Interest calculation: Default Behavior - Verify backdated re-aging on the day of repayment with interest recalculation enabled - UC16.3
When Admin sets the business date to "01 January 2024" When Admin sets the business date to "01 January 2024"
When Admin creates a client with random data When Admin creates a client with random data
@@ -1132,5 +1132,3 @@ Feature: LoanReschedule
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
| 24 July 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | false | | 24 July 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | false |
Then LoanRescheduledDueAdjustScheduleBusinessEvent is raised on "24 July 2025" Then LoanRescheduledDueAdjustScheduleBusinessEvent is raised on "24 July 2025"
@@ -46,7 +46,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations; import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanTermVariationsRepository; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanTermVariationsRepository;
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler; import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
import org.apache.fineract.portfolio.loanaccount.service.ReprocessLoanTransactionsService; import org.apache.fineract.portfolio.loanaccount.service.LoanScheduleService;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@AllArgsConstructor @AllArgsConstructor
@@ -56,8 +56,8 @@ public class InterestPauseWritePlatformServiceImpl implements InterestPauseWrite
private final LoanTermVariationsRepository loanTermVariationsRepository; private final LoanTermVariationsRepository loanTermVariationsRepository;
private final LoanRepositoryWrapper loanRepositoryWrapper; private final LoanRepositoryWrapper loanRepositoryWrapper;
private final LoanAssembler loanAssembler; private final LoanAssembler loanAssembler;
private final ReprocessLoanTransactionsService reprocessLoanTransactionsService;
private final BusinessEventNotifierService businessEventNotifierService; private final BusinessEventNotifierService businessEventNotifierService;
private final LoanScheduleService loanScheduleService;
@Override @Override
public CommandProcessingResult createInterestPause(final ExternalId loanExternalId, final String startDateString, public CommandProcessingResult createInterestPause(final ExternalId loanExternalId, final String startDateString,
@@ -114,7 +114,7 @@ public class InterestPauseWritePlatformServiceImpl implements InterestPauseWrite
loanTermVariationsRepository.delete(variation); loanTermVariationsRepository.delete(variation);
loan.getLoanTermVariations().remove(variation); loan.getLoanTermVariations().remove(variation);
reprocessLoanTransactionsService.reprocessTransactions(loan); loanScheduleService.regenerateScheduleWithReprocessingTransactions(loan);
businessEventNotifierService.notifyPostBusinessEvent(new LoanScheduleVariationsDeletedBusinessEvent(loan)); businessEventNotifierService.notifyPostBusinessEvent(new LoanScheduleVariationsDeletedBusinessEvent(loan));
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan)); businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
@@ -141,7 +141,7 @@ public class InterestPauseWritePlatformServiceImpl implements InterestPauseWrite
LoanTermVariations updatedVariation = loanTermVariationsRepository.save(variation); LoanTermVariations updatedVariation = loanTermVariationsRepository.save(variation);
reprocessLoanTransactionsService.reprocessTransactions(loan); loanScheduleService.regenerateScheduleWithReprocessingTransactions(loan);
businessEventNotifierService.notifyPostBusinessEvent(new LoanScheduleVariationsAddedBusinessEvent(loan)); businessEventNotifierService.notifyPostBusinessEvent(new LoanScheduleVariationsAddedBusinessEvent(loan));
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan)); businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
@@ -161,7 +161,7 @@ public class InterestPauseWritePlatformServiceImpl implements InterestPauseWrite
final LoanTermVariations savedVariation = loanTermVariationsRepository.saveAndFlush(variation); final LoanTermVariations savedVariation = loanTermVariationsRepository.saveAndFlush(variation);
loan.getLoanTermVariations().add(savedVariation); loan.getLoanTermVariations().add(savedVariation);
reprocessLoanTransactionsService.reprocessTransactions(loan); loanScheduleService.regenerateScheduleWithReprocessingTransactions(loan);
businessEventNotifierService.notifyPostBusinessEvent(new LoanScheduleVariationsAddedBusinessEvent(loan)); businessEventNotifierService.notifyPostBusinessEvent(new LoanScheduleVariationsAddedBusinessEvent(loan));
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan)); businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
@@ -0,0 +1,47 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.loanaccount.service;
import java.time.LocalDate;
import org.apache.fineract.portfolio.calendar.domain.Calendar;
import org.apache.fineract.portfolio.group.domain.Group;
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
public interface ILoanUtilService {
ScheduleGeneratorDTO buildScheduleGeneratorDTO(Loan loan, LocalDate recalculateFrom);
ScheduleGeneratorDTO buildScheduleGeneratorDTO(Loan loan, LocalDate recalculateFrom, LocalDate rescheduleTill);
ScheduleGeneratorDTO buildScheduleGeneratorDTO(Loan loan, LocalDate recalculateFrom, LocalDate recalculateTill,
HolidayDetailDTO holidayDetailDTO);
Boolean isLoanRepaymentsSyncWithMeeting(Group group, Calendar calendar);
LocalDate getCalculatedRepaymentsStartingFromDate(Loan loan);
HolidayDetailDTO constructHolidayDTO(Long officeId, LocalDate localDate);
void validateRepaymentTransactionType(LoanTransactionType repaymentTransactionType);
void checkClientOrGroupActive(Loan loan);
}
@@ -94,6 +94,7 @@ public class LoanBalanceService {
public void updateLoanSummaryDerivedFields(final Loan loan) { public void updateLoanSummaryDerivedFields(final Loan loan) {
flushModeHandler.withFlushMode(FlushModeType.COMMIT, () -> { flushModeHandler.withFlushMode(FlushModeType.COMMIT, () -> {
loan.updateLoanScheduleDependentDerivedFields();
if (loan.isNotDisbursed()) { if (loan.isNotDisbursed()) {
if (loan.getSummary() != null) { if (loan.getSummary() != null) {
loan.getSummary().zeroFields(); loan.getSummary().zeroFields();
@@ -33,6 +33,7 @@ import org.apache.fineract.infrastructure.configuration.service.TemporaryConfigu
import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.domain.ExternalId; import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.charge.domain.Charge; import org.apache.fineract.portfolio.charge.domain.Charge;
import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType; import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
@@ -901,4 +902,51 @@ public class LoanChargeService {
return amount; return amount;
} }
public void removeLoanCharge(final Loan loan, final LoanCharge loanCharge) {
final boolean removed = loanCharge.isActive();
if (removed) {
loanCharge.setActive(false);
final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
wrapper.reprocess(loan.getCurrency(), loan.getDisbursementDate(), loan.getRepaymentScheduleInstallments(),
loan.getActiveCharges());
loan.updateSummaryWithTotalFeeChargesDueAtDisbursement(loan.deriveSumTotalOfChargesDueAtDisbursement());
}
removeOrModifyTransactionAssociatedWithLoanChargeIfDueAtDisbursement(loan, loanCharge);
loan.getLoanCharges().remove(loanCharge);
}
private void removeOrModifyTransactionAssociatedWithLoanChargeIfDueAtDisbursement(final Loan loan, final LoanCharge loanCharge) {
if (loanCharge.isDueAtDisbursement()) {
LoanTransaction transactionToRemove = null;
List<LoanTransaction> transactions = loan.getLoanTransactions();
for (final LoanTransaction transaction : transactions) {
if (transaction.isRepaymentAtDisbursement()
&& doesLoanChargePaidByContainLoanCharge(transaction.getLoanChargesPaid(), loanCharge)) {
final MonetaryCurrency currency = loan.getCurrency();
final Money chargeAmount = Money.of(currency, loanCharge.amount());
if (transaction.isGreaterThan(chargeAmount)) {
final Money principalPortion = Money.zero(currency);
final Money interestPortion = Money.zero(currency);
final Money penaltyChargesPortion = Money.zero(currency);
transaction.updateComponentsAndTotal(principalPortion, interestPortion, chargeAmount, penaltyChargesPortion);
} else {
transactionToRemove = transaction;
}
}
}
if (transactionToRemove != null) {
loan.removeLoanTransaction(transactionToRemove);
}
}
}
private boolean doesLoanChargePaidByContainLoanCharge(Set<LoanChargePaidBy> loanChargePaidBys, LoanCharge loanCharge) {
return loanChargePaidBys.stream() //
.anyMatch(loanChargePaidBy -> loanChargePaidBy.getLoanCharge().equals(loanCharge));
}
} }
@@ -156,9 +156,7 @@ public class LoanDownPaymentHandlerServiceImpl implements LoanDownPaymentHandler
if (!processLatest || reprocessOnPostConditions) { if (!processLatest || reprocessOnPostConditions) {
if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) { if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) {
loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO); loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO);
} else if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy()) } else if (loan.isProgressiveSchedule()) {
|| loan.hasContractTerminationTransaction()
|| (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) {
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
} }
reprocessLoanTransactionsService.reprocessTransactions(loan); reprocessLoanTransactionsService.reprocessTransactions(loan);
@@ -42,6 +42,7 @@ public class LoanScheduleService {
private final LoanTransactionProcessingService loanTransactionProcessingService; private final LoanTransactionProcessingService loanTransactionProcessingService;
private final LoanScheduleComponent loanSchedule; private final LoanScheduleComponent loanSchedule;
private final LoanTransactionRepository loanTransactionRepository; private final LoanTransactionRepository loanTransactionRepository;
private final ILoanUtilService loanUtilService;
/** /**
* Ability to regenerate the repayment schedule based on the loans current details/state. * Ability to regenerate the repayment schedule based on the loans current details/state.
@@ -60,7 +61,7 @@ public class LoanScheduleService {
} }
} }
public void recalculateSchedule(final Loan loan, final ScheduleGeneratorDTO generatorDTO) { public void regenerateScheduleWithReprocessingTransactions(final Loan loan, final ScheduleGeneratorDTO generatorDTO) {
if (loan.isInterestBearingAndInterestRecalculationEnabled() && !loan.isChargedOff()) { if (loan.isInterestBearingAndInterestRecalculationEnabled() && !loan.isChargedOff()) {
regenerateRepaymentScheduleWithInterestRecalculation(loan, generatorDTO); regenerateRepaymentScheduleWithInterestRecalculation(loan, generatorDTO);
} else { } else {
@@ -123,11 +124,20 @@ public class LoanScheduleService {
} }
} }
} }
loanTransactionProcessingService.processPostDisbursementTransactions(loan);
} }
public void handleRegenerateRepaymentScheduleWithInterestRecalculation(final Loan loan, final ScheduleGeneratorDTO generatorDTO) { public void handleRegenerateRepaymentScheduleWithInterestRecalculation(final Loan loan, final ScheduleGeneratorDTO generatorDTO) {
regenerateRepaymentScheduleWithInterestRecalculation(loan, generatorDTO); regenerateRepaymentScheduleWithInterestRecalculation(loan, generatorDTO);
reprocessLoanTransactionsService.reprocessTransactions(loan); reprocessLoanTransactionsService.reprocessTransactions(loan);
} }
public void regenerateScheduleWithReprocessingTransactions(Loan loan) {
ScheduleGeneratorDTO generatorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null);
regenerateScheduleWithReprocessingTransactions(loan, generatorDTO);
}
public void regenerateRepaymentSchedule(final Loan loan) {
ScheduleGeneratorDTO generatorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null);
regenerateRepaymentSchedule(loan, generatorDTO);
}
} }
@@ -20,7 +20,6 @@ package org.apache.fineract.portfolio.loanaccount.service;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.portfolio.loanaccount.data.OutstandingAmountsDTO; import org.apache.fineract.portfolio.loanaccount.data.OutstandingAmountsDTO;
@@ -48,8 +47,6 @@ public interface LoanTransactionProcessingService {
LoanRepaymentScheduleTransactionProcessor getTransactionProcessor(String transactionProcessingStrategyCode); LoanRepaymentScheduleTransactionProcessor getTransactionProcessor(String transactionProcessingStrategyCode);
Optional<ChangedTransactionDetail> processPostDisbursementTransactions(Loan loan);
LoanScheduleDTO getRecalculatedSchedule(ScheduleGeneratorDTO generatorDTO, Loan loan); LoanScheduleDTO getRecalculatedSchedule(ScheduleGeneratorDTO generatorDTO, Loan loan);
OutstandingAmountsDTO fetchPrepaymentDetail(ScheduleGeneratorDTO scheduleGeneratorDTO, LocalDate onDate, Loan loan); OutstandingAmountsDTO fetchPrepaymentDetail(ScheduleGeneratorDTO scheduleGeneratorDTO, LocalDate onDate, Loan loan);
@@ -18,25 +18,17 @@
*/ */
package org.apache.fineract.portfolio.loanaccount.service; package org.apache.fineract.portfolio.loanaccount.service;
import java.time.LocalDate;
import java.util.List; import java.util.List;
import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
public interface ReprocessLoanTransactionsService { public interface ReprocessLoanTransactionsService {
void reprocessTransactions(Loan loan); void reprocessTransactions(Loan loan);
void reprocessParticularTransactions(Loan loan, List<LoanTransaction> loanTransactions); void reprocessTransactions(Loan loan, List<LoanTransaction> loanTransactions);
void reprocessTransactionsWithPostTransactionChecks(Loan loan, LocalDate transactionDate); void reprocessTransactionsWithoutChecks(Loan loan, List<LoanTransaction> newTransactions);
void reprocessTransactionsWithoutChecks(Loan loan, LocalDate transactionDate, List<LoanTransaction> newTransactions);
void processPostDisbursementTransactions(Loan loan);
void removeLoanCharge(Loan loan, LoanCharge loanCharge);
void processLatestTransaction(LoanTransaction loanTransaction, Loan loan); void processLatestTransaction(LoanTransaction loanTransaction, Loan loan);
@@ -51,11 +51,10 @@ public class ProgressivePossibleNextRepaymentCalculationServiceImpl extends Abst
Optional<ProgressiveLoanModel> progressiveLoanModel = interestScheduleModelRepository.findOneByLoan(loan); Optional<ProgressiveLoanModel> progressiveLoanModel = interestScheduleModelRepository.findOneByLoan(loan);
Optional<ProgressiveLoanInterestScheduleModel> optionalScheduleModel = interestScheduleModelRepository Optional<ProgressiveLoanInterestScheduleModel> optionalScheduleModel = interestScheduleModelRepository
.extractModel(progressiveLoanModel); .extractModel(progressiveLoanModel);
ProgressiveLoanInterestScheduleModel scheduleModel = optionalScheduleModel.orElseGet( if (optionalScheduleModel.isEmpty()) {
() -> advancedPaymentScheduleTransactionProcessor.calculateInterestScheduleModel(loan.getId(), nextPaymentDueDate));
if (scheduleModel == null) {
return BigDecimal.ZERO; return BigDecimal.ZERO;
} }
ProgressiveLoanInterestScheduleModel scheduleModel = optionalScheduleModel.get();
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments = loan.getRepaymentScheduleInstallments(); List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments = loan.getRepaymentScheduleInstallments();
ProgressiveTransactionCtx ctx = new ProgressiveTransactionCtx(loan.getCurrency(), repaymentScheduleInstallments, Set.of(), ProgressiveTransactionCtx ctx = new ProgressiveTransactionCtx(loan.getCurrency(), repaymentScheduleInstallments, Set.of(),
new MoneyHolder(loan.getTotalOverpaidAsMoney()), new ChangedTransactionDetail(), scheduleModel); new MoneyHolder(loan.getTotalOverpaidAsMoney()), new ChangedTransactionDetail(), scheduleModel);
@@ -84,14 +84,12 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalculatio
import org.apache.fineract.portfolio.loanaccount.domain.LoanPaymentAllocationRule; import org.apache.fineract.portfolio.loanaccount.domain.LoanPaymentAllocationRule;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType; import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations; import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionComparator; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionComparator;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
import org.apache.fineract.portfolio.loanaccount.domain.SingleLoanChargeRepaymentScheduleProcessingWrapper; import org.apache.fineract.portfolio.loanaccount.domain.SingleLoanChargeRepaymentScheduleProcessingWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter; import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter;
@@ -119,8 +117,6 @@ import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType; import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
import org.apache.fineract.util.LoopContext; import org.apache.fineract.util.LoopContext;
import org.apache.fineract.util.LoopGuard; import org.apache.fineract.util.LoopGuard;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Slf4j @Slf4j
public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRepaymentScheduleTransactionProcessor { public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRepaymentScheduleTransactionProcessor {
@@ -129,23 +125,18 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
public static final String ADVANCED_PAYMENT_ALLOCATION_STRATEGY_NAME = "Advanced payment allocation strategy"; public static final String ADVANCED_PAYMENT_ALLOCATION_STRATEGY_NAME = "Advanced payment allocation strategy";
private final EMICalculator emiCalculator; private final EMICalculator emiCalculator;
private final LoanRepositoryWrapper loanRepositoryWrapper;
private final InterestRefundService interestRefundService; private final InterestRefundService interestRefundService;
private final LoanScheduleComponent loanSchedule; private final LoanScheduleComponent loanSchedule;
private final LoanTransactionRepository loanTransactionRepository;
private final LoanChargeService loanChargeService; private final LoanChargeService loanChargeService;
private final SingleLoanChargeRepaymentScheduleProcessingWrapper loanChargeRepaymentScheduleProcessing; private final SingleLoanChargeRepaymentScheduleProcessingWrapper loanChargeRepaymentScheduleProcessing;
public AdvancedPaymentScheduleTransactionProcessor(final EMICalculator emiCalculator, final LoanRepositoryWrapper loanRepositoryWrapper, public AdvancedPaymentScheduleTransactionProcessor(final EMICalculator emiCalculator, final InterestRefundService interestRefundService,
final InterestRefundService interestRefundService, final ExternalIdFactory externalIdFactory, final ExternalIdFactory externalIdFactory, final LoanScheduleComponent loanSchedule,
final LoanScheduleComponent loanSchedule, final LoanTransactionRepository loanTransactionRepository,
final LoanChargeValidator loanChargeValidator, final LoanBalanceService loanBalanceService, final LoanChargeValidator loanChargeValidator, final LoanBalanceService loanBalanceService,
final LoanChargeService loanChargeService) { final LoanChargeService loanChargeService) {
super(externalIdFactory, loanChargeValidator, loanBalanceService); super(externalIdFactory, loanChargeValidator, loanBalanceService);
this.emiCalculator = emiCalculator; this.emiCalculator = emiCalculator;
this.loanRepositoryWrapper = loanRepositoryWrapper;
this.interestRefundService = interestRefundService; this.interestRefundService = interestRefundService;
this.loanTransactionRepository = loanTransactionRepository;
this.loanSchedule = loanSchedule; this.loanSchedule = loanSchedule;
this.loanChargeService = loanChargeService; this.loanChargeService = loanChargeService;
this.loanChargeRepaymentScheduleProcessing = new SingleLoanChargeRepaymentScheduleProcessingWrapper(); this.loanChargeRepaymentScheduleProcessing = new SingleLoanChargeRepaymentScheduleProcessingWrapper();
@@ -195,13 +186,6 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
throw new NotImplementedException(); throw new NotImplementedException();
} }
@Transactional
public Pair<ChangedTransactionDetail, ProgressiveLoanInterestScheduleModel> reprocessProgressiveLoanTransactionsTransactional(
final LocalDate disbursementDate, final LocalDate targetDate, final List<LoanTransaction> loanTransactions,
final MonetaryCurrency currency, final List<LoanRepaymentScheduleInstallment> installments, final Set<LoanCharge> charges) {
return reprocessProgressiveLoanTransactions(disbursementDate, targetDate, loanTransactions, currency, installments, charges);
}
// only for progressive loans // only for progressive loans
public Pair<ChangedTransactionDetail, ProgressiveLoanInterestScheduleModel> reprocessProgressiveLoanTransactions( public Pair<ChangedTransactionDetail, ProgressiveLoanInterestScheduleModel> reprocessProgressiveLoanTransactions(
LocalDate disbursementDate, LocalDate targetDate, List<LoanTransaction> loanTransactions, MonetaryCurrency currency, LocalDate disbursementDate, LocalDate targetDate, List<LoanTransaction> loanTransactions, MonetaryCurrency currency,
@@ -302,18 +286,6 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
return result.getLeft(); return result.getLeft();
} }
@NotNull
@Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true)
public ProgressiveLoanInterestScheduleModel calculateInterestScheduleModel(@NotNull Long loanId, LocalDate targetDate) {
Loan loan = loanRepositoryWrapper.findOneWithNotFoundDetection(loanId);
final List<LoanTransaction> transactions = loanTransactionRepository.findNonReversedTransactionsForReprocessingByLoan(loan);
MonetaryCurrency currency = loan.getLoanRepaymentScheduleDetail().getCurrency();
List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
Set<LoanCharge> charges = loan.getActiveCharges();
return reprocessProgressiveLoanTransactions(loan.getDisbursementDate(), targetDate, transactions, currency, installments, charges)
.getRight();
}
@NotNull @NotNull
private static LoanTransaction getProcessedTransaction(final ChangedTransactionDetail changedTransactionDetail, private static LoanTransaction getProcessedTransaction(final ChangedTransactionDetail changedTransactionDetail,
final LoanTransaction transaction) { final LoanTransaction transaction) {
@@ -2937,9 +2909,8 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
private void updateInstallmentsByModelForReAging(final LoanTransaction loanTransaction, final ProgressiveTransactionCtx ctx) { private void updateInstallmentsByModelForReAging(final LoanTransaction loanTransaction, final ProgressiveTransactionCtx ctx) {
ctx.getModel().repaymentPeriods().forEach(rp -> { ctx.getModel().repaymentPeriods().forEach(rp -> {
final LoanRepaymentScheduleInstallment installment = ctx.getInstallments().stream().filter( final LoanRepaymentScheduleInstallment installment = ctx.getInstallments().stream()
ri -> ri.getFromDate().equals(rp.getFromDate()) && ri.getDueDate().equals(rp.getDueDate()) && !ri.isDownPayment()) .filter(ri -> ri.getFromDate().equals(rp.getFromDate()) && !ri.isDownPayment()).findFirst().orElse(null);
.findFirst().orElse(null);
final LocalDate transactionDate = loanTransaction.getTransactionDate(); final LocalDate transactionDate = loanTransaction.getTransactionDate();
if (installment != null) { if (installment != null) {
installment.setFromDate(rp.getFromDate()); installment.setFromDate(rp.getFromDate());
@@ -2953,6 +2924,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
installment.updateInterestCharged(rp.getDueInterest().getAmount()); installment.updateInterestCharged(rp.getDueInterest().getAmount());
} }
installment.setReAged(true); installment.setReAged(true);
installment.setAdditional(false);
installment.updateObligationsMet(ctx.getCurrency(), transactionDate); installment.updateObligationsMet(ctx.getCurrency(), transactionDate);
} else { } else {
final LoanRepaymentScheduleInstallment lastInstallment = ctx.getInstallments().getLast(); final LoanRepaymentScheduleInstallment lastInstallment = ctx.getInstallments().getLast();
@@ -3247,7 +3219,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
final BigDecimal interestRate = loan.getLoanRepaymentScheduleDetail().getAnnualNominalInterestRate(); final BigDecimal interestRate = loan.getLoanRepaymentScheduleDetail().getAnnualNominalInterestRate();
final Money totalOutstandingPrincipal = ctx.getModel().getTotalOutstandingPrincipal(); final Money totalOutstandingPrincipal = ctx.getModel().getTotalOutstandingPrincipal();
final LoanApplicationTerms loanApplicationTerms = new LoanApplicationTerms.Builder().currency(currency.getCurrencyData()) final LoanApplicationTerms loanApplicationTerms = new LoanApplicationTerms.Builder().currency(currency.toData())
.repaymentsStartingFromDate(reAgingStartDate).principal(totalOutstandingPrincipal) .repaymentsStartingFromDate(reAgingStartDate).principal(totalOutstandingPrincipal)
.loanTermFrequency(loanTransaction.getLoanReAgeParameter().getNumberOfInstallments()) .loanTermFrequency(loanTransaction.getLoanReAgeParameter().getNumberOfInstallments())
.loanTermPeriodFrequencyType(loanTransaction.getLoanReAgeParameter().getFrequencyType()) .loanTermPeriodFrequencyType(loanTransaction.getLoanReAgeParameter().getFrequencyType())
@@ -202,8 +202,7 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
Optional<ProgressiveLoanInterestScheduleModel> savedModel = interestScheduleModelRepositoryWrapper.getSavedModel(loan, Optional<ProgressiveLoanInterestScheduleModel> savedModel = interestScheduleModelRepositoryWrapper.getSavedModel(loan,
transactionDate); transactionDate);
ProgressiveLoanInterestScheduleModel model = savedModel ProgressiveLoanInterestScheduleModel model = savedModel.orElseThrow();
.orElseGet(() -> processor.calculateInterestScheduleModel(loan.getId(), transactionDate));
OutstandingDetails outstandingAmounts = emiCalculator.getOutstandingAmountsTillDate(model, transactionDate); OutstandingDetails outstandingAmounts = emiCalculator.getOutstandingAmountsTillDate(model, transactionDate);
// TODO: We should add all the past due outstanding amounts as well // TODO: We should add all the past due outstanding amounts as well
OutstandingAmountsDTO result = new OutstandingAmountsDTO(currency) // OutstandingAmountsDTO result = new OutstandingAmountsDTO(currency) //
@@ -245,8 +244,7 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
return Money.zero(loan.getCurrency()); return Money.zero(loan.getCurrency());
} }
Optional<ProgressiveLoanInterestScheduleModel> savedModel = interestScheduleModelRepositoryWrapper.getSavedModel(loan, targetDate); Optional<ProgressiveLoanInterestScheduleModel> savedModel = interestScheduleModelRepositoryWrapper.getSavedModel(loan, targetDate);
ProgressiveLoanInterestScheduleModel model = savedModel ProgressiveLoanInterestScheduleModel model = savedModel.orElseThrow();
.orElseGet(() -> processor.calculateInterestScheduleModel(loan.getId(), targetDate));
return emiCalculator.getPeriodInterestTillDate(model, installment.getDueDate(), targetDate, false); return emiCalculator.getPeriodInterestTillDate(model, installment.getDueDate(), targetDate, false);
} }
@@ -438,7 +436,8 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
} else { } else {
amount = amount.add(principalInterestForThisPeriod.principal().getAmount()); amount = amount.add(principalInterestForThisPeriod.principal().getAmount());
} }
BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100), mc); Money loanChargeAmt = Money.of(cumulative.getCurrency(),
amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100), mc));
cumulative = cumulative.plus(loanChargeAmt); cumulative = cumulative.plus(loanChargeAmt);
} else { } else {
cumulative = cumulative.plus(loanCharge.amountOrPercentage()); cumulative = cumulative.plus(loanCharge.amountOrPercentage());
@@ -456,7 +455,8 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
} else { } else {
amount = amount.add(principalDisbursed.getAmount()); amount = amount.add(principalDisbursed.getAmount());
} }
BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100), mc); Money loanChargeAmt = Money.of(cumulative.getCurrency(),
amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100), mc));
cumulative = cumulative.plus(loanChargeAmt); cumulative = cumulative.plus(loanChargeAmt);
return cumulative; return cumulative;
} }
@@ -1,3 +1,21 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.loanaccount.mapper; package org.apache.fineract.portfolio.loanaccount.mapper;
import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.monetary.data.CurrencyData;
@@ -7,7 +25,9 @@ import org.apache.fineract.portfolio.common.domain.DaysInYearType;
import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanproduct.data.LoanConfigurationDetails; import org.apache.fineract.portfolio.loanproduct.data.LoanConfigurationDetails;
import org.apache.fineract.portfolio.loanproduct.domain.ILoanConfigurationDetails; import org.apache.fineract.portfolio.loanproduct.domain.ILoanConfigurationDetails;
import org.apache.fineract.portfolio.loanproduct.domain.LoanPreCloseInterestCalculationStrategy;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
import org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType;
public final class LoanConfigurationDetailsMapper { public final class LoanConfigurationDetailsMapper {
@@ -36,7 +56,22 @@ public final class LoanConfigurationDetailsMapper {
loanProductRelatedDetail.getRepaymentPeriodFrequencyType(), loanProductRelatedDetail.getRepayEvery(), loanProductRelatedDetail.getRepaymentPeriodFrequencyType(), loanProductRelatedDetail.getRepayEvery(),
loanProductRelatedDetail.getNumberOfRepayments(), loanProductRelatedDetail.isInterestRecognitionOnDisbursementDate(), loanProductRelatedDetail.getNumberOfRepayments(), loanProductRelatedDetail.isInterestRecognitionOnDisbursementDate(),
loanProductRelatedDetail.getDaysInYearCustomStrategy(), loanProductRelatedDetail.isAllowPartialPeriodInterestCalculation(), loanProductRelatedDetail.getDaysInYearCustomStrategy(), loanProductRelatedDetail.isAllowPartialPeriodInterestCalculation(),
loan.isInterestRecalculationEnabled(), loan.getLoanInterestRecalculationDetails().getRestFrequencyType(), loan.isInterestRecalculationEnabled(), getRestFrequencyType(loan), getPreCloseInterestCalculationStrategy(loan));
loan.getLoanInterestRecalculationDetails().getPreCloseInterestCalculationStrategy()); }
private static RecalculationFrequencyType getRestFrequencyType(Loan loan) {
if (loan.getLoanInterestRecalculationDetails() != null) {
return loan.getLoanInterestRecalculationDetails().getRestFrequencyType();
} else {
return RecalculationFrequencyType.INVALID;
}
}
private static LoanPreCloseInterestCalculationStrategy getPreCloseInterestCalculationStrategy(Loan loan) {
if (loan.getLoanInterestRecalculationDetails() != null) {
return loan.getLoanInterestRecalculationDetails().getPreCloseInterestCalculationStrategy();
} else {
return LoanPreCloseInterestCalculationStrategy.NONE;
}
} }
} }
@@ -21,6 +21,7 @@ package org.apache.fineract.portfolio.loanaccount.service;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@@ -69,6 +70,7 @@ public class CapitalizedIncomeWritePlatformServiceImpl implements CapitalizedInc
private final LoanLifecycleStateMachine loanLifecycleStateMachine; private final LoanLifecycleStateMachine loanLifecycleStateMachine;
private final BusinessEventNotifierService businessEventNotifierService; private final BusinessEventNotifierService businessEventNotifierService;
private final CodeValueRepository codeValueRepository; private final CodeValueRepository codeValueRepository;
private final LoanScheduleService loanScheduleService;
@Transactional @Transactional
@Override @Override
@@ -87,13 +89,13 @@ public class CapitalizedIncomeWritePlatformServiceImpl implements CapitalizedInc
final Money capitalizedIncomeAmount = calculateCapitalizedIncomeAmount(loan, transactionAmount); final Money capitalizedIncomeAmount = calculateCapitalizedIncomeAmount(loan, transactionAmount);
final LoanTransaction capitalizedIncomeTransaction = LoanTransaction.capitalizedIncome(loan, capitalizedIncomeAmount, paymentDetail, final LoanTransaction capitalizedIncomeTransaction = LoanTransaction.capitalizedIncome(loan, capitalizedIncomeAmount, paymentDetail,
transactionDate, txnExternalId); transactionDate, txnExternalId);
// Update loan with capitalized income
loan.addLoanTransaction(capitalizedIncomeTransaction);
// Add Loan Transaction classification // Add Loan Transaction classification
addClassificationCodeToTransaction(command, LoanTransactionApiConstants.CAPITALIZED_INCOME_CLASSIFICATION_CODE, addClassificationCodeToTransaction(command, LoanTransactionApiConstants.CAPITALIZED_INCOME_CLASSIFICATION_CODE,
capitalizedIncomeTransaction); capitalizedIncomeTransaction);
// Recalculate loan transactions // Recalculate loan transactions
recalculateLoanTransactions(loan, transactionDate, capitalizedIncomeTransaction); recalculateLoanTransactions(loan, capitalizedIncomeTransaction);
// Update loan with capitalized income
loan.addLoanTransaction(capitalizedIncomeTransaction);
// Save and flush (PK is set) // Save and flush (PK is set)
loanTransactionRepository.saveAndFlush(capitalizedIncomeTransaction); loanTransactionRepository.saveAndFlush(capitalizedIncomeTransaction);
// Create capitalized income balances // Create capitalized income balances
@@ -144,8 +146,8 @@ public class CapitalizedIncomeWritePlatformServiceImpl implements CapitalizedInc
capitalizedIncomeAdjustment.getLoanTransactionRelations().add(LoanTransactionRelation.linkToTransaction(capitalizedIncomeAdjustment, capitalizedIncomeAdjustment.getLoanTransactionRelations().add(LoanTransactionRelation.linkToTransaction(capitalizedIncomeAdjustment,
capitalizedIncome.get(), LoanTransactionRelationTypeEnum.ADJUSTMENT)); capitalizedIncome.get(), LoanTransactionRelationTypeEnum.ADJUSTMENT));
capitalizedIncomeAdjustment.setClassification(capitalizedIncome.get().getClassification()); capitalizedIncomeAdjustment.setClassification(capitalizedIncome.get().getClassification());
recalculateLoanTransactions(loan, capitalizedIncomeAdjustment);
loan.addLoanTransaction(capitalizedIncomeAdjustment); loan.addLoanTransaction(capitalizedIncomeAdjustment);
recalculateLoanTransactions(loan, transactionDate, capitalizedIncomeAdjustment);
LoanTransaction savedCapitalizedIncomeAdjustment = loanTransactionRepository.saveAndFlush(capitalizedIncomeAdjustment); LoanTransaction savedCapitalizedIncomeAdjustment = loanTransactionRepository.saveAndFlush(capitalizedIncomeAdjustment);
// Update outstanding loan balances // Update outstanding loan balances
@@ -182,9 +184,10 @@ public class CapitalizedIncomeWritePlatformServiceImpl implements CapitalizedInc
.build(); .build();
} }
private void recalculateLoanTransactions(Loan loan, LocalDate transactionDate, LoanTransaction transaction) { private void recalculateLoanTransactions(Loan loan, LoanTransaction transaction) {
if (loan.isInterestRecalculationEnabled() || DateUtils.isBeforeBusinessDate(transactionDate)) { if (loan.isInterestRecalculationEnabled() || DateUtils.isBeforeBusinessDate(transaction.getTransactionDate())) {
reprocessLoanTransactionsService.reprocessTransactions(loan); loanScheduleService.regenerateRepaymentSchedule(loan);
reprocessLoanTransactionsService.reprocessTransactions(loan, List.of(transaction));
} else { } else {
reprocessLoanTransactionsService.processLatestTransaction(transaction, loan); reprocessLoanTransactionsService.processLatestTransaction(transaction, loan);
} }
@@ -108,7 +108,7 @@ public class InterestScheduleModelRepositoryWrapperImpl implements InterestSched
public Optional<ProgressiveLoanInterestScheduleModel> getSavedModel(Loan loan, LocalDate businessDate) { public Optional<ProgressiveLoanInterestScheduleModel> getSavedModel(Loan loan, LocalDate businessDate) {
Optional<ProgressiveLoanModel> progressiveLoanModel = findOneByLoanId(loan.getId()); Optional<ProgressiveLoanModel> progressiveLoanModel = findOneByLoanId(loan.getId());
Optional<ProgressiveLoanInterestScheduleModel> savedModel; Optional<ProgressiveLoanInterestScheduleModel> savedModel;
if (progressiveLoanModel.isPresent() && !progressiveLoanModel.get().getBusinessDate().isAfter(businessDate)) { if (progressiveLoanModel.isPresent()) {
savedModel = extractModel(progressiveLoanModel); savedModel = extractModel(progressiveLoanModel);
if (savedModel.isPresent() && progressiveLoanModel.get().getBusinessDate().isBefore(businessDate)) { if (savedModel.isPresent() && progressiveLoanModel.get().getBusinessDate().isBefore(businessDate)) {
ProgressiveTransactionCtx ctx = new ProgressiveTransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), ProgressiveTransactionCtx ctx = new ProgressiveTransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(),
@@ -28,26 +28,18 @@ import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam; import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces; import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MediaType;
import java.time.LocalDate;
import java.util.List;
import java.util.Objects;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.fineract.infrastructure.core.boot.FineractProfiles; import org.apache.fineract.infrastructure.core.boot.FineractProfiles;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanConfigurationDetailsMapper; import org.apache.fineract.portfolio.loanaccount.mapper.LoanConfigurationDetailsMapper;
import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel; import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
import org.apache.fineract.portfolio.loanproduct.domain.ILoanConfigurationDetails; import org.apache.fineract.portfolio.loanproduct.domain.ILoanConfigurationDetails;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Profile; import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Slf4j @Slf4j
@RequiredArgsConstructor @RequiredArgsConstructor
@@ -58,9 +50,9 @@ import org.springframework.stereotype.Component;
public class InternalProgressiveLoanApiResource implements InitializingBean { public class InternalProgressiveLoanApiResource implements InitializingBean {
private final LoanRepositoryWrapper loanRepository; private final LoanRepositoryWrapper loanRepository;
private final AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor;
private final InterestScheduleModelRepositoryWrapper writePlatformService; private final InterestScheduleModelRepositoryWrapper writePlatformService;
private final LoanTransactionRepository loanTransactionRepository; private final InterestScheduleModelRepositoryWrapper interestScheduleModelRepositoryWrapper;
private final LoanScheduleService loanScheduleService;
@Override @Override
@SuppressFBWarnings("SLF4J_SIGN_ONLY_FORMAT") @SuppressFBWarnings("SLF4J_SIGN_ONLY_FORMAT")
@@ -89,28 +81,15 @@ public class InternalProgressiveLoanApiResource implements InitializingBean {
} }
private ProgressiveLoanInterestScheduleModel reprocessTransactionsAndGetModel(final Loan loan) { private ProgressiveLoanInterestScheduleModel reprocessTransactionsAndGetModel(final Loan loan) {
final List<LoanTransaction> transactionsToReprocess = loanTransactionRepository loanScheduleService.regenerateScheduleWithReprocessingTransactions(loan);
.findNonReversedTransactionsForReprocessingByLoan(loan); return interestScheduleModelRepositoryWrapper.extractModel(interestScheduleModelRepositoryWrapper.findOneByLoan(loan)).get();
final LocalDate businessDate = ThreadLocalContextUtil.getBusinessDate();
final Pair<ChangedTransactionDetail, ProgressiveLoanInterestScheduleModel> changedTransactionDetailProgressiveLoanInterestScheduleModelPair = advancedPaymentScheduleTransactionProcessor
.reprocessProgressiveLoanTransactionsTransactional(loan.getDisbursementDate(), businessDate, transactionsToReprocess,
loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges());
final ProgressiveLoanInterestScheduleModel model = changedTransactionDetailProgressiveLoanInterestScheduleModelPair.getRight();
final List<Long> replayedTransactions = changedTransactionDetailProgressiveLoanInterestScheduleModelPair.getLeft()
.getTransactionChanges().stream().filter(change -> change.getOldTransaction() != null)
.map(change -> change.getNewTransaction().getId()).filter(Objects::nonNull).toList();
if (!replayedTransactions.isEmpty()) {
log.warn("Reprocessed transactions show differences: There are unsaved changes of the following transactions: {}",
replayedTransactions);
}
return model;
} }
@POST @POST
@Path("{loanId}/model") @Path("{loanId}/model")
@Produces({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Update and Save ProgressiveLoanInterestScheduleModel", description = "DO NOT USE THIS IN PRODUCTION!") @Operation(summary = "Update and Save ProgressiveLoanInterestScheduleModel", description = "DO NOT USE THIS IN PRODUCTION!")
@Transactional
public ProgressiveLoanInterestScheduleModel updateModel(@PathParam("loanId") @Parameter(description = "loanId") long loanId) { public ProgressiveLoanInterestScheduleModel updateModel(@PathParam("loanId") @Parameter(description = "loanId") long loanId) {
Loan loan = loanRepository.findOneWithNotFoundDetection(loanId); Loan loan = loanRepository.findOneWithNotFoundDetection(loanId);
if (!loan.isProgressiveSchedule()) { if (!loan.isProgressiveSchedule()) {
@@ -38,6 +38,7 @@ import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService; import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
import org.apache.fineract.portfolio.loanaccount.service.LoanJournalEntryPoster; import org.apache.fineract.portfolio.loanaccount.service.LoanJournalEntryPoster;
import org.apache.fineract.portfolio.loanaccount.service.LoanMaximumAmountCalculator; import org.apache.fineract.portfolio.loanaccount.service.LoanMaximumAmountCalculator;
import org.apache.fineract.portfolio.loanaccount.service.LoanScheduleService;
import org.apache.fineract.portfolio.loanaccount.service.ProgressiveLoanTransactionValidator; import org.apache.fineract.portfolio.loanaccount.service.ProgressiveLoanTransactionValidator;
import org.apache.fineract.portfolio.loanaccount.service.ProgressiveLoanTransactionValidatorImpl; import org.apache.fineract.portfolio.loanaccount.service.ProgressiveLoanTransactionValidatorImpl;
import org.apache.fineract.portfolio.loanaccount.service.ReprocessLoanTransactionsService; import org.apache.fineract.portfolio.loanaccount.service.ReprocessLoanTransactionsService;
@@ -59,11 +60,11 @@ public class ProgressiveLoanAccountConfiguration {
LoanCapitalizedIncomeBalanceRepository capitalizedIncomeBalanceRepository, LoanCapitalizedIncomeBalanceRepository capitalizedIncomeBalanceRepository,
ReprocessLoanTransactionsService reprocessLoanTransactionsService, LoanBalanceService loanBalanceService, ReprocessLoanTransactionsService reprocessLoanTransactionsService, LoanBalanceService loanBalanceService,
LoanLifecycleStateMachine loanLifecycleStateMachine, BusinessEventNotifierService businessEventNotifierService, LoanLifecycleStateMachine loanLifecycleStateMachine, BusinessEventNotifierService businessEventNotifierService,
CodeValueRepository codeValueRepository) { CodeValueRepository codeValueRepository, LoanScheduleService loanScheduleService) {
return new CapitalizedIncomeWritePlatformServiceImpl(loanTransactionValidator, loanAssembler, loanTransactionRepository, return new CapitalizedIncomeWritePlatformServiceImpl(loanTransactionValidator, loanAssembler, loanTransactionRepository,
paymentDetailWritePlatformService, journalEntryPoster, noteWritePlatformService, externalIdFactory, paymentDetailWritePlatformService, journalEntryPoster, noteWritePlatformService, externalIdFactory,
capitalizedIncomeBalanceRepository, reprocessLoanTransactionsService, loanBalanceService, loanLifecycleStateMachine, capitalizedIncomeBalanceRepository, reprocessLoanTransactionsService, loanBalanceService, loanLifecycleStateMachine,
businessEventNotifierService, codeValueRepository); businessEventNotifierService, codeValueRepository, loanScheduleService);
} }
@Bean @Bean
@@ -64,11 +64,9 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanCreditAllocationRule
import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalculationDetails; import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalculationDetails;
import org.apache.fineract.portfolio.loanaccount.domain.LoanPaymentAllocationRule; import org.apache.fineract.portfolio.loanaccount.domain.LoanPaymentAllocationRule;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
@@ -120,9 +118,8 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
@BeforeEach @BeforeEach
public void setUp() { public void setUp() {
underTest = new AdvancedPaymentScheduleTransactionProcessor(emiCalculator, Mockito.mock(LoanRepositoryWrapper.class), underTest = new AdvancedPaymentScheduleTransactionProcessor(emiCalculator, Mockito.mock(InterestRefundService.class),
Mockito.mock(InterestRefundService.class), Mockito.mock(ExternalIdFactory.class), Mockito.mock(LoanScheduleComponent.class), Mockito.mock(ExternalIdFactory.class), Mockito.mock(LoanScheduleComponent.class), Mockito.mock(LoanChargeValidator.class),
Mockito.mock(LoanTransactionRepository.class), Mockito.mock(LoanChargeValidator.class),
Mockito.mock(LoanBalanceService.class), Mockito.mock(LoanChargeService.class)); Mockito.mock(LoanBalanceService.class), Mockito.mock(LoanChargeService.class));
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null)); ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
@@ -47,7 +47,7 @@ import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.service.ReprocessLoanTransactionsService; import org.apache.fineract.portfolio.loanaccount.service.LoanScheduleService;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Profile; import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -67,7 +67,7 @@ public class InternalCOBApiResource implements InitializingBean {
private final ApiRequestParameterHelper apiRequestParameterHelper; private final ApiRequestParameterHelper apiRequestParameterHelper;
private final ToApiJsonSerializer<List> toApiJsonSerializerForList; private final ToApiJsonSerializer<List> toApiJsonSerializerForList;
private final LoanRepositoryWrapper loanRepositoryWrapper; private final LoanRepositoryWrapper loanRepositoryWrapper;
private final ReprocessLoanTransactionsService reprocessLoanTransactionsService; private final LoanScheduleService loanScheduleService;
protected DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DATETIME_PATTERN); protected DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DATETIME_PATTERN);
@@ -113,7 +113,7 @@ public class InternalCOBApiResource implements InitializingBean {
@Path("loan-reprocess/{loanId}") @Path("loan-reprocess/{loanId}")
@Transactional @Transactional
public void loanReprocess(@Context final UriInfo uriInfo, @PathParam("loanId") long loanId) { public void loanReprocess(@Context final UriInfo uriInfo, @PathParam("loanId") long loanId) {
reprocessLoanTransactionsService.reprocessTransactions(loanRepositoryWrapper.findOneWithNotFoundDetection(loanId)); loanScheduleService.regenerateScheduleWithReprocessingTransactions(loanRepositoryWrapper.findOneWithNotFoundDetection(loanId));
} }
} }
@@ -859,9 +859,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
} else { } else {
if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) { if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) {
loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO); loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO);
} else if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy()) } else if (loan.isProgressiveSchedule()) {
|| loan.hasContractTerminationTransaction()
|| (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) {
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
} }
loan.getLoanTransactions().add(refundTransaction); loan.getLoanTransactions().add(refundTransaction);
@@ -113,9 +113,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.exception.InstallmentNotFoundException; import org.apache.fineract.portfolio.loanaccount.exception.InstallmentNotFoundException;
import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanTransactionTypeException; import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanTransactionTypeException;
@@ -277,20 +274,12 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
} }
if (reprocessRequired) { if (reprocessRequired) {
if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy()) if (loan.isProgressiveSchedule()) {
|| loan.hasContractTerminationTransaction()
|| (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) {
final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null); final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null);
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
} }
reprocessLoanTransactionsService.reprocessTransactions(loan);
if (overpaidReprocess) {
reprocessLoanTransactionsService.reprocessTransactionsWithPostTransactionChecks(loan, transactionDate);
} else {
reprocessLoanTransactionsService.reprocessTransactions(loan);
}
loanLifecycleStateMachine.determineAndTransition(loan, transactionDate); loanLifecycleStateMachine.determineAndTransition(loan, transactionDate);
loan = loanAccountService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
} }
if (loan.isInterestBearingAndInterestRecalculationEnabled() && isAppliedOnBackDate if (loan.isInterestBearingAndInterestRecalculationEnabled() && isAppliedOnBackDate
@@ -600,7 +589,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
loanChargeValidator.validateLoanIsNotClosed(loan, loanCharge); loanChargeValidator.validateLoanIsNotClosed(loan, loanCharge);
loanChargeValidator.validateLoanChargeIsNotWaived(loan, loanCharge); loanChargeValidator.validateLoanChargeIsNotWaived(loan, loanCharge);
reprocessLoanTransactionsService.removeLoanCharge(loan, loanCharge); loanChargeService.removeLoanCharge(loan, loanCharge);
this.loanRepositoryWrapper.save(loan); this.loanRepositoryWrapper.save(loan);
businessEventNotifierService.notifyPostBusinessEvent(new LoanDeleteChargeBusinessEvent(loanCharge)); businessEventNotifierService.notifyPostBusinessEvent(new LoanDeleteChargeBusinessEvent(loanCharge));
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
@@ -857,9 +846,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
if (reprocessRequired) { if (reprocessRequired) {
addInstallmentIfPenaltyAppliedAfterLastDueDate(loan, lastChargeDate); addInstallmentIfPenaltyAppliedAfterLastDueDate(loan, lastChargeDate);
if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy()) if (loan.isProgressiveSchedule()) {
|| loan.hasContractTerminationTransaction()
|| (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) {
final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null); final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null);
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
} }
@@ -885,22 +872,16 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
LoanTransactionRelationTypeEnum.CHARGE_ADJUSTMENT); LoanTransactionRelationTypeEnum.CHARGE_ADJUSTMENT);
loanChargeAdjustmentTransaction.getLoanTransactionRelations().add(loanTransactionRelation); loanChargeAdjustmentTransaction.getLoanTransactionRelations().add(loanTransactionRelation);
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanRepaymentScheduleTransactionProcessorFactory
.determineProcessor(loan.transactionProcessingStrategy());
loan.addLoanTransaction(loanChargeAdjustmentTransaction);
if (loan.isInterestBearingAndInterestRecalculationEnabled()) { if (loan.isInterestBearingAndInterestRecalculationEnabled()) {
if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy()) if (loan.isProgressiveSchedule()) {
|| loan.hasContractTerminationTransaction()
|| (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) {
final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null); final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null);
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
} }
reprocessLoanTransactionsService.reprocessTransactions(loan); reprocessLoanTransactionsService.reprocessTransactions(loan, List.of(loanChargeAdjustmentTransaction));
} else { } else {
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanChargeAdjustmentTransaction, reprocessLoanTransactionsService.processLatestTransaction(loanChargeAdjustmentTransaction, loan);
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
} }
loan.addLoanTransaction(loanChargeAdjustmentTransaction);
loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(loanChargeAdjustmentTransaction); loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(loanChargeAdjustmentTransaction);
loanLifecycleStateMachine.determineAndTransition(loan, loan.getLastUserTransactionDate()); loanLifecycleStateMachine.determineAndTransition(loan, loan.getLastUserTransactionDate());
@@ -1444,12 +1425,12 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(waiveLoanChargeTransaction, loanCharge, final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(waiveLoanChargeTransaction, loanCharge,
waiveLoanChargeTransaction.getAmount(loan.getCurrency()).getAmount(), loanInstallmentNumber); waiveLoanChargeTransaction.getAmount(loan.getCurrency()).getAmount(), loanInstallmentNumber);
waiveLoanChargeTransaction.getLoanChargesPaid().add(loanChargePaidBy); waiveLoanChargeTransaction.getLoanChargesPaid().add(loanChargePaidBy);
loan.addLoanTransaction(waiveLoanChargeTransaction); loan.addLoanTransaction(waiveLoanChargeTransaction);
if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled() if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()
&& DateUtils.isBefore(loanCharge.getDueLocalDate(), businessDate)) { && DateUtils.isBefore(loanCharge.getDueLocalDate(), businessDate)) {
loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO); loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO);
} else if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy()) } else if (loan.isProgressiveSchedule()) {
|| loan.hasContractTerminationTransaction() || (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) {
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
} }
// Waive of charges whose due date falls after latest 'repayment' transaction don't require entire loan schedule // Waive of charges whose due date falls after latest 'repayment' transaction don't require entire loan schedule
@@ -66,7 +66,6 @@ public class LoanDisbursementService {
private final LoanChargeValidator loanChargeValidator; private final LoanChargeValidator loanChargeValidator;
private final LoanDisbursementValidator loanDisbursementValidator; private final LoanDisbursementValidator loanDisbursementValidator;
private final ReprocessLoanTransactionsService reprocessLoanTransactionsService;
private final LoanChargeService loanChargeService; private final LoanChargeService loanChargeService;
private final LoanBalanceService loanBalanceService; private final LoanBalanceService loanBalanceService;
private final LoanJournalEntryPoster loanJournalEntryPoster; private final LoanJournalEntryPoster loanJournalEntryPoster;
@@ -326,7 +325,7 @@ public class LoanDisbursementService {
for (LoanCharge loanCharge : tempCharges) { for (LoanCharge loanCharge : tempCharges) {
loanChargeValidator.validateLoanIsNotClosed(loan, loanCharge); loanChargeValidator.validateLoanIsNotClosed(loan, loanCharge);
loanChargeValidator.validateLoanChargeIsNotWaived(loan, loanCharge); loanChargeValidator.validateLoanChargeIsNotWaived(loan, loanCharge);
reprocessLoanTransactionsService.removeLoanCharge(loan, loanCharge); loanChargeService.removeLoanCharge(loan, loanCharge);
} }
loan.getTrancheCharges().clear(); loan.getTrancheCharges().clear();
} else { } else {
@@ -336,7 +335,7 @@ public class LoanDisbursementService {
if (loan.getCharges().contains(deleteCharge)) { if (loan.getCharges().contains(deleteCharge)) {
loanChargeValidator.validateLoanIsNotClosed(loan, deleteCharge); loanChargeValidator.validateLoanIsNotClosed(loan, deleteCharge);
loanChargeValidator.validateLoanChargeIsNotWaived(loan, deleteCharge); loanChargeValidator.validateLoanChargeIsNotWaived(loan, deleteCharge);
reprocessLoanTransactionsService.removeLoanCharge(loan, deleteCharge); loanChargeService.removeLoanCharge(loan, deleteCharge);
} }
} }
} }
@@ -359,7 +358,7 @@ public class LoanDisbursementService {
loanChargeValidator.validateLoanChargeIsNotWaived(loan, charge); // loanChargeValidator.validateLoanChargeIsNotWaived(loan, charge); //
return true; // return true; //
}) // }) //
.forEach(loanCharge -> reprocessLoanTransactionsService.removeLoanCharge(loan, loanCharge)); .forEach(loanCharge -> loanChargeService.removeLoanCharge(loan, loanCharge));
} }
// This method returns date format and locale if present in the JsonCommand // This method returns date format and locale if present in the JsonCommand
@@ -81,7 +81,7 @@ public class LoanPointInTimeServiceImpl implements LoanPointInTimeService {
// the loan's state // the loan's state
if (txCount != afterRemovalTxCount || chargeCount != afterRemovalChargeCount) { if (txCount != afterRemovalTxCount || chargeCount != afterRemovalChargeCount) {
ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null, null); ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null, null);
loanScheduleService.recalculateSchedule(loan, scheduleGeneratorDTO); loanScheduleService.regenerateScheduleWithReprocessingTransactions(loan, scheduleGeneratorDTO);
} }
LoanArrearsData arrearsData = arrearsAgingService.calculateArrearsForLoan(loan); LoanArrearsData arrearsData = arrearsAgingService.calculateArrearsForLoan(loan);
@@ -21,7 +21,6 @@ package org.apache.fineract.portfolio.loanaccount.service;
import jakarta.persistence.FlushModeType; import jakarta.persistence.FlushModeType;
import java.math.MathContext; import java.math.MathContext;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
@@ -65,7 +64,6 @@ public class LoanTransactionProcessingServiceImpl implements LoanTransactionProc
private final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessorFactory; private final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessorFactory;
private final LoanTermVariationsMapper loanMapper; private final LoanTermVariationsMapper loanMapper;
private final InterestScheduleModelRepositoryWrapper modelRepository; private final InterestScheduleModelRepositoryWrapper modelRepository;
private final LoanBalanceService loanBalanceService;
private final LoanTransactionService loanTransactionService; private final LoanTransactionService loanTransactionService;
@Override @Override
@@ -131,32 +129,6 @@ public class LoanTransactionProcessingServiceImpl implements LoanTransactionProc
return transactionProcessorFactory.determineProcessor(transactionProcessingStrategyCode); return transactionProcessorFactory.determineProcessor(transactionProcessingStrategyCode);
} }
@Override
public Optional<ChangedTransactionDetail> processPostDisbursementTransactions(Loan loan) {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(
loan.getTransactionProcessingStrategyCode());
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = loanTransactionService
.retrieveListOfTransactionsForReprocessing(loan);
final List<LoanTransaction> copyTransactions = new ArrayList<>();
if (allNonContraTransactionsPostDisbursement.isEmpty()) {
return Optional.empty();
}
// TODO: Probably this is not needed and can be eliminated, make sure to double check it
for (LoanTransaction loanTransaction : allNonContraTransactionsPostDisbursement) {
copyTransactions.add(LoanTransaction.copyTransactionProperties(loanTransaction));
}
final ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(
loan.getDisbursementDate(), copyTransactions, loan.getCurrency(), loan.getRepaymentScheduleInstallments(),
loan.getActiveCharges());
loanBalanceService.updateLoanSummaryDerivedFields(loan);
return Optional.of(changedTransactionDetail);
}
@Override @Override
public LoanScheduleDTO getRecalculatedSchedule(final ScheduleGeneratorDTO generatorDTO, Loan loan) { public LoanScheduleDTO getRecalculatedSchedule(final ScheduleGeneratorDTO generatorDTO, Loan loan) {
if (!loan.isInterestBearingAndInterestRecalculationEnabled() || loan.isNpa() || loan.isChargedOff()) { if (!loan.isInterestBearingAndInterestRecalculationEnabled() || loan.isNpa() || loan.isChargedOff()) {
@@ -232,9 +204,10 @@ public class LoanTransactionProcessingServiceImpl implements LoanTransactionProc
AdvancedPaymentScheduleTransactionProcessor advancedProcessor, Loan loan, LoanTransaction loanTransaction) { AdvancedPaymentScheduleTransactionProcessor advancedProcessor, Loan loan, LoanTransaction loanTransaction) {
Optional<ProgressiveLoanInterestScheduleModel> savedModel = modelRepository.getSavedModel(loan, Optional<ProgressiveLoanInterestScheduleModel> savedModel = modelRepository.getSavedModel(loan,
loanTransaction.getTransactionDate()); loanTransaction.getTransactionDate());
ProgressiveLoanInterestScheduleModel model = savedModel if (savedModel.isEmpty()) {
.orElseGet(() -> advancedProcessor.calculateInterestScheduleModel(loan.getId(), loanTransaction.getTransactionDate())); throw new IllegalArgumentException("No saved model found for loan transaction " + loanTransaction);
}
ProgressiveLoanInterestScheduleModel model = savedModel.get();
ProgressiveTransactionCtx progressiveContext = new ProgressiveTransactionCtx(loan.getCurrency(), ProgressiveTransactionCtx progressiveContext = new ProgressiveTransactionCtx(loan.getCurrency(),
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()),
new ChangedTransactionDetail(), model, getTotalRefundInterestAmount(loan)); new ChangedTransactionDetail(), model, getTotalRefundInterestAmount(loan));
@@ -21,11 +21,9 @@ package org.apache.fineract.portfolio.loanaccount.service;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.exception.PlatformServiceUnavailableException; import org.apache.fineract.infrastructure.core.exception.PlatformServiceUnavailableException;
import org.apache.fineract.organisation.holiday.domain.Holiday; import org.apache.fineract.organisation.holiday.domain.Holiday;
import org.apache.fineract.organisation.holiday.domain.HolidayRepository; import org.apache.fineract.organisation.holiday.domain.HolidayRepository;
@@ -51,18 +49,16 @@ import org.apache.fineract.portfolio.floatingrates.exception.FloatingRateNotFoun
import org.apache.fineract.portfolio.floatingrates.service.FloatingRatesReadPlatformService; import org.apache.fineract.portfolio.floatingrates.service.FloatingRatesReadPlatformService;
import org.apache.fineract.portfolio.group.domain.Group; import org.apache.fineract.portfolio.group.domain.Group;
import org.apache.fineract.portfolio.group.exception.GroupNotActiveException; import org.apache.fineract.portfolio.group.exception.GroupNotActiveException;
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO; import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO; import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
import org.apache.fineract.portfolio.note.domain.Note;
import org.apache.fineract.portfolio.note.domain.NoteRepository; import org.apache.fineract.portfolio.note.domain.NoteRepository;
@RequiredArgsConstructor @RequiredArgsConstructor
public class LoanUtilService { public class LoanUtilService implements ILoanUtilService {
private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository; private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository;
private final CalendarInstanceRepository calendarInstanceRepository; private final CalendarInstanceRepository calendarInstanceRepository;
@@ -74,17 +70,20 @@ public class LoanUtilService {
private final CalendarReadPlatformService calendarReadPlatformService; private final CalendarReadPlatformService calendarReadPlatformService;
private final NoteRepository noteRepository; private final NoteRepository noteRepository;
@Override
public ScheduleGeneratorDTO buildScheduleGeneratorDTO(final Loan loan, final LocalDate recalculateFrom) { public ScheduleGeneratorDTO buildScheduleGeneratorDTO(final Loan loan, final LocalDate recalculateFrom) {
final HolidayDetailDTO holidayDetailDTO = null; final HolidayDetailDTO holidayDetailDTO = null;
return buildScheduleGeneratorDTO(loan, recalculateFrom, null, holidayDetailDTO); return buildScheduleGeneratorDTO(loan, recalculateFrom, null, holidayDetailDTO);
} }
@Override
public ScheduleGeneratorDTO buildScheduleGeneratorDTO(final Loan loan, final LocalDate recalculateFrom, public ScheduleGeneratorDTO buildScheduleGeneratorDTO(final Loan loan, final LocalDate recalculateFrom,
final LocalDate rescheduleTill) { final LocalDate rescheduleTill) {
final HolidayDetailDTO holidayDetailDTO = null; final HolidayDetailDTO holidayDetailDTO = null;
return buildScheduleGeneratorDTO(loan, recalculateFrom, rescheduleTill, holidayDetailDTO); return buildScheduleGeneratorDTO(loan, recalculateFrom, rescheduleTill, holidayDetailDTO);
} }
@Override
public ScheduleGeneratorDTO buildScheduleGeneratorDTO(final Loan loan, final LocalDate recalculateFrom, final LocalDate recalculateTill, public ScheduleGeneratorDTO buildScheduleGeneratorDTO(final Loan loan, final LocalDate recalculateFrom, final LocalDate recalculateTill,
final HolidayDetailDTO holidayDetailDTO) { final HolidayDetailDTO holidayDetailDTO) {
HolidayDetailDTO holidayDetails = holidayDetailDTO; HolidayDetailDTO holidayDetails = holidayDetailDTO;
@@ -147,6 +146,7 @@ public class LoanUtilService {
return scheduleGeneratorDTO; return scheduleGeneratorDTO;
} }
@Override
public Boolean isLoanRepaymentsSyncWithMeeting(final Group group, final Calendar calendar) { public Boolean isLoanRepaymentsSyncWithMeeting(final Group group, final Calendar calendar) {
Boolean isSkipRepaymentOnFirstMonth = false; Boolean isSkipRepaymentOnFirstMonth = false;
Long entityId = null; Long entityId = null;
@@ -170,6 +170,7 @@ public class LoanUtilService {
return isSkipRepaymentOnFirstMonth; return isSkipRepaymentOnFirstMonth;
} }
@Override
public LocalDate getCalculatedRepaymentsStartingFromDate(final Loan loan) { public LocalDate getCalculatedRepaymentsStartingFromDate(final Loan loan) {
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstanceByEntityId(loan.getId(), final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstanceByEntityId(loan.getId(),
CalendarEntityType.LOANS.getValue()); CalendarEntityType.LOANS.getValue());
@@ -190,6 +191,7 @@ public class LoanUtilService {
return holidayDetailDTO; return holidayDetailDTO;
} }
@Override
public HolidayDetailDTO constructHolidayDTO(final Long officeId, LocalDate localDate) { public HolidayDetailDTO constructHolidayDTO(final Long officeId, LocalDate localDate) {
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(officeId, localDate, final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(officeId, localDate,
@@ -225,16 +227,6 @@ public class LoanUtilService {
return calculateRepaymentStartingFromDate(actualDisbursementDate, loan, calendar, calendarHistoryDataWrapper); return calculateRepaymentStartingFromDate(actualDisbursementDate, loan, calendar, calendarHistoryDataWrapper);
} }
public LocalDate getCalculatedRepaymentsStartingFromDate(final LocalDate actualDisbursementDate, final Loan loan,
final Calendar calendar) {
final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null;
if (calendar == null) {
return getCalculatedRepaymentsStartingFromDate(loan);
}
return calculateRepaymentStartingFromDate(actualDisbursementDate, loan, calendar, calendarHistoryDataWrapper);
}
private LocalDate calculateRepaymentStartingFromDate(final LocalDate actualDisbursementDate, final Loan loan, final Calendar calendar, private LocalDate calculateRepaymentStartingFromDate(final LocalDate actualDisbursementDate, final Loan loan, final Calendar calendar,
final CalendarHistoryDataWrapper calendarHistoryDataWrapper) { final CalendarHistoryDataWrapper calendarHistoryDataWrapper) {
LocalDate calculatedRepaymentsStartingFromDate = loan.getExpectedFirstRepaymentOnDate(); LocalDate calculatedRepaymentsStartingFromDate = loan.getExpectedFirstRepaymentOnDate();
@@ -305,6 +297,7 @@ public class LoanUtilService {
return calculatedRepaymentsStartingFromDate; return calculatedRepaymentsStartingFromDate;
} }
@Override
public void validateRepaymentTransactionType(LoanTransactionType repaymentTransactionType) { public void validateRepaymentTransactionType(LoanTransactionType repaymentTransactionType) {
if (!repaymentTransactionType.isRepaymentType()) { if (!repaymentTransactionType.isRepaymentType()) {
throw new PlatformServiceUnavailableException("error.msg.repaymentTransactionType.provided.not.a.repayment.type", throw new PlatformServiceUnavailableException("error.msg.repaymentTransactionType.provided.not.a.repayment.type",
@@ -313,6 +306,7 @@ public class LoanUtilService {
} }
} }
@Override
public void checkClientOrGroupActive(final Loan loan) { public void checkClientOrGroupActive(final Loan loan) {
final Client client = loan.client(); final Client client = loan.client();
if (client != null && client.isNotActive()) { if (client != null && client.isNotActive()) {
@@ -324,14 +318,4 @@ public class LoanUtilService {
} }
} }
public void persistNote(Loan loan, JsonCommand command, Map<String, Object> changes) {
if (command.hasParameter(LoanApiConstants.noteParameterName)) {
final String note = command.stringValueOfParameterNamed(LoanApiConstants.noteParameterName);
final Note newNote = Note.loanNote(loan, note);
changes.put(LoanApiConstants.noteParameterName, note);
noteRepository.save(newNote);
}
}
} }
@@ -952,10 +952,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final boolean isTransactionChronologicallyLatest = loanTransactionService.isChronologicallyLatestRepaymentOrWaiver(loan, final boolean isTransactionChronologicallyLatest = loanTransactionService.isChronologicallyLatestRepaymentOrWaiver(loan,
newInterestPaymentWaiverTransaction); newInterestPaymentWaiverTransaction);
if (newInterestPaymentWaiverTransaction.isNotZero()) {
loan.addLoanTransaction(newInterestPaymentWaiverTransaction);
}
final LoanRepaymentScheduleInstallment currentInstallment = loan.fetchLoanRepaymentScheduleInstallmentByDueDate(transactionDate); final LoanRepaymentScheduleInstallment currentInstallment = loan.fetchLoanRepaymentScheduleInstallmentByDueDate(transactionDate);
boolean reprocess = true; boolean reprocess = true;
@@ -970,6 +966,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(),
newInterestPaymentWaiverTransaction, new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), newInterestPaymentWaiverTransaction, new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(),
loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), null)); loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
reprocess = false; reprocess = false;
if (loan.isInterestBearingAndInterestRecalculationEnabled()) { if (loan.isInterestBearingAndInterestRecalculationEnabled()) {
if (currentInstallment == null || currentInstallment.isNotFullyPaidOff()) { if (currentInstallment == null || currentInstallment.isNotFullyPaidOff()) {
@@ -982,22 +979,25 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
} }
} }
if (!reprocess) {
loan.addLoanTransaction(newInterestPaymentWaiverTransaction);
}
} }
if (reprocess) { if (reprocess) {
reprocessChangedLoanTransactions(loan, scheduleGeneratorDTO); reprocessChangedLoanTransactions(loan, newInterestPaymentWaiverTransaction, scheduleGeneratorDTO);
} }
loanLifecycleStateMachine.determineAndTransition(loan, newInterestPaymentWaiverTransaction.getTransactionDate()); loanLifecycleStateMachine.determineAndTransition(loan, newInterestPaymentWaiverTransaction.getTransactionDate());
} }
private void reprocessChangedLoanTransactions(Loan loan, ScheduleGeneratorDTO scheduleGeneratorDTO) { private void reprocessChangedLoanTransactions(Loan loan, LoanTransaction newInterestPaymentWaiverTransaction,
ScheduleGeneratorDTO scheduleGeneratorDTO) {
if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) { if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) {
loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO); loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO);
} else if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy()) } else if (loan.isProgressiveSchedule()) {
|| loan.hasContractTerminationTransaction() || (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) {
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
} }
loan.addLoanTransaction(newInterestPaymentWaiverTransaction);
reprocessLoanTransactionsService.reprocessTransactions(loan); reprocessLoanTransactionsService.reprocessTransactions(loan);
if (loan.isInterestBearingAndInterestRecalculationEnabled()) { if (loan.isInterestBearingAndInterestRecalculationEnabled()) {
@@ -1304,13 +1304,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
validateLoanTransactionAmountChargeBack(loanTransaction, newTransaction); validateLoanTransactionAmountChargeBack(loanTransaction, newTransaction);
if (loan.isInterestBearing() && loan.isInterestRecalculationEnabled()) { if (loan.isInterestBearing() && loan.isInterestRecalculationEnabled() && loan.isProgressiveSchedule()) {
if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy()) final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null);
|| loan.hasContractTerminationTransaction() loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
|| (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) {
final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null);
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
}
} }
// Store the Loan Transaction Relation // Store the Loan Transaction Relation
@@ -2285,11 +2281,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loan.getLoanProductRelatedDetail().setPrincipal(loan.getPrincipalAmountForRepaymentSchedule()); loan.getLoanProductRelatedDetail().setPrincipal(loan.getPrincipalAmountForRepaymentSchedule());
if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) { if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) {
loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO); loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO);
} else if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy()) } else if (loan.isProgressiveSchedule()) {
|| loan.hasContractTerminationTransaction()
|| (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) {
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
reprocessLoanTransactionsService.processPostDisbursementTransactions(loan); reprocessLoanTransactionsService.reprocessTransactions(loan);
} }
} }
@@ -2441,9 +2435,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|| rescheduledRepaymentDate != null) { || rescheduledRepaymentDate != null) {
if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) { if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) {
loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO); loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO);
} else if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy()) } else if (loan.isProgressiveSchedule()) {
|| loan.hasContractTerminationTransaction()
|| (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) {
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
} }
} }
@@ -2757,9 +2749,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, null, null); final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, null, null);
loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO); loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO);
} }
final List<LoanTransaction> loanTransactions = loanTransactionRepository.findNonReversedTransactionsForReprocessingByLoan(loan); reprocessLoanTransactionsService.reprocessTransactions(loan, List.of(chargeOffTransaction));
loanTransactions.add(chargeOffTransaction);
reprocessLoanTransactionsService.reprocessParticularTransactions(loan, loanTransactions);
loan.addLoanTransaction(chargeOffTransaction); loan.addLoanTransaction(chargeOffTransaction);
} else { } else {
reprocessLoanTransactionsService.processLatestTransaction(chargeOffTransaction, loan); reprocessLoanTransactionsService.processLatestTransaction(chargeOffTransaction, loan);
@@ -3010,9 +3000,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} else { } else {
if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) { if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) {
loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO); loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO);
} else if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy()) } else if (loan.isProgressiveSchedule()) {
|| loan.hasContractTerminationTransaction()
|| (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) {
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
} }
loan.addLoanTransaction(interestRefundTxn); loan.addLoanTransaction(interestRefundTxn);
@@ -3059,13 +3047,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
public void handleChargebackTransaction(final Loan loan, LoanTransaction chargebackTransaction) { public void handleChargebackTransaction(final Loan loan, LoanTransaction chargebackTransaction) {
loanTransactionValidator.validateIfTransactionIsChargeback(chargebackTransaction); loanTransactionValidator.validateIfTransactionIsChargeback(chargebackTransaction);
loan.addLoanTransaction(chargebackTransaction);
if (loan.isInterestBearing() && loan.isInterestRecalculationEnabled()) { if (loan.isInterestBearing() && loan.isInterestRecalculationEnabled()) {
final List<LoanTransaction> transactions = loanTransactionService.retrieveListOfTransactionsForReprocessing(loan); loan.addLoanTransaction(chargebackTransaction);
loanTransactionProcessingService.reprocessLoanTransactions(loan.getTransactionProcessingStrategyCode(), reprocessLoanTransactionsService.reprocessTransactions(loan);
loan.getDisbursementDate(), transactions, loan.getCurrency(), loan.getRepaymentScheduleInstallments(),
loan.getActiveCharges());
} else { } else {
loan.addLoanTransaction(chargebackTransaction);
loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), chargebackTransaction, loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), chargebackTransaction,
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null)); new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
@@ -3252,16 +3238,24 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final LoanTransaction loanTransaction = LoanTransaction.writeoff(loan, loan.getOffice(), writtenOffOnLocalDate, externalId); final LoanTransaction loanTransaction = LoanTransaction.writeoff(loan, loan.getOffice(), writtenOffOnLocalDate, externalId);
LocalDate lastTransactionDate = loan.getLastUserTransactionDate(); LocalDate lastTransactionDate = loan.getLastUserTransactionDate();
if (DateUtils.isAfter(lastTransactionDate, writtenOffOnLocalDate)) { if (DateUtils.isAfter(lastTransactionDate, writtenOffOnLocalDate)) {
final String errorMessage = "The date of the writeoff transaction must occur on or before previous transactions."; final String errorMessage = "The date of the writeoff transaction must occur on or after previous transactions.";
throw new InvalidLoanStateTransitionException("writeoff", "must.occur.on.or.after.other.transaction.dates", errorMessage, throw new InvalidLoanStateTransitionException("writeoff", "must.occur.on.or.after.other.transaction.dates", errorMessage,
writtenOffOnLocalDate); writtenOffOnLocalDate);
} }
loan.addLoanTransaction(loanTransaction); if (loan.isInterestBearingAndInterestRecalculationEnabled()
loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), loanTransaction, && DateUtils.isBeforeBusinessDate(loanTransaction.getTransactionDate())) {
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), if (loan.isProgressiveSchedule()) {
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null)); loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
}
loan.addLoanTransaction(loanTransaction);
reprocessLoanTransactionsService.reprocessTransactions(loan);
} else {
loan.addLoanTransaction(loanTransaction);
loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), loanTransaction,
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
}
loanBalanceService.updateLoanSummaryDerivedFields(loan); loanBalanceService.updateLoanSummaryDerivedFields(loan);
loanLifecycleStateMachine.transition(LoanEvent.WRITE_OFF_OUTSTANDING, loan); loanLifecycleStateMachine.transition(LoanEvent.WRITE_OFF_OUTSTANDING, loan);
changes.put(PARAM_STATUS, LoanEnumerations.status(loan.getLoanStatus())); changes.put(PARAM_STATUS, LoanEnumerations.status(loan.getLoanStatus()));
@@ -3272,17 +3266,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
if (loan.isDisbursementAllowed() && loan.atLeastOnceDisbursed()) { if (loan.isDisbursementAllowed() && loan.atLeastOnceDisbursed()) {
loan.getLoanRepaymentScheduleDetail().setPrincipal(loan.getDisbursedAmount()); loan.getLoanRepaymentScheduleDetail().setPrincipal(loan.getDisbursedAmount());
loan.removeDisbursementDetail(); loan.removeDisbursementDetail();
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); if (loan.isCumulativeSchedule()) {
if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) { if (loan.isInterestBearingAndInterestRecalculationEnabled()) {
loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO); loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO);
} else if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy()) } else {
|| loan.hasContractTerminationTransaction() loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
|| (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) { }
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
} }
reprocessLoanTransactionsService.reprocessTransactions(loan);
LocalDate lastLoanTransactionDate = loan.getLatestTransactionDate();
loanLifecycleStateMachine.determineAndTransition(loan, lastLoanTransactionDate);
} }
} }
@@ -3385,8 +3375,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) { if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) {
loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO); loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO);
} else if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy()) } else if (loan.isProgressiveSchedule()) {
|| loan.hasContractTerminationTransaction() || (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) {
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
} }
@@ -3422,7 +3411,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
&& DateUtils.isEqual(loanTermVariations.getTermApplicableFrom(), lastTransactionDate)) && DateUtils.isEqual(loanTermVariations.getTermApplicableFrom(), lastTransactionDate))
|| DateUtils.isAfter(loanTermVariations.getTermApplicableFrom(), lastTransactionDate)); || DateUtils.isAfter(loanTermVariations.getTermApplicableFrom(), lastTransactionDate));
reverseExistingTransactionsTillLastDisbursal(loan, lastDisbursalTransaction); reverseExistingTransactionsTillLastDisbursal(loan, lastDisbursalTransaction);
loanScheduleService.recalculateSchedule(loan, scheduleGeneratorDTO); loanScheduleService.regenerateScheduleWithReprocessingTransactions(loan, scheduleGeneratorDTO);
actualChanges.put("undolastdisbursal", "true"); actualChanges.put("undolastdisbursal", "true");
actualChanges.put("disbursedAmount", loan.getDisbursedAmount()); actualChanges.put("disbursedAmount", loan.getDisbursedAmount());
loanLifecycleStateMachine.determineAndTransition(loan, loan.getLastUserTransactionDate()); loanLifecycleStateMachine.determineAndTransition(loan, loan.getLastUserTransactionDate());
@@ -3442,8 +3431,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
"reversed"); "reversed");
writeOffTransaction.reverse(); writeOffTransaction.reverse();
loanLifecycleStateMachine.transition(LoanEvent.WRITE_OFF_OUTSTANDING_UNDO, loan); loanLifecycleStateMachine.transition(LoanEvent.WRITE_OFF_OUTSTANDING_UNDO, loan);
if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy()) if (loan.isProgressiveSchedule()) {
|| loan.hasContractTerminationTransaction() || (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) {
final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null); final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null);
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
} }
@@ -81,11 +81,9 @@ public class ProgressiveLoanInterestRefundServiceImpl implements InterestRefundS
LocalDate relatedRefundTransactionDate, List<LoanTransaction> transactionsToReprocess) { LocalDate relatedRefundTransactionDate, List<LoanTransaction> transactionsToReprocess) {
List<LoanRepaymentScheduleInstallment> installmentsToReprocess = new ArrayList<>( List<LoanRepaymentScheduleInstallment> installmentsToReprocess = new ArrayList<>(
loan.getRepaymentScheduleInstallments().stream().filter(i -> !i.isReAged() && !i.isAdditional()).toList()); loan.getRepaymentScheduleInstallments().stream().filter(i -> !i.isReAged() && !i.isAdditional()).toList());
if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy())
|| loan.hasContractTerminationTransaction() || (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) { final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null);
final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null); loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
}
Pair<ChangedTransactionDetail, ProgressiveLoanInterestScheduleModel> reprocessResult = processor Pair<ChangedTransactionDetail, ProgressiveLoanInterestScheduleModel> reprocessResult = processor
.reprocessProgressiveLoanTransactions(loan.getDisbursementDate(), relatedRefundTransactionDate, transactionsToReprocess, .reprocessProgressiveLoanTransactions(loan.getDisbursementDate(), relatedRefundTransactionDate, transactionsToReprocess,
@@ -21,23 +21,16 @@ package org.apache.fineract.portfolio.loanaccount.service;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.fineract.infrastructure.core.service.MathUtil; import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.portfolio.loanaccount.data.LoanSummaryData; import org.apache.fineract.portfolio.loanaccount.data.LoanSummaryData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionBalance; import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionBalance;
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator; import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
@@ -51,10 +44,8 @@ import org.springframework.transaction.annotation.Transactional;
@Slf4j @Slf4j
public class ProgressiveLoanSummaryDataProvider extends CommonLoanSummaryDataProvider { public class ProgressiveLoanSummaryDataProvider extends CommonLoanSummaryDataProvider {
private final AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor;
private final EMICalculator emiCalculator; private final EMICalculator emiCalculator;
private final LoanRepositoryWrapper loanRepository; private final LoanRepositoryWrapper loanRepository;
private final LoanTransactionRepository loanTransactionRepository;
private final InterestScheduleModelRepositoryWrapper modelRepository; private final InterestScheduleModelRepositoryWrapper modelRepository;
@Override @Override
@@ -81,24 +72,6 @@ public class ProgressiveLoanSummaryDataProvider extends CommonLoanSummaryDataPro
&& businessDate.isAfter(i.getFromDate()) && !businessDate.isAfter(i.getDueDate())).findFirst(); && businessDate.isAfter(i.getFromDate()) && !businessDate.isAfter(i.getDueDate())).findFirst();
} }
private ProgressiveLoanInterestScheduleModel calculateModel(Loan loan, LocalDate businessDate) {
final List<LoanTransaction> transactionsToReprocess = loanTransactionRepository
.findNonReversedTransactionsForReprocessingByLoan(loan).stream().filter(t -> !t.isAccrualActivity()).toList();
Pair<ChangedTransactionDetail, ProgressiveLoanInterestScheduleModel> changedTransactionDetailProgressiveLoanInterestScheduleModelPair = advancedPaymentScheduleTransactionProcessor
.reprocessProgressiveLoanTransactions(loan.getDisbursementDate(), businessDate, transactionsToReprocess, loan.getCurrency(),
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges());
ProgressiveLoanInterestScheduleModel model = changedTransactionDetailProgressiveLoanInterestScheduleModelPair.getRight();
final List<Long> replayedTransactions = changedTransactionDetailProgressiveLoanInterestScheduleModelPair.getLeft()
.getTransactionChanges().stream().filter(change -> change.getOldTransaction() != null && change.getNewTransaction() != null)
.map(change -> change.getNewTransaction().getId()).filter(Objects::nonNull).toList();
if (!replayedTransactions.isEmpty()) {
log.warn("Reprocessed transactions show differences: There are unsaved changes of the following transactions: {}",
replayedTransactions);
}
return model;
}
@Override @Override
public BigDecimal computeTotalUnpaidPayableNotDueInterestAmountOnActualPeriod(final Loan loan, public BigDecimal computeTotalUnpaidPayableNotDueInterestAmountOnActualPeriod(final Loan loan,
final Collection<LoanSchedulePeriodData> periods, final LocalDate businessDate, final CurrencyData currency, final Collection<LoanSchedulePeriodData> periods, final LocalDate businessDate, final CurrencyData currency,
@@ -117,7 +90,7 @@ public class ProgressiveLoanSummaryDataProvider extends CommonLoanSummaryDataPro
Optional<ProgressiveLoanInterestScheduleModel> savedModel = modelRepository.getSavedModel(loan, businessDate); Optional<ProgressiveLoanInterestScheduleModel> savedModel = modelRepository.getSavedModel(loan, businessDate);
ProgressiveLoanInterestScheduleModel model = savedModel.orElseGet(() -> calculateModel(loan, businessDate)); ProgressiveLoanInterestScheduleModel model = savedModel.orElse(null);
if (model != null) { if (model != null) {
OutstandingDetails outstandingDetails = emiCalculator.getOutstandingAmountsTillDate(model, businessDate); OutstandingDetails outstandingDetails = emiCalculator.getOutstandingAmountsTillDate(model, businessDate);
if (!loan.isInterestRecalculationEnabled()) { if (!loan.isInterestRecalculationEnabled()) {
@@ -19,10 +19,8 @@
package org.apache.fineract.portfolio.loanaccount.service; package org.apache.fineract.portfolio.loanaccount.service;
import jakarta.persistence.FlushModeType; import jakarta.persistence.FlushModeType;
import java.time.LocalDate;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.annotation.WithFlushMode; import org.apache.fineract.infrastructure.core.annotation.WithFlushMode;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
@@ -30,16 +28,11 @@ import org.apache.fineract.infrastructure.event.business.domain.loan.transaction
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanAccrualTransactionCreatedBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanAccrualTransactionCreatedBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionBusinessEvent;
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.interestpauses.service.LoanAccountTransfersService; import org.apache.fineract.portfolio.interestpauses.service.LoanAccountTransfersService;
import org.apache.fineract.portfolio.loanaccount.data.TransactionChangeData; import org.apache.fineract.portfolio.loanaccount.data.TransactionChangeData;
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail; import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountService; import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountService;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
@@ -78,111 +71,41 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
} }
@Override @Override
public void reprocessParticularTransactions(final Loan loan, final List<LoanTransaction> loanTransactions) { public void reprocessTransactions(final Loan loan, final List<LoanTransaction> newTransactions) {
final ChangedTransactionDetail changedTransactionDetail = reprocessTransactionsAndFetchChangedTransactions(loan, loanTransactions);
handleChangedDetail(changedTransactionDetail);
}
@Override
public void reprocessTransactionsWithPostTransactionChecks(final Loan loan, final LocalDate transactionDate) {
final List<LoanTransaction> transactions = loanTransactionRepository.findNonReversedTransactionsForReprocessingByLoan(loan); final List<LoanTransaction> transactions = loanTransactionRepository.findNonReversedTransactionsForReprocessingByLoan(loan);
transactions.addAll(newTransactions);
final ChangedTransactionDetail changedTransactionDetail = reprocessTransactionsAndFetchChangedTransactions(loan, transactions); final ChangedTransactionDetail changedTransactionDetail = reprocessTransactionsAndFetchChangedTransactions(loan, transactions);
handleChangedDetail(changedTransactionDetail); handleChangedDetail(changedTransactionDetail);
} }
@Override @Override
public void reprocessTransactionsWithoutChecks(final Loan loan, final LocalDate transactionDate, public void reprocessTransactionsWithoutChecks(final Loan loan, final List<LoanTransaction> newTransactions) {
final List<LoanTransaction> newTransactions) {
final List<LoanTransaction> transactions = loanTransactionRepository.findNonReversedTransactionsForReprocessingByLoan(loan); final List<LoanTransaction> transactions = loanTransactionRepository.findNonReversedTransactionsForReprocessingByLoan(loan);
transactions.addAll(newTransactions); transactions.addAll(newTransactions);
reprocessTransactionsAndFetchChangedTransactions(loan, transactions); reprocessTransactionsAndFetchChangedTransactions(loan, transactions);
} }
@Override
public void processPostDisbursementTransactions(final Loan loan) {
loanTransactionProcessingService.processPostDisbursementTransactions(loan).ifPresent(this::handleChangedDetail);
}
@Override
public void removeLoanCharge(final Loan loan, final LoanCharge loanCharge) {
final boolean removed = loanCharge.isActive();
if (removed) {
loanCharge.setActive(false);
final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
wrapper.reprocess(loan.getCurrency(), loan.getDisbursementDate(), loan.getRepaymentScheduleInstallments(),
loan.getActiveCharges());
loan.updateSummaryWithTotalFeeChargesDueAtDisbursement(loan.deriveSumTotalOfChargesDueAtDisbursement());
}
removeOrModifyTransactionAssociatedWithLoanChargeIfDueAtDisbursement(loan, loanCharge);
if (!loanCharge.isDueAtDisbursement() && loanCharge.isPaidOrPartiallyPaid(loan.getCurrency())) {
/*
* TODO Vishwas Currently we do not allow removing a loan charge after a loan is approved (hence there is no
* need to adjust any loan transactions).
*
* Consider removing this block of code or logically completing it for the future by getting the list of
* affected Transactions
*/
reprocessTransactions(loan);
return;
}
loan.getLoanCharges().remove(loanCharge);
loanBalanceService.updateLoanSummaryDerivedFields(loan);
}
private void removeOrModifyTransactionAssociatedWithLoanChargeIfDueAtDisbursement(final Loan loan, final LoanCharge loanCharge) {
if (loanCharge.isDueAtDisbursement()) {
LoanTransaction transactionToRemove = null;
List<LoanTransaction> transactions = loan.getLoanTransactions();
for (final LoanTransaction transaction : transactions) {
if (transaction.isRepaymentAtDisbursement()
&& doesLoanChargePaidByContainLoanCharge(transaction.getLoanChargesPaid(), loanCharge)) {
final MonetaryCurrency currency = loan.getCurrency();
final Money chargeAmount = Money.of(currency, loanCharge.amount());
if (transaction.isGreaterThan(chargeAmount)) {
final Money principalPortion = Money.zero(currency);
final Money interestPortion = Money.zero(currency);
final Money penaltyChargesPortion = Money.zero(currency);
transaction.updateComponentsAndTotal(principalPortion, interestPortion, chargeAmount, penaltyChargesPortion);
} else {
transactionToRemove = transaction;
}
}
}
if (transactionToRemove != null) {
loan.removeLoanTransaction(transactionToRemove);
}
}
}
private boolean doesLoanChargePaidByContainLoanCharge(Set<LoanChargePaidBy> loanChargePaidBys, LoanCharge loanCharge) {
return loanChargePaidBys.stream() //
.anyMatch(loanChargePaidBy -> loanChargePaidBy.getLoanCharge().equals(loanCharge));
}
@Override @Override
public void processLatestTransaction(final LoanTransaction loanTransaction, final Loan loan) { public void processLatestTransaction(final LoanTransaction loanTransaction, final Loan loan) {
LoanRepaymentScheduleTransactionProcessor transactionProcessor = loanTransactionProcessingService LoanRepaymentScheduleTransactionProcessor transactionProcessor = loanTransactionProcessingService
.getTransactionProcessor(loan.getTransactionProcessingStrategyCode()); .getTransactionProcessor(loan.getTransactionProcessingStrategyCode());
TransactionCtx transactionCtx; TransactionCtx transactionCtx;
if (transactionProcessor instanceof AdvancedPaymentScheduleTransactionProcessor advancedProcessor) { if (transactionProcessor instanceof AdvancedPaymentScheduleTransactionProcessor) {
Optional<ProgressiveLoanInterestScheduleModel> savedModel = interestScheduleModelRepositoryWrapper.getSavedModel(loan, Optional<ProgressiveLoanInterestScheduleModel> savedModel = interestScheduleModelRepositoryWrapper.getSavedModel(loan,
loanTransaction.getTransactionDate()); loanTransaction.getTransactionDate());
ProgressiveLoanInterestScheduleModel model = savedModel if (savedModel.isEmpty()) {
.orElseGet(() -> advancedProcessor.calculateInterestScheduleModel(loan.getId(), loanTransaction.getTransactionDate())); throw new IllegalArgumentException("No saved model found for loan transaction " + loanTransaction);
} else {
final ProgressiveTransactionCtx progressiveTransactionCtx = new ProgressiveTransactionCtx(loan.getCurrency(), final ProgressiveTransactionCtx progressiveTransactionCtx = new ProgressiveTransactionCtx(loan.getCurrency(),
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()),
new ChangedTransactionDetail(), model); new ChangedTransactionDetail(), savedModel.get());
progressiveTransactionCtx.setChargedOff(loan.isChargedOff()); progressiveTransactionCtx.setChargedOff(loan.isChargedOff());
progressiveTransactionCtx.setWrittenOff(loan.isClosedWrittenOff()); progressiveTransactionCtx.setWrittenOff(loan.isClosedWrittenOff());
progressiveTransactionCtx.setContractTerminated(loan.isContractTermination()); progressiveTransactionCtx.setContractTerminated(loan.isContractTermination());
transactionCtx = progressiveTransactionCtx; transactionCtx = progressiveTransactionCtx;
}
} else { } else {
transactionCtx = new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), transactionCtx = new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
new MoneyHolder(loan.getTotalOverpaidAsMoney()), new ChangedTransactionDetail()); new MoneyHolder(loan.getTotalOverpaidAsMoney()), new ChangedTransactionDetail());
@@ -201,11 +124,7 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
@Override @Override
public void updateModel(Loan loan) { public void updateModel(Loan loan) {
Optional<ProgressiveLoanInterestScheduleModel> savedModel = interestScheduleModelRepositoryWrapper.getSavedModel(loan, interestScheduleModelRepositoryWrapper.getSavedModel(loan, ThreadLocalContextUtil.getBusinessDate());
ThreadLocalContextUtil.getBusinessDate());
if (savedModel.isEmpty()) {
reprocessTransactions(loan);
}
} }
private void handleChangedDetail(final ChangedTransactionDetail changedTransactionDetail) { private void handleChangedDetail(final ChangedTransactionDetail changedTransactionDetail) {
@@ -67,6 +67,7 @@ import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualsProcessingS
import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService; import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
import org.apache.fineract.portfolio.loanaccount.service.LoanDownPaymentHandlerService; import org.apache.fineract.portfolio.loanaccount.service.LoanDownPaymentHandlerService;
import org.apache.fineract.portfolio.loanaccount.service.LoanJournalEntryPoster; import org.apache.fineract.portfolio.loanaccount.service.LoanJournalEntryPoster;
import org.apache.fineract.portfolio.loanaccount.service.LoanScheduleService;
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService; import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
import org.apache.fineract.portfolio.loanaccount.service.ReprocessLoanTransactionsService; import org.apache.fineract.portfolio.loanaccount.service.ReprocessLoanTransactionsService;
import org.apache.fineract.portfolio.note.domain.Note; import org.apache.fineract.portfolio.note.domain.Note;
@@ -100,6 +101,7 @@ public class LoanAdjustmentServiceImpl implements LoanAdjustmentService {
private final ReprocessLoanTransactionsService reprocessLoanTransactionsService; private final ReprocessLoanTransactionsService reprocessLoanTransactionsService;
private final LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository; private final LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository;
private final LoanBuyDownFeeBalanceRepository loanBuyDownFeeBalanceRepository; private final LoanBuyDownFeeBalanceRepository loanBuyDownFeeBalanceRepository;
private final LoanScheduleService loanScheduleService;
@Override @Override
public CommandProcessingResult adjustLoanTransaction(Loan loan, LoanTransaction transactionToAdjust, LoanAdjustmentParameter parameter, public CommandProcessingResult adjustLoanTransaction(Loan loan, LoanTransaction transactionToAdjust, LoanAdjustmentParameter parameter,
@@ -346,7 +348,7 @@ public class LoanAdjustmentServiceImpl implements LoanAdjustmentService {
} }
if (transactionForAdjustment.getTypeOf().equals(LoanTransactionType.CAPITALIZED_INCOME)) { if (transactionForAdjustment.getTypeOf().equals(LoanTransactionType.CAPITALIZED_INCOME)) {
reprocessLoanTransactionsService.reprocessTransactions(loan); loanScheduleService.regenerateScheduleWithReprocessingTransactions(loan);
} }
} }
@@ -76,7 +76,7 @@ public class LoanContractTerminationServiceImpl {
private final LoanTransactionService loanTransactionService; private final LoanTransactionService loanTransactionService;
public CommandProcessingResult applyContractTermination(final JsonCommand command) { public CommandProcessingResult applyContractTermination(final JsonCommand command) {
final Loan loan = loanAssembler.assembleFrom(command.getLoanId()); Loan loan = loanAssembler.assembleFrom(command.getLoanId());
// validate client or group is active // validate client or group is active
loanUtilService.checkClientOrGroupActive(loan); loanUtilService.checkClientOrGroupActive(loan);
@@ -87,7 +87,19 @@ public class LoanContractTerminationServiceImpl {
final Map<String, Object> changes = new LinkedHashMap<>(); final Map<String, Object> changes = new LinkedHashMap<>();
final LoanTransaction contractTermination = LoanTransaction.contractTermination(loan, DateUtils.getBusinessLocalDate(), externalId); final LoanTransaction contractTermination = LoanTransaction.contractTermination(loan, DateUtils.getBusinessLocalDate(), externalId);
loanTransactionRepository.save(contractTermination);
// Mark Contract Termination, Update Loan SubStatus
loan.setLoanSubStatus(LoanSubStatus.CONTRACT_TERMINATION);
changes.put(LoanApiConstants.subStatusAttributeName, loan.getLoanSubStatus().getCode());
if (loan.isInterestBearingAndInterestRecalculationEnabled()) {
loanScheduleService.regenerateRepaymentSchedule(loan);
reprocessLoanTransactionsService.reprocessTransactions(loan, List.of(contractTermination));
loan.addLoanTransaction(contractTermination);
} else {
reprocessLoanTransactionsService.processLatestTransaction(contractTermination, loan);
loan.addLoanTransaction(contractTermination);
}
final String noteText = command.stringValueOfParameterNamed("note"); final String noteText = command.stringValueOfParameterNamed("note");
if (StringUtils.isNotBlank(noteText)) { if (StringUtils.isNotBlank(noteText)) {
@@ -95,22 +107,7 @@ public class LoanContractTerminationServiceImpl {
final Note note = Note.loanTransactionNote(loan, contractTermination, noteText); final Note note = Note.loanTransactionNote(loan, contractTermination, noteText);
noteRepository.save(note); noteRepository.save(note);
} }
loanTransactionRepository.saveAndFlush(contractTermination);
// Mark Contract Termination, Update Loan SubStatus
loan.setLoanSubStatus(LoanSubStatus.CONTRACT_TERMINATION);
loanRepository.save(loan);
changes.put(LoanApiConstants.subStatusAttributeName, loan.getLoanSubStatus().getCode());
if (loan.isInterestBearingAndInterestRecalculationEnabled()) {
final List<LoanTransaction> loanTransactions = loanTransactionService.retrieveListOfTransactionsForReprocessing(loan);
loanTransactions.add(contractTermination);
reprocessLoanTransactionsService.reprocessParticularTransactions(loan, loanTransactions);
loan.addLoanTransaction(contractTermination);
} else {
reprocessLoanTransactionsService.processLatestTransaction(contractTermination, loan);
loan.addLoanTransaction(contractTermination);
}
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan)); businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
businessEventNotifierService.notifyPostBusinessEvent(new LoanTransactionContractTerminationPostBusinessEvent(contractTermination)); businessEventNotifierService.notifyPostBusinessEvent(new LoanTransactionContractTerminationPostBusinessEvent(contractTermination));
@@ -62,17 +62,16 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepositor
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeInterestHandlingType; import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeInterestHandlingType;
import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter; import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionNotFoundException; import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionNotFoundException;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
import org.apache.fineract.portfolio.loanaccount.repository.LoanCapitalizedIncomeBalanceRepository; import org.apache.fineract.portfolio.loanaccount.repository.LoanCapitalizedIncomeBalanceRepository;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator; import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
import org.apache.fineract.portfolio.loanaccount.service.InterestScheduleModelRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler; import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService; import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanRepaymentScheduleService; import org.apache.fineract.portfolio.loanaccount.service.LoanRepaymentScheduleService;
import org.apache.fineract.portfolio.loanaccount.service.LoanScheduleService; import org.apache.fineract.portfolio.loanaccount.service.LoanScheduleService;
import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionService;
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService; import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
import org.apache.fineract.portfolio.loanaccount.service.ReprocessLoanTransactionsService; import org.apache.fineract.portfolio.loanaccount.service.ReprocessLoanTransactionsService;
import org.apache.fineract.portfolio.note.domain.Note; import org.apache.fineract.portfolio.note.domain.Note;
@@ -100,14 +99,16 @@ public class LoanReAgingService {
private final LoanRepaymentScheduleService loanRepaymentScheduleService; private final LoanRepaymentScheduleService loanRepaymentScheduleService;
private final LoanReadPlatformService loanReadPlatformService; private final LoanReadPlatformService loanReadPlatformService;
private final LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository; private final LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository;
private final InterestScheduleModelRepositoryWrapper modelRepository;
private final LoanTransactionService loanTransactionService;
public CommandProcessingResult reAge(final Long loanId, final JsonCommand command) { public CommandProcessingResult reAge(final Long loanId, final JsonCommand command) {
final Loan loan = loanAssembler.assembleFrom(loanId); final Loan loan = loanAssembler.assembleFrom(loanId);
reAgingValidator.validateReAge(loan, command); reAgingValidator.validateReAge(loan, command);
final LoanTransaction reAgeTransaction = createReAgeTransaction(loan, command); final LoanTransaction reAgeTransaction = createReAgeTransaction(loan, command);
loanTransactionRepository.saveAndFlush(reAgeTransaction);
processReAgeTransaction(loan, reAgeTransaction, true); processReAgeTransaction(loan, reAgeTransaction, true);
loanTransactionRepository.saveAndFlush(reAgeTransaction);
loan.updateLoanScheduleDependentDerivedFields(); loan.updateLoanScheduleDependentDerivedFields();
final Map<String, Object> changes = new LinkedHashMap<>(); final Map<String, Object> changes = new LinkedHashMap<>();
@@ -170,8 +171,7 @@ public class LoanReAgingService {
if (reAgeTransaction == null) { if (reAgeTransaction == null) {
throw new LoanTransactionNotFoundException("Re-Age transaction for loan was not found"); throw new LoanTransactionNotFoundException("Re-Age transaction for loan was not found");
} }
if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy()) if (loan.isProgressiveSchedule()) {
|| loan.hasContractTerminationTransaction() || (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) {
final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null); final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null);
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
} }
@@ -196,31 +196,16 @@ public class LoanReAgingService {
} }
private void processReAgeTransaction(final Loan loan, final LoanTransaction reAgeTransaction, final boolean withPostTransactionChecks) { private void processReAgeTransaction(final Loan loan, final LoanTransaction reAgeTransaction, final boolean withPostTransactionChecks) {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanRepaymentScheduleTransactionProcessorFactory if (reAgeTransaction.getTransactionDate().isBefore(reAgeTransaction.getSubmittedOnDate())) {
.determineProcessor(loan.transactionProcessingStrategy()); final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null);
if (reAgeTransaction.getTransactionDate().isBefore(reAgeTransaction.getSubmittedOnDate()) loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
&& !loan.isInterestBearingAndInterestRecalculationEnabled()) {
if (withPostTransactionChecks) { if (withPostTransactionChecks) {
reprocessLoanTransactionsService.reprocessTransactionsWithPostTransactionChecks(loan, reprocessLoanTransactionsService.reprocessTransactions(loan, List.of(reAgeTransaction));
reAgeTransaction.getTransactionDate());
} else { } else {
reprocessLoanTransactionsService.reprocessTransactionsWithoutChecks(loan, reAgeTransaction.getTransactionDate(), reprocessLoanTransactionsService.reprocessTransactionsWithoutChecks(loan, List.of(reAgeTransaction));
List.of(reAgeTransaction));
} }
} else if (loan.isInterestBearingAndInterestRecalculationEnabled()) {
if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy())
|| loan.hasContractTerminationTransaction()
|| (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) {
final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null);
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
}
final List<LoanTransaction> loanTransactions = loanTransactionRepository.findNonReversedTransactionsForReprocessingByLoan(loan);
loanTransactions.add(reAgeTransaction);
reprocessLoanTransactionsService.reprocessParticularTransactions(loan, loanTransactions);
} else { } else {
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(reAgeTransaction, reprocessLoanTransactionsService.processLatestTransaction(reAgeTransaction, loan);
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
} }
} }
@@ -21,8 +21,6 @@ package org.apache.fineract.portfolio.loanaccount.starter;
import java.util.List; import java.util.List;
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.CreocoreLoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.CreocoreLoanRepaymentScheduleTransactionProcessor;
@@ -136,12 +134,11 @@ public class LoanAccountAutoStarter {
@Bean @Bean
@Conditional(AdvancedPaymentScheduleTransactionProcessorCondition.class) @Conditional(AdvancedPaymentScheduleTransactionProcessorCondition.class)
public AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor(final EMICalculator emiCalculator, public AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor(final EMICalculator emiCalculator,
final LoanRepositoryWrapper loanRepositoryWrapper,
final @Lazy ProgressiveLoanInterestRefundServiceImpl progressiveLoanInterestRefundService, final @Lazy ProgressiveLoanInterestRefundServiceImpl progressiveLoanInterestRefundService,
final ExternalIdFactory externalIdFactory, final LoanScheduleComponent loanSchedule, final ExternalIdFactory externalIdFactory, final LoanScheduleComponent loanSchedule,
final LoanTransactionRepository loanTransactionRepository, final LoanChargeValidator loanChargeValidator, final LoanChargeValidator loanChargeValidator, final LoanBalanceService loanBalanceService,
final LoanBalanceService loanBalanceService, @Lazy final LoanChargeService loanChargeService) { @Lazy final LoanChargeService loanChargeService) {
return new AdvancedPaymentScheduleTransactionProcessor(emiCalculator, loanRepositoryWrapper, progressiveLoanInterestRefundService, return new AdvancedPaymentScheduleTransactionProcessor(emiCalculator, progressiveLoanInterestRefundService, externalIdFactory,
externalIdFactory, loanSchedule, loanTransactionRepository, loanChargeValidator, loanBalanceService, loanChargeService); loanSchedule, loanChargeValidator, loanBalanceService, loanChargeService);
} }
} }
@@ -112,6 +112,7 @@ import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoReadPlat
import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoReadPlatformServiceImpl; import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoReadPlatformServiceImpl;
import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoWritePlatformService; import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoWritePlatformService;
import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoWritePlatformServiceImpl; import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoWritePlatformServiceImpl;
import org.apache.fineract.portfolio.loanaccount.service.ILoanUtilService;
import org.apache.fineract.portfolio.loanaccount.service.InterestRefundServiceDelegate; import org.apache.fineract.portfolio.loanaccount.service.InterestRefundServiceDelegate;
import org.apache.fineract.portfolio.loanaccount.service.LoanAccountServiceImpl; import org.apache.fineract.portfolio.loanaccount.service.LoanAccountServiceImpl;
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualActivityProcessingService; import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualActivityProcessingService;
@@ -495,11 +496,10 @@ public class LoanAccountConfiguration {
@Bean @Bean
@ConditionalOnMissingBean(LoanDisbursementService.class) @ConditionalOnMissingBean(LoanDisbursementService.class)
public LoanDisbursementService loanDisbursementService(LoanChargeValidator loanChargeValidator, public LoanDisbursementService loanDisbursementService(LoanChargeValidator loanChargeValidator,
LoanDisbursementValidator loanDisbursementValidator, ReprocessLoanTransactionsService reprocessLoanTransactionsService, LoanDisbursementValidator loanDisbursementValidator, LoanChargeService loanChargeService, LoanBalanceService loanBalanceService,
LoanChargeService loanChargeService, LoanBalanceService loanBalanceService, LoanJournalEntryPoster journalEntryPoster, LoanJournalEntryPoster journalEntryPoster, LoanTransactionRepository loanTransactionRepository) {
LoanTransactionRepository loanTransactionRepository) { return new LoanDisbursementService(loanChargeValidator, loanDisbursementValidator, loanChargeService, loanBalanceService,
return new LoanDisbursementService(loanChargeValidator, loanDisbursementValidator, reprocessLoanTransactionsService, journalEntryPoster, loanTransactionRepository);
loanChargeService, loanBalanceService, journalEntryPoster, loanTransactionRepository);
} }
@Bean @Bean
@@ -515,9 +515,9 @@ public class LoanAccountConfiguration {
public LoanScheduleService loanScheduleService(final LoanChargeService loanChargeService, public LoanScheduleService loanScheduleService(final LoanChargeService loanChargeService,
final ReprocessLoanTransactionsService reprocessLoanTransactionsService, final LoanMapper loanMapper, final ReprocessLoanTransactionsService reprocessLoanTransactionsService, final LoanMapper loanMapper,
final LoanTransactionProcessingService loanTransactionProcessingService, LoanScheduleComponent loanSchedule, final LoanTransactionProcessingService loanTransactionProcessingService, LoanScheduleComponent loanSchedule,
final LoanTransactionRepository loanTransactionRepository) { final LoanTransactionRepository loanTransactionRepository, final ILoanUtilService loanUtilService) {
return new LoanScheduleService(loanChargeService, reprocessLoanTransactionsService, loanMapper, loanTransactionProcessingService, return new LoanScheduleService(loanChargeService, reprocessLoanTransactionsService, loanMapper, loanTransactionProcessingService,
loanSchedule, loanTransactionRepository); loanSchedule, loanTransactionRepository, loanUtilService);
} }
@Bean @Bean
@@ -544,9 +544,9 @@ public class LoanAccountConfiguration {
@ConditionalOnMissingBean(InterestPauseWritePlatformService.class) @ConditionalOnMissingBean(InterestPauseWritePlatformService.class)
public InterestPauseWritePlatformService interestPauseWritePlatformService(LoanTermVariationsRepository loanTermVariationsRepository, public InterestPauseWritePlatformService interestPauseWritePlatformService(LoanTermVariationsRepository loanTermVariationsRepository,
LoanRepositoryWrapper loanRepositoryWrapper, LoanAssembler loanAssembler, LoanRepositoryWrapper loanRepositoryWrapper, LoanAssembler loanAssembler,
ReprocessLoanTransactionsService reprocessLoanTransactionsService, BusinessEventNotifierService businessEventNotifierService) { BusinessEventNotifierService businessEventNotifierService, LoanScheduleService loanScheduleService) {
return new InterestPauseWritePlatformServiceImpl(loanTermVariationsRepository, loanRepositoryWrapper, loanAssembler, return new InterestPauseWritePlatformServiceImpl(loanTermVariationsRepository, loanRepositoryWrapper, loanAssembler,
reprocessLoanTransactionsService, businessEventNotifierService); businessEventNotifierService, loanScheduleService);
} }
@Bean @Bean
@@ -165,6 +165,9 @@ class LoanChargeWritePlatformServiceImplTest {
@Mock @Mock
private LoanJournalEntryPoster journalEntryPoster; private LoanJournalEntryPoster journalEntryPoster;
@Mock
private LoanScheduleService loanScheduleService;
@BeforeEach @BeforeEach
void setUp() { void setUp() {
when(loanAssembler.assembleFrom(LOAN_ID)).thenReturn(loan); when(loanAssembler.assembleFrom(LOAN_ID)).thenReturn(loan);