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 48300cd19 FINERACT-1992: Multi delinquency implication on events
48300cd19 is described below

commit 48300cd19dc0c9cdf78e8b8fc0125f815859a930
Author: Ruchi Dhamankar <[email protected]>
AuthorDate: Tue Nov 21 14:00:02 2023 +0530

    FINERACT-1992: Multi delinquency implication on events
---
 .../loan/v1/LoanAccountDelinquencyRangeDataV1.avsc |   7 +
 ...=> LoanInstallmentDelinquencyBucketDataV1.avsc} |  36 +----
 ...inquencyRangeChangeBusinessEventSerializer.java |  86 +++++++++++-
 .../data/LoanInstallmentDelinquencyTagData.java    |   2 +
 .../LoanInstallmentDelinquencyTagRepository.java   |   2 +-
 .../DelinquencyWritePlatformServiceImpl.java       |  13 +-
 ...AccountDelinquencyRangeEventSerializerTest.java | 149 ++++++++++++++++++++-
 7 files changed, 255 insertions(+), 40 deletions(-)

diff --git 
a/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDelinquencyRangeDataV1.avsc
 
b/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDelinquencyRangeDataV1.avsc
index 3da294421..95d504c54 100644
--- 
a/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDelinquencyRangeDataV1.avsc
+++ 
b/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDelinquencyRangeDataV1.avsc
@@ -50,6 +50,13 @@
                 "null",
                 "string"
             ]
+        },
+        {
+            "name": "installmentDelinquencyBuckets",
+            "type": {
+                "type": "array",
+                "items": 
"org.apache.fineract.avro.loan.v1.LoanInstallmentDelinquencyBucketDataV1"
+            }
         }
     ]
 }
diff --git 
a/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDelinquencyRangeDataV1.avsc
 
b/fineract-avro-schemas/src/main/avro/loan/v1/LoanInstallmentDelinquencyBucketDataV1.avsc
similarity index 61%
copy from 
fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDelinquencyRangeDataV1.avsc
copy to 
fineract-avro-schemas/src/main/avro/loan/v1/LoanInstallmentDelinquencyBucketDataV1.avsc
index 3da294421..22c306a23 100644
--- 
a/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDelinquencyRangeDataV1.avsc
+++ 
b/fineract-avro-schemas/src/main/avro/loan/v1/LoanInstallmentDelinquencyBucketDataV1.avsc
@@ -1,24 +1,8 @@
 {
-    "name": "LoanAccountDelinquencyRangeDataV1",
+    "name": "LoanInstallmentDelinquencyBucketDataV1",
     "namespace": "org.apache.fineract.avro.loan.v1",
     "type": "record",
     "fields": [
-        {
-            "name": "loanId",
-            "type": "long"
-        },
-        {
-            "name": "loanAccountNo",
-            "type": "string"
-        },
-        {
-            "default": null,
-            "name": "loanExternalId",
-            "type": [
-                "null",
-                "string"
-            ]
-        },
         {
             "default": null,
             "name": "delinquencyRange",
@@ -27,6 +11,11 @@
                 "org.apache.fineract.avro.loan.v1.DelinquencyRangeDataV1"
             ]
         },
+        {
+            "name": "amount",
+            "doc": "Contains installments  total, fee, interest, principal and 
penalty amount summaries",
+            "type": "org.apache.fineract.avro.loan.v1.LoanAmountDataV1"
+        },
         {
             "name": "charges",
             "type": {
@@ -37,19 +26,6 @@
         {
             "name": "currency",
             "type": "org.apache.fineract.avro.generic.v1.CurrencyDataV1"
-        },
-        {
-            "name": "amount",
-            "doc": "Contains installments  total, fee, interest, principal and 
penalty amount summaries",
-            "type": "org.apache.fineract.avro.loan.v1.LoanAmountDataV1"
-        },
-        {
-            "default": null,
-            "name": "delinquentDate",
-            "type": [
-                "null",
-                "string"
-            ]
         }
     ]
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanDelinquencyRangeChangeBusinessEventSerializer.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanDelinquencyRangeChangeBusinessEventSerializer.java
index 7fb24d9eb..fcf9d4045 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanDelinquencyRangeChangeBusinessEventSerializer.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanDelinquencyRangeChangeBusinessEventSerializer.java
@@ -19,8 +19,12 @@
 package 
org.apache.fineract.infrastructure.event.external.service.serialization.serializer.loan;
 
 import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 import java.util.function.BiFunction;
+import java.util.stream.Collectors;
 import lombok.RequiredArgsConstructor;
 import org.apache.avro.generic.GenericContainer;
 import org.apache.fineract.avro.generator.ByteBufferSerializable;
@@ -28,6 +32,8 @@ import 
org.apache.fineract.avro.loan.v1.DelinquencyRangeDataV1;
 import org.apache.fineract.avro.loan.v1.LoanAccountDelinquencyRangeDataV1;
 import org.apache.fineract.avro.loan.v1.LoanAmountDataV1;
 import org.apache.fineract.avro.loan.v1.LoanChargeDataRangeViewV1;
+import org.apache.fineract.avro.loan.v1.LoanInstallmentDelinquencyBucketDataV1;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
 import org.apache.fineract.infrastructure.event.business.domain.BusinessEvent;
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.LoanDelinquencyRangeChangeBusinessEvent;
 import 
org.apache.fineract.infrastructure.event.external.service.serialization.mapper.generic.CurrencyDataMapper;
@@ -36,10 +42,12 @@ import 
org.apache.fineract.infrastructure.event.external.service.serialization.m
 import 
org.apache.fineract.infrastructure.event.external.service.serialization.mapper.support.AvroDateTimeMapper;
 import 
org.apache.fineract.infrastructure.event.external.service.serialization.serializer.BusinessEventSerializer;
 import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import 
org.apache.fineract.portfolio.delinquency.data.LoanInstallmentDelinquencyTagData;
 import 
org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService;
 import org.apache.fineract.portfolio.loanaccount.data.CollectionData;
 import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData;
 import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
 import 
org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService;
 import 
org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
@@ -94,6 +102,10 @@ public class 
LoanDelinquencyRangeChangeBusinessEventSerializer implements Busine
                 .build();
 
         DelinquencyRangeDataV1 delinquencyRange = 
mapper.map(data.getDelinquencyRange());
+
+        List<LoanInstallmentDelinquencyBucketDataV1> 
installmentsDelinquencyData = 
calculateInstallmentLevelDelinquencyData(event.get(),
+                data);
+
         LoanAccountDelinquencyRangeDataV1.Builder builder = 
LoanAccountDelinquencyRangeDataV1.newBuilder();
         return builder//
                 .setLoanId(id)//
@@ -104,7 +116,79 @@ public class 
LoanDelinquencyRangeChangeBusinessEventSerializer implements Busine
                 .setAmount(amount)//
                 .setCurrency(currencyMapper.map(data.getCurrency()))//
                 .setDelinquentDate(delinquentDate)//
-                .build();
+                
.setInstallmentDelinquencyBuckets(installmentsDelinquencyData).build();
+    }
+
+    private List<LoanInstallmentDelinquencyBucketDataV1> 
calculateInstallmentLevelDelinquencyData(Loan loan, LoanAccountData data) {
+        List<LoanInstallmentDelinquencyBucketDataV1> 
loanInstallmentDelinquencyData = new ArrayList<>();
+        if (loan.isEnableInstallmentLevelDelinquency()) {
+            Collection<LoanInstallmentDelinquencyTagData> 
installmentDelinquencyTags = delinquencyReadPlatformService
+                    
.retrieveLoanInstallmentsCurrentDelinquencyTag(loan.getId());
+            if (installmentDelinquencyTags != null && 
installmentDelinquencyTags.size() > 0) {
+                // group installments that are in same range
+                Map<Long, List<LoanInstallmentDelinquencyTagData>> 
installmentsInSameRange = installmentDelinquencyTags.stream().collect(
+                        Collectors.groupingBy(installmentDelnquencyTags -> 
installmentDelnquencyTags.getDelinquencyRange().getId()));
+                // for installments in each range, get details from loan 
repayment schedule installment, add amounts,
+                // list charges
+                for (Map.Entry<Long, List<LoanInstallmentDelinquencyTagData>> 
installmentDelinquencyTagData : installmentsInSameRange
+                        .entrySet()) {
+                    // get installments details
+                    List<LoanRepaymentScheduleInstallment> 
delinquentInstallmentsInSameRange = loan.getRepaymentScheduleInstallments()
+                            .stream().filter(installment -> 
installmentDelinquencyTagData.getValue().stream()
+                                    .anyMatch(installmentTag -> 
installmentTag.getId().equals(installment.getId())))
+                            .toList();
+                    // add amounts
+                    LoanAmountDataV1 amount = LoanAmountDataV1.newBuilder()//
+                            
.setPrincipalAmount(delinquentInstallmentsInSameRange.stream()
+                                    .map(instlment -> 
instlment.getPrincipalOutstanding(loan.getCurrency()).getAmount())
+                                    .reduce(BigDecimal.ZERO, 
BigDecimal::add))//
+                            
.setFeeAmount(delinquentInstallmentsInSameRange.stream()
+                                    .map(instlment -> 
instlment.getFeeChargesOutstanding(loan.getCurrency()).getAmount())
+                                    .reduce(BigDecimal.ZERO, 
BigDecimal::add))//
+                            
.setInterestAmount(delinquentInstallmentsInSameRange.stream()
+                                    .map(instlment -> 
instlment.getInterestOutstanding(loan.getCurrency()).getAmount())
+                                    .reduce(BigDecimal.ZERO, 
BigDecimal::add))//
+                            
.setPenaltyAmount(delinquentInstallmentsInSameRange.stream()
+                                    .map(instlment -> 
instlment.getPenaltyChargesOutstanding(loan.getCurrency()).getAmount())
+                                    .reduce(BigDecimal.ZERO, 
BigDecimal::add))//
+                            
.setTotalAmount(delinquentInstallmentsInSameRange.stream()
+                                    .map(instlment -> 
instlment.getTotalOutstanding(loan.getCurrency()).getAmount())
+                                    .reduce(BigDecimal.ZERO, 
BigDecimal::add))//
+                            .build();
+
+                    // get list of charges for installments in same range
+                    List<LoanCharge> chargesForInstallmentsInSameRange = 
loan.getLoanCharges().stream()
+                            .filter(loanCharge -> !loanCharge.isPaid() && 
delinquentInstallmentsInSameRange.stream().anyMatch(
+                                    installmentForCharge -> 
(DateUtils.isAfter(loanCharge.getDueDate(), installmentForCharge.getFromDate())
+                                            || 
DateUtils.isEqual(loanCharge.getDueDate(), installmentForCharge.getFromDate()))
+                                            && 
(DateUtils.isBefore(loanCharge.getDueDate(), installmentForCharge.getDueDate())
+                                                    || 
DateUtils.isEqual(loanCharge.getDueDate(), installmentForCharge.getDueDate()))))
+                            .toList();
+
+                    List<LoanChargeDataRangeViewV1> charges = new 
ArrayList<>();
+                    for (LoanCharge charge : 
chargesForInstallmentsInSameRange) {
+                        LoanChargeDataRangeViewV1 chargeData = 
LoanChargeDataRangeViewV1.newBuilder().setId(charge.getId())
+                                
.setName(charge.name()).setAmount(charge.amountOutstanding())
+                                
.setCurrency(currencyMapper.map(data.getCurrency())).build();
+                        charges.add(chargeData);
+                    }
+
+                    
LoanInstallmentDelinquencyTagData.InstallmentDelinquencyRange delinquencyRange 
= installmentDelinquencyTagData
+                            .getValue().get(0).getDelinquencyRange();
+
+                    DelinquencyRangeDataV1 delinquencyRangeDataV1 = 
DelinquencyRangeDataV1.newBuilder().setId(delinquencyRange.getId())
+                            
.setClassification(delinquencyRange.getClassification()).setMinimumAgeDays(delinquencyRange.getMinimumAgeDays())
+                            
.setMaximumAgeDays(delinquencyRange.getMaximumAgeDays()).build();
+
+                    LoanInstallmentDelinquencyBucketDataV1 
installmentDelinquencyBucketDataV1 = LoanInstallmentDelinquencyBucketDataV1
+                            
.newBuilder().setDelinquencyRange(delinquencyRangeDataV1).setAmount(amount).setCharges(charges)
+                            
.setCurrency(currencyMapper.map(data.getCurrency())).build();
+
+                    
loanInstallmentDelinquencyData.add(installmentDelinquencyBucketDataV1);
+                }
+            }
+        }
+        return loanInstallmentDelinquencyData;
     }
 
     private BigDecimal calculateDataSummary(Loan loan, BiFunction<Loan, 
LoanRepaymentScheduleInstallment, BigDecimal> mapper) {
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/data/LoanInstallmentDelinquencyTagData.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/data/LoanInstallmentDelinquencyTagData.java
index 860156951..819db7384 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/data/LoanInstallmentDelinquencyTagData.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/data/LoanInstallmentDelinquencyTagData.java
@@ -22,6 +22,8 @@ import java.math.BigDecimal;
 
 public interface LoanInstallmentDelinquencyTagData {
 
+    Long getId();
+
     InstallmentDelinquencyRange getDelinquencyRange();
 
     BigDecimal getOutstandingAmount();
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
index 5729fdec6..24dc1721b 100644
--- 
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
@@ -38,7 +38,7 @@ public interface LoanInstallmentDelinquencyTagRepository
     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")
+    @Query("select i.installment.id, i.delinquencyRange, i.outstandingAmount 
from LoanInstallmentDelinquencyTag i where i.loan.id = :loanId")
     List<LoanInstallmentDelinquencyTagData> 
findInstallmentDelinquencyTags(@Param("loanId") Long loanId);
 
     @Modifying(flushAutomatically = true)
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 75bcf3f7d..2e88c76ad 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
@@ -175,12 +175,13 @@ public class DelinquencyWritePlatformServiceImpl 
implements DelinquencyWritePlat
             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);
             }
+            // delinquency for loan
+            changes = lookUpDelinquencyRange(loan, delinquencyBucket, 
collectionData.getDelinquentDays());
+
         }
         return new 
CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(loan.getId())
                 
.withEntityExternalId(loan.getExternalId()).with(changes).build();
@@ -196,13 +197,13 @@ public class DelinquencyWritePlatformServiceImpl 
implements DelinquencyWritePlat
             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);
             }
+            log.debug("Delinquency {}", collectionData);
+            // delinquency for loan
+            lookUpDelinquencyRange(loan, delinquencyBucket, 
collectionData.getDelinquentDays());
         }
     }
 
@@ -231,10 +232,10 @@ public class DelinquencyWritePlatformServiceImpl 
implements DelinquencyWritePlat
 
     @Override
     public void removeDelinquencyTagToLoan(final Loan loan) {
-        setLoanDelinquencyTag(loan, null);
         if (loan.isEnableInstallmentLevelDelinquency()) {
             cleanLoanInstallmentsDelinquencyTags(loan);
         }
+        setLoanDelinquencyTag(loan, null);
     }
 
     @Override
diff --git 
a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanAccountDelinquencyRangeEventSerializerTest.java
 
b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanAccountDelinquencyRangeEventSerializerTest.java
index 807c7cbbf..0002caec3 100644
--- 
a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanAccountDelinquencyRangeEventSerializerTest.java
+++ 
b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanAccountDelinquencyRangeEventSerializerTest.java
@@ -38,6 +38,7 @@ import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 import org.apache.fineract.avro.loan.v1.LoanAccountDelinquencyRangeDataV1;
+import org.apache.fineract.avro.loan.v1.LoanInstallmentDelinquencyBucketDataV1;
 import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
 import org.apache.fineract.infrastructure.core.domain.ActionContext;
 import org.apache.fineract.infrastructure.core.domain.ExternalId;
@@ -59,6 +60,7 @@ import 
org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
 import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
 import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
 import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData;
+import 
org.apache.fineract.portfolio.delinquency.data.LoanInstallmentDelinquencyTagData;
 import 
org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService;
 import org.apache.fineract.portfolio.loanaccount.data.CollectionData;
 import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData;
@@ -75,9 +77,12 @@ import org.mockito.Mock;
 import org.mockito.MockedStatic;
 import org.mockito.Mockito;
 import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.junit.jupiter.MockitoSettings;
+import org.mockito.quality.Strictness;
 import org.springframework.test.util.ReflectionTestUtils;
 
 @ExtendWith(MockitoExtension.class)
+@MockitoSettings(strictness = Strictness.LENIENT)
 public class LoanAccountDelinquencyRangeEventSerializerTest {
 
     @Mock
@@ -101,7 +106,7 @@ public class LoanAccountDelinquencyRangeEventSerializerTest 
{
     }
 
     @Test
-    public void testLoanRepaymentEventPayloadSerialization() throws 
IOException {
+    public void testLoanDelinquencyRangeEventPayloadSerialization() throws 
IOException {
         // given
         LoanDelinquencyRangeChangeBusinessEventSerializer serializer = new 
LoanDelinquencyRangeChangeBusinessEventSerializer(
                 loanReadPlatformService, new 
LoanDelinquencyRangeDataMapperImpl(), loanChargeReadPlatformService,
@@ -123,6 +128,7 @@ public class LoanAccountDelinquencyRangeEventSerializerTest 
{
         when(loanAccountData.getCurrency()).thenAnswer(a -> new 
CurrencyData(loanCurrency.getCode(), loanCurrency.getDigitsAfterDecimal(),
                 loanCurrency.getCurrencyInMultiplesOf()));
         when(loanForProcessing.getCurrency()).thenReturn(loanCurrency);
+        
when(loanForProcessing.isEnableInstallmentLevelDelinquency()).thenReturn(false);
         when(delinquentData.getDelinquentDate()).thenReturn(delinquentDate);
         
when(loanReadPlatformService.retrieveOne(any(Long.class))).thenReturn(loanAccountData);
         
when(delinquencyReadPlatformService.calculateLoanCollectionData(any(Long.class))).thenReturn(delinquentData);
@@ -163,6 +169,141 @@ public class 
LoanAccountDelinquencyRangeEventSerializerTest {
         moneyHelper.close();
     }
 
+    @Test
+    public void 
testLoanDelinquencyRangeEventPayloadSerializationWithInstallmentDelinquencyData()
 throws IOException {
+        // given
+        LoanDelinquencyRangeChangeBusinessEventSerializer serializer = new 
LoanDelinquencyRangeChangeBusinessEventSerializer(
+                loanReadPlatformService, new 
LoanDelinquencyRangeDataMapperImpl(), loanChargeReadPlatformService,
+                delinquencyReadPlatformService, new 
LoanChargeDataMapperImpl(null, null, null), new CurrencyDataMapperImpl(), 
mapper);
+
+        Loan loanForProcessing = Mockito.mock(Loan.class);
+        LoanAccountData loanAccountData = mock(LoanAccountData.class);
+        CollectionData delinquentData = mock(CollectionData.class);
+        MonetaryCurrency loanCurrency = new MonetaryCurrency("CODE", 1, 1);
+        MockedStatic<MoneyHelper> moneyHelper = 
Mockito.mockStatic(MoneyHelper.class);
+        String delinquentDateAsStr = "2022-12-01";
+        LocalDate delinquentDate = LocalDate.parse(delinquentDateAsStr);
+        when(loanForProcessing.getId()).thenReturn(1L);
+        when(loanAccountData.getId()).thenReturn(1L);
+        when(loanAccountData.getAccountNo()).thenReturn("0001");
+        
when(loanAccountData.getExternalId()).thenReturn(ExternalIdFactory.produce("externalId"));
+        when(loanAccountData.getDelinquencyRange()).thenReturn(new 
DelinquencyRangeData(1L, "classification", 1, 10));
+        when(loanAccountData.getCurrency()).thenAnswer(a -> new 
CurrencyData(loanCurrency.getCode(), loanCurrency.getDigitsAfterDecimal(),
+                loanCurrency.getCurrencyInMultiplesOf()));
+        when(loanForProcessing.getCurrency()).thenReturn(loanCurrency);
+        
when(loanForProcessing.isEnableInstallmentLevelDelinquency()).thenReturn(true);
+        when(delinquentData.getDelinquentDate()).thenReturn(delinquentDate);
+        
when(loanReadPlatformService.retrieveOne(any(Long.class))).thenReturn(loanAccountData);
+        
when(delinquencyReadPlatformService.calculateLoanCollectionData(any(Long.class))).thenReturn(delinquentData);
+        
when(mapper.mapLocalDate(delinquentDate)).thenReturn(delinquentDateAsStr);
+
+        LoanDelinquencyRangeChangeBusinessEvent event = new 
LoanDelinquencyRangeChangeBusinessEvent(loanForProcessing);
+
+        List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments = 
new ArrayList<>();
+        LoanRepaymentScheduleInstallment repaymentScheduleInstallment_1 = 
buildInstallment(loanForProcessing, loanCurrency,
+                BigDecimal.valueOf(100), BigDecimal.valueOf(10), 
BigDecimal.valueOf(20), BigDecimal.valueOf(20), BigDecimal.valueOf(150),
+                new BigDecimal("10"), new BigDecimal("20"));
+        when(repaymentScheduleInstallment_1.getId()).thenReturn(1L);
+        
when(repaymentScheduleInstallment_1.getFromDate()).thenReturn(LocalDate.of(2022,
 6, 20));
+        
when(repaymentScheduleInstallment_1.getDueDate()).thenReturn(LocalDate.of(2022, 
6, 30));
+        repaymentScheduleInstallments.add(repaymentScheduleInstallment_1);
+
+        LoanRepaymentScheduleInstallment repaymentScheduleInstallment_2 = 
buildInstallment(loanForProcessing, loanCurrency,
+                BigDecimal.valueOf(100), BigDecimal.valueOf(0), 
BigDecimal.valueOf(20), BigDecimal.valueOf(0), BigDecimal.valueOf(120));
+        when(repaymentScheduleInstallment_2.getId()).thenReturn(2L);
+        
when(repaymentScheduleInstallment_2.getFromDate()).thenReturn(LocalDate.of(2022,
 7, 1));
+        
when(repaymentScheduleInstallment_2.getDueDate()).thenReturn(LocalDate.of(2022, 
7, 10));
+        repaymentScheduleInstallments.add(repaymentScheduleInstallment_2);
+
+        LoanRepaymentScheduleInstallment repaymentScheduleInstallment_3 = 
buildInstallment(loanForProcessing, loanCurrency,
+                BigDecimal.valueOf(100), BigDecimal.valueOf(0), 
BigDecimal.valueOf(20), BigDecimal.valueOf(0), BigDecimal.valueOf(120));
+        when(repaymentScheduleInstallment_3.getId()).thenReturn(3L);
+        
when(repaymentScheduleInstallment_3.getFromDate()).thenReturn(LocalDate.of(2022,
 7, 11));
+        
when(repaymentScheduleInstallment_3.getDueDate()).thenReturn(LocalDate.of(2022, 
7, 20));
+        repaymentScheduleInstallments.add(repaymentScheduleInstallment_3);
+
+        
when(loanForProcessing.getRepaymentScheduleInstallments()).thenReturn(repaymentScheduleInstallments);
+        
when(loanChargeReadPlatformService.retrieveLoanCharges(anyLong())).thenAnswer(a 
-> repaymentScheduleInstallments.get(0)
+                .getInstallmentCharges().stream().map(c -> 
c.getLoanCharge().toData()).collect(Collectors.toList()));
+
+        List<LoanInstallmentDelinquencyTagData> installmentDelinquencyTags = 
new ArrayList<>();
+        installmentDelinquencyTags.add(buildInstallmentDelinquencyTag(1L, 1L));
+        installmentDelinquencyTags.add(buildInstallmentDelinquencyTag(2L, 1L));
+        installmentDelinquencyTags.add(buildInstallmentDelinquencyTag(3L, 2L));
+
+        
when(delinquencyReadPlatformService.retrieveLoanInstallmentsCurrentDelinquencyTag(anyLong()))
+                .thenReturn(installmentDelinquencyTags);
+
+        when(loanForProcessing.getLoanCharges()).thenAnswer(a -> 
repaymentScheduleInstallments.get(0).getInstallmentCharges().stream()
+                .map(c -> c.getLoanCharge()).collect(Collectors.toList()));
+
+        moneyHelper.when(() -> 
MoneyHelper.getRoundingMode()).thenReturn(RoundingMode.UP);
+
+        // when
+        LoanAccountDelinquencyRangeDataV1 data = 
(LoanAccountDelinquencyRangeDataV1) serializer.toAvroDTO(event);
+
+        // then
+        assertEquals(1L, data.getLoanId());
+        assertEquals("0001", data.getLoanAccountNo());
+        assertEquals("externalId", data.getLoanExternalId());
+        assertEquals(1L, data.getDelinquencyRange().getId());
+        assertEquals("classification", 
data.getDelinquencyRange().getClassification());
+        assertEquals(1, data.getDelinquencyRange().getMinimumAgeDays());
+        assertEquals(10, data.getDelinquencyRange().getMaximumAgeDays());
+        assertEquals(2, data.getCharges().size());
+        assertTrue(data.getCharges().stream().anyMatch(a -> 
a.getAmount().compareTo(new BigDecimal("10")) == 0));
+        assertTrue(data.getCharges().stream().anyMatch(a -> 
a.getAmount().compareTo(new BigDecimal("20")) == 0));
+        assertEquals(0, data.getAmount().getTotalAmount().compareTo(new 
BigDecimal("390.0")));
+        assertEquals(0, data.getAmount().getPrincipalAmount().compareTo(new 
BigDecimal("300.0")));
+        assertEquals(0, data.getAmount().getInterestAmount().compareTo(new 
BigDecimal("60.0")));
+        assertEquals(0, data.getAmount().getFeeAmount().compareTo(new 
BigDecimal("10.0")));
+        assertEquals(0, data.getAmount().getPenaltyAmount().compareTo(new 
BigDecimal("20.0")));
+        assertEquals(delinquentDateAsStr, data.getDelinquentDate());
+
+        // check installment delinquency data
+        assertEquals(2, data.getInstallmentDelinquencyBuckets().size());
+
+        // check calculations
+
+        LoanInstallmentDelinquencyBucketDataV1 
installmentDelinquencyBucketDataV1_1 = 
data.getInstallmentDelinquencyBuckets().get(0);
+        assertEquals(1L, 
installmentDelinquencyBucketDataV1_1.getDelinquencyRange().getId());
+        assertEquals("range_1", 
installmentDelinquencyBucketDataV1_1.getDelinquencyRange().getClassification());
+        assertEquals(0, 
installmentDelinquencyBucketDataV1_1.getAmount().getTotalAmount().compareTo(new 
BigDecimal("270.0")));
+        assertEquals(0, 
installmentDelinquencyBucketDataV1_1.getAmount().getPrincipalAmount().compareTo(new
 BigDecimal("200.0")));
+        assertEquals(0, 
installmentDelinquencyBucketDataV1_1.getAmount().getInterestAmount().compareTo(new
 BigDecimal("40.0")));
+        assertEquals(0, 
installmentDelinquencyBucketDataV1_1.getAmount().getFeeAmount().compareTo(new 
BigDecimal("10.0")));
+        assertEquals(0, 
installmentDelinquencyBucketDataV1_1.getAmount().getPenaltyAmount().compareTo(new
 BigDecimal("20.0")));
+        assertEquals(2, 
installmentDelinquencyBucketDataV1_1.getCharges().size());
+        assertTrue(installmentDelinquencyBucketDataV1_1.getCharges().stream()
+                .anyMatch(a -> a.getAmount().compareTo(new BigDecimal("10")) 
== 0));
+        assertTrue(installmentDelinquencyBucketDataV1_1.getCharges().stream()
+                .anyMatch(a -> a.getAmount().compareTo(new BigDecimal("20")) 
== 0));
+
+        LoanInstallmentDelinquencyBucketDataV1 
installmentDelinquencyBucketDataV1_2 = 
data.getInstallmentDelinquencyBuckets().get(1);
+        assertEquals(2L, 
installmentDelinquencyBucketDataV1_2.getDelinquencyRange().getId());
+        assertEquals("range_2", 
installmentDelinquencyBucketDataV1_2.getDelinquencyRange().getClassification());
+        assertEquals(0, 
installmentDelinquencyBucketDataV1_2.getAmount().getTotalAmount().compareTo(new 
BigDecimal("120.0")));
+        assertEquals(0, 
installmentDelinquencyBucketDataV1_2.getAmount().getPrincipalAmount().compareTo(new
 BigDecimal("100.0")));
+        assertEquals(0, 
installmentDelinquencyBucketDataV1_2.getAmount().getInterestAmount().compareTo(new
 BigDecimal("20.0")));
+        assertEquals(0, 
installmentDelinquencyBucketDataV1_2.getAmount().getFeeAmount().compareTo(new 
BigDecimal("0.0")));
+        assertEquals(0, 
installmentDelinquencyBucketDataV1_2.getAmount().getPenaltyAmount().compareTo(new
 BigDecimal("0.0")));
+        assertEquals(0, 
installmentDelinquencyBucketDataV1_2.getCharges().size());
+        moneyHelper.close();
+    }
+
+    private LoanInstallmentDelinquencyTagData 
buildInstallmentDelinquencyTag(long installmentId, long rangeId) {
+        LoanInstallmentDelinquencyTagData.InstallmentDelinquencyRange 
delinquencyRange = mock(
+                
LoanInstallmentDelinquencyTagData.InstallmentDelinquencyRange.class);
+        when(delinquencyRange.getId()).thenReturn(rangeId);
+        when(delinquencyRange.getClassification()).thenReturn("range_" + 
rangeId);
+        when(delinquencyRange.getMaximumAgeDays()).thenReturn(1);
+        when(delinquencyRange.getMinimumAgeDays()).thenReturn(2);
+        LoanInstallmentDelinquencyTagData installmentDelinquencyTagData = 
mock(LoanInstallmentDelinquencyTagData.class);
+        when(installmentDelinquencyTagData.getId()).thenReturn(installmentId);
+        
when(installmentDelinquencyTagData.getDelinquencyRange()).thenReturn(delinquencyRange);
+        return installmentDelinquencyTagData;
+    }
+
     private LoanRepaymentScheduleInstallment buildInstallment(Loan loan, 
MonetaryCurrency currency, BigDecimal principalAmount,
             BigDecimal freeAmount, BigDecimal interestAmount, BigDecimal 
penaltyAmount, BigDecimal totalAmount, BigDecimal... charges) {
 
@@ -173,6 +314,7 @@ public class LoanAccountDelinquencyRangeEventSerializerTest 
{
         when(installment.getFeeChargesOutstanding(any())).thenAnswer(a -> 
Money.of(currency, freeAmount));
         when(installment.getTotalOutstanding(any())).thenAnswer(a -> 
Money.of(currency, totalAmount));
         Charge charge = mock(Charge.class);
+        when(charge.getName()).thenReturn("charge");
         when(charge.toData()).thenAnswer(a -> {
             ChargeData chargeData = mock(ChargeData.class);
             when(chargeData.getCurrency()).thenAnswer(b -> new 
CurrencyData(currency.getCode()));
@@ -193,7 +335,10 @@ public class 
LoanAccountDelinquencyRangeEventSerializerTest {
     }
 
     private LoanCharge buildLoanCharge(Loan loan, BigDecimal amount, Charge 
charge) {
-        return new LoanCharge(loan, charge, amount, amount, 
ChargeTimeType.TRANCHE_DISBURSEMENT, ChargeCalculationType.FLAT,
+        LoanCharge loanCharge = new LoanCharge(loan, charge, amount, amount, 
ChargeTimeType.SPECIFIED_DUE_DATE, ChargeCalculationType.FLAT,
                 LocalDate.of(2022, 6, 27), ChargePaymentMode.REGULAR, 1, new 
BigDecimal(100), ExternalId.generate());
+        ReflectionTestUtils.setField(loanCharge, "id", 1L);
+        return loanCharge;
     }
+
 }

Reply via email to