From 7408f10cb67b9d0b63a2508a128b08b54ba6e5b0 Mon Sep 17 00:00:00 2001 From: leksinomi Date: Wed, 13 Nov 2024 02:43:22 +0200 Subject: [PATCH] FINERACT-2107: Adjust event trigger on refund reversion --- .../portfolio/loanaccount/domain/Loan.java | 60 +--- ...WritePlatformServiceJpaRepositoryImpl.java | 58 +++- ...ePlatformServiceJpaRepositoryImplTest.java | 302 ++++++++++++++++++ 3 files changed, 362 insertions(+), 58 deletions(-) create mode 100644 fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImplTest.java diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java index f57235390..e039e505d 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java @@ -2360,7 +2360,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { doPostLoanTransactionChecks(loanTransaction.getTransactionDate(), loanLifecycleStateMachine); } - private ChangedTransactionDetail handleRepaymentOrRecoveryOrWaiverTransaction(final LoanTransaction loanTransaction, + public ChangedTransactionDetail handleRepaymentOrRecoveryOrWaiverTransaction(final LoanTransaction loanTransaction, final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction adjustedTransaction, final ScheduleGeneratorDTO scheduleGeneratorDTO) { ChangedTransactionDetail changedTransactionDetail = null; @@ -2681,60 +2681,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { possibleInterestToWaive.zero(), ExternalId.empty()); } - public ChangedTransactionDetail adjustExistingTransaction(final LoanTransaction newTransactionDetail, - final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction transactionForAdjustment, - final List existingTransactionIds, final List existingReversedTransactionIds, - final ScheduleGeneratorDTO scheduleGeneratorDTO, final ExternalId reversalExternalId) { - - ChangedTransactionDetail changedTransactionDetail = null; - - existingTransactionIds.addAll(findExistingTransactionIds()); - existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); - - validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_REPAYMENT_OR_WAIVER, - transactionForAdjustment.getTransactionDate()); - - if (transactionForAdjustment.isNotRepaymentLikeType() && transactionForAdjustment.isNotWaiver() - && transactionForAdjustment.isNotCreditBalanceRefund()) { - final String errorMessage = "Only (non-reversed) transactions of type repayment, waiver or credit balance refund can be adjusted."; - throw new InvalidLoanTransactionTypeException("transaction", - "adjustment.is.only.allowed.to.repayment.or.waiver.or.creditbalancerefund.transactions", errorMessage); - } - - transactionForAdjustment.reverse(reversalExternalId); - transactionForAdjustment.manuallyAdjustedOrReversed(); - - if (transactionForAdjustment.getTypeOf().equals(LoanTransactionType.MERCHANT_ISSUED_REFUND) - || transactionForAdjustment.getTypeOf().equals(LoanTransactionType.PAYOUT_REFUND)) { - getLoanTransactions().stream() // - .filter(LoanTransaction::isNotReversed) - .filter(loanTransaction -> loanTransaction.getLoanTransactionRelations().stream() - .anyMatch(relation -> relation.getRelationType().equals(LoanTransactionRelationTypeEnum.RELATED) - && relation.getToTransaction().getId().equals(transactionForAdjustment.getId()))) - .forEach(loanTransaction -> { - loanTransaction.reverse(); - loanTransaction.manuallyAdjustedOrReversed(); - }); - } - - if (isClosedWrittenOff()) { - // find write off transaction and reverse it - final LoanTransaction writeOffTransaction = findWriteOffTransaction(); - writeOffTransaction.reverse(); - } - - if (isClosedObligationsMet() || isClosedWrittenOff() || isClosedWithOutstandingAmountMarkedForReschedule()) { - loanLifecycleStateMachine.transition(LoanEvent.LOAN_ADJUST_TRANSACTION, this); - } - - if (newTransactionDetail.isRepaymentLikeType() || newTransactionDetail.isInterestWaiver()) { - changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(newTransactionDetail, loanLifecycleStateMachine, - transactionForAdjustment, scheduleGeneratorDTO); - } - - return changedTransactionDetail; - } - public ChangedTransactionDetail undoWrittenOff(LoanLifecycleStateMachine loanLifecycleStateMachine, final List existingTransactionIds, final List existingReversedTransactionIds, final ScheduleGeneratorDTO scheduleGeneratorDTO) { @@ -3087,7 +3033,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { return getStatus().isClosedWrittenOff(); } - private boolean isClosedWithOutstandingAmountMarkedForReschedule() { + public boolean isClosedWithOutstandingAmountMarkedForReschedule() { return getStatus().isClosedWithOutsandingAmountMarkedForReschedule(); } @@ -3777,7 +3723,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { this.loanTransactions.remove(loanTransaction); } - private void validateActivityNotBeforeClientOrGroupTransferDate(final LoanEvent event, final LocalDate activityDate) { + public void validateActivityNotBeforeClientOrGroupTransferDate(final LoanEvent event, final LocalDate activityDate) { if (this.client != null && this.client.getOfficeJoiningDate() != null) { final LocalDate clientOfficeJoiningDate = this.client.getOfficeJoiningDate(); if (DateUtils.isBefore(activityDate, clientOfficeJoiningDate)) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index faeb302a6..fe2b4b604 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -1489,7 +1489,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf loanTransactionValidator.validateRepaymentDateIsOnNonWorkingDay(newTransactionDetail.getTransactionDate(), holidayDetailDTO.getWorkingDays(), holidayDetailDTO.isAllowTransactionsOnNonWorkingDay()); - final ChangedTransactionDetail changedTransactionDetail = loan.adjustExistingTransaction(newTransactionDetail, + final ChangedTransactionDetail changedTransactionDetail = adjustExistingTransaction(loan, newTransactionDetail, loanLifecycleStateMachine, transactionToAdjust, existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO, reversalTxnExternalId); @@ -1583,6 +1583,62 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf .with(changes).build(); } + public ChangedTransactionDetail adjustExistingTransaction(final Loan loan, final LoanTransaction newTransactionDetail, + final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction transactionForAdjustment, + final List existingTransactionIds, final List existingReversedTransactionIds, + final ScheduleGeneratorDTO scheduleGeneratorDTO, final ExternalId reversalExternalId) { + + ChangedTransactionDetail changedTransactionDetail = null; + + existingTransactionIds.addAll(loan.findExistingTransactionIds()); + existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds()); + + loan.validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_REPAYMENT_OR_WAIVER, + transactionForAdjustment.getTransactionDate()); + + if (transactionForAdjustment.isNotRepaymentLikeType() && transactionForAdjustment.isNotWaiver() + && transactionForAdjustment.isNotCreditBalanceRefund()) { + final String errorMessage = "Only (non-reversed) transactions of type repayment, waiver or credit balance refund can be adjusted."; + throw new InvalidLoanTransactionTypeException("transaction", + "adjustment.is.only.allowed.to.repayment.or.waiver.or.creditbalancerefund.transactions", errorMessage); + } + + transactionForAdjustment.reverse(reversalExternalId); + transactionForAdjustment.manuallyAdjustedOrReversed(); + + if (transactionForAdjustment.getTypeOf().equals(LoanTransactionType.MERCHANT_ISSUED_REFUND) + || transactionForAdjustment.getTypeOf().equals(LoanTransactionType.PAYOUT_REFUND)) { + loan.getLoanTransactions().stream() // + .filter(LoanTransaction::isNotReversed) + .filter(loanTransaction -> loanTransaction.getLoanTransactionRelations().stream() + .anyMatch(relation -> relation.getRelationType().equals(LoanTransactionRelationTypeEnum.RELATED) + && relation.getToTransaction().getId().equals(transactionForAdjustment.getId()))) + .forEach(loanTransaction -> { + loanTransaction.reverse(); + loanTransaction.manuallyAdjustedOrReversed(); + LoanAdjustTransactionBusinessEvent.Data eventData = new LoanAdjustTransactionBusinessEvent.Data(loanTransaction); + businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(eventData)); + }); + } + + if (loan.isClosedWrittenOff()) { + // find write off transaction and reverse it + final LoanTransaction writeOffTransaction = loan.findWriteOffTransaction(); + writeOffTransaction.reverse(); + } + + if (loan.isClosedObligationsMet() || loan.isClosedWrittenOff() || loan.isClosedWithOutstandingAmountMarkedForReschedule()) { + loanLifecycleStateMachine.transition(LoanEvent.LOAN_ADJUST_TRANSACTION, loan); + } + + if (newTransactionDetail.isRepaymentLikeType() || newTransactionDetail.isInterestWaiver()) { + changedTransactionDetail = loan.handleRepaymentOrRecoveryOrWaiverTransaction(newTransactionDetail, loanLifecycleStateMachine, + transactionForAdjustment, scheduleGeneratorDTO); + } + + return changedTransactionDetail; + } + @Transactional @Override public CommandProcessingResult chargebackLoanTransaction(final Long loanId, final Long transactionId, final JsonCommand command) { diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImplTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImplTest.java new file mode 100644 index 000000000..828c608c5 --- /dev/null +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImplTest.java @@ -0,0 +1,302 @@ +/** + * 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.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService; +import org.apache.fineract.cob.service.LoanAccountLockService; +import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper; +import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; +import org.apache.fineract.infrastructure.core.domain.ExternalId; +import org.apache.fineract.infrastructure.core.exception.ErrorHandler; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; +import org.apache.fineract.infrastructure.dataqueries.service.EntityDatatableChecksWritePlatformService; +import org.apache.fineract.infrastructure.event.business.domain.loan.LoanAdjustTransactionBusinessEvent; +import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.organisation.holiday.domain.HolidayRepositoryWrapper; +import org.apache.fineract.organisation.teller.data.CashierTransactionDataValidator; +import org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepositoryWrapper; +import org.apache.fineract.portfolio.account.domain.AccountAssociationsRepository; +import org.apache.fineract.portfolio.account.domain.AccountTransferDetailRepository; +import org.apache.fineract.portfolio.account.domain.AccountTransferRepository; +import org.apache.fineract.portfolio.account.service.AccountAssociationsReadPlatformService; +import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService; +import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService; +import org.apache.fineract.portfolio.calendar.domain.CalendarInstanceRepository; +import org.apache.fineract.portfolio.calendar.domain.CalendarRepository; +import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO; +import org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService; +import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallmentRepository; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; +import org.apache.fineract.portfolio.loanaccount.domain.LoanSummaryWrapper; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationRepository; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType; +import org.apache.fineract.portfolio.loanaccount.guarantor.service.GuarantorDomainService; +import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryWritePlatformService; +import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationValidator; +import org.apache.fineract.portfolio.loanaccount.serialization.LoanTransactionValidator; +import org.apache.fineract.portfolio.loanaccount.serialization.LoanUpdateCommandFromApiJsonDeserializer; +import org.apache.fineract.portfolio.note.domain.NoteRepository; +import org.apache.fineract.portfolio.paymentdetail.service.PaymentDetailWritePlatformService; +import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecksRepository; +import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.service.RepaymentWithPostDatedChecksAssembler; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class LoanWritePlatformServiceJpaRepositoryImplTest { + + @InjectMocks + private LoanWritePlatformServiceJpaRepositoryImpl loanWritePlatformService; + + @Mock + private LoanRepaymentScheduleTransactionProcessorFactory transactionProcessorFactory; + @Mock + private PlatformSecurityContext context; + @Mock + private LoanTransactionValidator loanTransactionValidator; + @Mock + private LoanUpdateCommandFromApiJsonDeserializer loanUpdateCommandFromApiJsonDeserializer; + @Mock + private LoanRepositoryWrapper loanRepositoryWrapper; + @Mock + private LoanAccountDomainService loanAccountDomainService; + @Mock + private NoteRepository noteRepository; + @Mock + private LoanTransactionRepository loanTransactionRepository; + @Mock + private LoanTransactionRelationRepository loanTransactionRelationRepository; + @Mock + private LoanAssembler loanAssembler; + @Mock + private JournalEntryWritePlatformService journalEntryWritePlatformService; + @Mock + private CalendarInstanceRepository calendarInstanceRepository; + @Mock + private PaymentDetailWritePlatformService paymentDetailWritePlatformService; + @Mock + private HolidayRepositoryWrapper holidayRepository; + @Mock + private ConfigurationDomainService configurationDomainService; + @Mock + private WorkingDaysRepositoryWrapper workingDaysRepository; + @Mock + private AccountTransfersWritePlatformService accountTransfersWritePlatformService; + @Mock + private AccountTransfersReadPlatformService accountTransfersReadPlatformService; + @Mock + private AccountAssociationsReadPlatformService accountAssociationsReadPlatformService; + @Mock + private LoanReadPlatformService loanReadPlatformService; + @Mock + private FromJsonHelper fromApiJsonHelper; + @Mock + private CalendarRepository calendarRepository; + @Mock + private LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService; + @Mock + private LoanApplicationValidator loanApplicationCommandFromApiJsonHelper; + @Mock + private AccountAssociationsRepository accountAssociationRepository; + @Mock + private AccountTransferDetailRepository accountTransferDetailRepository; + @Mock + private BusinessEventNotifierService businessEventNotifierService; + @Mock + private GuarantorDomainService guarantorDomainService; + @Mock + private LoanUtilService loanUtilService; + @Mock + private LoanSummaryWrapper loanSummaryWrapper; + @Mock + private EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService; + @Mock + private LoanRepaymentScheduleTransactionProcessorFactory transactionProcessingStrategy; + @Mock + private CodeValueRepositoryWrapper codeValueRepository; + @Mock + private CashierTransactionDataValidator cashierTransactionDataValidator; + @Mock + private GLIMAccountInfoRepository glimRepository; + @Mock + private LoanRepository loanRepository; + @Mock + private RepaymentWithPostDatedChecksAssembler repaymentWithPostDatedChecksAssembler; + @Mock + private PostDatedChecksRepository postDatedChecksRepository; + @Mock + private LoanRepaymentScheduleInstallmentRepository loanRepaymentScheduleInstallmentRepository; + @Mock + private LoanLifecycleStateMachine loanLifecycleStateMachine; + @Mock + private LoanAccountLockService loanAccountLockService; + @Mock + private ExternalIdFactory externalIdFactory; + @Mock + private ReplayedTransactionBusinessEventService replayedTransactionBusinessEventService; + @Mock + private LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService; + @Mock + private ErrorHandler errorHandler; + @Mock + private LoanDownPaymentHandlerService loanDownPaymentHandlerService; + @Mock + private AccountTransferRepository accountTransferRepository; + @Mock + private LoanTransactionAssembler loanTransactionAssembler; + @Mock + private LoanAccrualsProcessingService loanAccrualsProcessingService; + + @Test + void givenMerchantIssuedRefundTransactionWithRelatedTransactions_whenAdjustExistingTransaction_thenRelatedTransactionsAreReversedAndEventsTriggered() { + // Arrange + Loan loan = mock(Loan.class); + LoanTransaction transactionForAdjustment = mock(LoanTransaction.class); + LoanTransaction newTransactionDetail = mock(LoanTransaction.class); + ScheduleGeneratorDTO scheduleGeneratorDTO = mock(ScheduleGeneratorDTO.class); + ExternalId reversalExternalId = ExternalId.generate(); + + List existingTransactionIds = new ArrayList<>(); + List existingReversedTransactionIds = new ArrayList<>(); + + // Mock transaction type + when(transactionForAdjustment.getTypeOf()).thenReturn(LoanTransactionType.MERCHANT_ISSUED_REFUND); + when(transactionForAdjustment.isNotRepaymentLikeType()).thenReturn(false); + + // Mock transaction date + when(transactionForAdjustment.getTransactionDate()).thenReturn(LocalDate.now(ZoneId.systemDefault())); + + // Mock transaction ID to prevent NullPointerException + when(transactionForAdjustment.getId()).thenReturn(1L); + + // Mock loan transactions + LoanTransaction relatedTransaction = mock(LoanTransaction.class); + when(relatedTransaction.isNotReversed()).thenReturn(true); + + LoanTransactionRelation transactionRelation = mock(LoanTransactionRelation.class); + when(transactionRelation.getRelationType()).thenReturn(LoanTransactionRelationTypeEnum.RELATED); + when(transactionRelation.getToTransaction()).thenReturn(transactionForAdjustment); + + Set transactionRelations = new HashSet<>(); + transactionRelations.add(transactionRelation); + + when(relatedTransaction.getLoanTransactionRelations()).thenReturn(transactionRelations); + + List loanTransactions = Arrays.asList(transactionForAdjustment, relatedTransaction); + when(loan.getLoanTransactions()).thenReturn(loanTransactions); + + // Mock methods called inside adjustExistingTransaction + when(loan.findExistingTransactionIds()).thenReturn(Collections.emptyList()); + when(loan.findExistingReversedTransactionIds()).thenReturn(Collections.emptyList()); + doNothing().when(loan).validateActivityNotBeforeClientOrGroupTransferDate(any(), any()); + when(loan.isClosedWrittenOff()).thenReturn(false); + when(loan.isClosedObligationsMet()).thenReturn(false); + when(loan.isClosedWithOutstandingAmountMarkedForReschedule()).thenReturn(false); + when(newTransactionDetail.isRepaymentLikeType()).thenReturn(true); + + // Act + loanWritePlatformService.adjustExistingTransaction(loan, newTransactionDetail, loanLifecycleStateMachine, transactionForAdjustment, + existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO, reversalExternalId); + + // Assert + // Verify that related transaction is reversed and event is triggered + verify(relatedTransaction).reverse(); + verify(relatedTransaction).manuallyAdjustedOrReversed(); + + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(LoanAdjustTransactionBusinessEvent.class); + verify(businessEventNotifierService).notifyPostBusinessEvent(eventCaptor.capture()); + + LoanAdjustTransactionBusinessEvent event = eventCaptor.getValue(); + assertEquals(relatedTransaction, event.get().getTransactionToAdjust()); + } + + @Test + void givenNonMerchantIssuedRefundTransaction_whenAdjustExistingTransaction_thenNoRelatedTransactionsReversed() { + // Arrange + Loan loan = mock(Loan.class); + LoanTransaction transactionForAdjustment = mock(LoanTransaction.class); + LoanTransaction newTransactionDetail = mock(LoanTransaction.class); + ScheduleGeneratorDTO scheduleGeneratorDTO = mock(ScheduleGeneratorDTO.class); + ExternalId reversalExternalId = ExternalId.generate(); + + List existingTransactionIds = new ArrayList<>(); + List existingReversedTransactionIds = new ArrayList<>(); + + // Mock transaction type + when(transactionForAdjustment.getTypeOf()).thenReturn(LoanTransactionType.REPAYMENT); + when(transactionForAdjustment.isNotRepaymentLikeType()).thenReturn(false); + + // Mock transaction date + when(transactionForAdjustment.getTransactionDate()).thenReturn(LocalDate.now(ZoneId.systemDefault())); + + // Mock loan transactions + LoanTransaction unrelatedTransaction = mock(LoanTransaction.class); + + // Mock methods called inside adjustExistingTransaction + when(loan.findExistingTransactionIds()).thenReturn(Collections.emptyList()); + when(loan.findExistingReversedTransactionIds()).thenReturn(Collections.emptyList()); + doNothing().when(loan).validateActivityNotBeforeClientOrGroupTransferDate(any(), any()); + when(loan.isClosedWrittenOff()).thenReturn(false); + when(loan.isClosedObligationsMet()).thenReturn(false); + when(loan.isClosedWithOutstandingAmountMarkedForReschedule()).thenReturn(false); + when(newTransactionDetail.isRepaymentLikeType()).thenReturn(true); + + // Act + loanWritePlatformService.adjustExistingTransaction(loan, newTransactionDetail, loanLifecycleStateMachine, transactionForAdjustment, + existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO, reversalExternalId); + + // Assert + // Verify that no related transactions are reversed + verify(unrelatedTransaction, never()).reverse(); + verify(unrelatedTransaction, never()).manuallyAdjustedOrReversed(); + verify(businessEventNotifierService, never()).notifyPostBusinessEvent(any(LoanAdjustTransactionBusinessEvent.class)); + } + +}