FINERACT-2234: Replace in-memory transaction loops with repository queries

This commit is contained in:
Oleksii Novikov
2025-07-01 20:14:28 +02:00
committed by Adam Saghy
parent dd4ca4d3e2
commit cb6a15c3d2
29 changed files with 281 additions and 228 deletions
@@ -58,6 +58,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.springframework.lang.NonNull; import org.springframework.lang.NonNull;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@@ -77,6 +78,7 @@ public class DelinquencyReadPlatformServiceImpl implements DelinquencyReadPlatfo
private final LoanDelinquencyActionRepository loanDelinquencyActionRepository; private final LoanDelinquencyActionRepository loanDelinquencyActionRepository;
private final DelinquencyEffectivePauseHelper delinquencyEffectivePauseHelper; private final DelinquencyEffectivePauseHelper delinquencyEffectivePauseHelper;
private final ConfigurationDomainService configurationDomainService; private final ConfigurationDomainService configurationDomainService;
private final LoanTransactionRepository loanTransactionRepository;
@Override @Override
public List<DelinquencyRangeData> retrieveAllDelinquencyRanges() { public List<DelinquencyRangeData> retrieveAllDelinquencyRanges() {
@@ -174,7 +176,7 @@ public class DelinquencyReadPlatformServiceImpl implements DelinquencyReadPlatfo
private void addInstallmentLevelDelinquencyData(CollectionData collectionData, Long loanId) { private void addInstallmentLevelDelinquencyData(CollectionData collectionData, Long loanId) {
Collection<LoanInstallmentDelinquencyTagData> loanInstallmentDelinquencyTagData = retrieveLoanInstallmentsCurrentDelinquencyTag( Collection<LoanInstallmentDelinquencyTagData> loanInstallmentDelinquencyTagData = retrieveLoanInstallmentsCurrentDelinquencyTag(
loanId); loanId);
if (loanInstallmentDelinquencyTagData != null && loanInstallmentDelinquencyTagData.size() > 0) { if (loanInstallmentDelinquencyTagData != null && !loanInstallmentDelinquencyTagData.isEmpty()) {
// installment level delinquency grouped by rangeId, and summed up the delinquent amount // installment level delinquency grouped by rangeId, and summed up the delinquent amount
Collection<InstallmentLevelDelinquency> installmentLevelDelinquencies = loanInstallmentDelinquencyTagData.stream() Collection<InstallmentLevelDelinquency> installmentLevelDelinquencies = loanInstallmentDelinquencyTagData.stream()
@@ -254,12 +256,7 @@ public class DelinquencyReadPlatformServiceImpl implements DelinquencyReadPlatfo
} }
} }
LocalDate lastTransactionDate = null; final LocalDate lastTransactionDate = loanTransactionRepository.findLastRepaymentLikeTransactionDate(loan).orElse(null);
for (final LoanTransaction transaction : loan.getLoanTransactions()) {
if (transaction.isRepaymentLikeType() && transaction.isGreaterThanZero()) {
lastTransactionDate = transaction.getTransactionDate();
}
}
LocalDate possibleNextRepaymentDate = earliestUnpaidInstallmentDate; LocalDate possibleNextRepaymentDate = earliestUnpaidInstallmentDate;
if (DateUtils.isAfter(lastTransactionDate, earliestUnpaidInstallmentDate)) { if (DateUtils.isAfter(lastTransactionDate, earliestUnpaidInstallmentDate)) {
@@ -42,6 +42,7 @@ import org.apache.fineract.portfolio.delinquency.validator.DelinquencyBucketPars
import org.apache.fineract.portfolio.delinquency.validator.DelinquencyRangeParseAndValidator; import org.apache.fineract.portfolio.delinquency.validator.DelinquencyRangeParseAndValidator;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionReadService; import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionReadService;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -60,11 +61,12 @@ public class DelinquencyConfiguration {
LoanDelinquencyDomainService loanDelinquencyDomainService, LoanDelinquencyDomainService loanDelinquencyDomainService,
LoanInstallmentDelinquencyTagRepository repositoryLoanInstallmentDelinquencyTag, LoanInstallmentDelinquencyTagRepository repositoryLoanInstallmentDelinquencyTag,
LoanDelinquencyActionRepository loanDelinquencyActionRepository, LoanDelinquencyActionRepository loanDelinquencyActionRepository,
DelinquencyEffectivePauseHelper delinquencyEffectivePauseHelper, ConfigurationDomainService configurationDomainService) { DelinquencyEffectivePauseHelper delinquencyEffectivePauseHelper, ConfigurationDomainService configurationDomainService,
LoanTransactionRepository loanTransactionRepository) {
return new DelinquencyReadPlatformServiceImpl(repositoryRange, repositoryBucket, repositoryLoanDelinquencyTagHistory, mapperRange, return new DelinquencyReadPlatformServiceImpl(repositoryRange, repositoryBucket, repositoryLoanDelinquencyTagHistory, mapperRange,
mapperBucket, mapperLoanDelinquencyTagHistory, loanRepository, loanDelinquencyDomainService, mapperBucket, mapperLoanDelinquencyTagHistory, loanRepository, loanDelinquencyDomainService,
repositoryLoanInstallmentDelinquencyTag, loanDelinquencyActionRepository, delinquencyEffectivePauseHelper, repositoryLoanInstallmentDelinquencyTag, loanDelinquencyActionRepository, delinquencyEffectivePauseHelper,
configurationDomainService); configurationDomainService, loanTransactionRepository);
} }
@Bean @Bean
@@ -59,7 +59,6 @@ import org.apache.fineract.infrastructure.codes.domain.CodeValue;
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom; import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
import org.apache.fineract.infrastructure.core.domain.ExternalId; import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.infrastructure.security.service.RandomPasswordGenerator; import org.apache.fineract.infrastructure.security.service.RandomPasswordGenerator;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.monetary.domain.Money;
@@ -74,7 +73,6 @@ import org.apache.fineract.portfolio.fund.domain.Fund;
import org.apache.fineract.portfolio.group.domain.Group; import org.apache.fineract.portfolio.group.domain.Group;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct; import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
import org.apache.fineract.portfolio.loanproduct.domain.LoanSupportedInterestRefundTypes; import org.apache.fineract.portfolio.loanproduct.domain.LoanSupportedInterestRefundTypes;
@@ -433,13 +431,13 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
final List<LoanDisbursementDetails> disbursementDetails, final BigDecimal maxOutstandingLoanBalance, final List<LoanDisbursementDetails> disbursementDetails, final BigDecimal maxOutstandingLoanBalance,
final Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate, final Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate,
final BigDecimal interestRateDifferential, final List<Rate> rates, final BigDecimal fixedPrincipalPercentagePerInstallment, final BigDecimal interestRateDifferential, final List<Rate> rates, final BigDecimal fixedPrincipalPercentagePerInstallment,
final ExternalId externalId, final LoanApplicationTerms loanApplicationTerms, final LoanScheduleModel loanScheduleModel, final ExternalId externalId, final LoanApplicationTerms loanApplicationTerms, final Boolean enableInstallmentLevelDelinquency,
final Boolean enableInstallmentLevelDelinquency, final LocalDate submittedOnDate) { final LocalDate submittedOnDate) {
return new Loan(accountNo, client, null, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct, return new Loan(accountNo, client, null, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct,
loanRepaymentScheduleDetail, null, loanCharges, collateral, null, fixedEmiAmount, disbursementDetails, loanRepaymentScheduleDetail, null, loanCharges, collateral, null, fixedEmiAmount, disbursementDetails,
maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential, rates, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential, rates,
fixedPrincipalPercentagePerInstallment, externalId, loanApplicationTerms, loanScheduleModel, fixedPrincipalPercentagePerInstallment, externalId, loanApplicationTerms, enableInstallmentLevelDelinquency,
enableInstallmentLevelDelinquency, submittedOnDate); submittedOnDate);
} }
public static Loan newGroupLoanApplication(final String accountNo, final Group group, final AccountType loanType, public static Loan newGroupLoanApplication(final String accountNo, final Group group, final AccountType loanType,
@@ -450,13 +448,13 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
final List<LoanDisbursementDetails> disbursementDetails, final BigDecimal maxOutstandingLoanBalance, final List<LoanDisbursementDetails> disbursementDetails, final BigDecimal maxOutstandingLoanBalance,
final Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate, final Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate,
final BigDecimal interestRateDifferential, final List<Rate> rates, final BigDecimal fixedPrincipalPercentagePerInstallment, final BigDecimal interestRateDifferential, final List<Rate> rates, final BigDecimal fixedPrincipalPercentagePerInstallment,
final ExternalId externalId, final LoanApplicationTerms loanApplicationTerms, final LoanScheduleModel loanScheduleModel, final ExternalId externalId, final LoanApplicationTerms loanApplicationTerms, final Boolean enableInstallmentLevelDelinquency,
final Boolean enableInstallmentLevelDelinquency, final LocalDate submittedOnDate) { final LocalDate submittedOnDate) {
return new Loan(accountNo, null, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct, return new Loan(accountNo, null, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct,
loanRepaymentScheduleDetail, null, loanCharges, null, syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails, loanRepaymentScheduleDetail, null, loanCharges, null, syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails,
maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential, rates, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential, rates,
fixedPrincipalPercentagePerInstallment, externalId, loanApplicationTerms, loanScheduleModel, fixedPrincipalPercentagePerInstallment, externalId, loanApplicationTerms, enableInstallmentLevelDelinquency,
enableInstallmentLevelDelinquency, submittedOnDate); submittedOnDate);
} }
public static Loan newIndividualLoanApplicationFromGroup(final String accountNo, final Client client, final Group group, public static Loan newIndividualLoanApplicationFromGroup(final String accountNo, final Client client, final Group group,
@@ -467,13 +465,13 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
final List<LoanDisbursementDetails> disbursementDetails, final BigDecimal maxOutstandingLoanBalance, final List<LoanDisbursementDetails> disbursementDetails, final BigDecimal maxOutstandingLoanBalance,
final Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate, final Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate,
final BigDecimal interestRateDifferential, final List<Rate> rates, final BigDecimal fixedPrincipalPercentagePerInstallment, final BigDecimal interestRateDifferential, final List<Rate> rates, final BigDecimal fixedPrincipalPercentagePerInstallment,
final ExternalId externalId, final LoanApplicationTerms loanApplicationTerms, final LoanScheduleModel loanScheduleModel, final ExternalId externalId, final LoanApplicationTerms loanApplicationTerms, final Boolean enableInstallmentLevelDelinquency,
final Boolean enableInstallmentLevelDelinquency, final LocalDate submittedOnDate) { final LocalDate submittedOnDate) {
return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct, return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct,
loanRepaymentScheduleDetail, null, loanCharges, null, syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails, loanRepaymentScheduleDetail, null, loanCharges, null, syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails,
maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential, rates, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential, rates,
fixedPrincipalPercentagePerInstallment, externalId, loanApplicationTerms, loanScheduleModel, fixedPrincipalPercentagePerInstallment, externalId, loanApplicationTerms, enableInstallmentLevelDelinquency,
enableInstallmentLevelDelinquency, submittedOnDate); submittedOnDate);
} }
protected Loan() { protected Loan() {
@@ -488,8 +486,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
final List<LoanDisbursementDetails> disbursementDetails, final BigDecimal maxOutstandingLoanBalance, final List<LoanDisbursementDetails> disbursementDetails, final BigDecimal maxOutstandingLoanBalance,
final Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate, final Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate,
final BigDecimal interestRateDifferential, final List<Rate> rates, final BigDecimal fixedPrincipalPercentagePerInstallment, final BigDecimal interestRateDifferential, final List<Rate> rates, final BigDecimal fixedPrincipalPercentagePerInstallment,
final ExternalId externalId, final LoanApplicationTerms loanApplicationTerms, final LoanScheduleModel loanScheduleModel, final ExternalId externalId, final LoanApplicationTerms loanApplicationTerms, final Boolean enableInstallmentLevelDelinquency,
final Boolean enableInstallmentLevelDelinquency, final LocalDate submittedOnDate) { final LocalDate submittedOnDate) {
this.loanRepaymentScheduleDetail = loanRepaymentScheduleDetail; this.loanRepaymentScheduleDetail = loanRepaymentScheduleDetail;
this.isFloatingInterestRate = isFloatingInterestRate; this.isFloatingInterestRate = isFloatingInterestRate;
@@ -850,17 +848,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
this.postDatedChecks = new ArrayList<>(); this.postDatedChecks = new ArrayList<>();
} }
public List<LoanTransaction> retrieveListOfTransactionsExcludeAccruals() {
final List<LoanTransaction> repaymentsOrWaivers = new ArrayList<>();
for (final LoanTransaction transaction : this.loanTransactions) {
if (transaction.isNotReversed() && !transaction.isNonMonetaryTransaction()) {
repaymentsOrWaivers.add(transaction);
}
}
repaymentsOrWaivers.sort(LoanTransactionComparator.INSTANCE);
return repaymentsOrWaivers;
}
public List<LoanTransaction> retrieveListOfTransactionsByType(final LoanTransactionType transactionType) { public List<LoanTransaction> retrieveListOfTransactionsByType(final LoanTransactionType transactionType) {
return this.loanTransactions.stream() return this.loanTransactions.stream()
.filter(transaction -> transaction.isNotReversed() && transaction.getTypeOf().equals(transactionType)) .filter(transaction -> transaction.isNotReversed() && transaction.getTypeOf().equals(transactionType))
@@ -880,11 +867,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
.orElse(null); .orElse(null);
} }
public Money calculateTotalRecoveredPayments() {
// in case logic for reversing recovered payment is implemented handle subtraction from totalRecoveredPayments
return getTotalRecoveredPayments();
}
public MonetaryCurrency loanCurrency() { public MonetaryCurrency loanCurrency() {
return this.loanRepaymentScheduleDetail.getCurrency(); return this.loanRepaymentScheduleDetail.getCurrency();
} }
@@ -1033,17 +1015,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
return cumulativePaid; return cumulativePaid;
} }
public Money getTotalRecoveredPayments() {
Money cumulativePaid = Money.zero(getCurrency());
for (final LoanTransaction recoveredPayment : this.loanTransactions) {
if (recoveredPayment.isRecoveryRepayment()) {
cumulativePaid = cumulativePaid.plus(recoveredPayment.getAmount(getCurrency()));
}
}
return cumulativePaid;
}
public Money getTotalPrincipalOutstandingUntil(LocalDate date) { public Money getTotalPrincipalOutstandingUntil(LocalDate date) {
return getRepaymentScheduleInstallments().stream() return getRepaymentScheduleInstallments().stream()
.filter(installment -> installment.getDueDate().isBefore(date) || installment.getDueDate().isEqual(date)) .filter(installment -> installment.getDueDate().isBefore(date) || installment.getDueDate().isEqual(date))
@@ -1236,7 +1207,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
.filter(date -> DateUtils.isBefore(getDisbursementDate(), date)).max(LocalDate::compareTo).orElse(getDisbursementDate()); .filter(date -> DateUtils.isBefore(getDisbursementDate(), date)).max(LocalDate::compareTo).orElse(getDisbursementDate());
} }
private boolean isUserTransaction(LoanTransaction transaction) { public boolean isUserTransaction(LoanTransaction transaction) {
return !(transaction.isReversed() || transaction.isAccrualRelated() || transaction.isIncomePosting()); return !(transaction.isReversed() || transaction.isAccrualRelated() || transaction.isIncomePosting());
} }
@@ -1391,47 +1362,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
return recalculatedOn; return recalculatedOn;
} }
public void updateLoanOutstandingBalances() {
Money outstanding = Money.zero(getCurrency());
List<LoanTransaction> loanTransactions = retrieveListOfTransactionsExcludeAccruals();
for (LoanTransaction loanTransaction : loanTransactions) {
if (loanTransaction.isDisbursement() || loanTransaction.isIncomePosting() || loanTransaction.isCapitalizedIncome()) {
outstanding = outstanding.plus(loanTransaction.getAmount(getCurrency()))
.minus(loanTransaction.getOverPaymentPortion(getCurrency()));
loanTransaction.updateOutstandingLoanBalance(MathUtil.negativeToZero(outstanding.getAmount()));
} else if (loanTransaction.isChargeback() || loanTransaction.isCreditBalanceRefund()) {
Money transactionOutstanding = loanTransaction.getPrincipalPortion(getCurrency());
if (loanTransaction.isOverPaid()) {
// in case of advanced payment strategy and creditAllocations the full amount is recognized first
if (this.getCreditAllocationRules() != null && !this.getCreditAllocationRules().isEmpty()) {
Money payedPrincipal = loanTransaction.getLoanTransactionToRepaymentScheduleMappings().stream() //
.map(mapping -> mapping.getPrincipalPortion(getCurrency())) //
.reduce(Money.zero(getCurrency()), Money::plus);
transactionOutstanding = loanTransaction.getPrincipalPortion(getCurrency()).minus(payedPrincipal);
} else {
// in case legacy payment strategy
transactionOutstanding = loanTransaction.getAmount(getCurrency())
.minus(loanTransaction.getOverPaymentPortion(getCurrency()));
}
if (transactionOutstanding.isLessThanZero()) {
transactionOutstanding = Money.zero(getCurrency());
}
}
outstanding = outstanding.plus(transactionOutstanding);
loanTransaction.updateOutstandingLoanBalance(MathUtil.negativeToZero(outstanding.getAmount()));
} else if (!loanTransaction.isAccrualActivity()) {
if (this.loanInterestRecalculationDetails != null
&& this.loanInterestRecalculationDetails.isCompoundingToBePostedAsTransaction()
&& !loanTransaction.isRepaymentAtDisbursement()) {
outstanding = outstanding.minus(loanTransaction.getAmount(getCurrency()));
} else {
outstanding = outstanding.minus(loanTransaction.getPrincipalPortion(getCurrency()));
}
loanTransaction.updateOutstandingLoanBalance(MathUtil.negativeToZero(outstanding.getAmount()));
}
}
}
public String transactionProcessingStrategy() { public String transactionProcessingStrategy() {
return this.transactionProcessingStrategyCode; return this.transactionProcessingStrategyCode;
} }
@@ -911,10 +911,6 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
|| this.isIncomePosting()); || this.isIncomePosting());
} }
public boolean hasLoanTransactionRelations() {
return !loanTransactionRelations.isEmpty();
}
public List<LoanTransactionRelation> getLoanTransactionRelations(Predicate<LoanTransactionRelation> predicate) { public List<LoanTransactionRelation> getLoanTransactionRelations(Predicate<LoanTransactionRelation> predicate) {
return loanTransactionRelations.stream().filter(predicate).toList(); return loanTransactionRelations.stream().filter(predicate).toList();
} }
@@ -127,7 +127,8 @@ public interface LoanTransactionRepository extends JpaRepository<LoanTransaction
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.INITIATE_TRANSFER, org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.INITIATE_TRANSFER,
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.REJECT_TRANSFER, org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.REJECT_TRANSFER,
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.WITHDRAW_TRANSFER, org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.WITHDRAW_TRANSFER,
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION,
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION_ADJUSTMENT
) )
""") """)
Optional<LocalDate> findLastTransactionDateForReprocessing(@Param("loan") Loan loan); Optional<LocalDate> findLastTransactionDateForReprocessing(@Param("loan") Loan loan);
@@ -174,7 +175,7 @@ public interface LoanTransactionRepository extends JpaRepository<LoanTransaction
AND lt.typeOf IN :types AND lt.typeOf IN :types
AND lt.dateOf = :transactionDate AND lt.dateOf = :transactionDate
""") """)
Optional<LoanTransaction> findNonReversedByLoanAndTypesAndDate(@Param("loan") Loan loan, @Param("type") Set<LoanTransactionType> types, Optional<LoanTransaction> findNonReversedByLoanAndTypesAndDate(@Param("loan") Loan loan, @Param("types") Set<LoanTransactionType> types,
@Param("transactionDate") LocalDate transactionDate); @Param("transactionDate") LocalDate transactionDate);
@Query(""" @Query("""
@@ -184,7 +185,7 @@ public interface LoanTransactionRepository extends JpaRepository<LoanTransaction
AND lt.reversed = false AND lt.reversed = false
AND lt.typeOf IN :types AND lt.typeOf IN :types
AND lt.dateOf > :transactionDate AND lt.dateOf > :transactionDate
ORDER BY lt.dateOf ORDER BY lt.dateOf, lt.createdDate, lt.id
""") """)
List<LoanTransaction> findNonReversedByLoanAndTypesAndAfterDate(@Param("loan") Loan loan, List<LoanTransaction> findNonReversedByLoanAndTypesAndAfterDate(@Param("loan") Loan loan,
@Param("types") Set<LoanTransactionType> types, @Param("transactionDate") LocalDate transactionDate); @Param("types") Set<LoanTransactionType> types, @Param("transactionDate") LocalDate transactionDate);
@@ -196,7 +197,7 @@ public interface LoanTransactionRepository extends JpaRepository<LoanTransaction
AND lt.reversed = false AND lt.reversed = false
AND lt.typeOf = :type AND lt.typeOf = :type
AND lt.dateOf > :transactionDate AND lt.dateOf > :transactionDate
ORDER BY lt.dateOf ORDER BY lt.dateOf, lt.createdDate, lt.id
""") """)
List<LoanTransaction> findNonReversedByLoanAndTypeAndAfterDate(@Param("loan") Loan loan, @Param("type") LoanTransactionType type, List<LoanTransaction> findNonReversedByLoanAndTypeAndAfterDate(@Param("loan") Loan loan, @Param("type") LoanTransactionType type,
@Param("transactionDate") LocalDate transactionDate); @Param("transactionDate") LocalDate transactionDate);
@@ -219,6 +220,7 @@ public interface LoanTransactionRepository extends JpaRepository<LoanTransaction
AND lt.reversed = false AND lt.reversed = false
AND lt.typeOf IN :types AND lt.typeOf IN :types
AND lt.id NOT IN :existingTransactionIds AND lt.id NOT IN :existingTransactionIds
ORDER BY lt.dateOf, lt.createdDate, lt.id
""") """)
List<LoanTransaction> findNonReversedByLoanAndTypesAndNotInIds(@Param("loan") Loan loan, @Param("types") Set<LoanTransactionType> types, List<LoanTransaction> findNonReversedByLoanAndTypesAndNotInIds(@Param("loan") Loan loan, @Param("types") Set<LoanTransactionType> types,
@Param("existingTransactionIds") List<Long> existingTransactionIds); @Param("existingTransactionIds") List<Long> existingTransactionIds);
@@ -229,6 +231,7 @@ public interface LoanTransactionRepository extends JpaRepository<LoanTransaction
WHERE lt.loan = :loan WHERE lt.loan = :loan
AND lt.reversed = false AND lt.reversed = false
AND lt.typeOf IN :types AND lt.typeOf IN :types
ORDER BY lt.dateOf, lt.createdDate, lt.id
""") """)
List<LoanTransaction> findNonReversedByLoanAndTypes(@Param("loan") Loan loan, @Param("types") Set<LoanTransactionType> types); List<LoanTransaction> findNonReversedByLoanAndTypes(@Param("loan") Loan loan, @Param("types") Set<LoanTransactionType> types);
@@ -238,6 +241,7 @@ public interface LoanTransactionRepository extends JpaRepository<LoanTransaction
WHERE lt.loan = :loan WHERE lt.loan = :loan
AND lt.reversed = false AND lt.reversed = false
AND lt.typeOf = :type AND lt.typeOf = :type
ORDER BY lt.dateOf, lt.createdDate, lt.id
""") """)
List<LoanTransaction> findNonReversedByLoanAndType(@Param("loan") Loan loan, @Param("type") LoanTransactionType type); List<LoanTransaction> findNonReversedByLoanAndType(@Param("loan") Loan loan, @Param("type") LoanTransactionType type);
@@ -248,7 +252,7 @@ public interface LoanTransactionRepository extends JpaRepository<LoanTransaction
AND lt.reversed = false AND lt.reversed = false
AND lt.dateOf >= :date AND lt.dateOf >= :date
AND lt.typeOf IN :types AND lt.typeOf IN :types
ORDER BY lt.dateOf ORDER BY lt.dateOf, lt.createdDate, lt.id
""") """)
List<LoanTransaction> findNonReversedByLoanAndTypesAndOnOrAfterDate(@Param("loan") Loan loan, List<LoanTransaction> findNonReversedByLoanAndTypesAndOnOrAfterDate(@Param("loan") Loan loan,
@Param("types") Set<LoanTransactionType> types, @Param("date") LocalDate date); @Param("types") Set<LoanTransactionType> types, @Param("date") LocalDate date);
@@ -333,7 +337,7 @@ public interface LoanTransactionRepository extends JpaRepository<LoanTransaction
AND lt.reversed = false AND lt.reversed = false
AND lt.typeOf = :type AND lt.typeOf = :type
AND lt.dateOf IN :transactionDates AND lt.dateOf IN :transactionDates
ORDER BY lt.id ORDER BY lt.dateOf, lt.createdDate, lt.id
""") """)
List<LoanTransaction> findNonReversedLoanAndTypeAndDates(@Param("loan") Loan loan, @Param("type") LoanTransactionType type, List<LoanTransaction> findNonReversedLoanAndTypeAndDates(@Param("loan") Loan loan, @Param("type") LoanTransactionType type,
@Param("transactionDates") Set<LocalDate> transactionDates); @Param("transactionDates") Set<LocalDate> transactionDates);
@@ -367,4 +371,90 @@ public interface LoanTransactionRepository extends JpaRepository<LoanTransaction
AND ltr.relationType = org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum.ADJUSTMENT AND ltr.relationType = org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum.ADJUSTMENT
""") """)
List<LoanTransaction> findAdjustmentsForCapitalizedIncome(@Param("capitalizedIncome") LoanTransaction capitalizedIncome); List<LoanTransaction> findAdjustmentsForCapitalizedIncome(@Param("capitalizedIncome") LoanTransaction capitalizedIncome);
@Query("""
SELECT lt FROM LoanTransaction lt
WHERE lt.loan = :loan
AND lt.reversed = false
AND lt.typeOf NOT IN (
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.CONTRA,
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.MARKED_FOR_RESCHEDULING,
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.ACCRUAL,
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.ACCRUAL_ADJUSTMENT,
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.APPROVE_TRANSFER,
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.INITIATE_TRANSFER,
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.REJECT_TRANSFER,
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.WITHDRAW_TRANSFER,
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION,
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION_ADJUSTMENT
)
ORDER BY lt.dateOf, lt.createdDate, lt.id
""")
List<LoanTransaction> findNonReversedTransactionsForReprocessingByLoan(@Param("loan") Loan loan);
@Query("""
SELECT MAX(lt.dateOf) FROM LoanTransaction lt
WHERE lt.loan = :loan
AND lt.reversed = false
AND lt.amount > 0
AND lt.typeOf IN (
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.REPAYMENT,
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.MERCHANT_ISSUED_REFUND,
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.PAYOUT_REFUND,
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.GOODWILL_CREDIT,
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.CHARGE_REFUND,
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.CHARGE_ADJUSTMENT,
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.DOWN_PAYMENT,
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.INTEREST_PAYMENT_WAIVER,
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.INTEREST_REFUND,
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.CAPITALIZED_INCOME_ADJUSTMENT
)
""")
Optional<LocalDate> findLastRepaymentLikeTransactionDate(@Param("loan") Loan loan);
@Query("""
SELECT CASE WHEN COUNT(lt) > 0 THEN true ELSE false END
FROM LoanTransaction lt
WHERE lt.loan = :loan
AND lt.reversed = false
AND lt.typeOf = :type
AND lt.dateOf > :transactionDate
""")
boolean existsNonReversedByLoanAndTypeAndAfterDate(@Param("loan") Loan loan, @Param("type") LoanTransactionType type,
@Param("transactionDate") LocalDate transactionDate);
@Query("""
SELECT lt FROM LoanTransaction lt
WHERE lt.loan = :loan
AND lt.reversed = false
AND lt.typeOf NOT IN (
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.CONTRA,
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.MARKED_FOR_RESCHEDULING,
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.ACCRUAL,
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.ACCRUAL_ADJUSTMENT,
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.ACCRUAL_ACTIVITY,
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.APPROVE_TRANSFER,
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.INITIATE_TRANSFER,
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.REJECT_TRANSFER,
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.WITHDRAW_TRANSFER,
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.CHARGE_OFF,
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.REAMORTIZE,
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.REAGE,
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION,
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.CONTRACT_TERMINATION,
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION_ADJUSTMENT
)
ORDER BY lt.dateOf, lt.createdDate, lt.id
""")
List<LoanTransaction> findNonReversedMonetaryTransactionsByLoan(@Param("loan") Loan loan);
@Query("""
SELECT COALESCE(SUM(lt.amount), 0)
FROM LoanTransaction lt
WHERE lt.loan = :loan
AND lt.reversed = false
AND lt.typeOf = org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.RECOVERY_REPAYMENT
""")
BigDecimal calculateTotalRecoveryPaymentAmount(@Param("loan") Loan loan);
} }
@@ -37,7 +37,6 @@ public class SingleLoanChargeRepaymentScheduleProcessingWrapper {
public void reprocess(final MonetaryCurrency currency, final LocalDate disbursementDate, public void reprocess(final MonetaryCurrency currency, final LocalDate disbursementDate,
final List<LoanRepaymentScheduleInstallment> installments, LoanCharge loanCharge) { final List<LoanRepaymentScheduleInstallment> installments, LoanCharge loanCharge) {
Loan loan = loanCharge.getLoan();
Money zero = Money.zero(currency); Money zero = Money.zero(currency);
Money totalInterest = zero; Money totalInterest = zero;
Money totalPrincipal = zero; Money totalPrincipal = zero;
@@ -217,11 +217,9 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
loanTransaction.resetDerivedComponents(); loanTransaction.resetDerivedComponents();
handleRefund(loanTransaction, currency, installments, charges); handleRefund(loanTransaction, currency, installments, charges);
} else if (loanTransaction.isCreditBalanceRefund()) { } else if (loanTransaction.isCreditBalanceRefund()) {
recalculateCreditTransaction(changedTransactionDetail, loanTransaction, currency, installments, transactionsToBeProcessed, recalculateCreditTransaction(changedTransactionDetail, loanTransaction, currency, installments, overpaymentHolder);
overpaymentHolder);
} else if (loanTransaction.isChargeback()) { } else if (loanTransaction.isChargeback()) {
recalculateCreditTransaction(changedTransactionDetail, loanTransaction, currency, installments, transactionsToBeProcessed, recalculateCreditTransaction(changedTransactionDetail, loanTransaction, currency, installments, overpaymentHolder);
overpaymentHolder);
reprocessChargebackTransactionRelation(changedTransactionDetail, transactionsToBeProcessed); reprocessChargebackTransactionRelation(changedTransactionDetail, transactionsToBeProcessed);
} else if (loanTransaction.isChargeOff()) { } else if (loanTransaction.isChargeOff()) {
recalculateChargeOffTransaction(changedTransactionDetail, loanTransaction, currency, installments); recalculateChargeOffTransaction(changedTransactionDetail, loanTransaction, currency, installments);
@@ -475,7 +473,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
protected void reprocessInstallments(LocalDate disbursementDate, List<LoanTransaction> transactions, protected void reprocessInstallments(LocalDate disbursementDate, List<LoanTransaction> transactions,
List<LoanRepaymentScheduleInstallment> installments, MonetaryCurrency currency) { List<LoanRepaymentScheduleInstallment> installments, MonetaryCurrency currency) {
LoanRepaymentScheduleInstallment lastInstallment = installments.get(installments.size() - 1); LoanRepaymentScheduleInstallment lastInstallment = installments.getLast();
if (lastInstallment.isAdditional() && lastInstallment.getDue(currency).isZero()) { if (lastInstallment.isAdditional() && lastInstallment.getDue(currency).isZero()) {
installments.remove(lastInstallment); installments.remove(lastInstallment);
} }
@@ -519,8 +517,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
} }
private void recalculateCreditTransaction(ChangedTransactionDetail changedTransactionDetail, LoanTransaction loanTransaction, private void recalculateCreditTransaction(ChangedTransactionDetail changedTransactionDetail, LoanTransaction loanTransaction,
MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments, List<LoanTransaction> transactionsToBeProcessed, MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments, MoneyHolder overpaymentHolder) {
MoneyHolder overpaymentHolder) {
// pass through for new transactions // pass through for new transactions
if (loanTransaction.getId() == null) { if (loanTransaction.getId() == null) {
return; return;
@@ -606,7 +603,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
// New installment will be added (N+1 scenario) // New installment will be added (N+1 scenario)
if (!loanTransactionMapped) { if (!loanTransactionMapped) {
if (loanTransaction.getTransactionDate().equals(pastDueDate)) { if (loanTransaction.getTransactionDate().equals(pastDueDate)) {
LoanRepaymentScheduleInstallment currentInstallment = installmentToBeProcessed.get(installmentToBeProcessed.size() - 1); LoanRepaymentScheduleInstallment currentInstallment = installmentToBeProcessed.getLast();
currentInstallment.addToCreditedPrincipal(transactionAmount.getAmount()); currentInstallment.addToCreditedPrincipal(transactionAmount.getAmount());
currentInstallment.addToPrincipal(transactionDate, transactionAmount); currentInstallment.addToPrincipal(transactionDate, transactionAmount);
if (repaidAmount.isGreaterThanZero()) { if (repaidAmount.isGreaterThanZero()) {
@@ -647,7 +644,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
final Set<LoanCharge> loanPenalties = extractPenaltyCharges(charges); final Set<LoanCharge> loanPenalties = extractPenaltyCharges(charges);
Integer installmentNumber = null; Integer installmentNumber = null;
if (loanTransaction.isChargePayment() && installments.size() == 1) { if (loanTransaction.isChargePayment() && installments.size() == 1) {
installmentNumber = installments.get(0).getInstallmentNumber(); installmentNumber = installments.getFirst().getInstallmentNumber();
} }
if (loanTransaction.isNotWaiver() && !loanTransaction.isAccrual() && !loanTransaction.isAccrualActivity()) { if (loanTransaction.isNotWaiver() && !loanTransaction.isAccrual() && !loanTransaction.isAccrualActivity()) {
@@ -944,12 +941,12 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
return null; return null;
} }
LoanCharge latestCharge = null; LoanCharge latestCharge = null;
List<LoanCharge> chargesWithSpecificDueDate = new ArrayList<>(); final List<LoanCharge> chargesWithSpecificDueDate = new ArrayList<>(
chargesWithSpecificDueDate.addAll(charges.stream().filter(charge -> charge.isSpecifiedDueDate()).toList()); charges.stream().filter(LoanCharge::isSpecifiedDueDate).toList());
if (!CollectionUtils.isEmpty(chargesWithSpecificDueDate)) { if (!CollectionUtils.isEmpty(chargesWithSpecificDueDate)) {
Collections.sort(chargesWithSpecificDueDate, chargesWithSpecificDueDate
(charge1, charge2) -> DateUtils.compare(charge1.getEffectiveDueDate(), charge2.getEffectiveDueDate())); .sort((charge1, charge2) -> DateUtils.compare(charge1.getEffectiveDueDate(), charge2.getEffectiveDueDate()));
latestCharge = chargesWithSpecificDueDate.get(chargesWithSpecificDueDate.size() - 1); latestCharge = chargesWithSpecificDueDate.getLast();
} }
return latestCharge; return latestCharge;
} }
@@ -56,6 +56,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalcualtionAdditionalDetails; import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalcualtionAdditionalDetails;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleModelDownPaymentPeriod; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleModelDownPaymentPeriod;
@@ -63,13 +64,12 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleP
import org.apache.fineract.portfolio.loanaccount.loanschedule.exception.MultiDisbursementEmiAmountException; import org.apache.fineract.portfolio.loanaccount.loanschedule.exception.MultiDisbursementEmiAmountException;
import org.apache.fineract.portfolio.loanaccount.loanschedule.exception.MultiDisbursementOutstandingAmoutException; import org.apache.fineract.portfolio.loanaccount.loanschedule.exception.MultiDisbursementOutstandingAmoutException;
import org.apache.fineract.portfolio.loanaccount.loanschedule.exception.ScheduleDateException; import org.apache.fineract.portfolio.loanaccount.loanschedule.exception.ScheduleDateException;
import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionService;
import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType; import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType;
@RequiredArgsConstructor @RequiredArgsConstructor
public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanScheduleGenerator { public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanScheduleGenerator {
private final LoanTransactionService loanTransactionService; private final LoanTransactionRepository loanTransactionRepository;
private final CurrencyMapper currencyMapper; private final CurrencyMapper currencyMapper;
@Override @Override
@@ -404,7 +404,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
// this condition is to add the interest from grace period if not // this condition is to add the interest from grace period if not
// already applied. // already applied.
if (scheduleParams.getTotalOutstandingInterestPaymentDueToGrace().isGreaterThanZero()) { if (scheduleParams.getTotalOutstandingInterestPaymentDueToGrace().isGreaterThanZero()) {
LoanScheduleModelPeriod installment = periods.get(periods.size() - 1); LoanScheduleModelPeriod installment = periods.getLast();
installment.addInterestAmount(scheduleParams.getTotalOutstandingInterestPaymentDueToGrace()); installment.addInterestAmount(scheduleParams.getTotalOutstandingInterestPaymentDueToGrace());
scheduleParams.addTotalRepaymentExpected(scheduleParams.getTotalOutstandingInterestPaymentDueToGrace()); scheduleParams.addTotalRepaymentExpected(scheduleParams.getTotalOutstandingInterestPaymentDueToGrace());
scheduleParams.addTotalCumulativeInterest(scheduleParams.getTotalOutstandingInterestPaymentDueToGrace()); scheduleParams.addTotalCumulativeInterest(scheduleParams.getTotalOutstandingInterestPaymentDueToGrace());
@@ -421,7 +421,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
if (scheduleParams.getScheduleTillDate() != null) { if (scheduleParams.getScheduleTillDate() != null) {
currentDate = scheduleParams.getScheduleTillDate(); currentDate = scheduleParams.getScheduleTillDate();
} }
if (scheduleParams.applyInterestRecalculation() && scheduleParams.getLatePaymentMap().size() > 0 if (scheduleParams.applyInterestRecalculation() && !scheduleParams.getLatePaymentMap().isEmpty()
&& DateUtils.isAfter(currentDate, scheduleParams.getPeriodStartDate())) { && DateUtils.isAfter(currentDate, scheduleParams.getPeriodStartDate())) {
Money totalInterest = addInterestOnlyRepaymentScheduleForCurrentDate(mc, loanApplicationTerms, holidayDetailDTO, Money totalInterest = addInterestOnlyRepaymentScheduleForCurrentDate(mc, loanApplicationTerms, holidayDetailDTO,
monetaryCurrency, periods, currentDate, loanRepaymentScheduleTransactionProcessor, transactions, loanCharges, monetaryCurrency, periods, currentDate, loanRepaymentScheduleTransactionProcessor, transactions, loanCharges,
@@ -1345,9 +1345,8 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
return principalToBeScheduled.minus(loanApplicationTerms.getDownPaymentAmount()); return principalToBeScheduled.minus(loanApplicationTerms.getDownPaymentAmount());
} }
private boolean updateFixedInstallmentAmount(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, int periodNumber, private void updateFixedInstallmentAmount(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, int periodNumber,
Money outstandingBalance) { Money outstandingBalance) {
boolean isAmountChanged = false;
if (loanApplicationTerms.getActualFixedEmiAmount() == null && loanApplicationTerms.getInterestMethod().isDecliningBalance() if (loanApplicationTerms.getActualFixedEmiAmount() == null && loanApplicationTerms.getInterestMethod().isDecliningBalance()
&& loanApplicationTerms.getAmortizationMethod().isEqualInstallment()) { && loanApplicationTerms.getAmortizationMethod().isEqualInstallment()) {
if (periodNumber < loanApplicationTerms.getPrincipalGrace() + 1) { if (periodNumber < loanApplicationTerms.getPrincipalGrace() + 1) {
@@ -1356,9 +1355,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
Money emiAmount = loanApplicationTerms.pmtForInstallment(getPaymentPeriodsInOneYearCalculator(), outstandingBalance, Money emiAmount = loanApplicationTerms.pmtForInstallment(getPaymentPeriodsInOneYearCalculator(), outstandingBalance,
periodNumber, mc); periodNumber, mc);
loanApplicationTerms.setFixedEmiAmount(emiAmount.getAmount()); loanApplicationTerms.setFixedEmiAmount(emiAmount.getAmount());
isAmountChanged = true;
} }
return isAmountChanged;
} }
private Money fetchArrears(final LoanApplicationTerms loanApplicationTerms, final MonetaryCurrency currency, private Money fetchArrears(final LoanApplicationTerms loanApplicationTerms, final MonetaryCurrency currency,
@@ -1984,7 +1981,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
// this method relates to multi-disbursement loans // this method relates to multi-disbursement loans
BigDecimal principal = BigDecimal.ZERO; BigDecimal principal = BigDecimal.ZERO;
if (loanApplicationTerms.getDisbursementDatas().size() == 0) { if (loanApplicationTerms.getDisbursementDatas().isEmpty()) {
// non tranche loans have no disbursement data entries in submitted and approved status // non tranche loans have no disbursement data entries in submitted and approved status
// the appropriate approved amount or applied for amount is used to show a proposed schedule // the appropriate approved amount or applied for amount is used to show a proposed schedule
if (loanApplicationTerms.getApprovedPrincipal().getAmount().compareTo(BigDecimal.ZERO) > 0) { if (loanApplicationTerms.getApprovedPrincipal().getAmount().compareTo(BigDecimal.ZERO) > 0) {
@@ -2521,10 +2518,9 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
} }
if (retainedInstallments.size() > 0 if (!retainedInstallments.isEmpty() && retainedInstallments.getLast().getRescheduleInterestPortion() != null) {
&& retainedInstallments.get(retainedInstallments.size() - 1).getRescheduleInterestPortion() != null) {
loanApplicationTerms.setInterestTobeApproppriated( loanApplicationTerms.setInterestTobeApproppriated(
Money.of(loan.getCurrency(), retainedInstallments.get(retainedInstallments.size() - 1).getRescheduleInterestPortion())); Money.of(loan.getCurrency(), retainedInstallments.getLast().getRescheduleInterestPortion()));
} }
LoanScheduleModel loanScheduleModel = generate(mc, loanApplicationTerms, loan.getActiveCharges(), holidayDetailDTO, LoanScheduleModel loanScheduleModel = generate(mc, loanApplicationTerms, loan.getActiveCharges(), holidayDetailDTO,
loanScheduleParams); loanScheduleParams);
@@ -2723,19 +2719,18 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
return principalPortionCalculated; return principalPortionCalculated;
} }
private LoanRepaymentScheduleInstallment addLoanRepaymentScheduleInstallment(final List<LoanRepaymentScheduleInstallment> installments, private void addLoanRepaymentScheduleInstallment(final List<LoanRepaymentScheduleInstallment> installments,
final LoanScheduleModelPeriod scheduledLoanInstallment) { final LoanScheduleModelPeriod scheduledLoanInstallment) {
LoanRepaymentScheduleInstallment installment = null;
if (scheduledLoanInstallment.isRepaymentPeriod() || scheduledLoanInstallment.isDownPaymentPeriod()) { if (scheduledLoanInstallment.isRepaymentPeriod() || scheduledLoanInstallment.isDownPaymentPeriod()) {
installment = new LoanRepaymentScheduleInstallment(null, scheduledLoanInstallment.periodNumber(), final LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(null,
scheduledLoanInstallment.periodFromDate(), scheduledLoanInstallment.periodDueDate(), scheduledLoanInstallment.periodNumber(), scheduledLoanInstallment.periodFromDate(),
scheduledLoanInstallment.principalDue(), scheduledLoanInstallment.interestDue(), scheduledLoanInstallment.periodDueDate(), scheduledLoanInstallment.principalDue(),
scheduledLoanInstallment.feeChargesDue(), scheduledLoanInstallment.penaltyChargesDue(), scheduledLoanInstallment.interestDue(), scheduledLoanInstallment.feeChargesDue(),
scheduledLoanInstallment.isRecalculatedInterestComponent(), scheduledLoanInstallment.getLoanCompoundingDetails(), scheduledLoanInstallment.penaltyChargesDue(), scheduledLoanInstallment.isRecalculatedInterestComponent(),
scheduledLoanInstallment.rescheduleInterestPortion(), scheduledLoanInstallment.isDownPaymentPeriod()); scheduledLoanInstallment.getLoanCompoundingDetails(), scheduledLoanInstallment.rescheduleInterestPortion(),
scheduledLoanInstallment.isDownPaymentPeriod());
installments.add(installment); installments.add(installment);
} }
return installment;
} }
private LoanScheduleModelPeriod createLoanScheduleModelDownPaymentPeriod(final LoanRepaymentScheduleInstallment installment, private LoanScheduleModelPeriod createLoanScheduleModelDownPaymentPeriod(final LoanRepaymentScheduleInstallment installment,
@@ -2804,7 +2799,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
LoanScheduleDTO loanScheduleDTO = rescheduleNextInstallments(mc, loanApplicationTerms, loan, holidayDetailDTO, LoanScheduleDTO loanScheduleDTO = rescheduleNextInstallments(mc, loanApplicationTerms, loan, holidayDetailDTO,
loanRepaymentScheduleTransactionProcessor, onDate, calculateTill); loanRepaymentScheduleTransactionProcessor, onDate, calculateTill);
final List<LoanTransaction> loanTransactions = loanTransactionService.retrieveListOfTransactionsForReprocessing(loan); final List<LoanTransaction> loanTransactions = loanTransactionRepository.findNonReversedTransactionsForReprocessingByLoan(loan);
loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(loanApplicationTerms.getExpectedDisbursementDate(), loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(loanApplicationTerms.getExpectedDisbursementDate(),
loanTransactions, currency, loanScheduleDTO.getInstallments(), loan.getActiveCharges()); loanTransactions, currency, loanScheduleDTO.getInstallments(), loan.getActiveCharges());
@@ -29,7 +29,7 @@ import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.organisation.monetary.mapper.CurrencyMapper; import org.apache.fineract.organisation.monetary.mapper.CurrencyMapper;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionService; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod; import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -63,9 +63,9 @@ public class CumulativeDecliningBalanceInterestLoanScheduleGenerator extends Abs
private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator; private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator;
public CumulativeDecliningBalanceInterestLoanScheduleGenerator(final ScheduledDateGenerator scheduledDateGenerator, public CumulativeDecliningBalanceInterestLoanScheduleGenerator(final ScheduledDateGenerator scheduledDateGenerator,
final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator, final LoanTransactionService loanTransactionService, final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator,
final CurrencyMapper currencyMapper) { final LoanTransactionRepository loanTransactionRepository, final CurrencyMapper currencyMapper) {
super(loanTransactionService, currencyMapper); super(loanTransactionRepository, currencyMapper);
this.scheduledDateGenerator = scheduledDateGenerator; this.scheduledDateGenerator = scheduledDateGenerator;
this.paymentPeriodsInOneYearCalculator = paymentPeriodsInOneYearCalculator; this.paymentPeriodsInOneYearCalculator = paymentPeriodsInOneYearCalculator;
} }
@@ -27,7 +27,7 @@ import java.util.TreeMap;
import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.organisation.monetary.mapper.CurrencyMapper; import org.apache.fineract.organisation.monetary.mapper.CurrencyMapper;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionService; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Component @Component
@@ -37,9 +37,9 @@ public class CumulativeFlatInterestLoanScheduleGenerator extends AbstractCumulat
private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator; private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator;
public CumulativeFlatInterestLoanScheduleGenerator(final ScheduledDateGenerator scheduledDateGenerator, public CumulativeFlatInterestLoanScheduleGenerator(final ScheduledDateGenerator scheduledDateGenerator,
final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator, final LoanTransactionService loanTransactionService, final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator,
final CurrencyMapper currencyMapper) { final LoanTransactionRepository loanTransactionRepository, final CurrencyMapper currencyMapper) {
super(loanTransactionService, currencyMapper); super(loanTransactionRepository, currencyMapper);
this.scheduledDateGenerator = scheduledDateGenerator; this.scheduledDateGenerator = scheduledDateGenerator;
this.paymentPeriodsInOneYearCalculator = paymentPeriodsInOneYearCalculator; this.paymentPeriodsInOneYearCalculator = paymentPeriodsInOneYearCalculator;
} }
@@ -19,18 +19,24 @@
package org.apache.fineract.portfolio.loanaccount.serialization; package org.apache.fineract.portfolio.loanaccount.serialization;
import java.time.LocalDate; import java.time.LocalDate;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.portfolio.charge.exception.LoanChargeCannotBeAddedException; import org.apache.fineract.portfolio.charge.exception.LoanChargeCannotBeAddedException;
import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanStateTransitionException; import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanStateTransitionException;
import org.apache.fineract.portfolio.loanaccount.exception.LoanChargeRefundException; import org.apache.fineract.portfolio.loanaccount.exception.LoanChargeRefundException;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Component @Component
@RequiredArgsConstructor
public final class LoanChargeValidator { public final class LoanChargeValidator {
private final LoanTransactionRepository loanTransactionRepository;
public void validateLoanIsNotClosed(final Loan loan, final LoanCharge loanCharge) { public void validateLoanIsNotClosed(final Loan loan, final LoanCharge loanCharge) {
if (loan.isClosed()) { if (loan.isClosed()) {
final String defaultUserMessage = "This charge cannot be added as the loan is already closed."; final String defaultUserMessage = "This charge cannot be added as the loan is already closed.";
@@ -80,13 +86,13 @@ public final class LoanChargeValidator {
public void validateRepaymentTypeTransactionNotBeforeAChargeRefund(final Loan loan, final LoanTransaction repaymentTransaction, public void validateRepaymentTypeTransactionNotBeforeAChargeRefund(final Loan loan, final LoanTransaction repaymentTransaction,
final String reversedOrCreated) { final String reversedOrCreated) {
if (repaymentTransaction.isRepaymentLikeType() && !repaymentTransaction.isChargeRefund()) { if (repaymentTransaction.isRepaymentLikeType() && !repaymentTransaction.isChargeRefund()) {
for (LoanTransaction txn : loan.getLoanTransactions()) { final boolean existsChargeRefund = loanTransactionRepository.existsNonReversedByLoanAndTypeAndAfterDate(loan,
if (txn.isChargeRefund() && DateUtils.isBefore(repaymentTransaction.getTransactionDate(), txn.getTransactionDate())) { LoanTransactionType.CHARGE_REFUND, repaymentTransaction.getTransactionDate());
final String errorMessage = "loan.transaction.cant.be." + reversedOrCreated + ".because.later.charge.refund.exists"; if (existsChargeRefund) {
final String details = "Loan Transaction: " + loan.getId() + " Can't be " + reversedOrCreated final String errorMessage = "loan.transaction.cant.be." + reversedOrCreated + ".because.later.charge.refund.exists";
+ " because a Later Charge Refund Exists."; final String details = "Loan Transaction: " + loan.getId() + " Can't be " + reversedOrCreated
throw new LoanChargeRefundException(errorMessage, details); + " because a Later Charge Refund Exists.";
} throw new LoanChargeRefundException(errorMessage, details);
} }
} }
} }
@@ -21,10 +21,12 @@ package org.apache.fineract.portfolio.loanaccount.service;
import jakarta.persistence.FlushModeType; import jakarta.persistence.FlushModeType;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.persistence.FlushModeHandler; import org.apache.fineract.infrastructure.core.persistence.FlushModeHandler;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.Loan;
@@ -34,6 +36,8 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanInstallmentCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionComparator;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.loanproduct.domain.CreditAllocationTransactionType; import org.apache.fineract.portfolio.loanproduct.domain.CreditAllocationTransactionType;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -43,6 +47,7 @@ public class LoanBalanceService {
private final CapitalizedIncomeBalanceService capitalizedIncomeBalanceService; private final CapitalizedIncomeBalanceService capitalizedIncomeBalanceService;
private final FlushModeHandler flushModeHandler; private final FlushModeHandler flushModeHandler;
private final LoanTransactionRepository loanTransactionRepository;
public Money calculateTotalOverpayment(final Loan loan) { public Money calculateTotalOverpayment(final Loan loan) {
Money totalPaidInRepayments = loan.getTotalPaidInRepayments(); Money totalPaidInRepayments = loan.getTotalPaidInRepayments();
@@ -107,7 +112,7 @@ public class LoanBalanceService {
loan.setTotalOverpaid(overpaidBy.getAmountDefaultedToNullIfZero()); loan.setTotalOverpaid(overpaidBy.getAmountDefaultedToNullIfZero());
} }
final Money recoveredAmount = loan.calculateTotalRecoveredPayments(); final Money recoveredAmount = calculateTotalRecoveredPayments(loan);
loan.setTotalRecovered(recoveredAmount.getAmountDefaultedToNullIfZero()); loan.setTotalRecovered(recoveredAmount.getAmountDefaultedToNullIfZero());
final Money principal = loan.getLoanRepaymentScheduleDetail().getPrincipal(); final Money principal = loan.getLoanRepaymentScheduleDetail().getPrincipal();
@@ -115,7 +120,61 @@ public class LoanBalanceService {
final Money capitalizedIncomeAdjustment = capitalizedIncomeBalanceService.calculateCapitalizedIncomeAdjustment(loan); final Money capitalizedIncomeAdjustment = capitalizedIncomeBalanceService.calculateCapitalizedIncomeAdjustment(loan);
loan.getSummary().updateSummary(loan.getCurrency(), principal, loan.getRepaymentScheduleInstallments(), loan.getLoanCharges(), loan.getSummary().updateSummary(loan.getCurrency(), principal, loan.getRepaymentScheduleInstallments(), loan.getLoanCharges(),
capitalizedIncome, capitalizedIncomeAdjustment); capitalizedIncome, capitalizedIncomeAdjustment);
loan.updateLoanOutstandingBalances(); updateLoanOutstandingBalances(loan);
}
private Money calculateTotalRecoveredPayments(Loan loan) {
// in case logic for reversing recovered payment is implemented handle subtraction from totalRecoveredPayments
final BigDecimal totalRecoveryAmount = loanTransactionRepository.calculateTotalRecoveryPaymentAmount(loan);
return Money.of(loan.getCurrency(), totalRecoveryAmount);
}
public void updateLoanOutstandingBalances(Loan loan) {
Money outstanding = Money.zero(loan.getCurrency());
final List<LoanTransaction> loanTransactions = new ArrayList<>();
for (final LoanTransaction transaction : loan.getLoanTransactions()) {
if (transaction.isNotReversed() && !transaction.isNonMonetaryTransaction()) {
loanTransactions.add(transaction);
}
}
loanTransactions.sort(LoanTransactionComparator.INSTANCE);
for (LoanTransaction loanTransaction : loanTransactions) {
if (loanTransaction.isDisbursement() || loanTransaction.isIncomePosting() || loanTransaction.isCapitalizedIncome()) {
outstanding = outstanding.plus(loanTransaction.getAmount(loan.getCurrency()))
.minus(loanTransaction.getOverPaymentPortion(loan.getCurrency()));
loanTransaction.updateOutstandingLoanBalance(MathUtil.negativeToZero(outstanding.getAmount()));
} else if (loanTransaction.isChargeback() || loanTransaction.isCreditBalanceRefund()) {
Money transactionOutstanding = loanTransaction.getPrincipalPortion(loan.getCurrency());
if (loanTransaction.isOverPaid()) {
// in case of advanced payment strategy and creditAllocations the full amount is recognized first
if (loan.getCreditAllocationRules() != null && !loan.getCreditAllocationRules().isEmpty()) {
Money payedPrincipal = loanTransaction.getLoanTransactionToRepaymentScheduleMappings().stream() //
.map(mapping -> mapping.getPrincipalPortion(loan.getCurrency())) //
.reduce(Money.zero(loan.getCurrency()), Money::plus);
transactionOutstanding = loanTransaction.getPrincipalPortion(loan.getCurrency()).minus(payedPrincipal);
} else {
// in case legacy payment strategy
transactionOutstanding = loanTransaction.getAmount(loan.getCurrency())
.minus(loanTransaction.getOverPaymentPortion(loan.getCurrency()));
}
if (transactionOutstanding.isLessThanZero()) {
transactionOutstanding = Money.zero(loan.getCurrency());
}
}
outstanding = outstanding.plus(transactionOutstanding);
loanTransaction.updateOutstandingLoanBalance(MathUtil.negativeToZero(outstanding.getAmount()));
} else if (!loanTransaction.isAccrualActivity()) {
if (loan.getLoanInterestRecalculationDetails() != null
&& loan.getLoanInterestRecalculationDetails().isCompoundingToBePostedAsTransaction()
&& !loanTransaction.isRepaymentAtDisbursement()) {
outstanding = outstanding.minus(loanTransaction.getAmount(loan.getCurrency()));
} else {
outstanding = outstanding.minus(loanTransaction.getPrincipalPortion(loan.getCurrency()));
}
loanTransaction.updateOutstandingLoanBalance(MathUtil.negativeToZero(outstanding.getAmount()));
}
}
} }
public void updateLoanToLastDisbursalState(final Loan loan, final LoanDisbursementDetails disbursementDetail) { public void updateLoanToLastDisbursalState(final Loan loan, final LoanDisbursementDetails disbursementDetail) {
@@ -84,6 +84,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionComparator; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionComparator;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter; import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
@@ -93,7 +94,6 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanSchedul
import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator; import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
import org.apache.fineract.portfolio.loanaccount.service.InterestRefundService; import org.apache.fineract.portfolio.loanaccount.service.InterestRefundService;
import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService; import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionService;
import org.apache.fineract.portfolio.loanaccount.service.schedule.LoanScheduleComponent; import org.apache.fineract.portfolio.loanaccount.service.schedule.LoanScheduleComponent;
import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator; import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
import org.apache.fineract.portfolio.loanproduct.calc.data.PeriodDueDetails; import org.apache.fineract.portfolio.loanproduct.calc.data.PeriodDueDetails;
@@ -121,17 +121,17 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
private final LoanRepositoryWrapper loanRepositoryWrapper; private final LoanRepositoryWrapper loanRepositoryWrapper;
private final InterestRefundService interestRefundService; private final InterestRefundService interestRefundService;
private final LoanScheduleComponent loanSchedule; private final LoanScheduleComponent loanSchedule;
private final LoanTransactionService loanTransactionService; private final LoanTransactionRepository loanTransactionRepository;
public AdvancedPaymentScheduleTransactionProcessor(final EMICalculator emiCalculator, final LoanRepositoryWrapper loanRepositoryWrapper, public AdvancedPaymentScheduleTransactionProcessor(final EMICalculator emiCalculator, final LoanRepositoryWrapper loanRepositoryWrapper,
final InterestRefundService interestRefundService, final ExternalIdFactory externalIdFactory, final InterestRefundService interestRefundService, final ExternalIdFactory externalIdFactory,
final LoanScheduleComponent loanSchedule, final LoanTransactionService loanTransactionRepository, final LoanScheduleComponent loanSchedule, final LoanTransactionRepository loanTransactionRepository,
final LoanChargeValidator loanChargeValidator, final LoanBalanceService loanBalanceService) { final LoanChargeValidator loanChargeValidator, final LoanBalanceService loanBalanceService) {
super(externalIdFactory, loanChargeValidator, loanBalanceService); super(externalIdFactory, loanChargeValidator, loanBalanceService);
this.emiCalculator = emiCalculator; this.emiCalculator = emiCalculator;
this.loanRepositoryWrapper = loanRepositoryWrapper; this.loanRepositoryWrapper = loanRepositoryWrapper;
this.interestRefundService = interestRefundService; this.interestRefundService = interestRefundService;
this.loanTransactionService = loanTransactionRepository; this.loanTransactionRepository = loanTransactionRepository;
this.loanSchedule = loanSchedule; this.loanSchedule = loanSchedule;
} }
@@ -212,7 +212,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
} }
MoneyHolder overpaymentHolder = new MoneyHolder(Money.zero(currency)); MoneyHolder overpaymentHolder = new MoneyHolder(Money.zero(currency));
final Loan loan = loanTransactions.get(0).getLoan(); final Loan loan = loanTransactions.getFirst().getLoan();
List<LoanTermVariationsData> loanTermVariations = loan.getActiveLoanTermVariations().stream().map(LoanTermVariations::toData) List<LoanTermVariationsData> loanTermVariations = loan.getActiveLoanTermVariations().stream().map(LoanTermVariations::toData)
.collect(Collectors.toCollection(ArrayList::new)); .collect(Collectors.toCollection(ArrayList::new));
final Integer installmentAmountInMultiplesOf = loan.getLoanProductRelatedDetail().getInstallmentAmountInMultiplesOf(); final Integer installmentAmountInMultiplesOf = loan.getLoanProductRelatedDetail().getInstallmentAmountInMultiplesOf();
@@ -276,7 +276,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
@Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true) @Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true)
public ProgressiveLoanInterestScheduleModel calculateInterestScheduleModel(@NotNull Long loanId, LocalDate targetDate) { public ProgressiveLoanInterestScheduleModel calculateInterestScheduleModel(@NotNull Long loanId, LocalDate targetDate) {
Loan loan = loanRepositoryWrapper.findOneWithNotFoundDetection(loanId); Loan loan = loanRepositoryWrapper.findOneWithNotFoundDetection(loanId);
final List<LoanTransaction> transactions = loanTransactionService.retrieveListOfTransactionsForReprocessing(loan); final List<LoanTransaction> transactions = loanTransactionRepository.findNonReversedTransactionsForReprocessingByLoan(loan);
MonetaryCurrency currency = loan.getLoanRepaymentScheduleDetail().getCurrency(); MonetaryCurrency currency = loan.getLoanRepaymentScheduleDetail().getCurrency();
List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments(); List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
Set<LoanCharge> charges = loan.getActiveCharges(); Set<LoanCharge> charges = loan.getActiveCharges();
@@ -552,8 +552,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
if (!loanTransactionMapped) { if (!loanTransactionMapped) {
if (transactionDate.equals(pastDueDate)) { if (transactionDate.equals(pastDueDate)) {
// Transaction is on Maturity date, no additional installment is needed // Transaction is on Maturity date, no additional installment is needed
LoanRepaymentScheduleInstallment currentInstallment = installmentToBeProcessed LoanRepaymentScheduleInstallment currentInstallment = installmentToBeProcessed.getLast();
.get(installmentToBeProcessed.size() - 1);
emiCalculator.creditPrincipal(model, transactionDate, transactionAmount); emiCalculator.creditPrincipal(model, transactionDate, transactionAmount);
updateRepaymentPeriods(loanTransaction, progressiveTransactionCtx); updateRepaymentPeriods(loanTransaction, progressiveTransactionCtx);
@@ -827,7 +826,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
// New installment will be added (N+1 scenario) // New installment will be added (N+1 scenario)
if (!loanTransactionMapped) { if (!loanTransactionMapped) {
if (loanTransaction.getTransactionDate().equals(pastDueDate)) { if (loanTransaction.getTransactionDate().equals(pastDueDate)) {
LoanRepaymentScheduleInstallment currentInstallment = ctx.getInstallments().get(ctx.getInstallments().size() - 1); LoanRepaymentScheduleInstallment currentInstallment = ctx.getInstallments().getLast();
recognizeAmountsAfterChargeback(ctx, transactionDate, currentInstallment, chargebackAllocation); recognizeAmountsAfterChargeback(ctx, transactionDate, currentInstallment, chargebackAllocation);
} else { } else {
Loan loan = loanTransaction.getLoan(); Loan loan = loanTransaction.getLoan();
@@ -1450,7 +1449,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
public void recalculateInterestForDate(LocalDate targetDate, ProgressiveTransactionCtx ctx) { public void recalculateInterestForDate(LocalDate targetDate, ProgressiveTransactionCtx ctx) {
if (ctx.getInstallments() != null && !ctx.getInstallments().isEmpty()) { if (ctx.getInstallments() != null && !ctx.getInstallments().isEmpty()) {
Loan loan = ctx.getInstallments().get(0).getLoan(); Loan loan = ctx.getInstallments().getFirst().getLoan();
if (loan.isInterestBearingAndInterestRecalculationEnabled() && !loan.isNpa() && !ctx.isChargedOff() if (loan.isInterestBearingAndInterestRecalculationEnabled() && !loan.isNpa() && !ctx.isChargedOff()
&& !ctx.isContractTerminated()) { && !ctx.isContractTerminated()) {
@@ -1761,8 +1760,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
.max(LocalDate::compareTo); .max(LocalDate::compareTo);
if (latestDueDate.isPresent()) { if (latestDueDate.isPresent()) {
final LoanRepaymentScheduleInstallment lastInstallment = installmentsUpToTransactionDate final LoanRepaymentScheduleInstallment lastInstallment = installmentsUpToTransactionDate.getLast();
.get(installmentsUpToTransactionDate.size() - 1);
final LoanRepaymentScheduleInstallment installmentForCharges = new LoanRepaymentScheduleInstallment(loan, final LoanRepaymentScheduleInstallment installmentForCharges = new LoanRepaymentScheduleInstallment(loan,
lastInstallment.getInstallmentNumber() + 1, currentInstallment.getDueDate(), latestDueDate.get(), lastInstallment.getInstallmentNumber() + 1, currentInstallment.getDueDate(), latestDueDate.get(),
@@ -148,7 +148,7 @@ public class CapitalizedIncomeWritePlatformServiceImpl implements CapitalizedInc
LoanTransaction savedCapitalizedIncomeAdjustment = loanTransactionRepository.saveAndFlush(capitalizedIncomeAdjustment); LoanTransaction savedCapitalizedIncomeAdjustment = loanTransactionRepository.saveAndFlush(capitalizedIncomeAdjustment);
// Update outstanding loan balances // Update outstanding loan balances
loan.updateLoanOutstandingBalances(); loanBalanceService.updateLoanOutstandingBalances(loan);
// Create a note if provided // Create a note if provided
final String noteText = command.stringValueOfParameterNamed("note"); final String noteText = command.stringValueOfParameterNamed("note");
@@ -40,6 +40,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail
import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.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.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel; import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
@@ -56,9 +57,8 @@ public class InternalProgressiveLoanApiResource implements InitializingBean {
private final LoanRepositoryWrapper loanRepository; private final LoanRepositoryWrapper loanRepository;
private final AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor; private final AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor;
private final ProgressiveLoanInterestScheduleModelParserService progressiveLoanInterestScheduleModelParserService;
private final InterestScheduleModelRepositoryWrapper writePlatformService; private final InterestScheduleModelRepositoryWrapper writePlatformService;
private final LoanTransactionService loanTransactionService; private final LoanTransactionRepository loanTransactionRepository;
@Override @Override
@SuppressFBWarnings("SLF4J_SIGN_ONLY_FORMAT") @SuppressFBWarnings("SLF4J_SIGN_ONLY_FORMAT")
@@ -87,7 +87,8 @@ public class InternalProgressiveLoanApiResource implements InitializingBean {
} }
private ProgressiveLoanInterestScheduleModel reprocessTransactionsAndGetModel(final Loan loan) { private ProgressiveLoanInterestScheduleModel reprocessTransactionsAndGetModel(final Loan loan) {
final List<LoanTransaction> transactionsToReprocess = loanTransactionService.retrieveListOfTransactionsForReprocessing(loan); final List<LoanTransaction> transactionsToReprocess = loanTransactionRepository
.findNonReversedTransactionsForReprocessingByLoan(loan);
final LocalDate businessDate = ThreadLocalContextUtil.getBusinessDate(); final LocalDate businessDate = ThreadLocalContextUtil.getBusinessDate();
final Pair<ChangedTransactionDetail, ProgressiveLoanInterestScheduleModel> changedTransactionDetailProgressiveLoanInterestScheduleModelPair = advancedPaymentScheduleTransactionProcessor final Pair<ChangedTransactionDetail, ProgressiveLoanInterestScheduleModel> changedTransactionDetailProgressiveLoanInterestScheduleModelPair = advancedPaymentScheduleTransactionProcessor
.reprocessProgressiveLoanTransactionsTransactional(loan.getDisbursementDate(), businessDate, transactionsToReprocess, .reprocessProgressiveLoanTransactionsTransactional(loan.getDisbursementDate(), businessDate, transactionsToReprocess,
@@ -67,6 +67,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
@@ -74,7 +75,6 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanSchedul
import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator; import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
import org.apache.fineract.portfolio.loanaccount.service.InterestRefundService; import org.apache.fineract.portfolio.loanaccount.service.InterestRefundService;
import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService; import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionService;
import org.apache.fineract.portfolio.loanaccount.service.schedule.LoanScheduleComponent; import org.apache.fineract.portfolio.loanaccount.service.schedule.LoanScheduleComponent;
import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator; import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel; import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
@@ -120,7 +120,7 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
public void setUp() { public void setUp() {
underTest = new AdvancedPaymentScheduleTransactionProcessor(emiCalculator, Mockito.mock(LoanRepositoryWrapper.class), underTest = new AdvancedPaymentScheduleTransactionProcessor(emiCalculator, Mockito.mock(LoanRepositoryWrapper.class),
Mockito.mock(InterestRefundService.class), Mockito.mock(ExternalIdFactory.class), Mockito.mock(LoanScheduleComponent.class), Mockito.mock(InterestRefundService.class), Mockito.mock(ExternalIdFactory.class), Mockito.mock(LoanScheduleComponent.class),
Mockito.mock(LoanTransactionService.class), Mockito.mock(LoanChargeValidator.class), Mockito.mock(LoanTransactionRepository.class), Mockito.mock(LoanChargeValidator.class),
Mockito.mock(LoanBalanceService.class)); Mockito.mock(LoanBalanceService.class));
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null)); ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
@@ -134,7 +134,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
private final ConfigurationDomainService configurationDomainService; private final ConfigurationDomainService configurationDomainService;
private final HolidayRepository holidayRepository; private final HolidayRepository holidayRepository;
private final WorkingDaysRepositoryWrapper workingDaysRepository; private final WorkingDaysRepositoryWrapper workingDaysRepository;
private final JournalEntryWritePlatformService journalEntryWritePlatformService; private final JournalEntryWritePlatformService journalEntryWritePlatformService;
private final NoteRepository noteRepository; private final NoteRepository noteRepository;
private final BusinessEventNotifierService businessEventNotifierService; private final BusinessEventNotifierService businessEventNotifierService;
@@ -464,7 +463,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
chargesPayment.updateComponents(zero, zero, charge.getAmount(loan.getCurrency()), zero); chargesPayment.updateComponents(zero, zero, charge.getAmount(loan.getCurrency()), zero);
chargesPayment.updateLoan(loan); chargesPayment.updateLoan(loan);
loan.addLoanTransaction(chargesPayment); loan.addLoanTransaction(chargesPayment);
loan.updateLoanOutstandingBalances(); loanBalanceService.updateLoanOutstandingBalances(loan);
charge.markAsFullyPaid(); charge.markAsFullyPaid();
} }
@@ -931,7 +931,7 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
createUpdateIncomePostingTransaction(loan, compoundingDetail, interest, fee, penalties, externalId); createUpdateIncomePostingTransaction(loan, compoundingDetail, interest, fee, penalties, externalId);
createUpdateAccrualTransaction(loan, compoundingDetail, interest, fee, penalties, feeDetails, externalId); createUpdateAccrualTransaction(loan, compoundingDetail, interest, fee, penalties, feeDetails, externalId);
loan.updateLoanOutstandingBalances(); loanBalanceService.updateLoanOutstandingBalances(loan);
} }
private void createUpdateIncomePostingTransaction(Loan loan, LoanInterestRecalcualtionAdditionalDetails compoundingDetail, private void createUpdateIncomePostingTransaction(Loan loan, LoanInterestRecalcualtionAdditionalDetails compoundingDetail,
@@ -1000,7 +1000,7 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
createIncomePostingAndAccrualTransactionOnLoanClosure(loan, closedDate, interestToPost, feeToPost, penaltyToPost, amountToPost); createIncomePostingAndAccrualTransactionOnLoanClosure(loan, closedDate, interestToPost, feeToPost, penaltyToPost, amountToPost);
} }
loan.updateLoanOutstandingBalances(); loanBalanceService.updateLoanOutstandingBalances(loan);
} }
private void determineCumulativeIncomeFromInstallments(final Loan loan, private void determineCumulativeIncomeFromInstallments(final Loan loan,
@@ -261,20 +261,20 @@ public class LoanAssemblerImpl implements LoanAssembler {
loanOfficer, loanPurpose, transactionProcessingStrategy, loanProductRelatedDetail, loanCharges, loanOfficer, loanPurpose, transactionProcessingStrategy, loanProductRelatedDetail, loanCharges,
syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance, syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance,
createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential, rates, createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential, rates,
fixedPrincipalPercentagePerInstallment, externalId, loanApplicationTerms, loanScheduleModel, fixedPrincipalPercentagePerInstallment, externalId, loanApplicationTerms, isEnableInstallmentLevelDelinquency,
isEnableInstallmentLevelDelinquency, submittedOnDate); submittedOnDate);
} else if (group != null) { } else if (group != null) {
loanApplication = Loan.newGroupLoanApplication(accountNo, group, loanAccountType, loanProduct, fund, loanOfficer, loanPurpose, loanApplication = Loan.newGroupLoanApplication(accountNo, group, loanAccountType, loanProduct, fund, loanOfficer, loanPurpose,
transactionProcessingStrategy, loanProductRelatedDetail, loanCharges, syncDisbursementWithMeeting, fixedEmiAmount, transactionProcessingStrategy, loanProductRelatedDetail, loanCharges, syncDisbursementWithMeeting, fixedEmiAmount,
disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate, disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate,
interestRateDifferential, rates, fixedPrincipalPercentagePerInstallment, externalId, loanApplicationTerms, interestRateDifferential, rates, fixedPrincipalPercentagePerInstallment, externalId, loanApplicationTerms,
loanScheduleModel, isEnableInstallmentLevelDelinquency, submittedOnDate); isEnableInstallmentLevelDelinquency, submittedOnDate);
} else if (client != null) { } else if (client != null) {
loanApplication = Loan.newIndividualLoanApplication(accountNo, client, loanAccountType, loanProduct, fund, loanOfficer, loanApplication = Loan.newIndividualLoanApplication(accountNo, client, loanAccountType, loanProduct, fund, loanOfficer,
loanPurpose, transactionProcessingStrategy, loanProductRelatedDetail, loanCharges, collateral, fixedEmiAmount, loanPurpose, transactionProcessingStrategy, loanProductRelatedDetail, loanCharges, collateral, fixedEmiAmount,
disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate, disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate,
interestRateDifferential, rates, fixedPrincipalPercentagePerInstallment, externalId, loanApplicationTerms, interestRateDifferential, rates, fixedPrincipalPercentagePerInstallment, externalId, loanApplicationTerms,
loanScheduleModel, isEnableInstallmentLevelDelinquency, submittedOnDate); isEnableInstallmentLevelDelinquency, submittedOnDate);
} else { } else {
throw new IllegalStateException("No loan application exists for either a client or group (or both)."); throw new IllegalStateException("No loan application exists for either a client or group (or both).");
} }
@@ -63,6 +63,7 @@ public class LoanDisbursementService {
private final LoanDisbursementValidator loanDisbursementValidator; private final LoanDisbursementValidator loanDisbursementValidator;
private final ReprocessLoanTransactionsService reprocessLoanTransactionsService; private final ReprocessLoanTransactionsService reprocessLoanTransactionsService;
private final LoanChargeService loanChargeService; private final LoanChargeService loanChargeService;
private final LoanBalanceService loanBalanceService;
public void updateDisbursementDetails(final Loan loan, final JsonCommand jsonCommand, final Map<String, Object> actualChanges) { public void updateDisbursementDetails(final Loan loan, final JsonCommand jsonCommand, final Map<String, Object> actualChanges) {
final List<Long> disbursementList = loan.fetchDisbursementIds(); final List<Long> disbursementList = loan.fetchDisbursementIds();
@@ -217,7 +218,7 @@ public class LoanDisbursementService {
chargesPayment.updateComponentsAndTotal(zero, zero, disbursentMoney, zero); chargesPayment.updateComponentsAndTotal(zero, zero, disbursentMoney, zero);
chargesPayment.updateLoan(loan); chargesPayment.updateLoan(loan);
loan.addLoanTransaction(chargesPayment); loan.addLoanTransaction(chargesPayment);
loan.updateLoanOutstandingBalances(); loanBalanceService.updateLoanOutstandingBalances(loan);
} }
final LocalDate expectedDate = loan.getExpectedFirstRepaymentOnDate(); final LocalDate expectedDate = loan.getExpectedFirstRepaymentOnDate();
@@ -61,8 +61,8 @@ public class LoanTransactionProcessingServiceImpl implements LoanTransactionProc
private final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessorFactory; private final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessorFactory;
private final LoanTermVariationsMapper loanMapper; private final LoanTermVariationsMapper loanMapper;
private final InterestScheduleModelRepositoryWrapper modelRepository; private final InterestScheduleModelRepositoryWrapper modelRepository;
private final LoanTransactionService loanTransactionService;
private final LoanBalanceService loanBalanceService; private final LoanBalanceService loanBalanceService;
private final LoanTransactionService loanTransactionService;
@Override @Override
public boolean canProcessLatestTransactionOnly(Loan loan, LoanTransaction loanTransaction, public boolean canProcessLatestTransactionOnly(Loan loan, LoanTransaction loanTransaction,
@@ -594,8 +594,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
if (loan.getLoanProduct().isMultiDisburseLoan() || loan.isProgressiveSchedule()) { if (loan.getLoanProduct().isMultiDisburseLoan() || loan.isProgressiveSchedule()) {
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = loanTransactionService final List<LoanTransaction> allNonContraTransactionsPostDisbursement = loanTransactionRepository
.retrieveListOfTransactionsForReprocessing(loan); .findNonReversedTransactionsForReprocessingByLoan(loan);
if (!allNonContraTransactionsPostDisbursement.isEmpty()) { if (!allNonContraTransactionsPostDisbursement.isEmpty()) {
reprocessLoanTransactionsService.reprocessTransactions(loan); reprocessLoanTransactionsService.reprocessTransactions(loan);
} }
@@ -2246,7 +2246,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
throw new MultiDisbursementDataNotAllowedException(LoanApiConstants.disbursementDataParameterName, errorMessage); throw new MultiDisbursementDataNotAllowedException(LoanApiConstants.disbursementDataParameterName, errorMessage);
} }
} else { } else {
if (disbursementDataArray == null || disbursementDataArray.size() == 0) { if (disbursementDataArray == null || disbursementDataArray.isEmpty()) {
final String errorMessage = "For this loan product, disbursement details must be provided"; final String errorMessage = "For this loan product, disbursement details must be provided";
throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage); throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
} }
@@ -2821,7 +2821,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, null, null); final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, null, null);
loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO); loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO);
} }
final List<LoanTransaction> loanTransactions = loanTransactionService.retrieveListOfTransactionsForReprocessing(loan); final List<LoanTransaction> loanTransactions = loanTransactionRepository.findNonReversedTransactionsForReprocessingByLoan(loan);
loanTransactions.add(chargeOffTransaction); loanTransactions.add(chargeOffTransaction);
reprocessLoanTransactionsService.reprocessParticularTransactions(loan, loanTransactions); reprocessLoanTransactionsService.reprocessParticularTransactions(loan, loanTransactions);
loan.addLoanTransaction(chargeOffTransaction); loan.addLoanTransaction(chargeOffTransaction);
@@ -3031,7 +3031,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final String errorMessage = "loan.product.does.not.support.multiple.disbursals.cannot.undo.last.disbursal"; final String errorMessage = "loan.product.does.not.support.multiple.disbursals.cannot.undo.last.disbursal";
throw new LoanMultiDisbursementException(errorMessage); throw new LoanMultiDisbursementException(errorMessage);
} }
Integer trancheDisbursedCount = 0; int trancheDisbursedCount = 0;
for (LoanDisbursementDetails disbursementDetails : loan.getDisbursementDetails()) { for (LoanDisbursementDetails disbursementDetails : loan.getDisbursementDetails()) {
if (disbursementDetails.actualDisbursementDate() != null) { if (disbursementDetails.actualDisbursementDate() != null) {
trancheDisbursedCount++; trancheDisbursedCount++;
@@ -3361,13 +3361,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Map<String, Object> actualChanges = new LinkedHashMap<>(); final Map<String, Object> actualChanges = new LinkedHashMap<>();
List<LoanTransaction> loanTransactions = loan.retrieveListOfTransactionsByType(LoanTransactionType.DISBURSEMENT); List<LoanTransaction> loanTransactions = loan.retrieveListOfTransactionsByType(LoanTransactionType.DISBURSEMENT);
loanTransactions.sort(Comparator.comparing(LoanTransaction::getId)); loanTransactions.sort(Comparator.comparing(LoanTransaction::getId));
final LoanTransaction lastDisbursalTransaction = loanTransactions.get(loanTransactions.size() - 1); final LoanTransaction lastDisbursalTransaction = loanTransactions.getLast();
final LocalDate lastTransactionDate = lastDisbursalTransaction.getTransactionDate(); final LocalDate lastTransactionDate = lastDisbursalTransaction.getTransactionDate();
existingTransactionIds.addAll(loanTransactionRepository.findTransactionIdsByLoan(loan)); existingTransactionIds.addAll(loanTransactionRepository.findTransactionIdsByLoan(loan));
existingReversedTransactionIds.addAll(loanTransactionRepository.findReversedTransactionIdsByLoan(loan)); existingReversedTransactionIds.addAll(loanTransactionRepository.findReversedTransactionIdsByLoan(loan));
loanTransactions = loan.retrieveListOfTransactionsExcludeAccruals(); loanTransactions = loanTransactionRepository.findNonReversedMonetaryTransactionsByLoan(loan);
Collections.reverse(loanTransactions); Collections.reverse(loanTransactions);
for (final LoanTransaction previousTransaction : loanTransactions) { for (final LoanTransaction previousTransaction : loanTransactions) {
if (DateUtils.isBefore(lastTransactionDate, previousTransaction.getTransactionDate()) if (DateUtils.isBefore(lastTransactionDate, previousTransaction.getTransactionDate())
@@ -36,6 +36,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.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.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
@@ -53,7 +54,7 @@ public class ProgressiveLoanSummaryDataProvider extends CommonLoanSummaryDataPro
private final AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor; private final AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor;
private final EMICalculator emiCalculator; private final EMICalculator emiCalculator;
private final LoanRepositoryWrapper loanRepository; private final LoanRepositoryWrapper loanRepository;
private final LoanTransactionService loanTransactionService; private final LoanTransactionRepository loanTransactionRepository;
private final InterestScheduleModelRepositoryWrapper modelRepository; private final InterestScheduleModelRepositoryWrapper modelRepository;
@Override @Override
@@ -81,8 +82,8 @@ public class ProgressiveLoanSummaryDataProvider extends CommonLoanSummaryDataPro
} }
private ProgressiveLoanInterestScheduleModel calculateModel(Loan loan, LocalDate businessDate) { private ProgressiveLoanInterestScheduleModel calculateModel(Loan loan, LocalDate businessDate) {
final List<LoanTransaction> transactionsToReprocess = loanTransactionService.retrieveListOfTransactionsForReprocessing(loan) final List<LoanTransaction> transactionsToReprocess = loanTransactionRepository
.stream().filter(t -> !t.isAccrualActivity()).toList(); .findNonReversedTransactionsForReprocessingByLoan(loan).stream().filter(t -> !t.isAccrualActivity()).toList();
Pair<ChangedTransactionDetail, ProgressiveLoanInterestScheduleModel> changedTransactionDetailProgressiveLoanInterestScheduleModelPair = advancedPaymentScheduleTransactionProcessor Pair<ChangedTransactionDetail, ProgressiveLoanInterestScheduleModel> changedTransactionDetailProgressiveLoanInterestScheduleModelPair = advancedPaymentScheduleTransactionProcessor
.reprocessProgressiveLoanTransactions(loan.getDisbursementDate(), businessDate, transactionsToReprocess, loan.getCurrency(), .reprocessProgressiveLoanTransactions(loan.getDisbursementDate(), businessDate, transactionsToReprocess, loan.getCurrency(),
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges()); loan.getRepaymentScheduleInstallments(), loan.getActiveCharges());
@@ -22,8 +22,6 @@ import java.time.LocalDate;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
@@ -37,7 +35,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy; import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionComparator;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
@@ -55,26 +52,18 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
private final InterestScheduleModelRepositoryWrapper interestScheduleModelRepositoryWrapper; private final InterestScheduleModelRepositoryWrapper interestScheduleModelRepositoryWrapper;
private final LoanBalanceService loanBalanceService; private final LoanBalanceService loanBalanceService;
private final LoanTransactionRepository loanTransactionRepository; private final LoanTransactionRepository loanTransactionRepository;
private final LoanTransactionService loanTransactionService;
@Override @Override
public void reprocessTransactions(final Loan loan) { public void reprocessTransactions(final Loan loan) {
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing(loan); final List<LoanTransaction> allNonContraTransactionsPostDisbursement = loanTransactionService
.retrieveListOfTransactionsForReprocessing(loan);
final ChangedTransactionDetail changedTransactionDetail = reprocessTransactionsAndFetchChangedTransactions(loan, final ChangedTransactionDetail changedTransactionDetail = reprocessTransactionsAndFetchChangedTransactions(loan,
allNonContraTransactionsPostDisbursement); allNonContraTransactionsPostDisbursement);
handleChangedDetail(changedTransactionDetail); handleChangedDetail(changedTransactionDetail);
} }
private List<LoanTransaction> retrieveListOfTransactionsForReprocessing(final Loan loan) {
return loan.getLoanTransactions().stream().filter(loanTransactionForReprocessingPredicate())
.sorted(LoanTransactionComparator.INSTANCE).collect(Collectors.toList());
}
private Predicate<LoanTransaction> loanTransactionForReprocessingPredicate() {
return transaction -> transaction.isNotReversed()
&& (transaction.isChargeOff() || transaction.isReAge() || transaction.isAccrualActivity() || transaction.isReAmortize()
|| !transaction.isNonMonetaryTransaction() || transaction.isContractTermination());
}
@Override @Override
public void reprocessParticularTransactions(final Loan loan, final List<LoanTransaction> loanTransactions) { public void reprocessParticularTransactions(final Loan loan, final List<LoanTransaction> loanTransactions) {
final ChangedTransactionDetail changedTransactionDetail = reprocessTransactionsAndFetchChangedTransactions(loan, loanTransactions); final ChangedTransactionDetail changedTransactionDetail = reprocessTransactionsAndFetchChangedTransactions(loan, loanTransactions);
@@ -83,7 +72,7 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
@Override @Override
public void reprocessTransactionsWithPostTransactionChecks(final Loan loan, final LocalDate transactionDate) { public void reprocessTransactionsWithPostTransactionChecks(final Loan loan, final LocalDate transactionDate) {
final List<LoanTransaction> transactions = retrieveListOfTransactionsForReprocessing(loan); final List<LoanTransaction> transactions = loanTransactionRepository.findNonReversedTransactionsForReprocessingByLoan(loan);
final ChangedTransactionDetail changedTransactionDetail = reprocessTransactionsAndFetchChangedTransactions(loan, transactions); final ChangedTransactionDetail changedTransactionDetail = reprocessTransactionsAndFetchChangedTransactions(loan, transactions);
handleChangedDetail(changedTransactionDetail); handleChangedDetail(changedTransactionDetail);
} }
@@ -70,10 +70,10 @@ public class LoanContractTerminationServiceImpl {
private final LoanUtilService loanUtilService; private final LoanUtilService loanUtilService;
private final ExternalIdFactory externalIdFactory; private final ExternalIdFactory externalIdFactory;
private final BusinessEventNotifierService businessEventNotifierService; private final BusinessEventNotifierService businessEventNotifierService;
private final LoanTransactionService loanTransactionService;
private final LoanScheduleService loanScheduleService; private final LoanScheduleService loanScheduleService;
private final LoanChargeValidator loanChargeValidator; private final LoanChargeValidator loanChargeValidator;
private final ProgressiveLoanTransactionValidator loanTransactionValidator; private final ProgressiveLoanTransactionValidator loanTransactionValidator;
private final LoanTransactionService loanTransactionService;
public CommandProcessingResult applyContractTermination(final JsonCommand command) { public CommandProcessingResult applyContractTermination(final JsonCommand command) {
final Loan loan = loanAssembler.assembleFrom(command.getLoanId()); final Loan loan = loanAssembler.assembleFrom(command.getLoanId());
@@ -22,6 +22,7 @@ import java.util.List;
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.CreocoreLoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.CreocoreLoanRepaymentScheduleTransactionProcessor;
@@ -35,7 +36,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.imp
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.RBILoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.RBILoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator; import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService; import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionService;
import org.apache.fineract.portfolio.loanaccount.service.ProgressiveLoanInterestRefundServiceImpl; import org.apache.fineract.portfolio.loanaccount.service.ProgressiveLoanInterestRefundServiceImpl;
import org.apache.fineract.portfolio.loanaccount.service.schedule.LoanScheduleComponent; import org.apache.fineract.portfolio.loanaccount.service.schedule.LoanScheduleComponent;
import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator; import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
@@ -138,9 +138,9 @@ public class LoanAccountAutoStarter {
final LoanRepositoryWrapper loanRepositoryWrapper, final LoanRepositoryWrapper loanRepositoryWrapper,
final @Lazy ProgressiveLoanInterestRefundServiceImpl progressiveLoanInterestRefundService, final @Lazy ProgressiveLoanInterestRefundServiceImpl progressiveLoanInterestRefundService,
final ExternalIdFactory externalIdFactory, final LoanScheduleComponent loanSchedule, final ExternalIdFactory externalIdFactory, final LoanScheduleComponent loanSchedule,
final LoanTransactionService loanTransactionService, final LoanChargeValidator loanChargeValidator, final LoanTransactionRepository loanTransactionRepository, final LoanChargeValidator loanChargeValidator,
final LoanBalanceService loanBalanceService) { final LoanBalanceService loanBalanceService) {
return new AdvancedPaymentScheduleTransactionProcessor(emiCalculator, loanRepositoryWrapper, progressiveLoanInterestRefundService, return new AdvancedPaymentScheduleTransactionProcessor(emiCalculator, loanRepositoryWrapper, progressiveLoanInterestRefundService,
externalIdFactory, loanSchedule, loanTransactionService, loanChargeValidator, loanBalanceService); externalIdFactory, loanSchedule, loanTransactionRepository, loanChargeValidator, loanBalanceService);
} }
} }
@@ -457,9 +457,9 @@ public class LoanAccountConfiguration {
@ConditionalOnMissingBean(LoanDisbursementService.class) @ConditionalOnMissingBean(LoanDisbursementService.class)
public LoanDisbursementService loanDisbursementService(LoanChargeValidator loanChargeValidator, public LoanDisbursementService loanDisbursementService(LoanChargeValidator loanChargeValidator,
LoanDisbursementValidator loanDisbursementValidator, ReprocessLoanTransactionsService reprocessLoanTransactionsService, LoanDisbursementValidator loanDisbursementValidator, ReprocessLoanTransactionsService reprocessLoanTransactionsService,
LoanChargeService loanChargeService) { LoanChargeService loanChargeService, LoanBalanceService loanBalanceService) {
return new LoanDisbursementService(loanChargeValidator, loanDisbursementValidator, reprocessLoanTransactionsService, return new LoanDisbursementService(loanChargeValidator, loanDisbursementValidator, reprocessLoanTransactionsService,
loanChargeService); loanChargeService, loanBalanceService);
} }
@Bean @Bean
@@ -363,7 +363,7 @@ public class LoanAccountDelinquencyRangeEventSerializerTest {
DelinquencyReadPlatformService delinquencyReadPlatformService = new DelinquencyReadPlatformServiceImpl(repositoryRange, DelinquencyReadPlatformService delinquencyReadPlatformService = new DelinquencyReadPlatformServiceImpl(repositoryRange,
repositoryBucket, repositoryLoanDelinquencyTagHistory, mapperRange, mapperBucket, mapperLoanDelinquencyTagHistory, repositoryBucket, repositoryLoanDelinquencyTagHistory, mapperRange, mapperBucket, mapperLoanDelinquencyTagHistory,
loanRepository, loanDelinquencyDomainService, repositoryLoanInstallmentDelinquencyTag, loanDelinquencyActionRepository, loanRepository, loanDelinquencyDomainService, repositoryLoanInstallmentDelinquencyTag, loanDelinquencyActionRepository,
delinquencyEffectivePauseHelper, configurationDomainService); delinquencyEffectivePauseHelper, configurationDomainService, Mockito.mock(LoanTransactionRepository.class));
LoanProduct loanProduct = Mockito.mock(LoanProduct.class); LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
when(loanProduct.isMultiDisburseLoan()).thenReturn(false); when(loanProduct.isMultiDisburseLoan()).thenReturn(false);
@@ -40,7 +40,6 @@ import org.apache.fineract.portfolio.group.domain.Group;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator; import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService; import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct; import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
@@ -80,7 +79,6 @@ public class LoanBuilder {
private BigDecimal fixedPrincipalPercentagePerInstallment; private BigDecimal fixedPrincipalPercentagePerInstallment;
private ExternalId externalId = ExternalId.empty(); private ExternalId externalId = ExternalId.empty();
private LoanApplicationTerms loanApplicationTerms = mock(LoanApplicationTerms.class); private LoanApplicationTerms loanApplicationTerms = mock(LoanApplicationTerms.class);
private LoanScheduleModel loanScheduleModel = mock(LoanScheduleModel.class);
private Boolean enableInstallmentLevelDelinquency = false; private Boolean enableInstallmentLevelDelinquency = false;
private LocalDate submittedOnDate = LocalDate.now(ZoneId.systemDefault()); private LocalDate submittedOnDate = LocalDate.now(ZoneId.systemDefault());
private LocalDate approvedOnDate; private LocalDate approvedOnDate;
@@ -116,8 +114,8 @@ public class LoanBuilder {
Loan loan = Loan.newIndividualLoanApplication(accountNo, client, loanType, loanProduct, fund, loanOfficer, loanPurpose, Loan loan = Loan.newIndividualLoanApplication(accountNo, client, loanType, loanProduct, fund, loanOfficer, loanPurpose,
transactionProcessor, loanRepaymentScheduleDetail, charges, collateral, fixedEmiAmount, disbursementDetails, transactionProcessor, loanRepaymentScheduleDetail, charges, collateral, fixedEmiAmount, disbursementDetails,
maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential,
rates, fixedPrincipalPercentagePerInstallment, externalId, loanApplicationTerms, loanScheduleModel, rates, fixedPrincipalPercentagePerInstallment, externalId, loanApplicationTerms, enableInstallmentLevelDelinquency,
enableInstallmentLevelDelinquency, submittedOnDate); submittedOnDate);
if (id != null) { if (id != null) {
loan.setId(id); loan.setId(id);
@@ -335,11 +333,6 @@ public class LoanBuilder {
return this; return this;
} }
public LoanBuilder withLoanScheduleModel(LoanScheduleModel loanScheduleModel) {
this.loanScheduleModel = loanScheduleModel;
return this;
}
public LoanBuilder withEnableInstallmentLevelDelinquency(Boolean enableInstallmentLevelDelinquency) { public LoanBuilder withEnableInstallmentLevelDelinquency(Boolean enableInstallmentLevelDelinquency) {
this.enableInstallmentLevelDelinquency = enableInstallmentLevelDelinquency; this.enableInstallmentLevelDelinquency = enableInstallmentLevelDelinquency;
return this; return this;