FINERACT-2178: Ability to retrieve a loan's balance at a certain point in time in the past

This commit is contained in:
Arnold Galovics
2025-02-17 21:48:05 +01:00
committed by Arnold Gálovics
parent 986dceea1c
commit 0f1be3c913
29 changed files with 1523 additions and 8 deletions
@@ -19,6 +19,9 @@
package org.apache.fineract.portfolio.loanaccount.data;
import lombok.Getter;
import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
/**
* Immutable data object represent loan status enumerations.
@@ -51,4 +54,12 @@ public class LoanStatusEnumData {
this.closed = this.closedObligationsMet || this.closedWrittenOff || this.closedRescheduled;
this.overpaid = Long.valueOf(700).equals(this.id);
}
@org.mapstruct.Mapper(config = MapstructMapperConfig.class)
public interface Mapper {
default LoanStatusEnumData map(Loan source) {
return LoanEnumerations.status(source.getPlainStatus());
}
}
}
@@ -2693,11 +2693,15 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
return changedTransactionDetail;
}
/*
* Probably this is buggy when a chargeback transaction happens
*/
public void processPostDisbursementTransactions() {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor();
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing();
final List<LoanTransaction> copyTransactions = new ArrayList<>();
if (!allNonContraTransactionsPostDisbursement.isEmpty()) {
// 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));
}
@@ -3507,6 +3511,10 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
return Optional.ofNullable(this.charges).orElse(new HashSet<>());
}
public void removeCharges(Predicate<LoanCharge> predicate) {
charges.removeIf(predicate);
}
public boolean hasDelinquencyBucket() {
return (getLoanProduct().getDelinquencyBucket() != null);
}
@@ -3537,6 +3545,10 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
return getLoanTransactions().stream().filter(predicate).findFirst().orElse(null);
}
public void removeLoanTransactions(Predicate<LoanTransaction> predicate) {
loanTransactions.removeIf(predicate);
}
public LoanTransaction findChargedOffTransaction() {
return getLoanTransaction(e -> e.isNotReversed() && e.isChargeOff());
}
@@ -85,6 +85,8 @@ public interface LoanRepository extends JpaRepository<Loan, Long>, JpaSpecificat
String FIND_ID_BY_EXTERNAL_ID = "SELECT loan.id FROM Loan loan WHERE loan.externalId = :externalId";
String FIND_IDS_BY_EXTERNAL_IDS = "SELECT loan.id FROM Loan loan WHERE loan.externalId IN :externalIds ORDER BY loan.id ASC";
// should follow the logic of `FIND_ALL_NON_CLOSED_LOANS_BY_LAST_CLOSED_BUSINESS_DATE` query
String FIND_OLDEST_COB_PROCESSED_LOAN = "select loan.id, loan.lastClosedBusinessDate from Loan loan where loan.loanStatus in (100,200,300,303,304) and loan.lastClosedBusinessDate = (select min(l.lastClosedBusinessDate) from Loan l where l"
+ ".loanStatus in (100,200,300,303,304) and l.lastClosedBusinessDate < :cobBusinessDate)";
@@ -217,6 +219,9 @@ public interface LoanRepository extends JpaRepository<Loan, Long>, JpaSpecificat
@Query(FIND_ID_BY_EXTERNAL_ID)
Long findIdByExternalId(@Param("externalId") ExternalId externalId);
@Query(FIND_IDS_BY_EXTERNAL_IDS)
List<Long> findIdsByExternalIds(@Param("externalIds") List<ExternalId> externalIds);
@Query(FIND_ALL_NON_CLOSED_LOANS_BEHIND_BY_LOAN_IDS)
List<LoanIdAndLastClosedBusinessDate> findAllNonClosedLoansBehindByLoanIds(@Param("cobBusinessDate") LocalDate cobBusinessDate,
@Param("loanIds") List<Long> loanIds);
@@ -285,4 +285,7 @@ public class LoanRepositoryWrapper {
return repository.findLoansForAddAccrual(accountingType, tillDate, futureCharges);
}
public List<Long> findIdByExternalIds(List<ExternalId> externalIds) {
return repository.findIdsByExternalIds(externalIds);
}
}
@@ -53,7 +53,7 @@ public class LoanScheduleService {
}
}
public void recalculateScheduleFromLastTransaction(final Loan loan, final ScheduleGeneratorDTO generatorDTO) {
public void recalculateSchedule(final Loan loan, final ScheduleGeneratorDTO generatorDTO) {
if (loan.isInterestBearingAndInterestRecalculationEnabled() && !loan.isChargedOff()) {
regenerateRepaymentScheduleWithInterestRecalculation(loan, generatorDTO);
} else {