diff --git a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/MonetaryCurrency.java b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/MonetaryCurrency.java index 334bcf714..2b56ae718 100644 --- a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/MonetaryCurrency.java +++ b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/MonetaryCurrency.java @@ -20,6 +20,7 @@ package org.apache.fineract.organisation.monetary.domain; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; +import lombok.AccessLevel; import lombok.Getter; import org.apache.fineract.organisation.monetary.data.CurrencyData; @@ -36,6 +37,7 @@ public class MonetaryCurrency { @Column(name = "currency_multiplesof") private Integer inMultiplesOf; + @Getter(AccessLevel.PRIVATE) private transient CurrencyData currencyData; protected MonetaryCurrency() { diff --git a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/Money.java b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/Money.java index 41c15f412..74bd60985 100644 --- a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/Money.java +++ b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/Money.java @@ -21,6 +21,7 @@ package org.apache.fineract.organisation.monetary.domain; import java.math.BigDecimal; import java.math.MathContext; import java.util.Iterator; +import org.apache.fineract.infrastructure.core.service.MathUtil; import org.apache.fineract.organisation.monetary.data.CurrencyData; public class Money implements Comparable { @@ -45,9 +46,8 @@ public class Money implements Comparable { // round monetary amounts into multiples of say 20/50. if (currency.getInMultiplesOf() != null && currency.getDecimalPlaces() == 0 && currency.getInMultiplesOf() > 0 - && amountScaled.doubleValue() > 0) { - final double existingVal = amountScaled.doubleValue(); - amountScaled = BigDecimal.valueOf(roundToMultiplesOf(existingVal, currency.getInMultiplesOf())); + && MathUtil.isGreaterThanZero(amountScaled)) { + amountScaled = roundToMultiplesOf(amountScaled, currency.getInMultiplesOf()); } this.amount = amountScaled.setScale(currency.getDecimalPlaces(), getMc().getRoundingMode()); } diff --git a/fineract-e2e-tests-runner/src/test/resources/features/Loan.feature b/fineract-e2e-tests-runner/src/test/resources/features/Loan.feature index 466a968f7..b992e3026 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/Loan.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/Loan.feature @@ -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 | | | | 01 February 2025 | | 500.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 | - | 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 | - | 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 | - | 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 | + | 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 | | 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 | | 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 | | 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: - | 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 | + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 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: | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | | 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 | | 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 - Then Loan status will be "CLOSED_OBLIGATIONS_MET" \ No newline at end of file + Then Loan status will be "CLOSED_OBLIGATIONS_MET" diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanReAging.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanReAging.feature index 432cd4c54..1f70001c5 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanReAging.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanReAging.feature @@ -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 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 | - 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: - | frequencyNumber | frequencyType | startDate | numberOfInstallments | - | 1 | MONTHS | 01 May 2024 | 6 | +# 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: +# | frequencyNumber | frequencyType | startDate | numberOfInstallments | +# | 1 | MONTHS | 01 May 2024 | 6 | @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 @@ -5014,6 +5014,8 @@ Feature: LoanReAging | 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 | 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 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 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 | + 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 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 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 | + 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 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 | | 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 | - | 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 | # --- Reversal of re-age transaction --- 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 | | 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 | - | 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 | 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 @@ -5416,12 +5422,12 @@ Feature: LoanReAging | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | | 2 | 29 | 01 March 2024 | 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 | - | 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 | - | 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 | - | 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 | - | 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 | - | 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 | + | 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 | | 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 | | 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.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.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.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: | 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 | @@ -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 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 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 @TestRailId:C4154 @AdvancedPaymentAllocation @@ -5508,7 +5514,7 @@ Feature: LoanReAging | frequencyNumber | frequencyType | startDate | numberOfInstallments | | 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 When Admin sets the business date to "01 January 2024" When Admin creates a client with random data diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanReschedule.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanReschedule.feature index 2587c0638..6e56f71f0 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanReschedule.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanReschedule.feature @@ -1132,5 +1132,3 @@ Feature: LoanReschedule | 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 | Then LoanRescheduledDueAdjustScheduleBusinessEvent is raised on "24 July 2025" - - diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/InterestPauseWritePlatformServiceImpl.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/InterestPauseWritePlatformServiceImpl.java index f61ec29be..4214f0ffe 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/InterestPauseWritePlatformServiceImpl.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/InterestPauseWritePlatformServiceImpl.java @@ -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.rescheduleloan.domain.LoanTermVariationsRepository; 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; @AllArgsConstructor @@ -56,8 +56,8 @@ public class InterestPauseWritePlatformServiceImpl implements InterestPauseWrite private final LoanTermVariationsRepository loanTermVariationsRepository; private final LoanRepositoryWrapper loanRepositoryWrapper; private final LoanAssembler loanAssembler; - private final ReprocessLoanTransactionsService reprocessLoanTransactionsService; private final BusinessEventNotifierService businessEventNotifierService; + private final LoanScheduleService loanScheduleService; @Override public CommandProcessingResult createInterestPause(final ExternalId loanExternalId, final String startDateString, @@ -114,7 +114,7 @@ public class InterestPauseWritePlatformServiceImpl implements InterestPauseWrite loanTermVariationsRepository.delete(variation); loan.getLoanTermVariations().remove(variation); - reprocessLoanTransactionsService.reprocessTransactions(loan); + loanScheduleService.regenerateScheduleWithReprocessingTransactions(loan); businessEventNotifierService.notifyPostBusinessEvent(new LoanScheduleVariationsDeletedBusinessEvent(loan)); businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan)); @@ -141,7 +141,7 @@ public class InterestPauseWritePlatformServiceImpl implements InterestPauseWrite LoanTermVariations updatedVariation = loanTermVariationsRepository.save(variation); - reprocessLoanTransactionsService.reprocessTransactions(loan); + loanScheduleService.regenerateScheduleWithReprocessingTransactions(loan); businessEventNotifierService.notifyPostBusinessEvent(new LoanScheduleVariationsAddedBusinessEvent(loan)); businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan)); @@ -161,7 +161,7 @@ public class InterestPauseWritePlatformServiceImpl implements InterestPauseWrite final LoanTermVariations savedVariation = loanTermVariationsRepository.saveAndFlush(variation); loan.getLoanTermVariations().add(savedVariation); - reprocessLoanTransactionsService.reprocessTransactions(loan); + loanScheduleService.regenerateScheduleWithReprocessingTransactions(loan); businessEventNotifierService.notifyPostBusinessEvent(new LoanScheduleVariationsAddedBusinessEvent(loan)); businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan)); diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ILoanUtilService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ILoanUtilService.java new file mode 100644 index 000000000..976f7e7d3 --- /dev/null +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ILoanUtilService.java @@ -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); +} diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanBalanceService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanBalanceService.java index a93d21b69..9396ab1ee 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanBalanceService.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanBalanceService.java @@ -94,6 +94,7 @@ public class LoanBalanceService { public void updateLoanSummaryDerivedFields(final Loan loan) { flushModeHandler.withFlushMode(FlushModeType.COMMIT, () -> { + loan.updateLoanScheduleDependentDerivedFields(); if (loan.isNotDisbursed()) { if (loan.getSummary() != null) { loan.getSummary().zeroFields(); diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeService.java index faaf18c0d..e778fdb70 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeService.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeService.java @@ -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.domain.ExternalId; 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.portfolio.charge.domain.Charge; import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType; @@ -901,4 +902,51 @@ public class LoanChargeService { 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 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 loanChargePaidBys, LoanCharge loanCharge) { + return loanChargePaidBys.stream() // + .anyMatch(loanChargePaidBy -> loanChargePaidBy.getLoanCharge().equals(loanCharge)); + } + } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImpl.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImpl.java index 40698ceab..858a27a41 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImpl.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImpl.java @@ -156,9 +156,7 @@ public class LoanDownPaymentHandlerServiceImpl implements LoanDownPaymentHandler if (!processLatest || reprocessOnPostConditions) { if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) { loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO); - } else if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy()) - || loan.hasContractTerminationTransaction() - || (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) { + } else if (loan.isProgressiveSchedule()) { loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); } reprocessLoanTransactionsService.reprocessTransactions(loan); diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanScheduleService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanScheduleService.java index ce488c26e..55dba6659 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanScheduleService.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanScheduleService.java @@ -42,6 +42,7 @@ public class LoanScheduleService { private final LoanTransactionProcessingService loanTransactionProcessingService; private final LoanScheduleComponent loanSchedule; private final LoanTransactionRepository loanTransactionRepository; + private final ILoanUtilService loanUtilService; /** * 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()) { regenerateRepaymentScheduleWithInterestRecalculation(loan, generatorDTO); } else { @@ -123,11 +124,20 @@ public class LoanScheduleService { } } } - loanTransactionProcessingService.processPostDisbursementTransactions(loan); } public void handleRegenerateRepaymentScheduleWithInterestRecalculation(final Loan loan, final ScheduleGeneratorDTO generatorDTO) { regenerateRepaymentScheduleWithInterestRecalculation(loan, generatorDTO); 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); + } } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanTransactionProcessingService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanTransactionProcessingService.java index 272f7fd3a..a57d20cca 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanTransactionProcessingService.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanTransactionProcessingService.java @@ -20,7 +20,6 @@ package org.apache.fineract.portfolio.loanaccount.service; import java.time.LocalDate; import java.util.List; -import java.util.Optional; import java.util.Set; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.portfolio.loanaccount.data.OutstandingAmountsDTO; @@ -48,8 +47,6 @@ public interface LoanTransactionProcessingService { LoanRepaymentScheduleTransactionProcessor getTransactionProcessor(String transactionProcessingStrategyCode); - Optional processPostDisbursementTransactions(Loan loan); - LoanScheduleDTO getRecalculatedSchedule(ScheduleGeneratorDTO generatorDTO, Loan loan); OutstandingAmountsDTO fetchPrepaymentDetail(ScheduleGeneratorDTO scheduleGeneratorDTO, LocalDate onDate, Loan loan); diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsService.java index 2fd1c9b84..5db714ebd 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsService.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsService.java @@ -18,25 +18,17 @@ */ package org.apache.fineract.portfolio.loanaccount.service; -import java.time.LocalDate; import java.util.List; import org.apache.fineract.portfolio.loanaccount.domain.Loan; -import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; public interface ReprocessLoanTransactionsService { void reprocessTransactions(Loan loan); - void reprocessParticularTransactions(Loan loan, List loanTransactions); + void reprocessTransactions(Loan loan, List loanTransactions); - void reprocessTransactionsWithPostTransactionChecks(Loan loan, LocalDate transactionDate); - - void reprocessTransactionsWithoutChecks(Loan loan, LocalDate transactionDate, List newTransactions); - - void processPostDisbursementTransactions(Loan loan); - - void removeLoanCharge(Loan loan, LoanCharge loanCharge); + void reprocessTransactionsWithoutChecks(Loan loan, List newTransactions); void processLatestTransaction(LoanTransaction loanTransaction, Loan loan); diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/ProgressivePossibleNextRepaymentCalculationServiceImpl.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/ProgressivePossibleNextRepaymentCalculationServiceImpl.java index 577f782f8..7b052e62a 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/ProgressivePossibleNextRepaymentCalculationServiceImpl.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/ProgressivePossibleNextRepaymentCalculationServiceImpl.java @@ -51,11 +51,10 @@ public class ProgressivePossibleNextRepaymentCalculationServiceImpl extends Abst Optional progressiveLoanModel = interestScheduleModelRepository.findOneByLoan(loan); Optional optionalScheduleModel = interestScheduleModelRepository .extractModel(progressiveLoanModel); - ProgressiveLoanInterestScheduleModel scheduleModel = optionalScheduleModel.orElseGet( - () -> advancedPaymentScheduleTransactionProcessor.calculateInterestScheduleModel(loan.getId(), nextPaymentDueDate)); - if (scheduleModel == null) { + if (optionalScheduleModel.isEmpty()) { return BigDecimal.ZERO; } + ProgressiveLoanInterestScheduleModel scheduleModel = optionalScheduleModel.get(); List repaymentScheduleInstallments = loan.getRepaymentScheduleInstallments(); ProgressiveTransactionCtx ctx = new ProgressiveTransactionCtx(loan.getCurrency(), repaymentScheduleInstallments, Set.of(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), new ChangedTransactionDetail(), scheduleModel); diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java index 7615700ca..57221a6a4 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java @@ -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.LoanRepaymentScheduleInstallment; 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.LoanTermVariations; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionComparator; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation; 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.SingleLoanChargeRepaymentScheduleProcessingWrapper; 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.util.LoopContext; import org.apache.fineract.util.LoopGuard; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; @Slf4j 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"; private final EMICalculator emiCalculator; - private final LoanRepositoryWrapper loanRepositoryWrapper; private final InterestRefundService interestRefundService; private final LoanScheduleComponent loanSchedule; - private final LoanTransactionRepository loanTransactionRepository; private final LoanChargeService loanChargeService; private final SingleLoanChargeRepaymentScheduleProcessingWrapper loanChargeRepaymentScheduleProcessing; - public AdvancedPaymentScheduleTransactionProcessor(final EMICalculator emiCalculator, final LoanRepositoryWrapper loanRepositoryWrapper, - final InterestRefundService interestRefundService, final ExternalIdFactory externalIdFactory, - final LoanScheduleComponent loanSchedule, final LoanTransactionRepository loanTransactionRepository, + public AdvancedPaymentScheduleTransactionProcessor(final EMICalculator emiCalculator, final InterestRefundService interestRefundService, + final ExternalIdFactory externalIdFactory, final LoanScheduleComponent loanSchedule, final LoanChargeValidator loanChargeValidator, final LoanBalanceService loanBalanceService, final LoanChargeService loanChargeService) { super(externalIdFactory, loanChargeValidator, loanBalanceService); this.emiCalculator = emiCalculator; - this.loanRepositoryWrapper = loanRepositoryWrapper; this.interestRefundService = interestRefundService; - this.loanTransactionRepository = loanTransactionRepository; this.loanSchedule = loanSchedule; this.loanChargeService = loanChargeService; this.loanChargeRepaymentScheduleProcessing = new SingleLoanChargeRepaymentScheduleProcessingWrapper(); @@ -195,13 +186,6 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep throw new NotImplementedException(); } - @Transactional - public Pair reprocessProgressiveLoanTransactionsTransactional( - final LocalDate disbursementDate, final LocalDate targetDate, final List loanTransactions, - final MonetaryCurrency currency, final List installments, final Set charges) { - return reprocessProgressiveLoanTransactions(disbursementDate, targetDate, loanTransactions, currency, installments, charges); - } - // only for progressive loans public Pair reprocessProgressiveLoanTransactions( LocalDate disbursementDate, LocalDate targetDate, List loanTransactions, MonetaryCurrency currency, @@ -302,18 +286,6 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep 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 transactions = loanTransactionRepository.findNonReversedTransactionsForReprocessingByLoan(loan); - MonetaryCurrency currency = loan.getLoanRepaymentScheduleDetail().getCurrency(); - List installments = loan.getRepaymentScheduleInstallments(); - Set charges = loan.getActiveCharges(); - return reprocessProgressiveLoanTransactions(loan.getDisbursementDate(), targetDate, transactions, currency, installments, charges) - .getRight(); - } - @NotNull private static LoanTransaction getProcessedTransaction(final ChangedTransactionDetail changedTransactionDetail, final LoanTransaction transaction) { @@ -2937,9 +2909,8 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep private void updateInstallmentsByModelForReAging(final LoanTransaction loanTransaction, final ProgressiveTransactionCtx ctx) { ctx.getModel().repaymentPeriods().forEach(rp -> { - final LoanRepaymentScheduleInstallment installment = ctx.getInstallments().stream().filter( - ri -> ri.getFromDate().equals(rp.getFromDate()) && ri.getDueDate().equals(rp.getDueDate()) && !ri.isDownPayment()) - .findFirst().orElse(null); + final LoanRepaymentScheduleInstallment installment = ctx.getInstallments().stream() + .filter(ri -> ri.getFromDate().equals(rp.getFromDate()) && !ri.isDownPayment()).findFirst().orElse(null); final LocalDate transactionDate = loanTransaction.getTransactionDate(); if (installment != null) { installment.setFromDate(rp.getFromDate()); @@ -2953,6 +2924,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep installment.updateInterestCharged(rp.getDueInterest().getAmount()); } installment.setReAged(true); + installment.setAdditional(false); installment.updateObligationsMet(ctx.getCurrency(), transactionDate); } else { final LoanRepaymentScheduleInstallment lastInstallment = ctx.getInstallments().getLast(); @@ -3247,7 +3219,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep final BigDecimal interestRate = loan.getLoanRepaymentScheduleDetail().getAnnualNominalInterestRate(); 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) .loanTermFrequency(loanTransaction.getLoanReAgeParameter().getNumberOfInstallments()) .loanTermPeriodFrequencyType(loanTransaction.getLoanReAgeParameter().getFrequencyType()) diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java index d1a710c1f..71c2fa731 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java @@ -202,8 +202,7 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator { Optional savedModel = interestScheduleModelRepositoryWrapper.getSavedModel(loan, transactionDate); - ProgressiveLoanInterestScheduleModel model = savedModel - .orElseGet(() -> processor.calculateInterestScheduleModel(loan.getId(), transactionDate)); + ProgressiveLoanInterestScheduleModel model = savedModel.orElseThrow(); OutstandingDetails outstandingAmounts = emiCalculator.getOutstandingAmountsTillDate(model, transactionDate); // TODO: We should add all the past due outstanding amounts as well OutstandingAmountsDTO result = new OutstandingAmountsDTO(currency) // @@ -245,8 +244,7 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator { return Money.zero(loan.getCurrency()); } Optional savedModel = interestScheduleModelRepositoryWrapper.getSavedModel(loan, targetDate); - ProgressiveLoanInterestScheduleModel model = savedModel - .orElseGet(() -> processor.calculateInterestScheduleModel(loan.getId(), targetDate)); + ProgressiveLoanInterestScheduleModel model = savedModel.orElseThrow(); return emiCalculator.getPeriodInterestTillDate(model, installment.getDueDate(), targetDate, false); } @@ -438,7 +436,8 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator { } else { 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); } else { cumulative = cumulative.plus(loanCharge.amountOrPercentage()); @@ -456,7 +455,8 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator { } else { 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); return cumulative; } diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanConfigurationDetailsMapper.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanConfigurationDetailsMapper.java index 4dc2c1f7b..9e80895dc 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanConfigurationDetailsMapper.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanConfigurationDetailsMapper.java @@ -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; 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.loanproduct.data.LoanConfigurationDetails; 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.RecalculationFrequencyType; public final class LoanConfigurationDetailsMapper { @@ -36,7 +56,22 @@ public final class LoanConfigurationDetailsMapper { loanProductRelatedDetail.getRepaymentPeriodFrequencyType(), loanProductRelatedDetail.getRepayEvery(), loanProductRelatedDetail.getNumberOfRepayments(), loanProductRelatedDetail.isInterestRecognitionOnDisbursementDate(), loanProductRelatedDetail.getDaysInYearCustomStrategy(), loanProductRelatedDetail.isAllowPartialPeriodInterestCalculation(), - loan.isInterestRecalculationEnabled(), loan.getLoanInterestRecalculationDetails().getRestFrequencyType(), - loan.getLoanInterestRecalculationDetails().getPreCloseInterestCalculationStrategy()); + loan.isInterestRecalculationEnabled(), getRestFrequencyType(loan), getPreCloseInterestCalculationStrategy(loan)); + } + + 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; + } } } diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/CapitalizedIncomeWritePlatformServiceImpl.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/CapitalizedIncomeWritePlatformServiceImpl.java index 23fb66ac9..896c54c6d 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/CapitalizedIncomeWritePlatformServiceImpl.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/CapitalizedIncomeWritePlatformServiceImpl.java @@ -21,6 +21,7 @@ package org.apache.fineract.portfolio.loanaccount.service; import java.math.BigDecimal; import java.time.LocalDate; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Optional; import lombok.RequiredArgsConstructor; @@ -69,6 +70,7 @@ public class CapitalizedIncomeWritePlatformServiceImpl implements CapitalizedInc private final LoanLifecycleStateMachine loanLifecycleStateMachine; private final BusinessEventNotifierService businessEventNotifierService; private final CodeValueRepository codeValueRepository; + private final LoanScheduleService loanScheduleService; @Transactional @Override @@ -87,13 +89,13 @@ public class CapitalizedIncomeWritePlatformServiceImpl implements CapitalizedInc final Money capitalizedIncomeAmount = calculateCapitalizedIncomeAmount(loan, transactionAmount); final LoanTransaction capitalizedIncomeTransaction = LoanTransaction.capitalizedIncome(loan, capitalizedIncomeAmount, paymentDetail, transactionDate, txnExternalId); - // Update loan with capitalized income - loan.addLoanTransaction(capitalizedIncomeTransaction); // Add Loan Transaction classification addClassificationCodeToTransaction(command, LoanTransactionApiConstants.CAPITALIZED_INCOME_CLASSIFICATION_CODE, capitalizedIncomeTransaction); // Recalculate loan transactions - recalculateLoanTransactions(loan, transactionDate, capitalizedIncomeTransaction); + recalculateLoanTransactions(loan, capitalizedIncomeTransaction); + // Update loan with capitalized income + loan.addLoanTransaction(capitalizedIncomeTransaction); // Save and flush (PK is set) loanTransactionRepository.saveAndFlush(capitalizedIncomeTransaction); // Create capitalized income balances @@ -144,8 +146,8 @@ public class CapitalizedIncomeWritePlatformServiceImpl implements CapitalizedInc capitalizedIncomeAdjustment.getLoanTransactionRelations().add(LoanTransactionRelation.linkToTransaction(capitalizedIncomeAdjustment, capitalizedIncome.get(), LoanTransactionRelationTypeEnum.ADJUSTMENT)); capitalizedIncomeAdjustment.setClassification(capitalizedIncome.get().getClassification()); + recalculateLoanTransactions(loan, capitalizedIncomeAdjustment); loan.addLoanTransaction(capitalizedIncomeAdjustment); - recalculateLoanTransactions(loan, transactionDate, capitalizedIncomeAdjustment); LoanTransaction savedCapitalizedIncomeAdjustment = loanTransactionRepository.saveAndFlush(capitalizedIncomeAdjustment); // Update outstanding loan balances @@ -182,9 +184,10 @@ public class CapitalizedIncomeWritePlatformServiceImpl implements CapitalizedInc .build(); } - private void recalculateLoanTransactions(Loan loan, LocalDate transactionDate, LoanTransaction transaction) { - if (loan.isInterestRecalculationEnabled() || DateUtils.isBeforeBusinessDate(transactionDate)) { - reprocessLoanTransactionsService.reprocessTransactions(loan); + private void recalculateLoanTransactions(Loan loan, LoanTransaction transaction) { + if (loan.isInterestRecalculationEnabled() || DateUtils.isBeforeBusinessDate(transaction.getTransactionDate())) { + loanScheduleService.regenerateRepaymentSchedule(loan); + reprocessLoanTransactionsService.reprocessTransactions(loan, List.of(transaction)); } else { reprocessLoanTransactionsService.processLatestTransaction(transaction, loan); } diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InterestScheduleModelRepositoryWrapperImpl.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InterestScheduleModelRepositoryWrapperImpl.java index a00f7af0a..c53665638 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InterestScheduleModelRepositoryWrapperImpl.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InterestScheduleModelRepositoryWrapperImpl.java @@ -108,7 +108,7 @@ public class InterestScheduleModelRepositoryWrapperImpl implements InterestSched public Optional getSavedModel(Loan loan, LocalDate businessDate) { Optional progressiveLoanModel = findOneByLoanId(loan.getId()); Optional savedModel; - if (progressiveLoanModel.isPresent() && !progressiveLoanModel.get().getBusinessDate().isAfter(businessDate)) { + if (progressiveLoanModel.isPresent()) { savedModel = extractModel(progressiveLoanModel); if (savedModel.isPresent() && progressiveLoanModel.get().getBusinessDate().isBefore(businessDate)) { ProgressiveTransactionCtx ctx = new ProgressiveTransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InternalProgressiveLoanApiResource.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InternalProgressiveLoanApiResource.java index 46bd49c05..4fca8a38f 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InternalProgressiveLoanApiResource.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InternalProgressiveLoanApiResource.java @@ -28,26 +28,18 @@ import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; -import java.time.LocalDate; -import java.util.List; -import java.util.Objects; import lombok.RequiredArgsConstructor; 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.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.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.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel; import org.apache.fineract.portfolio.loanproduct.domain.ILoanConfigurationDetails; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; @Slf4j @RequiredArgsConstructor @@ -58,9 +50,9 @@ import org.springframework.stereotype.Component; public class InternalProgressiveLoanApiResource implements InitializingBean { private final LoanRepositoryWrapper loanRepository; - private final AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor; private final InterestScheduleModelRepositoryWrapper writePlatformService; - private final LoanTransactionRepository loanTransactionRepository; + private final InterestScheduleModelRepositoryWrapper interestScheduleModelRepositoryWrapper; + private final LoanScheduleService loanScheduleService; @Override @SuppressFBWarnings("SLF4J_SIGN_ONLY_FORMAT") @@ -89,28 +81,15 @@ public class InternalProgressiveLoanApiResource implements InitializingBean { } private ProgressiveLoanInterestScheduleModel reprocessTransactionsAndGetModel(final Loan loan) { - final List transactionsToReprocess = loanTransactionRepository - .findNonReversedTransactionsForReprocessingByLoan(loan); - final LocalDate businessDate = ThreadLocalContextUtil.getBusinessDate(); - final Pair changedTransactionDetailProgressiveLoanInterestScheduleModelPair = advancedPaymentScheduleTransactionProcessor - .reprocessProgressiveLoanTransactionsTransactional(loan.getDisbursementDate(), businessDate, transactionsToReprocess, - loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges()); - final ProgressiveLoanInterestScheduleModel model = changedTransactionDetailProgressiveLoanInterestScheduleModelPair.getRight(); - final List 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; + loanScheduleService.regenerateScheduleWithReprocessingTransactions(loan); + return interestScheduleModelRepositoryWrapper.extractModel(interestScheduleModelRepositoryWrapper.findOneByLoan(loan)).get(); } @POST @Path("{loanId}/model") @Produces({ MediaType.APPLICATION_JSON }) @Operation(summary = "Update and Save ProgressiveLoanInterestScheduleModel", description = "DO NOT USE THIS IN PRODUCTION!") + @Transactional public ProgressiveLoanInterestScheduleModel updateModel(@PathParam("loanId") @Parameter(description = "loanId") long loanId) { Loan loan = loanRepository.findOneWithNotFoundDetection(loanId); if (!loan.isProgressiveSchedule()) { diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/ProgressiveLoanAccountConfiguration.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/ProgressiveLoanAccountConfiguration.java index eb70e364e..6b7a543b0 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/ProgressiveLoanAccountConfiguration.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/ProgressiveLoanAccountConfiguration.java @@ -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.LoanJournalEntryPoster; 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.ProgressiveLoanTransactionValidatorImpl; import org.apache.fineract.portfolio.loanaccount.service.ReprocessLoanTransactionsService; @@ -59,11 +60,11 @@ public class ProgressiveLoanAccountConfiguration { LoanCapitalizedIncomeBalanceRepository capitalizedIncomeBalanceRepository, ReprocessLoanTransactionsService reprocessLoanTransactionsService, LoanBalanceService loanBalanceService, LoanLifecycleStateMachine loanLifecycleStateMachine, BusinessEventNotifierService businessEventNotifierService, - CodeValueRepository codeValueRepository) { + CodeValueRepository codeValueRepository, LoanScheduleService loanScheduleService) { return new CapitalizedIncomeWritePlatformServiceImpl(loanTransactionValidator, loanAssembler, loanTransactionRepository, paymentDetailWritePlatformService, journalEntryPoster, noteWritePlatformService, externalIdFactory, capitalizedIncomeBalanceRepository, reprocessLoanTransactionsService, loanBalanceService, loanLifecycleStateMachine, - businessEventNotifierService, codeValueRepository); + businessEventNotifierService, codeValueRepository, loanScheduleService); } @Bean diff --git a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java index f64aea715..d03c7965f 100644 --- a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java +++ b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java @@ -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.LoanPaymentAllocationRule; 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.LoanTransactionRelation; 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.transactionprocessor.MoneyHolder; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx; @@ -120,9 +118,8 @@ class AdvancedPaymentScheduleTransactionProcessorTest { @BeforeEach public void setUp() { - underTest = new AdvancedPaymentScheduleTransactionProcessor(emiCalculator, Mockito.mock(LoanRepositoryWrapper.class), - Mockito.mock(InterestRefundService.class), Mockito.mock(ExternalIdFactory.class), Mockito.mock(LoanScheduleComponent.class), - Mockito.mock(LoanTransactionRepository.class), Mockito.mock(LoanChargeValidator.class), + underTest = new AdvancedPaymentScheduleTransactionProcessor(emiCalculator, Mockito.mock(InterestRefundService.class), + Mockito.mock(ExternalIdFactory.class), Mockito.mock(LoanScheduleComponent.class), Mockito.mock(LoanChargeValidator.class), Mockito.mock(LoanBalanceService.class), Mockito.mock(LoanChargeService.class)); ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null)); diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/api/InternalCOBApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/cob/api/InternalCOBApiResource.java index 3a3362b21..8c690ba68 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/api/InternalCOBApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/api/InternalCOBApiResource.java @@ -47,7 +47,7 @@ import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.portfolio.loanaccount.domain.Loan; 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.context.annotation.Profile; import org.springframework.stereotype.Component; @@ -67,7 +67,7 @@ public class InternalCOBApiResource implements InitializingBean { private final ApiRequestParameterHelper apiRequestParameterHelper; private final ToApiJsonSerializer toApiJsonSerializerForList; private final LoanRepositoryWrapper loanRepositoryWrapper; - private final ReprocessLoanTransactionsService reprocessLoanTransactionsService; + private final LoanScheduleService loanScheduleService; protected DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DATETIME_PATTERN); @@ -113,7 +113,7 @@ public class InternalCOBApiResource implements InitializingBean { @Path("loan-reprocess/{loanId}") @Transactional public void loanReprocess(@Context final UriInfo uriInfo, @PathParam("loanId") long loanId) { - reprocessLoanTransactionsService.reprocessTransactions(loanRepositoryWrapper.findOneWithNotFoundDetection(loanId)); + loanScheduleService.regenerateScheduleWithReprocessingTransactions(loanRepositoryWrapper.findOneWithNotFoundDetection(loanId)); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java index 3af967027..5b55b3f17 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java @@ -859,9 +859,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { } else { if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) { loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO); - } else if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy()) - || loan.hasContractTerminationTransaction() - || (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) { + } else if (loan.isProgressiveSchedule()) { loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); } loan.getLoanTransactions().add(refundTransaction); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java index 6f3ff467d..f0198b360 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java @@ -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.LoanTransactionRepository; 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.exception.InstallmentNotFoundException; import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanTransactionTypeException; @@ -277,20 +274,12 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo } if (reprocessRequired) { - if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy()) - || loan.hasContractTerminationTransaction() - || (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) { + if (loan.isProgressiveSchedule()) { final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null); loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); } - - if (overpaidReprocess) { - reprocessLoanTransactionsService.reprocessTransactionsWithPostTransactionChecks(loan, transactionDate); - } else { - reprocessLoanTransactionsService.reprocessTransactions(loan); - } + reprocessLoanTransactionsService.reprocessTransactions(loan); loanLifecycleStateMachine.determineAndTransition(loan, transactionDate); - loan = loanAccountService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan); } if (loan.isInterestBearingAndInterestRecalculationEnabled() && isAppliedOnBackDate @@ -600,7 +589,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo loanChargeValidator.validateLoanIsNotClosed(loan, loanCharge); loanChargeValidator.validateLoanChargeIsNotWaived(loan, loanCharge); - reprocessLoanTransactionsService.removeLoanCharge(loan, loanCharge); + loanChargeService.removeLoanCharge(loan, loanCharge); this.loanRepositoryWrapper.save(loan); businessEventNotifierService.notifyPostBusinessEvent(new LoanDeleteChargeBusinessEvent(loanCharge)); return new CommandProcessingResultBuilder() // @@ -857,9 +846,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo if (reprocessRequired) { addInstallmentIfPenaltyAppliedAfterLastDueDate(loan, lastChargeDate); - if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy()) - || loan.hasContractTerminationTransaction() - || (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) { + if (loan.isProgressiveSchedule()) { final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null); loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); } @@ -885,22 +872,16 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo LoanTransactionRelationTypeEnum.CHARGE_ADJUSTMENT); loanChargeAdjustmentTransaction.getLoanTransactionRelations().add(loanTransactionRelation); - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanRepaymentScheduleTransactionProcessorFactory - .determineProcessor(loan.transactionProcessingStrategy()); - loan.addLoanTransaction(loanChargeAdjustmentTransaction); if (loan.isInterestBearingAndInterestRecalculationEnabled()) { - if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy()) - || loan.hasContractTerminationTransaction() - || (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) { + if (loan.isProgressiveSchedule()) { final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null); loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); } - reprocessLoanTransactionsService.reprocessTransactions(loan); + reprocessLoanTransactionsService.reprocessTransactions(loan, List.of(loanChargeAdjustmentTransaction)); } else { - loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanChargeAdjustmentTransaction, - new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), - new MoneyHolder(loan.getTotalOverpaidAsMoney()), null)); + reprocessLoanTransactionsService.processLatestTransaction(loanChargeAdjustmentTransaction, loan); } + loan.addLoanTransaction(loanChargeAdjustmentTransaction); loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(loanChargeAdjustmentTransaction); loanLifecycleStateMachine.determineAndTransition(loan, loan.getLastUserTransactionDate()); @@ -1444,12 +1425,12 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(waiveLoanChargeTransaction, loanCharge, waiveLoanChargeTransaction.getAmount(loan.getCurrency()).getAmount(), loanInstallmentNumber); waiveLoanChargeTransaction.getLoanChargesPaid().add(loanChargePaidBy); + loan.addLoanTransaction(waiveLoanChargeTransaction); if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled() && DateUtils.isBefore(loanCharge.getDueLocalDate(), businessDate)) { loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO); - } else if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy()) - || loan.hasContractTerminationTransaction() || (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) { + } else if (loan.isProgressiveSchedule()) { loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); } // Waive of charges whose due date falls after latest 'repayment' transaction don't require entire loan schedule diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDisbursementService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDisbursementService.java index 2b19b1352..aa6cd0451 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDisbursementService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDisbursementService.java @@ -66,7 +66,6 @@ public class LoanDisbursementService { private final LoanChargeValidator loanChargeValidator; private final LoanDisbursementValidator loanDisbursementValidator; - private final ReprocessLoanTransactionsService reprocessLoanTransactionsService; private final LoanChargeService loanChargeService; private final LoanBalanceService loanBalanceService; private final LoanJournalEntryPoster loanJournalEntryPoster; @@ -326,7 +325,7 @@ public class LoanDisbursementService { for (LoanCharge loanCharge : tempCharges) { loanChargeValidator.validateLoanIsNotClosed(loan, loanCharge); loanChargeValidator.validateLoanChargeIsNotWaived(loan, loanCharge); - reprocessLoanTransactionsService.removeLoanCharge(loan, loanCharge); + loanChargeService.removeLoanCharge(loan, loanCharge); } loan.getTrancheCharges().clear(); } else { @@ -336,7 +335,7 @@ public class LoanDisbursementService { if (loan.getCharges().contains(deleteCharge)) { loanChargeValidator.validateLoanIsNotClosed(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); // 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 diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanPointInTimeServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanPointInTimeServiceImpl.java index da5c8db76..b0a3e91c0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanPointInTimeServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanPointInTimeServiceImpl.java @@ -81,7 +81,7 @@ public class LoanPointInTimeServiceImpl implements LoanPointInTimeService { // the loan's state if (txCount != afterRemovalTxCount || chargeCount != afterRemovalChargeCount) { ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null, null); - loanScheduleService.recalculateSchedule(loan, scheduleGeneratorDTO); + loanScheduleService.regenerateScheduleWithReprocessingTransactions(loan, scheduleGeneratorDTO); } LoanArrearsData arrearsData = arrearsAgingService.calculateArrearsForLoan(loan); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanTransactionProcessingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanTransactionProcessingServiceImpl.java index b97d15e88..a23fa37ac 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanTransactionProcessingServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanTransactionProcessingServiceImpl.java @@ -21,7 +21,6 @@ package org.apache.fineract.portfolio.loanaccount.service; import jakarta.persistence.FlushModeType; import java.math.MathContext; import java.time.LocalDate; -import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.Set; @@ -65,7 +64,6 @@ public class LoanTransactionProcessingServiceImpl implements LoanTransactionProc private final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessorFactory; private final LoanTermVariationsMapper loanMapper; private final InterestScheduleModelRepositoryWrapper modelRepository; - private final LoanBalanceService loanBalanceService; private final LoanTransactionService loanTransactionService; @Override @@ -131,32 +129,6 @@ public class LoanTransactionProcessingServiceImpl implements LoanTransactionProc return transactionProcessorFactory.determineProcessor(transactionProcessingStrategyCode); } - @Override - public Optional processPostDisbursementTransactions(Loan loan) { - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor( - loan.getTransactionProcessingStrategyCode()); - final List allNonContraTransactionsPostDisbursement = loanTransactionService - .retrieveListOfTransactionsForReprocessing(loan); - - final List 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 public LoanScheduleDTO getRecalculatedSchedule(final ScheduleGeneratorDTO generatorDTO, Loan loan) { if (!loan.isInterestBearingAndInterestRecalculationEnabled() || loan.isNpa() || loan.isChargedOff()) { @@ -232,9 +204,10 @@ public class LoanTransactionProcessingServiceImpl implements LoanTransactionProc AdvancedPaymentScheduleTransactionProcessor advancedProcessor, Loan loan, LoanTransaction loanTransaction) { Optional savedModel = modelRepository.getSavedModel(loan, loanTransaction.getTransactionDate()); - ProgressiveLoanInterestScheduleModel model = savedModel - .orElseGet(() -> advancedProcessor.calculateInterestScheduleModel(loan.getId(), loanTransaction.getTransactionDate())); - + if (savedModel.isEmpty()) { + throw new IllegalArgumentException("No saved model found for loan transaction " + loanTransaction); + } + ProgressiveLoanInterestScheduleModel model = savedModel.get(); ProgressiveTransactionCtx progressiveContext = new ProgressiveTransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), new ChangedTransactionDetail(), model, getTotalRefundInterestAmount(loan)); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanUtilService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanUtilService.java index d924132b4..0ca766f0a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanUtilService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanUtilService.java @@ -21,11 +21,9 @@ package org.apache.fineract.portfolio.loanaccount.service; import java.math.BigDecimal; import java.time.LocalDate; import java.util.List; -import java.util.Map; import java.util.Set; import lombok.RequiredArgsConstructor; 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.organisation.holiday.domain.Holiday; 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.group.domain.Group; 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.ScheduleGeneratorDTO; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail; -import org.apache.fineract.portfolio.note.domain.Note; import org.apache.fineract.portfolio.note.domain.NoteRepository; @RequiredArgsConstructor -public class LoanUtilService { +public class LoanUtilService implements ILoanUtilService { private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository; private final CalendarInstanceRepository calendarInstanceRepository; @@ -74,17 +70,20 @@ public class LoanUtilService { private final CalendarReadPlatformService calendarReadPlatformService; private final NoteRepository noteRepository; + @Override public ScheduleGeneratorDTO buildScheduleGeneratorDTO(final Loan loan, final LocalDate recalculateFrom) { final HolidayDetailDTO holidayDetailDTO = null; return buildScheduleGeneratorDTO(loan, recalculateFrom, null, holidayDetailDTO); } + @Override public ScheduleGeneratorDTO buildScheduleGeneratorDTO(final Loan loan, final LocalDate recalculateFrom, final LocalDate rescheduleTill) { final HolidayDetailDTO holidayDetailDTO = null; return buildScheduleGeneratorDTO(loan, recalculateFrom, rescheduleTill, holidayDetailDTO); } + @Override public ScheduleGeneratorDTO buildScheduleGeneratorDTO(final Loan loan, final LocalDate recalculateFrom, final LocalDate recalculateTill, final HolidayDetailDTO holidayDetailDTO) { HolidayDetailDTO holidayDetails = holidayDetailDTO; @@ -147,6 +146,7 @@ public class LoanUtilService { return scheduleGeneratorDTO; } + @Override public Boolean isLoanRepaymentsSyncWithMeeting(final Group group, final Calendar calendar) { Boolean isSkipRepaymentOnFirstMonth = false; Long entityId = null; @@ -170,6 +170,7 @@ public class LoanUtilService { return isSkipRepaymentOnFirstMonth; } + @Override public LocalDate getCalculatedRepaymentsStartingFromDate(final Loan loan) { final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstanceByEntityId(loan.getId(), CalendarEntityType.LOANS.getValue()); @@ -190,6 +191,7 @@ public class LoanUtilService { return holidayDetailDTO; } + @Override public HolidayDetailDTO constructHolidayDTO(final Long officeId, LocalDate localDate) { final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); final List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(officeId, localDate, @@ -225,16 +227,6 @@ public class LoanUtilService { 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, final CalendarHistoryDataWrapper calendarHistoryDataWrapper) { LocalDate calculatedRepaymentsStartingFromDate = loan.getExpectedFirstRepaymentOnDate(); @@ -305,6 +297,7 @@ public class LoanUtilService { return calculatedRepaymentsStartingFromDate; } + @Override public void validateRepaymentTransactionType(LoanTransactionType repaymentTransactionType) { if (!repaymentTransactionType.isRepaymentType()) { 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) { final Client client = loan.client(); if (client != null && client.isNotActive()) { @@ -324,14 +318,4 @@ public class LoanUtilService { } } - public void persistNote(Loan loan, JsonCommand command, Map 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); - } - } - } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 41c8135c7..444f089b4 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -952,10 +952,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final boolean isTransactionChronologicallyLatest = loanTransactionService.isChronologicallyLatestRepaymentOrWaiver(loan, newInterestPaymentWaiverTransaction); - if (newInterestPaymentWaiverTransaction.isNotZero()) { - loan.addLoanTransaction(newInterestPaymentWaiverTransaction); - } - final LoanRepaymentScheduleInstallment currentInstallment = loan.fetchLoanRepaymentScheduleInstallmentByDueDate(transactionDate); boolean reprocess = true; @@ -970,6 +966,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), newInterestPaymentWaiverTransaction, new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), null)); + reprocess = false; if (loan.isInterestBearingAndInterestRecalculationEnabled()) { if (currentInstallment == null || currentInstallment.isNotFullyPaidOff()) { @@ -982,22 +979,25 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } } } + if (!reprocess) { + loan.addLoanTransaction(newInterestPaymentWaiverTransaction); + } } if (reprocess) { - reprocessChangedLoanTransactions(loan, scheduleGeneratorDTO); + reprocessChangedLoanTransactions(loan, newInterestPaymentWaiverTransaction, scheduleGeneratorDTO); } 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()) { loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO); - } else if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy()) - || loan.hasContractTerminationTransaction() || (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) { + } else if (loan.isProgressiveSchedule()) { loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); } - + loan.addLoanTransaction(newInterestPaymentWaiverTransaction); reprocessLoanTransactionsService.reprocessTransactions(loan); if (loan.isInterestBearingAndInterestRecalculationEnabled()) { @@ -1304,13 +1304,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf validateLoanTransactionAmountChargeBack(loanTransaction, newTransaction); - if (loan.isInterestBearing() && loan.isInterestRecalculationEnabled()) { - if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy()) - || loan.hasContractTerminationTransaction() - || (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) { - final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null); - loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); - } + if (loan.isInterestBearing() && loan.isInterestRecalculationEnabled() && loan.isProgressiveSchedule()) { + final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null); + loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); } // Store the Loan Transaction Relation @@ -2285,11 +2281,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf loan.getLoanProductRelatedDetail().setPrincipal(loan.getPrincipalAmountForRepaymentSchedule()); if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) { loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO); - } else if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy()) - || loan.hasContractTerminationTransaction() - || (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) { + } else if (loan.isProgressiveSchedule()) { loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); - reprocessLoanTransactionsService.processPostDisbursementTransactions(loan); + reprocessLoanTransactionsService.reprocessTransactions(loan); } } @@ -2441,9 +2435,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf || rescheduledRepaymentDate != null) { if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) { loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO); - } else if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy()) - || loan.hasContractTerminationTransaction() - || (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) { + } else if (loan.isProgressiveSchedule()) { loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); } } @@ -2757,9 +2749,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, null, null); loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO); } - final List loanTransactions = loanTransactionRepository.findNonReversedTransactionsForReprocessingByLoan(loan); - loanTransactions.add(chargeOffTransaction); - reprocessLoanTransactionsService.reprocessParticularTransactions(loan, loanTransactions); + reprocessLoanTransactionsService.reprocessTransactions(loan, List.of(chargeOffTransaction)); loan.addLoanTransaction(chargeOffTransaction); } else { reprocessLoanTransactionsService.processLatestTransaction(chargeOffTransaction, loan); @@ -3010,9 +3000,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } else { if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) { loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO); - } else if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy()) - || loan.hasContractTerminationTransaction() - || (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) { + } else if (loan.isProgressiveSchedule()) { loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); } loan.addLoanTransaction(interestRefundTxn); @@ -3059,13 +3047,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf public void handleChargebackTransaction(final Loan loan, LoanTransaction chargebackTransaction) { loanTransactionValidator.validateIfTransactionIsChargeback(chargebackTransaction); - loan.addLoanTransaction(chargebackTransaction); if (loan.isInterestBearing() && loan.isInterestRecalculationEnabled()) { - final List transactions = loanTransactionService.retrieveListOfTransactionsForReprocessing(loan); - loanTransactionProcessingService.reprocessLoanTransactions(loan.getTransactionProcessingStrategyCode(), - loan.getDisbursementDate(), transactions, loan.getCurrency(), loan.getRepaymentScheduleInstallments(), - loan.getActiveCharges()); + loan.addLoanTransaction(chargebackTransaction); + reprocessLoanTransactionsService.reprocessTransactions(loan); } else { + loan.addLoanTransaction(chargebackTransaction); loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), chargebackTransaction, new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), null)); @@ -3252,16 +3238,24 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final LoanTransaction loanTransaction = LoanTransaction.writeoff(loan, loan.getOffice(), writtenOffOnLocalDate, externalId); LocalDate lastTransactionDate = loan.getLastUserTransactionDate(); 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, writtenOffOnLocalDate); } - loan.addLoanTransaction(loanTransaction); - loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), loanTransaction, - new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), - new MoneyHolder(loan.getTotalOverpaidAsMoney()), null)); - + if (loan.isInterestBearingAndInterestRecalculationEnabled() + && DateUtils.isBeforeBusinessDate(loanTransaction.getTransactionDate())) { + if (loan.isProgressiveSchedule()) { + 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); loanLifecycleStateMachine.transition(LoanEvent.WRITE_OFF_OUTSTANDING, loan); changes.put(PARAM_STATUS, LoanEnumerations.status(loan.getLoanStatus())); @@ -3272,17 +3266,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf if (loan.isDisbursementAllowed() && loan.atLeastOnceDisbursed()) { loan.getLoanRepaymentScheduleDetail().setPrincipal(loan.getDisbursedAmount()); loan.removeDisbursementDetail(); - loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); - if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) { - loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO); - } else if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy()) - || loan.hasContractTerminationTransaction() - || (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) { - loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); + if (loan.isCumulativeSchedule()) { + if (loan.isInterestBearingAndInterestRecalculationEnabled()) { + loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO); + } else { + 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()) { loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO); - } else if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy()) - || loan.hasContractTerminationTransaction() || (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) { + } else if (loan.isProgressiveSchedule()) { loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); } @@ -3422,7 +3411,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf && DateUtils.isEqual(loanTermVariations.getTermApplicableFrom(), lastTransactionDate)) || DateUtils.isAfter(loanTermVariations.getTermApplicableFrom(), lastTransactionDate)); reverseExistingTransactionsTillLastDisbursal(loan, lastDisbursalTransaction); - loanScheduleService.recalculateSchedule(loan, scheduleGeneratorDTO); + loanScheduleService.regenerateScheduleWithReprocessingTransactions(loan, scheduleGeneratorDTO); actualChanges.put("undolastdisbursal", "true"); actualChanges.put("disbursedAmount", loan.getDisbursedAmount()); loanLifecycleStateMachine.determineAndTransition(loan, loan.getLastUserTransactionDate()); @@ -3442,8 +3431,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf "reversed"); writeOffTransaction.reverse(); loanLifecycleStateMachine.transition(LoanEvent.WRITE_OFF_OUTSTANDING_UNDO, loan); - if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy()) - || loan.hasContractTerminationTransaction() || (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) { + if (loan.isProgressiveSchedule()) { final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null); loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanInterestRefundServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanInterestRefundServiceImpl.java index 584b747f5..c0f7efcb7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanInterestRefundServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanInterestRefundServiceImpl.java @@ -81,11 +81,9 @@ public class ProgressiveLoanInterestRefundServiceImpl implements InterestRefundS LocalDate relatedRefundTransactionDate, List transactionsToReprocess) { List installmentsToReprocess = new ArrayList<>( 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); - loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); - } + + final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null); + loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); Pair reprocessResult = processor .reprocessProgressiveLoanTransactions(loan.getDisbursementDate(), relatedRefundTransactionDate, transactionsToReprocess, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanSummaryDataProvider.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanSummaryDataProvider.java index 2606174fd..e2233bd91 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanSummaryDataProvider.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanSummaryDataProvider.java @@ -21,23 +21,16 @@ package org.apache.fineract.portfolio.loanaccount.service; import java.math.BigDecimal; import java.time.LocalDate; import java.util.Collection; -import java.util.List; -import java.util.Objects; import java.util.Optional; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.tuple.Pair; import org.apache.fineract.infrastructure.core.service.MathUtil; import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.portfolio.loanaccount.data.LoanSummaryData; 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.LoanRepaymentScheduleInstallment; 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.LoanSchedulePeriodData; import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator; @@ -51,10 +44,8 @@ import org.springframework.transaction.annotation.Transactional; @Slf4j public class ProgressiveLoanSummaryDataProvider extends CommonLoanSummaryDataProvider { - private final AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor; private final EMICalculator emiCalculator; private final LoanRepositoryWrapper loanRepository; - private final LoanTransactionRepository loanTransactionRepository; private final InterestScheduleModelRepositoryWrapper modelRepository; @Override @@ -81,24 +72,6 @@ public class ProgressiveLoanSummaryDataProvider extends CommonLoanSummaryDataPro && businessDate.isAfter(i.getFromDate()) && !businessDate.isAfter(i.getDueDate())).findFirst(); } - private ProgressiveLoanInterestScheduleModel calculateModel(Loan loan, LocalDate businessDate) { - final List transactionsToReprocess = loanTransactionRepository - .findNonReversedTransactionsForReprocessingByLoan(loan).stream().filter(t -> !t.isAccrualActivity()).toList(); - Pair changedTransactionDetailProgressiveLoanInterestScheduleModelPair = advancedPaymentScheduleTransactionProcessor - .reprocessProgressiveLoanTransactions(loan.getDisbursementDate(), businessDate, transactionsToReprocess, loan.getCurrency(), - loan.getRepaymentScheduleInstallments(), loan.getActiveCharges()); - ProgressiveLoanInterestScheduleModel model = changedTransactionDetailProgressiveLoanInterestScheduleModelPair.getRight(); - final List 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 public BigDecimal computeTotalUnpaidPayableNotDueInterestAmountOnActualPeriod(final Loan loan, final Collection periods, final LocalDate businessDate, final CurrencyData currency, @@ -117,7 +90,7 @@ public class ProgressiveLoanSummaryDataProvider extends CommonLoanSummaryDataPro Optional savedModel = modelRepository.getSavedModel(loan, businessDate); - ProgressiveLoanInterestScheduleModel model = savedModel.orElseGet(() -> calculateModel(loan, businessDate)); + ProgressiveLoanInterestScheduleModel model = savedModel.orElse(null); if (model != null) { OutstandingDetails outstandingDetails = emiCalculator.getOutstandingAmountsTillDate(model, businessDate); if (!loan.isInterestRecalculationEnabled()) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java index 82890eccf..e27bc157e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java @@ -19,10 +19,8 @@ package org.apache.fineract.portfolio.loanaccount.service; import jakarta.persistence.FlushModeType; -import java.time.LocalDate; import java.util.List; import java.util.Optional; -import java.util.Set; import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.annotation.WithFlushMode; 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.LoanTransactionBusinessEvent; 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.loanaccount.data.TransactionChangeData; import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail; import org.apache.fineract.portfolio.loanaccount.domain.Loan; 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.LoanTransactionRepository; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; @@ -78,111 +71,41 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa } @Override - public void reprocessParticularTransactions(final Loan loan, final List loanTransactions) { - final ChangedTransactionDetail changedTransactionDetail = reprocessTransactionsAndFetchChangedTransactions(loan, loanTransactions); - handleChangedDetail(changedTransactionDetail); - } - - @Override - public void reprocessTransactionsWithPostTransactionChecks(final Loan loan, final LocalDate transactionDate) { + public void reprocessTransactions(final Loan loan, final List newTransactions) { final List transactions = loanTransactionRepository.findNonReversedTransactionsForReprocessingByLoan(loan); + transactions.addAll(newTransactions); final ChangedTransactionDetail changedTransactionDetail = reprocessTransactionsAndFetchChangedTransactions(loan, transactions); handleChangedDetail(changedTransactionDetail); } @Override - public void reprocessTransactionsWithoutChecks(final Loan loan, final LocalDate transactionDate, - final List newTransactions) { + public void reprocessTransactionsWithoutChecks(final Loan loan, final List newTransactions) { final List transactions = loanTransactionRepository.findNonReversedTransactionsForReprocessingByLoan(loan); transactions.addAll(newTransactions); 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 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 loanChargePaidBys, LoanCharge loanCharge) { - return loanChargePaidBys.stream() // - .anyMatch(loanChargePaidBy -> loanChargePaidBy.getLoanCharge().equals(loanCharge)); - } - @Override public void processLatestTransaction(final LoanTransaction loanTransaction, final Loan loan) { LoanRepaymentScheduleTransactionProcessor transactionProcessor = loanTransactionProcessingService .getTransactionProcessor(loan.getTransactionProcessingStrategyCode()); TransactionCtx transactionCtx; - if (transactionProcessor instanceof AdvancedPaymentScheduleTransactionProcessor advancedProcessor) { + if (transactionProcessor instanceof AdvancedPaymentScheduleTransactionProcessor) { Optional savedModel = interestScheduleModelRepositoryWrapper.getSavedModel(loan, loanTransaction.getTransactionDate()); - ProgressiveLoanInterestScheduleModel model = savedModel - .orElseGet(() -> advancedProcessor.calculateInterestScheduleModel(loan.getId(), loanTransaction.getTransactionDate())); + if (savedModel.isEmpty()) { + throw new IllegalArgumentException("No saved model found for loan transaction " + loanTransaction); + } else { - final ProgressiveTransactionCtx progressiveTransactionCtx = new ProgressiveTransactionCtx(loan.getCurrency(), - loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), - new ChangedTransactionDetail(), model); - progressiveTransactionCtx.setChargedOff(loan.isChargedOff()); - progressiveTransactionCtx.setWrittenOff(loan.isClosedWrittenOff()); - progressiveTransactionCtx.setContractTerminated(loan.isContractTermination()); - transactionCtx = progressiveTransactionCtx; + final ProgressiveTransactionCtx progressiveTransactionCtx = new ProgressiveTransactionCtx(loan.getCurrency(), + loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), + new ChangedTransactionDetail(), savedModel.get()); + progressiveTransactionCtx.setChargedOff(loan.isChargedOff()); + progressiveTransactionCtx.setWrittenOff(loan.isClosedWrittenOff()); + progressiveTransactionCtx.setContractTerminated(loan.isContractTermination()); + transactionCtx = progressiveTransactionCtx; + } } else { transactionCtx = new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), new ChangedTransactionDetail()); @@ -201,11 +124,7 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa @Override public void updateModel(Loan loan) { - Optional savedModel = interestScheduleModelRepositoryWrapper.getSavedModel(loan, - ThreadLocalContextUtil.getBusinessDate()); - if (savedModel.isEmpty()) { - reprocessTransactions(loan); - } + interestScheduleModelRepositoryWrapper.getSavedModel(loan, ThreadLocalContextUtil.getBusinessDate()); } private void handleChangedDetail(final ChangedTransactionDetail changedTransactionDetail) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/adjustment/LoanAdjustmentServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/adjustment/LoanAdjustmentServiceImpl.java index da2ec721c..9366ac0d3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/adjustment/LoanAdjustmentServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/adjustment/LoanAdjustmentServiceImpl.java @@ -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.LoanDownPaymentHandlerService; 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.ReprocessLoanTransactionsService; import org.apache.fineract.portfolio.note.domain.Note; @@ -100,6 +101,7 @@ public class LoanAdjustmentServiceImpl implements LoanAdjustmentService { private final ReprocessLoanTransactionsService reprocessLoanTransactionsService; private final LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository; private final LoanBuyDownFeeBalanceRepository loanBuyDownFeeBalanceRepository; + private final LoanScheduleService loanScheduleService; @Override 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)) { - reprocessLoanTransactionsService.reprocessTransactions(loan); + loanScheduleService.regenerateScheduleWithReprocessingTransactions(loan); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/contracttermination/LoanContractTerminationServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/contracttermination/LoanContractTerminationServiceImpl.java index b9cb52797..808611769 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/contracttermination/LoanContractTerminationServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/contracttermination/LoanContractTerminationServiceImpl.java @@ -76,7 +76,7 @@ public class LoanContractTerminationServiceImpl { private final LoanTransactionService loanTransactionService; 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 loanUtilService.checkClientOrGroupActive(loan); @@ -87,7 +87,19 @@ public class LoanContractTerminationServiceImpl { final Map changes = new LinkedHashMap<>(); 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"); if (StringUtils.isNotBlank(noteText)) { @@ -95,22 +107,7 @@ public class LoanContractTerminationServiceImpl { final Note note = Note.loanTransactionNote(loan, contractTermination, noteText); noteRepository.save(note); } - - // 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 loanTransactions = loanTransactionService.retrieveListOfTransactionsForReprocessing(loan); - loanTransactions.add(contractTermination); - reprocessLoanTransactionsService.reprocessParticularTransactions(loan, loanTransactions); - loan.addLoanTransaction(contractTermination); - } else { - reprocessLoanTransactionsService.processLatestTransaction(contractTermination, loan); - loan.addLoanTransaction(contractTermination); - } - + loanTransactionRepository.saveAndFlush(contractTermination); businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan)); businessEventNotifierService.notifyPostBusinessEvent(new LoanTransactionContractTerminationPostBusinessEvent(contractTermination)); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reaging/LoanReAgingService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reaging/LoanReAgingService.java index fa0931e78..071ffc9f9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reaging/LoanReAgingService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reaging/LoanReAgingService.java @@ -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.reaging.LoanReAgeInterestHandlingType; 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.loanschedule.data.LoanScheduleData; import org.apache.fineract.portfolio.loanaccount.repository.LoanCapitalizedIncomeBalanceRepository; 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.LoanReadPlatformService; import org.apache.fineract.portfolio.loanaccount.service.LoanRepaymentScheduleService; 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.ReprocessLoanTransactionsService; import org.apache.fineract.portfolio.note.domain.Note; @@ -100,14 +99,16 @@ public class LoanReAgingService { private final LoanRepaymentScheduleService loanRepaymentScheduleService; private final LoanReadPlatformService loanReadPlatformService; private final LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository; + private final InterestScheduleModelRepositoryWrapper modelRepository; + private final LoanTransactionService loanTransactionService; public CommandProcessingResult reAge(final Long loanId, final JsonCommand command) { final Loan loan = loanAssembler.assembleFrom(loanId); reAgingValidator.validateReAge(loan, command); final LoanTransaction reAgeTransaction = createReAgeTransaction(loan, command); - loanTransactionRepository.saveAndFlush(reAgeTransaction); processReAgeTransaction(loan, reAgeTransaction, true); + loanTransactionRepository.saveAndFlush(reAgeTransaction); loan.updateLoanScheduleDependentDerivedFields(); final Map changes = new LinkedHashMap<>(); @@ -170,8 +171,7 @@ public class LoanReAgingService { if (reAgeTransaction == null) { throw new LoanTransactionNotFoundException("Re-Age transaction for loan was not found"); } - if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy()) - || loan.hasContractTerminationTransaction() || (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) { + if (loan.isProgressiveSchedule()) { final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null); loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); } @@ -196,31 +196,16 @@ public class LoanReAgingService { } private void processReAgeTransaction(final Loan loan, final LoanTransaction reAgeTransaction, final boolean withPostTransactionChecks) { - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanRepaymentScheduleTransactionProcessorFactory - .determineProcessor(loan.transactionProcessingStrategy()); - if (reAgeTransaction.getTransactionDate().isBefore(reAgeTransaction.getSubmittedOnDate()) - && !loan.isInterestBearingAndInterestRecalculationEnabled()) { + if (reAgeTransaction.getTransactionDate().isBefore(reAgeTransaction.getSubmittedOnDate())) { + final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null); + loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); if (withPostTransactionChecks) { - reprocessLoanTransactionsService.reprocessTransactionsWithPostTransactionChecks(loan, - reAgeTransaction.getTransactionDate()); + reprocessLoanTransactionsService.reprocessTransactions(loan, List.of(reAgeTransaction)); } else { - reprocessLoanTransactionsService.reprocessTransactionsWithoutChecks(loan, reAgeTransaction.getTransactionDate(), - List.of(reAgeTransaction)); + reprocessLoanTransactionsService.reprocessTransactionsWithoutChecks(loan, 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 loanTransactions = loanTransactionRepository.findNonReversedTransactionsForReprocessingByLoan(loan); - loanTransactions.add(reAgeTransaction); - reprocessLoanTransactionsService.reprocessParticularTransactions(loan, loanTransactions); } else { - loanRepaymentScheduleTransactionProcessor.processLatestTransaction(reAgeTransaction, - new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), - new MoneyHolder(loan.getTotalOverpaidAsMoney()), null)); + reprocessLoanTransactionsService.processLatestTransaction(reAgeTransaction, loan); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountAutoStarter.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountAutoStarter.java index 0b7453bca..2574d5940 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountAutoStarter.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountAutoStarter.java @@ -21,8 +21,6 @@ package org.apache.fineract.portfolio.loanaccount.starter; import java.util.List; import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; 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.impl.AdvancedPaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.CreocoreLoanRepaymentScheduleTransactionProcessor; @@ -136,12 +134,11 @@ public class LoanAccountAutoStarter { @Bean @Conditional(AdvancedPaymentScheduleTransactionProcessorCondition.class) public AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor(final EMICalculator emiCalculator, - final LoanRepositoryWrapper loanRepositoryWrapper, final @Lazy ProgressiveLoanInterestRefundServiceImpl progressiveLoanInterestRefundService, final ExternalIdFactory externalIdFactory, final LoanScheduleComponent loanSchedule, - final LoanTransactionRepository loanTransactionRepository, final LoanChargeValidator loanChargeValidator, - final LoanBalanceService loanBalanceService, @Lazy final LoanChargeService loanChargeService) { - return new AdvancedPaymentScheduleTransactionProcessor(emiCalculator, loanRepositoryWrapper, progressiveLoanInterestRefundService, - externalIdFactory, loanSchedule, loanTransactionRepository, loanChargeValidator, loanBalanceService, loanChargeService); + final LoanChargeValidator loanChargeValidator, final LoanBalanceService loanBalanceService, + @Lazy final LoanChargeService loanChargeService) { + return new AdvancedPaymentScheduleTransactionProcessor(emiCalculator, progressiveLoanInterestRefundService, externalIdFactory, + loanSchedule, loanChargeValidator, loanBalanceService, loanChargeService); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java index 39cd264df..1abb051df 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java @@ -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.GLIMAccountInfoWritePlatformService; 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.LoanAccountServiceImpl; import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualActivityProcessingService; @@ -495,11 +496,10 @@ public class LoanAccountConfiguration { @Bean @ConditionalOnMissingBean(LoanDisbursementService.class) public LoanDisbursementService loanDisbursementService(LoanChargeValidator loanChargeValidator, - LoanDisbursementValidator loanDisbursementValidator, ReprocessLoanTransactionsService reprocessLoanTransactionsService, - LoanChargeService loanChargeService, LoanBalanceService loanBalanceService, LoanJournalEntryPoster journalEntryPoster, - LoanTransactionRepository loanTransactionRepository) { - return new LoanDisbursementService(loanChargeValidator, loanDisbursementValidator, reprocessLoanTransactionsService, - loanChargeService, loanBalanceService, journalEntryPoster, loanTransactionRepository); + LoanDisbursementValidator loanDisbursementValidator, LoanChargeService loanChargeService, LoanBalanceService loanBalanceService, + LoanJournalEntryPoster journalEntryPoster, LoanTransactionRepository loanTransactionRepository) { + return new LoanDisbursementService(loanChargeValidator, loanDisbursementValidator, loanChargeService, loanBalanceService, + journalEntryPoster, loanTransactionRepository); } @Bean @@ -515,9 +515,9 @@ public class LoanAccountConfiguration { public LoanScheduleService loanScheduleService(final LoanChargeService loanChargeService, final ReprocessLoanTransactionsService reprocessLoanTransactionsService, final LoanMapper loanMapper, final LoanTransactionProcessingService loanTransactionProcessingService, LoanScheduleComponent loanSchedule, - final LoanTransactionRepository loanTransactionRepository) { + final LoanTransactionRepository loanTransactionRepository, final ILoanUtilService loanUtilService) { return new LoanScheduleService(loanChargeService, reprocessLoanTransactionsService, loanMapper, loanTransactionProcessingService, - loanSchedule, loanTransactionRepository); + loanSchedule, loanTransactionRepository, loanUtilService); } @Bean @@ -544,9 +544,9 @@ public class LoanAccountConfiguration { @ConditionalOnMissingBean(InterestPauseWritePlatformService.class) public InterestPauseWritePlatformService interestPauseWritePlatformService(LoanTermVariationsRepository loanTermVariationsRepository, LoanRepositoryWrapper loanRepositoryWrapper, LoanAssembler loanAssembler, - ReprocessLoanTransactionsService reprocessLoanTransactionsService, BusinessEventNotifierService businessEventNotifierService) { + BusinessEventNotifierService businessEventNotifierService, LoanScheduleService loanScheduleService) { return new InterestPauseWritePlatformServiceImpl(loanTermVariationsRepository, loanRepositoryWrapper, loanAssembler, - reprocessLoanTransactionsService, businessEventNotifierService); + businessEventNotifierService, loanScheduleService); } @Bean diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImplTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImplTest.java index 7ef287f53..3ee3db86d 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImplTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImplTest.java @@ -165,6 +165,9 @@ class LoanChargeWritePlatformServiceImplTest { @Mock private LoanJournalEntryPoster journalEntryPoster; + @Mock + private LoanScheduleService loanScheduleService; + @BeforeEach void setUp() { when(loanAssembler.assembleFrom(LOAN_ID)).thenReturn(loan);