FINERACT-1971: Fix for not properly resolving delinquency range data for loan in case there's an installment delinquency
This commit is contained in:
committed by
Arnold Gálovics
parent
ef465562b5
commit
cbe8f029e2
+268
@@ -0,0 +1,268 @@
|
|||||||
|
/**
|
||||||
|
* 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.time.LocalDate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
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.portfolio.delinquency.domain.DelinquencyBucket;
|
||||||
|
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange;
|
||||||
|
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyRangeRepository;
|
||||||
|
import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyTagHistory;
|
||||||
|
import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyTagHistoryRepository;
|
||||||
|
import org.apache.fineract.portfolio.delinquency.domain.LoanInstallmentDelinquencyTag;
|
||||||
|
import org.apache.fineract.portfolio.delinquency.domain.LoanInstallmentDelinquencyTagRepository;
|
||||||
|
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.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class DelinquencyWritePlatformServiceHelper {
|
||||||
|
|
||||||
|
private final BusinessEventNotifierService businessEventNotifierService;
|
||||||
|
private final LoanDelinquencyTagHistoryRepository loanDelinquencyTagRepository;
|
||||||
|
private final DelinquencyRangeRepository repositoryRange;
|
||||||
|
private final LoanInstallmentDelinquencyTagRepository loanInstallmentDelinquencyTagRepository;
|
||||||
|
|
||||||
|
public Map<String, Object> applyDelinquencyForLoan(final Loan loan, final DelinquencyBucket delinquencyBucket, long overdueDays) {
|
||||||
|
Map<String, Object> changes = new HashMap<>();
|
||||||
|
|
||||||
|
if (overdueDays <= 0) { // No Delinquency
|
||||||
|
log.debug("Loan {} without delinquency range with {} days", loan.getId(), overdueDays);
|
||||||
|
changes = setLoanDelinquencyTag(loan, null);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Sort the ranges based on the minAgeDays
|
||||||
|
final List<DelinquencyRange> ranges = sortDelinquencyRangesByMinAge(delinquencyBucket.getRanges());
|
||||||
|
|
||||||
|
for (final DelinquencyRange delinquencyRange : ranges) {
|
||||||
|
if (delinquencyRange.getMaximumAgeDays() == null) { // Last Range in the Bucket
|
||||||
|
if (delinquencyRange.getMinimumAgeDays() <= overdueDays) {
|
||||||
|
log.debug("Loan {} with delinquency range {} with {} days", loan.getId(), delinquencyRange.getClassification(),
|
||||||
|
overdueDays);
|
||||||
|
changes = setLoanDelinquencyTag(loan, delinquencyRange.getId());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (delinquencyRange.getMinimumAgeDays() <= overdueDays && delinquencyRange.getMaximumAgeDays() >= overdueDays) {
|
||||||
|
log.debug("Loan {} with delinquency range {} with {} days", loan.getId(), delinquencyRange.getClassification(),
|
||||||
|
overdueDays);
|
||||||
|
changes = setLoanDelinquencyTag(loan, delinquencyRange.getId());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
changes.put("overdueDays", overdueDays);
|
||||||
|
return changes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> setLoanDelinquencyTag(Loan loan, Long delinquencyRangeId) {
|
||||||
|
Map<String, Object> changes = new HashMap<>();
|
||||||
|
List<LoanDelinquencyTagHistory> loanDelinquencyTagHistory = new ArrayList<>();
|
||||||
|
final LocalDate transactionDate = DateUtils.getBusinessLocalDate();
|
||||||
|
Optional<LoanDelinquencyTagHistory> optLoanDelinquencyTag = this.loanDelinquencyTagRepository.findByLoanAndLiftedOnDate(loan, null);
|
||||||
|
// The delinquencyRangeId in null means just goes out from Delinquency
|
||||||
|
LoanDelinquencyTagHistory loanDelinquencyTagPrev = null;
|
||||||
|
if (delinquencyRangeId == null) {
|
||||||
|
// The Loan will go out from Delinquency
|
||||||
|
if (optLoanDelinquencyTag.isPresent()) {
|
||||||
|
loanDelinquencyTagPrev = optLoanDelinquencyTag.get();
|
||||||
|
loanDelinquencyTagPrev.setLiftedOnDate(transactionDate);
|
||||||
|
loanDelinquencyTagHistory.add(loanDelinquencyTagPrev);
|
||||||
|
changes.put("previous", loanDelinquencyTagPrev.getDelinquencyRange());
|
||||||
|
// event when loan goes out of delinquency we do not calculate at
|
||||||
|
// installment level and remove all installment tags, so event needs to raised here.
|
||||||
|
if (loan.isEnableInstallmentLevelDelinquency()) {
|
||||||
|
businessEventNotifierService.notifyPostBusinessEvent(new LoanDelinquencyRangeChangeBusinessEvent(loan));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (optLoanDelinquencyTag.isPresent()) {
|
||||||
|
loanDelinquencyTagPrev = optLoanDelinquencyTag.get();
|
||||||
|
}
|
||||||
|
// If the Delinquency Tag has not changed
|
||||||
|
if (loanDelinquencyTagPrev != null && loanDelinquencyTagPrev.getDelinquencyRange().getId().equals(delinquencyRangeId)) {
|
||||||
|
changes.put("current", loanDelinquencyTagPrev.getDelinquencyRange());
|
||||||
|
} else {
|
||||||
|
// The previous Loan Delinquency Tag will set as Lifted
|
||||||
|
if (loanDelinquencyTagPrev != null) {
|
||||||
|
loanDelinquencyTagPrev.setLiftedOnDate(transactionDate);
|
||||||
|
loanDelinquencyTagHistory.add(loanDelinquencyTagPrev);
|
||||||
|
changes.put("previous", loanDelinquencyTagPrev.getDelinquencyRange());
|
||||||
|
}
|
||||||
|
|
||||||
|
final DelinquencyRange delinquencyRange = repositoryRange.getReferenceById(delinquencyRangeId);
|
||||||
|
LoanDelinquencyTagHistory loanDelinquencyTag = new LoanDelinquencyTagHistory(delinquencyRange, loan, transactionDate, null);
|
||||||
|
loanDelinquencyTagHistory.add(loanDelinquencyTag);
|
||||||
|
changes.put("current", loanDelinquencyTag.getDelinquencyRange());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (loanDelinquencyTagHistory.size() > 0) {
|
||||||
|
this.loanDelinquencyTagRepository.saveAllAndFlush(loanDelinquencyTagHistory);
|
||||||
|
// if installment level delinquency is enabled event will be raised at installment level calculation, no
|
||||||
|
// need to raise the event here
|
||||||
|
if (!loan.isEnableInstallmentLevelDelinquency()) {
|
||||||
|
businessEventNotifierService.notifyPostBusinessEvent(new LoanDelinquencyRangeChangeBusinessEvent(loan));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return changes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<DelinquencyRange> sortDelinquencyRangesByMinAge(List<DelinquencyRange> ranges) {
|
||||||
|
final Comparator<DelinquencyRange> orderByMinAge = new Comparator<DelinquencyRange>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(DelinquencyRange o1, DelinquencyRange o2) {
|
||||||
|
return o1.getMinimumAgeDays().compareTo(o2.getMinimumAgeDays());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Collections.sort(ranges, orderByMinAge);
|
||||||
|
return ranges;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void applyDelinquencyForLoanInstallments(final Loan loan, final DelinquencyBucket delinquencyBucket,
|
||||||
|
final Map<Long, CollectionData> installmentsCollectionData) {
|
||||||
|
boolean isDelinquencyRangeChangedForAnyOfInstallment = false;
|
||||||
|
for (LoanRepaymentScheduleInstallment installment : loan.getRepaymentScheduleInstallments()) {
|
||||||
|
if (installmentsCollectionData.containsKey(installment.getId())) {
|
||||||
|
boolean isDelinquencySetForInstallment = setInstallmentDelinquencyDetails(loan, installment, delinquencyBucket,
|
||||||
|
installmentsCollectionData.get(installment.getId()));
|
||||||
|
isDelinquencyRangeChangedForAnyOfInstallment = isDelinquencyRangeChangedForAnyOfInstallment
|
||||||
|
|| isDelinquencySetForInstallment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// remove tags for non-existing installments that got deleted due to re-schedule
|
||||||
|
removeDelinquencyTagsForNonExistingInstallments(loan.getId());
|
||||||
|
// raise event if there is any change at installment level delinquency
|
||||||
|
if (isDelinquencyRangeChangedForAnyOfInstallment) {
|
||||||
|
businessEventNotifierService.notifyPostBusinessEvent(new LoanDelinquencyRangeChangeBusinessEvent(loan));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeDelinquencyTagsForNonExistingInstallments(Long loanId) {
|
||||||
|
List<LoanInstallmentDelinquencyTag> currentLoanInstallmentDelinquencyTags = loanInstallmentDelinquencyTagRepository
|
||||||
|
.findByLoanId(loanId);
|
||||||
|
if (currentLoanInstallmentDelinquencyTags != null && currentLoanInstallmentDelinquencyTags.size() > 0) {
|
||||||
|
List<Long> loanInstallmentTagsForDelete = currentLoanInstallmentDelinquencyTags.stream()
|
||||||
|
.filter(tag -> tag.getInstallment() == null).map(tag -> tag.getId()).toList();
|
||||||
|
if (loanInstallmentTagsForDelete.size() > 0) {
|
||||||
|
loanInstallmentDelinquencyTagRepository.deleteAllLoanInstallmentsTagsByIds(loanInstallmentTagsForDelete);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean setInstallmentDelinquencyDetails(final Loan loan, final LoanRepaymentScheduleInstallment installment,
|
||||||
|
final DelinquencyBucket delinquencyBucket, final CollectionData installmentDelinquencyData) {
|
||||||
|
DelinquencyRange delinquencyRangeForInstallment = getInstallmentDelinquencyRange(delinquencyBucket,
|
||||||
|
installmentDelinquencyData.getDelinquentDays());
|
||||||
|
return setDelinquencyDetailsForInstallment(loan, installment, installmentDelinquencyData, delinquencyRangeForInstallment);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DelinquencyRange getInstallmentDelinquencyRange(final DelinquencyBucket delinquencyBucket, Long overDueDays) {
|
||||||
|
DelinquencyRange delinquencyRangeForInstallment = null;
|
||||||
|
if (overDueDays > 0) {
|
||||||
|
// Sort the ranges based on the minAgeDays
|
||||||
|
final List<DelinquencyRange> ranges = sortDelinquencyRangesByMinAge(delinquencyBucket.getRanges());
|
||||||
|
for (final DelinquencyRange delinquencyRange : ranges) {
|
||||||
|
if (delinquencyRange.getMaximumAgeDays() == null) { // Last Range in the Bucket
|
||||||
|
if (delinquencyRange.getMinimumAgeDays() <= overDueDays) {
|
||||||
|
delinquencyRangeForInstallment = delinquencyRange;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (delinquencyRange.getMinimumAgeDays() <= overDueDays && delinquencyRange.getMaximumAgeDays() >= overDueDays) {
|
||||||
|
delinquencyRangeForInstallment = delinquencyRange;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return delinquencyRangeForInstallment;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean setDelinquencyDetailsForInstallment(final Loan loan, final LoanRepaymentScheduleInstallment installment,
|
||||||
|
CollectionData installmentDelinquencyData, final DelinquencyRange delinquencyRangeForInstallment) {
|
||||||
|
List<LoanInstallmentDelinquencyTag> installmentDelinquencyTags = new ArrayList<>();
|
||||||
|
LocalDate delinquencyCalculationDate = DateUtils.getBusinessLocalDate();
|
||||||
|
boolean isDelinquencyRangeChanged = false;
|
||||||
|
|
||||||
|
LoanInstallmentDelinquencyTag previousInstallmentDelinquencyTag = loanInstallmentDelinquencyTagRepository
|
||||||
|
.findByLoanAndInstallment(loan, installment).orElse(null);
|
||||||
|
|
||||||
|
if (delinquencyRangeForInstallment == null) {
|
||||||
|
// if currentInstallmentDelinquencyTag exists and range is null, installment is out of delinquency, delete
|
||||||
|
// delinquency details
|
||||||
|
if (previousInstallmentDelinquencyTag != null) {
|
||||||
|
// event installment out of delinquency
|
||||||
|
loanInstallmentDelinquencyTagRepository.delete(previousInstallmentDelinquencyTag);
|
||||||
|
isDelinquencyRangeChanged = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LoanInstallmentDelinquencyTag installmentDelinquency = null;
|
||||||
|
if (previousInstallmentDelinquencyTag != null) {
|
||||||
|
if (!previousInstallmentDelinquencyTag.getDelinquencyRange().getId().equals(delinquencyRangeForInstallment.getId())) {
|
||||||
|
// if current delinquency range exists and there is range change, delete previous delinquency
|
||||||
|
// details and add new range details
|
||||||
|
installmentDelinquency = new LoanInstallmentDelinquencyTag(delinquencyRangeForInstallment, loan, installment,
|
||||||
|
delinquencyCalculationDate, null, previousInstallmentDelinquencyTag.getFirstOverdueDate(),
|
||||||
|
installmentDelinquencyData.getDelinquentAmount());
|
||||||
|
loanInstallmentDelinquencyTagRepository.delete(previousInstallmentDelinquencyTag);
|
||||||
|
// event installment delinquency range change
|
||||||
|
isDelinquencyRangeChanged = true;
|
||||||
|
} else {
|
||||||
|
previousInstallmentDelinquencyTag.setOutstandingAmount(installmentDelinquencyData.getDelinquentAmount());
|
||||||
|
installmentDelinquency = previousInstallmentDelinquencyTag;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// add new range, first time delinquent
|
||||||
|
installmentDelinquency = new LoanInstallmentDelinquencyTag(delinquencyRangeForInstallment, loan, installment,
|
||||||
|
delinquencyCalculationDate, null, installmentDelinquencyData.getDelinquentDate(),
|
||||||
|
installmentDelinquencyData.getDelinquentAmount());
|
||||||
|
// event installment delinquent
|
||||||
|
isDelinquencyRangeChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (installmentDelinquency != null) {
|
||||||
|
installmentDelinquencyTags.add(installmentDelinquency);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (installmentDelinquencyTags.size() > 0) {
|
||||||
|
loanInstallmentDelinquencyTagRepository.saveAllAndFlush(installmentDelinquencyTags);
|
||||||
|
}
|
||||||
|
return isDelinquencyRangeChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
+29
-236
@@ -20,8 +20,6 @@ package org.apache.fineract.portfolio.delinquency.service;
|
|||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -50,7 +48,6 @@ import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyAction;
|
|||||||
import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyActionRepository;
|
import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyActionRepository;
|
||||||
import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyTagHistory;
|
import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyTagHistory;
|
||||||
import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyTagHistoryRepository;
|
import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyTagHistoryRepository;
|
||||||
import org.apache.fineract.portfolio.delinquency.domain.LoanInstallmentDelinquencyTag;
|
|
||||||
import org.apache.fineract.portfolio.delinquency.domain.LoanInstallmentDelinquencyTagRepository;
|
import org.apache.fineract.portfolio.delinquency.domain.LoanInstallmentDelinquencyTagRepository;
|
||||||
import org.apache.fineract.portfolio.delinquency.exception.DelinquencyBucketAgesOverlapedException;
|
import org.apache.fineract.portfolio.delinquency.exception.DelinquencyBucketAgesOverlapedException;
|
||||||
import org.apache.fineract.portfolio.delinquency.exception.DelinquencyRangeInvalidAgesException;
|
import org.apache.fineract.portfolio.delinquency.exception.DelinquencyRangeInvalidAgesException;
|
||||||
@@ -63,7 +60,6 @@ import org.apache.fineract.portfolio.loanaccount.data.CollectionData;
|
|||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanDelinquencyData;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanDelinquencyData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
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.LoanRepositoryWrapper;
|
||||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
|
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
@@ -74,20 +70,20 @@ public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlat
|
|||||||
|
|
||||||
private final DelinquencyBucketParseAndValidator dataValidatorBucket;
|
private final DelinquencyBucketParseAndValidator dataValidatorBucket;
|
||||||
private final DelinquencyRangeParseAndValidator dataValidatorRange;
|
private final DelinquencyRangeParseAndValidator dataValidatorRange;
|
||||||
|
|
||||||
private final DelinquencyRangeRepository repositoryRange;
|
private final DelinquencyRangeRepository repositoryRange;
|
||||||
private final DelinquencyBucketRepository repositoryBucket;
|
private final DelinquencyBucketRepository repositoryBucket;
|
||||||
private final DelinquencyBucketMappingsRepository repositoryBucketMappings;
|
private final DelinquencyBucketMappingsRepository repositoryBucketMappings;
|
||||||
private final LoanDelinquencyTagHistoryRepository loanDelinquencyTagRepository;
|
private final LoanDelinquencyTagHistoryRepository loanDelinquencyTagRepository;
|
||||||
private final LoanRepositoryWrapper loanRepository;
|
private final LoanRepositoryWrapper loanRepository;
|
||||||
private final LoanProductRepository loanProductRepository;
|
private final LoanProductRepository loanProductRepository;
|
||||||
private final BusinessEventNotifierService businessEventNotifierService;
|
|
||||||
private final LoanDelinquencyDomainService loanDelinquencyDomainService;
|
private final LoanDelinquencyDomainService loanDelinquencyDomainService;
|
||||||
private final LoanInstallmentDelinquencyTagRepository loanInstallmentDelinquencyTagRepository;
|
private final LoanInstallmentDelinquencyTagRepository loanInstallmentDelinquencyTagRepository;
|
||||||
private final DelinquencyReadPlatformService delinquencyReadPlatformService;
|
private final DelinquencyReadPlatformService delinquencyReadPlatformService;
|
||||||
private final LoanDelinquencyActionRepository loanDelinquencyActionRepository;
|
private final LoanDelinquencyActionRepository loanDelinquencyActionRepository;
|
||||||
private final DelinquencyActionParseAndValidator delinquencyActionParseAndValidator;
|
private final DelinquencyActionParseAndValidator delinquencyActionParseAndValidator;
|
||||||
private final DelinquencyEffectivePauseHelper delinquencyEffectivePauseHelper;
|
private final DelinquencyEffectivePauseHelper delinquencyEffectivePauseHelper;
|
||||||
|
private final BusinessEventNotifierService businessEventNotifierService;
|
||||||
|
private final DelinquencyWritePlatformServiceHelper delinquencyHelper;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommandProcessingResult createDelinquencyRange(JsonCommand command) {
|
public CommandProcessingResult createDelinquencyRange(JsonCommand command) {
|
||||||
@@ -186,16 +182,17 @@ public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlat
|
|||||||
final CollectionData collectionData = loanDelinquencyData.getLoanCollectionData();
|
final CollectionData collectionData = loanDelinquencyData.getLoanCollectionData();
|
||||||
// loan installments delinquent data
|
// loan installments delinquent data
|
||||||
final Map<Long, CollectionData> installmentsCollectionData = loanDelinquencyData.getLoanInstallmentsCollectionData();
|
final Map<Long, CollectionData> installmentsCollectionData = loanDelinquencyData.getLoanInstallmentsCollectionData();
|
||||||
// delinquency for installments
|
log.debug("Delinquency {}", collectionData);
|
||||||
if (installmentsCollectionData.size() > 0) {
|
|
||||||
applyDelinquencyDetailsForLoanInstallments(loan, delinquencyBucket, installmentsCollectionData);
|
changes = applyDelinquencyToLoanAndInstallments(loan, delinquencyBucket, collectionData, installmentsCollectionData);
|
||||||
}
|
|
||||||
// delinquency for loan
|
|
||||||
changes = lookUpDelinquencyRange(loan, delinquencyBucket, collectionData.getDelinquentDays());
|
|
||||||
|
|
||||||
}
|
}
|
||||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(loan.getId())
|
return new CommandProcessingResultBuilder() //
|
||||||
.withEntityExternalId(loan.getExternalId()).with(changes).build();
|
.withCommandId(command.commandId()) //
|
||||||
|
.withEntityId(loan.getId()) //
|
||||||
|
.withEntityExternalId(loan.getExternalId()) //
|
||||||
|
.with(changes) //
|
||||||
|
.build(); //
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -210,16 +207,24 @@ public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlat
|
|||||||
final CollectionData collectionData = loanDelinquentData.getLoanCollectionData();
|
final CollectionData collectionData = loanDelinquentData.getLoanCollectionData();
|
||||||
// loan installments delinquent data
|
// loan installments delinquent data
|
||||||
final Map<Long, CollectionData> installmentsCollectionData = loanDelinquentData.getLoanInstallmentsCollectionData();
|
final Map<Long, CollectionData> installmentsCollectionData = loanDelinquentData.getLoanInstallmentsCollectionData();
|
||||||
// delinquency for installments
|
|
||||||
if (installmentsCollectionData.size() > 0) {
|
|
||||||
applyDelinquencyDetailsForLoanInstallments(loan, delinquencyBucket, installmentsCollectionData);
|
|
||||||
}
|
|
||||||
log.debug("Delinquency {}", collectionData);
|
log.debug("Delinquency {}", collectionData);
|
||||||
// delinquency for loan
|
|
||||||
lookUpDelinquencyRange(loan, delinquencyBucket, collectionData.getDelinquentDays());
|
applyDelinquencyToLoanAndInstallments(loan, delinquencyBucket, collectionData, installmentsCollectionData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> applyDelinquencyToLoanAndInstallments(Loan loan, DelinquencyBucket delinquencyBucket,
|
||||||
|
CollectionData collectionData, Map<Long, CollectionData> installmentsCollectionData) {
|
||||||
|
// Order is important: first calculate loan level delinquency, then the installment level
|
||||||
|
// delinquency for loan
|
||||||
|
Map<String, Object> result = delinquencyHelper.applyDelinquencyForLoan(loan, delinquencyBucket, collectionData.getDelinquentDays());
|
||||||
|
// delinquency for installments
|
||||||
|
if (!installmentsCollectionData.isEmpty()) {
|
||||||
|
delinquencyHelper.applyDelinquencyForLoanInstallments(loan, delinquencyBucket, installmentsCollectionData);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public CommandProcessingResult createDelinquencyAction(Long loanId, JsonCommand command) {
|
public CommandProcessingResult createDelinquencyAction(Long loanId, JsonCommand command) {
|
||||||
@@ -242,7 +247,8 @@ public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanAccountDelinquencyPauseChangedBusinessEvent(loan));
|
businessEventNotifierService.notifyPostBusinessEvent(new LoanAccountDelinquencyPauseChangedBusinessEvent(loan));
|
||||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()) //
|
return new CommandProcessingResultBuilder() //
|
||||||
|
.withCommandId(command.commandId()) //
|
||||||
.withEntityId(saved.getId()) //
|
.withEntityId(saved.getId()) //
|
||||||
.withOfficeId(loan.getOfficeId()) //
|
.withOfficeId(loan.getOfficeId()) //
|
||||||
.withClientId(loan.getClientId()) //
|
.withClientId(loan.getClientId()) //
|
||||||
@@ -271,7 +277,7 @@ public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlat
|
|||||||
if (loan.isEnableInstallmentLevelDelinquency()) {
|
if (loan.isEnableInstallmentLevelDelinquency()) {
|
||||||
cleanLoanInstallmentsDelinquencyTags(loan);
|
cleanLoanInstallmentsDelinquencyTags(loan);
|
||||||
}
|
}
|
||||||
setLoanDelinquencyTag(loan, null);
|
delinquencyHelper.setLoanDelinquencyTag(loan, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -365,7 +371,7 @@ public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlat
|
|||||||
|
|
||||||
private void validateDelinquencyRanges(List<DelinquencyRange> ranges) {
|
private void validateDelinquencyRanges(List<DelinquencyRange> ranges) {
|
||||||
// Sort the ranges based on the minAgeDays
|
// Sort the ranges based on the minAgeDays
|
||||||
ranges = sortDelinquencyRangesByMinAge(ranges);
|
ranges = delinquencyHelper.sortDelinquencyRangesByMinAge(ranges);
|
||||||
|
|
||||||
DelinquencyRange prevDelinquencyRange = null;
|
DelinquencyRange prevDelinquencyRange = null;
|
||||||
for (DelinquencyRange delinquencyRange : ranges) {
|
for (DelinquencyRange delinquencyRange : ranges) {
|
||||||
@@ -387,220 +393,7 @@ public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Object> lookUpDelinquencyRange(final Loan loan, final DelinquencyBucket delinquencyBucket, long overdueDays) {
|
|
||||||
Map<String, Object> changes = new HashMap<>();
|
|
||||||
|
|
||||||
if (overdueDays <= 0) { // No Delinquency
|
|
||||||
log.debug("Loan {} without delinquency range with {} days", loan.getId(), overdueDays);
|
|
||||||
changes = setLoanDelinquencyTag(loan, null);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// Sort the ranges based on the minAgeDays
|
|
||||||
final List<DelinquencyRange> ranges = sortDelinquencyRangesByMinAge(delinquencyBucket.getRanges());
|
|
||||||
|
|
||||||
for (final DelinquencyRange delinquencyRange : ranges) {
|
|
||||||
if (delinquencyRange.getMaximumAgeDays() == null) { // Last Range in the Bucket
|
|
||||||
if (delinquencyRange.getMinimumAgeDays() <= overdueDays) {
|
|
||||||
log.debug("Loan {} with delinquency range {} with {} days", loan.getId(), delinquencyRange.getClassification(),
|
|
||||||
overdueDays);
|
|
||||||
changes = setLoanDelinquencyTag(loan, delinquencyRange.getId());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (delinquencyRange.getMinimumAgeDays() <= overdueDays && delinquencyRange.getMaximumAgeDays() >= overdueDays) {
|
|
||||||
log.debug("Loan {} with delinquency range {} with {} days", loan.getId(), delinquencyRange.getClassification(),
|
|
||||||
overdueDays);
|
|
||||||
changes = setLoanDelinquencyTag(loan, delinquencyRange.getId());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
changes.put("overdueDays", overdueDays);
|
|
||||||
return changes;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String, Object> setLoanDelinquencyTag(Loan loan, Long delinquencyRangeId) {
|
|
||||||
Map<String, Object> changes = new HashMap<>();
|
|
||||||
List<LoanDelinquencyTagHistory> loanDelinquencyTagHistory = new ArrayList<>();
|
|
||||||
final LocalDate transactionDate = DateUtils.getBusinessLocalDate();
|
|
||||||
Optional<LoanDelinquencyTagHistory> optLoanDelinquencyTag = this.loanDelinquencyTagRepository.findByLoanAndLiftedOnDate(loan, null);
|
|
||||||
// The delinquencyRangeId in null means just goes out from Delinquency
|
|
||||||
LoanDelinquencyTagHistory loanDelinquencyTagPrev = null;
|
|
||||||
if (delinquencyRangeId == null) {
|
|
||||||
// The Loan will go out from Delinquency
|
|
||||||
if (optLoanDelinquencyTag.isPresent()) {
|
|
||||||
loanDelinquencyTagPrev = optLoanDelinquencyTag.get();
|
|
||||||
loanDelinquencyTagPrev.setLiftedOnDate(transactionDate);
|
|
||||||
loanDelinquencyTagHistory.add(loanDelinquencyTagPrev);
|
|
||||||
changes.put("previous", loanDelinquencyTagPrev.getDelinquencyRange());
|
|
||||||
// event when loan goes out of delinquency we do not calculate at
|
|
||||||
// installment level and remove all installment tags, so event needs to raised here.
|
|
||||||
if (loan.isEnableInstallmentLevelDelinquency()) {
|
|
||||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanDelinquencyRangeChangeBusinessEvent(loan));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (optLoanDelinquencyTag.isPresent()) {
|
|
||||||
loanDelinquencyTagPrev = optLoanDelinquencyTag.get();
|
|
||||||
}
|
|
||||||
// If the Delinquency Tag has not changed
|
|
||||||
if (loanDelinquencyTagPrev != null && loanDelinquencyTagPrev.getDelinquencyRange().getId().equals(delinquencyRangeId)) {
|
|
||||||
changes.put("current", loanDelinquencyTagPrev.getDelinquencyRange());
|
|
||||||
} else {
|
|
||||||
// The previous Loan Delinquency Tag will set as Lifted
|
|
||||||
if (loanDelinquencyTagPrev != null) {
|
|
||||||
loanDelinquencyTagPrev.setLiftedOnDate(transactionDate);
|
|
||||||
loanDelinquencyTagHistory.add(loanDelinquencyTagPrev);
|
|
||||||
changes.put("previous", loanDelinquencyTagPrev.getDelinquencyRange());
|
|
||||||
}
|
|
||||||
|
|
||||||
final DelinquencyRange delinquencyRange = repositoryRange.getReferenceById(delinquencyRangeId);
|
|
||||||
LoanDelinquencyTagHistory loanDelinquencyTag = new LoanDelinquencyTagHistory(delinquencyRange, loan, transactionDate, null);
|
|
||||||
loanDelinquencyTagHistory.add(loanDelinquencyTag);
|
|
||||||
changes.put("current", loanDelinquencyTag.getDelinquencyRange());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (loanDelinquencyTagHistory.size() > 0) {
|
|
||||||
this.loanDelinquencyTagRepository.saveAllAndFlush(loanDelinquencyTagHistory);
|
|
||||||
// if installment level delinquency is enabled event will be raised at installment level calculation, no
|
|
||||||
// need to raise the event here
|
|
||||||
if (!loan.isEnableInstallmentLevelDelinquency()) {
|
|
||||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanDelinquencyRangeChangeBusinessEvent(loan));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return changes;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<DelinquencyRange> sortDelinquencyRangesByMinAge(List<DelinquencyRange> ranges) {
|
|
||||||
final Comparator<DelinquencyRange> orderByMinAge = new Comparator<DelinquencyRange>() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compare(DelinquencyRange o1, DelinquencyRange o2) {
|
|
||||||
return o1.getMinimumAgeDays().compareTo(o2.getMinimumAgeDays());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Collections.sort(ranges, orderByMinAge);
|
|
||||||
return ranges;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void applyDelinquencyDetailsForLoanInstallments(final Loan loan, final DelinquencyBucket delinquencyBucket,
|
|
||||||
final Map<Long, CollectionData> installmentsCollectionData) {
|
|
||||||
boolean isDelinquencyRangeChangedForAnyOfInstallment = false;
|
|
||||||
for (LoanRepaymentScheduleInstallment installment : loan.getRepaymentScheduleInstallments()) {
|
|
||||||
if (installmentsCollectionData.containsKey(installment.getId())) {
|
|
||||||
boolean isDelinquencySetForInstallment = setInstallmentDelinquencyDetails(loan, installment, delinquencyBucket,
|
|
||||||
installmentsCollectionData.get(installment.getId()));
|
|
||||||
isDelinquencyRangeChangedForAnyOfInstallment = isDelinquencyRangeChangedForAnyOfInstallment
|
|
||||||
|| isDelinquencySetForInstallment;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// remove tags for non existing installments that got deleted due to re-schedule
|
|
||||||
removeDelinquencyTagsForNonExistingInstallments(loan.getId());
|
|
||||||
// raise event if there is any change at installment level delinquency
|
|
||||||
if (isDelinquencyRangeChangedForAnyOfInstallment) {
|
|
||||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanDelinquencyRangeChangeBusinessEvent(loan));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean setInstallmentDelinquencyDetails(final Loan loan, final LoanRepaymentScheduleInstallment installment,
|
|
||||||
final DelinquencyBucket delinquencyBucket, final CollectionData installmentDelinquencyData) {
|
|
||||||
DelinquencyRange delinquencyRangeForInstallment = getInstallmentDelinquencyRange(delinquencyBucket,
|
|
||||||
installmentDelinquencyData.getDelinquentDays());
|
|
||||||
return setDelinquencyDetailsForInstallment(loan, installment, installmentDelinquencyData, delinquencyRangeForInstallment);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DelinquencyRange getInstallmentDelinquencyRange(final DelinquencyBucket delinquencyBucket, Long overDueDays) {
|
|
||||||
DelinquencyRange delinquencyRangeForInstallment = null;
|
|
||||||
if (overDueDays > 0) {
|
|
||||||
// Sort the ranges based on the minAgeDays
|
|
||||||
final List<DelinquencyRange> ranges = sortDelinquencyRangesByMinAge(delinquencyBucket.getRanges());
|
|
||||||
for (final DelinquencyRange delinquencyRange : ranges) {
|
|
||||||
if (delinquencyRange.getMaximumAgeDays() == null) { // Last Range in the Bucket
|
|
||||||
if (delinquencyRange.getMinimumAgeDays() <= overDueDays) {
|
|
||||||
delinquencyRangeForInstallment = delinquencyRange;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (delinquencyRange.getMinimumAgeDays() <= overDueDays && delinquencyRange.getMaximumAgeDays() >= overDueDays) {
|
|
||||||
delinquencyRangeForInstallment = delinquencyRange;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return delinquencyRangeForInstallment;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean setDelinquencyDetailsForInstallment(final Loan loan, final LoanRepaymentScheduleInstallment installment,
|
|
||||||
CollectionData installmentDelinquencyData, final DelinquencyRange delinquencyRangeForInstallment) {
|
|
||||||
List<LoanInstallmentDelinquencyTag> installmentDelinquencyTags = new ArrayList<>();
|
|
||||||
LocalDate delinquencyCalculationDate = DateUtils.getBusinessLocalDate();
|
|
||||||
boolean isDelinquencyRangeChanged = false;
|
|
||||||
|
|
||||||
LoanInstallmentDelinquencyTag previousInstallmentDelinquencyTag = loanInstallmentDelinquencyTagRepository
|
|
||||||
.findByLoanAndInstallment(loan, installment).orElse(null);
|
|
||||||
|
|
||||||
if (delinquencyRangeForInstallment == null) {
|
|
||||||
// if currentInstallmentDelinquencyTag exists and range is null, installment is out of delinquency, delete
|
|
||||||
// delinquency details
|
|
||||||
if (previousInstallmentDelinquencyTag != null) {
|
|
||||||
// event installment out of delinquency
|
|
||||||
loanInstallmentDelinquencyTagRepository.delete(previousInstallmentDelinquencyTag);
|
|
||||||
isDelinquencyRangeChanged = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
LoanInstallmentDelinquencyTag installmentDelinquency = null;
|
|
||||||
if (previousInstallmentDelinquencyTag != null) {
|
|
||||||
if (!previousInstallmentDelinquencyTag.getDelinquencyRange().getId().equals(delinquencyRangeForInstallment.getId())) {
|
|
||||||
// if current delinquency range exists and there is range change, delete previous delinquency
|
|
||||||
// details and add new range details
|
|
||||||
installmentDelinquency = new LoanInstallmentDelinquencyTag(delinquencyRangeForInstallment, loan, installment,
|
|
||||||
delinquencyCalculationDate, null, previousInstallmentDelinquencyTag.getFirstOverdueDate(),
|
|
||||||
installmentDelinquencyData.getDelinquentAmount());
|
|
||||||
loanInstallmentDelinquencyTagRepository.delete(previousInstallmentDelinquencyTag);
|
|
||||||
// event installment delinquency range change
|
|
||||||
isDelinquencyRangeChanged = true;
|
|
||||||
} else {
|
|
||||||
previousInstallmentDelinquencyTag.setOutstandingAmount(installmentDelinquencyData.getDelinquentAmount());
|
|
||||||
installmentDelinquency = previousInstallmentDelinquencyTag;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// add new range, first time delinquent
|
|
||||||
installmentDelinquency = new LoanInstallmentDelinquencyTag(delinquencyRangeForInstallment, loan, installment,
|
|
||||||
delinquencyCalculationDate, null, installmentDelinquencyData.getDelinquentDate(),
|
|
||||||
installmentDelinquencyData.getDelinquentAmount());
|
|
||||||
// event installment delinquent
|
|
||||||
isDelinquencyRangeChanged = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (installmentDelinquency != null) {
|
|
||||||
installmentDelinquencyTags.add(installmentDelinquency);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (installmentDelinquencyTags.size() > 0) {
|
|
||||||
loanInstallmentDelinquencyTagRepository.saveAllAndFlush(installmentDelinquencyTags);
|
|
||||||
}
|
|
||||||
return isDelinquencyRangeChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void cleanLoanInstallmentsDelinquencyTags(Loan loan) {
|
private void cleanLoanInstallmentsDelinquencyTags(Loan loan) {
|
||||||
loanInstallmentDelinquencyTagRepository.deleteAllLoanInstallmentsTags(loan.getId());
|
loanInstallmentDelinquencyTagRepository.deleteAllLoanInstallmentsTags(loan.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeDelinquencyTagsForNonExistingInstallments(Long loanId) {
|
|
||||||
List<LoanInstallmentDelinquencyTag> currentLoanInstallmentDelinquencyTags = loanInstallmentDelinquencyTagRepository
|
|
||||||
.findByLoanId(loanId);
|
|
||||||
if (currentLoanInstallmentDelinquencyTags != null && currentLoanInstallmentDelinquencyTags.size() > 0) {
|
|
||||||
List<Long> loanInstallmentTagsForDelete = currentLoanInstallmentDelinquencyTags.stream()
|
|
||||||
.filter(tag -> tag.getInstallment() == null).map(tag -> tag.getId()).toList();
|
|
||||||
if (loanInstallmentTagsForDelete.size() > 0) {
|
|
||||||
loanInstallmentDelinquencyTagRepository.deleteAllLoanInstallmentsTagsByIds(loanInstallmentTagsForDelete);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-4
@@ -33,6 +33,7 @@ import org.apache.fineract.portfolio.delinquency.mapper.LoanDelinquencyTagMapper
|
|||||||
import org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService;
|
import org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService;
|
||||||
import org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformServiceImpl;
|
import org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformServiceImpl;
|
||||||
import org.apache.fineract.portfolio.delinquency.service.DelinquencyWritePlatformService;
|
import org.apache.fineract.portfolio.delinquency.service.DelinquencyWritePlatformService;
|
||||||
|
import org.apache.fineract.portfolio.delinquency.service.DelinquencyWritePlatformServiceHelper;
|
||||||
import org.apache.fineract.portfolio.delinquency.service.DelinquencyWritePlatformServiceImpl;
|
import org.apache.fineract.portfolio.delinquency.service.DelinquencyWritePlatformServiceImpl;
|
||||||
import org.apache.fineract.portfolio.delinquency.service.LoanDelinquencyDomainService;
|
import org.apache.fineract.portfolio.delinquency.service.LoanDelinquencyDomainService;
|
||||||
import org.apache.fineract.portfolio.delinquency.service.LoanDelinquencyDomainServiceImpl;
|
import org.apache.fineract.portfolio.delinquency.service.LoanDelinquencyDomainServiceImpl;
|
||||||
@@ -76,11 +77,13 @@ public class DelinquencyConfiguration {
|
|||||||
LoanInstallmentDelinquencyTagRepository loanInstallmentDelinquencyTagRepository,
|
LoanInstallmentDelinquencyTagRepository loanInstallmentDelinquencyTagRepository,
|
||||||
DelinquencyReadPlatformService delinquencyReadPlatformService, LoanDelinquencyActionRepository loanDelinquencyActionRepository,
|
DelinquencyReadPlatformService delinquencyReadPlatformService, LoanDelinquencyActionRepository loanDelinquencyActionRepository,
|
||||||
DelinquencyActionParseAndValidator delinquencyActionParseAndValidator,
|
DelinquencyActionParseAndValidator delinquencyActionParseAndValidator,
|
||||||
DelinquencyEffectivePauseHelper delinquencyEffectivePauseHelper) {
|
DelinquencyEffectivePauseHelper delinquencyEffectivePauseHelper,
|
||||||
|
DelinquencyWritePlatformServiceHelper delinquencyWritePlatformServiceHelper) {
|
||||||
return new DelinquencyWritePlatformServiceImpl(dataValidatorBucket, dataValidatorRange, repositoryRange, repositoryBucket,
|
return new DelinquencyWritePlatformServiceImpl(dataValidatorBucket, dataValidatorRange, repositoryRange, repositoryBucket,
|
||||||
repositoryBucketMappings, loanDelinquencyTagRepository, loanRepository, loanProductRepository, businessEventNotifierService,
|
repositoryBucketMappings, loanDelinquencyTagRepository, loanRepository, loanProductRepository, loanDelinquencyDomainService,
|
||||||
loanDelinquencyDomainService, loanInstallmentDelinquencyTagRepository, delinquencyReadPlatformService,
|
loanInstallmentDelinquencyTagRepository, delinquencyReadPlatformService, loanDelinquencyActionRepository,
|
||||||
loanDelinquencyActionRepository, delinquencyActionParseAndValidator, delinquencyEffectivePauseHelper);
|
delinquencyActionParseAndValidator, delinquencyEffectivePauseHelper, businessEventNotifierService,
|
||||||
|
delinquencyWritePlatformServiceHelper);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|||||||
+76
-2
@@ -24,6 +24,8 @@ import static org.mockito.ArgumentMatchers.any;
|
|||||||
import static org.mockito.ArgumentMatchers.anyIterable;
|
import static org.mockito.ArgumentMatchers.anyIterable;
|
||||||
import static org.mockito.ArgumentMatchers.anyList;
|
import static org.mockito.ArgumentMatchers.anyList;
|
||||||
import static org.mockito.ArgumentMatchers.anyLong;
|
import static org.mockito.ArgumentMatchers.anyLong;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.inOrder;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
@@ -62,6 +64,7 @@ import org.apache.fineract.portfolio.delinquency.domain.LoanInstallmentDelinquen
|
|||||||
import org.apache.fineract.portfolio.delinquency.domain.LoanInstallmentDelinquencyTagRepository;
|
import org.apache.fineract.portfolio.delinquency.domain.LoanInstallmentDelinquencyTagRepository;
|
||||||
import org.apache.fineract.portfolio.delinquency.helper.DelinquencyEffectivePauseHelper;
|
import org.apache.fineract.portfolio.delinquency.helper.DelinquencyEffectivePauseHelper;
|
||||||
import org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService;
|
import org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService;
|
||||||
|
import org.apache.fineract.portfolio.delinquency.service.DelinquencyWritePlatformServiceHelper;
|
||||||
import org.apache.fineract.portfolio.delinquency.service.DelinquencyWritePlatformServiceImpl;
|
import org.apache.fineract.portfolio.delinquency.service.DelinquencyWritePlatformServiceImpl;
|
||||||
import org.apache.fineract.portfolio.delinquency.service.LoanDelinquencyDomainService;
|
import org.apache.fineract.portfolio.delinquency.service.LoanDelinquencyDomainService;
|
||||||
import org.apache.fineract.portfolio.delinquency.validator.DelinquencyActionParseAndValidator;
|
import org.apache.fineract.portfolio.delinquency.validator.DelinquencyActionParseAndValidator;
|
||||||
@@ -81,7 +84,7 @@ import org.junit.jupiter.api.BeforeEach;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InOrder;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
@@ -120,7 +123,8 @@ public class DelinquencyWritePlatformServiceRangeChangeEventTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private DelinquencyEffectivePauseHelper delinquencyEffectivePauseHelper;
|
private DelinquencyEffectivePauseHelper delinquencyEffectivePauseHelper;
|
||||||
|
|
||||||
@InjectMocks
|
private DelinquencyWritePlatformServiceHelper delinquencyWritePlatformServiceHelper;
|
||||||
|
|
||||||
private DelinquencyWritePlatformServiceImpl underTest;
|
private DelinquencyWritePlatformServiceImpl underTest;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
@@ -129,6 +133,14 @@ public class DelinquencyWritePlatformServiceRangeChangeEventTest {
|
|||||||
ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT);
|
ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT);
|
||||||
ThreadLocalContextUtil
|
ThreadLocalContextUtil
|
||||||
.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.now(ZoneId.systemDefault()))));
|
.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.now(ZoneId.systemDefault()))));
|
||||||
|
|
||||||
|
delinquencyWritePlatformServiceHelper = Mockito.spy(new DelinquencyWritePlatformServiceHelper(businessEventNotifierService,
|
||||||
|
loanDelinquencyTagRepository, repositoryRange, loanInstallmentDelinquencyTagRepository));
|
||||||
|
underTest = new DelinquencyWritePlatformServiceImpl(dataValidatorBucket, dataValidatorRange, repositoryRange, repositoryBucket,
|
||||||
|
repositoryBucketMappings, loanDelinquencyTagRepository, loanRepository, loanProductRepository, loanDelinquencyDomainService,
|
||||||
|
loanInstallmentDelinquencyTagRepository, delinquencyReadPlatformService, loanDelinquencyActionRepository,
|
||||||
|
delinquencyActionParseAndValidator, delinquencyEffectivePauseHelper, businessEventNotifierService,
|
||||||
|
delinquencyWritePlatformServiceHelper);
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
@@ -180,6 +192,68 @@ public class DelinquencyWritePlatformServiceRangeChangeEventTest {
|
|||||||
assertEquals(loanForProcessing, loanPayloadForEvent);
|
assertEquals(loanForProcessing, loanPayloadForEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_ApplyDelinquencyTagToLoan_ExecutesDelinquencyApplication_InTheRightOrder() {
|
||||||
|
// given
|
||||||
|
final List<LoanDelinquencyActionData> effectiveDelinquencyList = Collections.emptyList();
|
||||||
|
Loan loanForProcessing = Mockito.mock(Loan.class);
|
||||||
|
LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
|
||||||
|
DelinquencyRange range1 = DelinquencyRange.instance("Range1", 1, 2);
|
||||||
|
range1.setId(1L);
|
||||||
|
DelinquencyRange range2 = DelinquencyRange.instance("Range30", 3, 30);
|
||||||
|
range2.setId(2L);
|
||||||
|
List<DelinquencyRange> listDelinquencyRanges = Arrays.asList(range1, range2);
|
||||||
|
DelinquencyBucket delinquencyBucket = new DelinquencyBucket("test Bucket");
|
||||||
|
delinquencyBucket.setRanges(listDelinquencyRanges);
|
||||||
|
|
||||||
|
final Long daysDiff = 2L;
|
||||||
|
final LocalDate fromDate = DateUtils.getBusinessLocalDate().minusMonths(1).minusDays(daysDiff);
|
||||||
|
final LocalDate dueDate = DateUtils.getBusinessLocalDate().minusDays(daysDiff);
|
||||||
|
final BigDecimal installmentPrincipalAmount = BigDecimal.valueOf(100);
|
||||||
|
final BigDecimal zeroAmount = BigDecimal.ZERO;
|
||||||
|
|
||||||
|
LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(loanForProcessing, 1, fromDate, dueDate,
|
||||||
|
installmentPrincipalAmount, zeroAmount, zeroAmount, zeroAmount, false, new HashSet<>(), zeroAmount);
|
||||||
|
installment.setId(1L);
|
||||||
|
|
||||||
|
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments = Arrays.asList(installment);
|
||||||
|
|
||||||
|
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,
|
||||||
|
null, null, null, null);
|
||||||
|
|
||||||
|
CollectionData installmentCollectionData = new CollectionData(BigDecimal.ZERO, 2L, null, 2L, overDueSinceDate,
|
||||||
|
installmentPrincipalAmount, null, null, null, null, null, null);
|
||||||
|
|
||||||
|
Map<Long, CollectionData> installmentsCollection = new HashMap<>();
|
||||||
|
installmentsCollection.put(1L, installmentCollectionData);
|
||||||
|
|
||||||
|
LoanDelinquencyData loanDelinquencyData = new LoanDelinquencyData(collectionData, installmentsCollection);
|
||||||
|
|
||||||
|
when(loanForProcessing.getLoanProduct()).thenReturn(loanProduct);
|
||||||
|
when(loanProduct.getDelinquencyBucket()).thenReturn(delinquencyBucket);
|
||||||
|
when(loanForProcessing.hasDelinquencyBucket()).thenReturn(true);
|
||||||
|
when(loanForProcessing.getRepaymentScheduleInstallments()).thenReturn(repaymentScheduleInstallments);
|
||||||
|
when(loanForProcessing.isEnableInstallmentLevelDelinquency()).thenReturn(true);
|
||||||
|
when(loanDelinquencyTagRepository.findByLoanAndLiftedOnDate(any(), any())).thenReturn(Optional.empty());
|
||||||
|
when(loanDelinquencyDomainService.getLoanDelinquencyData(loanForProcessing, effectiveDelinquencyList))
|
||||||
|
.thenReturn(loanDelinquencyData);
|
||||||
|
when(loanInstallmentDelinquencyTagRepository.findByLoanAndInstallment(loanForProcessing, repaymentScheduleInstallments.get(0)))
|
||||||
|
.thenReturn(Optional.empty());
|
||||||
|
|
||||||
|
// when
|
||||||
|
underTest.applyDelinquencyTagToLoan(loanScheduleDelinquencyData, effectiveDelinquencyList);
|
||||||
|
|
||||||
|
// then
|
||||||
|
InOrder inOrder = inOrder(delinquencyWritePlatformServiceHelper);
|
||||||
|
inOrder.verify(delinquencyWritePlatformServiceHelper).applyDelinquencyForLoan(eq(loanForProcessing), eq(delinquencyBucket),
|
||||||
|
anyLong());
|
||||||
|
inOrder.verify(delinquencyWritePlatformServiceHelper).applyDelinquencyForLoanInstallments(eq(loanForProcessing),
|
||||||
|
eq(delinquencyBucket), eq(installmentsCollection));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void givenLoanAccountWithDelinquencyBucketWhenNoRangeChangeThenNoEventIsRaised() {
|
public void givenLoanAccountWithDelinquencyBucketWhenNoRangeChangeThenNoEventIsRaised() {
|
||||||
// given
|
// given
|
||||||
|
|||||||
Reference in New Issue
Block a user