From 4f9f71da9b1600e06502125a2e5655b3e06c5311 Mon Sep 17 00:00:00 2001 From: Jose Alberto Hernandez Date: Thu, 10 Nov 2022 19:34:50 -0600 Subject: [PATCH] Fix Loan Delinquency Classification after Repayment --- .../DelinquencyReadPlatformServiceImpl.java | 79 +------- .../DelinquencyWritePlatformService.java | 3 - .../DelinquencyWritePlatformServiceImpl.java | 88 +-------- .../service/LoanDelinquencyDomainService.java | 34 ++++ .../LoanDelinquencyDomainServiceImpl.java | 136 +++++++++++++ .../loanaccount/data/CollectionData.java | 2 + .../LoanRepaymentScheduleInstallment.java | 7 + ...tePlatformServiceRangeChangeEventTest.java | 18 +- .../LoanDelinquencyDomainServiceTest.java | 185 ++++++++++++++++++ ...linquencyAndChargebackIntegrationTest.java | 96 ++++++--- .../DelinquencyBucketsIntegrationTest.java | 2 +- .../LoanTransactionChargebackTest.java | 119 ++++++----- .../common/loans/LoanTransactionHelper.java | 12 +- .../products/DelinquencyBucketsHelper.java | 8 +- 14 files changed, 531 insertions(+), 258 deletions(-) create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/LoanDelinquencyDomainService.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/LoanDelinquencyDomainServiceImpl.java create mode 100644 fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/LoanDelinquencyDomainServiceTest.java 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 e1585d9d5..8655942d5 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,15 +18,10 @@ */ 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; @@ -41,11 +36,8 @@ 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; @@ -61,6 +53,7 @@ public class DelinquencyReadPlatformServiceImpl implements DelinquencyReadPlatfo private final DelinquencyBucketMapper mapperBucket; private final LoanDelinquencyTagMapper mapperLoanDelinquencyTagHistory; private final LoanRepository loanRepository; + private final LoanDelinquencyDomainService loanDelinquencyDomainService; @Override public Collection retrieveAllDelinquencyRanges() { @@ -109,89 +102,21 @@ public class DelinquencyReadPlatformServiceImpl implements DelinquencyReadPlatfo @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 = loanDelinquencyDomainService.getOverdueCollectionData(loan); 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 d5596ce72..d5f6a1b06 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,7 +18,6 @@ */ 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; @@ -48,6 +47,4 @@ public interface DelinquencyWritePlatformService { 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 a0c678fed..a9a1688df 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 @@ -35,7 +35,6 @@ import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityEx import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.event.business.domain.loan.LoanDelinquencyRangeChangeBusinessEvent; import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; -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; @@ -51,13 +50,10 @@ 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.CollectionData; 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.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; @@ -76,6 +72,7 @@ public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlat private final LoanRepositoryWrapper loanRepository; private final LoanProductRepository loanProductRepository; private final BusinessEventNotifierService businessEventNotifierService; + private final LoanDelinquencyDomainService loanDelinquencyDomainService; @Override public CommandProcessingResult createDelinquencyRange(JsonCommand command) { @@ -147,66 +144,13 @@ public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlat @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; + final CollectionData collectionData = loanDelinquencyDomainService.getOverdueCollectionData(loan); + log.debug("Delinquency {}", collectionData); + return new LoanScheduleDelinquencyData(loan.getId(), collectionData.getDelinquentDate(), collectionData.getDelinquentDays(), loan); } @Override @@ -216,13 +160,8 @@ public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlat final Loan loan = this.loanRepository.findOneWithNotFoundDetection(loanId); final DelinquencyBucket delinquencyBucket = loan.getLoanProduct().getDelinquencyBucket(); if (delinquencyBucket != null) { - final LocalDate businessDate = DateUtils.getBusinessLocalDate(); - - 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); + final CollectionData collectionData = loanDelinquencyDomainService.getOverdueCollectionData(loan); + changes = lookUpDelinquencyRange(loan, delinquencyBucket, collectionData.getDelinquentDays()); } return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(loan.getId()).with(changes).build(); } @@ -231,17 +170,10 @@ public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlat public void applyDelinquencyTagToLoan(LoanScheduleDelinquencyData loanDelinquencyData) { final Loan loan = loanDelinquencyData.getLoan(); if (loan.hasDelinquencyBucket()) { - 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); + final CollectionData collectionData = loanDelinquencyDomainService.getOverdueCollectionData(loan); + log.debug("Delinquency {}", collectionData); + lookUpDelinquencyRange(loan, delinquencyBucket, collectionData.getDelinquentDays()); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/LoanDelinquencyDomainService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/LoanDelinquencyDomainService.java new file mode 100644 index 000000000..5cd19cb5f --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/LoanDelinquencyDomainService.java @@ -0,0 +1,34 @@ +/** + * 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.delinquency.service; + +import org.apache.fineract.portfolio.loanaccount.data.CollectionData; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; + +public interface LoanDelinquencyDomainService { + + /** + * This method is to calculate the Overdue date and other properties, If the loan is overdue or If there is some + * Charge back transaction + * + * @param loan + */ + CollectionData getOverdueCollectionData(Loan loan); + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/LoanDelinquencyDomainServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/LoanDelinquencyDomainServiceImpl.java new file mode 100644 index 000000000..df694fac9 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/LoanDelinquencyDomainServiceImpl.java @@ -0,0 +1,136 @@ +/** + * 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.delinquency.service; + +import java.math.BigDecimal; +import java.time.LocalDate; +import javax.transaction.Transactional; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; +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.LoanTransaction; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +public class LoanDelinquencyDomainServiceImpl implements LoanDelinquencyDomainService { + + @Override + @Transactional + public CollectionData getOverdueCollectionData(final Loan loan) { + final LocalDate businessDate = DateUtils.getBusinessLocalDate(); + + final MonetaryCurrency loanCurrency = loan.getCurrency(); + LocalDate overdueSinceDate = null; + CollectionData collectionData = CollectionData.template(); + BigDecimal amountAvailable = BigDecimal.ZERO; + BigDecimal outstandingAmount = BigDecimal.ZERO; + boolean oldestOverdueInstallment = false; + boolean overdueSinceDateWasSet = false; + boolean firstNotYetDueInstallment = false; + + log.debug("Loan id {} with {} installments", loan.getId(), loan.getRepaymentScheduleInstallments().size()); + // Get the oldest overdue installment if exists one + for (LoanRepaymentScheduleInstallment installment : loan.getRepaymentScheduleInstallments()) { + if (!installment.isObligationsMet()) { + + if (installment.getDueDate().isBefore(businessDate)) { + log.debug("Loan Id: {} with installment {} due date {}", loan.getId(), installment.getInstallmentNumber(), + installment.getDueDate()); + outstandingAmount = outstandingAmount.add(installment.getTotalOutstanding(loanCurrency).getAmount()); + if (!oldestOverdueInstallment) { + log.debug("Oldest installment {} {}", installment.getInstallmentNumber(), installment.getDueDate()); + oldestOverdueInstallment = true; + overdueSinceDate = installment.getDueDate(); + overdueSinceDateWasSet = true; + + amountAvailable = installment.getTotalPaid(loanCurrency).getAmount(); + + for (LoanTransactionToRepaymentScheduleMapping mappingInstallment : installment + .getLoanTransactionToRepaymentScheduleMappings()) { + final LoanTransaction loanTransaction = mappingInstallment.getLoanTransaction(); + if (loanTransaction.isChargeback()) { + amountAvailable = amountAvailable.subtract(loanTransaction.getAmount()); + if (amountAvailable.compareTo(BigDecimal.ZERO) < 0) { + overdueSinceDate = loanTransaction.getTransactionDate(); + break; + } + } + } + } + } + } else if (!firstNotYetDueInstallment) { + log.debug("Loan Id: {} with installment {} due date {}", loan.getId(), installment.getInstallmentNumber(), + installment.getDueDate()); + firstNotYetDueInstallment = true; + amountAvailable = installment.getTotalPaid(loanCurrency).getAmount(); + log.debug("Amount available {}", amountAvailable); + for (LoanTransactionToRepaymentScheduleMapping mappingInstallment : installment + .getLoanTransactionToRepaymentScheduleMappings()) { + final LoanTransaction loanTransaction = mappingInstallment.getLoanTransaction(); + if (loanTransaction.isChargeback() && loanTransaction.getTransactionDate().isBefore(businessDate)) { + log.debug("Loan CB Transaction: {} {} {}", loanTransaction.getId(), loanTransaction.getTransactionDate(), + loanTransaction.getAmount()); + amountAvailable = amountAvailable.subtract(loanTransaction.getAmount()); + if (amountAvailable.compareTo(BigDecimal.ZERO) < 0 && !overdueSinceDateWasSet) { + overdueSinceDate = loanTransaction.getTransactionDate(); + overdueSinceDateWasSet = true; + } + } + } + + if (amountAvailable.compareTo(BigDecimal.ZERO) < 0) { + outstandingAmount = outstandingAmount.add(amountAvailable.abs()); + } + } + } + + Integer graceDays = 0; + if (loan.getLoanProduct().getLoanProductRelatedDetail().getGraceOnArrearsAgeing() != null) { + graceDays = loan.getLoanProduct().getLoanProductRelatedDetail().getGraceOnArrearsAgeing(); + if (graceDays == null) { + graceDays = 0; + } + } + log.debug("Loan id {} with overdue since date {} and outstanding amount {}", loan.getId(), overdueSinceDate, outstandingAmount); + + Long overdueDays = 0L; + if (overdueSinceDate != null) { + overdueSinceDate = overdueSinceDate.plusDays(graceDays.longValue()); + collectionData.setDelinquentDate(overdueSinceDate); + overdueDays = DateUtils.getDifferenceInDays(overdueSinceDate, businessDate); + if (overdueDays < 0) { + overdueDays = 0L; + } + collectionData.setPastDueDays(overdueDays); + } + collectionData.setDelinquentAmount(outstandingAmount); + + if (overdueDays > 0) { + collectionData.setDelinquentDays(overdueDays - graceDays); + } + log.debug("Result: {}", collectionData.toString()); + return collectionData; + } + +} 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 45c2545cc..abfe84705 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 @@ -23,8 +23,10 @@ import java.time.LocalDate; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; +import lombok.ToString; @AllArgsConstructor +@ToString @Getter @Setter public final class CollectionData { 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 444c47aeb..13bd94f98 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 @@ -140,6 +140,9 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY, mappedBy = "installment") private Set installmentCharges = new HashSet<>(); + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY, mappedBy = "installment") + private Set loanTransactionToRepaymentScheduleMappings = new HashSet<>(); + public LoanRepaymentScheduleInstallment() { this.installmentNumber = null; this.fromDate = null; @@ -894,4 +897,8 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa return (this.installmentNumber == 1); } + public Set getLoanTransactionToRepaymentScheduleMappings() { + return this.loanTransactionToRepaymentScheduleMappings; + } + } diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/DelinquencyWritePlatformServiceRangeChangeEventTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/DelinquencyWritePlatformServiceRangeChangeEventTest.java index 5343b1ed4..f52ed68cf 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/DelinquencyWritePlatformServiceRangeChangeEventTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/DelinquencyWritePlatformServiceRangeChangeEventTest.java @@ -21,11 +21,11 @@ package org.apache.fineract.portfolio.deliquency; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyIterable; -import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.math.BigDecimal; import java.time.LocalDate; import java.time.ZoneId; import java.util.Arrays; @@ -46,13 +46,14 @@ import org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange; import org.apache.fineract.portfolio.delinquency.domain.DelinquencyRangeRepository; import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyTagHistoryRepository; import org.apache.fineract.portfolio.delinquency.service.DelinquencyWritePlatformServiceImpl; +import org.apache.fineract.portfolio.delinquency.service.LoanDelinquencyDomainService; import org.apache.fineract.portfolio.delinquency.validator.DelinquencyBucketParseAndValidator; import org.apache.fineract.portfolio.delinquency.validator.DelinquencyRangeParseAndValidator; +import org.apache.fineract.portfolio.loanaccount.data.CollectionData; import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct; -import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -84,6 +85,8 @@ public class DelinquencyWritePlatformServiceRangeChangeEventTest { private LoanProductRepository loanProductRepository; @Mock private BusinessEventNotifierService businessEventNotifierService; + @Mock + private LoanDelinquencyDomainService loanDelinquencyDomainService; @InjectMocks private DelinquencyWritePlatformServiceImpl underTest; @@ -101,7 +104,6 @@ public class DelinquencyWritePlatformServiceRangeChangeEventTest { // given Loan loanForProcessing = Mockito.mock(Loan.class); LoanProduct loanProduct = Mockito.mock(LoanProduct.class); - LoanProductRelatedDetail loanProductRelatedDetail = Mockito.mock(LoanProductRelatedDetail.class); DelinquencyRange range1 = DelinquencyRange.instance("Range1", 1, 2); range1.setId(1L); DelinquencyRange range2 = DelinquencyRange.instance("Range30", 3, 30); @@ -113,14 +115,13 @@ public class DelinquencyWritePlatformServiceRangeChangeEventTest { LocalDate overDueSinceDate = DateUtils.getBusinessLocalDate().minusDays(2); LoanScheduleDelinquencyData loanScheduleDelinquencyData = new LoanScheduleDelinquencyData(1L, overDueSinceDate, 1L, loanForProcessing); + CollectionData collectionData = new CollectionData(BigDecimal.ZERO, 2L, null, 2L, overDueSinceDate, BigDecimal.ZERO, null, null); when(loanForProcessing.getLoanProduct()).thenReturn(loanProduct); when(loanProduct.getDelinquencyBucket()).thenReturn(delinquencyBucket); - when(loanProduct.getLoanProductRelatedDetail()).thenReturn(loanProductRelatedDetail); - when(loanProductRelatedDetail.getGraceOnArrearsAgeing()).thenReturn(1); when(loanForProcessing.hasDelinquencyBucket()).thenReturn(true); when(loanDelinquencyTagRepository.findByLoanAndLiftedOnDate(any(), any())).thenReturn(Optional.empty()); - when(repositoryRange.getReferenceById(anyLong())).thenReturn(range1); + when(loanDelinquencyDomainService.getOverdueCollectionData(loanForProcessing)).thenReturn(collectionData); // when underTest.applyDelinquencyTagToLoan(loanScheduleDelinquencyData); @@ -137,7 +138,6 @@ public class DelinquencyWritePlatformServiceRangeChangeEventTest { // given Loan loanForProcessing = Mockito.mock(Loan.class); LoanProduct loanProduct = Mockito.mock(LoanProduct.class); - LoanProductRelatedDetail loanProductRelatedDetail = Mockito.mock(LoanProductRelatedDetail.class); DelinquencyRange range1 = DelinquencyRange.instance("Range1", 1, 2); range1.setId(1L); @@ -154,10 +154,9 @@ public class DelinquencyWritePlatformServiceRangeChangeEventTest { when(loanForProcessing.getLoanProduct()).thenReturn(loanProduct); when(loanProduct.getDelinquencyBucket()).thenReturn(delinquencyBucket); - when(loanProduct.getLoanProductRelatedDetail()).thenReturn(loanProductRelatedDetail); - when(loanProductRelatedDetail.getGraceOnArrearsAgeing()).thenReturn(1); when(loanForProcessing.hasDelinquencyBucket()).thenReturn(true); when(loanDelinquencyTagRepository.findByLoanAndLiftedOnDate(any(), any())).thenReturn(Optional.empty()); + when(loanDelinquencyDomainService.getOverdueCollectionData(loanForProcessing)).thenReturn(CollectionData.template()); // when underTest.applyDelinquencyTagToLoan(loanScheduleDelinquencyData); @@ -165,7 +164,6 @@ public class DelinquencyWritePlatformServiceRangeChangeEventTest { // then verify(loanDelinquencyTagRepository, times(0)).saveAllAndFlush(anyIterable()); verify(businessEventNotifierService, times(0)).notifyPostBusinessEvent(any()); - } @Test diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/LoanDelinquencyDomainServiceTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/LoanDelinquencyDomainServiceTest.java new file mode 100644 index 000000000..e462aeb67 --- /dev/null +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/LoanDelinquencyDomainServiceTest.java @@ -0,0 +1,185 @@ +/** + * 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.deliquency; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; +import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; +import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; +import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; +import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; +import org.apache.fineract.organisation.monetary.domain.Money; +import org.apache.fineract.organisation.monetary.domain.MoneyHelper; +import org.apache.fineract.portfolio.delinquency.service.LoanDelinquencyDomainServiceImpl; +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.LoanTransaction; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping; +import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct; +import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail; +import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class LoanDelinquencyDomainServiceTest { + + @Mock + private LoanProductRelatedDetail loanProductRelatedDetail; + @Mock + private ConfigurationDomainService staticConfigurationDomainService; + @Mock + private MoneyHelper moneyHelper; + @Mock + private Loan loan; + @Mock + private LoanProduct loanProduct; + @InjectMocks + private LoanDelinquencyDomainServiceImpl underTest; + + private LocalDate businessDate; + private MonetaryCurrency currency; + private MockedStatic moneyHelperStatic; + + private final BigDecimal principal = BigDecimal.valueOf(1000); + private final BigDecimal zeroAmount = BigDecimal.ZERO; + + @BeforeEach + public void setUp() { + ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null)); + ThreadLocalContextUtil + .setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.now(ZoneId.systemDefault())))); + + businessDate = DateUtils.getBusinessLocalDate(); + currency = new MonetaryCurrency("USD", 2, null); + + moneyHelperStatic = Mockito.mockStatic(MoneyHelper.class); + moneyHelperStatic.when(() -> MoneyHelper.getRoundingMode()).thenReturn(RoundingMode.UP); + } + + @AfterEach + public void deregister() { + moneyHelperStatic.close(); + } + + @Test + public void givenLoanAccountWithoutOverdueThenCalculateDelinquentData() { + // given + final LocalDate fromDate = businessDate.minusMonths(1); + final LocalDate dueDate = businessDate; + List repaymentScheduleInstallments = Arrays.asList(new LoanRepaymentScheduleInstallment(loan, 1, + fromDate, dueDate, principal, zeroAmount, zeroAmount, zeroAmount, false, new HashSet<>(), zeroAmount)); + + // when + when(loanProductRelatedDetail.getGraceOnArrearsAgeing()).thenReturn(0); + when(loanProduct.getLoanProductRelatedDetail()).thenReturn(loanProductRelatedDetail); + when(loan.getLoanProduct()).thenReturn(loanProduct); + when(loan.getRepaymentScheduleInstallments()).thenReturn(repaymentScheduleInstallments); + when(loan.getCurrency()).thenReturn(currency); + + CollectionData collectionData = underTest.getOverdueCollectionData(loan); + + // then + assertEquals(0L, collectionData.getDelinquentDays()); + assertEquals(null, collectionData.getDelinquentDate()); + assertEquals(collectionData.getDelinquentDays(), collectionData.getPastDueDays()); + + } + + @Test + public void givenLoanAccountWithOverdueThenCalculateDelinquentData() { + // given + final Long daysDiff = 2L; + final LocalDate fromDate = businessDate.minusMonths(1).minusDays(daysDiff); + final LocalDate dueDate = businessDate.minusDays(daysDiff); + List repaymentScheduleInstallments = Arrays.asList(new LoanRepaymentScheduleInstallment(loan, 1, + fromDate, dueDate, principal, zeroAmount, zeroAmount, zeroAmount, false, new HashSet<>(), zeroAmount)); + + // when + when(loanProductRelatedDetail.getGraceOnArrearsAgeing()).thenReturn(0); + when(loanProduct.getLoanProductRelatedDetail()).thenReturn(loanProductRelatedDetail); + when(loan.getLoanProduct()).thenReturn(loanProduct); + when(loan.getRepaymentScheduleInstallments()).thenReturn(repaymentScheduleInstallments); + when(loan.getCurrency()).thenReturn(currency); + + CollectionData collectionData = underTest.getOverdueCollectionData(loan); + + // then + assertEquals(daysDiff, collectionData.getDelinquentDays()); + assertEquals(dueDate, collectionData.getDelinquentDate()); + assertEquals(collectionData.getDelinquentDays(), collectionData.getPastDueDays()); + + } + + @Test + public void givenLoanAccountWithoutOverdueWithChargebackThenCalculateDelinquentData() { + // given + PaymentDetail paymentDetail = Mockito.mock(PaymentDetail.class); + Long daysDiff = 2L; + final LocalDate fromDate = businessDate.minusMonths(1).plusDays(daysDiff); + final LocalDate dueDate = businessDate.plusDays(daysDiff); + final LocalDate transactionDate = businessDate.minusDays(daysDiff); + + final Money zeroMoney = Money.zero(currency); + LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(loan, 1, fromDate, dueDate, principal, + zeroAmount, zeroAmount, zeroAmount, false, new HashSet<>(), zeroAmount); + LoanTransaction loanTransaction = LoanTransaction.chargeback(null, Money.of(currency, principal), paymentDetail, transactionDate, + null); + installment.getLoanTransactionToRepaymentScheduleMappings().add(LoanTransactionToRepaymentScheduleMapping + .createFrom(loanTransaction, installment, zeroMoney, zeroMoney, zeroMoney, zeroMoney)); + + List repaymentScheduleInstallments = Arrays.asList(installment); + + // when + when(loanProductRelatedDetail.getGraceOnArrearsAgeing()).thenReturn(0); + when(loanProduct.getLoanProductRelatedDetail()).thenReturn(loanProductRelatedDetail); + when(loan.getLoanProduct()).thenReturn(loanProduct); + when(loan.getRepaymentScheduleInstallments()).thenReturn(repaymentScheduleInstallments); + when(loan.getCurrency()).thenReturn(currency); + + CollectionData collectionData = underTest.getOverdueCollectionData(loan); + + // then + assertEquals(0L, collectionData.getDelinquentDays()); + assertEquals(null, collectionData.getDelinquentDate()); + assertEquals(collectionData.getDelinquentDays(), collectionData.getPastDueDays()); + + } + +} 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 index 9ed8bfbe9..191728fb4 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyAndChargebackIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyAndChargebackIntegrationTest.java @@ -18,7 +18,6 @@ */ 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; @@ -72,7 +71,7 @@ public class DelinquencyAndChargebackIntegrationTest { GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); final LocalDate todaysDate = Utils.getLocalDateOfTenant(); - LocalDate businessDate = todaysDate.minusDays(57); + LocalDate businessDate = todaysDate.minusMonths(3); log.info("Current Business date {}", businessDate); BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, businessDate); @@ -88,62 +87,97 @@ public class DelinquencyAndChargebackIntegrationTest { 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); + String operationDate = Utils.dateFormatter.format(businessDate); + log.info("Operation date {}", businessDate); // 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); + // Move the Business date 1 month to apply the first repayment + businessDate = businessDate.plusMonths(1); BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, businessDate); log.info("Current Business date {}", businessDate); String amountVal = "100.00"; Float transactionAmount = Float.valueOf(amountVal); + operationDate = Utils.dateFormatter.format(businessDate); PostLoansLoanIdTransactionsResponse loanIdTransactionsResponse = loanTransactionHelper.makeLoanRepayment(operationDate, transactionAmount, loanId); assertNotNull(loanIdTransactionsResponse); - final Long transactionId = loanIdTransactionsResponse.getResourceId(); - loanTransactionHelper.reviewLoanTransactionRelations(loanId, transactionId.intValue(), 0); - getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); - validateLoanAccount(getLoansLoanIdResponse, "0.00", "1100.00", 0, doubleZERO); + Long transactionId = loanIdTransactionsResponse.getResourceId(); + loanTransactionHelper.reviewLoanTransactionRelations(loanId, transactionId, 0); - final Long chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId.intValue(), amountVal, - 0, responseSpec); - loanTransactionHelper.reviewLoanTransactionRelations(loanId, transactionId.intValue(), 1); + // Move the Business date 1 month more to apply the second repayment + businessDate = businessDate.plusMonths(1); + BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, businessDate); + log.info("Current Business date {}", businessDate); + operationDate = Utils.dateFormatter.format(businessDate); + loanIdTransactionsResponse = loanTransactionHelper.makeLoanRepayment(operationDate, transactionAmount, loanId); + assertNotNull(loanIdTransactionsResponse); + transactionId = loanIdTransactionsResponse.getResourceId(); + loanTransactionHelper.reviewLoanTransactionRelations(loanId, transactionId, 0); + + // Get loan details expecting to have not a delinquency classification and 1,000 as Outstanding + GetLoansLoanIdResponse getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); + validateLoanAccount(getLoansLoanIdResponse, "0.00", "1000.00", 0, doubleZERO); + + // Move the Business date n days to apply the chargeback for the previous repayment + businessDate = businessDate.plusDays(21); + BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, businessDate); + log.info("Current Business date {}", businessDate); + + // Apply the Chargeback transaction + final Long chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId, amountVal, 0, + responseSpec); + loanTransactionHelper.reviewLoanTransactionRelations(loanId, transactionId, 1); + + // Validate the account expecting to have an adjustment for 100.00 and Outstanding 1,100 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")); + validateLoanAccount(getLoansLoanIdResponse, amountVal, "1100.00", 0, Double.valueOf("0.00")); - // Move the Business date to get older the loan and to have an overdue loan - businessDate = businessDate.plusDays(9); + // Move the Business date n days to run the COB + businessDate = businessDate.plusDays(14); BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, businessDate); log.info("Current Business date {}", businessDate); - // Run Second time the Job + + // Run the Loan COB Job + final String jobName = "Loan COB"; schedulerJobHelper.executeAndAwaitJob(jobName); // Get loan details expecting to have a delinquency classification getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); + validateLoanAccount(getLoansLoanIdResponse, amountVal, "1100.00", 14, Double.valueOf("200.00")); - // The value is lower due the 3 grace days - validateLoanAccount(getLoansLoanIdResponse, "100.00", principalAmount, 9, Double.valueOf("100.00")); + // Move the Business date few days to apply the repayment for Chargeback + businessDate = todaysDate.plusDays(4); + BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, businessDate); + log.info("Current Business date {}", businessDate); + + operationDate = Utils.dateFormatter.format(businessDate); + loanIdTransactionsResponse = loanTransactionHelper.makeLoanRepayment(operationDate, transactionAmount, loanId); + assertNotNull(loanIdTransactionsResponse); + transactionId = loanIdTransactionsResponse.getResourceId(); + loanTransactionHelper.reviewLoanTransactionRelations(loanId, transactionId, 0); + + // Get loan details expecting to have a delinquency classification + getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); + validateLoanAccount(getLoansLoanIdResponse, amountVal, "1000.00", 4, Double.valueOf("100.00")); + + // Apply a partial repayment + operationDate = Utils.dateFormatter.format(businessDate); + transactionAmount = Float.valueOf("50.00"); + loanIdTransactionsResponse = loanTransactionHelper.makeLoanRepayment(operationDate, transactionAmount, loanId); + assertNotNull(loanIdTransactionsResponse); + transactionId = loanIdTransactionsResponse.getResourceId(); + loanTransactionHelper.reviewLoanTransactionRelations(loanId, transactionId, 0); + // Get loan details expecting to have a delinquency classification + getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); + validateLoanAccount(getLoansLoanIdResponse, amountVal, "950.00", 4, Double.valueOf("50.00")); GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); } 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 b78af8743..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 @@ -597,7 +597,7 @@ public class DelinquencyBucketsIntegrationTest { // Reverse the Previous Loan Repayment PostLoansLoanIdTransactionsResponse loansLoanIdReverseTransactions = loanTransactionHelper.reverseLoanTransaction(loanId, - loansLoanIdTransactions.getResourceId().intValue(), operationDate, responseSpec); + loansLoanIdTransactions.getResourceId(), operationDate, responseSpec); assertNotNull(loansLoanIdReverseTransactions); log.info("Loan repayment reverse transaction id {}", loansLoanIdReverseTransactions.getResourceId()); getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); 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 dff586068..7a15c9453 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 @@ -40,7 +40,10 @@ import org.apache.fineract.client.models.GetLoansLoanIdResponse; import org.apache.fineract.client.models.GetLoansLoanIdTransactions; import org.apache.fineract.client.models.GetLoansLoanIdTransactionsTransactionIdResponse; 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.Utils; import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder; import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder; @@ -95,13 +98,13 @@ public class LoanTransactionChargebackTest { assertNotNull(getLoansLoanIdResponse); loanTransactionHelper.validateLoanStatus(getLoansLoanIdResponse, "loanStatusType.closed.obligations.met"); - reviewLoanTransactionRelations(loanId, transactionId.intValue(), 0, Double.valueOf("0.00")); + reviewLoanTransactionRelations(loanId, transactionId, 0, Double.valueOf("0.00")); - final Long chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId.intValue(), "1000.00", - 0, responseSpec); + final Long chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId, "1000.00", 0, + responseSpec); - reviewLoanTransactionRelations(loanId, transactionId.intValue(), 1, Double.valueOf("0.00")); - reviewLoanTransactionRelations(loanId, chargebackTransactionId.intValue(), 0, Double.valueOf("1000.00")); + reviewLoanTransactionRelations(loanId, transactionId, 1, Double.valueOf("0.00")); + reviewLoanTransactionRelations(loanId, chargebackTransactionId, 0, Double.valueOf("1000.00")); getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); assertNotNull(getLoansLoanIdResponse); @@ -111,11 +114,10 @@ public class LoanTransactionChargebackTest { // Try to reverse a Loan Transaction charge back PostLoansLoanIdTransactionsResponse reverseTransactionResponse = loanTransactionHelper.reverseLoanTransaction(loanId, - chargebackTransactionId.intValue(), operationDate, responseSpecErr503); + chargebackTransactionId, operationDate, responseSpecErr503); // Try to reverse a Loan Transaction repayment with linked transactions - reverseTransactionResponse = loanTransactionHelper.reverseLoanTransaction(loanId, transactionId.intValue(), operationDate, - responseSpecErr503); + reverseTransactionResponse = loanTransactionHelper.reverseLoanTransaction(loanId, transactionId, operationDate, responseSpecErr503); } @Test @@ -138,7 +140,7 @@ public class LoanTransactionChargebackTest { assertNotNull(getLoansLoanIdResponse); loanTransactionHelper.validateLoanStatus(getLoansLoanIdResponse, "loanStatusType.closed.obligations.met"); - loanTransactionHelper.applyChargebackTransaction(loanId, transactionId.intValue(), "0.00", 0, responseSpecErr400); + loanTransactionHelper.applyChargebackTransaction(loanId, transactionId, "0.00", 0, responseSpecErr400); } @@ -163,12 +165,12 @@ public class LoanTransactionChargebackTest { loanId); assertNotNull(loanIdTransactionsResponse); final Long transactionId = loanIdTransactionsResponse.getResourceId(); - reviewLoanTransactionRelations(loanId, transactionId.intValue(), 0, Double.valueOf("666.67")); + reviewLoanTransactionRelations(loanId, transactionId, 0, Double.valueOf("666.67")); - final Long chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId.intValue(), - amount.toString(), 0, responseSpec); - reviewLoanTransactionRelations(loanId, transactionId.intValue(), 1, Double.valueOf("666.67")); - reviewLoanTransactionRelations(loanId, chargebackTransactionId.intValue(), 0, Double.valueOf("1000.00")); + final Long 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")); getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); assertNotNull(getLoansLoanIdResponse); @@ -186,7 +188,7 @@ public class LoanTransactionChargebackTest { } loanTransactionHelper.evaluateLoanSummaryAdjustments(getLoansLoanIdResponse, Double.valueOf(baseAmount)); - DelinquencyBucketsHelper.evaluateLoanCollectionData(getLoansLoanIdResponse, 0, Double.valueOf("333.33")); + DelinquencyBucketsHelper.evaluateLoanCollectionData(getLoansLoanIdResponse, 0, Double.valueOf("0.00")); } @Test @@ -205,11 +207,16 @@ public class LoanTransactionChargebackTest { log.info("Try to apply the Charge back over transaction Id {} with type {}", loanTransaction.getId(), loanTransaction.getType().getCode()); - loanTransactionHelper.applyChargebackTransaction(loanId, loanTransaction.getId().intValue(), amountVal, 0, responseSpecErr503); + loanTransactionHelper.applyChargebackTransaction(loanId, loanTransaction.getId(), amountVal, 0, responseSpecErr503); } @Test public void applyLoanTransactionChargebackAfterMature() { + GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); + final LocalDate todaysDate = Utils.getLocalDateOfTenant(); + BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, todaysDate); + log.info("Current Business date {}", todaysDate); + // Client and Loan account creation final Integer loanId = createAccounts(45, 1); @@ -236,13 +243,12 @@ public class LoanTransactionChargebackTest { assertNotNull(getLoansLoanIdResponse); loanTransactionHelper.validateLoanStatus(getLoansLoanIdResponse, "loanStatusType.closed.obligations.met"); - reviewLoanTransactionRelations(loanId, transactionId.intValue(), 0, Double.valueOf("0.00")); + reviewLoanTransactionRelations(loanId, transactionId, 0, Double.valueOf("0.00")); - Long chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId.intValue(), "500.00", 0, - responseSpec); + Long chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId, "500.00", 0, responseSpec); - reviewLoanTransactionRelations(loanId, transactionId.intValue(), 1, Double.valueOf("0.00")); - reviewLoanTransactionRelations(loanId, chargebackTransactionId.intValue(), 0, Double.valueOf("500.00")); + reviewLoanTransactionRelations(loanId, transactionId, 1, Double.valueOf("0.00")); + reviewLoanTransactionRelations(loanId, chargebackTransactionId, 0, Double.valueOf("500.00")); getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); assertNotNull(getLoansLoanIdResponse); @@ -264,11 +270,10 @@ public class LoanTransactionChargebackTest { } } - chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId.intValue(), "300.00", 0, - responseSpec); + chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId, "300.00", 0, responseSpec); - reviewLoanTransactionRelations(loanId, transactionId.intValue(), 2, Double.valueOf("0.00")); - reviewLoanTransactionRelations(loanId, chargebackTransactionId.intValue(), 0, Double.valueOf("800.00")); + reviewLoanTransactionRelations(loanId, transactionId, 2, Double.valueOf("0.00")); + reviewLoanTransactionRelations(loanId, chargebackTransactionId, 0, Double.valueOf("800.00")); getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); assertNotNull(getLoansLoanIdResponse); @@ -293,6 +298,17 @@ public class LoanTransactionChargebackTest { assertEquals(Double.valueOf("800.00"), period.getPrincipalDue()); } } + + // Move the Business date few days to get Collection data + LocalDate businessDate = todaysDate.plusDays(4); + BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, businessDate); + log.info("Current Business date {}", businessDate); + + // Get loan details expecting to have a delinquency classification + getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); + DelinquencyBucketsHelper.evaluateLoanCollectionData(getLoansLoanIdResponse, 4, Double.valueOf("800.00")); + + GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); } @Test @@ -315,13 +331,13 @@ public class LoanTransactionChargebackTest { assertNotNull(getLoansLoanIdResponse); loanTransactionHelper.validateLoanStatus(getLoansLoanIdResponse, "loanStatusType.overpaid"); - reviewLoanTransactionRelations(loanId, transactionId.intValue(), 0, Double.valueOf("0.00")); + reviewLoanTransactionRelations(loanId, transactionId, 0, Double.valueOf("0.00")); - final Long chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId.intValue(), "200.00", 0, + final Long chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId, "200.00", 0, responseSpec); - reviewLoanTransactionRelations(loanId, transactionId.intValue(), 1, Double.valueOf("0.00")); - reviewLoanTransactionRelations(loanId, chargebackTransactionId.intValue(), 0, Double.valueOf("100.00")); + reviewLoanTransactionRelations(loanId, transactionId, 1, Double.valueOf("0.00")); + reviewLoanTransactionRelations(loanId, chargebackTransactionId, 0, Double.valueOf("100.00")); getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); assertNotNull(getLoansLoanIdResponse); @@ -354,13 +370,13 @@ public class LoanTransactionChargebackTest { assertNotNull(getLoansLoanIdResponse); loanTransactionHelper.validateLoanStatus(getLoansLoanIdResponse, "loanStatusType.overpaid"); - reviewLoanTransactionRelations(loanId, transactionId.intValue(), 0, Double.valueOf("0.00")); + reviewLoanTransactionRelations(loanId, transactionId, 0, Double.valueOf("0.00")); - final Long chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId.intValue(), "100.00", 0, + final Long chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId, "100.00", 0, responseSpec); - reviewLoanTransactionRelations(loanId, transactionId.intValue(), 1, Double.valueOf("0.00")); - reviewLoanTransactionRelations(loanId, chargebackTransactionId.intValue(), 0, Double.valueOf("0.00")); + reviewLoanTransactionRelations(loanId, transactionId, 1, Double.valueOf("0.00")); + reviewLoanTransactionRelations(loanId, chargebackTransactionId, 0, Double.valueOf("0.00")); getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); assertNotNull(getLoansLoanIdResponse); @@ -389,15 +405,15 @@ public class LoanTransactionChargebackTest { assertNotNull(getLoansLoanIdResponse); loanTransactionHelper.validateLoanStatus(getLoansLoanIdResponse, "loanStatusType.overpaid"); - reviewLoanTransactionRelations(loanId, transactionId.intValue(), 0, Double.valueOf("0.00")); + reviewLoanTransactionRelations(loanId, transactionId, 0, Double.valueOf("0.00")); GetDelinquencyRangesResponse delinquencyRange = getLoansLoanIdResponse.getDelinquencyRange(); assertNull(delinquencyRange); log.info("Loan Delinquency Range is null {}", (delinquencyRange == null)); - final Long chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId.intValue(), "50.00", 0, + final Long chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId, "50.00", 0, responseSpec); - reviewLoanTransactionRelations(loanId, transactionId.intValue(), 1, Double.valueOf("0.00")); - reviewLoanTransactionRelations(loanId, chargebackTransactionId.intValue(), 0, Double.valueOf("0.00")); + reviewLoanTransactionRelations(loanId, transactionId, 1, Double.valueOf("0.00")); + reviewLoanTransactionRelations(loanId, chargebackTransactionId, 0, Double.valueOf("0.00")); getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); assertNotNull(getLoansLoanIdResponse); @@ -412,6 +428,11 @@ public class LoanTransactionChargebackTest { @Test public void applyMultipleLoanTransactionChargeback() { + GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); + final LocalDate todaysDate = Utils.getLocalDateOfTenant(); + BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, todaysDate); + log.info("Current Business date {}", todaysDate); + // Client and Loan account creation final Integer loanId = createAccounts(15, 1); @@ -431,9 +452,9 @@ public class LoanTransactionChargebackTest { loanTransactionHelper.validateLoanStatus(getLoansLoanIdResponse, "loanStatusType.closed.obligations.met"); // First round, empty array - reviewLoanTransactionRelations(loanId, transactionId.intValue(), 0, Double.valueOf("0.00")); + reviewLoanTransactionRelations(loanId, transactionId, 0, Double.valueOf("0.00")); - loanTransactionHelper.applyChargebackTransaction(loanId, transactionId.intValue(), "200.00", 0, responseSpec); + loanTransactionHelper.applyChargebackTransaction(loanId, transactionId, "200.00", 0, responseSpec); Double expectedAmount = Double.valueOf("200.00"); getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); @@ -441,24 +462,24 @@ public class LoanTransactionChargebackTest { loanTransactionHelper.evaluateLoanSummaryAdjustments(getLoansLoanIdResponse, expectedAmount); loanTransactionHelper.printDelinquencyData(getLoansLoanIdResponse); - DelinquencyBucketsHelper.evaluateLoanCollectionData(getLoansLoanIdResponse, 0, expectedAmount); + DelinquencyBucketsHelper.evaluateLoanCollectionData(getLoansLoanIdResponse, 0, Double.valueOf("0.00")); // Second round, array size equal to 1 - reviewLoanTransactionRelations(loanId, transactionId.intValue(), 1, Double.valueOf("0.00")); + reviewLoanTransactionRelations(loanId, transactionId, 1, Double.valueOf("0.00")); - loanTransactionHelper.applyChargebackTransaction(loanId, transactionId.intValue(), "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, expectedAmount); loanTransactionHelper.evaluateLoanSummaryAdjustments(getLoansLoanIdResponse, expectedAmount); - DelinquencyBucketsHelper.evaluateLoanCollectionData(getLoansLoanIdResponse, 0, expectedAmount); + DelinquencyBucketsHelper.evaluateLoanCollectionData(getLoansLoanIdResponse, 0, Double.valueOf("0.00")); // Third round, array size equal to 2 - reviewLoanTransactionRelations(loanId, transactionId.intValue(), 2, Double.valueOf("0.00")); + reviewLoanTransactionRelations(loanId, transactionId, 2, Double.valueOf("0.00")); - loanTransactionHelper.applyChargebackTransaction(loanId, transactionId.intValue(), "500.00", 0, responseSpec); + loanTransactionHelper.applyChargebackTransaction(loanId, transactionId, "500.00", 0, responseSpec); expectedAmount = Double.valueOf("1000.00"); getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); @@ -467,7 +488,9 @@ public class LoanTransactionChargebackTest { loanTransactionHelper.evaluateLoanSummaryAdjustments(getLoansLoanIdResponse, expectedAmount); loanTransactionHelper.printRepaymentSchedule(getLoansLoanIdResponse); - DelinquencyBucketsHelper.evaluateLoanCollectionData(getLoansLoanIdResponse, 0, expectedAmount); + DelinquencyBucketsHelper.evaluateLoanCollectionData(getLoansLoanIdResponse, 0, Double.valueOf("0.00")); + + GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); } private Integer createAccounts(final Integer daysToSubtract, final Integer numberOfRepayments) { @@ -515,12 +538,12 @@ public class LoanTransactionChargebackTest { return loanId; } - private void reviewLoanTransactionRelations(final Integer loanId, final Integer transactionId, final Integer expectedSize, + private void reviewLoanTransactionRelations(final Integer loanId, final Long transactionId, final Integer expectedSize, final Double outstandingBalance) { log.info("Loan Transaction Id: {} {}", loanId, transactionId); GetLoansLoanIdTransactionsTransactionIdResponse getLoansTransactionResponse = loanTransactionHelper.getLoanTransaction(loanId, - transactionId); + transactionId.intValue()); log.info("Loan with {} Chargeback Transactions and balance {}", getLoansTransactionResponse.getTransactionRelations().size(), getLoansTransactionResponse.getOutstandingLoanBalance()); assertNotNull(getLoansTransactionResponse); 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 79e380f98..33f53a2d6 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 @@ -469,9 +469,9 @@ public class LoanTransactionHelper extends IntegrationTest { return ok(fineract().loanTransactions.executeLoanTransaction(loanId, request, "repayment")); } - public PostLoansLoanIdTransactionsResponse reverseLoanTransaction(final Integer loanId, final Integer transactionId, String date, + public PostLoansLoanIdTransactionsResponse reverseLoanTransaction(final Integer loanId, final Long transactionId, String date, ResponseSpecification responseSpec) { - return postLoanTransaction(createLoanTransactionURL(UNDO, loanId, transactionId), getUndoJsonBody(date), responseSpec); + return postLoanTransaction(createLoanTransactionURL(UNDO, loanId, transactionId.intValue()), getUndoJsonBody(date), responseSpec); } public PostLoansLoanIdTransactionsTransactionIdResponse reverseLoanTransaction(final Long loanId, final Long transactionId, @@ -1229,7 +1229,7 @@ public class LoanTransactionHelper extends IntegrationTest { } } - public Long applyChargebackTransaction(final Integer loanId, final Integer transactionId, final String amount, + public Long applyChargebackTransaction(final Integer loanId, final Long transactionId, final String amount, final Integer paymentTypeIdx, ResponseSpecification responseSpec) { List paymentTypeList = PaymentTypeHelper.getSystemPaymentType(this.requestSpec, this.responseSpec); assertTrue(!paymentTypeList.isEmpty()); @@ -1237,15 +1237,15 @@ public class LoanTransactionHelper extends IntegrationTest { final String payload = createChargebackPayload(amount, paymentTypeList.get(paymentTypeIdx).getId()); log.info("Loan Chargeback: {}", payload); PostLoansLoanIdTransactionsTransactionIdResponse postLoansTransactionCommandResponse = applyLoanTransactionCommand(loanId, - transactionId, "chargeback", payload, responseSpec); + transactionId.intValue(), "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); + public void reviewLoanTransactionRelations(final Integer loanId, final Long transactionId, final Integer expectedSize) { + GetLoansLoanIdTransactionsTransactionIdResponse getLoansTransactionResponse = getLoanTransaction(loanId, transactionId.intValue()); assertNotNull(getLoansTransactionResponse); assertNotNull(getLoansTransactionResponse.getTransactionRelations()); assertEquals(expectedSize, getLoansTransactionResponse.getTransactionRelations().size()); 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 7b5810b1d..0d8dea91d 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 @@ -125,8 +125,8 @@ public class DelinquencyBucketsHelper { if (getCollectionData != null) { log.info("Loan Delinquency Data in Days {} and Amount {}", getCollectionData.getPastDueDays(), getCollectionData.getDelinquentAmount()); - assertEquals(pastDueDays, getCollectionData.getPastDueDays()); - assertEquals(amountExpected, getCollectionData.getDelinquentAmount()); + assertEquals(pastDueDays, getCollectionData.getPastDueDays(), "Past due days"); + assertEquals(amountExpected, getCollectionData.getDelinquentAmount(), "Amount expected"); } else { log.info("Loan Delinquency Data is null"); } @@ -135,8 +135,8 @@ public class DelinquencyBucketsHelper { if (delinquencyRange != null) { log.info("Loan Delinquency Classification is {} : ({} - {}) {}", delinquencyRange.getClassification(), delinquencyRange.getMinimumAgeDays(), delinquencyRange.getMaximumAgeDays(), pastDueDays); - assertTrue(delinquencyRange.getMinimumAgeDays() <= pastDueDays); - assertTrue(delinquencyRange.getMaximumAgeDays() >= pastDueDays); + assertTrue(delinquencyRange.getMinimumAgeDays() <= pastDueDays, "Min Age Days"); + assertTrue(delinquencyRange.getMaximumAgeDays() >= pastDueDays, "Max Age Days"); } else { log.info("Loan Delinquency Classification is null"); }