Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5926,6 +5926,13 @@ public void inLoanTransactionsTheThTransactionOfOnHasRelationshipWithTypeREPLAYE
assertEquals(Integer.valueOf(numberOfRelations), relationshipOptional.size(), "Missed relationship for transaction");
}

@When("Call Internal API to remove progressive loan model by loan Id")
public void callInternalAPIToRemoveProgressiveLoanModelByLoanId() {
final PostLoansResponse loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
final long loanId = loanCreateResponse.getLoanId();
ok(() -> fineractClient.progressiveLoan().deleteModel(loanId));
}

public static AdvancedPaymentData editPaymentAllocationFutureInstallment(String transactionType, String futureInstallmentAllocationRule,
List<PaymentAllocationOrder> paymentAllocationOrder) {
AdvancedPaymentData advancedPaymentData = new AdvancedPaymentData();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1619,6 +1619,7 @@ Feature: LoanAccrualTransaction
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false |
# --- Early repayment with 17.01 EUR on 15 Jan ---
When Admin sets the business date to "15 January 2024"
When Call Internal API to remove progressive loan model by loan Id
When Admin makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "15 January 2024" with 17.01 EUR transaction amount
Then Loan Repayment schedule has 6 periods, with the following data for periods:
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
Expand Down Expand Up @@ -1660,6 +1661,7 @@ Feature: LoanAccrualTransaction
| 01 May 2024 | Accrual Activity | 0.48 | 0.0 | 0.48 | 0.0 | 0.0 | 0.0 | false | false |
| 31 May 2024 | Accrual | 1.87 | 0.0 | 1.87 | 0.0 | 0.0 | 0.0 | false | false |
When Admin sets the business date to "02 June 2024"
When Call Internal API to remove progressive loan model by loan Id
And Admin runs inline COB job for Loan
Then Loan Repayment schedule has 6 periods, with the following data for periods:
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
Expand All @@ -1685,6 +1687,7 @@ Feature: LoanAccrualTransaction
| 01 June 2024 | Accrual | 0.58 | 0.0 | 0.58 | 0.0 | 0.0 | 0.0 | false | false |
| 01 June 2024 | Accrual Activity | 0.48 | 0.0 | 0.48 | 0.0 | 0.0 | 0.0 | false | false |
When Admin sets the business date to "01 July 2024"
When Call Internal API to remove progressive loan model by loan Id
And Admin runs inline COB job for Loan
Then Loan Repayment schedule has 6 periods, with the following data for periods:
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
Expand Down Expand Up @@ -1739,6 +1742,7 @@ Feature: LoanAccrualTransaction
| 29 June 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false |
| 30 June 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false |
When Admin sets the business date to "02 July 2024"
When Call Internal API to remove progressive loan model by loan Id
And Admin runs inline COB job for Loan
Then Loan Repayment schedule has 6 periods, with the following data for periods:
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
Expand Down Expand Up @@ -1794,6 +1798,7 @@ Feature: LoanAccrualTransaction
| 30 June 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false |
| 01 July 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false |
When Admin sets the business date to "03 July 2024"
When Call Internal API to remove progressive loan model by loan Id
And Admin runs inline COB job for Loan
Then Loan Repayment schedule has 6 periods, with the following data for periods:
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
Expand Down Expand Up @@ -1849,6 +1854,7 @@ Feature: LoanAccrualTransaction
| 30 June 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false |
| 01 July 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false |
When Admin sets the business date to "01 August 2024"
When Call Internal API to remove progressive loan model by loan Id
And Admin runs inline COB job for Loan
Then Loan Repayment schedule has 6 periods, with the following data for periods:
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
Expand Down Expand Up @@ -1904,6 +1910,7 @@ Feature: LoanAccrualTransaction
| 30 June 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false |
| 01 July 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false |
When Admin sets the business date to "02 August 2024"
When Call Internal API to remove progressive loan model by loan Id
And Admin runs inline COB job for Loan
Then Loan Repayment schedule has 6 periods, with the following data for periods:
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ public class LoanScheduleService {
private final LoanTransactionRepository loanTransactionRepository;
private final ILoanUtilService loanUtilService;

public boolean shouldReprocessLoan(final Loan loan) {
return reprocessLoanTransactionsService.shouldReprocessLoan(loan);
}

/**
* Ability to regenerate the repayment schedule based on the loans current details/state.
*/
Expand Down Expand Up @@ -81,7 +85,7 @@ public void recalculateScheduleFromLastTransaction(final Loan loan, final Schedu
existingTransactionIds.addAll(loanTransactionRepository.findTransactionIdsByLoan(loan));
existingReversedTransactionIds.addAll(loanTransactionRepository.findReversedTransactionIdsByLoan(loan));
}
if (!loan.isProgressiveSchedule()) {
if (!loan.isProgressiveSchedule() || reprocessLoanTransactionsService.shouldReprocessLoan(loan)) {
if (loan.isInterestBearingAndInterestRecalculationEnabled() && !loan.isChargedOff()) {
regenerateRepaymentScheduleWithInterestRecalculation(loan, generatorDTO);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public interface ReprocessLoanTransactionsService {

void reprocessTransactionsWithoutChecks(Loan loan, List<LoanTransaction> newTransactions);

boolean shouldReprocessLoan(Loan loan);

void processLatestTransaction(LoanTransaction loanTransaction, Loan loan);

void updateModel(Loan loan);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,9 @@
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;

import java.math.MathContext;
import java.time.LocalDate;
import java.util.Optional;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.ProgressiveLoanModel;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePlan;
import org.apache.fineract.portfolio.loanaccount.service.InterestScheduleModelRepositoryWrapper;
import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
import org.apache.fineract.portfolio.loanproduct.calc.ProgressiveEMICalculator;
import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
import org.apache.fineract.portfolio.loanproduct.domain.ILoanConfigurationDetails;

@SuppressWarnings("unused")
public class EmbeddableProgressiveLoanScheduleGenerator {
Expand All @@ -38,50 +31,10 @@ public class EmbeddableProgressiveLoanScheduleGenerator {
public EmbeddableProgressiveLoanScheduleGenerator() {
final ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
final EMICalculator emiCalculator = new ProgressiveEMICalculator(scheduledDateGenerator);
this.scheduleGenerator = new ProgressiveLoanScheduleGenerator(scheduledDateGenerator, emiCalculator,
new NoopInterestScheduleModelRepositoryWrapper());
this.scheduleGenerator = new ProgressiveLoanScheduleGenerator(scheduledDateGenerator, emiCalculator);
}

public LoanSchedulePlan generate(final MathContext mc, final LoanRepaymentScheduleModelData modelData) {
return scheduleGenerator.generate(mc, modelData);
}

private static final class NoopInterestScheduleModelRepositoryWrapper implements InterestScheduleModelRepositoryWrapper {

@Override
public Optional<ProgressiveLoanModel> findOneByLoanId(Long loanId) {
return Optional.empty();
}

@Override
public Optional<ProgressiveLoanModel> findOneByLoan(Loan loan) {
return Optional.empty();
}

@Override
public Optional<ProgressiveLoanInterestScheduleModel> extractModel(Optional<ProgressiveLoanModel> progressiveLoanModel) {
return Optional.empty();
}

@Override
public ProgressiveLoanInterestScheduleModel writeInterestScheduleModel(Loan loan, ProgressiveLoanInterestScheduleModel model) {
return null;
}

@Override
public Optional<ProgressiveLoanInterestScheduleModel> readProgressiveLoanInterestScheduleModel(Long loanId,
ILoanConfigurationDetails detail, Integer installmentAmountInMultipliesOf) {
return Optional.empty();
}

@Override
public boolean hasValidModelForDate(Long loanId, LocalDate targetDate) {
return false;
}

@Override
public Optional<ProgressiveLoanInterestScheduleModel> getSavedModel(Loan loan, LocalDate businessDate) {
return Optional.empty();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import java.util.List;
import java.util.Optional;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
Expand Down Expand Up @@ -58,18 +59,17 @@
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {

private final ScheduledDateGenerator scheduledDateGenerator;
private final EMICalculator emiCalculator;
private final InterestScheduleModelRepositoryWrapper interestScheduleModelRepositoryWrapper;

private InterestScheduleModelRepositoryWrapper interestScheduleModelRepositoryWrapper;
private LoanTransactionProcessingService loanTransactionProcessingService;

public ProgressiveLoanScheduleGenerator(ScheduledDateGenerator scheduledDateGenerator, EMICalculator emiCalculator,
InterestScheduleModelRepositoryWrapper interestScheduleModelRepositoryWrapper) {
this.scheduledDateGenerator = scheduledDateGenerator;
this.emiCalculator = emiCalculator;
@Autowired(required = false)
public void setInterestScheduleModelRepositoryWrapper(InterestScheduleModelRepositoryWrapper interestScheduleModelRepositoryWrapper) {
this.interestScheduleModelRepositoryWrapper = interestScheduleModelRepositoryWrapper;
}

Expand Down Expand Up @@ -251,6 +251,7 @@ public Money getPeriodInterestTillDate(@NotNull LoanRepaymentScheduleInstallment
return Money.zero(loan.getCurrency());
}
Optional<ProgressiveLoanInterestScheduleModel> savedModel = interestScheduleModelRepositoryWrapper.getSavedModel(loan, targetDate);

ProgressiveLoanInterestScheduleModel model = savedModel.orElseThrow();
return emiCalculator.getPeriodInterestTillDate(model, installment.getFromDate(), installment.getDueDate(), targetDate, false, true);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,6 @@ public interface ProgressiveLoanModelRepository
Optional<ProgressiveLoanModel> findOneByLoanId(Long loanId);

Optional<ProgressiveLoanModel> findOneByLoan(Loan loan);

Long removeByLoanId(Long loanId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,8 @@ public CommandProcessingResult capitalizedIncomeAdjustment(final Long loanId, fi
}

private void recalculateLoanTransactions(Loan loan, LoanTransaction transaction) {
if (loan.isInterestRecalculationEnabled() || DateUtils.isBeforeBusinessDate(transaction.getTransactionDate())) {
if (loan.isInterestRecalculationEnabled() || DateUtils.isBeforeBusinessDate(transaction.getTransactionDate())
|| reprocessLoanTransactionsService.shouldReprocessLoan(loan)) {
loanScheduleService.regenerateRepaymentSchedule(loan);
reprocessLoanTransactionsService.reprocessTransactions(loan, List.of(transaction));
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ public interface InterestScheduleModelRepositoryWrapper {

Optional<ProgressiveLoanModel> findOneByLoan(Loan loan);

Long removeByLoanId(Long loanId);

Optional<ProgressiveLoanInterestScheduleModel> extractModel(Optional<ProgressiveLoanModel> progressiveLoanModel);

ProgressiveLoanInterestScheduleModel writeInterestScheduleModel(Loan loan, ProgressiveLoanInterestScheduleModel model);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ public Optional<ProgressiveLoanModel> findOneByLoan(Loan loan) {
return progressiveLoanModelRef.get();
}

@Override
public Long removeByLoanId(Long loanId) {
return loanModelRepository.removeByLoanId(loanId);
}

@Override
public Optional<ProgressiveLoanInterestScheduleModel> extractModel(Optional<ProgressiveLoanModel> progressiveLoanModel) {
return progressiveLoanModel.map(ProgressiveLoanModel::getJsonModel) //
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
Expand Down Expand Up @@ -99,4 +100,14 @@ public ProgressiveLoanInterestScheduleModel updateModel(@PathParam("loanId") @Pa

return writePlatformService.writeInterestScheduleModel(loan, model);
}

@DELETE
@Path("{loanId}/model")
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Delete ProgressiveLoanInterestScheduleModel By Loan ID", description = "DO NOT USE THIS IN PRODUCTION!")
@Transactional
public Long deleteModel(@PathParam("loanId") @Parameter(description = "loanId") long loanId) {
return writePlatformService.removeByLoanId(loanId);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* 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 java.util.List;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
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.LoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Component
@RequiredArgsConstructor
public class ProgressiveLoanModelProcessingService {

private final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessorFactory;
private final LoanTransactionRepository loanTransactionRepository;
private final LoanRepositoryWrapper loanRepositoryWrapper;

@Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW)
public ProgressiveLoanInterestScheduleModel getRecalculatedModel(Long loanId, LocalDate tillDate) {
Loan loan = loanRepositoryWrapper.findOneWithNotFoundDetection(loanId);
LoanRepaymentScheduleTransactionProcessor transactionProcessor = transactionProcessorFactory
.determineProcessor(loan.getTransactionProcessingStrategyCode());
if (transactionProcessor instanceof AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor) {
List<LoanTransaction> loanTransactions = loanTransactionRepository.findNonReversedTransactionsForReprocessingByLoan(loan);
Pair<ChangedTransactionDetail, ProgressiveLoanInterestScheduleModel> result = advancedPaymentScheduleTransactionProcessor
.reprocessProgressiveLoanTransactions(loan.getDisbursementDate(), tillDate, loanTransactions, loan.getCurrency(),
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges());
return result.getRight();
}
return null;
}

}
Loading
Loading