FINERACT-2234: Replace in-memory transaction loops with repository queries
This commit is contained in:
committed by
Adam Saghy
parent
dd4ca4d3e2
commit
cb6a15c3d2
+4
-7
@@ -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.LoanRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@@ -77,6 +78,7 @@ public class DelinquencyReadPlatformServiceImpl implements DelinquencyReadPlatfo
|
||||
private final LoanDelinquencyActionRepository loanDelinquencyActionRepository;
|
||||
private final DelinquencyEffectivePauseHelper delinquencyEffectivePauseHelper;
|
||||
private final ConfigurationDomainService configurationDomainService;
|
||||
private final LoanTransactionRepository loanTransactionRepository;
|
||||
|
||||
@Override
|
||||
public List<DelinquencyRangeData> retrieveAllDelinquencyRanges() {
|
||||
@@ -174,7 +176,7 @@ public class DelinquencyReadPlatformServiceImpl implements DelinquencyReadPlatfo
|
||||
private void addInstallmentLevelDelinquencyData(CollectionData collectionData, Long loanId) {
|
||||
Collection<LoanInstallmentDelinquencyTagData> loanInstallmentDelinquencyTagData = retrieveLoanInstallmentsCurrentDelinquencyTag(
|
||||
loanId);
|
||||
if (loanInstallmentDelinquencyTagData != null && loanInstallmentDelinquencyTagData.size() > 0) {
|
||||
if (loanInstallmentDelinquencyTagData != null && !loanInstallmentDelinquencyTagData.isEmpty()) {
|
||||
|
||||
// installment level delinquency grouped by rangeId, and summed up the delinquent amount
|
||||
Collection<InstallmentLevelDelinquency> installmentLevelDelinquencies = loanInstallmentDelinquencyTagData.stream()
|
||||
@@ -254,12 +256,7 @@ public class DelinquencyReadPlatformServiceImpl implements DelinquencyReadPlatfo
|
||||
}
|
||||
}
|
||||
|
||||
LocalDate lastTransactionDate = null;
|
||||
for (final LoanTransaction transaction : loan.getLoanTransactions()) {
|
||||
if (transaction.isRepaymentLikeType() && transaction.isGreaterThanZero()) {
|
||||
lastTransactionDate = transaction.getTransactionDate();
|
||||
}
|
||||
}
|
||||
final LocalDate lastTransactionDate = loanTransactionRepository.findLastRepaymentLikeTransactionDate(loan).orElse(null);
|
||||
|
||||
LocalDate possibleNextRepaymentDate = earliestUnpaidInstallmentDate;
|
||||
if (DateUtils.isAfter(lastTransactionDate, earliestUnpaidInstallmentDate)) {
|
||||
|
||||
+4
-2
@@ -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.loanaccount.domain.LoanRepository;
|
||||
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.loanproduct.domain.LoanProductRepository;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
@@ -60,11 +61,12 @@ public class DelinquencyConfiguration {
|
||||
LoanDelinquencyDomainService loanDelinquencyDomainService,
|
||||
LoanInstallmentDelinquencyTagRepository repositoryLoanInstallmentDelinquencyTag,
|
||||
LoanDelinquencyActionRepository loanDelinquencyActionRepository,
|
||||
DelinquencyEffectivePauseHelper delinquencyEffectivePauseHelper, ConfigurationDomainService configurationDomainService) {
|
||||
DelinquencyEffectivePauseHelper delinquencyEffectivePauseHelper, ConfigurationDomainService configurationDomainService,
|
||||
LoanTransactionRepository loanTransactionRepository) {
|
||||
return new DelinquencyReadPlatformServiceImpl(repositoryRange, repositoryBucket, repositoryLoanDelinquencyTagHistory, mapperRange,
|
||||
mapperBucket, mapperLoanDelinquencyTagHistory, loanRepository, loanDelinquencyDomainService,
|
||||
repositoryLoanInstallmentDelinquencyTag, loanDelinquencyActionRepository, delinquencyEffectivePauseHelper,
|
||||
configurationDomainService);
|
||||
configurationDomainService, loanTransactionRepository);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
+15
-85
@@ -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.ExternalId;
|
||||
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.organisation.monetary.domain.MonetaryCurrency;
|
||||
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.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
||||
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.LoanProductRelatedDetail;
|
||||
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 Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate,
|
||||
final BigDecimal interestRateDifferential, final List<Rate> rates, final BigDecimal fixedPrincipalPercentagePerInstallment,
|
||||
final ExternalId externalId, final LoanApplicationTerms loanApplicationTerms, final LoanScheduleModel loanScheduleModel,
|
||||
final Boolean enableInstallmentLevelDelinquency, final LocalDate submittedOnDate) {
|
||||
final ExternalId externalId, final LoanApplicationTerms loanApplicationTerms, final Boolean enableInstallmentLevelDelinquency,
|
||||
final LocalDate submittedOnDate) {
|
||||
return new Loan(accountNo, client, null, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct,
|
||||
loanRepaymentScheduleDetail, null, loanCharges, collateral, null, fixedEmiAmount, disbursementDetails,
|
||||
maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential, rates,
|
||||
fixedPrincipalPercentagePerInstallment, externalId, loanApplicationTerms, loanScheduleModel,
|
||||
enableInstallmentLevelDelinquency, submittedOnDate);
|
||||
fixedPrincipalPercentagePerInstallment, externalId, loanApplicationTerms, enableInstallmentLevelDelinquency,
|
||||
submittedOnDate);
|
||||
}
|
||||
|
||||
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 Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate,
|
||||
final BigDecimal interestRateDifferential, final List<Rate> rates, final BigDecimal fixedPrincipalPercentagePerInstallment,
|
||||
final ExternalId externalId, final LoanApplicationTerms loanApplicationTerms, final LoanScheduleModel loanScheduleModel,
|
||||
final Boolean enableInstallmentLevelDelinquency, final LocalDate submittedOnDate) {
|
||||
final ExternalId externalId, final LoanApplicationTerms loanApplicationTerms, final Boolean enableInstallmentLevelDelinquency,
|
||||
final LocalDate submittedOnDate) {
|
||||
return new Loan(accountNo, null, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct,
|
||||
loanRepaymentScheduleDetail, null, loanCharges, null, syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails,
|
||||
maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential, rates,
|
||||
fixedPrincipalPercentagePerInstallment, externalId, loanApplicationTerms, loanScheduleModel,
|
||||
enableInstallmentLevelDelinquency, submittedOnDate);
|
||||
fixedPrincipalPercentagePerInstallment, externalId, loanApplicationTerms, enableInstallmentLevelDelinquency,
|
||||
submittedOnDate);
|
||||
}
|
||||
|
||||
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 Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate,
|
||||
final BigDecimal interestRateDifferential, final List<Rate> rates, final BigDecimal fixedPrincipalPercentagePerInstallment,
|
||||
final ExternalId externalId, final LoanApplicationTerms loanApplicationTerms, final LoanScheduleModel loanScheduleModel,
|
||||
final Boolean enableInstallmentLevelDelinquency, final LocalDate submittedOnDate) {
|
||||
final ExternalId externalId, final LoanApplicationTerms loanApplicationTerms, final Boolean enableInstallmentLevelDelinquency,
|
||||
final LocalDate submittedOnDate) {
|
||||
return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct,
|
||||
loanRepaymentScheduleDetail, null, loanCharges, null, syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails,
|
||||
maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential, rates,
|
||||
fixedPrincipalPercentagePerInstallment, externalId, loanApplicationTerms, loanScheduleModel,
|
||||
enableInstallmentLevelDelinquency, submittedOnDate);
|
||||
fixedPrincipalPercentagePerInstallment, externalId, loanApplicationTerms, enableInstallmentLevelDelinquency,
|
||||
submittedOnDate);
|
||||
}
|
||||
|
||||
protected Loan() {
|
||||
@@ -488,8 +486,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
final List<LoanDisbursementDetails> disbursementDetails, final BigDecimal maxOutstandingLoanBalance,
|
||||
final Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate,
|
||||
final BigDecimal interestRateDifferential, final List<Rate> rates, final BigDecimal fixedPrincipalPercentagePerInstallment,
|
||||
final ExternalId externalId, final LoanApplicationTerms loanApplicationTerms, final LoanScheduleModel loanScheduleModel,
|
||||
final Boolean enableInstallmentLevelDelinquency, final LocalDate submittedOnDate) {
|
||||
final ExternalId externalId, final LoanApplicationTerms loanApplicationTerms, final Boolean enableInstallmentLevelDelinquency,
|
||||
final LocalDate submittedOnDate) {
|
||||
this.loanRepaymentScheduleDetail = loanRepaymentScheduleDetail;
|
||||
|
||||
this.isFloatingInterestRate = isFloatingInterestRate;
|
||||
@@ -850,17 +848,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
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) {
|
||||
return this.loanTransactions.stream()
|
||||
.filter(transaction -> transaction.isNotReversed() && transaction.getTypeOf().equals(transactionType))
|
||||
@@ -880,11 +867,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
public Money calculateTotalRecoveredPayments() {
|
||||
// in case logic for reversing recovered payment is implemented handle subtraction from totalRecoveredPayments
|
||||
return getTotalRecoveredPayments();
|
||||
}
|
||||
|
||||
public MonetaryCurrency loanCurrency() {
|
||||
return this.loanRepaymentScheduleDetail.getCurrency();
|
||||
}
|
||||
@@ -1033,17 +1015,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
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) {
|
||||
return getRepaymentScheduleInstallments().stream()
|
||||
.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());
|
||||
}
|
||||
|
||||
private boolean isUserTransaction(LoanTransaction transaction) {
|
||||
public boolean isUserTransaction(LoanTransaction transaction) {
|
||||
return !(transaction.isReversed() || transaction.isAccrualRelated() || transaction.isIncomePosting());
|
||||
}
|
||||
|
||||
@@ -1391,47 +1362,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
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() {
|
||||
return this.transactionProcessingStrategyCode;
|
||||
}
|
||||
|
||||
-4
@@ -911,10 +911,6 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
|
||||
|| this.isIncomePosting());
|
||||
}
|
||||
|
||||
public boolean hasLoanTransactionRelations() {
|
||||
return !loanTransactionRelations.isEmpty();
|
||||
}
|
||||
|
||||
public List<LoanTransactionRelation> getLoanTransactionRelations(Predicate<LoanTransactionRelation> predicate) {
|
||||
return loanTransactionRelations.stream().filter(predicate).toList();
|
||||
}
|
||||
|
||||
+96
-6
@@ -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.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,
|
||||
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION_ADJUSTMENT
|
||||
)
|
||||
""")
|
||||
Optional<LocalDate> findLastTransactionDateForReprocessing(@Param("loan") Loan loan);
|
||||
@@ -174,7 +175,7 @@ public interface LoanTransactionRepository extends JpaRepository<LoanTransaction
|
||||
AND lt.typeOf IN :types
|
||||
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);
|
||||
|
||||
@Query("""
|
||||
@@ -184,7 +185,7 @@ public interface LoanTransactionRepository extends JpaRepository<LoanTransaction
|
||||
AND lt.reversed = false
|
||||
AND lt.typeOf IN :types
|
||||
AND lt.dateOf > :transactionDate
|
||||
ORDER BY lt.dateOf
|
||||
ORDER BY lt.dateOf, lt.createdDate, lt.id
|
||||
""")
|
||||
List<LoanTransaction> findNonReversedByLoanAndTypesAndAfterDate(@Param("loan") Loan loan,
|
||||
@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.typeOf = :type
|
||||
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,
|
||||
@Param("transactionDate") LocalDate transactionDate);
|
||||
@@ -219,6 +220,7 @@ public interface LoanTransactionRepository extends JpaRepository<LoanTransaction
|
||||
AND lt.reversed = false
|
||||
AND lt.typeOf IN :types
|
||||
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,
|
||||
@Param("existingTransactionIds") List<Long> existingTransactionIds);
|
||||
@@ -229,6 +231,7 @@ public interface LoanTransactionRepository extends JpaRepository<LoanTransaction
|
||||
WHERE lt.loan = :loan
|
||||
AND lt.reversed = false
|
||||
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);
|
||||
|
||||
@@ -238,6 +241,7 @@ public interface LoanTransactionRepository extends JpaRepository<LoanTransaction
|
||||
WHERE lt.loan = :loan
|
||||
AND lt.reversed = false
|
||||
AND lt.typeOf = :type
|
||||
ORDER BY lt.dateOf, lt.createdDate, lt.id
|
||||
""")
|
||||
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.dateOf >= :date
|
||||
AND lt.typeOf IN :types
|
||||
ORDER BY lt.dateOf
|
||||
ORDER BY lt.dateOf, lt.createdDate, lt.id
|
||||
""")
|
||||
List<LoanTransaction> findNonReversedByLoanAndTypesAndOnOrAfterDate(@Param("loan") Loan loan,
|
||||
@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.typeOf = :type
|
||||
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,
|
||||
@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
|
||||
""")
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
-1
@@ -37,7 +37,6 @@ public class SingleLoanChargeRepaymentScheduleProcessingWrapper {
|
||||
|
||||
public void reprocess(final MonetaryCurrency currency, final LocalDate disbursementDate,
|
||||
final List<LoanRepaymentScheduleInstallment> installments, LoanCharge loanCharge) {
|
||||
Loan loan = loanCharge.getLoan();
|
||||
Money zero = Money.zero(currency);
|
||||
Money totalInterest = zero;
|
||||
Money totalPrincipal = zero;
|
||||
|
||||
+11
-14
@@ -217,11 +217,9 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
loanTransaction.resetDerivedComponents();
|
||||
handleRefund(loanTransaction, currency, installments, charges);
|
||||
} else if (loanTransaction.isCreditBalanceRefund()) {
|
||||
recalculateCreditTransaction(changedTransactionDetail, loanTransaction, currency, installments, transactionsToBeProcessed,
|
||||
overpaymentHolder);
|
||||
recalculateCreditTransaction(changedTransactionDetail, loanTransaction, currency, installments, overpaymentHolder);
|
||||
} else if (loanTransaction.isChargeback()) {
|
||||
recalculateCreditTransaction(changedTransactionDetail, loanTransaction, currency, installments, transactionsToBeProcessed,
|
||||
overpaymentHolder);
|
||||
recalculateCreditTransaction(changedTransactionDetail, loanTransaction, currency, installments, overpaymentHolder);
|
||||
reprocessChargebackTransactionRelation(changedTransactionDetail, transactionsToBeProcessed);
|
||||
} else if (loanTransaction.isChargeOff()) {
|
||||
recalculateChargeOffTransaction(changedTransactionDetail, loanTransaction, currency, installments);
|
||||
@@ -475,7 +473,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
|
||||
protected void reprocessInstallments(LocalDate disbursementDate, List<LoanTransaction> transactions,
|
||||
List<LoanRepaymentScheduleInstallment> installments, MonetaryCurrency currency) {
|
||||
LoanRepaymentScheduleInstallment lastInstallment = installments.get(installments.size() - 1);
|
||||
LoanRepaymentScheduleInstallment lastInstallment = installments.getLast();
|
||||
if (lastInstallment.isAdditional() && lastInstallment.getDue(currency).isZero()) {
|
||||
installments.remove(lastInstallment);
|
||||
}
|
||||
@@ -519,8 +517,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
}
|
||||
|
||||
private void recalculateCreditTransaction(ChangedTransactionDetail changedTransactionDetail, LoanTransaction loanTransaction,
|
||||
MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments, List<LoanTransaction> transactionsToBeProcessed,
|
||||
MoneyHolder overpaymentHolder) {
|
||||
MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments, MoneyHolder overpaymentHolder) {
|
||||
// pass through for new transactions
|
||||
if (loanTransaction.getId() == null) {
|
||||
return;
|
||||
@@ -606,7 +603,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
// New installment will be added (N+1 scenario)
|
||||
if (!loanTransactionMapped) {
|
||||
if (loanTransaction.getTransactionDate().equals(pastDueDate)) {
|
||||
LoanRepaymentScheduleInstallment currentInstallment = installmentToBeProcessed.get(installmentToBeProcessed.size() - 1);
|
||||
LoanRepaymentScheduleInstallment currentInstallment = installmentToBeProcessed.getLast();
|
||||
currentInstallment.addToCreditedPrincipal(transactionAmount.getAmount());
|
||||
currentInstallment.addToPrincipal(transactionDate, transactionAmount);
|
||||
if (repaidAmount.isGreaterThanZero()) {
|
||||
@@ -647,7 +644,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
final Set<LoanCharge> loanPenalties = extractPenaltyCharges(charges);
|
||||
Integer installmentNumber = null;
|
||||
if (loanTransaction.isChargePayment() && installments.size() == 1) {
|
||||
installmentNumber = installments.get(0).getInstallmentNumber();
|
||||
installmentNumber = installments.getFirst().getInstallmentNumber();
|
||||
}
|
||||
|
||||
if (loanTransaction.isNotWaiver() && !loanTransaction.isAccrual() && !loanTransaction.isAccrualActivity()) {
|
||||
@@ -944,12 +941,12 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
return null;
|
||||
}
|
||||
LoanCharge latestCharge = null;
|
||||
List<LoanCharge> chargesWithSpecificDueDate = new ArrayList<>();
|
||||
chargesWithSpecificDueDate.addAll(charges.stream().filter(charge -> charge.isSpecifiedDueDate()).toList());
|
||||
final List<LoanCharge> chargesWithSpecificDueDate = new ArrayList<>(
|
||||
charges.stream().filter(LoanCharge::isSpecifiedDueDate).toList());
|
||||
if (!CollectionUtils.isEmpty(chargesWithSpecificDueDate)) {
|
||||
Collections.sort(chargesWithSpecificDueDate,
|
||||
(charge1, charge2) -> DateUtils.compare(charge1.getEffectiveDueDate(), charge2.getEffectiveDueDate()));
|
||||
latestCharge = chargesWithSpecificDueDate.get(chargesWithSpecificDueDate.size() - 1);
|
||||
chargesWithSpecificDueDate
|
||||
.sort((charge1, charge2) -> DateUtils.compare(charge1.getEffectiveDueDate(), charge2.getEffectiveDueDate()));
|
||||
latestCharge = chargesWithSpecificDueDate.getLast();
|
||||
}
|
||||
return latestCharge;
|
||||
}
|
||||
|
||||
+17
-22
@@ -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.LoanRepaymentScheduleInstallment;
|
||||
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.loanschedule.data.LoanScheduleDTO;
|
||||
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.MultiDisbursementOutstandingAmoutException;
|
||||
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;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanScheduleGenerator {
|
||||
|
||||
private final LoanTransactionService loanTransactionService;
|
||||
private final LoanTransactionRepository loanTransactionRepository;
|
||||
private final CurrencyMapper currencyMapper;
|
||||
|
||||
@Override
|
||||
@@ -404,7 +404,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
|
||||
// this condition is to add the interest from grace period if not
|
||||
// already applied.
|
||||
if (scheduleParams.getTotalOutstandingInterestPaymentDueToGrace().isGreaterThanZero()) {
|
||||
LoanScheduleModelPeriod installment = periods.get(periods.size() - 1);
|
||||
LoanScheduleModelPeriod installment = periods.getLast();
|
||||
installment.addInterestAmount(scheduleParams.getTotalOutstandingInterestPaymentDueToGrace());
|
||||
scheduleParams.addTotalRepaymentExpected(scheduleParams.getTotalOutstandingInterestPaymentDueToGrace());
|
||||
scheduleParams.addTotalCumulativeInterest(scheduleParams.getTotalOutstandingInterestPaymentDueToGrace());
|
||||
@@ -421,7 +421,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
|
||||
if (scheduleParams.getScheduleTillDate() != null) {
|
||||
currentDate = scheduleParams.getScheduleTillDate();
|
||||
}
|
||||
if (scheduleParams.applyInterestRecalculation() && scheduleParams.getLatePaymentMap().size() > 0
|
||||
if (scheduleParams.applyInterestRecalculation() && !scheduleParams.getLatePaymentMap().isEmpty()
|
||||
&& DateUtils.isAfter(currentDate, scheduleParams.getPeriodStartDate())) {
|
||||
Money totalInterest = addInterestOnlyRepaymentScheduleForCurrentDate(mc, loanApplicationTerms, holidayDetailDTO,
|
||||
monetaryCurrency, periods, currentDate, loanRepaymentScheduleTransactionProcessor, transactions, loanCharges,
|
||||
@@ -1345,9 +1345,8 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
|
||||
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) {
|
||||
boolean isAmountChanged = false;
|
||||
if (loanApplicationTerms.getActualFixedEmiAmount() == null && loanApplicationTerms.getInterestMethod().isDecliningBalance()
|
||||
&& loanApplicationTerms.getAmortizationMethod().isEqualInstallment()) {
|
||||
if (periodNumber < loanApplicationTerms.getPrincipalGrace() + 1) {
|
||||
@@ -1356,9 +1355,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
|
||||
Money emiAmount = loanApplicationTerms.pmtForInstallment(getPaymentPeriodsInOneYearCalculator(), outstandingBalance,
|
||||
periodNumber, mc);
|
||||
loanApplicationTerms.setFixedEmiAmount(emiAmount.getAmount());
|
||||
isAmountChanged = true;
|
||||
}
|
||||
return isAmountChanged;
|
||||
}
|
||||
|
||||
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
|
||||
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
|
||||
// the appropriate approved amount or applied for amount is used to show a proposed schedule
|
||||
if (loanApplicationTerms.getApprovedPrincipal().getAmount().compareTo(BigDecimal.ZERO) > 0) {
|
||||
@@ -2521,10 +2518,9 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
|
||||
|
||||
}
|
||||
|
||||
if (retainedInstallments.size() > 0
|
||||
&& retainedInstallments.get(retainedInstallments.size() - 1).getRescheduleInterestPortion() != null) {
|
||||
if (!retainedInstallments.isEmpty() && retainedInstallments.getLast().getRescheduleInterestPortion() != null) {
|
||||
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,
|
||||
loanScheduleParams);
|
||||
@@ -2723,19 +2719,18 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
|
||||
return principalPortionCalculated;
|
||||
}
|
||||
|
||||
private LoanRepaymentScheduleInstallment addLoanRepaymentScheduleInstallment(final List<LoanRepaymentScheduleInstallment> installments,
|
||||
private void addLoanRepaymentScheduleInstallment(final List<LoanRepaymentScheduleInstallment> installments,
|
||||
final LoanScheduleModelPeriod scheduledLoanInstallment) {
|
||||
LoanRepaymentScheduleInstallment installment = null;
|
||||
if (scheduledLoanInstallment.isRepaymentPeriod() || scheduledLoanInstallment.isDownPaymentPeriod()) {
|
||||
installment = new LoanRepaymentScheduleInstallment(null, scheduledLoanInstallment.periodNumber(),
|
||||
scheduledLoanInstallment.periodFromDate(), scheduledLoanInstallment.periodDueDate(),
|
||||
scheduledLoanInstallment.principalDue(), scheduledLoanInstallment.interestDue(),
|
||||
scheduledLoanInstallment.feeChargesDue(), scheduledLoanInstallment.penaltyChargesDue(),
|
||||
scheduledLoanInstallment.isRecalculatedInterestComponent(), scheduledLoanInstallment.getLoanCompoundingDetails(),
|
||||
scheduledLoanInstallment.rescheduleInterestPortion(), scheduledLoanInstallment.isDownPaymentPeriod());
|
||||
final LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(null,
|
||||
scheduledLoanInstallment.periodNumber(), scheduledLoanInstallment.periodFromDate(),
|
||||
scheduledLoanInstallment.periodDueDate(), scheduledLoanInstallment.principalDue(),
|
||||
scheduledLoanInstallment.interestDue(), scheduledLoanInstallment.feeChargesDue(),
|
||||
scheduledLoanInstallment.penaltyChargesDue(), scheduledLoanInstallment.isRecalculatedInterestComponent(),
|
||||
scheduledLoanInstallment.getLoanCompoundingDetails(), scheduledLoanInstallment.rescheduleInterestPortion(),
|
||||
scheduledLoanInstallment.isDownPaymentPeriod());
|
||||
installments.add(installment);
|
||||
}
|
||||
return installment;
|
||||
}
|
||||
|
||||
private LoanScheduleModelPeriod createLoanScheduleModelDownPaymentPeriod(final LoanRepaymentScheduleInstallment installment,
|
||||
@@ -2804,7 +2799,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
|
||||
|
||||
LoanScheduleDTO loanScheduleDTO = rescheduleNextInstallments(mc, loanApplicationTerms, loan, holidayDetailDTO,
|
||||
loanRepaymentScheduleTransactionProcessor, onDate, calculateTill);
|
||||
final List<LoanTransaction> loanTransactions = loanTransactionService.retrieveListOfTransactionsForReprocessing(loan);
|
||||
final List<LoanTransaction> loanTransactions = loanTransactionRepository.findNonReversedTransactionsForReprocessingByLoan(loan);
|
||||
|
||||
loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(loanApplicationTerms.getExpectedDisbursementDate(),
|
||||
loanTransactions, currency, loanScheduleDTO.getInstallments(), loan.getActiveCharges());
|
||||
|
||||
+4
-4
@@ -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.mapper.CurrencyMapper;
|
||||
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.springframework.stereotype.Component;
|
||||
|
||||
@@ -63,9 +63,9 @@ public class CumulativeDecliningBalanceInterestLoanScheduleGenerator extends Abs
|
||||
private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator;
|
||||
|
||||
public CumulativeDecliningBalanceInterestLoanScheduleGenerator(final ScheduledDateGenerator scheduledDateGenerator,
|
||||
final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator, final LoanTransactionService loanTransactionService,
|
||||
final CurrencyMapper currencyMapper) {
|
||||
super(loanTransactionService, currencyMapper);
|
||||
final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator,
|
||||
final LoanTransactionRepository loanTransactionRepository, final CurrencyMapper currencyMapper) {
|
||||
super(loanTransactionRepository, currencyMapper);
|
||||
this.scheduledDateGenerator = scheduledDateGenerator;
|
||||
this.paymentPeriodsInOneYearCalculator = paymentPeriodsInOneYearCalculator;
|
||||
}
|
||||
|
||||
+4
-4
@@ -27,7 +27,7 @@ import java.util.TreeMap;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.organisation.monetary.mapper.CurrencyMapper;
|
||||
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;
|
||||
|
||||
@Component
|
||||
@@ -37,9 +37,9 @@ public class CumulativeFlatInterestLoanScheduleGenerator extends AbstractCumulat
|
||||
private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator;
|
||||
|
||||
public CumulativeFlatInterestLoanScheduleGenerator(final ScheduledDateGenerator scheduledDateGenerator,
|
||||
final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator, final LoanTransactionService loanTransactionService,
|
||||
final CurrencyMapper currencyMapper) {
|
||||
super(loanTransactionService, currencyMapper);
|
||||
final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator,
|
||||
final LoanTransactionRepository loanTransactionRepository, final CurrencyMapper currencyMapper) {
|
||||
super(loanTransactionRepository, currencyMapper);
|
||||
this.scheduledDateGenerator = scheduledDateGenerator;
|
||||
this.paymentPeriodsInOneYearCalculator = paymentPeriodsInOneYearCalculator;
|
||||
}
|
||||
|
||||
+13
-7
@@ -19,18 +19,24 @@
|
||||
package org.apache.fineract.portfolio.loanaccount.serialization;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.portfolio.charge.exception.LoanChargeCannotBeAddedException;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
|
||||
import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanStateTransitionException;
|
||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanChargeRefundException;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public final class LoanChargeValidator {
|
||||
|
||||
private final LoanTransactionRepository loanTransactionRepository;
|
||||
|
||||
public void validateLoanIsNotClosed(final Loan loan, final LoanCharge loanCharge) {
|
||||
if (loan.isClosed()) {
|
||||
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,
|
||||
final String reversedOrCreated) {
|
||||
if (repaymentTransaction.isRepaymentLikeType() && !repaymentTransaction.isChargeRefund()) {
|
||||
for (LoanTransaction txn : loan.getLoanTransactions()) {
|
||||
if (txn.isChargeRefund() && DateUtils.isBefore(repaymentTransaction.getTransactionDate(), txn.getTransactionDate())) {
|
||||
final String errorMessage = "loan.transaction.cant.be." + reversedOrCreated + ".because.later.charge.refund.exists";
|
||||
final String details = "Loan Transaction: " + loan.getId() + " Can't be " + reversedOrCreated
|
||||
+ " because a Later Charge Refund Exists.";
|
||||
throw new LoanChargeRefundException(errorMessage, details);
|
||||
}
|
||||
final boolean existsChargeRefund = loanTransactionRepository.existsNonReversedByLoanAndTypeAndAfterDate(loan,
|
||||
LoanTransactionType.CHARGE_REFUND, repaymentTransaction.getTransactionDate());
|
||||
if (existsChargeRefund) {
|
||||
final String errorMessage = "loan.transaction.cant.be." + reversedOrCreated + ".because.later.charge.refund.exists";
|
||||
final String details = "Loan Transaction: " + loan.getId() + " Can't be " + reversedOrCreated
|
||||
+ " because a Later Charge Refund Exists.";
|
||||
throw new LoanChargeRefundException(errorMessage, details);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+61
-2
@@ -21,10 +21,12 @@ package org.apache.fineract.portfolio.loanaccount.service;
|
||||
import jakarta.persistence.FlushModeType;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.persistence.FlushModeHandler;
|
||||
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.Money;
|
||||
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.LoanRepaymentScheduleProcessingWrapper;
|
||||
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.springframework.stereotype.Service;
|
||||
|
||||
@@ -43,6 +47,7 @@ public class LoanBalanceService {
|
||||
|
||||
private final CapitalizedIncomeBalanceService capitalizedIncomeBalanceService;
|
||||
private final FlushModeHandler flushModeHandler;
|
||||
private final LoanTransactionRepository loanTransactionRepository;
|
||||
|
||||
public Money calculateTotalOverpayment(final Loan loan) {
|
||||
Money totalPaidInRepayments = loan.getTotalPaidInRepayments();
|
||||
@@ -107,7 +112,7 @@ public class LoanBalanceService {
|
||||
loan.setTotalOverpaid(overpaidBy.getAmountDefaultedToNullIfZero());
|
||||
}
|
||||
|
||||
final Money recoveredAmount = loan.calculateTotalRecoveredPayments();
|
||||
final Money recoveredAmount = calculateTotalRecoveredPayments(loan);
|
||||
loan.setTotalRecovered(recoveredAmount.getAmountDefaultedToNullIfZero());
|
||||
|
||||
final Money principal = loan.getLoanRepaymentScheduleDetail().getPrincipal();
|
||||
@@ -115,7 +120,61 @@ public class LoanBalanceService {
|
||||
final Money capitalizedIncomeAdjustment = capitalizedIncomeBalanceService.calculateCapitalizedIncomeAdjustment(loan);
|
||||
loan.getSummary().updateSummary(loan.getCurrency(), principal, loan.getRepaymentScheduleInstallments(), loan.getLoanCharges(),
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user