From 6bb68051ce6619d619fa10da4a5ebf1ac1a11290 Mon Sep 17 00:00:00 2001 From: mariiaKraievska Date: Fri, 14 Feb 2025 13:34:57 +0200 Subject: [PATCH] FINERACT-2327: Forbid accrual and accrual activity creation for closed, overpaid and cancelled loans --- ...nAccrualActivityProcessingServiceImpl.java | 2 +- .../LoanAccrualsProcessingServiceImpl.java | 3 + ...rualActivityProcessingServiceImplTest.java | 110 ++++++++++++++++++ ...LoanAccrualsProcessingServiceImplTest.java | 102 ++++++++++++++++ 4 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImplTest.java create mode 100644 fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImplTest.java diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImpl.java index 4da472e38..310c21a6d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImpl.java @@ -65,7 +65,7 @@ public class LoanAccrualActivityProcessingServiceImpl implements LoanAccrualActi @Override public void makeAccrualActivityTransaction(@NotNull Loan loan, @NotNull LocalDate currentDate) { - if (!loan.getLoanProductRelatedDetail().isEnableAccrualActivityPosting()) { + if (!loan.getLoanProductRelatedDetail().isEnableAccrualActivityPosting() || loan.isClosed() || loan.getStatus().isOverpaid()) { return; } // check if loan has installment in the past or due on current date diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java index ba140eff1..b3f9161d7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java @@ -129,6 +129,9 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing @Override @Transactional public void addPeriodicAccruals(@NotNull LocalDate tillDate, @NotNull Loan loan) { + if (loan.isClosed() || loan.getStatus().isOverpaid()) { + return; + } addAccruals(loan, tillDate, true, false, true); } diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImplTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImplTest.java new file mode 100644 index 000000000..47e2f860d --- /dev/null +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImplTest.java @@ -0,0 +1,110 @@ +/** + * 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.loanaccount.service; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.stream.Stream; +import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService; +import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus; +import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +public class LoanAccrualActivityProcessingServiceImplTest { + + @InjectMocks + private LoanAccrualActivityProcessingServiceImpl accrualActivityProcessingService; + + @Mock + private Loan loan; + + @Mock + private LoanStatus loanStatus; + + @Mock + private LoanProductRelatedDetail loanProductRelatedDetail; + + @Mock + private BusinessEventNotifierService businessEventNotifierService; + + @Mock + private LoanAccountDomainService loanAccountDomainService; + + @Mock + private LoanTransactionAssembler loanTransactionAssembler; + + @BeforeEach + void setUp() { + when(loan.isClosed()).thenReturn(false); + when(loan.getStatus()).thenReturn(loanStatus); + when(loanStatus.isOverpaid()).thenReturn(false); + } + + @ParameterizedTest + @MethodSource("loanStatusTestCases") + void addPeriodicAccruals_ShouldNotProceed_WhenLoanIsClosedOrOverpaid(final boolean isClosed, final boolean isOverpaid) { + // Given + final LocalDate currentDate = LocalDate.now(ZoneId.systemDefault()); + when(loan.isClosed()).thenReturn(isClosed); + + when(loan.getStatus()).thenReturn(loanStatus); + when(loanStatus.isOverpaid()).thenReturn(isOverpaid); + + when(loan.getLoanProductRelatedDetail()).thenReturn(loanProductRelatedDetail); + when(loanProductRelatedDetail.isEnableAccrualActivityPosting()).thenReturn(true); + + // When + accrualActivityProcessingService.makeAccrualActivityTransaction(loan, currentDate); + + // Then + verify(loan, times(1)).isClosed(); + + verify(loan, never()).getRepaymentScheduleInstallments(any()); + verify(loan, never()).addLoanTransaction(any()); + verify(loanAccountDomainService, never()).saveLoanTransactionWithDataIntegrityViolationChecks(any()); + verify(loanTransactionAssembler, never()).assembleAccrualActivityTransaction(any(), any(), any()); + verify(businessEventNotifierService, never()).notifyPreBusinessEvent(any()); + verify(businessEventNotifierService, never()).notifyPostBusinessEvent(any()); + } + + private static Stream loanStatusTestCases() { + return Stream.of(Arguments.of(true, false), // Loan is closed + Arguments.of(false, true) // Loan is overpaid + ); + } +} diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImplTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImplTest.java new file mode 100644 index 000000000..d007d54c8 --- /dev/null +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImplTest.java @@ -0,0 +1,102 @@ +/** + * 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.loanaccount.service; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.stream.Stream; +import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService; +import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +public class LoanAccrualsProcessingServiceImplTest { + + @InjectMocks + private LoanAccrualsProcessingServiceImpl accrualsProcessingService; + + @Mock + private Loan loan; + + @Mock + private LoanStatus loanStatus; + + @Mock + private BusinessEventNotifierService businessEventNotifierService; + + @Mock + private LoanTransactionRepository loanTransactionRepository; + + @Mock + private JournalEntryWritePlatformService journalEntryWritePlatformService; + + @BeforeEach + void setUp() { + when(loan.isClosed()).thenReturn(false); + when(loan.getStatus()).thenReturn(loanStatus); + when(loanStatus.isOverpaid()).thenReturn(false); + } + + @ParameterizedTest + @MethodSource("loanStatusTestCases") + void addPeriodicAccruals_ShouldNotProceed_WhenLoanIsClosedOrOverpaid(final boolean isClosed, final boolean isOverpaid) { + // Given + final LocalDate tillDate = LocalDate.now(ZoneId.systemDefault()); + when(loan.isClosed()).thenReturn(isClosed); + + when(loan.getStatus()).thenReturn(loanStatus); + when(loanStatus.isOverpaid()).thenReturn(isOverpaid); + + // When + accrualsProcessingService.addPeriodicAccruals(tillDate, loan); + + // Then + verify(loan, times(1)).isClosed(); + + verify(loanTransactionRepository, never()).saveAndFlush(any()); + verify(journalEntryWritePlatformService, never()).createJournalEntriesForLoan(any()); + verify(businessEventNotifierService, never()).notifyPostBusinessEvent(any()); + verify(loan, never()).addLoanTransaction(any()); + } + + private static Stream loanStatusTestCases() { + return Stream.of(Arguments.of(true, false), // Loan is closed + Arguments.of(false, true) // Loan is overpaid + ); + } +}