You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by ar...@apache.org on 2022/12/19 14:48:44 UTC

[fineract] branch develop updated: FINERACT-1734 - Loan Account Delinquency Changed event definition Extend current Loan Account Delinquency range change event.

This is an automated email from the ASF dual-hosted git repository.

arnold 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 adcd76154 FINERACT-1734 - Loan Account Delinquency Changed event definition Extend current Loan Account Delinquency range change event.
adcd76154 is described below

commit adcd76154eb957bc0cb6f0d740481803ec26940c
Author: Janos Haber <ja...@finesolution.hu>
AuthorDate: Fri Dec 16 12:27:54 2022 +0100

    FINERACT-1734 - Loan Account Delinquency Changed event definition
    Extend current Loan Account Delinquency range change event.
---
 .../loan/v1/LoanAccountDelinquencyRangeDataV1.avsc |  34 ++--
 ...uencyRangeDataV1.avsc => LoanAmountDataV1.avsc} |  26 +--
 .../avro/loan/v1/LoanChargeDataRangeViewV1.avsc    |  23 +++
 .../CurrencyDataMapper.java}                       |  11 +-
 .../mapper/loan/LoanChargeDataMapper.java          |   3 +
 ...inquencyRangeChangeBusinessEventSerializer.java |  55 ++++++-
 ...AccountDelinquencyRangeEventSerializerTest.java | 178 +++++++++++++++++++++
 7 files changed, 302 insertions(+), 28 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 048526c34..b9bb2dbf1 100644
--- a/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDelinquencyRangeDataV1.avsc
+++ b/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDelinquencyRangeDataV1.avsc
@@ -4,24 +4,16 @@
     "type": "record",
     "fields": [
         {
-            "default": null,
-            "name": "id",
-            "type": [
-                "null",
-                "long"
-            ]
+            "name": "loanId",
+            "type": "long"
         },
         {
-            "default": null,
-            "name": "accountNo",
-            "type": [
-                "null",
-                "string"
-            ]
+            "name": "loanAccountNo",
+            "type": "string"
         },
         {
             "default": null,
-            "name": "externalId",
+            "name": "loanExternalId",
             "type": [
                 "null",
                 "string"
@@ -34,6 +26,22 @@
                 "null",
                 "org.apache.fineract.avro.loan.v1.DelinquencyRangeDataV1"
             ]
+        },
+        {
+            "name": "charges",
+            "type": {
+                "type": "array",
+                "items": "org.apache.fineract.avro.loan.v1.LoanChargeDataRangeViewV1"
+            }
+        },
+        {
+            "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"
         }
     ]
 }
diff --git a/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDelinquencyRangeDataV1.avsc b/fineract-avro-schemas/src/main/avro/loan/v1/LoanAmountDataV1.avsc
similarity index 53%
copy from fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDelinquencyRangeDataV1.avsc
copy to fineract-avro-schemas/src/main/avro/loan/v1/LoanAmountDataV1.avsc
index 048526c34..584f24dd0 100644
--- a/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDelinquencyRangeDataV1.avsc
+++ b/fineract-avro-schemas/src/main/avro/loan/v1/LoanAmountDataV1.avsc
@@ -1,38 +1,46 @@
 {
-    "name": "LoanAccountDelinquencyRangeDataV1",
+    "name": "LoanAmountDataV1",
     "namespace": "org.apache.fineract.avro.loan.v1",
     "type": "record",
     "fields": [
         {
             "default": null,
-            "name": "id",
+            "name": "totalAmount",
             "type": [
                 "null",
-                "long"
+                "bigdecimal"
             ]
         },
         {
             "default": null,
-            "name": "accountNo",
+            "name": "principalAmount",
             "type": [
                 "null",
-                "string"
+                "bigdecimal"
             ]
         },
         {
             "default": null,
-            "name": "externalId",
+            "name": "interestAmount",
             "type": [
                 "null",
-                "string"
+                "bigdecimal"
             ]
         },
         {
             "default": null,
-            "name": "delinquencyRange",
+            "name": "feeAmount",
             "type": [
                 "null",
-                "org.apache.fineract.avro.loan.v1.DelinquencyRangeDataV1"
+                "bigdecimal"
+            ]
+        },
+        {
+            "default": null,
+            "name": "penaltyAmount",
+            "type": [
+                "null",
+                "bigdecimal"
             ]
         }
     ]
diff --git a/fineract-avro-schemas/src/main/avro/loan/v1/LoanChargeDataRangeViewV1.avsc b/fineract-avro-schemas/src/main/avro/loan/v1/LoanChargeDataRangeViewV1.avsc
new file mode 100644
index 000000000..ebb747c76
--- /dev/null
+++ b/fineract-avro-schemas/src/main/avro/loan/v1/LoanChargeDataRangeViewV1.avsc
@@ -0,0 +1,23 @@
+{
+    "name": "LoanChargeDataRangeViewV1",
+    "namespace": "org.apache.fineract.avro.loan.v1",
+    "type": "record",
+    "fields": [
+        {
+            "name": "id",
+            "type": "long"
+        },
+        {
+            "name": "name",
+            "type": "string"
+        },
+        {
+            "name": "currency",
+            "type": ["org.apache.fineract.avro.generic.v1.CurrencyDataV1"]
+        },
+        {
+            "name": "amount",
+            "type": "bigdecimal"
+        }
+    ]
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/loan/LoanChargeDataMapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/generic/CurrencyDataMapper.java
similarity index 81%
copy from fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/loan/LoanChargeDataMapper.java
copy to fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/generic/CurrencyDataMapper.java
index b000dde1a..4195640d7 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/loan/LoanChargeDataMapper.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/generic/CurrencyDataMapper.java
@@ -16,15 +16,16 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.infrastructure.event.external.service.serialization.mapper.loan;
+package org.apache.fineract.infrastructure.event.external.service.serialization.mapper.generic;
 
-import org.apache.fineract.avro.loan.v1.LoanChargeDataV1;
+import org.apache.fineract.avro.generic.v1.CurrencyDataV1;
 import org.apache.fineract.infrastructure.event.external.service.serialization.mapper.support.AvroMapperConfig;
-import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
 import org.mapstruct.Mapper;
 
 @Mapper(config = AvroMapperConfig.class)
-public interface LoanChargeDataMapper {
+public interface CurrencyDataMapper {
+
+    CurrencyDataV1 map(CurrencyData source);
 
-    LoanChargeDataV1 map(LoanChargeData source);
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/loan/LoanChargeDataMapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/loan/LoanChargeDataMapper.java
index b000dde1a..0a7ae2710 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/loan/LoanChargeDataMapper.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/loan/LoanChargeDataMapper.java
@@ -18,6 +18,7 @@
  */
 package org.apache.fineract.infrastructure.event.external.service.serialization.mapper.loan;
 
+import org.apache.fineract.avro.loan.v1.LoanChargeDataRangeViewV1;
 import org.apache.fineract.avro.loan.v1.LoanChargeDataV1;
 import org.apache.fineract.infrastructure.event.external.service.serialization.mapper.support.AvroMapperConfig;
 import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData;
@@ -27,4 +28,6 @@ import org.mapstruct.Mapper;
 public interface LoanChargeDataMapper {
 
     LoanChargeDataV1 map(LoanChargeData source);
+
+    LoanChargeDataRangeViewV1 mapRangeView(LoanChargeData source);
 }
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 a7e4879fc..f73b16b6c 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
@@ -18,16 +18,27 @@
  */
 package org.apache.fineract.infrastructure.event.external.service.serialization.serializer.loan;
 
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.function.BiFunction;
 import lombok.RequiredArgsConstructor;
 import org.apache.avro.generic.GenericContainer;
 import org.apache.fineract.avro.generator.ByteBufferSerializable;
 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.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;
+import org.apache.fineract.infrastructure.event.external.service.serialization.mapper.loan.LoanChargeDataMapper;
 import org.apache.fineract.infrastructure.event.external.service.serialization.mapper.loan.LoanDelinquencyRangeDataMapper;
 import org.apache.fineract.infrastructure.event.external.service.serialization.serializer.AbstractBusinessEventSerializer;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
 import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanInstallmentCharge;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
 import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
 import org.springframework.core.Ordered;
 import org.springframework.core.annotation.Order;
@@ -39,8 +50,13 @@ import org.springframework.stereotype.Component;
 public class LoanDelinquencyRangeChangeBusinessEventSerializer extends AbstractBusinessEventSerializer {
 
     private final LoanReadPlatformService service;
+
     private final LoanDelinquencyRangeDataMapper mapper;
 
+    private final LoanChargeDataMapper chargeMapper;
+
+    private final CurrencyDataMapper currencyMapper;
+
     @Override
     protected <T> ByteBufferSerializable toAvroDTO(BusinessEvent<T> rawEvent) {
         LoanDelinquencyRangeChangeBusinessEvent event = (LoanDelinquencyRangeChangeBusinessEvent) rawEvent;
@@ -48,8 +64,45 @@ public class LoanDelinquencyRangeChangeBusinessEventSerializer extends AbstractB
         Long id = data.getId();
         String accountNumber = data.getAccountNo();
         String externalId = data.getExternalId().getValue();
+        MonetaryCurrency loanCurrency = event.get().getCurrency();
+        List<LoanChargeDataRangeViewV1> charges = event//
+                .get()//
+                .getRepaymentScheduleInstallments()//
+                .stream()//
+                .flatMap(installment -> installment.getInstallmentCharges().stream())//
+                .map(LoanInstallmentCharge::getLoancharge)//
+                .map(charge -> chargeMapper.mapRangeView(charge.toData()))//
+                .toList();
+        LoanAmountDataV1 amount = LoanAmountDataV1.newBuilder()//
+                .setPrincipalAmount(calculateDataSummary(event.get(),
+                        (loan, installment) -> installment.getPrincipalOutstanding(loanCurrency).getAmount()))//
+                .setFeeAmount(calculateDataSummary(event.get(),
+                        (loan, installment) -> installment.getFeeChargesOutstanding(loanCurrency).getAmount()))//
+                .setInterestAmount(calculateDataSummary(event.get(),
+                        (loan, installment) -> installment.getInterestOutstanding(loanCurrency).getAmount()))//
+                .setPenaltyAmount(calculateDataSummary(event.get(),
+                        (loan, installment) -> installment.getPenaltyChargesOutstanding(loanCurrency).getAmount()))//
+                .setTotalAmount(
+                        calculateDataSummary(event.get(), (loan, installment) -> installment.getTotalOutstanding(loanCurrency).getAmount()))//
+                .build();
+
         DelinquencyRangeDataV1 delinquencyRange = mapper.map(data.getDelinquencyRange());
-        return new LoanAccountDelinquencyRangeDataV1(id, accountNumber, externalId, delinquencyRange);
+        LoanAccountDelinquencyRangeDataV1.Builder builder = LoanAccountDelinquencyRangeDataV1.newBuilder();
+        return builder//
+                .setLoanId(id)//
+                .setLoanAccountNo(accountNumber)//
+                .setLoanExternalId(externalId)//
+                .setDelinquencyRange(delinquencyRange)//
+                .setCharges(charges)//
+                .setAmount(amount)//
+                .setCurrency(currencyMapper.map(data.getCurrency()))//
+                .build();
+    }
+
+    private BigDecimal calculateDataSummary(Loan loan, BiFunction<Loan, LoanRepaymentScheduleInstallment, BigDecimal> mapper) {
+        return loan.getRepaymentScheduleInstallments().stream().map(installment -> mapper.apply(loan, installment)).reduce(BigDecimal.ZERO,
+                BigDecimal::add);
+
     }
 
     @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
new file mode 100644
index 000000000..e9e8fcbe9
--- /dev/null
+++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanAccountDelinquencyRangeEventSerializerTest.java
@@ -0,0 +1,178 @@
+/**
+ * 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.infrastructure.event.external.service.serialization.serializer.loan;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+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.infrastructure.businessdate.domain.BusinessDateType;
+import org.apache.fineract.infrastructure.core.domain.ExternalId;
+import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
+import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
+import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.apache.fineract.infrastructure.event.business.domain.loan.LoanDelinquencyRangeChangeBusinessEvent;
+import org.apache.fineract.infrastructure.event.external.service.serialization.mapper.generic.CurrencyDataMapperImpl;
+import org.apache.fineract.infrastructure.event.external.service.serialization.mapper.loan.LoanChargeDataMapperImpl;
+import org.apache.fineract.infrastructure.event.external.service.serialization.mapper.loan.LoanDelinquencyRangeDataMapperImpl;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
+import org.apache.fineract.portfolio.charge.data.ChargeData;
+import org.apache.fineract.portfolio.charge.domain.Charge;
+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.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.LoanInstallmentCharge;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.test.util.ReflectionTestUtils;
+
+@ExtendWith(MockitoExtension.class)
+public class LoanAccountDelinquencyRangeEventSerializerTest {
+
+    @Mock
+    private LoanReadPlatformService loanReadPlatformService;
+
+    @BeforeEach
+    public void setUp() {
+        ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
+        ThreadLocalContextUtil
+                .setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.now(ZoneId.systemDefault()))));
+    }
+
+    @Test
+    public void testLoanRepaymentEventPayloadSerialization() throws IOException {
+        // given
+        LoanDelinquencyRangeChangeBusinessEventSerializer serializer = new LoanDelinquencyRangeChangeBusinessEventSerializer(
+                loanReadPlatformService, new LoanDelinquencyRangeDataMapperImpl(), new LoanChargeDataMapperImpl(),
+                new CurrencyDataMapperImpl());
+
+        Loan loanForProcessing = Mockito.mock(Loan.class);
+        LoanAccountData loanAccountData = mock(LoanAccountData.class);
+        MonetaryCurrency loanCurrency = new MonetaryCurrency("CODE", 1, 1);
+        MockedStatic<MoneyHelper> moneyHelper = Mockito.mockStatic(MoneyHelper.class);
+
+        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(loanReadPlatformService.retrieveOne(any(Long.class))).thenReturn(loanAccountData);
+
+        LoanDelinquencyRangeChangeBusinessEvent event = new LoanDelinquencyRangeChangeBusinessEvent(loanForProcessing);
+        List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments = new ArrayList<>();
+
+        repaymentScheduleInstallments.add(buildInstallment(loanForProcessing, loanCurrency, BigDecimal.valueOf(100), BigDecimal.valueOf(5),
+                BigDecimal.valueOf(30), BigDecimal.valueOf(50), BigDecimal.valueOf(185), new BigDecimal("100.5"), new BigDecimal("200.3")));
+        when(loanForProcessing.getRepaymentScheduleInstallments()).thenReturn(repaymentScheduleInstallments);
+
+        moneyHelper.when(() -> MoneyHelper.getRoundingMode()).thenReturn(RoundingMode.UP);
+
+        // when
+        LoanAccountDelinquencyRangeDataV1 data = (LoanAccountDelinquencyRangeDataV1) serializer.toAvroDTO(event);
+
+        // then
+        Assertions.assertEquals(1L, data.getLoanId());
+        Assertions.assertEquals("0001", data.getLoanAccountNo());
+        Assertions.assertEquals("externalId", data.getLoanExternalId());
+        Assertions.assertEquals(1L, data.getDelinquencyRange().getId());
+        Assertions.assertEquals("classification", data.getDelinquencyRange().getClassification());
+        Assertions.assertEquals(1, data.getDelinquencyRange().getMinimumAgeDays());
+        Assertions.assertEquals(10, data.getDelinquencyRange().getMaximumAgeDays());
+        Assertions.assertEquals(2, data.getCharges().size());
+        Assertions.assertTrue(io.vavr.collection.List.ofAll(data.getCharges())
+                .find(a -> a.getAmount().compareTo(new BigDecimal("100.5")) == 0).isDefined());
+        Assertions.assertTrue(io.vavr.collection.List.ofAll(data.getCharges())
+                .find(a -> a.getAmount().compareTo(new BigDecimal("200.3")) == 0).isDefined());
+
+        Assertions.assertEquals(0, data.getAmount().getTotalAmount().compareTo(new BigDecimal("185.0")));
+        Assertions.assertEquals(0, data.getAmount().getPrincipalAmount().compareTo(new BigDecimal("100.0")));
+        Assertions.assertEquals(0, data.getAmount().getInterestAmount().compareTo(new BigDecimal("30.0")));
+        Assertions.assertEquals(0, data.getAmount().getFeeAmount().compareTo(new BigDecimal("5.0")));
+        Assertions.assertEquals(0, data.getAmount().getPenaltyAmount().compareTo(new BigDecimal("50.0")));
+
+        // assertEquals(data, expectedSerializedData);
+        moneyHelper.close();
+    }
+
+    private LoanRepaymentScheduleInstallment buildInstallment(Loan loan, MonetaryCurrency currency, BigDecimal principalAmount,
+            BigDecimal freeAmount, BigDecimal interestAmount, BigDecimal penaltyAmount, BigDecimal totalAmount, BigDecimal... charges) {
+
+        LoanRepaymentScheduleInstallment installment = mock(LoanRepaymentScheduleInstallment.class);
+        when(installment.getPrincipalOutstanding(any())).thenAnswer(a -> Money.of(currency, principalAmount));
+        when(installment.getInterestOutstanding(any())).thenAnswer(a -> Money.of(currency, interestAmount));
+        when(installment.getPenaltyChargesOutstanding(any())).thenAnswer(a -> Money.of(currency, penaltyAmount));
+        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.toData()).thenAnswer(a -> {
+            ChargeData chargeData = mock(ChargeData.class);
+            when(chargeData.getCurrency()).thenAnswer(b -> new CurrencyData(currency.getCode()));
+            return chargeData;
+        });
+
+        Set<LoanInstallmentCharge> installmentCharges = Arrays.stream(charges)
+                .map(amount -> buildLoanInstallmentCharge(amount, charge, loan)).collect(Collectors.toSet());
+        when(installment.getInstallmentCharges()).thenReturn(installmentCharges);
+        return installment;
+    }
+
+    private LoanInstallmentCharge buildLoanInstallmentCharge(BigDecimal amount, Charge charge, Loan loan) {
+        LoanInstallmentCharge installmentCharge = new LoanInstallmentCharge();
+        ReflectionTestUtils.setField(installmentCharge, "amount", amount);
+        ReflectionTestUtils.setField(installmentCharge, "loancharge", buildLoanCharge(loan, amount, charge));
+        return installmentCharge;
+    }
+
+    private LoanCharge buildLoanCharge(Loan loan, BigDecimal amount, Charge charge) {
+        return new LoanCharge(loan, charge, amount, amount, ChargeTimeType.TRANCHE_DISBURSEMENT, ChargeCalculationType.FLAT,
+                LocalDate.of(2022, 6, 27), ChargePaymentMode.REGULAR, 1, new BigDecimal(100), ExternalId.generate());
+    }
+}