This is an automated email from the ASF dual-hosted git repository.
adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/develop by this push:
new 8cea837a5
FINERACT-1992-FINERACT-Installment-level-delinquency-calculation
8cea837a5 is described below
commit 8cea837a5b8071ab8e59659b16f9dc0f85fbec40
Author: Ruchi Dhamankar <[email protected]>
AuthorDate: Thu Oct 26 20:25:07 2023 +0530
FINERACT-1992-FINERACT-Installment-level-delinquency-calculation
---
.../tenant/module/loan/module-changelog-master.xml | 1 +
...9_refactor_loan_Installment_delinquency_tag.xml | 31 +++
.../LoanInstallmentDelinquencyTagData.java} | 27 ++-
.../domain/LoanInstallmentDelinquencyTag.java | 80 +++++++
.../LoanInstallmentDelinquencyTagRepository.java | 52 +++++
.../service/DelinquencyReadPlatformService.java | 3 +
.../DelinquencyReadPlatformServiceImpl.java | 8 +
.../DelinquencyWritePlatformServiceImpl.java | 133 +++++++++++-
.../service/LoanDelinquencyDomainService.java | 3 +
.../service/LoanDelinquencyDomainServiceImpl.java | 233 ++++++++++++++++-----
.../starter/DelinquencyConfiguration.java | 12 +-
.../data/LoanDelinquencyData.java} | 25 +--
...cyWritePlatformServiceRangeChangeEventTest.java | 161 +++++++++++++-
.../LoanDelinquencyDomainServiceTest.java | 93 ++++++++
14 files changed, 782 insertions(+), 80 deletions(-)
diff --git
a/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml
b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml
index 27e5aea91..56a18e16e 100644
---
a/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml
+++
b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml
@@ -31,4 +31,5 @@
<include relativeToChangelogFile="true"
file="parts/1006_add_downpayment_transaction_enum_permissions.xml"/>
<include relativeToChangelogFile="true"
file="parts/1007_add_loan_product_schedule_extension_for_down_payment_configuration.xml"/>
<include relativeToChangelogFile="true"
file="parts/1008_add_loan_installment_delinquency_tag.xml"/>
+ <include relativeToChangelogFile="true"
file="parts/1009_refactor_loan_Installment_delinquency_tag.xml"/>
</databaseChangeLog>
diff --git
a/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1009_refactor_loan_Installment_delinquency_tag.xml
b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1009_refactor_loan_Installment_delinquency_tag.xml
new file mode 100644
index 000000000..62df11440
--- /dev/null
+++
b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1009_refactor_loan_Installment_delinquency_tag.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+
+-->
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
+ <changeSet author="fineract" id="1">
+ <renameTable oldTableName="m_loan_installment_delinquency_tag_history"
newTableName="m_loan_installment_delinquency_tag"/>
+ </changeSet>
+ <changeSet author="fineract" id="2">
+ <dropForeignKeyConstraint
baseTableName="m_loan_installment_delinquency_tag"
constraintName="FK_m_delinquency_installment_tags_installment" />
+ </changeSet>
+</databaseChangeLog>
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/data/LoanInstallmentDelinquencyTagData.java
similarity index 62%
copy from
fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/LoanDelinquencyDomainService.java
copy to
fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/data/LoanInstallmentDelinquencyTagData.java
index 5cd19cb5f..860156951 100644
---
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/data/LoanInstallmentDelinquencyTagData.java
@@ -16,19 +16,24 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.fineract.portfolio.delinquency.service;
+package org.apache.fineract.portfolio.delinquency.data;
-import org.apache.fineract.portfolio.loanaccount.data.CollectionData;
-import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import java.math.BigDecimal;
-public interface LoanDelinquencyDomainService {
+public interface LoanInstallmentDelinquencyTagData {
- /**
- * 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);
+ InstallmentDelinquencyRange getDelinquencyRange();
+ BigDecimal getOutstandingAmount();
+
+ interface InstallmentDelinquencyRange {
+
+ Long getId();
+
+ String getClassification();
+
+ Integer getMinimumAgeDays();
+
+ Integer getMaximumAgeDays();
+ }
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/domain/LoanInstallmentDelinquencyTag.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/domain/LoanInstallmentDelinquencyTag.java
new file mode 100644
index 000000000..248c49d51
--- /dev/null
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/domain/LoanInstallmentDelinquencyTag.java
@@ -0,0 +1,80 @@
+/**
+ * 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.domain;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.ManyToOne;
+import jakarta.persistence.Table;
+import jakarta.persistence.Version;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import
org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@Entity
+@Table(name = "m_loan_installment_delinquency_tag")
+public class LoanInstallmentDelinquencyTag extends
AbstractAuditableWithUTCDateTimeCustom {
+
+ @ManyToOne
+ @JoinColumn(name = "delinquency_range_id", nullable = false)
+ private DelinquencyRange delinquencyRange;
+
+ @ManyToOne
+ @JoinColumn(name = "loan_id", nullable = false)
+ private Loan loan;
+
+ @ManyToOne
+ @JoinColumn(name = "installment_id", nullable = false)
+ private LoanRepaymentScheduleInstallment installment;
+
+ @Column(name = "addedon_date", nullable = false)
+ private LocalDate addedOnDate;
+
+ @Column(name = "liftedon_date", nullable = true)
+ private LocalDate liftedOnDate;
+
+ @Column(name = "first_overdue_date", nullable = false)
+ private LocalDate firstOverdueDate;
+
+ @Column(name = "outstanding_amount", scale = 6, precision = 19)
+ private BigDecimal outstandingAmount;
+
+ @Version
+ private Long version;
+
+ public LoanInstallmentDelinquencyTag(DelinquencyRange delinquencyRange,
Loan loan, LoanRepaymentScheduleInstallment installment,
+ LocalDate addedOnDate, LocalDate liftedOnDate, LocalDate
firstOverdueDate, BigDecimal outstandingAmount) {
+ this.delinquencyRange = delinquencyRange;
+ this.loan = loan;
+ this.installment = installment;
+ this.addedOnDate = addedOnDate;
+ this.liftedOnDate = liftedOnDate;
+ this.firstOverdueDate = firstOverdueDate;
+ this.outstandingAmount = outstandingAmount;
+ }
+}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/domain/LoanInstallmentDelinquencyTagRepository.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/domain/LoanInstallmentDelinquencyTagRepository.java
new file mode 100644
index 000000000..5729fdec6
--- /dev/null
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/domain/LoanInstallmentDelinquencyTagRepository.java
@@ -0,0 +1,52 @@
+/**
+ * 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.domain;
+
+import java.util.List;
+import java.util.Optional;
+import
org.apache.fineract.portfolio.delinquency.data.LoanInstallmentDelinquencyTagData;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+
+public interface LoanInstallmentDelinquencyTagRepository
+ extends JpaRepository<LoanInstallmentDelinquencyTag, Long>,
JpaSpecificationExecutor<LoanInstallmentDelinquencyTag> {
+
+ Optional<LoanInstallmentDelinquencyTag> findByLoanAndInstallment(Loan
loan, LoanRepaymentScheduleInstallment installment);
+
+ @Query("select i from LoanInstallmentDelinquencyTag i where i.loan.id =
:loanId")
+ List<LoanInstallmentDelinquencyTag> findByLoanId(@Param("loanId") Long
loanId);
+
+ // Fetching Installment Delinquency range and outstanding amount
+ @Query("select i.delinquencyRange, i.outstandingAmount from
LoanInstallmentDelinquencyTag i where i.loan.id = :loanId")
+ List<LoanInstallmentDelinquencyTagData>
findInstallmentDelinquencyTags(@Param("loanId") Long loanId);
+
+ @Modifying(flushAutomatically = true)
+ @Query("delete from LoanInstallmentDelinquencyTag i where i.loan.id =
:loanId")
+ void deleteAllLoanInstallmentsTags(@Param("loanId") Long loanId);
+
+ @Modifying(flushAutomatically = true)
+ @Query("delete from LoanInstallmentDelinquencyTag i where i.id IN :tagIds")
+ void deleteAllLoanInstallmentsTagsByIds(@Param("tagIds") List<Long>
tagIds);
+
+}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformService.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformService.java
index b1c2dad11..9f3510cce 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformService.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformService.java
@@ -22,6 +22,7 @@ import java.util.Collection;
import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData;
import
org.apache.fineract.portfolio.delinquency.data.LoanDelinquencyTagHistoryData;
+import
org.apache.fineract.portfolio.delinquency.data.LoanInstallmentDelinquencyTagData;
import org.apache.fineract.portfolio.loanaccount.data.CollectionData;
public interface DelinquencyReadPlatformService {
@@ -40,4 +41,6 @@ public interface DelinquencyReadPlatformService {
CollectionData calculateLoanCollectionData(Long loanId);
+ Collection<LoanInstallmentDelinquencyTagData>
retrieveLoanInstallmentsCurrentDelinquencyTag(Long loanId);
+
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformServiceImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformServiceImpl.java
index c98b49327..7e1d9c145 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
@@ -25,12 +25,14 @@ import lombok.RequiredArgsConstructor;
import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData;
import
org.apache.fineract.portfolio.delinquency.data.LoanDelinquencyTagHistoryData;
+import
org.apache.fineract.portfolio.delinquency.data.LoanInstallmentDelinquencyTagData;
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket;
import
org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketRepository;
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.LoanInstallmentDelinquencyTagRepository;
import
org.apache.fineract.portfolio.delinquency.mapper.DelinquencyBucketMapper;
import org.apache.fineract.portfolio.delinquency.mapper.DelinquencyRangeMapper;
import
org.apache.fineract.portfolio.delinquency.mapper.LoanDelinquencyTagMapper;
@@ -52,6 +54,7 @@ public class DelinquencyReadPlatformServiceImpl implements
DelinquencyReadPlatfo
private final LoanDelinquencyTagMapper mapperLoanDelinquencyTagHistory;
private final LoanRepository loanRepository;
private final LoanDelinquencyDomainService loanDelinquencyDomainService;
+ private final LoanInstallmentDelinquencyTagRepository
repositoryLoanInstallmentDelinquencyTag;
@Override
public Collection<DelinquencyRangeData> retrieveAllDelinquencyRanges() {
@@ -126,4 +129,9 @@ public class DelinquencyReadPlatformServiceImpl implements
DelinquencyReadPlatfo
return collectionData;
}
+ @Override
+ public Collection<LoanInstallmentDelinquencyTagData>
retrieveLoanInstallmentsCurrentDelinquencyTag(Long loanId) {
+ return
repositoryLoanInstallmentDelinquencyTag.findInstallmentDelinquencyTags(loanId);
+ }
+
}
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 883a47808..0520c36d1 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
@@ -46,13 +46,17 @@ 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.delinquency.exception.DelinquencyBucketAgesOverlapedException;
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.LoanDelinquencyData;
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.loanproduct.domain.LoanProductRepository;
@@ -71,6 +75,7 @@ public class DelinquencyWritePlatformServiceImpl implements
DelinquencyWritePlat
private final LoanProductRepository loanProductRepository;
private final BusinessEventNotifierService businessEventNotifierService;
private final LoanDelinquencyDomainService loanDelinquencyDomainService;
+ private final LoanInstallmentDelinquencyTagRepository
loanInstallmentDelinquencyTagRepository;
@Override
public CommandProcessingResult createDelinquencyRange(JsonCommand command)
{
@@ -158,8 +163,17 @@ public class DelinquencyWritePlatformServiceImpl
implements DelinquencyWritePlat
final Loan loan =
this.loanRepository.findOneWithNotFoundDetection(loanId);
final DelinquencyBucket delinquencyBucket =
loan.getLoanProduct().getDelinquencyBucket();
if (delinquencyBucket != null) {
- final CollectionData collectionData =
loanDelinquencyDomainService.getOverdueCollectionData(loan);
+ final LoanDelinquencyData loanDelinquencyData =
loanDelinquencyDomainService.getLoanDelinquencyData(loan);
+ // loan delinquent data
+ final CollectionData collectionData =
loanDelinquencyData.getLoanCollectionData();
+ // loan installments delinquent data
+ final Map<Long, CollectionData> installmentsCollectionData =
loanDelinquencyData.getLoanInstallmentsCollectionData();
+ // delinquency for loan
changes = lookUpDelinquencyRange(loan, delinquencyBucket,
collectionData.getDelinquentDays());
+ // delinquency for installments
+ if (installmentsCollectionData.size() > 0) {
+ applyDelinquencyDetailsForLoanInstallments(loan,
delinquencyBucket, installmentsCollectionData);
+ }
}
return new
CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(loan.getId())
.withEntityExternalId(loan.getExternalId()).with(changes).build();
@@ -170,15 +184,27 @@ public class DelinquencyWritePlatformServiceImpl
implements DelinquencyWritePlat
final Loan loan = loanDelinquencyData.getLoan();
if (loan.hasDelinquencyBucket()) {
final DelinquencyBucket delinquencyBucket =
loan.getLoanProduct().getDelinquencyBucket();
- final CollectionData collectionData =
loanDelinquencyDomainService.getOverdueCollectionData(loan);
+ final LoanDelinquencyData loanDelinquentData =
loanDelinquencyDomainService.getLoanDelinquencyData(loan);
+ // loan delinquent data
+ final CollectionData collectionData =
loanDelinquentData.getLoanCollectionData();
+ // loan installments delinquent data
+ final Map<Long, CollectionData> installmentsCollectionData =
loanDelinquentData.getLoanInstallmentsCollectionData();
log.debug("Delinquency {}", collectionData);
+ // delinquency for loan
lookUpDelinquencyRange(loan, delinquencyBucket,
collectionData.getDelinquentDays());
+ // delinquency for installments
+ if (installmentsCollectionData.size() > 0) {
+ applyDelinquencyDetailsForLoanInstallments(loan,
delinquencyBucket, installmentsCollectionData);
+ }
}
}
@Override
public void removeDelinquencyTagToLoan(final Loan loan) {
setLoanDelinquencyTag(loan, null);
+ if (loan.isEnableInstallmentLevelDelinquency()) {
+ cleanLoanInstallmentsDelinquencyTags(loan);
+ }
}
@Override
@@ -382,4 +408,107 @@ public class DelinquencyWritePlatformServiceImpl
implements DelinquencyWritePlat
return ranges;
}
+ private void applyDelinquencyDetailsForLoanInstallments(final Loan loan,
final DelinquencyBucket delinquencyBucket,
+ final Map<Long, CollectionData> installmentsCollectionData) {
+ for (LoanRepaymentScheduleInstallment installment :
loan.getRepaymentScheduleInstallments()) {
+ if (installmentsCollectionData.containsKey(installment.getId())) {
+ setInstallmentDelinquencyDetails(loan, installment,
delinquencyBucket, installmentsCollectionData.get(installment.getId()));
+ }
+ }
+ // remove tags for non existing installments that got deleted due to
re-schedule
+ removeDelinquencyTagsForNonExistingInstallments(loan.getId());
+ }
+
+ private void setInstallmentDelinquencyDetails(final Loan loan, final
LoanRepaymentScheduleInstallment installment,
+ final DelinquencyBucket delinquencyBucket, final CollectionData
installmentDelinquencyData) {
+ DelinquencyRange delinquencyRangeForInstallment =
getInstallmentDelinquencyRange(delinquencyBucket,
+ installmentDelinquencyData.getDelinquentDays());
+ 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 void setDelinquencyDetailsForInstallment(final Loan loan, final
LoanRepaymentScheduleInstallment installment,
+ CollectionData installmentDelinquencyData, final DelinquencyRange
delinquencyRangeForInstallment) {
+ List<LoanInstallmentDelinquencyTag> installmentDelinquencyTags = new
ArrayList<>();
+ LocalDate delinquencyCalculationDate =
DateUtils.getBusinessLocalDate();
+
+ 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);
+ }
+ } 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
+ }
+ } else {
+ // add new range, first time delinquent
+ installmentDelinquency = new
LoanInstallmentDelinquencyTag(delinquencyRangeForInstallment, loan, installment,
+ delinquencyCalculationDate, null,
installmentDelinquencyData.getDelinquentDate(),
+ installmentDelinquencyData.getDelinquentAmount());
+ // event installment delinquent
+ }
+
+ if (installmentDelinquency != null) {
+ installmentDelinquencyTags.add(installmentDelinquency);
+ }
+
+ }
+
+ if (installmentDelinquencyTags.size() > 0) {
+
loanInstallmentDelinquencyTagRepository.saveAllAndFlush(installmentDelinquencyTags);
+ }
+
+ }
+
+ private void cleanLoanInstallmentsDelinquencyTags(Loan loan) {
+
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);
+ }
+ }
+ }
}
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
index 5cd19cb5f..656ceb6b7 100644
---
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
@@ -19,6 +19,7 @@
package org.apache.fineract.portfolio.delinquency.service;
import org.apache.fineract.portfolio.loanaccount.data.CollectionData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanDelinquencyData;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
public interface LoanDelinquencyDomainService {
@@ -31,4 +32,6 @@ public interface LoanDelinquencyDomainService {
*/
CollectionData getOverdueCollectionData(Loan loan);
+ LoanDelinquencyData getLoanDelinquencyData(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
index 343eeabdb..67085cd65 100644
---
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
@@ -20,12 +20,15 @@ package org.apache.fineract.portfolio.delinquency.service;
import java.math.BigDecimal;
import java.time.LocalDate;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
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.data.LoanDelinquencyData;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
@@ -42,15 +45,10 @@ public class LoanDelinquencyDomainServiceImpl implements
LoanDelinquencyDomainSe
final MonetaryCurrency loanCurrency = loan.getCurrency();
LocalDate overdueSinceDate = null;
CollectionData collectionData = CollectionData.template();
- BigDecimal amountAvailable;
BigDecimal outstandingAmount = BigDecimal.ZERO;
boolean oldestOverdueInstallment = false;
boolean overdueSinceDateWasSet = false;
boolean firstNotYetDueInstallment = false;
- LoanRepaymentScheduleInstallment latestInstallment =
loan.getLastLoanRepaymentScheduleInstallment();
-
- List<LoanTransaction> chargebackTransactions =
loan.getLoanTransactions(LoanTransaction::isChargeback);
-
log.debug("Loan id {} with {} installments", loan.getId(),
loan.getRepaymentScheduleInstallments().size());
// Get the oldest overdue installment if exists one
for (LoanRepaymentScheduleInstallment installment :
loan.getRepaymentScheduleInstallments()) {
@@ -61,61 +59,90 @@ public class LoanDelinquencyDomainServiceImpl implements
LoanDelinquencyDomainSe
outstandingAmount =
outstandingAmount.add(installment.getTotalOutstanding(loanCurrency).getAmount());
if (!oldestOverdueInstallment) {
log.debug("Oldest installment {} {}",
installment.getInstallmentNumber(), installment.getDueDate());
+ CollectionData overDueInstallmentDelinquentData =
calculateDelinquencyDataForOverdueInstallment(loan, installment);
+ overdueSinceDate =
overDueInstallmentDelinquentData.getDelinquentDate();
oldestOverdueInstallment = true;
- overdueSinceDate = installment.getDueDate();
overdueSinceDateWasSet = true;
-
- amountAvailable =
installment.getTotalPaid(loanCurrency).getAmount();
-
- boolean isLatestInstallment =
Objects.equals(installment.getId(), latestInstallment.getId());
- for (LoanTransaction loanTransaction :
chargebackTransactions) {
- boolean
isLoanTransactionIsOnOrAfterInstallmentFromDate = DateUtils
-
.isEqual(loanTransaction.getTransactionDate(), installment.getFromDate())
- ||
DateUtils.isAfter(loanTransaction.getTransactionDate(),
installment.getFromDate());
- boolean
isLoanTransactionIsBeforeNotLastInstallmentDueDate = !isLatestInstallment
- &&
DateUtils.isBefore(loanTransaction.getTransactionDate(),
installment.getDueDate());
- boolean
isLoanTransactionIsOnOrBeforeLastInstallmentDueDate = isLatestInstallment
- &&
!DateUtils.isAfter(loanTransaction.getTransactionDate(),
installment.getDueDate());
- if
(isLoanTransactionIsOnOrAfterInstallmentFromDate &&
(isLoanTransactionIsBeforeNotLastInstallmentDueDate
- ||
isLoanTransactionIsOnOrBeforeLastInstallmentDueDate)) {
- 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 (LoanTransaction loanTransaction :
chargebackTransactions) {
- boolean
isLoanTransactionIsOnOrAfterInstallmentFromDate =
!DateUtils.isBefore(loanTransaction.getTransactionDate(),
- installment.getFromDate());
- boolean isLoanTransactionIsBeforeInstallmentDueDate =
DateUtils.isBefore(loanTransaction.getTransactionDate(),
- installment.getDueDate());
- boolean isLoanTransactionIsBeforeBusinessDate =
DateUtils.isBefore(loanTransaction.getTransactionDate(),
- businessDate);
- if (isLoanTransactionIsOnOrAfterInstallmentFromDate &&
isLoanTransactionIsBeforeInstallmentDueDate
- && isLoanTransactionIsBeforeBusinessDate) {
- 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;
- }
- }
+ CollectionData nonOverDueInstallmentDelinquentData =
calculateDelinquencyDataForNonOverdueInstallment(loan,
+ installment);
+ outstandingAmount =
outstandingAmount.add(nonOverDueInstallmentDelinquentData.getDelinquentAmount());
+ if (!overdueSinceDateWasSet) {
+ overdueSinceDate =
nonOverDueInstallmentDelinquentData.getDelinquentDate();
+ overdueSinceDateWasSet = true;
}
+ }
+ }
+ }
+
+ Integer graceDays = 0;
+ if (loan.getLoanProductRelatedDetail().getGraceOnArrearsAgeing() !=
null) {
+ graceDays =
loan.getLoanProductRelatedDetail().getGraceOnArrearsAgeing();
+ }
+ log.debug("Loan id {} with overdue since date {} and outstanding
amount {}", loan.getId(), overdueSinceDate, outstandingAmount);
- if (amountAvailable.compareTo(BigDecimal.ZERO) < 0) {
- outstandingAmount =
outstandingAmount.add(amountAvailable.abs());
+ Long overdueDays = 0L;
+ if (overdueSinceDate != null) {
+ overdueDays = DateUtils.getDifferenceInDays(overdueSinceDate,
businessDate);
+ if (overdueDays < 0) {
+ overdueDays = 0L;
+ }
+ collectionData.setPastDueDays(overdueDays);
+ overdueSinceDate =
overdueSinceDate.plusDays(graceDays.longValue());
+ collectionData.setDelinquentDate(overdueSinceDate);
+ }
+ collectionData.setDelinquentAmount(outstandingAmount);
+ collectionData.setDelinquentDays(0L);
+ Long delinquentDays = overdueDays - graceDays;
+ if (delinquentDays > 0) {
+ collectionData.setDelinquentDays(delinquentDays);
+ }
+
+ log.debug("Result: {}", collectionData.toString());
+ return collectionData;
+ }
+
+ @Override
+ public LoanDelinquencyData getLoanDelinquencyData(final Loan loan) {
+
+ final LocalDate businessDate = DateUtils.getBusinessLocalDate();
+ LocalDate overdueSinceDate = null;
+ CollectionData collectionData = CollectionData.template();
+ Map<Long, CollectionData> loanInstallmentsCollectionData = new
HashMap<>();
+ BigDecimal outstandingAmount = BigDecimal.ZERO;
+ boolean oldestOverdueInstallment = false;
+ boolean overdueSinceDateWasSet = false;
+ boolean firstNotYetDueInstallment = false;
+ log.debug("Loan id {} with {} installments", loan.getId(),
loan.getRepaymentScheduleInstallments().size());
+ for (LoanRepaymentScheduleInstallment installment :
loan.getRepaymentScheduleInstallments()) {
+ CollectionData installmentCollectionData =
CollectionData.template();
+ if (!installment.isObligationsMet()) {
+ installmentCollectionData =
getInstallmentOverdueCollectionData(loan, installment);
+ outstandingAmount =
outstandingAmount.add(installmentCollectionData.getDelinquentAmount());
+ // Get the oldest overdue installment if exists
+ if (DateUtils.isBefore(installment.getDueDate(),
businessDate)) {
+ if (!oldestOverdueInstallment) {
+ overdueSinceDate =
installmentCollectionData.getDelinquentDate();
+ oldestOverdueInstallment = true;
+ overdueSinceDateWasSet = true;
+ }
+ } else if (!firstNotYetDueInstallment) {
+ firstNotYetDueInstallment = true;
+ if (!overdueSinceDateWasSet) {
+ overdueSinceDate =
installmentCollectionData.getDelinquentDate();
+ overdueSinceDateWasSet = true;
}
}
}
+ // if installment level delinquency enabled add delinquency data
for installment
+ if (loan.isEnableInstallmentLevelDelinquency()) {
+ loanInstallmentsCollectionData.put(installment.getId(),
installmentCollectionData);
+ }
+
}
Integer graceDays = 0;
@@ -140,8 +167,116 @@ public class LoanDelinquencyDomainServiceImpl implements
LoanDelinquencyDomainSe
if (delinquentDays > 0) {
collectionData.setDelinquentDays(delinquentDays);
}
+ return new LoanDelinquencyData(collectionData,
loanInstallmentsCollectionData);
+ }
- log.debug("Result: {}", collectionData.toString());
+ private CollectionData getInstallmentOverdueCollectionData(final Loan
loan, final LoanRepaymentScheduleInstallment installment) {
+ final LocalDate businessDate = DateUtils.getBusinessLocalDate();
+ LocalDate overdueSinceDate = null;
+ CollectionData collectionData = CollectionData.template();
+ BigDecimal outstandingAmount = BigDecimal.ZERO;
+ if (DateUtils.isBefore(installment.getDueDate(), businessDate)) {
+ // checking overdue installment delinquency data
+ CollectionData overDueInstallmentDelinquentData =
calculateDelinquencyDataForOverdueInstallment(loan, installment);
+ outstandingAmount =
outstandingAmount.add(overDueInstallmentDelinquentData.getDelinquentAmount());
+ overdueSinceDate =
overDueInstallmentDelinquentData.getDelinquentDate();
+
+ } else {
+ // checking non overdue installment for chargeback transactions
before installment due date and before
+ // business date
+ CollectionData nonOverDueInstallmentDelinquentData =
calculateDelinquencyDataForNonOverdueInstallment(loan, installment);
+ outstandingAmount =
outstandingAmount.add(nonOverDueInstallmentDelinquentData.getDelinquentAmount());
+ overdueSinceDate =
nonOverDueInstallmentDelinquentData.getDelinquentDate();
+ }
+
+ // Grace days are not considered for installment level delinquency
calculation currently.
+
+ Long overdueDays = 0L;
+ if (overdueSinceDate != null) {
+ // TODO : Changes for considering paused delinquency days for
overdue days calculation
+ overdueDays = DateUtils.getDifferenceInDays(overdueSinceDate,
businessDate);
+ if (overdueDays < 0) {
+ overdueDays = 0L;
+ }
+ collectionData.setPastDueDays(overdueDays);
+ collectionData.setDelinquentDate(overdueSinceDate);
+ }
+ collectionData.setDelinquentAmount(outstandingAmount);
+ collectionData.setDelinquentDays(0L);
+ Long delinquentDays = overdueDays;
+ if (delinquentDays > 0) {
+ collectionData.setDelinquentDays(delinquentDays);
+ }
+ return collectionData;
+
+ }
+
+ private CollectionData calculateDelinquencyDataForOverdueInstallment(final
Loan loan,
+ final LoanRepaymentScheduleInstallment installment) {
+ final MonetaryCurrency loanCurrency = loan.getCurrency();
+ LoanRepaymentScheduleInstallment latestInstallment =
loan.getLastLoanRepaymentScheduleInstallment();
+ List<LoanTransaction> chargebackTransactions =
loan.getLoanTransactions(LoanTransaction::isChargeback);
+ LocalDate overdueSinceDate = null;
+ CollectionData collectionData = CollectionData.template();
+ BigDecimal outstandingAmount = BigDecimal.ZERO;
+
+ outstandingAmount =
outstandingAmount.add(installment.getTotalOutstanding(loanCurrency).getAmount());
+ overdueSinceDate = installment.getDueDate();
+ BigDecimal amountAvailable =
installment.getTotalPaid(loanCurrency).getAmount();
+ boolean isLatestInstallment = Objects.equals(installment.getId(),
latestInstallment.getId());
+ for (LoanTransaction loanTransaction : chargebackTransactions) {
+ boolean isLoanTransactionIsOnOrAfterInstallmentFromDate =
DateUtils.isEqual(loanTransaction.getTransactionDate(),
+ installment.getFromDate()) ||
DateUtils.isAfter(loanTransaction.getTransactionDate(),
installment.getFromDate());
+ boolean isLoanTransactionIsBeforeNotLastInstallmentDueDate =
!isLatestInstallment
+ &&
DateUtils.isBefore(loanTransaction.getTransactionDate(),
installment.getDueDate());
+ boolean isLoanTransactionIsOnOrBeforeLastInstallmentDueDate =
isLatestInstallment
+ &&
(DateUtils.isEqual(loanTransaction.getTransactionDate(),
installment.getDueDate())
+ ||
DateUtils.isBefore(loanTransaction.getTransactionDate(),
installment.getDueDate()));
+ if (isLoanTransactionIsOnOrAfterInstallmentFromDate
+ && (isLoanTransactionIsBeforeNotLastInstallmentDueDate ||
isLoanTransactionIsOnOrBeforeLastInstallmentDueDate)) {
+ amountAvailable =
amountAvailable.subtract(loanTransaction.getAmount());
+ if (amountAvailable.compareTo(BigDecimal.ZERO) < 0) {
+ overdueSinceDate = loanTransaction.getTransactionDate();
+ break;
+ }
+ }
+ }
+ collectionData.setDelinquentDate(overdueSinceDate);
+ collectionData.setDelinquentAmount(outstandingAmount);
+ return collectionData;
+ }
+
+ private CollectionData
calculateDelinquencyDataForNonOverdueInstallment(final Loan loan,
+ final LoanRepaymentScheduleInstallment installment) {
+ final LocalDate businessDate = DateUtils.getBusinessLocalDate();
+ final MonetaryCurrency loanCurrency = loan.getCurrency();
+
+ LocalDate overdueSinceDate = null;
+ CollectionData collectionData = CollectionData.template();
+ BigDecimal outstandingAmount = BigDecimal.ZERO;
+
+ List<LoanTransaction> chargebackTransactions =
loan.getLoanTransactions(LoanTransaction::isChargeback);
+ BigDecimal amountAvailable =
installment.getTotalPaid(loanCurrency).getAmount();
+ for (LoanTransaction loanTransaction : chargebackTransactions) {
+
+ boolean isLoanTransactionIsOnOrAfterInstallmentFromDate =
DateUtils.isEqual(loanTransaction.getTransactionDate(),
+ installment.getFromDate()) ||
DateUtils.isAfter(loanTransaction.getTransactionDate(),
installment.getFromDate());
+ boolean isLoanTransactionIsBeforeInstallmentDueDate =
DateUtils.isBefore(loanTransaction.getTransactionDate(),
+ installment.getDueDate());
+ boolean isLoanTransactionIsBeforeBusinessDate =
DateUtils.isBefore(loanTransaction.getTransactionDate(), businessDate);
+ if (isLoanTransactionIsOnOrAfterInstallmentFromDate &&
isLoanTransactionIsBeforeInstallmentDueDate
+ && isLoanTransactionIsBeforeBusinessDate) {
+ amountAvailable =
amountAvailable.subtract(loanTransaction.getAmount());
+ if (amountAvailable.compareTo(BigDecimal.ZERO) < 0) {
+ overdueSinceDate = loanTransaction.getTransactionDate();
+ }
+ }
+ }
+ if (amountAvailable.compareTo(BigDecimal.ZERO) < 0) {
+ outstandingAmount = outstandingAmount.add(amountAvailable.abs());
+ }
+ collectionData.setDelinquentDate(overdueSinceDate);
+ collectionData.setDelinquentAmount(outstandingAmount);
return collectionData;
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/starter/DelinquencyConfiguration.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/starter/DelinquencyConfiguration.java
index 40131394b..88c27afa8 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/starter/DelinquencyConfiguration.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/starter/DelinquencyConfiguration.java
@@ -23,6 +23,7 @@ import
org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketMapping
import
org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketRepository;
import
org.apache.fineract.portfolio.delinquency.domain.DelinquencyRangeRepository;
import
org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyTagHistoryRepository;
+import
org.apache.fineract.portfolio.delinquency.domain.LoanInstallmentDelinquencyTagRepository;
import
org.apache.fineract.portfolio.delinquency.mapper.DelinquencyBucketMapper;
import org.apache.fineract.portfolio.delinquency.mapper.DelinquencyRangeMapper;
import
org.apache.fineract.portfolio.delinquency.mapper.LoanDelinquencyTagMapper;
@@ -50,9 +51,11 @@ public class DelinquencyConfiguration {
DelinquencyBucketRepository repositoryBucket,
LoanDelinquencyTagHistoryRepository repositoryLoanDelinquencyTagHistory,
DelinquencyRangeMapper mapperRange, DelinquencyBucketMapper
mapperBucket,
LoanDelinquencyTagMapper mapperLoanDelinquencyTagHistory,
LoanRepository loanRepository,
- LoanDelinquencyDomainService loanDelinquencyDomainService) {
+ LoanDelinquencyDomainService loanDelinquencyDomainService,
+ LoanInstallmentDelinquencyTagRepository
repositoryLoanInstallmentDelinquencyTag) {
return new DelinquencyReadPlatformServiceImpl(repositoryRange,
repositoryBucket, repositoryLoanDelinquencyTagHistory, mapperRange,
- mapperBucket, mapperLoanDelinquencyTagHistory, loanRepository,
loanDelinquencyDomainService);
+ mapperBucket, mapperLoanDelinquencyTagHistory, loanRepository,
loanDelinquencyDomainService,
+ repositoryLoanInstallmentDelinquencyTag);
}
@Bean
@@ -62,10 +65,11 @@ public class DelinquencyConfiguration {
DelinquencyBucketRepository repositoryBucket,
DelinquencyBucketMappingsRepository repositoryBucketMappings,
LoanDelinquencyTagHistoryRepository loanDelinquencyTagRepository,
LoanRepositoryWrapper loanRepository,
LoanProductRepository loanProductRepository,
BusinessEventNotifierService businessEventNotifierService,
- LoanDelinquencyDomainService loanDelinquencyDomainService) {
+ LoanDelinquencyDomainService loanDelinquencyDomainService,
+ LoanInstallmentDelinquencyTagRepository
loanInstallmentDelinquencyTagRepository) {
return new DelinquencyWritePlatformServiceImpl(dataValidatorBucket,
dataValidatorRange, repositoryRange, repositoryBucket,
repositoryBucketMappings, loanDelinquencyTagRepository,
loanRepository, loanProductRepository, businessEventNotifierService,
- loanDelinquencyDomainService);
+ loanDelinquencyDomainService,
loanInstallmentDelinquencyTagRepository);
}
@Bean
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/loanaccount/data/LoanDelinquencyData.java
similarity index 62%
copy from
fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/LoanDelinquencyDomainService.java
copy to
fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanDelinquencyData.java
index 5cd19cb5f..3efaea390 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/LoanDelinquencyDomainService.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanDelinquencyData.java
@@ -16,19 +16,20 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.fineract.portfolio.delinquency.service;
+package org.apache.fineract.portfolio.loanaccount.data;
-import org.apache.fineract.portfolio.loanaccount.data.CollectionData;
-import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import java.util.Map;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
-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);
+@AllArgsConstructor
+@ToString
+@Getter
+@Setter
+public class LoanDelinquencyData {
+ private CollectionData loanCollectionData;
+ private Map<Long, CollectionData> loanInstallmentsCollectionData;
}
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 866c87d4a..ab85206e2 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
@@ -19,6 +19,7 @@
package org.apache.fineract.portfolio.deliquency;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyIterable;
import static org.mockito.Mockito.times;
@@ -30,6 +31,7 @@ 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 java.util.Optional;
@@ -46,13 +48,17 @@ import
org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketReposit
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.domain.LoanInstallmentDelinquencyTag;
+import
org.apache.fineract.portfolio.delinquency.domain.LoanInstallmentDelinquencyTagRepository;
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.LoanDelinquencyData;
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.loanproduct.domain.LoanProduct;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
@@ -88,6 +94,9 @@ public class
DelinquencyWritePlatformServiceRangeChangeEventTest {
private BusinessEventNotifierService businessEventNotifierService;
@Mock
private LoanDelinquencyDomainService loanDelinquencyDomainService;
+ @Mock
+ private LoanInstallmentDelinquencyTagRepository
loanInstallmentDelinquencyTagRepository;
+
@InjectMocks
private DelinquencyWritePlatformServiceImpl underTest;
@@ -120,11 +129,15 @@ public class
DelinquencyWritePlatformServiceRangeChangeEventTest {
CollectionData collectionData = new CollectionData(BigDecimal.ZERO,
2L, null, 2L, overDueSinceDate, BigDecimal.ZERO, null, null,
null, null);
+ Map<Long, CollectionData> installmentsCollection = new HashMap<>();
+
+ LoanDelinquencyData loanDelinquencyData = new
LoanDelinquencyData(collectionData, installmentsCollection);
+
when(loanForProcessing.getLoanProduct()).thenReturn(loanProduct);
when(loanProduct.getDelinquencyBucket()).thenReturn(delinquencyBucket);
when(loanForProcessing.hasDelinquencyBucket()).thenReturn(true);
when(loanDelinquencyTagRepository.findByLoanAndLiftedOnDate(any(),
any())).thenReturn(Optional.empty());
-
when(loanDelinquencyDomainService.getOverdueCollectionData(loanForProcessing)).thenReturn(collectionData);
+
when(loanDelinquencyDomainService.getLoanDelinquencyData(loanForProcessing)).thenReturn(loanDelinquencyData);
// when
underTest.applyDelinquencyTagToLoan(loanScheduleDelinquencyData);
@@ -155,11 +168,17 @@ public class
DelinquencyWritePlatformServiceRangeChangeEventTest {
LoanScheduleDelinquencyData loanScheduleDelinquencyData = new
LoanScheduleDelinquencyData(1L, overDueSinceDate, 2L,
loanForProcessing);
+ CollectionData collectionData = CollectionData.template();
+
+ Map<Long, CollectionData> installmentsCollection = new HashMap<>();
+
+ LoanDelinquencyData loanDelinquencyData = new
LoanDelinquencyData(collectionData, installmentsCollection);
+
when(loanForProcessing.getLoanProduct()).thenReturn(loanProduct);
when(loanProduct.getDelinquencyBucket()).thenReturn(delinquencyBucket);
when(loanForProcessing.hasDelinquencyBucket()).thenReturn(true);
when(loanDelinquencyTagRepository.findByLoanAndLiftedOnDate(any(),
any())).thenReturn(Optional.empty());
-
when(loanDelinquencyDomainService.getOverdueCollectionData(loanForProcessing)).thenReturn(CollectionData.template());
+
when(loanDelinquencyDomainService.getLoanDelinquencyData(loanForProcessing)).thenReturn(loanDelinquencyData);
// when
underTest.applyDelinquencyTagToLoan(loanScheduleDelinquencyData);
@@ -189,4 +208,142 @@ public class
DelinquencyWritePlatformServiceRangeChangeEventTest {
}
+ @Test
+ public void
givenLoanAccountWithOverdueInstallmentAndEnableInstallmentThenDelinquencyRangeIsSetForInstallmentTest()
{
+ ArgumentCaptor<List<LoanInstallmentDelinquencyTag>>
loanInstallmentDelinquencyTagsArgumentCaptor = ArgumentCaptor
+ .forClass(List.class);
+ // given
+ 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);
+
+ CollectionData installmentCollectionData = new
CollectionData(BigDecimal.ZERO, 2L, null, 2L, overDueSinceDate,
+ installmentPrincipalAmount, 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(loanDelinquencyTagRepository.findByLoanAndLiftedOnDate(any(),
any())).thenReturn(Optional.empty());
+
when(loanDelinquencyDomainService.getLoanDelinquencyData(loanForProcessing)).thenReturn(loanDelinquencyData);
+
when(loanInstallmentDelinquencyTagRepository.findByLoanAndInstallment(loanForProcessing,
repaymentScheduleInstallments.get(0)))
+ .thenReturn(Optional.empty());
+
+ // when
+ underTest.applyDelinquencyTagToLoan(loanScheduleDelinquencyData);
+
+ // then
+ verify(loanDelinquencyTagRepository,
times(1)).saveAllAndFlush(anyIterable());
+ verify(loanInstallmentDelinquencyTagRepository,
times(1)).saveAllAndFlush(loanInstallmentDelinquencyTagsArgumentCaptor.capture());
+
+ List<LoanInstallmentDelinquencyTag> installmentDelinquencyTags =
loanInstallmentDelinquencyTagsArgumentCaptor.getValue();
+ assertEquals(1, installmentDelinquencyTags.size());
+ assertEquals(1,
installmentDelinquencyTags.get(0).getInstallment().getInstallmentNumber());
+ assertEquals(1,
installmentDelinquencyTags.get(0).getDelinquencyRange().getId());
+ assertEquals(installmentPrincipalAmount,
installmentDelinquencyTags.get(0).getOutstandingAmount());
+ }
+
+ @Test
+ public void
givenLoanAccountWithOverdueInstallmentAndEnableInstallmentThenDelinquencyRangeChangesForInstallmentTest()
{
+ ArgumentCaptor<List<LoanInstallmentDelinquencyTag>>
loanInstallmentDelinquencyTagsArgumentCaptor = ArgumentCaptor
+ .forClass(List.class);
+ ArgumentCaptor<LoanInstallmentDelinquencyTag>
loanInstallmentDelinquencyTagArgumentCaptorForDelete = ArgumentCaptor
+ .forClass(LoanInstallmentDelinquencyTag.class);
+ // given
+ 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(29);
+ LoanScheduleDelinquencyData loanScheduleDelinquencyData = new
LoanScheduleDelinquencyData(1L, overDueSinceDate, 1L,
+ loanForProcessing);
+ CollectionData collectionData = new CollectionData(BigDecimal.ZERO,
29L, null, 29L, overDueSinceDate, BigDecimal.ZERO, null, null,
+ null, null);
+
+ CollectionData installmentCollectionData = new
CollectionData(BigDecimal.ZERO, 29L, null, 29L, overDueSinceDate,
+ installmentPrincipalAmount, null, null, null, null);
+
+ Map<Long, CollectionData> installmentsCollection = new HashMap<>();
+ installmentsCollection.put(1L, installmentCollectionData);
+
+ LoanDelinquencyData loanDelinquencyData = new
LoanDelinquencyData(collectionData, installmentsCollection);
+
+ LoanInstallmentDelinquencyTag previousInstallmentTag = new
LoanInstallmentDelinquencyTag();
+ previousInstallmentTag.setDelinquencyRange(range1);
+
+ when(loanForProcessing.getLoanProduct()).thenReturn(loanProduct);
+ when(loanProduct.getDelinquencyBucket()).thenReturn(delinquencyBucket);
+ when(loanForProcessing.hasDelinquencyBucket()).thenReturn(true);
+
when(loanForProcessing.getRepaymentScheduleInstallments()).thenReturn(repaymentScheduleInstallments);
+ when(loanDelinquencyTagRepository.findByLoanAndLiftedOnDate(any(),
any())).thenReturn(Optional.empty());
+
when(loanDelinquencyDomainService.getLoanDelinquencyData(loanForProcessing)).thenReturn(loanDelinquencyData);
+
when(loanInstallmentDelinquencyTagRepository.findByLoanAndInstallment(loanForProcessing,
repaymentScheduleInstallments.get(0)))
+ .thenReturn(Optional.of(previousInstallmentTag));
+
+ // when
+ underTest.applyDelinquencyTagToLoan(loanScheduleDelinquencyData);
+
+ // then
+ verify(loanDelinquencyTagRepository,
times(1)).saveAllAndFlush(anyIterable());
+ verify(loanInstallmentDelinquencyTagRepository,
times(1)).saveAllAndFlush(loanInstallmentDelinquencyTagsArgumentCaptor.capture());
+ verify(loanInstallmentDelinquencyTagRepository,
times(1)).delete(loanInstallmentDelinquencyTagArgumentCaptorForDelete.capture());
+
+ List<LoanInstallmentDelinquencyTag> installmentDelinquencyTags =
loanInstallmentDelinquencyTagsArgumentCaptor.getValue();
+ assertEquals(1, installmentDelinquencyTags.size());
+ assertEquals(1,
installmentDelinquencyTags.get(0).getInstallment().getInstallmentNumber());
+ assertEquals(2,
installmentDelinquencyTags.get(0).getDelinquencyRange().getId());
+ assertEquals(installmentPrincipalAmount,
installmentDelinquencyTags.get(0).getOutstandingAmount());
+
+ LoanInstallmentDelinquencyTag deletedInstallmentDelinquencyTag =
loanInstallmentDelinquencyTagArgumentCaptorForDelete.getValue();
+ assertNotNull(deletedInstallmentDelinquencyTag);
+ assertEquals(previousInstallmentTag, deletedInstallmentDelinquencyTag);
+
+ }
}
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
index c75580724..047f7a08d 100644
---
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
@@ -19,6 +19,7 @@
package org.apache.fineract.portfolio.deliquency;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.Mockito.when;
import java.math.BigDecimal;
@@ -43,6 +44,7 @@ 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.data.LoanDelinquencyData;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
@@ -185,4 +187,95 @@ public class LoanDelinquencyDomainServiceTest {
}
+ @Test
+ public void
givenLoanInstallmentWithOverdueEnableInstallmentDelinquencyThenCalculateDelinquentData()
{
+ // given
+ final Long daysDiff = 2L;
+ final LocalDate fromDate =
businessDate.minusMonths(1).minusDays(daysDiff);
+ final LocalDate dueDate = businessDate.minusDays(daysDiff);
+
+ LoanRepaymentScheduleInstallment installment = new
LoanRepaymentScheduleInstallment(loan, 1, fromDate, dueDate, principal,
+ zeroAmount, zeroAmount, zeroAmount, false, new HashSet<>(),
zeroAmount);
+ installment.setId(1L);
+ List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments =
Arrays.asList(installment);
+
+ // when
+ when(loanProductRelatedDetail.getGraceOnArrearsAgeing()).thenReturn(0);
+
when(loan.getLoanProductRelatedDetail()).thenReturn(loanProductRelatedDetail);
+
when(loan.getRepaymentScheduleInstallments()).thenReturn(repaymentScheduleInstallments);
+
when(loan.getLoanTransactions(Mockito.any(Predicate.class))).thenReturn(Collections.emptyList());
+
when(loan.getLastLoanRepaymentScheduleInstallment()).thenReturn(repaymentScheduleInstallments.get(0));
+ when(loan.getCurrency()).thenReturn(currency);
+ when(loan.isEnableInstallmentLevelDelinquency()).thenReturn(true);
+
+ LoanDelinquencyData collectionData =
underTest.getLoanDelinquencyData(loan);
+
+ // then
+ assertNotNull(collectionData);
+ assertNotNull(collectionData.getLoanInstallmentsCollectionData());
+ assertEquals(1L,
collectionData.getLoanInstallmentsCollectionData().size());
+
+ CollectionData loanCollectionData =
collectionData.getLoanCollectionData();
+ CollectionData installmentCollectionData =
collectionData.getLoanInstallmentsCollectionData().get(1L);
+
+ assertEquals(daysDiff, loanCollectionData.getDelinquentDays());
+ assertEquals(dueDate, loanCollectionData.getDelinquentDate());
+ assertEquals(loanCollectionData.getDelinquentDays(),
loanCollectionData.getPastDueDays());
+
+ assertEquals(daysDiff, installmentCollectionData.getDelinquentDays());
+ assertEquals(dueDate, installmentCollectionData.getDelinquentDate());
+ assertEquals(installmentCollectionData.getDelinquentDays(),
installmentCollectionData.getPastDueDays());
+
+ }
+
+ @Test
+ public void
givenLoanInstallmentWithoutOverdueWithChargebackAndEnableInstallmentDelinquencyThenCalculateDelinquentData()
{
+
+ // 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);
+ installment.setId(1L);
+ LoanTransaction loanTransaction = LoanTransaction.chargeback(loan,
Money.of(currency, principal), paymentDetail, transactionDate,
+ null);
+
installment.getLoanTransactionToRepaymentScheduleMappings().add(LoanTransactionToRepaymentScheduleMapping
+ .createFrom(loanTransaction, installment, zeroMoney,
zeroMoney, zeroMoney, zeroMoney));
+
+ List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments =
Arrays.asList(installment);
+ // when
+ when(loanProductRelatedDetail.getGraceOnArrearsAgeing()).thenReturn(0);
+
when(loan.getLoanProductRelatedDetail()).thenReturn(loanProductRelatedDetail);
+
when(loan.getRepaymentScheduleInstallments()).thenReturn(repaymentScheduleInstallments);
+ when(loan.isEnableInstallmentLevelDelinquency()).thenReturn(true);
+ when(loan.getCurrency()).thenReturn(currency);
+
when(loan.getLoanTransactions(Mockito.any(Predicate.class))).thenReturn(Arrays.asList(loanTransaction));
+
+ LoanDelinquencyData collectionData =
underTest.getLoanDelinquencyData(loan);
+
+ // then
+ assertNotNull(collectionData);
+ assertNotNull(collectionData.getLoanInstallmentsCollectionData());
+ assertEquals(1L,
collectionData.getLoanInstallmentsCollectionData().size());
+
+ CollectionData loanCollectionData =
collectionData.getLoanCollectionData();
+ CollectionData installmentCollectionData =
collectionData.getLoanInstallmentsCollectionData().get(1L);
+
+ assertEquals(daysDiff, loanCollectionData.getDelinquentDays());
+ assertEquals(transactionDate, loanCollectionData.getDelinquentDate());
+ assertEquals(loanCollectionData.getDelinquentDays(),
loanCollectionData.getPastDueDays());
+
+ // then
+ assertEquals(daysDiff, installmentCollectionData.getDelinquentDays());
+ assertEquals(transactionDate,
installmentCollectionData.getDelinquentDate());
+ assertEquals(installmentCollectionData.getDelinquentDays(),
installmentCollectionData.getPastDueDays());
+ assertEquals(0,
principal.compareTo(installmentCollectionData.getDelinquentAmount()));
+
+ }
+
}