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 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 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 chargebackTransactions = loan.retrieveListOfTransactionsByType(LoanTransactionType.CHARGEBACK); + for (LoanTransaction loanTransaction : chargebackTransactions) { + Set loanTransactionToRepaymentScheduleMappings = loanTransaction + .getLoanTransactionToRepaymentScheduleMappings(); + LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = loanTransactionToRepaymentScheduleMappings + .iterator().next(); + if (!loanTransactionToRepaymentScheduleMapping.getLoanRepaymentScheduleInstallment().isPrincipalCompleted(currency)) { + overdueSinceDate = loanTransaction.getTransactionDate(); + break; + } + } + + final List 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 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 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 lookUpDelinquencyRange(final Loan loan, final DelinquencyBucket delinquencyBucket, long ageDays) { + private Map lookUpDelinquencyRange(final Loan loan, final DelinquencyBucket delinquencyBucket, long overdueDays) { Map 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 setLoanDelinquencyTag(Loan loan, Long delinquencyRangeId) { + private Map setLoanDelinquencyTag(Loan loan, Long delinquencyRangeId) { Map changes = new HashMap<>(); List 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 allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); + final List allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement(); changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges()); for (final Map.Entry 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 allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); + final List 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 allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); + final List 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 allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); + final List 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 accruals = retreiveListOfAccrualTransactions(); + Collection 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 allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); + final List 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 allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); + final List allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement(); changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges()); for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { @@ -3300,7 +3300,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { return installment; } - private List retreiveListOfIncomePostingTransactions() { + private List retrieveListOfIncomePostingTransactions() { final List incomePostTransactions = new ArrayList<>(); List trans = getLoanTransactions(); for (final LoanTransaction transaction : trans) { @@ -3313,7 +3313,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { return incomePostTransactions; } - private List retreiveListOfTransactionsPostDisbursement() { + private List retrieveListOfTransactionsPostDisbursement() { final List repaymentsOrWaivers = new ArrayList<>(); List trans = getLoanTransactions(); for (final LoanTransaction transaction : trans) { @@ -3326,7 +3326,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { return repaymentsOrWaivers; } - public List retreiveListOfTransactionsPostDisbursementExcludeAccruals() { + public List retrieveListOfTransactionsPostDisbursementExcludeAccruals() { final List 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 retreiveListOfTransactionsExcludeAccruals() { + private List retrieveListOfTransactionsExcludeAccruals() { final List 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 retreiveListOfAccrualTransactions() { + private List retrieveListOfAccrualTransactions() { final List 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 retrieveListOfTransactionsByType(LoanTransactionType transactionType) { + final List 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 cumulativeIncomeFromInstallments = new HashMap<>(); determineCumulativeIncomeFromInstallments(cumulativeIncomeFromInstallments); HashMap 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 updatedAccrualTransactions = retreiveListOfAccrualTransactions(); + List 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 allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); + final List 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 allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); + final List allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement(); changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges()); for (final Map.Entry 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 allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); + final List 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 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 allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); + final List 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 compoundingDetails = extractInterestRecalculationAdditionalDetails(); - List incomeTransactions = retreiveListOfIncomePostingTransactions(); - List accrualTransactions = retreiveListOfAccrualTransactions(); + List incomeTransactions = retrieveListOfIncomePostingTransactions(); + List 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 allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); + final List allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement(); final List 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 loanTransactions = retreiveListOfTransactionsExcludeAccruals(); + List 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 allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); + final List allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement(); changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges()); for (final Map.Entry 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 loanTransactions = retreiveListOfTransactionsExcludeAccruals(); + List 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, JpaSpecificationExecutor { - 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 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 fetchLoanScheduleDataByDueDateAndObligationsMet(Integer loanStatus, LocalDate businessDate, + boolean obligationsMet, List loanIds); - List 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, JpaSpecificationExecutor { Optional 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 fetchLoanTransactionsByTypeAndLessOrEqualDate(Integer transactionType, LocalDate businessDate); + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionToRepaymentScheduleMappingRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionToRepaymentScheduleMappingRepository.java new file mode 100644 index 000000000..7dfe8e990 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionToRepaymentScheduleMappingRepository.java @@ -0,0 +1,29 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanaccount.domain; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +public interface LoanTransactionToRepaymentScheduleMappingRepository extends JpaRepository, + JpaSpecificationExecutor { + + LoanTransactionToRepaymentScheduleMapping findByLoanTransaction(LoanTransaction loanTransaction); + +} 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 = 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 = this.loanTransactionRepository + .fetchLoanTransactionsByTypeAndLessOrEqualDate(LoanTransactionType.CHARGEBACK.getValue(), businessDate); + List 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 applyDelinquencyTagToLoans(Collection loanScheduleDelinquencyData) { + List 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 loanTransactions = loan.retreiveListOfTransactionsPostDisbursementExcludeAccruals(); + List 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 retriveScheduleAccrualData(); - Collection retrieveScheduleDelinquencyData(LocalDate businessLocalDate); - LoanTransactionData retrieveRecoveryPaymentTemplate(Long loanId); LoanTransactionData retrieveLoanWriteoffTemplate(Long loanId); @@ -157,8 +153,6 @@ public interface LoanReadPlatformService { List getRepaymentDataResponse(Long loanId); - CollectionData retrieveLoanCollectionData(Long loanId); - List 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, boolean isInterestRecalculationEnabled, BigDecimal totalPaidFeeCharges) { + Collection 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 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 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 retriveScheduleAccrualData() { @@ -1887,33 +1862,6 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { } } - private static final class LoanScheduleDelinquencyMapper implements RowMapper { - - 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 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 { - - 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 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 savingsAccountTransactions = retreiveListOfTransactions(); + final List 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 savingsAccountTransactions = retreiveListOfTransactions(); + final List 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 savingsAccountTransactions = retreiveListOfTransactions(); + final List 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 savingsAccountTransactions = retreiveListOfTransactions(); + final List 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 retreiveOrderedDepositTransactions() { - final List listOfTransactionsSorted = retreiveListOfTransactions(); + final List listOfTransactionsSorted = retrieveListOfTransactions(); final List 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 retreiveOrderedNonInterestPostingTransactions() { - final List listOfTransactionsSorted = retreiveListOfTransactions(); + final List listOfTransactionsSorted = retrieveListOfTransactions(); final List orderedNonInterestPostingTransactions = new ArrayList<>(); @@ -965,7 +965,7 @@ public class SavingsAccount extends AbstractPersistableCustom { return listOfTransactionsSorted; } - protected List retreiveListOfTransactions() { + protected List retrieveListOfTransactions() { final List 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 transactionsSortedByDate = retreiveListOfTransactions(); + // final List 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 savingsAccountTransactions = retreiveListOfTransactions(); + final List 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 transactionsSortedByDate = retreiveListOfTransactions(); + final List 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 retreiveOrderedNonInterestPostingTransactions(final SavingsAccountData savingsAccountData) { - final List listOfTransactionsSorted = retreiveListOfTransactions(savingsAccountData); + final List listOfTransactionsSorted = retrieveListOfTransactions(savingsAccountData); final List orderedNonInterestPostingTransactions = new ArrayList<>(); @@ -319,7 +319,7 @@ public class SavingsAccountInterestPostingServiceImpl implements SavingsAccountI return orderedNonInterestPostingTransactions; } - private List retreiveListOfTransactions(final SavingsAccountData savingsAccountData) { + private List retrieveListOfTransactions(final SavingsAccountData savingsAccountData) { final List listOfTransactionsSorted = new ArrayList<>(); listOfTransactionsSorted.addAll(savingsAccountData.getSavingsAccountTransactionData()); @@ -407,7 +407,7 @@ public class SavingsAccountInterestPostingServiceImpl implements SavingsAccountI Money runningBalance = openingAccountBalance.copy(); - List accountTransactionsSorted = retreiveListOfTransactions(savingsAccountData); + List 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 @@ + 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 @@ + + + + + + + + + + + + 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 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,12 +234,394 @@ public class DelinquencyBucketsIntegrationTest { DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, response403Spec, jsonBucket); } + @Test + public void testLoanClassificationRealtime() { + // Given + final LoanTransactionHelper loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec); + + ArrayList 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()); + final String classificationExpected = range.getClassification(); + log.info("Expected Delinquency Range classification after Disbursement {}", classificationExpected); + + String jsonBucket = DelinquencyBucketsHelper.getAsJSON(rangeIds); + PostDelinquencyBucketResponse delinquencyBucketResponse = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, + responseSpec, jsonBucket); + assertNotNull(delinquencyBucketResponse); + 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(50); + String operationDate = Utils.dateFormatter.format(transactionDate); + + // Create Loan Account + final Integer loanId = createLoanAccount(loanTransactionHelper, clientId.toString(), + getLoanProductsProductResponse.getId().toString(), operationDate); + + GetLoansLoanIdResponse getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); + log.info("Loan Delinquency Range after Disbursement {}", getLoansLoanIdResponse.getDelinquencyRange().getClassification()); + assertNotNull(getLoansLoanIdResponse); + // First Loan Delinquency Classification after Disbursement command + assertEquals(getLoansLoanIdResponse.getDelinquencyRange().getClassification(), classificationExpected); + + loanTransactionHelper.printRepaymentSchedule(getLoansLoanIdResponse); + + // Apply a partial repayment + operationDate = Utils.dateFormatter.format(todaysDate); + loanTransactionHelper.makeLoanRepayment(operationDate, 100.0f, loanId); + + getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); + log.info("Loan Delinquency Range after Repayment {}", getLoansLoanIdResponse.getDelinquencyRange()); + assertNotNull(getLoansLoanIdResponse.getDelinquencyRange()); + // First Loan Delinquency Classification remains after Repayment because the installment is not fully paid + assertEquals(getLoansLoanIdResponse.getDelinquencyRange().getClassification(), classificationExpected); + + // Apply a repayment to get a full paid installment + loanTransactionHelper.makeLoanRepayment(operationDate, 1000.0f, 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 + assertNull(getLoansLoanIdResponse.getDelinquencyRange()); + // Get the Delinquency Tags + ArrayList getDelinquencyTagsHistory = loanTransactionHelper.getLoanDelinquencyTags(requestSpec, + responseSpec, loanId); + assertNotNull(getDelinquencyTagsHistory); + log.info("Delinquency Tag History items {}", getDelinquencyTagsHistory.size()); + assertEquals(1, getDelinquencyTagsHistory.size()); + assertNotNull(getDelinquencyTagsHistory.get(0).getLiftedOnDate()); + assertEquals(getDelinquencyTagsHistory.get(0).getAddedOnDate(), Utils.getLocalDateOfTenant()); + assertEquals(getDelinquencyTagsHistory.get(0).getLiftedOnDate(), Utils.getLocalDateOfTenant()); + assertEquals(getDelinquencyTagsHistory.get(0).getDelinquencyRange().getClassification(), classificationExpected); + log.info("Delinquency Tag Item with Lifted On {}", getDelinquencyTagsHistory.get(0).getLiftedOnDate()); + } + + @Test + public void testLoanClassificationRealtimeWithCharges() { + GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); + + // Given + final LoanTransactionHelper loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec); + + ArrayList 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()); + final String classificationExpected = range.getClassification(); + log.info("Expected Delinquency Range classification after Disbursement {}", classificationExpected); + + String jsonBucket = DelinquencyBucketsHelper.getAsJSON(rangeIds); + PostDelinquencyBucketResponse delinquencyBucketResponse = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, + responseSpec, jsonBucket); + assertNotNull(delinquencyBucketResponse); + 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 + LocalDate transactionDate = todaysDate.minusMonths(2).minusDays(5); + String operationDate = Utils.dateFormatter.format(transactionDate); + + // Create Loan Account + final Integer loanId = createLoanAccount(loanTransactionHelper, clientId.toString(), + getLoanProductsProductResponse.getId().toString(), operationDate); + + GetLoansLoanIdResponse getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); + log.info("Loan Delinquency Range after Disbursement {}", getLoansLoanIdResponse.getDelinquencyRange().getClassification()); + assertNotNull(getLoansLoanIdResponse); + // First Loan Delinquency Classification after Disbursement command + assertEquals(getLoansLoanIdResponse.getDelinquencyRange().getClassification(), classificationExpected); + loanTransactionHelper.printRepaymentSchedule(getLoansLoanIdResponse); + + // Apply a repayment to get a full paid installment + operationDate = Utils.dateFormatter.format(todaysDate); + loanTransactionHelper.makeLoanRepayment(operationDate, 2049.99f, loanId); + + getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); + assertNotNull(getLoansLoanIdResponse); + // The Loan Delinquency Classification after Repayment command must be null + log.info("Loan Delinquency Range after Repayment {}", getLoansLoanIdResponse.getDelinquencyRange()); + assertNull(getLoansLoanIdResponse.getDelinquencyRange()); + loanTransactionHelper.printRepaymentSchedule(getLoansLoanIdResponse); + + transactionDate = todaysDate.minusDays(18); + operationDate = Utils.dateFormatter.format(transactionDate); + + // Create and apply Charge for Specific Due Date + final Integer chargeId = ChargesHelper.createCharges(requestSpec, responseSpec, + ChargesHelper.getLoanSpecifiedDueDateJSON(1, "30", false)); + assertNotNull(chargeId); + final Integer loanChargeId = loanTransactionHelper.addChargesForLoan(loanId, getChargeApplyJSON(chargeId, operationDate), + responseSpec); + assertNotNull(loanChargeId); + + getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); + loanTransactionHelper.printRepaymentSchedule(getLoansLoanIdResponse); + + log.info("Loan Delinquency Range after add Loan Charge {}", getLoansLoanIdResponse.getDelinquencyRange()); + assertNotNull(getLoansLoanIdResponse.getDelinquencyRange()); + // Evaluate a Delinquency Tag set after add charge to the Loan + assertEquals(getLoansLoanIdResponse.getDelinquencyRange().getClassification(), classificationExpected); + } + + @Test + public void testLoanClassificationRealtimeOlderLoan() { + GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); + + // Given + final LoanTransactionHelper loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec); + + ArrayList rangeIds = new ArrayList<>(); + // First Range + String jsonRange = DelinquencyRangesHelper.getAsJSON(4, 30); + PostDelinquencyRangeResponse delinquencyRangeResponse = DelinquencyRangesHelper.createDelinquencyRange(requestSpec, responseSpec, + jsonRange); + rangeIds.add(delinquencyRangeResponse.getResourceId()); + GetDelinquencyRangesResponse range = DelinquencyRangesHelper.getDelinquencyRange(requestSpec, responseSpec, + delinquencyRangeResponse.getResourceId()); + final String classificationExpected02 = range.getClassification(); + log.info("Expected Delinquency Range classification after first repayment {}", classificationExpected02); + + // Second Range + jsonRange = DelinquencyRangesHelper.getAsJSON(31, 60); + delinquencyRangeResponse = DelinquencyRangesHelper.createDelinquencyRange(requestSpec, responseSpec, jsonRange); + rangeIds.add(delinquencyRangeResponse.getResourceId()); + + range = DelinquencyRangesHelper.getDelinquencyRange(requestSpec, responseSpec, delinquencyRangeResponse.getResourceId()); + final String classificationExpected01 = range.getClassification(); + log.info("Expected Delinquency Range classification after Disbursement {}", classificationExpected01); + + // Third Range + jsonRange = DelinquencyRangesHelper.getAsJSON(61, 90); + 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); + 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 + LocalDate transactionDate = todaysDate.minusDays(85); + String operationDate = Utils.dateFormatter.format(transactionDate); + + // Create Loan Account + final Integer loanId = createLoanAccount(loanTransactionHelper, clientId.toString(), + getLoanProductsProductResponse.getId().toString(), operationDate); + + GetLoansLoanIdResponse getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); + assertNotNull(getLoansLoanIdResponse); + log.info("Loan Delinquency Range after Disbursement in null? {}", (getLoansLoanIdResponse.getDelinquencyRange() == null)); + assertNotNull(getLoansLoanIdResponse.getDelinquencyRange()); + log.info("Loan Delinquency Range after Disbursement {}", getLoansLoanIdResponse.getDelinquencyRange()); + // First Loan Delinquency Classification after Disbursement command + assertEquals(getLoansLoanIdResponse.getDelinquencyRange().getClassification(), classificationExpected01); + + loanTransactionHelper.printRepaymentSchedule(getLoansLoanIdResponse); + + // Apply a repayment to get a first full paid installment + transactionDate = todaysDate.minusDays(1); + operationDate = Utils.dateFormatter.format(transactionDate); + PostLoansLoanIdTransactionsResponse loansLoanIdTransactions = loanTransactionHelper.makeLoanRepayment(operationDate, 1050.0f, + loanId); + assertNotNull(loansLoanIdTransactions); + log.info("Loan repayment transaction id {}", loansLoanIdTransactions.getResourceId()); + getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); + log.info("Loan Delinquency Range after first Repayment {}", getLoansLoanIdResponse.getDelinquencyRange()); + assertNotNull(getLoansLoanIdResponse.getDelinquencyRange()); + // First Loan Delinquency Classification remains after Repayment because the installment is not fully paid + assertEquals(getLoansLoanIdResponse.getDelinquencyRange().getClassification(), classificationExpected02); + + ArrayList getDelinquencyTagsHistory = loanTransactionHelper.getLoanDelinquencyTags(requestSpec, + responseSpec, loanId); + assertNotNull(getDelinquencyTagsHistory); + log.info("Delinquency Tag History items {}", getDelinquencyTagsHistory.size()); + log.info("Delinquency Tag Item with Lifted On {}", getDelinquencyTagsHistory.get(0).getLiftedOnDate()); + assertEquals(getDelinquencyTagsHistory.get(0).getAddedOnDate(), Utils.getLocalDateOfTenant()); + assertEquals(getDelinquencyTagsHistory.get(0).getLiftedOnDate(), Utils.getLocalDateOfTenant()); + assertEquals(getDelinquencyTagsHistory.get(0).getDelinquencyRange().getClassification(), classificationExpected01); + log.info("Loan Id {} with Loan status {}", getLoansLoanIdResponse.getId(), getLoansLoanIdResponse.getStatus().getCode()); + + // Apply a repayment to get a second full paid installment + loansLoanIdTransactions = loanTransactionHelper.makeLoanRepayment(operationDate, 1020.0f, loanId); + assertNotNull(loansLoanIdTransactions); + log.info("Loan repayment transaction id {}", loansLoanIdTransactions.getResourceId()); + getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); + log.info("Loan Delinquency Range after second Repayment {}", getLoansLoanIdResponse.getDelinquencyRange()); + assertNotNull(getLoansLoanIdResponse); + // The Loan Delinquency Classification after Repayment command must be null + assertNull(getLoansLoanIdResponse.getDelinquencyRange()); + + getDelinquencyTagsHistory = loanTransactionHelper.getLoanDelinquencyTags(requestSpec, responseSpec, loanId); + assertNotNull(getDelinquencyTagsHistory); + log.info("Delinquency Tag History items {}", getDelinquencyTagsHistory.size()); + log.info("Delinquency Tag Item with Lifted On {}", getDelinquencyTagsHistory.get(1).getLiftedOnDate()); + assertEquals(getDelinquencyTagsHistory.get(1).getAddedOnDate(), Utils.getLocalDateOfTenant()); + assertEquals(getDelinquencyTagsHistory.get(1).getLiftedOnDate(), Utils.getLocalDateOfTenant()); + assertEquals(getDelinquencyTagsHistory.get(1).getDelinquencyRange().getClassification(), classificationExpected02); + log.info("Loan Id {} with final Loan status {}", getLoansLoanIdResponse.getId(), getLoansLoanIdResponse.getStatus().getCode()); + } + + @Test + public void testLoanClassificationRealtimeWithReversedRepayment() { + // Given + final LoanTransactionHelper loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec); + + ArrayList rangeIds = new ArrayList<>(); + // First Range + String jsonRange = DelinquencyRangesHelper.getAsJSON(4, 30); + PostDelinquencyRangeResponse delinquencyRangeResponse = DelinquencyRangesHelper.createDelinquencyRange(requestSpec, responseSpec, + jsonRange); + rangeIds.add(delinquencyRangeResponse.getResourceId()); + GetDelinquencyRangesResponse range = DelinquencyRangesHelper.getDelinquencyRange(requestSpec, responseSpec, + delinquencyRangeResponse.getResourceId()); + final String classificationExpected = range.getClassification(); + log.info("Expected Delinquency Range classification after first repayment {}", classificationExpected); + + String jsonBucket = DelinquencyBucketsHelper.getAsJSON(rangeIds); + PostDelinquencyBucketResponse delinquencyBucketResponse = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, + responseSpec, jsonBucket); + assertNotNull(delinquencyBucketResponse); + 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(); + log.info("Local date of Tenant: {}", todaysDate); + + // Older date to have more than one overdue installment + final LocalDate transactionDate = todaysDate.minusDays(50); + String operationDate = Utils.dateFormatter.format(transactionDate); + + // Create Loan Account + final Integer loanId = createLoanAccount(loanTransactionHelper, clientId.toString(), + getLoanProductsProductResponse.getId().toString(), operationDate); + + GetLoansLoanIdResponse getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); + 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 + assertEquals(getLoansLoanIdResponse.getDelinquencyRange().getClassification(), classificationExpected); + + loanTransactionHelper.printRepaymentSchedule(getLoansLoanIdResponse); + + // Apply a repayment to get a full paid installment + operationDate = Utils.dateFormatter.format(todaysDate); + PostLoansLoanIdTransactionsResponse loansLoanIdTransactions = loanTransactionHelper.makeLoanRepayment(operationDate, 1050.0f, + loanId); + assertNotNull(loansLoanIdTransactions); + log.info("Loan repayment transaction id {}", loansLoanIdTransactions.getResourceId()); + getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); + log.info("Loan Delinquency Range after Repayment {}", getLoansLoanIdResponse.getDelinquencyRange()); + // Loan Delinquency Classification removed after Repayment because the installment is fully paid + assertNull(getLoansLoanIdResponse.getDelinquencyRange()); + + ArrayList getDelinquencyTagsHistory = loanTransactionHelper.getLoanDelinquencyTags(requestSpec, + responseSpec, loanId); + assertNotNull(getDelinquencyTagsHistory); + log.info("Delinquency Tag History items {}", getDelinquencyTagsHistory.size()); + log.info("Delinquency Tag Item with Lifted On {}", getDelinquencyTagsHistory.get(0).getLiftedOnDate()); + assertEquals(getDelinquencyTagsHistory.get(0).getAddedOnDate(), Utils.getLocalDateOfTenant()); + assertEquals(getDelinquencyTagsHistory.get(0).getLiftedOnDate(), Utils.getLocalDateOfTenant()); + assertEquals(getDelinquencyTagsHistory.get(0).getDelinquencyRange().getClassification(), classificationExpected); + log.info("Loan Id {} with Loan status {}", getLoansLoanIdResponse.getId(), getLoansLoanIdResponse.getStatus().getCode()); + + // Reverse the Previous Loan Repayment + PostLoansLoanIdTransactionsResponse loansLoanIdReverseTransactions = loanTransactionHelper.reverseLoanTransaction(loanId, + loansLoanIdTransactions.getResourceId(), operationDate, responseSpec); + assertNotNull(loansLoanIdReverseTransactions); + log.info("Loan repayment reverse transaction id {}", loansLoanIdReverseTransactions.getResourceId()); + getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); + log.info("Loan Delinquency Range after Reverse Repayment {}", getLoansLoanIdResponse.getDelinquencyRange()); + // Loan Delinquency Classification goes back after Repayment because the installment is not paid + assertEquals(getLoansLoanIdResponse.getDelinquencyRange().getClassification(), classificationExpected); + + getDelinquencyTagsHistory = loanTransactionHelper.getLoanDelinquencyTags(requestSpec, responseSpec, loanId); + assertNotNull(getDelinquencyTagsHistory); + log.info("Delinquency Tag History items {}", getDelinquencyTagsHistory.size()); + log.info("Delinquency Tag Item with Lifted On {}", getDelinquencyTagsHistory.get(1).getLiftedOnDate()); + assertEquals(getDelinquencyTagsHistory.get(1).getAddedOnDate(), Utils.getLocalDateOfTenant()); + // Second record is open with liftedOn in null + assertNull(getDelinquencyTagsHistory.get(1).getLiftedOnDate()); + assertEquals(getDelinquencyTagsHistory.get(1).getDelinquencyRange().getClassification(), classificationExpected); + 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(57); + businessDate = businessDate.minusDays(37); log.info("Current date {}", businessDate); BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, businessDate); @@ -277,7 +659,7 @@ public class DelinquencyBucketsIntegrationTest { final LocalDate todaysDate = Utils.getLocalDateOfTenant(); // Older date to have more than one overdue installment - final LocalDate transactionDate = todaysDate.minusDays(65); + final LocalDate transactionDate = todaysDate.minusDays(57); String operationDate = Utils.dateFormatter.format(transactionDate); // Create Loan Account @@ -290,19 +672,13 @@ public class DelinquencyBucketsIntegrationTest { // 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)); - 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()); - } - } + loanTransactionHelper.printRepaymentSchedule(getLoansLoanIdResponse); // Move the Business date to get older the loan and to have an overdue loan - businessDate = businessDate.plusDays(40); + businessDate = businessDate.plusMonths(1); log.info("Current date {}", businessDate); BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, businessDate); // Run Second time the Job @@ -310,7 +686,11 @@ public class DelinquencyBucketsIntegrationTest { // 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 @@ -318,7 +698,6 @@ public class DelinquencyBucketsIntegrationTest { assertNotNull(getLoanProductsProductResponse); assertNull(firstTestCase); assertEquals(getLoanProductsProductResponse.getDelinquencyBucket().getName(), delinquencyBucket.getName()); - assertNotNull(secondTestCase); assertEquals(secondTestCase.getClassification(), classificationExpected); GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); @@ -402,7 +781,7 @@ public class DelinquencyBucketsIntegrationTest { } // Move the Business date to get older the loan and to have an overdue loan - businessDate = businessDate.plusDays(40); + businessDate = businessDate.plusDays(50); log.info("Current date {}", businessDate); BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, businessDate); // Run Second time the Job @@ -410,7 +789,9 @@ public class DelinquencyBucketsIntegrationTest { // 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 @@ -418,384 +799,11 @@ public class DelinquencyBucketsIntegrationTest { 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 - final LoanTransactionHelper loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec); - - ArrayList 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()); - final String classificationExpected = range.getClassification(); - log.info("Expected Delinquency Range classification after Disbursement {}", classificationExpected); - - String jsonBucket = DelinquencyBucketsHelper.getAsJSON(rangeIds); - PostDelinquencyBucketResponse delinquencyBucketResponse = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, - responseSpec, jsonBucket); - assertNotNull(delinquencyBucketResponse); - 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(45); - String operationDate = Utils.dateFormatter.format(transactionDate); - - // Create Loan Account - final Integer loanId = createLoanAccount(loanTransactionHelper, clientId.toString(), - getLoanProductsProductResponse.getId().toString(), operationDate); - - GetLoansLoanIdResponse getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); - log.info("Loan Delinquency Range after Disbursement {}", getLoansLoanIdResponse.getDelinquencyRange().getClassification()); - assertNotNull(getLoansLoanIdResponse); - // First Loan Delinquency Classification after Disbursement command - assertEquals(getLoansLoanIdResponse.getDelinquencyRange().getClassification(), classificationExpected); - - loanTransactionHelper.printRepaymentSchedule(getLoansLoanIdResponse); - - // Apply a partial repayment - operationDate = Utils.dateFormatter.format(todaysDate); - loanTransactionHelper.makeLoanRepayment(operationDate, 100.0f, loanId); - - getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); - log.info("Loan Delinquency Range after Repayment {}", getLoansLoanIdResponse.getDelinquencyRange()); - assertNotNull(getLoansLoanIdResponse.getDelinquencyRange()); - // First Loan Delinquency Classification remains after Repayment because the installment is not fully paid - assertEquals(getLoansLoanIdResponse.getDelinquencyRange().getClassification(), classificationExpected); - - // Apply a repayment to get a full paid installment - loanTransactionHelper.makeLoanRepayment(operationDate, 1000.0f, 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 - assertNull(getLoansLoanIdResponse.getDelinquencyRange()); - // Get the Delinquency Tags - ArrayList getDelinquencyTagsHistory = loanTransactionHelper.getLoanDelinquencyTags(requestSpec, - responseSpec, loanId); - assertNotNull(getDelinquencyTagsHistory); - log.info("Delinquency Tag History items {}", getDelinquencyTagsHistory.size()); - assertEquals(1, getDelinquencyTagsHistory.size()); - assertNotNull(getDelinquencyTagsHistory.get(0).getLiftedOnDate()); - assertEquals(getDelinquencyTagsHistory.get(0).getAddedOnDate(), Utils.getLocalDateOfTenant()); - assertEquals(getDelinquencyTagsHistory.get(0).getLiftedOnDate(), Utils.getLocalDateOfTenant()); - assertEquals(getDelinquencyTagsHistory.get(0).getDelinquencyRange().getClassification(), classificationExpected); - log.info("Delinquency Tag Item with Lifted On {}", getDelinquencyTagsHistory.get(0).getLiftedOnDate()); - } - - @Test - public void testLoanClassificationRealtimeWithCharges() { - // Given - final LoanTransactionHelper loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec); - - ArrayList 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()); - final String classificationExpected = range.getClassification(); - log.info("Expected Delinquency Range classification after Disbursement {}", classificationExpected); - - String jsonBucket = DelinquencyBucketsHelper.getAsJSON(rangeIds); - PostDelinquencyBucketResponse delinquencyBucketResponse = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, - responseSpec, jsonBucket); - assertNotNull(delinquencyBucketResponse); - 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 - LocalDate transactionDate = todaysDate.minusDays(45); - String operationDate = Utils.dateFormatter.format(transactionDate); - - // Create Loan Account - final Integer loanId = createLoanAccount(loanTransactionHelper, clientId.toString(), - getLoanProductsProductResponse.getId().toString(), operationDate); - - GetLoansLoanIdResponse getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); - log.info("Loan Delinquency Range after Disbursement {}", getLoansLoanIdResponse.getDelinquencyRange().getClassification()); - assertNotNull(getLoansLoanIdResponse); - // First Loan Delinquency Classification after Disbursement command - assertEquals(getLoansLoanIdResponse.getDelinquencyRange().getClassification(), classificationExpected); - printRepaymentSchedule(getLoansLoanIdResponse); - - // Apply a repayment to get a full paid installment - operationDate = Utils.dateFormatter.format(todaysDate); - loanTransactionHelper.makeLoanRepayment(operationDate, 1033.33f, 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 - assertNull(getLoansLoanIdResponse.getDelinquencyRange()); - printRepaymentSchedule(getLoansLoanIdResponse); - - transactionDate = todaysDate.minusDays(18); - operationDate = Utils.dateFormatter.format(transactionDate); - - // Create and apply Charge for Specific Due Date - final Integer chargeId = ChargesHelper.createCharges(requestSpec, responseSpec, - ChargesHelper.getLoanSpecifiedDueDateJSON(1, "30", false)); - assertNotNull(chargeId); - final Integer loanChargeId = loanTransactionHelper.addChargesForLoan(loanId, getChargeApplyJSON(chargeId, operationDate), - responseSpec); - assertNotNull(loanChargeId); - - getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); - printRepaymentSchedule(getLoansLoanIdResponse); - - log.info("Loan Delinquency Range after add Loan Charge {}", getLoansLoanIdResponse.getDelinquencyRange()); - assertNotNull(getLoansLoanIdResponse.getDelinquencyRange()); - // Evaluate a Delinquency Tag set after add charge to the Loan - assertEquals(getLoansLoanIdResponse.getDelinquencyRange().getClassification(), classificationExpected); - } - - @Test - public void testLoanClassificationRealtimeOlderLoan() { - // Given - final LoanTransactionHelper loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec); - - ArrayList rangeIds = new ArrayList<>(); - // First Range - String jsonRange = DelinquencyRangesHelper.getAsJSON(4, 30); - PostDelinquencyRangeResponse delinquencyRangeResponse = DelinquencyRangesHelper.createDelinquencyRange(requestSpec, responseSpec, - jsonRange); - rangeIds.add(delinquencyRangeResponse.getResourceId()); - GetDelinquencyRangesResponse range = DelinquencyRangesHelper.getDelinquencyRange(requestSpec, responseSpec, - delinquencyRangeResponse.getResourceId()); - final String classificationExpected02 = range.getClassification(); - log.info("Expected Delinquency Range classification after first repayment {}", classificationExpected02); - - // Second Range - jsonRange = DelinquencyRangesHelper.getAsJSON(31, 60); - delinquencyRangeResponse = DelinquencyRangesHelper.createDelinquencyRange(requestSpec, responseSpec, jsonRange); - rangeIds.add(delinquencyRangeResponse.getResourceId()); - - range = DelinquencyRangesHelper.getDelinquencyRange(requestSpec, responseSpec, delinquencyRangeResponse.getResourceId()); - final String classificationExpected01 = range.getClassification(); - log.info("Expected Delinquency Range classification after Disbursement {}", classificationExpected01); - - // Third Range - jsonRange = DelinquencyRangesHelper.getAsJSON(61, 90); - 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); - 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(85); - String operationDate = Utils.dateFormatter.format(transactionDate); - - // Create Loan Account - final Integer loanId = createLoanAccount(loanTransactionHelper, clientId.toString(), - getLoanProductsProductResponse.getId().toString(), operationDate); - - GetLoansLoanIdResponse getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); - assertNotNull(getLoansLoanIdResponse); - log.info("Loan Delinquency Range after Disbursement in null? {}", (getLoansLoanIdResponse.getDelinquencyRange() == null)); - assertNotNull(getLoansLoanIdResponse.getDelinquencyRange()); - log.info("Loan Delinquency Range after Disbursement {}", getLoansLoanIdResponse.getDelinquencyRange()); - // First Loan Delinquency Classification after Disbursement command - assertEquals(getLoansLoanIdResponse.getDelinquencyRange().getClassification(), classificationExpected01); - - loanTransactionHelper.printRepaymentSchedule(getLoansLoanIdResponse); - - // Apply a repayment to get a first full paid installment - operationDate = Utils.dateFormatter.format(todaysDate); - PostLoansLoanIdTransactionsResponse loansLoanIdTransactions = loanTransactionHelper.makeLoanRepayment(operationDate, 1050.0f, - loanId); - assertNotNull(loansLoanIdTransactions); - log.info("Loan repayment transaction id {}", loansLoanIdTransactions.getResourceId()); - getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); - log.info("Loan Delinquency Range after first Repayment {}", getLoansLoanIdResponse.getDelinquencyRange()); - assertNotNull(getLoansLoanIdResponse.getDelinquencyRange()); - // First Loan Delinquency Classification remains after Repayment because the installment is not fully paid - assertEquals(getLoansLoanIdResponse.getDelinquencyRange().getClassification(), classificationExpected02); - - ArrayList getDelinquencyTagsHistory = loanTransactionHelper.getLoanDelinquencyTags(requestSpec, - responseSpec, loanId); - assertNotNull(getDelinquencyTagsHistory); - log.info("Delinquency Tag History items {}", getDelinquencyTagsHistory.size()); - log.info("Delinquency Tag Item with Lifted On {}", getDelinquencyTagsHistory.get(0).getLiftedOnDate()); - assertEquals(getDelinquencyTagsHistory.get(0).getAddedOnDate(), Utils.getLocalDateOfTenant()); - assertEquals(getDelinquencyTagsHistory.get(0).getLiftedOnDate(), Utils.getLocalDateOfTenant()); - assertEquals(getDelinquencyTagsHistory.get(0).getDelinquencyRange().getClassification(), classificationExpected01); - log.info("Loan Id {} with Loan status {}", getLoansLoanIdResponse.getId(), getLoansLoanIdResponse.getStatus().getCode()); - - // Apply a repayment to get a second full paid installment - loansLoanIdTransactions = loanTransactionHelper.makeLoanRepayment(operationDate, 1020.0f, loanId); - assertNotNull(loansLoanIdTransactions); - log.info("Loan repayment transaction id {}", loansLoanIdTransactions.getResourceId()); - getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); - log.info("Loan Delinquency Range after second Repayment {}", getLoansLoanIdResponse.getDelinquencyRange()); - assertNotNull(getLoansLoanIdResponse); - // The Loan Delinquency Classification after Repayment command must be null - assertNull(getLoansLoanIdResponse.getDelinquencyRange()); - - getDelinquencyTagsHistory = loanTransactionHelper.getLoanDelinquencyTags(requestSpec, responseSpec, loanId); - assertNotNull(getDelinquencyTagsHistory); - log.info("Delinquency Tag History items {}", getDelinquencyTagsHistory.size()); - log.info("Delinquency Tag Item with Lifted On {}", getDelinquencyTagsHistory.get(1).getLiftedOnDate()); - assertEquals(getDelinquencyTagsHistory.get(1).getAddedOnDate(), Utils.getLocalDateOfTenant()); - assertEquals(getDelinquencyTagsHistory.get(1).getLiftedOnDate(), Utils.getLocalDateOfTenant()); - assertEquals(getDelinquencyTagsHistory.get(1).getDelinquencyRange().getClassification(), classificationExpected02); - log.info("Loan Id {} with final Loan status {}", getLoansLoanIdResponse.getId(), getLoansLoanIdResponse.getStatus().getCode()); - } - - @Test - public void testLoanClassificationRealtimeWithReversedRepayment() { - // Given - final LoanTransactionHelper loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec); - - ArrayList rangeIds = new ArrayList<>(); - // First Range - String jsonRange = DelinquencyRangesHelper.getAsJSON(4, 30); - PostDelinquencyRangeResponse delinquencyRangeResponse = DelinquencyRangesHelper.createDelinquencyRange(requestSpec, responseSpec, - jsonRange); - rangeIds.add(delinquencyRangeResponse.getResourceId()); - GetDelinquencyRangesResponse range = DelinquencyRangesHelper.getDelinquencyRange(requestSpec, responseSpec, - delinquencyRangeResponse.getResourceId()); - final String classificationExpected = range.getClassification(); - log.info("Expected Delinquency Range classification after first repayment {}", classificationExpected); - - String jsonBucket = DelinquencyBucketsHelper.getAsJSON(rangeIds); - PostDelinquencyBucketResponse delinquencyBucketResponse = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, - responseSpec, jsonBucket); - assertNotNull(delinquencyBucketResponse); - 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(45); - String operationDate = Utils.dateFormatter.format(transactionDate); - - // Create Loan Account - final Integer loanId = createLoanAccount(loanTransactionHelper, clientId.toString(), - getLoanProductsProductResponse.getId().toString(), operationDate); - - GetLoansLoanIdResponse getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); - assertNotNull(getLoansLoanIdResponse); - log.info("Loan Delinquency Range after Disbursement in null? {}", (getLoansLoanIdResponse.getDelinquencyRange() == null)); - assertNotNull(getLoansLoanIdResponse.getDelinquencyRange()); - log.info("Loan Delinquency Range after Disbursement {}", getLoansLoanIdResponse.getDelinquencyRange()); - // First Loan Delinquency Classification after Disbursement command - assertEquals(getLoansLoanIdResponse.getDelinquencyRange().getClassification(), classificationExpected); - - loanTransactionHelper.printRepaymentSchedule(getLoansLoanIdResponse); - - // Apply a repayment to get a full paid installment - operationDate = Utils.dateFormatter.format(todaysDate); - PostLoansLoanIdTransactionsResponse loansLoanIdTransactions = loanTransactionHelper.makeLoanRepayment(operationDate, 1050.0f, - loanId); - assertNotNull(loansLoanIdTransactions); - log.info("Loan repayment transaction id {}", loansLoanIdTransactions.getResourceId()); - getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); - log.info("Loan Delinquency Range after Repayment {}", getLoansLoanIdResponse.getDelinquencyRange()); - // Loan Delinquency Classification removed after Repayment because the installment is fully paid - assertNull(getLoansLoanIdResponse.getDelinquencyRange()); - - ArrayList getDelinquencyTagsHistory = loanTransactionHelper.getLoanDelinquencyTags(requestSpec, - responseSpec, loanId); - assertNotNull(getDelinquencyTagsHistory); - log.info("Delinquency Tag History items {}", getDelinquencyTagsHistory.size()); - log.info("Delinquency Tag Item with Lifted On {}", getDelinquencyTagsHistory.get(0).getLiftedOnDate()); - assertEquals(getDelinquencyTagsHistory.get(0).getAddedOnDate(), Utils.getLocalDateOfTenant()); - assertEquals(getDelinquencyTagsHistory.get(0).getLiftedOnDate(), Utils.getLocalDateOfTenant()); - assertEquals(getDelinquencyTagsHistory.get(0).getDelinquencyRange().getClassification(), classificationExpected); - log.info("Loan Id {} with Loan status {}", getLoansLoanIdResponse.getId(), getLoansLoanIdResponse.getStatus().getCode()); - - // Reverse the Previous Loan Repayment - PostLoansLoanIdTransactionsResponse loansLoanIdReverseTransactions = loanTransactionHelper.reverseLoanTransaction(loanId, - loansLoanIdTransactions.getResourceId(), operationDate, responseSpec); - assertNotNull(loansLoanIdReverseTransactions); - log.info("Loan repayment reverse transaction id {}", loansLoanIdReverseTransactions.getResourceId()); - getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); - log.info("Loan Delinquency Range after Reverse Repayment {}", getLoansLoanIdResponse.getDelinquencyRange()); - // Loan Delinquency Classification goes back after Repayment because the installment is not paid - assertEquals(getLoansLoanIdResponse.getDelinquencyRange().getClassification(), classificationExpected); - - getDelinquencyTagsHistory = loanTransactionHelper.getLoanDelinquencyTags(requestSpec, responseSpec, loanId); - assertNotNull(getDelinquencyTagsHistory); - log.info("Delinquency Tag History items {}", getDelinquencyTagsHistory.size()); - log.info("Delinquency Tag Item with Lifted On {}", getDelinquencyTagsHistory.get(1).getLiftedOnDate()); - assertEquals(getDelinquencyTagsHistory.get(1).getAddedOnDate(), Utils.getLocalDateOfTenant()); - // Second record is open with liftedOn in null - assertNull(getDelinquencyTagsHistory.get(1).getLiftedOnDate()); - assertEquals(getDelinquencyTagsHistory.get(1).getDelinquencyRange().getClassification(), classificationExpected); - log.info("Loan Id {} with final Loan status {}", getLoansLoanIdResponse.getId(), getLoansLoanIdResponse.getStatus().getCode()); - } - private GetLoanProductsProductIdResponse createLoanProduct(final LoanTransactionHelper loanTransactionHelper, final Integer delinquencyBucketId) { final HashMap 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 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 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 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 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 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 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 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 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 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");