Skip to content

Commit 19edc7c

Browse files
committed
FINERACT-2560: Fix NullPointerException in calcInterestTransactionWaivedAmount by adding null check for LoanTransaction
1 parent b26571b commit 19edc7c

2 files changed

Lines changed: 48 additions & 1 deletion

File tree

fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,7 @@ private BigDecimal calcInterestTransactionWaivedAmount(@NonNull LoanRepaymentSch
529529
Predicate<LoanTransaction> transactionPredicate = t -> !t.isReversed() && t.isInterestWaiver()
530530
&& !DateUtils.isAfter(t.getTransactionDate(), tillDate);
531531
return installment.getLoanTransactionToRepaymentScheduleMappings().stream()
532-
.filter(tm -> transactionPredicate.test(tm.getLoanTransaction()))
532+
.filter(tm -> tm.getLoanTransaction() != null && transactionPredicate.test(tm.getLoanTransaction()))
533533
.map(LoanTransactionToRepaymentScheduleMapping::getInterestPortion).reduce(BigDecimal.ZERO, MathUtil::add);
534534
}
535535

fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImplTest.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,34 @@
1818
*/
1919
package org.apache.fineract.portfolio.loanaccount.service;
2020

21+
import static org.assertj.core.api.Assertions.assertThat;
2122
import static org.mockito.ArgumentMatchers.any;
23+
import static org.mockito.Mockito.mock;
2224
import static org.mockito.Mockito.never;
2325
import static org.mockito.Mockito.times;
2426
import static org.mockito.Mockito.verify;
2527
import static org.mockito.Mockito.verifyNoInteractions;
2628
import static org.mockito.Mockito.when;
2729

30+
import java.math.BigDecimal;
2831
import java.time.LocalDate;
2932
import java.time.ZoneId;
33+
import java.util.Set;
3034
import java.util.stream.Stream;
3135
import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
36+
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
37+
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
3238
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
39+
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
3340
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
41+
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
3442
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
43+
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
3544
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
45+
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
46+
import org.junit.jupiter.api.AfterEach;
3647
import org.junit.jupiter.api.BeforeEach;
48+
import org.junit.jupiter.api.Test;
3749
import org.junit.jupiter.api.extension.ExtendWith;
3850
import org.junit.jupiter.params.ParameterizedTest;
3951
import org.junit.jupiter.params.provider.Arguments;
@@ -43,6 +55,7 @@
4355
import org.mockito.junit.jupiter.MockitoExtension;
4456
import org.mockito.junit.jupiter.MockitoSettings;
4557
import org.mockito.quality.Strictness;
58+
import org.springframework.test.util.ReflectionTestUtils;
4659

4760
@ExtendWith(MockitoExtension.class)
4861
@MockitoSettings(strictness = Strictness.LENIENT)
@@ -71,6 +84,15 @@ void setUp() {
7184
when(loan.isClosed()).thenReturn(false);
7285
when(loan.getStatus()).thenReturn(loanStatus);
7386
when(loanStatus.isOverpaid()).thenReturn(false);
87+
88+
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "test", "Test Tenant", "America/Mexico_City", null));
89+
MoneyHelper.initializeTenantRoundingMode("test", 6);
90+
}
91+
92+
@AfterEach
93+
void tearDown() {
94+
ThreadLocalContextUtil.reset();
95+
MoneyHelper.clearCache();
7496
}
7597

7698
@ParameterizedTest
@@ -95,6 +117,31 @@ void addPeriodicAccruals_ShouldNotProceed_WhenLoanIsClosedOrOverpaid(final boole
95117
verify(loan, never()).addLoanTransaction(any());
96118
}
97119

120+
@Test
121+
void calcInterestTransactionWaivedAmount_ShouldSkipMappingsWithoutTransaction() {
122+
// Given
123+
LocalDate tillDate = LocalDate.now(ZoneId.systemDefault());
124+
LoanRepaymentScheduleInstallment installment = mock(LoanRepaymentScheduleInstallment.class);
125+
LoanTransactionToRepaymentScheduleMapping nullTransactionMapping = mock(LoanTransactionToRepaymentScheduleMapping.class);
126+
LoanTransactionToRepaymentScheduleMapping interestWaiverMapping = mock(LoanTransactionToRepaymentScheduleMapping.class);
127+
LoanTransaction interestWaiverTransaction = mock(LoanTransaction.class);
128+
129+
when(nullTransactionMapping.getLoanTransaction()).thenReturn(null);
130+
when(interestWaiverMapping.getLoanTransaction()).thenReturn(interestWaiverTransaction);
131+
when(interestWaiverMapping.getInterestPortion()).thenReturn(new BigDecimal("12.34"));
132+
when(interestWaiverTransaction.isReversed()).thenReturn(false);
133+
when(interestWaiverTransaction.isInterestWaiver()).thenReturn(true);
134+
when(interestWaiverTransaction.getTransactionDate()).thenReturn(tillDate);
135+
when(installment.getLoanTransactionToRepaymentScheduleMappings()).thenReturn(Set.of(nullTransactionMapping, interestWaiverMapping));
136+
137+
// When
138+
BigDecimal result = ReflectionTestUtils.invokeMethod(accrualsProcessingService, "calcInterestTransactionWaivedAmount", installment,
139+
tillDate);
140+
141+
// Then
142+
assertThat(result).isEqualByComparingTo("12.34");
143+
}
144+
98145
private static Stream<Arguments> loanStatusTestCases() {
99146
return Stream.of(Arguments.of(true, false), // Loan is closed
100147
Arguments.of(false, true) // Loan is overpaid

0 commit comments

Comments
 (0)