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 885316690 FINERACT-1981: Rate factor using simple interest for EMI
calculation
885316690 is described below
commit 885316690e59f24d1fc686147201228c64ca81cc
Author: Jose Alberto Hernandez <[email protected]>
AuthorDate: Thu May 23 22:58:22 2024 -0600
FINERACT-1981: Rate factor using simple interest for EMI calculation
---
.../infrastructure/core/service/DateUtils.java | 11 +++
.../ratefactor/RateFactorFunctions.java | 50 ++++++++++
.../ratefactor/RateFactorFunctionsTest.java | 107 +++++++++++++++++++++
3 files changed, 168 insertions(+)
diff --git
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java
index c8a9ccb6f..e972204cd 100644
---
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java
+++
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java
@@ -24,6 +24,7 @@ import jakarta.validation.constraints.NotNull;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
+import java.time.Year;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
@@ -34,6 +35,7 @@ import java.util.Locale;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import
org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.portfolio.common.domain.DaysInYearType;
public final class DateUtils {
@@ -393,4 +395,13 @@ public final class DateUtils {
}
return formatter;
}
+
+ public static Integer daysInYear(final DaysInYearType daysInYear, final
LocalDate referenceDate) {
+ return daysInYear.isActual() ?
DateUtils.getDaysInYear(referenceDate.getYear()) : daysInYear.getValue();
+ }
+
+ public static Integer getDaysInYear(final Integer year) {
+ return Year.isLeap(year) ? 366 : 365;
+ }
+
}
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/ratefactor/RateFactorFunctions.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/ratefactor/RateFactorFunctions.java
new file mode 100644
index 000000000..c663d4fa7
--- /dev/null
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/ratefactor/RateFactorFunctions.java
@@ -0,0 +1,50 @@
+/**
+ * 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.loanproduct.ratefactor;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+
+public class RateFactorFunctions {
+
+ protected RateFactorFunctions() {}
+
+ /**
+ * To calculate the monthly payment, we first need to calculate something
called the Rate Factor. We're going to be
+ * using simple interest. The Rate Factor for simple interest is
calculated by the following formula:
+ *
+ *
+ * R = 1 + (r * d / y)
+ *
+ * @param interestRate
+ * (r)
+ * @param daysInPeriod
+ * (d)
+ * @param daysInYear
+ * (y)
+ */
+ public static BigDecimal rateFactor(final BigDecimal interestRate, final
Long daysInPeriod, final Integer daysInYear,
+ final MathContext mc) {
+ final BigDecimal daysPeriod = BigDecimal.valueOf(daysInPeriod);
+ final BigDecimal daysYear = BigDecimal.valueOf(daysInYear);
+
+ return
BigDecimal.ONE.add(interestRate.multiply(daysPeriod.divide(daysYear, mc), mc),
mc);
+ }
+
+}
diff --git
a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/ratefactor/RateFactorFunctionsTest.java
b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/ratefactor/RateFactorFunctionsTest.java
new file mode 100644
index 000000000..b535992d7
--- /dev/null
+++
b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/ratefactor/RateFactorFunctionsTest.java
@@ -0,0 +1,107 @@
+/**
+ * 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.loanproduct.ratefactor;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.math.RoundingMode;
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
+import org.apache.fineract.portfolio.common.domain.DaysInYearType;
+import
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.jetbrains.annotations.NotNull;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+class RateFactorFunctionsTest {
+
+ private static MockedStatic<MoneyHelper> moneyHelper =
Mockito.mockStatic(MoneyHelper.class);
+
+ private static List<LoanRepaymentScheduleInstallment> periods;
+ private final BigDecimal interestRate = BigDecimal.valueOf(0.09482);
+
+ @BeforeAll
+ public static void init() {
+ periods = new ArrayList<>();
+ LocalDate startDate = LocalDate.of(2024, 01, 1);
+ periods.add(createPeriod(1, startDate, startDate.plusMonths(1)));
+ periods.add(createPeriod(2, startDate.plusMonths(1),
startDate.plusMonths(2)));
+ periods.add(createPeriod(3, startDate.plusMonths(2),
startDate.plusMonths(3)));
+ periods.add(createPeriod(4, startDate.plusMonths(3),
startDate.plusMonths(4)));
+ periods.add(createPeriod(5, startDate.plusMonths(4),
startDate.plusMonths(5)));
+ periods.add(createPeriod(6, startDate.plusMonths(5),
startDate.plusMonths(6)));
+
+ // When
+ moneyHelper.when(() ->
MoneyHelper.getRoundingMode()).thenReturn(RoundingMode.HALF_EVEN);
+ moneyHelper.when(() -> MoneyHelper.getMathContext()).thenReturn(new
MathContext(8, RoundingMode.HALF_EVEN));
+ }
+
+ @Test
+ public void testRateFactorFunctionDay365() {
+ // Given
+ final DaysInYearType daysInYearType = DaysInYearType.DAYS_365;
+ final String[] expectedValues = new String[] { "1.0080532",
"1.0075336", "1.0080532", "1.0077934", "1.0080532", "1.0077934" };
+
+ // Then
+ for (LoanRepaymentScheduleInstallment period : periods) {
+ final Long daysInPeriod =
DateUtils.getDifferenceInDays(period.getFromDate(), period.getDueDate());
+ final Integer daysInYear = DateUtils.daysInYear(daysInYearType,
period.getFromDate());
+ BigDecimal rateFactor =
RateFactorFunctions.rateFactor(interestRate, daysInPeriod, daysInYear,
MoneyHelper.getMathContext());
+
+
Assertions.assertEquals(expectedValues[period.getInstallmentNumber() - 1],
rateFactor.toString());
+ }
+ }
+
+ @Test
+ public void testRateFactorFunctionActual() {
+ // Given
+ final DaysInYearType daysInYearType = DaysInYearType.ACTUAL;
+ final String[] expectedValues = new String[] { "1.0080312",
"1.0075131", "1.0080312", "1.0077721", "1.0080312", "1.0077721" };
+
+ // Then
+ for (LoanRepaymentScheduleInstallment period : periods) {
+ final Long daysInPeriod =
DateUtils.getDifferenceInDays(period.getFromDate(), period.getDueDate());
+ final Integer daysInYear = DateUtils.daysInYear(daysInYearType,
period.getFromDate());
+
+ BigDecimal rateFactor =
RateFactorFunctions.rateFactor(interestRate, daysInPeriod, daysInYear,
MoneyHelper.getMathContext());
+
+
Assertions.assertEquals(expectedValues[period.getInstallmentNumber() - 1],
rateFactor.toString());
+ }
+ }
+
+ @NotNull
+ private static LoanRepaymentScheduleInstallment createPeriod(int periodId,
LocalDate start, LocalDate end) {
+ LoanRepaymentScheduleInstallment period =
Mockito.mock(LoanRepaymentScheduleInstallment.class);
+ Mockito.when(period.getInstallmentNumber()).thenReturn(periodId);
+ Mockito.when(period.getFromDate()).thenReturn(start);
+ Mockito.when(period.getDueDate()).thenReturn(end);
+
+ return period;
+ }
+
+}