You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by ar...@apache.org on 2022/11/02 14:37:45 UTC
[fineract] branch develop updated: Delinquency classification including Chargeback transactions
This is an automated email from the ASF dual-hosted git repository.
arnold pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/develop by this push:
new 90f1eccd4 Delinquency classification including Chargeback transactions
90f1eccd4 is described below
commit 90f1eccd4b70d9035c90055fc07126998c99d758
Author: Jose Alberto Hernandez <al...@MacBook-Pro.local>
AuthorDate: Thu Oct 13 13:31:40 2022 -0500
Delinquency classification including Chargeback transactions
---
...etailsReadPlatformServiceJpaRepositoryImpl.java | 7 +-
.../service/DelinquencyReadPlatformService.java | 3 +
.../DelinquencyReadPlatformServiceImpl.java | 100 +++++
.../service/DelinquencyWritePlatformService.java | 12 +-
.../DelinquencyWritePlatformServiceImpl.java | 125 ++++--
.../loanaccount/api/LoansApiResource.java | 4 +-
.../portfolio/loanaccount/data/CollectionData.java | 38 +-
.../loanaccount/data/LoanAccountData.java | 6 +
.../data/LoanScheduleDelinquencyData.java | 13 +-
.../portfolio/loanaccount/domain/Loan.java | 75 ++--
.../domain/LoanAccountDomainServiceJpa.java | 15 +-
.../domain/LoanRepaymentScheduleInstallment.java | 6 +-
...LoanRepaymentScheduleInstallmentRepository.java | 39 +-
.../domain/LoanTransactionRepository.java | 19 +
...ctionToRepaymentScheduleMappingRepository.java} | 7 +-
...tLoanRepaymentScheduleTransactionProcessor.java | 2 +-
.../SetLoanDelinquencyTagsConfig.java | 9 +-
.../SetLoanDelinquencyTagsTasklet.java | 62 ++-
.../domain/AbstractLoanScheduleGenerator.java | 2 +-
.../service/LoanReadPlatformService.java | 6 -
.../service/LoanReadPlatformServiceImpl.java | 123 +-----
.../LoanWritePlatformServiceJpaRepositoryImpl.java | 2 +-
.../savings/domain/FixedDepositAccount.java | 4 +-
.../savings/domain/RecurringDepositAccount.java | 6 +-
.../portfolio/savings/domain/SavingsAccount.java | 20 +-
.../SavingsAccountInterestPostingServiceImpl.java | 8 +-
.../db/changelog/tenant/changelog-tenant.xml | 1 +
.../0066_delinquency_classification_chargeback.xml | 33 ++
.../DelinquencyAndChargebackIntegrationTest.java | 192 ++++++++++
.../DelinquencyBucketsIntegrationTest.java | 421 ++++++++++-----------
.../LoanTransactionChargebackTest.java | 130 ++++---
.../common/loans/LoanTransactionHelper.java | 57 ++-
.../common/products/DelinquencyBucketsHelper.java | 66 +++-
.../common/products/DelinquencyRangesHelper.java | 2 +-
34 files changed, 1061 insertions(+), 554 deletions(-)
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accountdetails/service/AccountDetailsReadPlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accountdetails/service/AccountDetailsReadPlatformServiceJpaRepositoryImpl.java
index e783e7bfb..ff1f30313 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accountdetails/service/AccountDetailsReadPlatformServiceJpaRepositoryImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accountdetails/service/AccountDetailsReadPlatformServiceJpaRepositoryImpl.java
@@ -478,7 +478,7 @@ public class AccountDetailsReadPlatformServiceJpaRepositoryImpl implements Accou
.append(" l.closedon_date as closedOnDate,")
.append(" cbu.username as closedByUsername, cbu.firstname as closedByFirstname, cbu.lastname as closedByLastname,")
- .append(" la.overdue_since_date_derived as overdueSinceDate,")
+ .append(" la.overdue_since_date_derived as overdueSinceDate, ")
.append(" l.writtenoffon_date as writtenOffOnDate, l.expected_maturedon_date as expectedMaturityDate")
.append(" from m_loan l ").append("LEFT JOIN m_product_loan AS lp ON lp.id = l.product_id")
@@ -550,10 +550,7 @@ public class AccountDetailsReadPlatformServiceJpaRepositoryImpl implements Accou
final LocalDate expectedMaturityDate = JdbcSupport.getLocalDate(rs, "expectedMaturityDate");
final LocalDate overdueSinceDate = JdbcSupport.getLocalDate(rs, "overdueSinceDate");
- Boolean inArrears = true;
- if (overdueSinceDate == null) {
- inArrears = false;
- }
+ Boolean inArrears = (overdueSinceDate != null);
final LoanApplicationTimelineData timeline = new LoanApplicationTimelineData(submittedOnDate, submittedByUsername,
submittedByFirstname, submittedByLastname, rejectedOnDate, rejectedByUsername, rejectedByFirstname, rejectedByLastname,
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformService.java
index 9206c615f..b1c2dad11 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformService.java
@@ -22,6 +22,7 @@ import java.util.Collection;
import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData;
import org.apache.fineract.portfolio.delinquency.data.LoanDelinquencyTagHistoryData;
+import org.apache.fineract.portfolio.loanaccount.data.CollectionData;
public interface DelinquencyReadPlatformService {
@@ -37,4 +38,6 @@ public interface DelinquencyReadPlatformService {
Collection<LoanDelinquencyTagHistoryData> retrieveDelinquencyRangeHistory(Long loanId);
+ CollectionData calculateLoanCollectionData(Long loanId);
+
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformServiceImpl.java
index 023970b39..e1585d9d5 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformServiceImpl.java
@@ -18,10 +18,15 @@
*/
package org.apache.fineract.portfolio.delinquency.service;
+import java.math.BigDecimal;
+import java.time.LocalDate;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
+import java.util.Set;
import lombok.RequiredArgsConstructor;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData;
import org.apache.fineract.portfolio.delinquency.data.LoanDelinquencyTagHistoryData;
@@ -34,8 +39,13 @@ import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyTagHistor
import org.apache.fineract.portfolio.delinquency.mapper.DelinquencyBucketMapper;
import org.apache.fineract.portfolio.delinquency.mapper.DelinquencyRangeMapper;
import org.apache.fineract.portfolio.delinquency.mapper.LoanDelinquencyTagMapper;
+import org.apache.fineract.portfolio.loanaccount.data.CollectionData;
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.LoanTransactionToRepaymentScheduleMapping;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -97,4 +107,94 @@ public class DelinquencyReadPlatformServiceImpl implements DelinquencyReadPlatfo
return mapperLoanDelinquencyTagHistory.map(loanDelinquencyTagData);
}
+ @Override
+ public CollectionData calculateLoanCollectionData(final Long loanId) {
+ final LocalDate businessDate = DateUtils.getBusinessLocalDate();
+ final Optional<Loan> optLoan = this.loanRepository.findById(loanId);
+
+ CollectionData collectionData = CollectionData.template();
+ if (optLoan.isPresent()) {
+ final Loan loan = optLoan.get();
+
+ collectionData.setAvailableDisbursementAmount(loan.getApprovedPrincipal().subtract(loan.getDisbursedAmount()));
+ collectionData.setNextPaymentDueDate(loan.possibleNextRepaymentDate());
+
+ final MonetaryCurrency currency = loan.getCurrency();
+ BigDecimal delinquentAmount = BigDecimal.ZERO;
+ // Overdue Days calculation
+ Long overdueDays = 0L;
+ LocalDate overdueSinceDate = null;
+ final List<LoanTransaction> chargebackTransactions = loan.retrieveListOfTransactionsByType(LoanTransactionType.CHARGEBACK);
+ for (LoanTransaction loanTransaction : chargebackTransactions) {
+ Set<LoanTransactionToRepaymentScheduleMapping> loanTransactionToRepaymentScheduleMappings = loanTransaction
+ .getLoanTransactionToRepaymentScheduleMappings();
+ LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = loanTransactionToRepaymentScheduleMappings
+ .iterator().next();
+ if (!loanTransactionToRepaymentScheduleMapping.getLoanRepaymentScheduleInstallment().isPrincipalCompleted(currency)) {
+ overdueSinceDate = loanTransaction.getTransactionDate();
+ break;
+ }
+ }
+
+ final List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
+ if (overdueSinceDate == null) {
+ for (LoanRepaymentScheduleInstallment installment : installments) {
+ if (installment.isNotFullyPaidOff()) {
+ overdueSinceDate = installment.getDueDate();
+ break;
+ }
+ }
+ }
+
+ Integer graceDays = 0;
+ if (overdueSinceDate != null) {
+ if (loan.loanProduct().getLoanProductConfigurableAttributes().getGraceOnArrearsAgingBoolean()) {
+ graceDays = loan.loanProduct().getLoanProductRelatedDetail().getGraceOnArrearsAgeing();
+ if (graceDays == null) {
+ graceDays = 0;
+ }
+ overdueSinceDate = overdueSinceDate.plusDays(graceDays);
+ }
+ overdueDays = DateUtils.getDifferenceInDays(overdueSinceDate, businessDate);
+ if (overdueDays < 0) {
+ overdueDays = 0L;
+ }
+ collectionData.setDelinquentDate(overdueSinceDate);
+ }
+
+ collectionData.setPastDueDays(overdueDays);
+ if (overdueDays > 0) {
+ collectionData.setDelinquentDays(overdueDays - graceDays);
+ }
+
+ final LoanTransaction lastRepaymenTransaction = loan.getLastRepaymentTransaction();
+ if (lastRepaymenTransaction != null) {
+ collectionData.setLastPaymentDate(lastRepaymenTransaction.getTransactionDate());
+ collectionData.setLastPaymentAmount(lastRepaymenTransaction.getAmount());
+ }
+
+ // Calculate Delinquency Amount
+ for (LoanRepaymentScheduleInstallment installment : installments) {
+ if (installment.getDueDate().isBefore(businessDate)) {
+ delinquentAmount = delinquentAmount.add(installment.getTotalOutstanding(currency).getAmount());
+ }
+ }
+ for (LoanTransaction loanTransaction : chargebackTransactions) {
+ Set<LoanTransactionToRepaymentScheduleMapping> loanTransactionToRepaymentScheduleMappings = loanTransaction
+ .getLoanTransactionToRepaymentScheduleMappings();
+ for (LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping : loanTransactionToRepaymentScheduleMappings) {
+ LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = loanTransactionToRepaymentScheduleMapping
+ .getLoanRepaymentScheduleInstallment();
+ if (!loanRepaymentScheduleInstallment.isPrincipalCompleted(currency)) {
+ delinquentAmount = delinquentAmount.add(loanTransaction.getAmount());
+ }
+ }
+ }
+
+ collectionData.setDelinquentAmount(delinquentAmount);
+ }
+
+ return collectionData;
+ }
+
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformService.java
index 8795ace83..d5596ce72 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformService.java
@@ -18,8 +18,10 @@
*/
package org.apache.fineract.portfolio.delinquency.service;
+import java.time.LocalDate;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
public interface DelinquencyWritePlatformService {
@@ -38,12 +40,14 @@ public interface DelinquencyWritePlatformService {
CommandProcessingResult applyDelinquencyTagToLoan(Long loanId, JsonCommand command);
- void applyDelinquencyTagToLoan(Long loanId, Long ageDays);
-
- void applyDelinquencyTagToLoan(Loan loan, Long ageDays);
-
void removeDelinquencyTagToLoan(Loan loan);
void cleanLoanDelinquencyTags(Loan loan);
+ LoanScheduleDelinquencyData calculateDelinquencyData(LoanScheduleDelinquencyData loanScheduleDelinquencyData);
+
+ void applyDelinquencyTagToLoan(LoanScheduleDelinquencyData loanDelinquencyData);
+
+ LocalDate getOverdueSinceDate(Loan loan, LocalDate businessDate, Integer graceOnArrearAgeing);
+
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformServiceImpl.java
index 4d1dc384c..b1d543a7d 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformServiceImpl.java
@@ -33,6 +33,7 @@ import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.portfolio.delinquency.api.DelinquencyApiConstants;
import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData;
@@ -48,10 +49,13 @@ import org.apache.fineract.portfolio.delinquency.exception.DelinquencyBucketAges
import org.apache.fineract.portfolio.delinquency.exception.DelinquencyRangeInvalidAgesException;
import org.apache.fineract.portfolio.delinquency.validator.DelinquencyBucketParseAndValidator;
import org.apache.fineract.portfolio.delinquency.validator.DelinquencyRangeParseAndValidator;
+import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
-import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallmentRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
import org.springframework.stereotype.Service;
@@ -69,7 +73,6 @@ public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlat
private final LoanDelinquencyTagHistoryRepository loanDelinquencyTagRepository;
private final LoanRepositoryWrapper loanRepository;
private final LoanProductRepository loanProductRepository;
- private final LoanRepaymentScheduleInstallmentRepository loanRepaymentScheduleInstallmentRepository;
@Override
public CommandProcessingResult createDelinquencyRange(JsonCommand command) {
@@ -100,8 +103,9 @@ public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlat
"Data integrity issue with resource: " + delinquencyRange.getId());
}
repositoryRange.delete(delinquencyRange);
+ return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyRange.getId()).build();
}
- return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyRange.getId()).build();
+ return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyRangeId).build();
}
@Override
@@ -135,7 +139,71 @@ public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlat
}
repositoryBucket.delete(delinquencyBucket);
}
- return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyBucket.getId()).build();
+ return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyBucketId).build();
+ }
+
+ @Override
+ public LoanScheduleDelinquencyData calculateDelinquencyData(LoanScheduleDelinquencyData loanScheduleDelinquencyData) {
+ final LocalDate businessDate = DateUtils.getBusinessLocalDate();
+ Loan loan = loanScheduleDelinquencyData.getLoan();
+ if (loan == null) {
+ loan = this.loanRepository.findOneWithNotFoundDetection(loanScheduleDelinquencyData.getLoanId());
+ }
+
+ final Integer graceOnArrearAgeing = loan.getLoanProduct().getLoanProductRelatedDetail().getGraceOnArrearsAgeing();
+ final LocalDate overdueSinceDate = getOverdueSinceDate(loan, businessDate, graceOnArrearAgeing);
+ final Long overdueDays = calculateOverdueDays(businessDate, overdueSinceDate);
+ return new LoanScheduleDelinquencyData(loan.getId(), overdueSinceDate, overdueDays, loan);
+ }
+
+ @Override
+ public LocalDate getOverdueSinceDate(final Loan loan, final LocalDate businessDate, final Integer graceOnArrearAgeing) {
+ LoanRepaymentScheduleInstallment loanRepaymentSchedule = null;
+ for (LoanRepaymentScheduleInstallment installment : loan.getRepaymentScheduleInstallments()) {
+ if (installment.getDueDate().isBefore(businessDate) && installment.isNotFullyPaidOff()) {
+ loanRepaymentSchedule = installment;
+ break;
+ }
+ }
+
+ LocalDate overdueSinceDate = null;
+ final MonetaryCurrency loanCurrency = loan.getCurrency();
+ final List<LoanTransaction> loanTransactions = loan.retrieveListOfTransactionsByType(LoanTransactionType.CHARGEBACK);
+ if (loanRepaymentSchedule != null) {
+ // Default Due date
+ overdueSinceDate = loanRepaymentSchedule.getDueDate();
+ }
+
+ // If there is some Loan Transaction Chargeback
+ for (LoanTransaction loanTransaction : loanTransactions) {
+ if (loanTransaction.getLoanTransactionToRepaymentScheduleMappings().iterator().hasNext()) {
+ final LoanTransactionToRepaymentScheduleMapping transactionMapping = loanTransaction
+ .getLoanTransactionToRepaymentScheduleMappings().iterator().next();
+
+ if (transactionMapping != null
+ && !transactionMapping.getLoanRepaymentScheduleInstallment().isPrincipalCompleted(loanCurrency)) {
+ overdueSinceDate = loanTransaction.getTransactionDate();
+ break;
+ }
+ }
+ }
+
+ // Include grace days If It's defined
+ if (overdueSinceDate != null && graceOnArrearAgeing != null) {
+ overdueSinceDate = overdueSinceDate.plusDays(graceOnArrearAgeing.longValue());
+ }
+ return overdueSinceDate;
+ }
+
+ private Long calculateOverdueDays(final LocalDate businessDate, LocalDate overdueSinceDate) {
+ Long overdueDays = 0L;
+ if (overdueSinceDate != null) {
+ overdueDays = DateUtils.getDifferenceInDays(overdueSinceDate, businessDate);
+ if (overdueDays < 0) {
+ overdueDays = 0L;
+ }
+ }
+ return overdueDays;
}
@Override
@@ -146,28 +214,31 @@ public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlat
final DelinquencyBucket delinquencyBucket = loan.getLoanProduct().getDelinquencyBucket();
if (delinquencyBucket != null) {
final LocalDate businessDate = DateUtils.getBusinessLocalDate();
- LoanRepaymentScheduleInstallment loanRepaymentSchedule = this.loanRepaymentScheduleInstallmentRepository
- .findFirstByLoanAndDueDateLessThanEqualAndObligationsMetOnDateOrderByDueDate(loan, businessDate, null);
- Long overdueDays = 0L;
- if (loanRepaymentSchedule != null) {
- overdueDays = DateUtils.getDifferenceInDays(loanRepaymentSchedule.getDueDate(), businessDate);
- }
+ final Integer graceOnArrearAgeing = loan.getLoanProduct().getLoanProductRelatedDetail().getGraceOnArrearsAgeing();
+ final LocalDate overdueSinceDate = getOverdueSinceDate(loan, businessDate, graceOnArrearAgeing);
+
+ final Long overdueDays = calculateOverdueDays(businessDate, overdueSinceDate);
changes = lookUpDelinquencyRange(loan, delinquencyBucket, overdueDays);
}
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(loan.getId()).with(changes).build();
}
@Override
- public void applyDelinquencyTagToLoan(Long loanId, Long ageDays) {
- final Loan loan = this.loanRepository.findOneWithNotFoundDetection(loanId);
- applyDelinquencyTagToLoan(loan, ageDays);
- }
-
- @Override
- public void applyDelinquencyTagToLoan(final Loan loan, Long ageDays) {
+ public void applyDelinquencyTagToLoan(LoanScheduleDelinquencyData loanDelinquencyData) {
+ final Loan loan = loanDelinquencyData.getLoan();
if (loan.hasDelinquencyBucket()) {
- lookUpDelinquencyRange(loan, loan.getLoanProduct().getDelinquencyBucket(), ageDays);
+ final LocalDate businessDate = DateUtils.getBusinessLocalDate();
+ final DelinquencyBucket delinquencyBucket = loan.getLoanProduct().getDelinquencyBucket();
+ final Integer graceOnArrearAgeing = loan.getLoanProduct().getLoanProductRelatedDetail().getGraceOnArrearsAgeing();
+
+ LocalDate overdueSinceDate = loanDelinquencyData.getOverdueSinceDate();
+ if (overdueSinceDate == null) {
+ getOverdueSinceDate(loan, businessDate, graceOnArrearAgeing);
+ }
+
+ final Long overdueDays = calculateOverdueDays(businessDate, overdueSinceDate);
+ lookUpDelinquencyRange(loan, delinquencyBucket, overdueDays);
}
}
@@ -289,11 +360,11 @@ public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlat
}
}
- private Map<String, Object> lookUpDelinquencyRange(final Loan loan, final DelinquencyBucket delinquencyBucket, long ageDays) {
+ private Map<String, Object> lookUpDelinquencyRange(final Loan loan, final DelinquencyBucket delinquencyBucket, long overdueDays) {
Map<String, Object> changes = new HashMap<>();
- if (ageDays <= 0) { // No Delinquency
- log.debug("Loan {} without delinquency range with {} days", loan.getId(), ageDays);
+ if (overdueDays <= 0) { // No Delinquency
+ log.debug("Loan {} without delinquency range with {} days", loan.getId(), overdueDays);
changes = setLoanDelinquencyTag(loan, null);
} else {
@@ -302,27 +373,27 @@ public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlat
for (final DelinquencyRange delinquencyRange : ranges) {
if (delinquencyRange.getMaximumAgeDays() == null) { // Last Range in the Bucket
- if (delinquencyRange.getMinimumAgeDays() <= ageDays) {
+ if (delinquencyRange.getMinimumAgeDays() <= overdueDays) {
log.debug("Loan {} with delinquency range {} with {} days", loan.getId(), delinquencyRange.getClassification(),
- ageDays);
+ overdueDays);
changes = setLoanDelinquencyTag(loan, delinquencyRange.getId());
break;
}
} else {
- if (delinquencyRange.getMinimumAgeDays() <= ageDays && delinquencyRange.getMaximumAgeDays() >= ageDays) {
+ if (delinquencyRange.getMinimumAgeDays() <= overdueDays && delinquencyRange.getMaximumAgeDays() >= overdueDays) {
log.debug("Loan {} with delinquency range {} with {} days", loan.getId(), delinquencyRange.getClassification(),
- ageDays);
+ overdueDays);
changes = setLoanDelinquencyTag(loan, delinquencyRange.getId());
break;
}
}
}
}
- changes.put("ageDays", ageDays);
+ changes.put("overdueDays", overdueDays);
return changes;
}
- public Map<String, Object> setLoanDelinquencyTag(Loan loan, Long delinquencyRangeId) {
+ private Map<String, Object> setLoanDelinquencyTag(Loan loan, Long delinquencyRangeId) {
Map<String, Object> changes = new HashMap<>();
List<LoanDelinquencyTagHistory> loanDelinquencyTagHistory = new ArrayList<>();
final LocalDate transactionDate = DateUtils.getBusinessLocalDate();
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java
index 8468e2e01..95d6a082a 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java
@@ -610,8 +610,8 @@ public class LoansApiResource {
if (associationParameters.contains(DataTableApiConstant.collectionAssociateParamName)) {
mandatoryResponseParameters.add(DataTableApiConstant.collectionAssociateParamName);
- if (LoanStatus.fromInt(loanBasicDetails.getStatus().getId().intValue()).isActive()) {
- collectionData = this.loanReadPlatformService.retrieveLoanCollectionData(loanId);
+ if (loanBasicDetails.isActive()) {
+ collectionData = this.delinquencyReadPlatformService.calculateLoanCollectionData(loanId);
}
}
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/CollectionData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/CollectionData.java
index 37882f00e..45c2545cc 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/CollectionData.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/CollectionData.java
@@ -20,45 +20,27 @@ package org.apache.fineract.portfolio.loanaccount.data;
import java.math.BigDecimal;
import java.time.LocalDate;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-import lombok.experimental.Accessors;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
-@Data
-@NoArgsConstructor
-@Accessors(chain = true)
+@AllArgsConstructor
+@Getter
+@Setter
public final class CollectionData {
private BigDecimal availableDisbursementAmount;
- private int pastDueDays;
+ private Long pastDueDays;
private LocalDate nextPaymentDueDate;
- private int delinquentDays;
+ private Long delinquentDays;
private LocalDate delinquentDate;
private BigDecimal delinquentAmount;
private LocalDate lastPaymentDate;
private BigDecimal lastPaymentAmount;
- private CollectionData(BigDecimal availableDisbursementAmount, int pastDueDays, LocalDate nextPaymentDueDate, int delinquentDays,
- LocalDate delinquentDate, BigDecimal delinquentAmount, LocalDate lastPaymentDate, BigDecimal lastPaymentAmount) {
- this.availableDisbursementAmount = availableDisbursementAmount;
- this.pastDueDays = pastDueDays;
- this.nextPaymentDueDate = nextPaymentDueDate;
- this.delinquentDays = delinquentDays;
- this.delinquentDate = delinquentDate;
- this.delinquentAmount = delinquentAmount;
- this.lastPaymentDate = lastPaymentDate;
- this.lastPaymentAmount = lastPaymentAmount;
- }
-
- public static CollectionData instance(BigDecimal availableDisbursementAmount, int pastDueDays, LocalDate nextPaymentDueDate,
- int delinquentDays, LocalDate delinquentDate, BigDecimal delinquentAmount, LocalDate lastPaymentDate,
- BigDecimal lastPaymentAmount) {
- return new CollectionData(availableDisbursementAmount, pastDueDays, nextPaymentDueDate, delinquentDays, delinquentDate,
- delinquentAmount, lastPaymentDate, lastPaymentAmount);
- }
-
public static CollectionData template() {
final BigDecimal zero = BigDecimal.ZERO;
- return new CollectionData(zero, 0, null, 0, null, zero, null, zero);
+ return new CollectionData(zero, 0L, null, 0L, null, zero, null, zero);
}
+
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java
index 98769f2bc..5b149027b 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java
@@ -44,6 +44,7 @@ import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData;
import org.apache.fineract.portfolio.floatingrates.data.InterestRatePeriodData;
import org.apache.fineract.portfolio.fund.data.FundData;
import org.apache.fineract.portfolio.group.data.GroupGeneralData;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
import org.apache.fineract.portfolio.loanaccount.guarantor.data.GuarantorData;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
import org.apache.fineract.portfolio.loanproduct.data.LoanProductBorrowerCycleVariationData;
@@ -56,6 +57,7 @@ import org.apache.fineract.portfolio.rate.data.RateData;
@Data
@NoArgsConstructor
@Accessors(chain = true)
+@SuppressWarnings("ObjectToString")
public class LoanAccountData {
// basic loan details
@@ -1167,4 +1169,8 @@ public class LoanAccountData {
}
return null;
}
+
+ public boolean isActive() {
+ return LoanStatus.fromInt(getStatus().getId().intValue()).isActive();
+ }
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanScheduleDelinquencyData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanScheduleDelinquencyData.java
index 4458ae419..7d3fad62a 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanScheduleDelinquencyData.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanScheduleDelinquencyData.java
@@ -22,14 +22,19 @@ import java.io.Serializable;
import java.time.LocalDate;
import lombok.AllArgsConstructor;
import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
@AllArgsConstructor
+@NoArgsConstructor
@Getter
+@Setter
public class LoanScheduleDelinquencyData implements Serializable {
- private final Long loanId;
- private final Long productId;
- private final LocalDate dueDate;
- private final Long ageDays;
+ private Long loanId;
+ private LocalDate overdueSinceDate;
+ private Long overdueDays;
+ private Loan loan;
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
index ac3f0a932..6b5779d9f 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
@@ -672,7 +672,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
ChangedTransactionDetail changedTransactionDetail = null;
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategyCode);
- final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
+ final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement();
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(),
allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges());
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
@@ -832,7 +832,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
* Consider removing this block of code or logically completing it for the future by getting the list of
* affected Transactions
***/
- final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
+ final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement();
loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement,
getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges());
}
@@ -900,7 +900,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
* Consider removing this block of code or logically completing it for the future by getting the list of
* affected Transactions
***/
- final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
+ final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement();
loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement,
getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges());
} else {
@@ -1087,7 +1087,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
* Consider removing this block of code or logically completing it for the future by getting the list of
* affected Transactions
***/
- final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
+ final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement();
loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement,
getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges());
} else {
@@ -1284,7 +1284,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
* method updates accrual derived fields on installments and reverse the unprocessed transactions
*/
private void applyAccurals() {
- Collection<LoanTransaction> accruals = retreiveListOfAccrualTransactions();
+ Collection<LoanTransaction> accruals = retrieveListOfAccrualTransactions();
if (!accruals.isEmpty()) {
if (isPeriodicAccrualAccountingEnabledOnLoanProduct()) {
applyPeriodicAccruals(accruals);
@@ -2634,7 +2634,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
private ChangedTransactionDetail reprocessTransactionForDisbursement() {
ChangedTransactionDetail changedTransactionDetail = null;
if (this.loanProduct.isMultiDisburseLoan()) {
- final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
+ final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement();
if (!allNonContraTransactionsPostDisbursement.isEmpty()) {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategyCode);
@@ -3250,7 +3250,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
}
- final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
+ final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement();
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(),
allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges());
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
@@ -3300,7 +3300,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
return installment;
}
- private List<LoanTransaction> retreiveListOfIncomePostingTransactions() {
+ private List<LoanTransaction> retrieveListOfIncomePostingTransactions() {
final List<LoanTransaction> incomePostTransactions = new ArrayList<>();
List<LoanTransaction> trans = getLoanTransactions();
for (final LoanTransaction transaction : trans) {
@@ -3313,7 +3313,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
return incomePostTransactions;
}
- private List<LoanTransaction> retreiveListOfTransactionsPostDisbursement() {
+ private List<LoanTransaction> retrieveListOfTransactionsPostDisbursement() {
final List<LoanTransaction> repaymentsOrWaivers = new ArrayList<>();
List<LoanTransaction> trans = getLoanTransactions();
for (final LoanTransaction transaction : trans) {
@@ -3326,7 +3326,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
return repaymentsOrWaivers;
}
- public List<LoanTransaction> retreiveListOfTransactionsPostDisbursementExcludeAccruals() {
+ public List<LoanTransaction> retrieveListOfTransactionsPostDisbursementExcludeAccruals() {
final List<LoanTransaction> repaymentsOrWaivers = new ArrayList<>();
for (final LoanTransaction transaction : this.loanTransactions) {
if (transaction.isNotReversed()
@@ -3340,7 +3340,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
return repaymentsOrWaivers;
}
- private List<LoanTransaction> retreiveListOfTransactionsExcludeAccruals() {
+ private List<LoanTransaction> retrieveListOfTransactionsExcludeAccruals() {
final List<LoanTransaction> repaymentsOrWaivers = new ArrayList<>();
for (final LoanTransaction transaction : this.loanTransactions) {
if (transaction.isNotReversed() && !(transaction.isAccrual() || transaction.isNonMonetaryTransaction())) {
@@ -3352,7 +3352,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
return repaymentsOrWaivers;
}
- private List<LoanTransaction> retreiveListOfAccrualTransactions() {
+ private List<LoanTransaction> retrieveListOfAccrualTransactions() {
final List<LoanTransaction> transactions = new ArrayList<>();
for (final LoanTransaction transaction : this.loanTransactions) {
if (transaction.isNotReversed() && transaction.isAccrual()) {
@@ -3364,6 +3364,18 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
return transactions;
}
+ public List<LoanTransaction> retrieveListOfTransactionsByType(LoanTransactionType transactionType) {
+ final List<LoanTransaction> transactions = new ArrayList<>();
+ for (final LoanTransaction transaction : this.loanTransactions) {
+ if (transaction.isNotReversed() && transaction.getTypeOf().equals(transactionType)) {
+ transactions.add(transaction);
+ }
+ }
+ final LoanTransactionComparator transactionComparator = new LoanTransactionComparator();
+ Collections.sort(transactions, transactionComparator);
+ return transactions;
+ }
+
private boolean doPostLoanTransactionChecks(final LocalDate transactionDate,
final LoanLifecycleStateMachine loanLifecycleStateMachine) {
boolean statusChanged = false;
@@ -3403,12 +3415,12 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
if (this.loanInterestRecalculationDetails != null && this.loanInterestRecalculationDetails.isCompoundingToBePostedAsTransaction()
&& this.getStatus().isClosedObligationsMet()) {
LocalDate closedDate = this.getClosedOnDate();
- reverseTransactionsOnOrAfter(retreiveListOfIncomePostingTransactions(), closedDate);
- reverseTransactionsOnOrAfter(retreiveListOfAccrualTransactions(), closedDate);
+ reverseTransactionsOnOrAfter(retrieveListOfIncomePostingTransactions(), closedDate);
+ reverseTransactionsOnOrAfter(retrieveListOfAccrualTransactions(), closedDate);
HashMap<String, BigDecimal> cumulativeIncomeFromInstallments = new HashMap<>();
determineCumulativeIncomeFromInstallments(cumulativeIncomeFromInstallments);
HashMap<String, BigDecimal> cumulativeIncomeFromIncomePosting = new HashMap<>();
- determineCumulativeIncomeDetails(retreiveListOfIncomePostingTransactions(), cumulativeIncomeFromIncomePosting);
+ determineCumulativeIncomeDetails(retrieveListOfIncomePostingTransactions(), cumulativeIncomeFromIncomePosting);
BigDecimal interestToPost = cumulativeIncomeFromInstallments.get(INTEREST)
.subtract(cumulativeIncomeFromIncomePosting.get(INTEREST));
BigDecimal feeToPost = cumulativeIncomeFromInstallments.get(FEE).subtract(cumulativeIncomeFromIncomePosting.get(FEE));
@@ -3419,7 +3431,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
interestToPost, feeToPost, penaltyToPost);
addLoanTransaction(finalIncomeTransaction);
if (isPeriodicAccrualAccountingEnabledOnLoanProduct()) {
- List<LoanTransaction> updatedAccrualTransactions = retreiveListOfAccrualTransactions();
+ List<LoanTransaction> updatedAccrualTransactions = retrieveListOfAccrualTransactions();
LocalDate lastAccruedDate = this.getDisbursementDate();
if (!updatedAccrualTransactions.isEmpty()) {
lastAccruedDate = updatedAccrualTransactions.get(updatedAccrualTransactions.size() - 1).getTransactionDate();
@@ -3646,7 +3658,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
loanLifecycleStateMachine.transition(LoanEvent.WRITE_OFF_OUTSTANDING_UNDO, this);
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategyCode);
- final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
+ final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement();
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
}
@@ -3789,7 +3801,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
}
- final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
+ final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement();
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(),
allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges());
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
@@ -4856,6 +4868,15 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
return currentTransactionDate;
}
+ public LoanTransaction getLastRepaymentTransaction() {
+ for (final LoanTransaction loanTransaction : this.loanTransactions) {
+ if (!loanTransaction.isReversed() && loanTransaction.isRepaymentType()) {
+ return loanTransaction;
+ }
+ }
+ return null;
+ }
+
public LocalDate getLastUserTransactionForChargeCalc() {
LocalDate lastTransaction = getDisbursementDate();
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
@@ -5130,7 +5151,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategyCode);
- final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
+ final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement();
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(
getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(),
getActiveCharges());
@@ -5243,7 +5264,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
/*
* LocalDate recalculateFrom = null; List<LoanTransaction> loanTransactions =
- * this.retreiveListOfTransactionsPostDisbursementExcludeAccruals(); for (LoanTransaction loanTransaction :
+ * this.retrieveListOfTransactionsPostDisbursementExcludeAccruals(); for (LoanTransaction loanTransaction :
* loanTransactions) { if (recalculateFrom == null ||
* loanTransaction.getTransactionDate().isAfter(recalculateFrom)) { recalculateFrom =
* loanTransaction.getTransactionDate(); } } generatorDTO.setRecalculateFrom(recalculateFrom);
@@ -5266,7 +5287,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
public ChangedTransactionDetail processTransactions() {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategyCode);
- final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
+ final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement();
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(
getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(),
getActiveCharges());
@@ -5344,8 +5365,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
if (this.loanInterestRecalculationDetails != null && this.loanInterestRecalculationDetails.isCompoundingToBePostedAsTransaction()) {
LocalDate lastCompoundingDate = this.getDisbursementDate();
List<LoanInterestRecalcualtionAdditionalDetails> compoundingDetails = extractInterestRecalculationAdditionalDetails();
- List<LoanTransaction> incomeTransactions = retreiveListOfIncomePostingTransactions();
- List<LoanTransaction> accrualTransactions = retreiveListOfAccrualTransactions();
+ List<LoanTransaction> incomeTransactions = retrieveListOfIncomePostingTransactions();
+ List<LoanTransaction> accrualTransactions = retrieveListOfAccrualTransactions();
for (LoanInterestRecalcualtionAdditionalDetails compoundingDetail : compoundingDetails) {
if (!compoundingDetail.getEffectiveDate().isBefore(DateUtils.getBusinessLocalDate())) {
break;
@@ -5495,7 +5516,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
public void processPostDisbursementTransactions() {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategyCode);
- final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
+ final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement();
final List<LoanTransaction> copyTransactions = new ArrayList<>();
if (!allNonContraTransactionsPostDisbursement.isEmpty()) {
for (LoanTransaction loanTransaction : allNonContraTransactionsPostDisbursement) {
@@ -5653,7 +5674,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
private void updateLoanOutstandingBalances() {
Money outstanding = Money.zero(getCurrency());
- List<LoanTransaction> loanTransactions = retreiveListOfTransactionsExcludeAccruals();
+ List<LoanTransaction> loanTransactions = retrieveListOfTransactionsExcludeAccruals();
for (LoanTransaction loanTransaction : loanTransactions) {
if (loanTransaction.isDisbursement() || loanTransaction.isIncomePosting()) {
outstanding = outstanding.plus(loanTransaction.getAmount(getCurrency()));
@@ -6099,7 +6120,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
loanRepaymentScheduleTransactionProcessor.handleRefund(loanTransaction, getCurrency(), getRepaymentScheduleInstallments(),
getActiveCharges());
} else {
- final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
+ final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement();
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(),
allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges());
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
@@ -6191,7 +6212,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_DISBURSAL_UNDO_LAST, getDisbursementDate());
LocalDate actualDisbursementDate = null;
LocalDate lastTransactionDate = getDisbursementDate();
- List<LoanTransaction> loanTransactions = retreiveListOfTransactionsExcludeAccruals();
+ List<LoanTransaction> loanTransactions = retrieveListOfTransactionsExcludeAccruals();
Collections.reverse(loanTransactions);
for (final LoanTransaction previousTransaction : loanTransactions) {
if (lastTransactionDate.isBefore(previousTransaction.getTransactionDate())
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
index 53ded9aa4..54d1989e8 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
@@ -84,6 +84,7 @@ import org.apache.fineract.portfolio.group.domain.Group;
import org.apache.fineract.portfolio.group.exception.GroupNotActiveException;
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData;
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualPlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
@@ -541,13 +542,15 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
@Override
public void setLoanDelinquencyTag(final Loan loan, final LocalDate transactionDate) {
- // Revalidate the Delinquency Classification
- final Long ageOfOverdueDays = loan.getAgeOfOverdueDays(transactionDate);
- log.debug("Loan {} with {} days and current classification {}", loan.getId(), ageOfOverdueDays);
- if (ageOfOverdueDays > 0L) { // If loan is overdue
- this.delinquencyWritePlatformService.applyDelinquencyTagToLoan(loan, ageOfOverdueDays);
+ LoanScheduleDelinquencyData loanDelinquencyData = new LoanScheduleDelinquencyData(loan.getId(), transactionDate, null, loan);
+ loanDelinquencyData = this.delinquencyWritePlatformService.calculateDelinquencyData(loanDelinquencyData);
+ log.debug("Processing Loan {} with {} overdue days since date {}", loanDelinquencyData.getLoanId(),
+ loanDelinquencyData.getOverdueDays(), loanDelinquencyData.getOverdueSinceDate());
+ // Set or Unset the Delinquency Classification Tag
+ if (loanDelinquencyData.getOverdueDays() > 0) {
+ this.delinquencyWritePlatformService.applyDelinquencyTagToLoan(loanDelinquencyData);
} else {
- this.delinquencyWritePlatformService.removeDelinquencyTagToLoan(loan);
+ this.delinquencyWritePlatformService.removeDelinquencyTagToLoan(loanDelinquencyData.getLoan());
}
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
index 071dcf377..444c47aeb 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
@@ -637,7 +637,10 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
private void checkIfRepaymentPeriodObligationsAreMet(final LocalDate transactionDate, final MonetaryCurrency currency) {
this.obligationsMet = getTotalOutstanding(currency).isZero();
if (this.obligationsMet) {
- this.obligationsMetOnDate = transactionDate;
+ this.obligationsMet = getCredits(currency).isZero();
+ if (this.obligationsMet) {
+ this.obligationsMetOnDate = transactionDate;
+ }
} else {
this.obligationsMetOnDate = null;
}
@@ -704,7 +707,6 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
this.principal = this.principal.add(transactionAmount.getAmount());
}
checkIfRepaymentPeriodObligationsAreMet(transactionDate, transactionAmount.getCurrency());
-
}
public void addToCredits(final BigDecimal amount) {
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallmentRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallmentRepository.java
index b6da7b843..a484fa551 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallmentRepository.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallmentRepository.java
@@ -19,16 +19,47 @@
package org.apache.fineract.portfolio.loanaccount.domain;
import java.time.LocalDate;
+import java.util.Collection;
import java.util.List;
+import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Query;
public interface LoanRepaymentScheduleInstallmentRepository
extends JpaRepository<LoanRepaymentScheduleInstallment, Long>, JpaSpecificationExecutor<LoanRepaymentScheduleInstallment> {
- LoanRepaymentScheduleInstallment findFirstByLoanAndDueDateLessThanEqualAndObligationsMetOnDateOrderByDueDate(Loan loan,
- LocalDate businnessDate, LocalDate obligationsMetOnDate);
+ @Query("""
+ SELECT new org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData(
+ lrs.loan.id,
+ min(lrs.dueDate),
+ 0L,
+ lrs.loan
+ ) FROM LoanRepaymentScheduleInstallment lrs
+ WHERE lrs.loan.loanStatus = :loanStatus AND
+ lrs.dueDate <= :businessDate AND
+ lrs.obligationsMet = :obligationsMet AND
+ lrs.loan.loanProduct.delinquencyBucket IS NOT NULL
+ GROUP BY lrs.loan
+ """)
+ Collection<LoanScheduleDelinquencyData> fetchLoanScheduleDataByDueDateAndObligationsMet(Integer loanStatus, LocalDate businessDate,
+ boolean obligationsMet);
+
+ @Query("""
+ SELECT new org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData(
+ lrs.loan.id,
+ min(lrs.dueDate),
+ 0L,
+ lrs.loan
+ ) FROM LoanRepaymentScheduleInstallment lrs
+ WHERE lrs.loan.loanStatus = :loanStatus AND
+ lrs.dueDate <= :businessDate AND
+ lrs.obligationsMet = :obligationsMet AND
+ lrs.loan.loanProduct.delinquencyBucket IS NOT NULL AND
+ lrs.loan.id NOT IN :loanIds
+ GROUP BY lrs.loan
+ """)
+ Collection<LoanScheduleDelinquencyData> fetchLoanScheduleDataByDueDateAndObligationsMet(Integer loanStatus, LocalDate businessDate,
+ boolean obligationsMet, List<Long> loanIds);
- List<LoanRepaymentScheduleInstallment> findByLoanAndDueDateLessThanEqualAndObligationsMetOnDateOrderByDueDate(Loan loan,
- LocalDate businnessDate, LocalDate obligationsMetOnDate);
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRepository.java
index 16f6268cf..42429f083 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRepository.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRepository.java
@@ -18,11 +18,30 @@
*/
package org.apache.fineract.portfolio.loanaccount.domain;
+import java.time.LocalDate;
+import java.util.Collection;
import java.util.Optional;
+import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Query;
public interface LoanTransactionRepository extends JpaRepository<LoanTransaction, Long>, JpaSpecificationExecutor<LoanTransaction> {
Optional<LoanTransaction> findByIdAndLoanId(Long transactionId, Long loanId);
+
+ @Query("""
+ SELECT new org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData(
+ lt.loan.id,
+ min(lt.dateOf),
+ 0L,
+ lt.loan
+ ) FROM LoanTransaction lt
+ WHERE lt.typeOf = :transactionType and
+ lt.dateOf <= :businessDate and
+ lt.loan.loanProduct.delinquencyBucket is not null
+ GROUP BY lt.loan
+ """)
+ Collection<LoanScheduleDelinquencyData> fetchLoanTransactionsByTypeAndLessOrEqualDate(Integer transactionType, LocalDate businessDate);
+
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionToRepaymentScheduleMappingRepository.java
similarity index 75%
copy from fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRepository.java
copy to fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionToRepaymentScheduleMappingRepository.java
index 16f6268cf..7dfe8e990 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRepository.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionToRepaymentScheduleMappingRepository.java
@@ -18,11 +18,12 @@
*/
package org.apache.fineract.portfolio.loanaccount.domain;
-import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
-public interface LoanTransactionRepository extends JpaRepository<LoanTransaction, Long>, JpaSpecificationExecutor<LoanTransaction> {
+public interface LoanTransactionToRepaymentScheduleMappingRepository extends JpaRepository<LoanTransactionToRepaymentScheduleMapping, Long>,
+ JpaSpecificationExecutor<LoanTransactionToRepaymentScheduleMapping> {
+
+ LoanTransactionToRepaymentScheduleMapping findByLoanTransaction(LoanTransaction loanTransaction);
- Optional<LoanTransaction> findByIdAndLoanId(Long transactionId, Long loanId);
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
index b13780e34..a52a241ca 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
@@ -540,8 +540,8 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
for (final LoanRepaymentScheduleInstallment currentInstallment : installments) {
pastDueDate = currentInstallment.getDueDate();
if (!currentInstallment.isAdditional() && currentInstallment.getDueDate().isAfter(transactionDate)) {
- currentInstallment.addToPrincipal(transactionDate, transactionAmountUnprocessed);
currentInstallment.addToCredits(transactionAmountUnprocessed.getAmount());
+ currentInstallment.addToPrincipal(transactionDate, transactionAmountUnprocessed);
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction, currentInstallment,
transactionAmountUnprocessed, zeroMoney, zeroMoney, zeroMoney));
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/setloandelinquencytags/SetLoanDelinquencyTagsConfig.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/setloandelinquencytags/SetLoanDelinquencyTagsConfig.java
index 3334b90ca..15cc21b5b 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/setloandelinquencytags/SetLoanDelinquencyTagsConfig.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/setloandelinquencytags/SetLoanDelinquencyTagsConfig.java
@@ -21,7 +21,8 @@ package org.apache.fineract.portfolio.loanaccount.jobs.setloandelinquencytags;
import lombok.AllArgsConstructor;
import org.apache.fineract.infrastructure.jobs.service.JobName;
import org.apache.fineract.portfolio.delinquency.service.DelinquencyWritePlatformService;
-import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallmentRepository;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
@@ -38,7 +39,8 @@ public class SetLoanDelinquencyTagsConfig {
private StepBuilderFactory steps;
private DelinquencyWritePlatformService delinquencyWritePlatformService;
- private LoanReadPlatformService loanReadPlatformService;
+ private LoanRepaymentScheduleInstallmentRepository loanRepaymentScheduleInstallmentRepository;
+ private LoanTransactionRepository loanTransactionRepository;
@Bean
public Step setLoanDelinquencyTagsStep() {
@@ -53,7 +55,8 @@ public class SetLoanDelinquencyTagsConfig {
@Bean
public SetLoanDelinquencyTagsTasklet setLoanDelinquencyTagsTasklet() {
- return new SetLoanDelinquencyTagsTasklet(delinquencyWritePlatformService, loanReadPlatformService);
+ return new SetLoanDelinquencyTagsTasklet(delinquencyWritePlatformService, loanRepaymentScheduleInstallmentRepository,
+ loanTransactionRepository);
}
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/setloandelinquencytags/SetLoanDelinquencyTagsTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/setloandelinquencytags/SetLoanDelinquencyTagsTasklet.java
index 9bf2d274b..5cfabe3f9 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/setloandelinquencytags/SetLoanDelinquencyTagsTasklet.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/setloandelinquencytags/SetLoanDelinquencyTagsTasklet.java
@@ -18,14 +18,20 @@
*/
package org.apache.fineract.portfolio.loanaccount.jobs.setloandelinquencytags;
+import java.time.LocalDate;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.portfolio.delinquency.service.DelinquencyWritePlatformService;
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData;
-import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallmentRepository;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
@@ -36,24 +42,54 @@ import org.springframework.batch.repeat.RepeatStatus;
public class SetLoanDelinquencyTagsTasklet implements Tasklet {
private final DelinquencyWritePlatformService delinquencyWritePlatformService;
- private final LoanReadPlatformService loanReadPlatformService;
+ private final LoanRepaymentScheduleInstallmentRepository loanRepaymentScheduleInstallmentRepository;
+ private final LoanTransactionRepository loanTransactionRepository;
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
- log.debug("Run job for date {}", DateUtils.getBusinessLocalDate());
- Collection<LoanScheduleDelinquencyData> loanScheduleDelinquencyData = this.loanReadPlatformService
- .retrieveScheduleDelinquencyData(DateUtils.getBusinessLocalDate());
- log.debug("Were found {} items", loanScheduleDelinquencyData.size());
- for (LoanScheduleDelinquencyData loanDelinquencyData : loanScheduleDelinquencyData) {
- log.debug("Processing Loan {} with due date {} and {} overdue days", loanDelinquencyData.getLoanId(),
- loanDelinquencyData.getDueDate(), loanDelinquencyData.getAgeDays());
- this.delinquencyWritePlatformService.applyDelinquencyTagToLoan(loanDelinquencyData.getLoanId(),
- loanDelinquencyData.getAgeDays());
- }
+ final LocalDate businessDate = DateUtils.getBusinessLocalDate();
+ log.debug("Run job for date {}", businessDate);
+
+ // Read Loan Ids with Loan Transaction Charge back
+ Collection<LoanScheduleDelinquencyData> loanScheduleDelinquencyData = this.loanTransactionRepository
+ .fetchLoanTransactionsByTypeAndLessOrEqualDate(LoanTransactionType.CHARGEBACK.getValue(), businessDate);
+ List<Long> processedLoans = applyDelinquencyTagToLoans(loanScheduleDelinquencyData);
log.debug("{}: Records affected by setLoanDelinquencyTags: {}", ThreadLocalContextUtil.getTenant().getName(),
- loanScheduleDelinquencyData.size());
+ processedLoans.size());
+
+ // Read Loan Ids with overdue installments
+ if (processedLoans.isEmpty()) {
+ loanScheduleDelinquencyData = this.loanRepaymentScheduleInstallmentRepository
+ .fetchLoanScheduleDataByDueDateAndObligationsMet(LoanStatus.ACTIVE.getValue(), businessDate, false);
+ } else {
+ loanScheduleDelinquencyData = this.loanRepaymentScheduleInstallmentRepository
+ .fetchLoanScheduleDataByDueDateAndObligationsMet(LoanStatus.ACTIVE.getValue(), businessDate, false, processedLoans);
+ }
+ processedLoans = applyDelinquencyTagToLoans(loanScheduleDelinquencyData);
+
return RepeatStatus.FINISHED;
}
+ private List<Long> applyDelinquencyTagToLoans(Collection<LoanScheduleDelinquencyData> loanScheduleDelinquencyData) {
+ List<Long> processedLoans = new ArrayList<>();
+
+ log.debug("Were found {} items", loanScheduleDelinquencyData.size());
+ for (LoanScheduleDelinquencyData loanDelinquencyData : loanScheduleDelinquencyData) {
+ // Set the data used by Delinquency Classification method
+ loanDelinquencyData = this.delinquencyWritePlatformService.calculateDelinquencyData(loanDelinquencyData);
+ log.debug("Processing Loan {} with {} overdue days since date {}", loanDelinquencyData.getLoanId(),
+ loanDelinquencyData.getOverdueDays(), loanDelinquencyData.getOverdueSinceDate());
+ // Set or Unset the Delinquency Classification Tag
+ if (loanDelinquencyData.getOverdueDays() > 0) {
+ this.delinquencyWritePlatformService.applyDelinquencyTagToLoan(loanDelinquencyData);
+ } else {
+ this.delinquencyWritePlatformService.removeDelinquencyTagToLoan(loanDelinquencyData.getLoan());
+ }
+
+ processedLoans.add(loanDelinquencyData.getLoanId());
+ }
+ return processedLoans;
+ }
+
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java
index 11dae4619..1d1cc4ac3 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java
@@ -2671,7 +2671,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
LoanScheduleDTO loanScheduleDTO = rescheduleNextInstallments(mc, loanApplicationTerms, loan, holidayDetailDTO,
loanRepaymentScheduleTransactionProcessor, onDate, calculateTill);
- List<LoanTransaction> loanTransactions = loan.retreiveListOfTransactionsPostDisbursementExcludeAccruals();
+ List<LoanTransaction> loanTransactions = loan.retrieveListOfTransactionsPostDisbursementExcludeAccruals();
loanRepaymentScheduleTransactionProcessor.handleTransaction(loanApplicationTerms.getExpectedDisbursementDate(), loanTransactions,
currency, loanScheduleDTO.getInstallments(), loan.getActiveCharges());
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java
index b47ff7547..cc2103f70 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java
@@ -27,13 +27,11 @@ import org.apache.fineract.infrastructure.core.service.SearchParameters;
import org.apache.fineract.organisation.staff.data.StaffData;
import org.apache.fineract.portfolio.calendar.data.CalendarData;
import org.apache.fineract.portfolio.floatingrates.data.InterestRatePeriodData;
-import org.apache.fineract.portfolio.loanaccount.data.CollectionData;
import org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData;
import org.apache.fineract.portfolio.loanaccount.data.LoanApprovalData;
import org.apache.fineract.portfolio.loanaccount.data.LoanRepaymentScheduleInstallmentData;
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData;
-import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionRelationData;
@@ -109,8 +107,6 @@ public interface LoanReadPlatformService {
Collection<LoanScheduleAccrualData> retriveScheduleAccrualData();
- Collection<LoanScheduleDelinquencyData> retrieveScheduleDelinquencyData(LocalDate businessLocalDate);
-
LoanTransactionData retrieveRecoveryPaymentTemplate(Long loanId);
LoanTransactionData retrieveLoanWriteoffTemplate(Long loanId);
@@ -157,8 +153,6 @@ public interface LoanReadPlatformService {
List<LoanRepaymentScheduleInstallmentData> getRepaymentDataResponse(Long loanId);
- CollectionData retrieveLoanCollectionData(Long loanId);
-
List<LoanTransactionRelationData> retrieveLoanTransactionRelationsByLoanTransactionId(Long loanTransactionId);
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
index 1dced1891..e90d0cedc 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
@@ -84,7 +84,6 @@ import org.apache.fineract.portfolio.group.data.GroupGeneralData;
import org.apache.fineract.portfolio.group.data.GroupRoleData;
import org.apache.fineract.portfolio.group.service.GroupReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
-import org.apache.fineract.portfolio.loanaccount.data.CollectionData;
import org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData;
import org.apache.fineract.portfolio.loanaccount.data.LoanApplicationTimelineData;
@@ -92,7 +91,6 @@ import org.apache.fineract.portfolio.loanaccount.data.LoanApprovalData;
import org.apache.fineract.portfolio.loanaccount.data.LoanInterestRecalculationData;
import org.apache.fineract.portfolio.loanaccount.data.LoanRepaymentScheduleInstallmentData;
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData;
-import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData;
import org.apache.fineract.portfolio.loanaccount.data.LoanStatusEnumData;
import org.apache.fineract.portfolio.loanaccount.data.LoanSummaryData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
@@ -240,7 +238,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
this.context.authenticatedUser();
final LoanScheduleResultSetExtractor fullResultsetExtractor = new LoanScheduleResultSetExtractor(
- repaymentScheduleRelatedLoanData, disbursementData, isInterestRecalculationEnabled, totalPaidFeeCharges);
+ repaymentScheduleRelatedLoanData, disbursementData, isInterestRecalculationEnabled);
final String sql = "select " + fullResultsetExtractor.schema() + " where ls.loan_id = ? order by ls.loan_id, ls.installment";
return this.jdbcTemplate.query(sql, fullResultsetExtractor, loanId); // NOSONAR
@@ -639,7 +637,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
+ " l.loan_sub_status_id as loanSubStatusId, la.principal_overdue_derived as principalOverdue, l.is_fraud as isFraud, "
+ " la.interest_overdue_derived as interestOverdue, la.fee_charges_overdue_derived as feeChargesOverdue,"
+ " la.penalty_charges_overdue_derived as penaltyChargesOverdue, la.total_overdue_derived as totalOverdue,"
- + " la.overdue_since_date_derived as overdueSinceDate,"
+ + " la.overdue_since_date_derived as overdueSinceDate, "
+ " l.sync_disbursement_with_meeting as syncDisbursementWithMeeting,"
+ " l.loan_counter as loanCounter, l.loan_product_counter as loanProductCounter,"
+ " l.is_npa as isNPA, l.days_in_month_enum as daysInMonth, l.days_in_year_enum as daysInYear, "
@@ -678,7 +676,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
+ " left join m_code_value codev on codev.id = l.writeoff_reason_cv_id"
+ " left join m_product_loan_variable_installment_config lpvi on lpvi.loan_product_id = l.product_id"
+ " left join m_loan_topup as topup on l.id = topup.loan_id"
- + " left join m_loan as topuploan on topuploan.id = topup.closure_loan_id";
+ + " left join m_loan as topuploan on topuploan.id = topup.closure_loan_id ";
}
@@ -888,9 +886,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final BigDecimal totalRecovered = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "totalRecovered");
final LocalDate overdueSinceDate = JdbcSupport.getLocalDate(rs, "overdueSinceDate");
- if (overdueSinceDate != null) {
- inArrears = true;
- }
+ inArrears = (overdueSinceDate != null);
loanSummary = new LoanSummaryData(currencyData, principalDisbursed, principalAdjustments, principalPaid,
principalWrittenOff, principalOutstanding, principalOverdue, interestCharged, interestPaid, interestWaived,
@@ -1062,10 +1058,9 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
private LocalDate lastDueDate;
private BigDecimal outstandingLoanPrincipalBalance;
private boolean excludePastUndisbursed;
- private final BigDecimal totalPaidFeeCharges;
LoanScheduleResultSetExtractor(final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData,
- Collection<DisbursementData> disbursementData, boolean isInterestRecalculationEnabled, BigDecimal totalPaidFeeCharges) {
+ Collection<DisbursementData> disbursementData, boolean isInterestRecalculationEnabled) {
this.currency = repaymentScheduleRelatedLoanData.getCurrency();
this.disbursement = repaymentScheduleRelatedLoanData.disbursementData();
this.totalFeeChargesDueAtDisbursement = repaymentScheduleRelatedLoanData.getTotalFeeChargesAtDisbursement();
@@ -1073,7 +1068,6 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
this.outstandingLoanPrincipalBalance = this.disbursement.getPrincipal();
this.disbursementData = disbursementData;
this.excludePastUndisbursed = isInterestRecalculationEnabled;
- this.totalPaidFeeCharges = totalPaidFeeCharges;
}
public String schema() {
@@ -1699,25 +1693,6 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
}
}
- @Override
- public Collection<LoanScheduleDelinquencyData> retrieveScheduleDelinquencyData(LocalDate businessLocalDate) {
- LoanScheduleDelinquencyMapper mapper = new LoanScheduleDelinquencyMapper(DateUtils.getBusinessLocalDate());
- final StringBuilder sqlBuilder = new StringBuilder(400);
- sqlBuilder.append("select ").append(mapper.schema())
- // Just get the overdued installments
- .append(" where loan.loan_status_id=:active and ls.duedate <= :businessLocalDate and ")
- // Just get the unpaid installments using the completed_derived flag
- .append(" ls.completed_derived = false and ")
- // Just the Loan Product linked to delinquency bucket
- .append(" mpl.delinquency_bucket_id is not null ");
- sqlBuilder.append(" group by ls.loan_id, loan.product_id ");
- Map<String, Object> paramMap = new HashMap<>(3);
- paramMap.put("active", LoanStatus.ACTIVE.getValue());
- paramMap.put("businessLocalDate", businessLocalDate);
-
- return this.namedParameterJdbcTemplate.query(sqlBuilder.toString(), paramMap, mapper);
- }
-
@Override
public Collection<LoanScheduleAccrualData> retriveScheduleAccrualData() {
@@ -1887,33 +1862,6 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
}
}
- private static final class LoanScheduleDelinquencyMapper implements RowMapper<LoanScheduleDelinquencyData> {
-
- private LocalDate businessDate;
-
- LoanScheduleDelinquencyMapper(LocalDate businessDate) {
- this.businessDate = businessDate;
- }
-
- public String schema() {
- final StringBuilder sqlBuilder = new StringBuilder(400);
- sqlBuilder.append(" ls.loan_id as loanId, loan.product_id as productId, min(ls.duedate) as duedate ")
- .append(" from m_loan_repayment_schedule ls left join m_loan loan on loan.id=ls.loan_id ")
- .append(" left join m_product_loan mpl on mpl.id = loan.product_id ");
- return sqlBuilder.toString();
- }
-
- @Override
- public LoanScheduleDelinquencyData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException {
-
- final Long loanId = rs.getLong("loanId");
- final Long productId = rs.getLong("productId");
- final LocalDate dueDate = JdbcSupport.getLocalDate(rs, "duedate");
- final long ageDays = DateUtils.getDifferenceInDays(dueDate, businessDate);
- return new LoanScheduleDelinquencyData(loanId, productId, dueDate, ageDays);
- }
- }
-
@Override
public LoanTransactionData retrieveRecoveryPaymentTemplate(Long loanId) {
final Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId, true);
@@ -2283,7 +2231,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
@Override
public Collection<Long> retrieveLoanIdsWithPendingIncomePostingTransactions() {
LocalDate currentdate = DateUtils.getBusinessLocalDate();
- StringBuilder sqlBuilder = new StringBuilder().append(" select distinct loan.id ").append(" from m_loan as loan ").append(
+ StringBuilder sqlBuilder = new StringBuilder().append(" select distinct loan.id from m_loan as loan ").append(
" inner join m_loan_recalculation_details as recdet on (recdet.loan_id = loan.id and recdet.is_compounding_to_be_posted_as_transaction is not null and recdet.is_compounding_to_be_posted_as_transaction = true) ")
.append(" inner join m_loan_repayment_schedule as repsch on repsch.loan_id = loan.id ")
.append(" inner join m_loan_interest_recalculation_additional_details as adddet on adddet.loan_repayment_schedule_id = repsch.id ")
@@ -2292,7 +2240,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
.append(" and adddet.effective_date is not null ").append(" and trans.transaction_date is null ")
.append(" and adddet.effective_date < ? ");
try {
- return this.jdbcTemplate.queryForList(sqlBuilder.toString(), new Object[] { currentdate }, Long.class);
+ return this.jdbcTemplate.queryForList(sqlBuilder.toString(), Long.class, new Object[] { currentdate });
} catch (final EmptyResultDataAccessException e) {
return null;
}
@@ -2438,62 +2386,6 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
return this.jdbcTemplate.queryForObject(sql, Integer.class);
}
- @Override
- public CollectionData retrieveLoanCollectionData(Long loanId) {
- final CollectionDataMapper mapper = new CollectionDataMapper(sqlGenerator);
- String sql = "select " + mapper.schema();
- CollectionData collectionData = this.jdbcTemplate.queryForObject(sql, mapper, loanId, loanId); // NOSONAR
- return collectionData;
- }
-
- private static final class CollectionDataMapper implements RowMapper<CollectionData> {
-
- private final DatabaseSpecificSQLGenerator sqlGenerator;
-
- CollectionDataMapper(DatabaseSpecificSQLGenerator sqlGenerator) {
- this.sqlGenerator = sqlGenerator;
- }
-
- public String schema() {
- StringBuilder sqlBuilder = new StringBuilder();
-
- sqlBuilder.append(
- "l.id as loanId, coalesce((l.approved_principal - l.principal_disbursed_derived), 0) as availableDisbursementAmount, ");
- sqlBuilder.append(
- sqlGenerator.dateDiff(sqlGenerator.currentBusinessDate(), "laa.overdue_since_date_derived") + " as pastDueDays, ");
- sqlBuilder.append(
- "(select coalesce(min(lrs.duedate), null) as duedate from m_loan_repayment_schedule lrs where lrs.loan_id=l.id and lrs.completed_derived is false and lrs.duedate >= "
- + sqlGenerator.currentBusinessDate() + ") as nextPaymentDueDate, ");
- sqlBuilder.append(
- sqlGenerator.dateDiff(sqlGenerator.currentBusinessDate(), "laa.overdue_since_date_derived") + " as delinquentDays, ");
- sqlBuilder.append(sqlGenerator.currentBusinessDate()
- + " as delinquentDate, coalesce(laa.total_overdue_derived, 0) as delinquentAmount, ");
- sqlBuilder.append("lre.transactionDate as lastPaymentDate, coalesce(lre.amount, 0) as lastPaymentAmount ");
- sqlBuilder.append("from m_loan l left join m_loan_arrears_aging laa on laa.loan_id = l.id ");
- sqlBuilder.append(
- "left join (select lt.loan_id, lt.transaction_date as transactionDate, lt.amount as amount from m_loan_transaction lt ");
- sqlBuilder.append(
- "where lt.loan_id =? and lt.is_reversed = false and lt.transaction_type_enum=2 order by lt.transaction_date desc, lt.id desc limit 1) lre on lre.loan_id = l.id ");
- sqlBuilder.append("where l.id=? ");
- return sqlBuilder.toString();
- }
-
- @Override
- public CollectionData mapRow(ResultSet rs, int rowNum) throws SQLException {
- final LocalDate nextPaymentDueDate = JdbcSupport.getLocalDate(rs, "nextPaymentDueDate");
- final LocalDate delinquentDate = JdbcSupport.getLocalDate(rs, "delinquentDate");
- final LocalDate lastPaymentDate = JdbcSupport.getLocalDate(rs, "lastPaymentDate");
- final BigDecimal availableDisbursementAmount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "availableDisbursementAmount");
- final BigDecimal delinquentAmount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "delinquentAmount");
- final BigDecimal lastPaymentAmount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "lastPaymentAmount");
- final int pastDueDays = rs.getInt("pastDueDays");
- final int delinquentDays = rs.getInt("delinquentDays");
-
- return CollectionData.instance(availableDisbursementAmount, pastDueDays, nextPaymentDueDate, delinquentDays, delinquentDate,
- delinquentAmount, lastPaymentDate, lastPaymentAmount);
- }
- }
-
@Override
public List<LoanTransactionRelationData> retrieveLoanTransactionRelationsByLoanTransactionId(Long loanTransactionId) {
final LoanTransaction loanTransaction = this.loanTransactionRepository.getReferenceById(loanTransactionId);
@@ -2501,4 +2393,5 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
.findByFromTransactionAndRelationType(loanTransaction, LoanTransactionRelationTypeEnum.CHARGEBACK);
return loanTransactionRelationMapper.map(loanTransactionRelations);
}
+
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
index 1db95735b..608353ff2 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
@@ -1289,7 +1289,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
- this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
+ this.loanAccountDomainService.setLoanDelinquencyTag(loan, transactionDate);
businessEventNotifierService.notifyPostBusinessEvent(new LoanChargebackTransactionBusinessEvent(loanTransaction));
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java
index 60eec4dc7..c78abf37f 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java
@@ -388,7 +388,7 @@ public class FixedDepositAccount extends SavingsAccount {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}
- final List<SavingsAccountTransaction> savingsAccountTransactions = retreiveListOfTransactions();
+ final List<SavingsAccountTransaction> savingsAccountTransactions = retrieveListOfTransactions();
if (savingsAccountTransactions.size() > 0) {
final SavingsAccountTransaction accountTransaction = savingsAccountTransactions.get(savingsAccountTransactions.size() - 1);
if (accountTransaction.isAfter(closedDate)) {
@@ -473,7 +473,7 @@ public class FixedDepositAccount extends SavingsAccount {
}
}
- final List<SavingsAccountTransaction> savingsAccountTransactions = retreiveListOfTransactions();
+ final List<SavingsAccountTransaction> savingsAccountTransactions = retrieveListOfTransactions();
if (savingsAccountTransactions.size() > 0) {
final SavingsAccountTransaction accountTransaction = savingsAccountTransactions.get(savingsAccountTransactions.size() - 1);
if (accountTransaction.isAfter(closedDate)) {
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java
index 7d59e1825..a602e55ff 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java
@@ -492,7 +492,7 @@ public class RecurringDepositAccount extends SavingsAccount {
}
}
- final List<SavingsAccountTransaction> savingsAccountTransactions = retreiveListOfTransactions();
+ final List<SavingsAccountTransaction> savingsAccountTransactions = retrieveListOfTransactions();
if (savingsAccountTransactions.size() > 0) {
final SavingsAccountTransaction accountTransaction = savingsAccountTransactions.get(savingsAccountTransactions.size() - 1);
if (accountTransaction.isAfter(closedDate)) {
@@ -593,7 +593,7 @@ public class RecurringDepositAccount extends SavingsAccount {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}
- final List<SavingsAccountTransaction> savingsAccountTransactions = retreiveListOfTransactions();
+ final List<SavingsAccountTransaction> savingsAccountTransactions = retrieveListOfTransactions();
if (savingsAccountTransactions.size() > 0) {
final SavingsAccountTransaction accountTransaction = savingsAccountTransactions.get(savingsAccountTransactions.size() - 1);
if (accountTransaction.isAfter(closedDate)) {
@@ -934,7 +934,7 @@ public class RecurringDepositAccount extends SavingsAccount {
}
private List<SavingsAccountTransaction> retreiveOrderedDepositTransactions() {
- final List<SavingsAccountTransaction> listOfTransactionsSorted = retreiveListOfTransactions();
+ final List<SavingsAccountTransaction> listOfTransactionsSorted = retrieveListOfTransactions();
final List<SavingsAccountTransaction> orderedDepositTransactions = new ArrayList<>();
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java
index ae10f099a..a65f9b602 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java
@@ -928,7 +928,7 @@ public class SavingsAccount extends AbstractPersistableCustom {
}
protected List<SavingsAccountTransaction> retreiveOrderedNonInterestPostingTransactions() {
- final List<SavingsAccountTransaction> listOfTransactionsSorted = retreiveListOfTransactions();
+ final List<SavingsAccountTransaction> listOfTransactionsSorted = retrieveListOfTransactions();
final List<SavingsAccountTransaction> orderedNonInterestPostingTransactions = new ArrayList<>();
@@ -965,7 +965,7 @@ public class SavingsAccount extends AbstractPersistableCustom {
return listOfTransactionsSorted;
}
- protected List<SavingsAccountTransaction> retreiveListOfTransactions() {
+ protected List<SavingsAccountTransaction> retrieveListOfTransactions() {
final List<SavingsAccountTransaction> listOfTransactionsSorted = new ArrayList<>();
listOfTransactionsSorted.addAll(this.transactions);
@@ -983,7 +983,7 @@ public class SavingsAccount extends AbstractPersistableCustom {
if (backdatedTxnsAllowedTill) {
accountTransactionsSorted = retrieveSortedTransactions();
} else {
- accountTransactionsSorted = retreiveListOfTransactions();
+ accountTransactionsSorted = retrieveListOfTransactions();
}
boolean isTransactionsModified = false;
@@ -1058,7 +1058,7 @@ public class SavingsAccount extends AbstractPersistableCustom {
if (backdatedTxnsAllowedTill) {
accountTransactionsSorted = retrieveSortedTransactions();
} else {
- accountTransactionsSorted = retreiveListOfTransactions();
+ accountTransactionsSorted = retrieveListOfTransactions();
}
}
resetAccountTransactionsEndOfDayBalances(accountTransactionsSorted, interestPostingUpToDate);
@@ -1415,7 +1415,7 @@ public class SavingsAccount extends AbstractPersistableCustom {
boolean transactionBeforeLastInterestPosting = false;
if (!backdatedTxnsAllowedTill) {
- for (final SavingsAccountTransaction transaction : retreiveListOfTransactions()) {
+ for (final SavingsAccountTransaction transaction : retrieveListOfTransactions()) {
if ((transaction.isInterestPostingAndNotReversed() || transaction.isOverdraftInterestAndNotReversed())
&& transaction.isAfter(transactionDate) && !transaction.isReversalTransaction()) {
transactionBeforeLastInterestPosting = true;
@@ -1441,7 +1441,7 @@ public class SavingsAccount extends AbstractPersistableCustom {
if (backdatedTxnsAllowedTill) {
transactionsSortedByDate = retrieveSortedTransactions();
} else {
- transactionsSortedByDate = retreiveListOfTransactions();
+ transactionsSortedByDate = retrieveListOfTransactions();
}
Money runningBalance = Money.zero(this.currency);
@@ -1524,7 +1524,7 @@ public class SavingsAccount extends AbstractPersistableCustom {
if (backdatedTxnsAllowedTill) {
transactionsSortedByDate = retrieveSortedTransactions();
} else {
- transactionsSortedByDate = retreiveListOfTransactions();
+ transactionsSortedByDate = retrieveListOfTransactions();
}
Money runningBalance = Money.zero(this.currency);
@@ -2507,7 +2507,7 @@ public class SavingsAccount extends AbstractPersistableCustom {
}
public void validateAccountBalanceDoesNotBecomeNegativeMinimal(final BigDecimal transactionAmount, final boolean isException) {
- // final List<SavingsAccountTransaction> transactionsSortedByDate = retreiveListOfTransactions();
+ // final List<SavingsAccountTransaction> transactionsSortedByDate = retrieveListOfTransactions();
Money runningBalance = this.summary.getAccountBalance(getCurrency());
Money minRequiredBalance = minRequiredBalanceDerived(getCurrency());
org.joda.time.LocalDate lastSavingsDate = null;
@@ -2846,7 +2846,7 @@ public class SavingsAccount extends AbstractPersistableCustom {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}
- final List<SavingsAccountTransaction> savingsAccountTransactions = retreiveListOfTransactions();
+ final List<SavingsAccountTransaction> savingsAccountTransactions = retrieveListOfTransactions();
if (savingsAccountTransactions.size() > 0) {
final SavingsAccountTransaction accountTransaction = savingsAccountTransactions.get(savingsAccountTransactions.size() - 1);
if (accountTransaction.isAfter(closedDate)) {
@@ -3806,7 +3806,7 @@ public class SavingsAccount extends AbstractPersistableCustom {
}
public LocalDate retrieveLastTransactionDate() {
- final List<SavingsAccountTransaction> transactionsSortedByDate = retreiveListOfTransactions();
+ final List<SavingsAccountTransaction> transactionsSortedByDate = retrieveListOfTransactions();
SavingsAccountTransaction lastTransaction = null;
if (transactionsSortedByDate.size() > 0) {
lastTransaction = transactionsSortedByDate.get(transactionsSortedByDate.size() - 1);
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountInterestPostingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountInterestPostingServiceImpl.java
index 344dd2bc0..cbfd124fa 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountInterestPostingServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountInterestPostingServiceImpl.java
@@ -305,7 +305,7 @@ public class SavingsAccountInterestPostingServiceImpl implements SavingsAccountI
}
private List<SavingsAccountTransactionData> retreiveOrderedNonInterestPostingTransactions(final SavingsAccountData savingsAccountData) {
- final List<SavingsAccountTransactionData> listOfTransactionsSorted = retreiveListOfTransactions(savingsAccountData);
+ final List<SavingsAccountTransactionData> listOfTransactionsSorted = retrieveListOfTransactions(savingsAccountData);
final List<SavingsAccountTransactionData> orderedNonInterestPostingTransactions = new ArrayList<>();
@@ -319,7 +319,7 @@ public class SavingsAccountInterestPostingServiceImpl implements SavingsAccountI
return orderedNonInterestPostingTransactions;
}
- private List<SavingsAccountTransactionData> retreiveListOfTransactions(final SavingsAccountData savingsAccountData) {
+ private List<SavingsAccountTransactionData> retrieveListOfTransactions(final SavingsAccountData savingsAccountData) {
final List<SavingsAccountTransactionData> listOfTransactionsSorted = new ArrayList<>();
listOfTransactionsSorted.addAll(savingsAccountData.getSavingsAccountTransactionData());
@@ -407,7 +407,7 @@ public class SavingsAccountInterestPostingServiceImpl implements SavingsAccountI
Money runningBalance = openingAccountBalance.copy();
- List<SavingsAccountTransactionData> accountTransactionsSorted = retreiveListOfTransactions(savingsAccountData);
+ List<SavingsAccountTransactionData> accountTransactionsSorted = retrieveListOfTransactions(savingsAccountData);
boolean isTransactionsModified = false;
for (final SavingsAccountTransactionData transaction : accountTransactionsSorted) {
@@ -466,7 +466,7 @@ public class SavingsAccountInterestPostingServiceImpl implements SavingsAccountI
}
if (isTransactionsModified) {
- accountTransactionsSorted = retreiveListOfTransactions(savingsAccountData);
+ accountTransactionsSorted = retrieveListOfTransactions(savingsAccountData);
}
resetAccountTransactionsEndOfDayBalances(accountTransactionsSorted, interestPostingUpToDate, savingsAccountData);
}
diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
index f0f92f568..e2750a085 100644
--- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
+++ b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
@@ -85,4 +85,5 @@
<include file="parts/0063_add_permissions_for_external_event_configuration.xml" relativeToChangelogFile="true"/>
<include file="parts/0064_refactor_loan_transaction_strategy.xml" relativeToChangelogFile="true"/>
<include file="parts/0065_add_bypass_loan_write_transaction_permission.xml" relativeToChangelogFile="true"/>
+ <include file="parts/0066_delinquency_classification_chargeback.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>
diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0066_delinquency_classification_chargeback.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0066_delinquency_classification_chargeback.xml
new file mode 100644
index 000000000..d7b17251e
--- /dev/null
+++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0066_delinquency_classification_chargeback.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+
+-->
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
+ <changeSet id="1" author="fineract">
+ <createIndex indexName="ind_m_loan_delinquency_tag_history_loan_id" tableName="m_loan_delinquency_tag_history">
+ <column name="loan_id"/>
+ </createIndex>
+ <createIndex indexName="ind_m_loan_delinquency_tag_history_liftedon_date" tableName="m_loan_delinquency_tag_history">
+ <column name="liftedon_date"/>
+ </createIndex>
+ </changeSet>
+</databaseChangeLog>
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyAndChargebackIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyAndChargebackIntegrationTest.java
new file mode 100644
index 000000000..ee25a5c40
--- /dev/null
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyAndChargebackIntegrationTest.java
@@ -0,0 +1,192 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.integrationtests;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import io.restassured.builder.RequestSpecBuilder;
+import io.restassured.builder.ResponseSpecBuilder;
+import io.restassured.http.ContentType;
+import io.restassured.specification.RequestSpecification;
+import io.restassured.specification.ResponseSpecification;
+import java.time.LocalDate;
+import java.util.HashMap;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.client.models.GetDelinquencyBucketsResponse;
+import org.apache.fineract.client.models.GetDelinquencyRangesResponse;
+import org.apache.fineract.client.models.GetLoanProductsProductIdResponse;
+import org.apache.fineract.client.models.GetLoansLoanIdResponse;
+import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
+import org.apache.fineract.integrationtests.common.BusinessDateHelper;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
+import org.apache.fineract.integrationtests.common.SchedulerJobHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import org.apache.fineract.integrationtests.common.products.DelinquencyBucketsHelper;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+@Slf4j
+public class DelinquencyAndChargebackIntegrationTest {
+
+ private ResponseSpecification responseSpec;
+ private RequestSpecification requestSpec;
+ private LoanTransactionHelper loanTransactionHelper;
+ private static final String principalAmount = "1200.00";
+ private static final Double doubleZERO = Double.valueOf("0.00");
+
+ @BeforeEach
+ public void setup() {
+ Utils.initializeRESTAssured();
+
+ requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+ requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+ responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+
+ loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+ }
+
+ @Test
+ public void testLoanClassificationStepAsPartOfCOB() {
+ GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE);
+
+ final LocalDate todaysDate = Utils.getLocalDateOfTenant();
+ LocalDate businessDate = todaysDate.minusDays(57);
+ log.info("Current Business date {}", businessDate);
+ BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, businessDate);
+
+ final SchedulerJobHelper schedulerJobHelper = new SchedulerJobHelper(requestSpec);
+ // Delinquency Bucket
+ final Integer delinquencyBucketId = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, responseSpec);
+ final GetDelinquencyBucketsResponse delinquencyBucket = DelinquencyBucketsHelper.getDelinquencyBucket(requestSpec, responseSpec,
+ delinquencyBucketId);
+
+ // Client and Loan account creation
+ final Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2012");
+ final GetLoanProductsProductIdResponse getLoanProductsProductResponse = createLoanProduct(loanTransactionHelper,
+ delinquencyBucket.getId());
+ assertNotNull(getLoanProductsProductResponse);
+
+ log.info("Loan Product Bucket Name: {}", getLoanProductsProductResponse.getDelinquencyBucket().getName());
+ assertEquals(getLoanProductsProductResponse.getDelinquencyBucket().getName(), delinquencyBucket.getName());
+
+ // Older date to have more than one overdue installment
+ LocalDate transactionDate = businessDate;
+ String operationDate = Utils.dateFormatter.format(transactionDate);
+ log.info("Operation date {}", transactionDate);
+
+ // Create Loan Account
+ final Integer loanId = createLoanAccount(loanTransactionHelper, clientId.toString(),
+ getLoanProductsProductResponse.getId().toString(), operationDate);
+
+ // Run first time the Loan COB Job
+ final String jobName = "Loan COB";
+ schedulerJobHelper.executeAndAwaitJob(jobName);
+
+ // Get loan details expecting to have not a delinquency classification
+ GetLoansLoanIdResponse getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
+ validateLoanAccount(getLoansLoanIdResponse, "0.00", principalAmount, 0, doubleZERO);
+
+ // Move the Business date to get older the loan and to have an overdue loan
+ businessDate = businessDate.plusDays(43);
+ BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, businessDate);
+ log.info("Current Business date {}", businessDate);
+
+ String amountVal = "100.00";
+ Float transactionAmount = Float.valueOf(amountVal);
+ PostLoansLoanIdTransactionsResponse loanIdTransactionsResponse = loanTransactionHelper.makeLoanRepayment(operationDate,
+ transactionAmount, loanId);
+ assertNotNull(loanIdTransactionsResponse);
+ final Integer transactionId = loanIdTransactionsResponse.getResourceId();
+ loanTransactionHelper.reviewLoanTransactionRelations(loanId, transactionId, 0);
+ getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
+ validateLoanAccount(getLoansLoanIdResponse, "0.00", "1100.00", 0, doubleZERO);
+
+ final Integer chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId, amountVal, 0,
+ responseSpec);
+ loanTransactionHelper.reviewLoanTransactionRelations(loanId, transactionId, 1);
+
+ getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
+ assertNotNull(getLoansLoanIdResponse);
+ // Past Due Days in Zero because the Charge back transaction exists and It was done with the current date
+ validateLoanAccount(getLoansLoanIdResponse, amountVal, principalAmount, 0, Double.valueOf("100.00"));
+
+ // Move the Business date to get older the loan and to have an overdue loan
+ businessDate = businessDate.plusDays(9);
+ BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, businessDate);
+ log.info("Current Business date {}", businessDate);
+ // Run Second time the Job
+ schedulerJobHelper.executeAndAwaitJob(jobName);
+
+ // Get loan details expecting to have a delinquency classification
+ getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
+
+ // The value is lower due the 3 grace days
+ validateLoanAccount(getLoansLoanIdResponse, "100.00", principalAmount, 9, Double.valueOf("100.00"));
+
+ GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE);
+ }
+
+ private GetLoanProductsProductIdResponse createLoanProduct(final LoanTransactionHelper loanTransactionHelper,
+ final Integer delinquencyBucketId) {
+ final HashMap<String, Object> loanProductMap = new LoanProductTestBuilder().build(null, delinquencyBucketId);
+ final Integer loanProductId = loanTransactionHelper.getLoanProductId(Utils.convertToJson(loanProductMap));
+ return loanTransactionHelper.getLoanProduct(loanProductId);
+ }
+
+ private Integer createLoanAccount(final LoanTransactionHelper loanTransactionHelper, final String clientId, final String loanProductId,
+ final String operationDate) {
+ final String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal(principalAmount).withLoanTermFrequency("12")
+ .withLoanTermFrequencyAsMonths().withNumberOfRepayments("12").withRepaymentEveryAfter("1")
+ .withRepaymentFrequencyTypeAsMonths() //
+ .withInterestRatePerPeriod("0") //
+ .withExpectedDisbursementDate(operationDate) //
+ .withInterestTypeAsDecliningBalance() //
+ .withSubmittedOnDate(operationDate) //
+ .build(clientId, loanProductId, null);
+ final Integer loanId = loanTransactionHelper.getLoanId(loanApplicationJSON);
+ loanTransactionHelper.approveLoan(operationDate, principalAmount, loanId, null);
+ loanTransactionHelper.disburseLoanWithNetDisbursalAmount(operationDate, loanId, principalAmount);
+ return loanId;
+ }
+
+ private GetDelinquencyRangesResponse validateLoanAccount(GetLoansLoanIdResponse getLoansLoanIdResponse, final String adjustments,
+ final String outstanding, Integer pastDueDays, Double delinquentAmount) {
+ assertNotNull(getLoansLoanIdResponse);
+ final GetDelinquencyRangesResponse delinquencyRange = getLoansLoanIdResponse.getDelinquencyRange();
+
+ log.info("Loan Delinquency Range is null {}", (delinquencyRange == null));
+ if (delinquencyRange != null) {
+ log.info("Loan Delinquency Range is {}", delinquencyRange.getClassification());
+ }
+ loanTransactionHelper.printRepaymentSchedule(getLoansLoanIdResponse);
+ loanTransactionHelper.evaluateLoanSummaryAdjustments(getLoansLoanIdResponse, Double.valueOf(adjustments));
+ DelinquencyBucketsHelper.evaluateLoanCollectionData(getLoansLoanIdResponse, pastDueDays, delinquentAmount);
+
+ loanTransactionHelper.validateLoanPrincipalOustandingBalance(getLoansLoanIdResponse, Double.valueOf(outstanding));
+
+ return delinquencyRange;
+ }
+
+}
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyBucketsIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyBucketsIntegrationTest.java
index 282ee48f1..fbc2e73ed 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyBucketsIntegrationTest.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyBucketsIntegrationTest.java
@@ -234,196 +234,6 @@ public class DelinquencyBucketsIntegrationTest {
DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, response403Spec, jsonBucket);
}
- @Test
- public void testLoanClassificationJob() {
- GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE);
-
- LocalDate businessDate = Utils.getLocalDateOfTenant();
- businessDate = businessDate.minusDays(57);
- log.info("Current date {}", businessDate);
- BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, businessDate);
-
- final LoanTransactionHelper loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
- final SchedulerJobHelper schedulerJobHelper = new SchedulerJobHelper(requestSpec);
-
- ArrayList<Integer> rangeIds = new ArrayList<>();
- String jsonRange = DelinquencyRangesHelper.getAsJSON(1, 3);
- PostDelinquencyRangeResponse delinquencyRangeResponse = DelinquencyRangesHelper.createDelinquencyRange(requestSpec, responseSpec,
- jsonRange);
- rangeIds.add(delinquencyRangeResponse.getResourceId());
- jsonRange = DelinquencyRangesHelper.getAsJSON(4, 60);
- // Create
- delinquencyRangeResponse = DelinquencyRangesHelper.createDelinquencyRange(requestSpec, responseSpec, jsonRange);
- rangeIds.add(delinquencyRangeResponse.getResourceId());
-
- final GetDelinquencyRangesResponse range = DelinquencyRangesHelper.getDelinquencyRange(requestSpec, responseSpec,
- delinquencyRangeResponse.getResourceId());
- final String classificationExpected = range.getClassification();
- log.info("Expected Delinquency Range classification {}", classificationExpected);
-
- String jsonBucket = DelinquencyBucketsHelper.getAsJSON(rangeIds);
- PostDelinquencyBucketResponse delinquencyBucketResponse = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec,
- responseSpec, jsonBucket);
- final GetDelinquencyBucketsResponse delinquencyBucket = DelinquencyBucketsHelper.getDelinquencyBucket(requestSpec, responseSpec,
- delinquencyBucketResponse.getResourceId());
-
- // Client and Loan account creation
- final Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2012");
- final GetLoanProductsProductIdResponse getLoanProductsProductResponse = createLoanProduct(loanTransactionHelper,
- delinquencyBucket.getId());
- assertNotNull(getLoanProductsProductResponse);
- log.info("Loan Product Bucket Name: {}", getLoanProductsProductResponse.getDelinquencyBucket().getName());
- assertEquals(getLoanProductsProductResponse.getDelinquencyBucket().getName(), delinquencyBucket.getName());
-
- final LocalDate todaysDate = Utils.getLocalDateOfTenant();
- // Older date to have more than one overdue installment
- final LocalDate transactionDate = todaysDate.minusDays(65);
- String operationDate = Utils.dateFormatter.format(transactionDate);
-
- // Create Loan Account
- final Integer loanId = createLoanAccount(loanTransactionHelper, clientId.toString(),
- getLoanProductsProductResponse.getId().toString(), operationDate);
-
- // Run first time the Job
- final String jobName = "Loan Delinquency Classification";
- schedulerJobHelper.executeAndAwaitJob(jobName);
-
- // Get loan details expecting to have not a delinquency classification
- GetLoansLoanIdResponse getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
- final GetDelinquencyRangesResponse firstTestCase = getLoansLoanIdResponse.getDelinquencyRange();
- log.info("Loan Delinquency Range is null {}", (firstTestCase == null));
- GetLoansLoanIdRepaymentSchedule getLoanRepaymentSchedule = getLoansLoanIdResponse.getRepaymentSchedule();
- if (getLoanRepaymentSchedule != null) {
- log.info("Loan with {} periods", getLoanRepaymentSchedule.getPeriods().size());
- for (GetLoansLoanIdRepaymentPeriod period : getLoanRepaymentSchedule.getPeriods()) {
- log.info("Period number {} for due date {} and outstanding {}", period.getPeriod(), period.getDueDate(),
- period.getTotalOutstandingForPeriod());
- }
- }
-
- // Move the Business date to get older the loan and to have an overdue loan
- businessDate = businessDate.plusDays(40);
- log.info("Current date {}", businessDate);
- BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, businessDate);
- // Run Second time the Job
- schedulerJobHelper.executeAndAwaitJob(jobName);
-
- // Get loan details expecting to have a delinquency classification
- getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
- final GetDelinquencyRangesResponse secondTestCase = getLoansLoanIdResponse.getDelinquencyRange();
- log.info("Loan Delinquency Range is {}", secondTestCase.getClassification());
-
- // Then
- assertNotNull(delinquencyBucketResponse);
- assertNotNull(getLoanProductsProductResponse);
- assertNull(firstTestCase);
- assertEquals(getLoanProductsProductResponse.getDelinquencyBucket().getName(), delinquencyBucket.getName());
- assertNotNull(secondTestCase);
- assertEquals(secondTestCase.getClassification(), classificationExpected);
-
- GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE);
- }
-
- @Test
- public void testLoanClassificationStepAsPartOfCOB() {
- GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE);
-
- LocalDate businessDate = Utils.getLocalDateOfTenant();
- businessDate = businessDate.minusDays(57);
- log.info("Current date {}", businessDate);
- BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, businessDate);
-
- // Given
- final LoanTransactionHelper loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
- final SchedulerJobHelper schedulerJobHelper = new SchedulerJobHelper(requestSpec);
-
- ArrayList<Integer> rangeIds = new ArrayList<>();
- String jsonRange = DelinquencyRangesHelper.getAsJSON(1, 3);
- PostDelinquencyRangeResponse delinquencyRangeResponse = DelinquencyRangesHelper.createDelinquencyRange(requestSpec, responseSpec,
- jsonRange);
- rangeIds.add(delinquencyRangeResponse.getResourceId());
- jsonRange = DelinquencyRangesHelper.getAsJSON(4, 60);
- // Create
- delinquencyRangeResponse = DelinquencyRangesHelper.createDelinquencyRange(requestSpec, responseSpec, jsonRange);
- rangeIds.add(delinquencyRangeResponse.getResourceId());
-
- final GetDelinquencyRangesResponse range = DelinquencyRangesHelper.getDelinquencyRange(requestSpec, responseSpec,
- delinquencyRangeResponse.getResourceId());
- final String classificationExpected = range.getClassification();
- log.info("Expected Delinquency Range classification {}", classificationExpected);
-
- String jsonBucket = DelinquencyBucketsHelper.getAsJSON(rangeIds);
- PostDelinquencyBucketResponse delinquencyBucketResponse = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec,
- responseSpec, jsonBucket);
- final GetDelinquencyBucketsResponse delinquencyBucket = DelinquencyBucketsHelper.getDelinquencyBucket(requestSpec, responseSpec,
- delinquencyBucketResponse.getResourceId());
-
- // Client and Loan account creation
- final Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2012");
- final GetLoanProductsProductIdResponse getLoanProductsProductResponse = createLoanProduct(loanTransactionHelper,
- delinquencyBucket.getId());
- assertNotNull(getLoanProductsProductResponse);
- log.info("Loan Product Bucket Name: {}", getLoanProductsProductResponse.getDelinquencyBucket().getName());
- assertEquals(getLoanProductsProductResponse.getDelinquencyBucket().getName(), delinquencyBucket.getName());
-
- final LocalDate todaysDate = Utils.getLocalDateOfTenant();
- // Older date to have more than one overdue installment
- final LocalDate transactionDate = todaysDate.minusDays(65);
- String operationDate = Utils.dateFormatter.format(transactionDate);
-
- // Create Loan Account
- final Integer loanId = createLoanAccount(loanTransactionHelper, clientId.toString(),
- getLoanProductsProductResponse.getId().toString(), operationDate);
-
- // COB Step Validation
- final JobBusinessStepConfigData jobBusinessStepConfigData = BusinessStepConfigurationHelper
- .getConfiguredBusinessStepsByJobName(requestSpec, responseSpec, BusinessConfigurationApiTest.LOAN_JOB_NAME);
- assertNotNull(jobBusinessStepConfigData);
- assertEquals(BusinessConfigurationApiTest.LOAN_JOB_NAME, jobBusinessStepConfigData.getJobName());
- assertTrue(jobBusinessStepConfigData.getBusinessSteps().size() > 0);
- assertTrue(jobBusinessStepConfigData.getBusinessSteps().stream()
- .anyMatch(businessStep -> BusinessConfigurationApiTest.LOAN_DELINQUENCY_CLASSIFICATION.equals(businessStep.getStepName())));
-
- // Run first time the Loan COB Job
- final String jobName = "Loan COB";
- schedulerJobHelper.executeAndAwaitJob(jobName);
-
- // Get loan details expecting to have not a delinquency classification
- GetLoansLoanIdResponse getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
- final GetDelinquencyRangesResponse firstTestCase = getLoansLoanIdResponse.getDelinquencyRange();
- log.info("Loan Delinquency Range is null {}", (firstTestCase == null));
- GetLoansLoanIdRepaymentSchedule getLoanRepaymentSchedule = getLoansLoanIdResponse.getRepaymentSchedule();
- if (getLoanRepaymentSchedule != null) {
- log.info("Loan with {} periods", getLoanRepaymentSchedule.getPeriods().size());
- for (GetLoansLoanIdRepaymentPeriod period : getLoanRepaymentSchedule.getPeriods()) {
- log.info("Period number {} for due date {} and outstanding {}", period.getPeriod(), period.getDueDate(),
- period.getTotalOutstandingForPeriod());
- }
- }
-
- // Move the Business date to get older the loan and to have an overdue loan
- businessDate = businessDate.plusDays(40);
- log.info("Current date {}", businessDate);
- BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, businessDate);
- // Run Second time the Job
- schedulerJobHelper.executeAndAwaitJob(jobName);
-
- // Get loan details expecting to have a delinquency classification
- getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
- final GetDelinquencyRangesResponse secondTestCase = getLoansLoanIdResponse.getDelinquencyRange();
- log.info("Loan Delinquency Range is {}", secondTestCase.getClassification());
-
- // Then
- assertNotNull(delinquencyBucketResponse);
- assertNotNull(getLoanProductsProductResponse);
- assertNull(firstTestCase);
- assertEquals(getLoanProductsProductResponse.getDelinquencyBucket().getName(), delinquencyBucket.getName());
- assertNotNull(secondTestCase);
- assertEquals(secondTestCase.getClassification(), classificationExpected);
-
- GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE);
- }
-
@Test
public void testLoanClassificationRealtime() {
// Given
@@ -465,7 +275,7 @@ public class DelinquencyBucketsIntegrationTest {
final LocalDate todaysDate = Utils.getLocalDateOfTenant();
// Older date to have more than one overdue installment
- final LocalDate transactionDate = todaysDate.minusDays(45);
+ final LocalDate transactionDate = todaysDate.minusDays(50);
String operationDate = Utils.dateFormatter.format(transactionDate);
// Create Loan Account
@@ -512,6 +322,8 @@ public class DelinquencyBucketsIntegrationTest {
@Test
public void testLoanClassificationRealtimeWithCharges() {
+ GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE);
+
// Given
final LoanTransactionHelper loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
@@ -551,7 +363,7 @@ public class DelinquencyBucketsIntegrationTest {
final LocalDate todaysDate = Utils.getLocalDateOfTenant();
// Older date to have more than one overdue installment
- LocalDate transactionDate = todaysDate.minusDays(45);
+ LocalDate transactionDate = todaysDate.minusMonths(2).minusDays(5);
String operationDate = Utils.dateFormatter.format(transactionDate);
// Create Loan Account
@@ -563,18 +375,18 @@ public class DelinquencyBucketsIntegrationTest {
assertNotNull(getLoansLoanIdResponse);
// First Loan Delinquency Classification after Disbursement command
assertEquals(getLoansLoanIdResponse.getDelinquencyRange().getClassification(), classificationExpected);
- printRepaymentSchedule(getLoansLoanIdResponse);
+ loanTransactionHelper.printRepaymentSchedule(getLoansLoanIdResponse);
// Apply a repayment to get a full paid installment
operationDate = Utils.dateFormatter.format(todaysDate);
- loanTransactionHelper.makeLoanRepayment(operationDate, 1033.33f, loanId);
+ loanTransactionHelper.makeLoanRepayment(operationDate, 2049.99f, loanId);
getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
- log.info("Loan Delinquency Range after Repayment {}", getLoansLoanIdResponse.getDelinquencyRange());
assertNotNull(getLoansLoanIdResponse);
// The Loan Delinquency Classification after Repayment command must be null
+ log.info("Loan Delinquency Range after Repayment {}", getLoansLoanIdResponse.getDelinquencyRange());
assertNull(getLoansLoanIdResponse.getDelinquencyRange());
- printRepaymentSchedule(getLoansLoanIdResponse);
+ loanTransactionHelper.printRepaymentSchedule(getLoansLoanIdResponse);
transactionDate = todaysDate.minusDays(18);
operationDate = Utils.dateFormatter.format(transactionDate);
@@ -588,7 +400,7 @@ public class DelinquencyBucketsIntegrationTest {
assertNotNull(loanChargeId);
getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
- printRepaymentSchedule(getLoansLoanIdResponse);
+ loanTransactionHelper.printRepaymentSchedule(getLoansLoanIdResponse);
log.info("Loan Delinquency Range after add Loan Charge {}", getLoansLoanIdResponse.getDelinquencyRange());
assertNotNull(getLoansLoanIdResponse.getDelinquencyRange());
@@ -598,6 +410,8 @@ public class DelinquencyBucketsIntegrationTest {
@Test
public void testLoanClassificationRealtimeOlderLoan() {
+ GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE);
+
// Given
final LoanTransactionHelper loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
@@ -645,7 +459,7 @@ public class DelinquencyBucketsIntegrationTest {
final LocalDate todaysDate = Utils.getLocalDateOfTenant();
// Older date to have more than one overdue installment
- final LocalDate transactionDate = todaysDate.minusDays(85);
+ LocalDate transactionDate = todaysDate.minusDays(85);
String operationDate = Utils.dateFormatter.format(transactionDate);
// Create Loan Account
@@ -663,7 +477,8 @@ public class DelinquencyBucketsIntegrationTest {
loanTransactionHelper.printRepaymentSchedule(getLoansLoanIdResponse);
// Apply a repayment to get a first full paid installment
- operationDate = Utils.dateFormatter.format(todaysDate);
+ transactionDate = todaysDate.minusDays(1);
+ operationDate = Utils.dateFormatter.format(transactionDate);
PostLoansLoanIdTransactionsResponse loansLoanIdTransactions = loanTransactionHelper.makeLoanRepayment(operationDate, 1050.0f,
loanId);
assertNotNull(loansLoanIdTransactions);
@@ -736,8 +551,10 @@ public class DelinquencyBucketsIntegrationTest {
assertEquals(getLoanProductsProductResponse.getDelinquencyBucket().getName(), delinquencyBucket.getName());
final LocalDate todaysDate = Utils.getLocalDateOfTenant();
+ log.info("Local date of Tenant: {}", todaysDate);
+
// Older date to have more than one overdue installment
- final LocalDate transactionDate = todaysDate.minusDays(45);
+ final LocalDate transactionDate = todaysDate.minusDays(50);
String operationDate = Utils.dateFormatter.format(transactionDate);
// Create Loan Account
@@ -745,8 +562,11 @@ public class DelinquencyBucketsIntegrationTest {
getLoanProductsProductResponse.getId().toString(), operationDate);
GetLoansLoanIdResponse getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
- assertNotNull(getLoansLoanIdResponse);
+ loanTransactionHelper.printRepaymentSchedule(getLoansLoanIdResponse);
+ loanTransactionHelper.printDelinquencyData(getLoansLoanIdResponse);
+
log.info("Loan Delinquency Range after Disbursement in null? {}", (getLoansLoanIdResponse.getDelinquencyRange() == null));
+ assertNotNull(getLoansLoanIdResponse);
assertNotNull(getLoansLoanIdResponse.getDelinquencyRange());
log.info("Loan Delinquency Range after Disbursement {}", getLoansLoanIdResponse.getDelinquencyRange());
// First Loan Delinquency Classification after Disbursement command
@@ -796,6 +616,194 @@ public class DelinquencyBucketsIntegrationTest {
log.info("Loan Id {} with final Loan status {}", getLoansLoanIdResponse.getId(), getLoansLoanIdResponse.getStatus().getCode());
}
+ @Test
+ public void testLoanClassificationJob() {
+ GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE);
+
+ LocalDate businessDate = Utils.getLocalDateOfTenant();
+ businessDate = businessDate.minusDays(37);
+ log.info("Current date {}", businessDate);
+ BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, businessDate);
+
+ final LoanTransactionHelper loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+ final SchedulerJobHelper schedulerJobHelper = new SchedulerJobHelper(requestSpec);
+
+ ArrayList<Integer> rangeIds = new ArrayList<>();
+ String jsonRange = DelinquencyRangesHelper.getAsJSON(1, 3);
+ PostDelinquencyRangeResponse delinquencyRangeResponse = DelinquencyRangesHelper.createDelinquencyRange(requestSpec, responseSpec,
+ jsonRange);
+ rangeIds.add(delinquencyRangeResponse.getResourceId());
+ jsonRange = DelinquencyRangesHelper.getAsJSON(4, 60);
+ // Create
+ delinquencyRangeResponse = DelinquencyRangesHelper.createDelinquencyRange(requestSpec, responseSpec, jsonRange);
+ rangeIds.add(delinquencyRangeResponse.getResourceId());
+
+ final GetDelinquencyRangesResponse range = DelinquencyRangesHelper.getDelinquencyRange(requestSpec, responseSpec,
+ delinquencyRangeResponse.getResourceId());
+ final String classificationExpected = range.getClassification();
+ log.info("Expected Delinquency Range classification {}", classificationExpected);
+
+ String jsonBucket = DelinquencyBucketsHelper.getAsJSON(rangeIds);
+ PostDelinquencyBucketResponse delinquencyBucketResponse = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec,
+ responseSpec, jsonBucket);
+ final GetDelinquencyBucketsResponse delinquencyBucket = DelinquencyBucketsHelper.getDelinquencyBucket(requestSpec, responseSpec,
+ delinquencyBucketResponse.getResourceId());
+
+ // Client and Loan account creation
+ final Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2012");
+ final GetLoanProductsProductIdResponse getLoanProductsProductResponse = createLoanProduct(loanTransactionHelper,
+ delinquencyBucket.getId());
+ assertNotNull(getLoanProductsProductResponse);
+ log.info("Loan Product Bucket Name: {}", getLoanProductsProductResponse.getDelinquencyBucket().getName());
+ assertEquals(getLoanProductsProductResponse.getDelinquencyBucket().getName(), delinquencyBucket.getName());
+
+ final LocalDate todaysDate = Utils.getLocalDateOfTenant();
+ // Older date to have more than one overdue installment
+ final LocalDate transactionDate = todaysDate.minusDays(57);
+ String operationDate = Utils.dateFormatter.format(transactionDate);
+
+ // Create Loan Account
+ final Integer loanId = createLoanAccount(loanTransactionHelper, clientId.toString(),
+ getLoanProductsProductResponse.getId().toString(), operationDate);
+
+ // Run first time the Job
+ final String jobName = "Loan Delinquency Classification";
+ schedulerJobHelper.executeAndAwaitJob(jobName);
+
+ // Get loan details expecting to have not a delinquency classification
+ GetLoansLoanIdResponse getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
+ loanTransactionHelper.printDelinquencyData(getLoansLoanIdResponse);
+ final GetDelinquencyRangesResponse firstTestCase = getLoansLoanIdResponse.getDelinquencyRange();
+ log.info("Loan Delinquency Range is null {}", (firstTestCase == null));
+ loanTransactionHelper.printRepaymentSchedule(getLoansLoanIdResponse);
+
+ // Move the Business date to get older the loan and to have an overdue loan
+ businessDate = businessDate.plusMonths(1);
+ log.info("Current date {}", businessDate);
+ BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, businessDate);
+ // Run Second time the Job
+ schedulerJobHelper.executeAndAwaitJob(jobName);
+
+ // Get loan details expecting to have a delinquency classification
+ getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
+ loanTransactionHelper.printRepaymentSchedule(getLoansLoanIdResponse);
+ loanTransactionHelper.printDelinquencyData(getLoansLoanIdResponse);
+
+ final GetDelinquencyRangesResponse secondTestCase = getLoansLoanIdResponse.getDelinquencyRange();
+ assertNotNull(secondTestCase);
+ log.info("Loan Delinquency Range is {}", secondTestCase.getClassification());
+
+ // Then
+ assertNotNull(delinquencyBucketResponse);
+ assertNotNull(getLoanProductsProductResponse);
+ assertNull(firstTestCase);
+ assertEquals(getLoanProductsProductResponse.getDelinquencyBucket().getName(), delinquencyBucket.getName());
+ assertEquals(secondTestCase.getClassification(), classificationExpected);
+
+ GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE);
+ }
+
+ @Test
+ public void testLoanClassificationStepAsPartOfCOB() {
+ GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE);
+
+ LocalDate businessDate = Utils.getLocalDateOfTenant();
+ businessDate = businessDate.minusDays(57);
+ log.info("Current date {}", businessDate);
+ BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, businessDate);
+
+ // Given
+ final LoanTransactionHelper loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+ final SchedulerJobHelper schedulerJobHelper = new SchedulerJobHelper(requestSpec);
+
+ ArrayList<Integer> rangeIds = new ArrayList<>();
+ String jsonRange = DelinquencyRangesHelper.getAsJSON(1, 3);
+ PostDelinquencyRangeResponse delinquencyRangeResponse = DelinquencyRangesHelper.createDelinquencyRange(requestSpec, responseSpec,
+ jsonRange);
+ rangeIds.add(delinquencyRangeResponse.getResourceId());
+ jsonRange = DelinquencyRangesHelper.getAsJSON(4, 60);
+ // Create
+ delinquencyRangeResponse = DelinquencyRangesHelper.createDelinquencyRange(requestSpec, responseSpec, jsonRange);
+ rangeIds.add(delinquencyRangeResponse.getResourceId());
+
+ final GetDelinquencyRangesResponse range = DelinquencyRangesHelper.getDelinquencyRange(requestSpec, responseSpec,
+ delinquencyRangeResponse.getResourceId());
+ final String classificationExpected = range.getClassification();
+ log.info("Expected Delinquency Range classification {}", classificationExpected);
+
+ String jsonBucket = DelinquencyBucketsHelper.getAsJSON(rangeIds);
+ PostDelinquencyBucketResponse delinquencyBucketResponse = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec,
+ responseSpec, jsonBucket);
+ final GetDelinquencyBucketsResponse delinquencyBucket = DelinquencyBucketsHelper.getDelinquencyBucket(requestSpec, responseSpec,
+ delinquencyBucketResponse.getResourceId());
+
+ // Client and Loan account creation
+ final Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2012");
+ final GetLoanProductsProductIdResponse getLoanProductsProductResponse = createLoanProduct(loanTransactionHelper,
+ delinquencyBucket.getId());
+ assertNotNull(getLoanProductsProductResponse);
+ log.info("Loan Product Bucket Name: {}", getLoanProductsProductResponse.getDelinquencyBucket().getName());
+ assertEquals(getLoanProductsProductResponse.getDelinquencyBucket().getName(), delinquencyBucket.getName());
+
+ final LocalDate todaysDate = Utils.getLocalDateOfTenant();
+ // Older date to have more than one overdue installment
+ final LocalDate transactionDate = todaysDate.minusDays(65);
+ String operationDate = Utils.dateFormatter.format(transactionDate);
+
+ // Create Loan Account
+ final Integer loanId = createLoanAccount(loanTransactionHelper, clientId.toString(),
+ getLoanProductsProductResponse.getId().toString(), operationDate);
+
+ // COB Step Validation
+ final JobBusinessStepConfigData jobBusinessStepConfigData = BusinessStepConfigurationHelper
+ .getConfiguredBusinessStepsByJobName(requestSpec, responseSpec, BusinessConfigurationApiTest.LOAN_JOB_NAME);
+ assertNotNull(jobBusinessStepConfigData);
+ assertEquals(BusinessConfigurationApiTest.LOAN_JOB_NAME, jobBusinessStepConfigData.getJobName());
+ assertTrue(jobBusinessStepConfigData.getBusinessSteps().size() > 0);
+ assertTrue(jobBusinessStepConfigData.getBusinessSteps().stream()
+ .anyMatch(businessStep -> BusinessConfigurationApiTest.LOAN_DELINQUENCY_CLASSIFICATION.equals(businessStep.getStepName())));
+
+ // Run first time the Loan COB Job
+ final String jobName = "Loan COB";
+ schedulerJobHelper.executeAndAwaitJob(jobName);
+
+ // Get loan details expecting to have not a delinquency classification
+ GetLoansLoanIdResponse getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
+ final GetDelinquencyRangesResponse firstTestCase = getLoansLoanIdResponse.getDelinquencyRange();
+ log.info("Loan Delinquency Range is null {}", (firstTestCase == null));
+ GetLoansLoanIdRepaymentSchedule getLoanRepaymentSchedule = getLoansLoanIdResponse.getRepaymentSchedule();
+ if (getLoanRepaymentSchedule != null) {
+ log.info("Loan with {} periods", getLoanRepaymentSchedule.getPeriods().size());
+ for (GetLoansLoanIdRepaymentPeriod period : getLoanRepaymentSchedule.getPeriods()) {
+ log.info("Period number {} for due date {} and outstanding {}", period.getPeriod(), period.getDueDate(),
+ period.getTotalOutstandingForPeriod());
+ }
+ }
+
+ // Move the Business date to get older the loan and to have an overdue loan
+ businessDate = businessDate.plusDays(50);
+ log.info("Current date {}", businessDate);
+ BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, businessDate);
+ // Run Second time the Job
+ schedulerJobHelper.executeAndAwaitJob(jobName);
+
+ // Get loan details expecting to have a delinquency classification
+ getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
+ loanTransactionHelper.printRepaymentSchedule(getLoansLoanIdResponse);
+ final GetDelinquencyRangesResponse secondTestCase = getLoansLoanIdResponse.getDelinquencyRange();
+ assertNotNull(secondTestCase);
+ log.info("Loan Delinquency Range is {}", secondTestCase.getClassification());
+
+ // Then
+ assertNotNull(delinquencyBucketResponse);
+ assertNotNull(getLoanProductsProductResponse);
+ assertNull(firstTestCase);
+ assertEquals(getLoanProductsProductResponse.getDelinquencyBucket().getName(), delinquencyBucket.getName());
+ assertEquals(secondTestCase.getClassification(), classificationExpected);
+
+ GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE);
+ }
+
private GetLoanProductsProductIdResponse createLoanProduct(final LoanTransactionHelper loanTransactionHelper,
final Integer delinquencyBucketId) {
final HashMap<String, Object> loanProductMap = new LoanProductTestBuilder().build(null, delinquencyBucketId);
@@ -819,17 +827,6 @@ public class DelinquencyBucketsIntegrationTest {
return loanId;
}
- private void printRepaymentSchedule(GetLoansLoanIdResponse getLoansLoanIdResponse) {
- GetLoansLoanIdRepaymentSchedule getLoanRepaymentSchedule = getLoansLoanIdResponse.getRepaymentSchedule();
- if (getLoanRepaymentSchedule != null) {
- log.info("Loan with {} periods", getLoanRepaymentSchedule.getPeriods().size());
- for (GetLoansLoanIdRepaymentPeriod period : getLoanRepaymentSchedule.getPeriods()) {
- log.info("Period number {} for due date {} and outstanding {}", period.getPeriod(), period.getDueDate(),
- period.getTotalOutstandingForPeriod());
- }
- }
- }
-
private String getChargeApplyJSON(final Integer chargeId, final String dueDate) {
final HashMap<String, Object> map = new HashMap<>();
map.put("chargeId", chargeId);
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionChargebackTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionChargebackTest.java
index e7d5612bf..b4426d739 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionChargebackTest.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionChargebackTest.java
@@ -20,9 +20,8 @@ package org.apache.fineract.integrationtests;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertNull;
-import com.google.gson.Gson;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.builder.ResponseSpecBuilder;
import io.restassured.http.ContentType;
@@ -30,26 +29,23 @@ import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification;
import java.time.LocalDate;
import java.util.HashMap;
-import java.util.List;
import java.util.Set;
import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.client.models.GetDelinquencyBucketsResponse;
+import org.apache.fineract.client.models.GetDelinquencyRangesResponse;
import org.apache.fineract.client.models.GetLoanProductsProductIdResponse;
import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod;
import org.apache.fineract.client.models.GetLoansLoanIdRepaymentSchedule;
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
-import org.apache.fineract.client.models.GetLoansLoanIdSummary;
import org.apache.fineract.client.models.GetLoansLoanIdTransactions;
import org.apache.fineract.client.models.GetLoansLoanIdTransactionsTransactionIdResponse;
-import org.apache.fineract.client.models.GetPaymentTypesResponse;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
-import org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdResponse;
import org.apache.fineract.integrationtests.common.ClientHelper;
-import org.apache.fineract.integrationtests.common.CommonConstants;
-import org.apache.fineract.integrationtests.common.PaymentTypeHelper;
import org.apache.fineract.integrationtests.common.Utils;
import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import org.apache.fineract.integrationtests.common.products.DelinquencyBucketsHelper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -99,7 +95,8 @@ public class LoanTransactionChargebackTest {
reviewLoanTransactionRelations(loanId, transactionId, 0, Double.valueOf("0.00"));
- final Integer chargebackTransactionId = applyChargebackTransaction(loanId, transactionId, "1000.00", 0, responseSpec);
+ final Integer chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId, "1000.00", 0,
+ responseSpec);
reviewLoanTransactionRelations(loanId, transactionId, 1, Double.valueOf("0.00"));
reviewLoanTransactionRelations(loanId, chargebackTransactionId, 0, Double.valueOf("1000.00"));
@@ -141,7 +138,8 @@ public class LoanTransactionChargebackTest {
final Integer transactionId = loanIdTransactionsResponse.getResourceId();
reviewLoanTransactionRelations(loanId, transactionId, 0, Double.valueOf("666.67"));
- final Integer chargebackTransactionId = applyChargebackTransaction(loanId, transactionId, amount.toString(), 0, responseSpec);
+ final Integer chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId, amount.toString(),
+ 0, responseSpec);
reviewLoanTransactionRelations(loanId, transactionId, 1, Double.valueOf("666.67"));
reviewLoanTransactionRelations(loanId, chargebackTransactionId, 0, Double.valueOf("1000.00"));
@@ -160,7 +158,8 @@ public class LoanTransactionChargebackTest {
}
}
- evaluateLoanSummaryAdjustments(getLoansLoanIdResponse, Double.valueOf(baseAmount));
+ loanTransactionHelper.evaluateLoanSummaryAdjustments(getLoansLoanIdResponse, Double.valueOf(baseAmount));
+ DelinquencyBucketsHelper.evaluateLoanCollectionData(getLoansLoanIdResponse, 0, Double.valueOf("333.33"));
}
@Test
@@ -179,7 +178,7 @@ public class LoanTransactionChargebackTest {
log.info("Try to apply the Charge back over transaction Id {} with type {}", loanTransaction.getId(),
loanTransaction.getType().getCode());
- applyChargebackTransaction(loanId, loanTransaction.getId().intValue(), amountVal, 0, responseSpecError);
+ loanTransactionHelper.applyChargebackTransaction(loanId, loanTransaction.getId().intValue(), amountVal, 0, responseSpecError);
}
@Test
@@ -191,6 +190,11 @@ public class LoanTransactionChargebackTest {
assertNotNull(getLoansLoanIdResponse);
loanTransactionHelper.printRepaymentSchedule(getLoansLoanIdResponse);
+
+ GetDelinquencyRangesResponse delinquencyRange = getLoansLoanIdResponse.getDelinquencyRange();
+ assertNotNull(delinquencyRange);
+ log.info("Loan Delinquency Range is {}", delinquencyRange.getClassification());
+
GetLoansLoanIdRepaymentSchedule getLoanRepaymentSchedule = getLoansLoanIdResponse.getRepaymentSchedule();
log.info("Loan with {} periods", getLoanRepaymentSchedule.getPeriods().size());
assertEquals(2, getLoanRepaymentSchedule.getPeriods().size());
@@ -207,7 +211,8 @@ public class LoanTransactionChargebackTest {
reviewLoanTransactionRelations(loanId, transactionId, 0, Double.valueOf("0.00"));
- Integer chargebackTransactionId = applyChargebackTransaction(loanId, transactionId, "500.00", 0, responseSpec);
+ Integer chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId, "500.00", 0,
+ responseSpec);
reviewLoanTransactionRelations(loanId, transactionId, 1, Double.valueOf("0.00"));
reviewLoanTransactionRelations(loanId, chargebackTransactionId, 0, Double.valueOf("500.00"));
@@ -232,7 +237,7 @@ public class LoanTransactionChargebackTest {
}
}
- chargebackTransactionId = applyChargebackTransaction(loanId, transactionId, "300.00", 0, responseSpec);
+ chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId, "300.00", 0, responseSpec);
reviewLoanTransactionRelations(loanId, transactionId, 2, Double.valueOf("0.00"));
reviewLoanTransactionRelations(loanId, chargebackTransactionId, 0, Double.valueOf("800.00"));
@@ -241,6 +246,10 @@ public class LoanTransactionChargebackTest {
assertNotNull(getLoansLoanIdResponse);
loanTransactionHelper.validateLoanStatus(getLoansLoanIdResponse, "loanStatusType.active");
+ delinquencyRange = getLoansLoanIdResponse.getDelinquencyRange();
+ assertNull(delinquencyRange);
+ log.info("Loan Delinquency Range is null {}", (delinquencyRange == null));
+
loanTransactionHelper.validateLoanPrincipalOustandingBalance(getLoansLoanIdResponse, Double.valueOf("800.00"));
// N+1 Scenario -- Remains the same periods number
@@ -280,7 +289,8 @@ public class LoanTransactionChargebackTest {
reviewLoanTransactionRelations(loanId, transactionId, 0, Double.valueOf("0.00"));
- final Integer chargebackTransactionId = applyChargebackTransaction(loanId, transactionId, "200.00", 0, responseSpec);
+ final Integer chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId, "200.00", 0,
+ responseSpec);
reviewLoanTransactionRelations(loanId, transactionId, 1, Double.valueOf("0.00"));
reviewLoanTransactionRelations(loanId, chargebackTransactionId, 0, Double.valueOf("100.00"));
@@ -290,6 +300,10 @@ public class LoanTransactionChargebackTest {
loanTransactionHelper.validateLoanStatus(getLoansLoanIdResponse, "loanStatusType.active");
loanTransactionHelper.validateLoanPrincipalOustandingBalance(getLoansLoanIdResponse, Double.valueOf("100.00"));
+
+ final GetDelinquencyRangesResponse delinquencyRange = getLoansLoanIdResponse.getDelinquencyRange();
+ assertNull(delinquencyRange);
+ log.info("Loan Delinquency Range is null {}", (delinquencyRange == null));
}
@Test
@@ -314,7 +328,8 @@ public class LoanTransactionChargebackTest {
reviewLoanTransactionRelations(loanId, transactionId, 0, Double.valueOf("0.00"));
- final Integer chargebackTransactionId = applyChargebackTransaction(loanId, transactionId, "100.00", 0, responseSpec);
+ final Integer chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId, "100.00", 0,
+ responseSpec);
reviewLoanTransactionRelations(loanId, transactionId, 1, Double.valueOf("0.00"));
reviewLoanTransactionRelations(loanId, chargebackTransactionId, 0, Double.valueOf("0.00"));
@@ -348,8 +363,11 @@ public class LoanTransactionChargebackTest {
reviewLoanTransactionRelations(loanId, transactionId, 0, Double.valueOf("0.00"));
- final Integer chargebackTransactionId = applyChargebackTransaction(loanId, transactionId, "50.00", 0, responseSpec);
-
+ GetDelinquencyRangesResponse delinquencyRange = getLoansLoanIdResponse.getDelinquencyRange();
+ assertNull(delinquencyRange);
+ log.info("Loan Delinquency Range is null {}", (delinquencyRange == null));
+ final Integer chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId, "50.00", 0,
+ responseSpec);
reviewLoanTransactionRelations(loanId, transactionId, 1, Double.valueOf("0.00"));
reviewLoanTransactionRelations(loanId, chargebackTransactionId, 0, Double.valueOf("0.00"));
@@ -357,6 +375,10 @@ public class LoanTransactionChargebackTest {
assertNotNull(getLoansLoanIdResponse);
loanTransactionHelper.validateLoanStatus(getLoansLoanIdResponse, "loanStatusType.overpaid");
+ delinquencyRange = getLoansLoanIdResponse.getDelinquencyRange();
+ assertNull(delinquencyRange);
+ log.info("Loan Delinquency Range is null {}", (delinquencyRange == null));
+
loanTransactionHelper.validateLoanPrincipalOustandingBalance(getLoansLoanIdResponse, Double.valueOf("0.00"));
}
@@ -383,38 +405,56 @@ public class LoanTransactionChargebackTest {
// First round, empty array
reviewLoanTransactionRelations(loanId, transactionId, 0, Double.valueOf("0.00"));
- applyChargebackTransaction(loanId, transactionId, "200.00", 0, responseSpec);
+ loanTransactionHelper.applyChargebackTransaction(loanId, transactionId, "200.00", 0, responseSpec);
+ Double expectedAmount = Double.valueOf("200.00");
getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
- loanTransactionHelper.validateLoanPrincipalOustandingBalance(getLoansLoanIdResponse, Double.valueOf("200.00"));
+ loanTransactionHelper.validateLoanPrincipalOustandingBalance(getLoansLoanIdResponse, expectedAmount);
- evaluateLoanSummaryAdjustments(getLoansLoanIdResponse, Double.valueOf("200.00"));
+ loanTransactionHelper.evaluateLoanSummaryAdjustments(getLoansLoanIdResponse, expectedAmount);
+ loanTransactionHelper.printDelinquencyData(getLoansLoanIdResponse);
+ DelinquencyBucketsHelper.evaluateLoanCollectionData(getLoansLoanIdResponse, 0, expectedAmount);
// Second round, array size equal to 1
reviewLoanTransactionRelations(loanId, transactionId, 1, Double.valueOf("0.00"));
- applyChargebackTransaction(loanId, transactionId, "300.00", 1, responseSpec);
+ loanTransactionHelper.applyChargebackTransaction(loanId, transactionId, "300.00", 1, responseSpec);
+ expectedAmount = Double.valueOf("500.00");
getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
- loanTransactionHelper.validateLoanPrincipalOustandingBalance(getLoansLoanIdResponse, Double.valueOf("500.00"));
+ loanTransactionHelper.validateLoanPrincipalOustandingBalance(getLoansLoanIdResponse, expectedAmount);
- evaluateLoanSummaryAdjustments(getLoansLoanIdResponse, Double.valueOf("500.00"));
+ loanTransactionHelper.evaluateLoanSummaryAdjustments(getLoansLoanIdResponse, expectedAmount);
+ DelinquencyBucketsHelper.evaluateLoanCollectionData(getLoansLoanIdResponse, 0, expectedAmount);
// Third round, array size equal to 2
reviewLoanTransactionRelations(loanId, transactionId, 2, Double.valueOf("0.00"));
- applyChargebackTransaction(loanId, transactionId, "500.00", 0, responseSpec);
+ loanTransactionHelper.applyChargebackTransaction(loanId, transactionId, "500.00", 0, responseSpec);
+ expectedAmount = Double.valueOf("1000.00");
getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
- loanTransactionHelper.validateLoanPrincipalOustandingBalance(getLoansLoanIdResponse, Double.valueOf("1000.00"));
+ loanTransactionHelper.validateLoanPrincipalOustandingBalance(getLoansLoanIdResponse, expectedAmount);
- evaluateLoanSummaryAdjustments(getLoansLoanIdResponse, Double.valueOf("1000.00"));
+ loanTransactionHelper.evaluateLoanSummaryAdjustments(getLoansLoanIdResponse, expectedAmount);
+ loanTransactionHelper.printRepaymentSchedule(getLoansLoanIdResponse);
+
+ DelinquencyBucketsHelper.evaluateLoanCollectionData(getLoansLoanIdResponse, 0, expectedAmount);
}
private Integer createAccounts(final Integer daysToSubtract, final Integer numberOfRepayments) {
+ // Delinquency Bucket
+ final Integer delinquencyBucketId = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, responseSpec);
+ final GetDelinquencyBucketsResponse delinquencyBucket = DelinquencyBucketsHelper.getDelinquencyBucket(requestSpec, responseSpec,
+ delinquencyBucketId);
+
// Client and Loan account creation
final Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2012");
- final GetLoanProductsProductIdResponse getLoanProductsProductResponse = createLoanProduct(loanTransactionHelper, null);
+ final GetLoanProductsProductIdResponse getLoanProductsProductResponse = createLoanProduct(loanTransactionHelper,
+ delinquencyBucketId);
+ assertNotNull(getLoanProductsProductResponse);
+ log.info("Loan Product Bucket Name: {}", getLoanProductsProductResponse.getDelinquencyBucket().getName());
+ assertEquals(getLoanProductsProductResponse.getDelinquencyBucket().getName(), delinquencyBucket.getName());
// Older date to have more than one overdue installment
final LocalDate transactionDate = this.todaysDate.minusDays(daysToSubtract + (30 * (numberOfRepayments - 1)));
@@ -424,16 +464,6 @@ public class LoanTransactionChargebackTest {
operationDate, amountVal, numberOfRepayments.toString());
}
- private String createChargebackPayload(final String transactionAmount, final Integer paymentTypeId) {
- final HashMap<String, Object> map = new HashMap<>();
- map.put("transactionAmount", transactionAmount);
- map.put("paymentTypeId", paymentTypeId);
- map.put("locale", CommonConstants.LOCALE);
- final String chargebackPayload = new Gson().toJson(map);
- log.info("{}", chargebackPayload);
- return chargebackPayload;
- }
-
private GetLoanProductsProductIdResponse createLoanProduct(final LoanTransactionHelper loanTransactionHelper,
final Integer delinquencyBucketId) {
final HashMap<String, Object> loanProductMap = new LoanProductTestBuilder().build(null, delinquencyBucketId);
@@ -457,21 +487,6 @@ public class LoanTransactionChargebackTest {
return loanId;
}
- private Integer applyChargebackTransaction(final Integer loanId, final Integer transactionId, final String amount,
- final Integer paymentTypeIdx, ResponseSpecification responseSpec) {
- List<GetPaymentTypesResponse> paymentTypeList = PaymentTypeHelper.getSystemPaymentType(this.requestSpec, this.responseSpec);
- assertTrue(!paymentTypeList.isEmpty());
-
- final String payload = createChargebackPayload(amount, paymentTypeList.get(paymentTypeIdx).getId());
- log.info("Loan Chargeback: {}", payload);
- PostLoansLoanIdTransactionsTransactionIdResponse postLoansTransactionCommandResponse = loanTransactionHelper
- .applyLoanTransactionCommand(loanId, transactionId, "chargeback", payload, responseSpec);
- assertNotNull(postLoansTransactionCommandResponse);
-
- log.info("Loan Chargeback Id: {}", postLoansTransactionCommandResponse.getResourceId());
- return postLoansTransactionCommandResponse.getResourceId();
- }
-
private void reviewLoanTransactionRelations(final Integer loanId, final Integer transactionId, final Integer expectedSize,
final Double outstandingBalance) {
log.info("Loan Transaction Id: {} {}", loanId, transactionId);
@@ -487,13 +502,4 @@ public class LoanTransactionChargebackTest {
assertEquals(outstandingBalance, getLoansTransactionResponse.getOutstandingLoanBalance());
}
- private void evaluateLoanSummaryAdjustments(GetLoansLoanIdResponse getLoansLoanIdResponse, Double amountExpected) {
- // Evaluate The Loan Summary Principal Adjustments
- GetLoansLoanIdSummary getLoansLoanIdSummary = getLoansLoanIdResponse.getSummary();
- if (getLoansLoanIdSummary != null) {
- log.info("Loan with Principal Adjustments {} expected {}", getLoansLoanIdSummary.getPrincipalAdjustments(), amountExpected);
- assertEquals(amountExpected, getLoansLoanIdSummary.getPrincipalAdjustments());
- }
- }
-
}
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
index df68df1c1..a0a624ae6 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
@@ -20,6 +20,7 @@ package org.apache.fineract.integrationtests.common.loans;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import com.google.common.reflect.TypeToken;
@@ -43,11 +44,13 @@ import javax.ws.rs.core.MediaType;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.client.models.GetDelinquencyTagHistoryResponse;
import org.apache.fineract.client.models.GetLoanProductsProductIdResponse;
+import org.apache.fineract.client.models.GetLoansLoanIdCollectionData;
import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod;
import org.apache.fineract.client.models.GetLoansLoanIdRepaymentSchedule;
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
import org.apache.fineract.client.models.GetLoansLoanIdSummary;
import org.apache.fineract.client.models.GetLoansLoanIdTransactionsTransactionIdResponse;
+import org.apache.fineract.client.models.GetPaymentTypesResponse;
import org.apache.fineract.client.models.PostLoansLoanIdChargesResponse;
import org.apache.fineract.client.models.PostLoansLoanIdResponse;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
@@ -55,6 +58,7 @@ import org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionI
import org.apache.fineract.client.models.PutLoansLoanIdResponse;
import org.apache.fineract.client.util.JSON;
import org.apache.fineract.integrationtests.common.CommonConstants;
+import org.apache.fineract.integrationtests.common.PaymentTypeHelper;
import org.apache.fineract.integrationtests.common.Utils;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Workbook;
@@ -1155,12 +1159,19 @@ public class LoanTransactionHelper {
if (getLoanRepaymentSchedule != null) {
log.info("Loan with {} periods", getLoanRepaymentSchedule.getPeriods().size());
for (GetLoansLoanIdRepaymentPeriod period : getLoanRepaymentSchedule.getPeriods()) {
- log.info("Period number {} for due date {} and outstanding {}", period.getPeriod(), period.getDueDate(),
- period.getTotalOutstandingForPeriod());
+ log.info("Period number {} for due date {} and outstanding {} {}", period.getPeriod(), period.getDueDate(),
+ period.getTotalOutstandingForPeriod(), period.getComplete());
}
}
}
+ public void printDelinquencyData(GetLoansLoanIdResponse getLoansLoanIdResponse) {
+ GetLoansLoanIdCollectionData getLoansLoanIdCollectionData = getLoansLoanIdResponse.getDelinquent();
+ if (getLoansLoanIdCollectionData != null) {
+ log.info("Loan Delinquency {}", getLoansLoanIdCollectionData.toString());
+ }
+ }
+
public void validateLoanStatus(GetLoansLoanIdResponse getLoansLoanIdResponse, final String statusCodeExpected) {
final String statusCode = getLoansLoanIdResponse.getStatus().getCode();
log.info("Loan with Id {} is with Status {}", getLoansLoanIdResponse.getId(), statusCode);
@@ -1192,4 +1203,46 @@ public class LoanTransactionHelper {
}
}
+ public Integer applyChargebackTransaction(final Integer loanId, final Integer transactionId, final String amount,
+ final Integer paymentTypeIdx, ResponseSpecification responseSpec) {
+ List<GetPaymentTypesResponse> paymentTypeList = PaymentTypeHelper.getSystemPaymentType(this.requestSpec, this.responseSpec);
+ assertTrue(!paymentTypeList.isEmpty());
+
+ final String payload = createChargebackPayload(amount, paymentTypeList.get(paymentTypeIdx).getId());
+ log.info("Loan Chargeback: {}", payload);
+ PostLoansLoanIdTransactionsTransactionIdResponse postLoansTransactionCommandResponse = applyLoanTransactionCommand(loanId,
+ transactionId, "chargeback", payload, responseSpec);
+ assertNotNull(postLoansTransactionCommandResponse);
+
+ log.info("Loan Chargeback Id: {}", postLoansTransactionCommandResponse.getResourceId());
+ return postLoansTransactionCommandResponse.getResourceId();
+ }
+
+ public void reviewLoanTransactionRelations(final Integer loanId, final Integer transactionId, final Integer expectedSize) {
+ GetLoansLoanIdTransactionsTransactionIdResponse getLoansTransactionResponse = getLoanTransaction(loanId, transactionId);
+ assertNotNull(getLoansTransactionResponse);
+ assertNotNull(getLoansTransactionResponse.getTransactionRelations());
+ assertEquals(expectedSize, getLoansTransactionResponse.getTransactionRelations().size());
+ log.info("Loan with {} Chargeback Transactions", getLoansTransactionResponse.getTransactionRelations().size());
+ }
+
+ public void evaluateLoanSummaryAdjustments(GetLoansLoanIdResponse getLoansLoanIdResponse, Double amountExpected) {
+ // Evaluate The Loan Summary Principal Adjustments
+ GetLoansLoanIdSummary getLoansLoanIdSummary = getLoansLoanIdResponse.getSummary();
+ if (getLoansLoanIdSummary != null) {
+ log.info("Loan with Principal Adjustments {} expected {}", getLoansLoanIdSummary.getPrincipalAdjustments(), amountExpected);
+ assertEquals(amountExpected, getLoansLoanIdSummary.getPrincipalAdjustments());
+ }
+ }
+
+ private String createChargebackPayload(final String transactionAmount, final Integer paymentTypeId) {
+ final HashMap<String, Object> map = new HashMap<>();
+ map.put("transactionAmount", transactionAmount);
+ map.put("paymentTypeId", paymentTypeId);
+ map.put("locale", CommonConstants.LOCALE);
+ final String chargebackPayload = new Gson().toJson(map);
+ log.info("{}", chargebackPayload);
+ return chargebackPayload;
+ }
+
}
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/products/DelinquencyBucketsHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/products/DelinquencyBucketsHelper.java
index 7027e847a..7b5810b1d 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/products/DelinquencyBucketsHelper.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/products/DelinquencyBucketsHelper.java
@@ -18,6 +18,10 @@
*/
package org.apache.fineract.integrationtests.common.products;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
import com.google.gson.Gson;
import com.linecorp.armeria.internal.shaded.guava.reflect.TypeToken;
import io.restassured.specification.RequestSpecification;
@@ -25,22 +29,24 @@ import io.restassured.specification.ResponseSpecification;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
+import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.client.models.DeleteDelinquencyBucketResponse;
import org.apache.fineract.client.models.GetDelinquencyBucketsResponse;
+import org.apache.fineract.client.models.GetDelinquencyRangesResponse;
+import org.apache.fineract.client.models.GetLoansLoanIdCollectionData;
+import org.apache.fineract.client.models.GetLoansLoanIdResponse;
import org.apache.fineract.client.models.PostDelinquencyBucketResponse;
+import org.apache.fineract.client.models.PostDelinquencyRangeResponse;
import org.apache.fineract.client.models.PutDelinquencyBucketResponse;
import org.apache.fineract.client.util.JSON;
import org.apache.fineract.integrationtests.common.Utils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+@Slf4j
public class DelinquencyBucketsHelper {
private static final String DELINQUENCY_BUCKETS_URL = "/fineract-provider/api/v1/delinquency/buckets";
private static final Gson GSON = new JSON().getGson();
- private static final Logger LOG = LoggerFactory.getLogger(DelinquencyBucketsHelper.class);
-
protected DelinquencyBucketsHelper() {}
public static ArrayList<GetDelinquencyBucketsResponse> getDelinquencyBuckets(final RequestSpecification requestSpec,
@@ -60,7 +66,7 @@ public class DelinquencyBucketsHelper {
public static PostDelinquencyBucketResponse createDelinquencyBucket(final RequestSpecification requestSpec,
final ResponseSpecification responseSpec, final String json) {
- LOG.info("JSON: {}", json);
+ log.info("JSON: {}", json);
final String response = Utils.performServerPost(requestSpec, responseSpec, DELINQUENCY_BUCKETS_URL + "?" + Utils.TENANT_IDENTIFIER,
json, null);
return GSON.fromJson(response, PostDelinquencyBucketResponse.class);
@@ -68,7 +74,7 @@ public class DelinquencyBucketsHelper {
public static PutDelinquencyBucketResponse updateDelinquencyBucket(final RequestSpecification requestSpec,
final ResponseSpecification responseSpec, final Integer resourceId, final String json) {
- LOG.info("JSON: {}", json);
+ log.info("JSON: {}", json);
final String response = Utils.performServerPut(requestSpec, responseSpec,
DELINQUENCY_BUCKETS_URL + "/" + resourceId + "?" + Utils.TENANT_IDENTIFIER, json, null);
return GSON.fromJson(response, PutDelinquencyBucketResponse.class);
@@ -88,4 +94,52 @@ public class DelinquencyBucketsHelper {
return new Gson().toJson(map);
}
+ public static Integer createDelinquencyBucket(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+ ArrayList<Integer> rangeIds = new ArrayList<>();
+
+ // First Range
+ String jsonRange = DelinquencyRangesHelper.getAsJSON(1, 3);
+ PostDelinquencyRangeResponse delinquencyRangeResponse = DelinquencyRangesHelper.createDelinquencyRange(requestSpec, responseSpec,
+ jsonRange);
+ rangeIds.add(delinquencyRangeResponse.getResourceId());
+ jsonRange = DelinquencyRangesHelper.getAsJSON(4, 60);
+ GetDelinquencyRangesResponse range = DelinquencyRangesHelper.getDelinquencyRange(requestSpec, responseSpec,
+ delinquencyRangeResponse.getResourceId());
+
+ // Second Range
+ delinquencyRangeResponse = DelinquencyRangesHelper.createDelinquencyRange(requestSpec, responseSpec, jsonRange);
+ rangeIds.add(delinquencyRangeResponse.getResourceId());
+ range = DelinquencyRangesHelper.getDelinquencyRange(requestSpec, responseSpec, delinquencyRangeResponse.getResourceId());
+
+ String jsonBucket = DelinquencyBucketsHelper.getAsJSON(rangeIds);
+ PostDelinquencyBucketResponse delinquencyBucketResponse = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec,
+ responseSpec, jsonBucket);
+ assertNotNull(delinquencyBucketResponse);
+
+ return delinquencyBucketResponse.getResourceId();
+ }
+
+ public static void evaluateLoanCollectionData(GetLoansLoanIdResponse getLoansLoanIdResponse, Integer pastDueDays,
+ Double amountExpected) {
+ GetLoansLoanIdCollectionData getCollectionData = getLoansLoanIdResponse.getDelinquent();
+ if (getCollectionData != null) {
+ log.info("Loan Delinquency Data in Days {} and Amount {}", getCollectionData.getPastDueDays(),
+ getCollectionData.getDelinquentAmount());
+ assertEquals(pastDueDays, getCollectionData.getPastDueDays());
+ assertEquals(amountExpected, getCollectionData.getDelinquentAmount());
+ } else {
+ log.info("Loan Delinquency Data is null");
+ }
+
+ GetDelinquencyRangesResponse delinquencyRange = getLoansLoanIdResponse.getDelinquencyRange();
+ if (delinquencyRange != null) {
+ log.info("Loan Delinquency Classification is {} : ({} - {}) {}", delinquencyRange.getClassification(),
+ delinquencyRange.getMinimumAgeDays(), delinquencyRange.getMaximumAgeDays(), pastDueDays);
+ assertTrue(delinquencyRange.getMinimumAgeDays() <= pastDueDays);
+ assertTrue(delinquencyRange.getMaximumAgeDays() >= pastDueDays);
+ } else {
+ log.info("Loan Delinquency Classification is null");
+ }
+ }
+
}
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/products/DelinquencyRangesHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/products/DelinquencyRangesHelper.java
index 0a7af2d48..32a7cef4a 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/products/DelinquencyRangesHelper.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/products/DelinquencyRangesHelper.java
@@ -85,7 +85,7 @@ public class DelinquencyRangesHelper {
public static String getAsJSON(int minimumAgeDays, int maximumAgeDays) {
final HashMap<String, Object> map = new HashMap<>();
- map.put("classification", Utils.randomNameGenerator("Delinquency_", 4));
+ map.put("classification", Utils.randomNameGenerator("Delinquency__" + minimumAgeDays + "_" + maximumAgeDays + "__", 4));
map.put("minimumAgeDays", minimumAgeDays);
map.put("maximumAgeDays", maximumAgeDays);
map.put("locale", "en");