Skip to content

Commit b688899

Browse files
authored
Merge pull request #5714
FINERACT-2560: Fix NullPointerException in calcInterestTransactionWaivedAmount
2 parents db7061d + bdba01f commit b688899

2 files changed

Lines changed: 74 additions & 2 deletions

File tree

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

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

@@ -640,7 +640,7 @@ private BigDecimal calcChargeWaivedAmount(@NonNull final Collection<LoanChargePa
640640
@NonNull final LocalDate tillDate) {
641641
return loanChargePaidBy.stream().filter(pb -> {
642642
final LoanTransaction t = pb.getLoanTransaction();
643-
return !t.isReversed() && t.isWaiveCharge() && !DateUtils.isAfter(t.getTransactionDate(), tillDate);
643+
return t != null && !t.isReversed() && t.isWaiveCharge() && !DateUtils.isAfter(t.getTransactionDate(), tillDate);
644644
}).map(LoanChargePaidBy::getAmount).reduce(BigDecimal.ZERO, MathUtil::add);
645645
}
646646

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

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,36 @@
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.List;
34+
import java.util.Set;
3035
import java.util.stream.Stream;
3136
import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
37+
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
38+
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
3239
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
40+
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
3341
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
42+
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy;
43+
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
3444
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
45+
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
3546
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
47+
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
48+
import org.junit.jupiter.api.AfterEach;
3649
import org.junit.jupiter.api.BeforeEach;
50+
import org.junit.jupiter.api.Test;
3751
import org.junit.jupiter.api.extension.ExtendWith;
3852
import org.junit.jupiter.params.ParameterizedTest;
3953
import org.junit.jupiter.params.provider.Arguments;
@@ -43,6 +57,7 @@
4357
import org.mockito.junit.jupiter.MockitoExtension;
4458
import org.mockito.junit.jupiter.MockitoSettings;
4559
import org.mockito.quality.Strictness;
60+
import org.springframework.test.util.ReflectionTestUtils;
4661

4762
@ExtendWith(MockitoExtension.class)
4863
@MockitoSettings(strictness = Strictness.LENIENT)
@@ -71,6 +86,15 @@ void setUp() {
7186
when(loan.isClosed()).thenReturn(false);
7287
when(loan.getStatus()).thenReturn(loanStatus);
7388
when(loanStatus.isOverpaid()).thenReturn(false);
89+
90+
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "test", "Test Tenant", "America/Mexico_City", null));
91+
MoneyHelper.initializeTenantRoundingMode("test", 6);
92+
}
93+
94+
@AfterEach
95+
void tearDown() {
96+
ThreadLocalContextUtil.reset();
97+
MoneyHelper.clearCache();
7498
}
7599

76100
@ParameterizedTest
@@ -95,6 +119,54 @@ void addPeriodicAccruals_ShouldNotProceed_WhenLoanIsClosedOrOverpaid(final boole
95119
verify(loan, never()).addLoanTransaction(any());
96120
}
97121

122+
@Test
123+
void calcInterestTransactionWaivedAmount_ShouldSkipMappingsWithoutTransaction() {
124+
// Given
125+
LocalDate tillDate = LocalDate.now(ZoneId.systemDefault());
126+
LoanRepaymentScheduleInstallment installment = mock(LoanRepaymentScheduleInstallment.class);
127+
LoanTransactionToRepaymentScheduleMapping nullTransactionMapping = mock(LoanTransactionToRepaymentScheduleMapping.class);
128+
LoanTransactionToRepaymentScheduleMapping interestWaiverMapping = mock(LoanTransactionToRepaymentScheduleMapping.class);
129+
LoanTransaction interestWaiverTransaction = mock(LoanTransaction.class);
130+
131+
when(nullTransactionMapping.getLoanTransaction()).thenReturn(null);
132+
when(interestWaiverMapping.getLoanTransaction()).thenReturn(interestWaiverTransaction);
133+
when(interestWaiverMapping.getInterestPortion()).thenReturn(new BigDecimal("12.34"));
134+
when(interestWaiverTransaction.isReversed()).thenReturn(false);
135+
when(interestWaiverTransaction.isInterestWaiver()).thenReturn(true);
136+
when(interestWaiverTransaction.getTransactionDate()).thenReturn(tillDate);
137+
when(installment.getLoanTransactionToRepaymentScheduleMappings()).thenReturn(Set.of(nullTransactionMapping, interestWaiverMapping));
138+
139+
// When
140+
BigDecimal result = ReflectionTestUtils.invokeMethod(accrualsProcessingService, "calcInterestTransactionWaivedAmount", installment,
141+
tillDate);
142+
143+
// Then
144+
assertThat(result).isEqualByComparingTo("12.34");
145+
}
146+
147+
@Test
148+
void calcChargeWaivedAmount_ShouldSkipMappingsWithoutTransaction() {
149+
// Given
150+
LocalDate tillDate = LocalDate.now(ZoneId.systemDefault());
151+
LoanChargePaidBy nullTransactionPaidBy = mock(LoanChargePaidBy.class);
152+
LoanChargePaidBy chargeWaiverPaidBy = mock(LoanChargePaidBy.class);
153+
LoanTransaction chargeWaiverTransaction = mock(LoanTransaction.class);
154+
155+
when(nullTransactionPaidBy.getLoanTransaction()).thenReturn(null);
156+
when(chargeWaiverPaidBy.getLoanTransaction()).thenReturn(chargeWaiverTransaction);
157+
when(chargeWaiverPaidBy.getAmount()).thenReturn(new BigDecimal("12.34"));
158+
when(chargeWaiverTransaction.isReversed()).thenReturn(false);
159+
when(chargeWaiverTransaction.isWaiveCharge()).thenReturn(true);
160+
when(chargeWaiverTransaction.getTransactionDate()).thenReturn(tillDate);
161+
162+
// When
163+
BigDecimal result = ReflectionTestUtils.invokeMethod(accrualsProcessingService, "calcChargeWaivedAmount",
164+
List.of(nullTransactionPaidBy, chargeWaiverPaidBy), tillDate);
165+
166+
// Then
167+
assertThat(result).isEqualByComparingTo("12.34");
168+
}
169+
98170
private static Stream<Arguments> loanStatusTestCases() {
99171
return Stream.of(Arguments.of(true, false), // Loan is closed
100172
Arguments.of(false, true) // Loan is overpaid

0 commit comments

Comments
 (0)