FINERACT-2233: Rework journal entry handling logic in Loan module

This commit is contained in:
mariiaKraievska
2025-08-19 13:19:40 +02:00
committed by Adam Saghy
parent 942ad0183c
commit ace59352fa
37 changed files with 609 additions and 971 deletions
@@ -39,11 +39,11 @@ import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferLoanMapping
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferLoanMappingRepository;
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferRepository;
import org.apache.fineract.investor.domain.LoanOwnershipTransferBusinessEvent;
import org.apache.fineract.investor.service.AccountingService;
import org.apache.fineract.investor.service.DelayedSettlementAttributeService;
import org.apache.fineract.investor.service.ExternalAssetOwnerTransferOutstandingInterestCalculation;
import org.apache.fineract.investor.service.LoanTransferabilityService;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.service.LoanJournalEntryPoster;
import org.springframework.context.annotation.Conditional;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Component;
@@ -61,7 +61,7 @@ public class LoanAccountOwnerTransferBusinessStep implements LoanCOBBusinessStep
ExternalTransferStatus.BUYBACK);
private final ExternalAssetOwnerTransferRepository externalAssetOwnerTransferRepository;
private final ExternalAssetOwnerTransferLoanMappingRepository externalAssetOwnerTransferLoanMappingRepository;
private final AccountingService accountingService;
private final LoanJournalEntryPoster loanJournalEntryPoster;
private final BusinessEventNotifierService businessEventNotifierService;
private final LoanTransferabilityService loanTransferabilityService;
private final DelayedSettlementAttributeService delayedSettlementAttributeService;
@@ -148,7 +148,7 @@ public class LoanAccountOwnerTransferBusinessStep implements LoanCOBBusinessStep
externalAssetOwnerTransferRepository.save(activeExternalAssetOwnerTransfer);
buybackExternalAssetOwnerTransfer = externalAssetOwnerTransferRepository.save(buybackExternalAssetOwnerTransfer);
externalAssetOwnerTransferLoanMappingRepository.deleteByLoanIdAndOwnerTransfer(loan.getId(), activeExternalAssetOwnerTransfer);
accountingService.createJournalEntriesForBuybackAssetTransfer(loan, buybackExternalAssetOwnerTransfer);
loanJournalEntryPoster.postJournalEntriesForExternalOwnerTransfer(loan, buybackExternalAssetOwnerTransfer, null);
return buybackExternalAssetOwnerTransfer;
}
@@ -174,7 +174,7 @@ public class LoanAccountOwnerTransferBusinessStep implements LoanCOBBusinessStep
ExternalTransferStatus activeStatus = determineActiveStatus(externalAssetOwnerTransfer);
ExternalAssetOwnerTransfer newTransfer = activatePendingEntry(settlementDate, externalAssetOwnerTransfer, activeStatus);
accountingService.createJournalEntriesForSaleAssetTransfer(loan, newTransfer, previousOwner);
loanJournalEntryPoster.postJournalEntriesForExternalOwnerTransfer(loan, newTransfer, previousOwner);
return newTransfer;
}
@@ -47,6 +47,7 @@ import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferLoanMapping
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferRepository;
import org.apache.fineract.investor.domain.LoanOwnershipTransferBusinessEvent;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.service.LoanJournalEntryPoster;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@@ -60,7 +61,7 @@ public class LoanAccountOwnerTransferServiceImpl implements LoanAccountOwnerTran
public static final LocalDate FUTURE_DATE_9999_12_31 = LocalDate.of(9999, 12, 31);
private final ExternalAssetOwnerTransferRepository externalAssetOwnerTransferRepository;
private final ExternalAssetOwnerTransferLoanMappingRepository externalAssetOwnerTransferLoanMappingRepository;
private final AccountingService accountingService;
private final LoanJournalEntryPoster loanJournalEntryPoster;
private final BusinessEventNotifierService businessEventNotifierService;
private final ExternalAssetOwnerTransferOutstandingInterestCalculation externalAssetOwnerTransferOutstandingInterestCalculation;
@@ -107,7 +108,7 @@ public class LoanAccountOwnerTransferServiceImpl implements LoanAccountOwnerTran
buybackTransfer = updatePendingBuybackTransfer(loan, buybackTransfer);
externalAssetOwnerTransferLoanMappingRepository.deleteByLoanIdAndOwnerTransfer(loan.getId(), activeTransfer);
accountingService.createJournalEntriesForBuybackAssetTransfer(loan, buybackTransfer);
loanJournalEntryPoster.postJournalEntriesForExternalOwnerTransfer(loan, buybackTransfer, null);
businessEventNotifierService.notifyPostBusinessEvent(new LoanOwnershipTransferBusinessEvent(buybackTransfer, loan));
businessEventNotifierService.notifyPostBusinessEvent(new LoanAccountSnapshotBusinessEvent(loan));
@@ -56,13 +56,13 @@ import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferLoanMapping
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferLoanMappingRepository;
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferRepository;
import org.apache.fineract.investor.domain.LoanOwnershipTransferBusinessEvent;
import org.apache.fineract.investor.service.AccountingService;
import org.apache.fineract.investor.service.DelayedSettlementAttributeService;
import org.apache.fineract.investor.service.ExternalAssetOwnerTransferOutstandingInterestCalculation;
import org.apache.fineract.investor.service.LoanTransferabilityService;
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanSummary;
import org.apache.fineract.portfolio.loanaccount.service.LoanJournalEntryPoster;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.AfterAll;
@@ -97,7 +97,7 @@ public class LoanAccountOwnerTransferBusinessStepTest {
private ExternalAssetOwnerTransferLoanMappingRepository externalAssetOwnerTransferLoanMappingRepository;
@Mock
private AccountingService accountingService;
private LoanJournalEntryPoster loanJournalEntryPoster;
@Mock
private BusinessEventNotifierService businessEventNotifierService;
@@ -130,7 +130,7 @@ public class LoanAccountOwnerTransferBusinessStepTest {
ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT);
ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, actualDate)));
underTest = new LoanAccountOwnerTransferBusinessStep(externalAssetOwnerTransferRepository,
externalAssetOwnerTransferLoanMappingRepository, accountingService, businessEventNotifierService,
externalAssetOwnerTransferLoanMappingRepository, loanJournalEntryPoster, businessEventNotifierService,
loanTransferabilityService, delayedSettlementAttributeService, externalAssetOwnerTransferOutstandingInterestCalculation);
}
@@ -149,7 +149,7 @@ public class LoanAccountOwnerTransferBusinessStepTest {
final Loan processedLoan = underTest.execute(loanForProcessing);
// then
verify(externalAssetOwnerTransferRepository, times(1)).findAll(any(Specification.class), eq(Sort.by(Sort.Direction.ASC, "id")));
verifyNoInteractions(businessEventNotifierService, loanTransferabilityService, accountingService);
verifyNoInteractions(businessEventNotifierService, loanTransferabilityService, loanJournalEntryPoster);
assertEquals(processedLoan, loanForProcessing);
}
@@ -176,7 +176,7 @@ public class LoanAccountOwnerTransferBusinessStepTest {
// then
assertEquals("Illegal transfer found. Expected PENDING and BUYBACK, found: PENDING and ACTIVE", exception.getMessage());
verify(externalAssetOwnerTransferRepository, times(1)).findAll(any(Specification.class), eq(Sort.by(Sort.Direction.ASC, "id")));
verifyNoInteractions(businessEventNotifierService, loanTransferabilityService, accountingService);
verifyNoInteractions(businessEventNotifierService, loanTransferabilityService, loanJournalEntryPoster);
}
@Test
@@ -202,7 +202,7 @@ public class LoanAccountOwnerTransferBusinessStepTest {
// then
assertEquals("Delayed Settlement enabled, but found 2 transfers of statuses: PENDING and BUYBACK", exception.getMessage());
verify(externalAssetOwnerTransferRepository, times(1)).findAll(any(Specification.class), eq(Sort.by(Sort.Direction.ASC, "id")));
verifyNoInteractions(businessEventNotifierService, loanTransferabilityService, accountingService);
verifyNoInteractions(businessEventNotifierService, loanTransferabilityService, loanJournalEntryPoster);
}
@Test
@@ -273,7 +273,7 @@ public class LoanAccountOwnerTransferBusinessStepTest {
assertEquals(processedLoan, loanForProcessing);
verifyNoInteractions(loanTransferabilityService, accountingService);
verifyNoInteractions(loanTransferabilityService, loanJournalEntryPoster);
ArgumentCaptor<BusinessEvent<?>> businessEventArgumentCaptor = verifyBusinessEvents(2);
verifyLoanTransferBusinessEvent(businessEventArgumentCaptor, 0, loanForProcessing, secondSaveResult);
@@ -316,8 +316,8 @@ public class LoanAccountOwnerTransferBusinessStepTest {
assertEquals(processedLoan, loanForProcessing);
verify(accountingService).createJournalEntriesForBuybackAssetTransfer(loanForProcessing, firstResponseItem);
verifyNoMoreInteractions(accountingService);
verify(loanJournalEntryPoster).postJournalEntriesForExternalOwnerTransfer(loanForProcessing, firstResponseItem, null);
verifyNoMoreInteractions(loanJournalEntryPoster);
ArgumentCaptor<BusinessEvent<?>> businessEventArgumentCaptor = verifyBusinessEvents(2);
verifyLoanTransferBusinessEvent(businessEventArgumentCaptor, 0, loanForProcessing, firstResponseItem);
@@ -387,8 +387,8 @@ public class LoanAccountOwnerTransferBusinessStepTest {
verify(externalAssetOwnerTransferLoanMappingRepository).save(externalAssetOwnerTransferLoanMappingArgumentCaptor.capture());
verify(accountingService).createJournalEntriesForSaleAssetTransfer(loanForProcessing, savedNewTransfer, null);
verifyNoMoreInteractions(accountingService);
verify(loanJournalEntryPoster).postJournalEntriesForExternalOwnerTransfer(loanForProcessing, savedNewTransfer, null);
verifyNoMoreInteractions(loanJournalEntryPoster);
ArgumentCaptor<BusinessEvent<?>> businessEventArgumentCaptor = verifyBusinessEvents(2);
verifyLoanTransferBusinessEvent(businessEventArgumentCaptor, 0, loanForProcessing, savedNewTransfer);
@@ -445,7 +445,7 @@ public class LoanAccountOwnerTransferBusinessStepTest {
assertEquals(actualDate, capturedActiveTransfer.getEffectiveDateTo());
assertEquals(processedLoan, loanForProcessing);
verifyNoInteractions(accountingService);
verifyNoInteractions(loanJournalEntryPoster);
ArgumentCaptor<BusinessEvent<?>> businessEventArgumentCaptor = verifyBusinessEvents(1);
verifyLoanTransferBusinessEvent(businessEventArgumentCaptor, 0, loanForProcessing, savedNewTransfer);
@@ -516,8 +516,8 @@ public class LoanAccountOwnerTransferBusinessStepTest {
assertEquals(savedNewTransfer, externalAssetOwnerTransferLoanMappingArgumentCaptor.getValue().getOwnerTransfer());
assertEquals(processedLoan, loanForProcessing);
verify(accountingService).createJournalEntriesForSaleAssetTransfer(loanForProcessing, savedNewTransfer, previousOwner);
verifyNoMoreInteractions(accountingService);
verify(loanJournalEntryPoster).postJournalEntriesForExternalOwnerTransfer(loanForProcessing, savedNewTransfer, previousOwner);
verifyNoMoreInteractions(loanJournalEntryPoster);
ArgumentCaptor<BusinessEvent<?>> businessEventArgumentCaptor = verifyBusinessEvents(2);
verifyLoanTransferBusinessEvent(businessEventArgumentCaptor, 0, loanForProcessing, savedNewTransfer);
@@ -552,7 +552,7 @@ public class LoanAccountOwnerTransferBusinessStepTest {
verify(loanTransferabilityService).isTransferable(loanForProcessing, pendingTransfer);
verifyNoMoreInteractions(loanTransferabilityService);
verifyNoInteractions(accountingService);
verifyNoInteractions(loanJournalEntryPoster);
verify(externalAssetOwnerTransferRepository).findAll(any(Specification.class), eq(Sort.by(Sort.Direction.ASC, "id")));
verify(externalAssetOwnerTransferRepository).findOne(any(Specification.class));
verify(externalAssetOwnerTransferRepository, never()).save(any(ExternalAssetOwnerTransfer.class));
@@ -50,6 +50,7 @@ import org.apache.fineract.investor.domain.LoanOwnershipTransferBusinessEvent;
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanSummary;
import org.apache.fineract.portfolio.loanaccount.service.LoanJournalEntryPoster;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
@@ -82,6 +83,8 @@ public class LoanAccountOwnerTransferServiceTest {
private BusinessEventNotifierService businessEventNotifierService;
@Mock
private ExternalAssetOwnerTransferOutstandingInterestCalculation externalAssetOwnerTransferOutstandingInterestCalculation;
@Mock
private LoanJournalEntryPoster loanJournalEntryPoster;
@InjectMocks
private LoanAccountOwnerTransferServiceImpl underTest;
@@ -1,274 +0,0 @@
/**
* 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.mapper;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.portfolio.loanaccount.data.AccountingBridgeDataDTO;
import org.apache.fineract.portfolio.loanaccount.data.AccountingBridgeLoanTransactionDTO;
import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidByDTO;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
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.loanproduct.service.LoanEnumerations;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class LoanAccountingBridgeMapper {
private final LoanTransactionRepository loanTransactionRepository;
public List<AccountingBridgeDataDTO> deriveAccountingBridgeDataForChargeOff(final String currencyCode,
final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds, final boolean isAccountTransfer,
final Loan loan) {
final List<AccountingBridgeLoanTransactionDTO> newLoanTransactionsBeforeChargeOff = new ArrayList<>();
final List<AccountingBridgeLoanTransactionDTO> newLoanTransactionsAfterChargeOff = new ArrayList<>();
// split the transactions according charge-off date
classifyTransactionsBasedOnChargeOffDate(newLoanTransactionsBeforeChargeOff, newLoanTransactionsAfterChargeOff,
existingTransactionIds, existingReversedTransactionIds, currencyCode, loan);
AccountingBridgeDataDTO beforeChargeOff = new AccountingBridgeDataDTO(loan.getId(), loan.productId(), loan.getOfficeId(),
currencyCode, loan.getSummary().getTotalInterestCharged(), loan.isCashBasedAccountingEnabledOnLoanProduct(),
loan.isUpfrontAccrualAccountingEnabledOnLoanProduct(), loan.isPeriodicAccrualAccountingEnabledOnLoanProduct(),
isAccountTransfer, false, loan.isFraud(), loan.fetchChargeOffReasonId(), loan.isClosedWrittenOff(),
newLoanTransactionsBeforeChargeOff, loan.getLoanProductRelatedDetail().isMerchantBuyDownFee());
AccountingBridgeDataDTO afterChargeOff = new AccountingBridgeDataDTO(loan.getId(), loan.productId(), loan.getOfficeId(),
currencyCode, loan.getSummary().getTotalInterestCharged(), loan.isCashBasedAccountingEnabledOnLoanProduct(),
loan.isUpfrontAccrualAccountingEnabledOnLoanProduct(), loan.isPeriodicAccrualAccountingEnabledOnLoanProduct(),
isAccountTransfer, true, loan.isFraud(), loan.fetchChargeOffReasonId(), loan.isClosedWrittenOff(),
newLoanTransactionsAfterChargeOff, loan.getLoanProductRelatedDetail().isMerchantBuyDownFee());
List<AccountingBridgeDataDTO> result = new ArrayList<>();
result.add(beforeChargeOff);
result.add(afterChargeOff);
return result;
}
public AccountingBridgeDataDTO deriveAccountingBridgeData(final String currencyCode, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds, final boolean isAccountTransfer, final Loan loan) {
final List<LoanTransaction> transactions = findTransactionsForAccountingBridge(existingTransactionIds,
existingReversedTransactionIds, loan);
final List<AccountingBridgeLoanTransactionDTO> newLoanTransactions = transactions.stream() //
.map(transaction -> mapToLoanTransactionData(transaction, currencyCode)) //
.toList();
return new AccountingBridgeDataDTO(loan.getId(), loan.productId(), loan.getOfficeId(), currencyCode,
loan.getSummary().getTotalInterestCharged(), loan.isCashBasedAccountingEnabledOnLoanProduct(),
loan.isUpfrontAccrualAccountingEnabledOnLoanProduct(), loan.isPeriodicAccrualAccountingEnabledOnLoanProduct(),
isAccountTransfer, loan.isChargedOff(), loan.isFraud(), loan.fetchChargeOffReasonId(), loan.isClosedWrittenOff(),
newLoanTransactions, loan.getLoanProductRelatedDetail().isMerchantBuyDownFee());
}
public AccountingBridgeLoanTransactionDTO mapToLoanTransactionData(final LoanTransaction transaction, final String currencyCode) {
final AccountingBridgeLoanTransactionDTO transactionDTO = new AccountingBridgeLoanTransactionDTO();
transactionDTO.setId(transaction.getId());
transactionDTO.setOfficeId(transaction.getOffice().getId());
transactionDTO.setType(LoanEnumerations.transactionType(transaction.getTypeOf()));
transactionDTO.setReversed(transaction.isReversed());
transactionDTO.setDate(transaction.getTransactionDate());
transactionDTO.setCurrencyCode(currencyCode);
transactionDTO.setAmount(transaction.getAmount());
transactionDTO.setNetDisbursalAmount(transaction.getLoan().getNetDisbursalAmount());
if (transactionDTO.getType().isChargeback() && (transaction.getLoan().getCreditAllocationRules() == null
|| transaction.getLoan().getCreditAllocationRules().isEmpty())) {
transactionDTO.setPrincipalPortion(transaction.getAmount());
} else {
transactionDTO.setPrincipalPortion(transaction.getPrincipalPortion());
}
transactionDTO.setInterestPortion(transaction.getInterestPortion());
transactionDTO.setFeeChargesPortion(transaction.getFeeChargesPortion());
transactionDTO.setPenaltyChargesPortion(transaction.getPenaltyChargesPortion());
transactionDTO.setOverPaymentPortion(transaction.getOverPaymentPortion());
if (transactionDTO.getType().isChargeRefund()) {
transactionDTO.setChargeRefundChargeType(transaction.getChargeRefundChargeType());
}
if (transaction.getPaymentDetail() != null) {
transactionDTO.setPaymentTypeId(transaction.getPaymentDetail().getPaymentType().getId());
}
if (!transaction.getLoanChargesPaid().isEmpty()) {
List<LoanChargePaidByDTO> loanChargesPaidData = new ArrayList<>();
for (final LoanChargePaidBy chargePaidBy : transaction.getLoanChargesPaid()) {
final LoanChargePaidByDTO loanChargePaidData = new LoanChargePaidByDTO();
loanChargePaidData.setChargeId(chargePaidBy.getLoanCharge().getCharge().getId());
loanChargePaidData.setIsPenalty(chargePaidBy.getLoanCharge().isPenaltyCharge());
loanChargePaidData.setLoanChargeId(chargePaidBy.getLoanCharge().getId());
loanChargePaidData.setAmount(chargePaidBy.getAmount());
loanChargePaidData.setInstallmentNumber(chargePaidBy.getInstallmentNumber());
loanChargesPaidData.add(loanChargePaidData);
}
transactionDTO.setLoanChargesPaid(loanChargesPaidData);
}
if (transactionDTO.getType().isChargeback() && transaction.getOverPaymentPortion() != null
&& transaction.getOverPaymentPortion().compareTo(BigDecimal.ZERO) > 0) {
BigDecimal principalPaid = transaction.getOverPaymentPortion();
BigDecimal feePaid = BigDecimal.ZERO;
BigDecimal penaltyPaid = BigDecimal.ZERO;
if (!transaction.getLoanTransactionToRepaymentScheduleMappings().isEmpty()) {
principalPaid = transaction.getLoanTransactionToRepaymentScheduleMappings().stream()
.map(mapping -> Optional.ofNullable(mapping.getPrincipalPortion()).orElse(BigDecimal.ZERO))
.reduce(BigDecimal.ZERO, BigDecimal::add);
feePaid = transaction.getLoanTransactionToRepaymentScheduleMappings().stream()
.map(mapping -> Optional.ofNullable(mapping.getFeeChargesPortion()).orElse(BigDecimal.ZERO))
.reduce(BigDecimal.ZERO, BigDecimal::add);
penaltyPaid = transaction.getLoanTransactionToRepaymentScheduleMappings().stream()
.map(mapping -> Optional.ofNullable(mapping.getPenaltyChargesPortion()).orElse(BigDecimal.ZERO))
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
transactionDTO.setPrincipalPaid(principalPaid);
transactionDTO.setFeePaid(feePaid);
transactionDTO.setPenaltyPaid(penaltyPaid);
}
LoanTransactionRelation loanTransactionRelation = transaction.getLoanTransactionRelations().stream()
.filter(e -> LoanTransactionRelationTypeEnum.CHARGE_ADJUSTMENT.equals(e.getRelationType())).findAny().orElse(null);
if (loanTransactionRelation != null) {
LoanCharge loanCharge = loanTransactionRelation.getToCharge();
transactionDTO.setLoanChargeData(loanCharge.toData());
}
transactionDTO.setLoanToLoanTransfer(false);
return transactionDTO;
}
private void classifyTransactionsBasedOnChargeOffDate(final List<AccountingBridgeLoanTransactionDTO> newLoanTransactionsBeforeChargeOff,
final List<AccountingBridgeLoanTransactionDTO> newLoanTransactionsAfterChargeOff, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds, final String currencyCode, final Loan loan) {
final LocalDate chargeOffDate = loan.getChargedOffOnDate();
if (chargeOffDate == null) {
return;
}
// Before charge-off
final List<LoanTransaction> beforeTransactions = findTransactionsForChargeOffClassification(loan, chargeOffDate, "BEFORE",
existingTransactionIds, existingReversedTransactionIds);
processTransactionsForChargeOff(beforeTransactions, newLoanTransactionsBeforeChargeOff, newLoanTransactionsAfterChargeOff,
currencyCode, loan);
// On charge-off date
final List<LoanTransaction> onTransactions = findTransactionsForChargeOffClassification(loan, chargeOffDate, "EQUAL",
existingTransactionIds, existingReversedTransactionIds);
processTransactionsForChargeOff(onTransactions, newLoanTransactionsBeforeChargeOff, newLoanTransactionsAfterChargeOff, currencyCode,
loan);
// After charge-off
final List<LoanTransaction> afterTransactions = findTransactionsForChargeOffClassification(loan, chargeOffDate, "AFTER",
existingTransactionIds, existingReversedTransactionIds);
processTransactionsForChargeOff(afterTransactions, newLoanTransactionsBeforeChargeOff, newLoanTransactionsAfterChargeOff,
currencyCode, loan);
}
private List<LoanTransaction> findTransactionsForChargeOffClassification(final Loan loan, final LocalDate chargeOffDate,
final String dateComparison, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds) {
final boolean hasExistingIds = existingTransactionIds != null && !existingTransactionIds.isEmpty();
final boolean hasExistingReversedIds = existingReversedTransactionIds != null && !existingReversedTransactionIds.isEmpty();
if (hasExistingIds && hasExistingReversedIds) {
return loanTransactionRepository.findTransactionsForChargeOffClassification(loan, chargeOffDate, dateComparison,
existingTransactionIds, existingReversedTransactionIds);
} else if (hasExistingIds) {
return loanTransactionRepository.findTransactionsForChargeOffClassification(loan, chargeOffDate, dateComparison,
existingTransactionIds);
} else {
return loanTransactionRepository.findTransactionsForChargeOffClassification(loan, chargeOffDate, dateComparison);
}
}
private void processTransactionsForChargeOff(final List<LoanTransaction> transactions,
final List<AccountingBridgeLoanTransactionDTO> newLoanTransactionsBeforeChargeOff,
final List<AccountingBridgeLoanTransactionDTO> newLoanTransactionsAfterChargeOff, final String currencyCode, final Loan loan) {
final Optional<LoanTransaction> chargeOffTransactionOptional = findChargeOffTransaction(loan);
if (chargeOffTransactionOptional.isEmpty()) {
return;
}
final LoanTransaction chargeOffTransaction = chargeOffTransactionOptional.get();
final LoanTransaction originalChargeOffTransaction = getOriginalTransactionIfReverseReplayed(chargeOffTransaction);
transactions.forEach(transaction -> {
List<AccountingBridgeLoanTransactionDTO> targetList;
if (transaction.isReversed()) {
final LoanTransaction originalTransaction = getOriginalTransactionIfReverseReplayed(transaction);
targetList = originalTransaction.happenedBefore(originalChargeOffTransaction) ? newLoanTransactionsBeforeChargeOff
: newLoanTransactionsAfterChargeOff;
} else {
targetList = transaction.happenedBefore(chargeOffTransaction) ? newLoanTransactionsBeforeChargeOff
: newLoanTransactionsAfterChargeOff;
}
targetList.add(mapToLoanTransactionData(transaction, currencyCode));
});
}
private Optional<LoanTransaction> findChargeOffTransaction(final Loan loan) {
return loanTransactionRepository.findNonReversedByLoanAndType(loan, LoanTransactionType.CHARGE_OFF).stream() //
.findFirst();
}
private List<LoanTransaction> findTransactionsForAccountingBridge(final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds, final Loan loan) {
if (existingTransactionIds == null || existingTransactionIds.isEmpty()) {
return loanTransactionRepository.findNonReversedByLoan(loan);
} else if (existingReversedTransactionIds == null || existingReversedTransactionIds.isEmpty()) {
return loanTransactionRepository.findTransactionsForAccountingBridge(loan, existingTransactionIds);
} else {
return loanTransactionRepository.findTransactionsForAccountingBridge(loan, existingTransactionIds,
existingReversedTransactionIds);
}
}
private LoanTransaction getOriginalTransactionIfReverseReplayed(final LoanTransaction loanTransaction) {
if (loanTransaction.getLoanTransactionRelations().isEmpty()) {
return loanTransaction;
}
return loanTransaction.getLoanTransactionRelations().stream() //
.filter(tr -> LoanTransactionRelationTypeEnum.REPLAYED.equals(tr.getRelationType())) //
.map(LoanTransactionRelation::getToTransaction) //
.min(Comparator.comparingLong(LoanTransaction::getId)) //
.orElse(loanTransaction);
}
}
@@ -34,13 +34,13 @@ public interface LoanAccrualsProcessingService {
void addAccruals(@NotNull LocalDate tillDate) throws MultiException;
void reprocessExistingAccruals(@NotNull Loan loan);
void reprocessExistingAccruals(@NotNull Loan loan, boolean addEvent);
void processAccrualsOnInterestRecalculation(@NotNull Loan loan, boolean isInterestRecalculationEnabled, boolean addJournal);
void addIncomePostingAndAccruals(Long loanId) throws Exception;
void processIncomePostingAndAccruals(@NotNull Loan loan);
void processIncomePostingAndAccruals(@NotNull Loan loan, boolean addEvent);
void processAccrualsOnLoanClosure(@NonNull Loan loan, boolean addJournal);
@@ -51,7 +51,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleIns
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTrancheDisbursementCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.loanaccount.domain.SingleLoanChargeRepaymentScheduleProcessingWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
@@ -64,7 +63,6 @@ public class LoanChargeService {
private final LoanTransactionProcessingService loanTransactionProcessingService;
private final LoanLifecycleStateMachine loanLifecycleStateMachine;
private final LoanBalanceService loanBalanceService;
private final LoanTransactionRepository loanTransactionRepository;
public void recalculateAllCharges(final Loan loan) {
Set<LoanCharge> charges = loan.getActiveCharges();
@@ -134,11 +132,9 @@ public class LoanChargeService {
}
}
public void makeChargePayment(final Loan loan, final Long chargeId, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds, final LoanTransaction paymentTransaction, final Integer installmentNumber) {
public void makeChargePayment(final Loan loan, final Long chargeId, final LoanTransaction paymentTransaction,
final Integer installmentNumber) {
loanChargeValidator.validateChargePaymentNotInFuture(paymentTransaction);
existingTransactionIds.addAll(loanTransactionRepository.findTransactionIdsByLoan(loan));
existingReversedTransactionIds.addAll(loanTransactionRepository.findReversedTransactionIdsByLoan(loan));
LoanCharge charge = null;
for (final LoanCharge loanCharge : loan.getCharges()) {
if (loanCharge.isActive() && chargeId.equals(loanCharge.getId())) {
@@ -63,6 +63,7 @@ public class LoanDownPaymentHandlerServiceImpl implements LoanDownPaymentHandler
private final LoanLifecycleStateMachine loanLifecycleStateMachine;
private final LoanBalanceService loanBalanceService;
private final LoanTransactionService loanTransactionService;
private final LoanJournalEntryPoster journalEntryPoster;
@Override
public LoanTransaction handleDownPayment(ScheduleGeneratorDTO scheduleGeneratorDTO, JsonCommand command,
@@ -71,6 +72,7 @@ public class LoanDownPaymentHandlerServiceImpl implements LoanDownPaymentHandler
LoanTransaction downPaymentTransaction = handleDownPayment(loan, disbursementTransaction, command, scheduleGeneratorDTO);
if (downPaymentTransaction != null) {
downPaymentTransaction = loanTransactionRepository.saveAndFlush(downPaymentTransaction);
journalEntryPoster.postJournalEntriesForLoanTransaction(downPaymentTransaction, false, false);
businessEventNotifierService.notifyPostBusinessEvent(new LoanTransactionDownPaymentPostBusinessEvent(downPaymentTransaction));
}
return downPaymentTransaction;
@@ -18,10 +18,33 @@
*/
package org.apache.fineract.portfolio.loanaccount.service;
import java.util.List;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
public interface LoanJournalEntryPoster {
void postJournalEntries(Loan loan, List<Long> existingTransactionIds, List<Long> existingReversedTransactionIds);
/**
* Create journal entries immediately for a single loan transaction This replaces the old 2-step process of
* collecting transaction IDs and then creating journal entries
*
* @param loanTransaction
* the loan transaction to create journal entries for
* @param isAccountTransfer
* whether this is an account transfer transaction
* @param isLoanToLoanTransfer
* whether this is a loan-to-loan transfer transaction
*/
void postJournalEntriesForLoanTransaction(LoanTransaction loanTransaction, boolean isAccountTransfer, boolean isLoanToLoanTransfer);
/**
* Create journal entries immediately for an external owner transfer
*
* @param loan
* the loan being transferred
* @param externalAssetOwnerTransfer
* the external owner transfer details
* @param previousOwner
* the previous owner (can be null for initial transfers)
*/
void postJournalEntriesForExternalOwnerTransfer(Loan loan, Object externalAssetOwnerTransfer, Object previousOwner);
}
@@ -19,13 +19,11 @@
package org.apache.fineract.portfolio.loanaccount.service;
import java.time.LocalDate;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanEvent;
import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanRefundValidator;
@@ -36,13 +34,8 @@ public class LoanRefundService {
private final LoanRefundValidator loanRefundValidator;
private final LoanTransactionProcessingService loadTransactionProcessingService;
private final LoanLifecycleStateMachine loanLifecycleStateMachine;
private final LoanTransactionRepository loanTransactionRepository;
public void makeRefund(final Loan loan, final LoanTransaction loanTransaction, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds) {
existingTransactionIds.addAll(loanTransactionRepository.findTransactionIdsByLoan(loan));
existingReversedTransactionIds.addAll(loanTransactionRepository.findReversedTransactionIdsByLoan(loan));
public void makeRefund(final Loan loan, final LoanTransaction loanTransaction) {
loanRefundValidator.validateTransferRefund(loan, loanTransaction);
loanTransaction.updateLoan(loan);
@@ -59,21 +52,12 @@ public class LoanRefundService {
return loanTransactionDate;
}
public void makeRefundForActiveLoan(final Loan loan, final LoanTransaction loanTransaction, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds) {
existingTransactionIds.addAll(loanTransactionRepository.findTransactionIdsByLoan(loan));
existingReversedTransactionIds.addAll(loanTransactionRepository.findReversedTransactionIdsByLoan(loan));
public void makeRefundForActiveLoan(final Loan loan, final LoanTransaction loanTransaction) {
handleRefundTransaction(loan, loanTransaction);
}
public void creditBalanceRefund(final Loan loan, final LoanTransaction newCreditBalanceRefundTransaction,
final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds) {
public void creditBalanceRefund(final Loan loan, final LoanTransaction newCreditBalanceRefundTransaction) {
loanRefundValidator.validateCreditBalanceRefund(loan, newCreditBalanceRefundTransaction);
existingTransactionIds.addAll(loanTransactionRepository.findTransactionIdsByLoan(loan));
existingReversedTransactionIds.addAll(loanTransactionRepository.findReversedTransactionIdsByLoan(loan));
loan.getLoanTransactions().add(newCreditBalanceRefundTransaction);
loanLifecycleStateMachine.determineAndTransition(loan, newCreditBalanceRefundTransaction.getTransactionDate());
@@ -69,8 +69,7 @@ public class SingleLoanChargeRepaymentScheduleProcessingWrapperTest {
private ArgumentCaptor<Money> penaltyChargesWrittenOff = ArgumentCaptor.forClass(Money.class);
private final LoanChargeService loanChargeService = new LoanChargeService(mock(LoanChargeValidator.class),
mock(LoanTransactionProcessingService.class), mock(LoanLifecycleStateMachine.class), mock(LoanBalanceService.class),
mock(LoanTransactionRepository.class));
mock(LoanTransactionProcessingService.class), mock(LoanLifecycleStateMachine.class), mock(LoanBalanceService.class));
@BeforeAll
public static void init() {
@@ -20,9 +20,7 @@ package org.apache.fineract.portfolio.loanaccount.service;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
@@ -77,8 +75,6 @@ public class BuyDownFeeWritePlatformServiceImpl implements BuyDownFeePlatformSer
final Loan loan = this.loanAssembler.assembleFrom(loanId);
checkClientOrGroupActive(loan);
final List<Long> existingTransactionIds = new ArrayList<>(loanTransactionRepository.findTransactionIdsByLoan(loan));
final List<Long> existingReversedTransactionIds = new ArrayList<>(loanTransactionRepository.findReversedTransactionIdsByLoan(loan));
final Map<String, Object> changes = new LinkedHashMap<>();
// Create payment details
@@ -109,8 +105,7 @@ public class BuyDownFeeWritePlatformServiceImpl implements BuyDownFeePlatformSer
noteWritePlatformService.createLoanTransactionNote(buyDownFeeTransaction.getId(), noteText);
}
// Post journal entries
loanJournalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
loanJournalEntryPoster.postJournalEntriesForLoanTransaction(buyDownFeeTransaction, false, false);
// Notify business events
businessEventNotifierService.notifyPostBusinessEvent(new LoanBuyDownFeeTransactionCreatedBusinessEvent(buyDownFeeTransaction));
@@ -131,8 +126,6 @@ public class BuyDownFeeWritePlatformServiceImpl implements BuyDownFeePlatformSer
final Loan loan = this.loanAssembler.assembleFrom(loanId);
checkClientOrGroupActive(loan);
final List<Long> existingTransactionIds = new ArrayList<>(loanTransactionRepository.findTransactionIdsByLoan(loan));
final List<Long> existingReversedTransactionIds = new ArrayList<>(loanTransactionRepository.findReversedTransactionIdsByLoan(loan));
final Map<String, Object> changes = new LinkedHashMap<>();
// Create payment details
@@ -178,8 +171,8 @@ public class BuyDownFeeWritePlatformServiceImpl implements BuyDownFeePlatformSer
if (StringUtils.isNotBlank(noteText)) {
noteWritePlatformService.createLoanTransactionNote(savedBuyDownFeeAdjustment.getId(), noteText);
}
// Post journal entries
loanJournalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
loanJournalEntryPoster.postJournalEntriesForLoanTransaction(savedBuyDownFeeAdjustment, false, false);
// Notify business events
businessEventNotifierService
@@ -20,9 +20,7 @@ package org.apache.fineract.portfolio.loanaccount.service;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
@@ -74,8 +72,6 @@ public class CapitalizedIncomeWritePlatformServiceImpl implements CapitalizedInc
public CommandProcessingResult addCapitalizedIncome(final Long loanId, final JsonCommand command) {
loanTransactionValidator.validateCapitalizedIncome(command, loanId);
final Loan loan = loanAssembler.assembleFrom(loanId);
final List<Long> existingTransactionIds = new ArrayList<>(loanTransactionRepository.findTransactionIdsByLoan(loan));
final List<Long> existingReversedTransactionIds = new ArrayList<>(loanTransactionRepository.findReversedTransactionIdsByLoan(loan));
final Map<String, Object> changes = new LinkedHashMap<>();
// Create payment details
final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
@@ -106,8 +102,8 @@ public class CapitalizedIncomeWritePlatformServiceImpl implements CapitalizedInc
noteWritePlatformService.createLoanTransactionNote(capitalizedIncomeTransaction.getId(), noteText);
}
// Post journal entries
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
// Create journal entries immediately for this transaction
journalEntryPoster.postJournalEntriesForLoanTransaction(capitalizedIncomeTransaction, false, false);
loanLifecycleStateMachine.determineAndTransition(loan, transactionDate);
@@ -128,8 +124,6 @@ public class CapitalizedIncomeWritePlatformServiceImpl implements CapitalizedInc
final JsonCommand command) {
loanTransactionValidator.validateCapitalizedIncomeAdjustment(command, loanId, capitalizedIncomeTransactionId);
final Loan loan = loanAssembler.assembleFrom(loanId);
final List<Long> existingTransactionIds = new ArrayList<>(loanTransactionRepository.findTransactionIdsByLoan(loan));
final List<Long> existingReversedTransactionIds = new ArrayList<>(loanTransactionRepository.findReversedTransactionIdsByLoan(loan));
final Map<String, Object> changes = new LinkedHashMap<>();
// Create payment details
final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
@@ -155,8 +149,8 @@ public class CapitalizedIncomeWritePlatformServiceImpl implements CapitalizedInc
if (noteText != null && !noteText.isEmpty()) {
noteWritePlatformService.createLoanTransactionNote(savedCapitalizedIncomeAdjustment.getId(), noteText);
}
// Post journal entries
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
// Create journal entries immediately for this transaction
journalEntryPoster.postJournalEntriesForLoanTransaction(savedCapitalizedIncomeAdjustment, false, false);
LoanCapitalizedIncomeBalance capitalizedIncomeBalance = capitalizedIncomeBalanceRepository.findByLoanIdAndLoanTransactionId(loanId,
capitalizedIncomeTransactionId);
@@ -24,7 +24,11 @@ import java.util.Map;
import org.apache.fineract.accounting.provisioning.domain.ProvisioningEntry;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.investor.domain.ExternalAssetOwner;
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransfer;
import org.apache.fineract.portfolio.loanaccount.data.AccountingBridgeDataDTO;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
public interface JournalEntryWritePlatformService {
@@ -50,4 +54,29 @@ public interface JournalEntryWritePlatformService {
void revertShareAccountJournalEntries(ArrayList<Long> transactionId, LocalDate transactionDate);
/**
* Create journal entries immediately for a single loan transaction
*
* @param loanTransaction
* the loan transaction to create journal entries for
* @param isAccountTransfer
* whether this is an account transfer transaction
* @param isLoanToLoanTransfer
* whether this is a loan-to-loan transfer transaction
*/
void createJournalEntriesForLoanTransaction(LoanTransaction loanTransaction, boolean isAccountTransfer, boolean isLoanToLoanTransfer);
/**
* Create journal entries immediately for an external owner transfer
*
* @param loan
* the loan being transferred
* @param externalAssetOwnerTransfer
* the external owner transfer details
* @param previousOwner
* the previous owner (can be null for initial transfers)
*/
void createJournalEntriesForExternalOwnerTransfer(Loan loan, ExternalAssetOwnerTransfer externalAssetOwnerTransfer,
ExternalAssetOwner previousOwner);
}
@@ -78,13 +78,24 @@ import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.investor.domain.ExternalAssetOwner;
import org.apache.fineract.investor.domain.ExternalAssetOwnerRepository;
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransfer;
import org.apache.fineract.investor.exception.ExternalAssetOwnerNotFoundException;
import org.apache.fineract.investor.service.AccountingService;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.OrganisationCurrencyRepositoryWrapper;
import org.apache.fineract.organisation.office.domain.Office;
import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper;
import org.apache.fineract.portfolio.PortfolioProductType;
import org.apache.fineract.portfolio.loanaccount.data.AccountingBridgeDataDTO;
import org.apache.fineract.portfolio.loanaccount.data.AccountingBridgeLoanTransactionDTO;
import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidByDTO;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum;
import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
import org.apache.fineract.portfolio.paymentdetail.service.PaymentDetailWritePlatformService;
import org.apache.fineract.useradministration.domain.AppUser;
@@ -789,6 +800,160 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
accountingProcessorForClientTransactions.createJournalEntriesForClientTransaction(clientTransactionDTO);
}
@Transactional
@Override
public void createJournalEntriesForLoanTransaction(final LoanTransaction loanTransaction, final boolean isAccountTransfer,
final boolean isLoanToLoanTransfer) {
final Loan loan = loanTransaction.getLoan();
// Check if accounting is enabled for this loan
if (!loan.isCashBasedAccountingEnabledOnLoanProduct() && !loan.isUpfrontAccrualAccountingEnabledOnLoanProduct()
&& !loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()) {
return; // No accounting enabled, skip journal entry creation
}
final AccountingBridgeDataDTO accountingBridgeData = createAccountingBridgeDataForSingleTransaction(loanTransaction,
isAccountTransfer);
if (isLoanToLoanTransfer) {
accountingBridgeData.getNewLoanTransactions().forEach(tx -> tx.setLoanToLoanTransfer(true));
}
this.createJournalEntriesForLoan(accountingBridgeData);
}
@Transactional
@Override
public void createJournalEntriesForExternalOwnerTransfer(final Loan loan, final ExternalAssetOwnerTransfer externalAssetOwnerTransfer,
final ExternalAssetOwner previousOwner) {
final boolean isBuyback = externalAssetOwnerTransfer.getStatus().name().contains("BUYBACK");
if (isBuyback) {
this.accountingService.createJournalEntriesForBuybackAssetTransfer(loan, externalAssetOwnerTransfer);
} else {
this.accountingService.createJournalEntriesForSaleAssetTransfer(loan, externalAssetOwnerTransfer, previousOwner);
}
}
/**
* Create AccountingBridgeDataDTO for a single loan transaction This converts a single LoanTransaction to the format
* expected by existing journal entry logic
*/
private AccountingBridgeDataDTO createAccountingBridgeDataForSingleTransaction(final LoanTransaction loanTransaction,
final boolean isAccountTransfer) {
final Loan loan = loanTransaction.getLoan();
final String currencyCode = loan.getCurrencyCode();
final AccountingBridgeLoanTransactionDTO transactionDTO = convertToAccountingBridgeTransaction(loanTransaction);
final List<AccountingBridgeLoanTransactionDTO> transactions = new ArrayList<>();
transactions.add(transactionDTO);
boolean wasChargedOffAtTransactionTime = loan.isChargedOff();
if (loan.isChargedOff() && loan.getChargedOffOnDate() != null) {
// If transaction date is before charge-off date, treat as non-charged-off
if (loanTransaction.getTransactionDate().isBefore(loan.getChargedOffOnDate())) {
wasChargedOffAtTransactionTime = false;
}
}
return new AccountingBridgeDataDTO(loan.getId(), loan.productId(), loan.getOfficeId(), currencyCode,
loan.getSummary().getTotalInterestCharged(), loan.isCashBasedAccountingEnabledOnLoanProduct(),
loan.isUpfrontAccrualAccountingEnabledOnLoanProduct(), loan.isPeriodicAccrualAccountingEnabledOnLoanProduct(),
isAccountTransfer, wasChargedOffAtTransactionTime, loan.isFraud(), loan.fetchChargeOffReasonId(), loan.isClosedWrittenOff(),
transactions, loan.getLoanProductRelatedDetail().isMerchantBuyDownFee());
}
/**
* Convert LoanTransaction to AccountingBridgeLoanTransactionDTO
*/
private AccountingBridgeLoanTransactionDTO convertToAccountingBridgeTransaction(LoanTransaction loanTransaction) {
final MonetaryCurrency currency = loanTransaction.getLoan().getCurrency();
final AccountingBridgeLoanTransactionDTO transactionDTO = new AccountingBridgeLoanTransactionDTO();
transactionDTO.setId(loanTransaction.getId());
transactionDTO.setOfficeId(loanTransaction.getOffice().getId());
transactionDTO.setType(LoanEnumerations.transactionType(loanTransaction.getTypeOf()));
transactionDTO.setReversed(loanTransaction.isReversed());
transactionDTO.setDate(loanTransaction.getTransactionDate());
transactionDTO.setCurrencyCode(currency.getCode());
transactionDTO.setAmount(loanTransaction.getAmount());
transactionDTO.setNetDisbursalAmount(loanTransaction.getLoan().getNetDisbursalAmount());
// Handle principalPortion for chargeback
if (transactionDTO.getType().isChargeback() && (loanTransaction.getLoan().getCreditAllocationRules() == null
|| loanTransaction.getLoan().getCreditAllocationRules().isEmpty())) {
transactionDTO.setPrincipalPortion(loanTransaction.getAmount());
} else {
transactionDTO.setPrincipalPortion(loanTransaction.getPrincipalPortion());
}
transactionDTO.setInterestPortion(loanTransaction.getInterestPortion());
transactionDTO.setFeeChargesPortion(loanTransaction.getFeeChargesPortion());
transactionDTO.setPenaltyChargesPortion(loanTransaction.getPenaltyChargesPortion());
transactionDTO.setOverPaymentPortion(loanTransaction.getOverPaymentPortion());
// Handle ChargeRefund transactions
if (transactionDTO.getType().isChargeRefund()) {
transactionDTO.setChargeRefundChargeType(loanTransaction.getChargeRefundChargeType());
}
if (loanTransaction.getPaymentDetail() != null) {
transactionDTO.setPaymentTypeId(loanTransaction.getPaymentDetail().getPaymentType().getId());
}
// Populate loanChargesPaid from the transaction
if (!loanTransaction.getLoanChargesPaid().isEmpty()) {
List<LoanChargePaidByDTO> loanChargesPaidData = new ArrayList<>();
for (final LoanChargePaidBy chargePaidBy : loanTransaction.getLoanChargesPaid()) {
final LoanChargePaidByDTO loanChargePaidData = new LoanChargePaidByDTO();
loanChargePaidData.setChargeId(chargePaidBy.getLoanCharge().getCharge().getId());
loanChargePaidData.setIsPenalty(chargePaidBy.getLoanCharge().isPenaltyCharge());
loanChargePaidData.setLoanChargeId(chargePaidBy.getLoanCharge().getId());
loanChargePaidData.setAmount(chargePaidBy.getAmount());
loanChargePaidData.setInstallmentNumber(chargePaidBy.getInstallmentNumber());
loanChargesPaidData.add(loanChargePaidData);
}
transactionDTO.setLoanChargesPaid(loanChargesPaidData);
}
// Handle chargeback principalPaid/feePaid/penaltyPaid
if (transactionDTO.getType().isChargeback() && loanTransaction.getOverPaymentPortion() != null
&& loanTransaction.getOverPaymentPortion().compareTo(BigDecimal.ZERO) > 0) {
BigDecimal principalPaid = loanTransaction.getOverPaymentPortion();
BigDecimal feePaid = BigDecimal.ZERO;
BigDecimal penaltyPaid = BigDecimal.ZERO;
if (!loanTransaction.getLoanTransactionToRepaymentScheduleMappings().isEmpty()) {
principalPaid = loanTransaction.getLoanTransactionToRepaymentScheduleMappings().stream()
.map(mapping -> Optional.ofNullable(mapping.getPrincipalPortion()).orElse(BigDecimal.ZERO))
.reduce(BigDecimal.ZERO, BigDecimal::add);
feePaid = loanTransaction.getLoanTransactionToRepaymentScheduleMappings().stream()
.map(mapping -> Optional.ofNullable(mapping.getFeeChargesPortion()).orElse(BigDecimal.ZERO))
.reduce(BigDecimal.ZERO, BigDecimal::add);
penaltyPaid = loanTransaction.getLoanTransactionToRepaymentScheduleMappings().stream()
.map(mapping -> Optional.ofNullable(mapping.getPenaltyChargesPortion()).orElse(BigDecimal.ZERO))
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
transactionDTO.setPrincipalPaid(principalPaid);
transactionDTO.setFeePaid(feePaid);
transactionDTO.setPenaltyPaid(penaltyPaid);
}
// Populate loanChargeData for CHARGE_ADJUSTMENT transactions
LoanTransactionRelation loanTransactionRelation = loanTransaction.getLoanTransactionRelations().stream()
.filter(e -> LoanTransactionRelationTypeEnum.CHARGE_ADJUSTMENT.equals(e.getRelationType())).findAny().orElse(null);
if (loanTransactionRelation != null) {
LoanCharge loanCharge = loanTransactionRelation.getToCharge();
transactionDTO.setLoanChargeData(loanCharge.toData());
}
// Set loanToLoanTransfer
transactionDTO.setLoanToLoanTransfer(false);
return transactionDTO;
}
private static class OfficeCurrencyKey {
final Office office;
@@ -31,7 +31,6 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
@@ -88,26 +87,24 @@ import org.apache.fineract.portfolio.delinquency.service.DelinquencyWritePlatfor
import org.apache.fineract.portfolio.delinquency.validator.LoanDelinquencyActionData;
import org.apache.fineract.portfolio.group.domain.Group;
import org.apache.fineract.portfolio.group.exception.GroupNotActiveException;
import org.apache.fineract.portfolio.loanaccount.data.AccountingBridgeDataDTO;
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
import org.apache.fineract.portfolio.loanaccount.data.LoanRefundRequestData;
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData;
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanAccountingBridgeMapper;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanDownPaymentTransactionValidator;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanForeclosureValidator;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanTransactionValidator;
import org.apache.fineract.portfolio.loanaccount.service.InterestRefundService;
import org.apache.fineract.portfolio.loanaccount.service.InterestRefundServiceDelegate;
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualTransactionBusinessEventService;
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualsProcessingService;
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeService;
import org.apache.fineract.portfolio.loanaccount.service.LoanDownPaymentHandlerService;
import org.apache.fineract.portfolio.loanaccount.service.LoanJournalEntryPoster;
import org.apache.fineract.portfolio.loanaccount.service.LoanRefundService;
import org.apache.fineract.portfolio.loanaccount.service.LoanScheduleService;
import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionProcessingService;
@@ -135,7 +132,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
private final ConfigurationDomainService configurationDomainService;
private final HolidayRepository holidayRepository;
private final WorkingDaysRepositoryWrapper workingDaysRepository;
private final JournalEntryWritePlatformService journalEntryWritePlatformService;
private final NoteRepository noteRepository;
private final BusinessEventNotifierService businessEventNotifierService;
private final LoanUtilService loanUtilService;
@@ -145,7 +141,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
private final DelinquencyWritePlatformService delinquencyWritePlatformService;
private final LoanLifecycleStateMachine loanLifecycleStateMachine;
private final ExternalIdFactory externalIdFactory;
private final LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService;
private final DelinquencyEffectivePauseHelper delinquencyEffectivePauseHelper;
private final DelinquencyReadPlatformService delinquencyReadPlatformService;
private final LoanAccrualsProcessingService loanAccrualsProcessingService;
@@ -160,11 +155,11 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
private final LoanRefundService loanRefundService;
private final LoanAccountService loanAccountService;
private final ReprocessLoanTransactionsService reprocessLoanTransactionsService;
private final LoanAccountingBridgeMapper loanAccountingBridgeMapper;
private final LoanTransactionProcessingService loanTransactionProcessingService;
private final LoanBalanceService loanBalanceService;
private final LoanTransactionService loanTransactionService;
private final LoanAccountDomainServiceJpaHelper loanAccountDomainServiceJpaHelper;
private final LoanJournalEntryPoster journalEntryPoster;
@Transactional
@Override
@@ -235,9 +230,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
* .validateRepaymentDateWithMeetingDate(transactionDate, calendarInstance); }
*/
final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>();
final Money repaymentAmount = Money.of(loan.getCurrency(), transactionAmount);
LoanTransaction newRepaymentTransaction;
if (isRecoveryRepayment) {
@@ -273,11 +265,11 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
loanDownPaymentTransactionValidator.validateRepaymentTypeAccountStatus(loan, newRepaymentTransaction, event);
loanTransactionValidator.validateActivityNotBeforeClientOrGroupTransferDate(loan, event,
newRepaymentTransaction.getTransactionDate());
makeRepayment(loan, newRepaymentTransaction, existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO);
makeRepayment(loan, newRepaymentTransaction, scheduleGeneratorDTO);
if (loan.isInterestBearingAndInterestRecalculationEnabled()) {
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
loanAccrualsProcessingService.reprocessExistingAccruals(loan, true);
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan, true);
}
loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(newRepaymentTransaction);
@@ -289,12 +281,11 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
}
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, loan.isInterestBearingAndInterestRecalculationEnabled(),
false);
true);
setLoanDelinquencyTag(loan, transactionDate);
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, isLoanToLoanTransfer);
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
journalEntryPoster.postJournalEntriesForLoanTransaction(newRepaymentTransaction, isAccountTransfer, isLoanToLoanTransfer);
if (!repaymentTransactionType.isChargeRefund()) {
final LoanTransactionBusinessEvent transactionRepaymentEvent = getTransactionRepaymentTypeBusinessEvent(
repaymentTransactionType, isRecoveryRepayment, newRepaymentTransaction);
@@ -393,7 +384,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
public LoanTransaction makeChargePayment(final Loan loan, final Long chargeId, final LocalDate transactionDate,
final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final String noteText, final ExternalId txnExternalId,
final Integer transactionType, Integer installmentNumber) {
boolean isAccountTransfer = true;
checkClientOrGroupActive(loan);
if (loan.isChargedOff() && DateUtils.isBefore(transactionDate, loan.getChargedOffOnDate())) {
throw new GeneralPlatformDomainRuleException("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date", "Loan: "
@@ -403,9 +393,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
}
businessEventNotifierService.notifyPreBusinessEvent(new LoanChargePaymentPreBusinessEvent(loan));
final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>();
final Money paymentAmout = Money.of(loan.getCurrency(), transactionAmount);
final LoanTransactionType loanTransactionType = LoanTransactionType.fromInt(transactionType);
@@ -413,7 +400,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
transactionDate, txnExternalId, loanTransactionType);
if (loanTransactionType.isRepaymentAtDisbursement()) {
handlePayDisbursementTransaction(loan, chargeId, newPaymentTransaction, existingTransactionIds, existingReversedTransactionIds);
handlePayDisbursementTransaction(loan, chargeId, newPaymentTransaction);
} else {
final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled();
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), transactionDate,
@@ -433,8 +420,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
LoanEvent.LOAN_CHARGE_PAYMENT);
loanTransactionValidator.validateActivityNotBeforeClientOrGroupTransferDate(loan, LoanEvent.LOAN_CHARGE_PAYMENT,
newPaymentTransaction.getTransactionDate());
loanChargeService.makeChargePayment(loan, chargeId, existingTransactionIds, existingReversedTransactionIds,
newPaymentTransaction, installmentNumber);
loanChargeService.makeChargePayment(loan, chargeId, newPaymentTransaction, installmentNumber);
}
loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(newPaymentTransaction);
loanAccountService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
@@ -445,19 +431,15 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
}
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, loan.isInterestBearingAndInterestRecalculationEnabled(),
false);
true);
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
journalEntryPoster.postJournalEntriesForLoanTransaction(newPaymentTransaction, true, false);
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
businessEventNotifierService.notifyPostBusinessEvent(new LoanChargePaymentPostBusinessEvent(newPaymentTransaction));
return newPaymentTransaction;
}
private void handlePayDisbursementTransaction(final Loan loan, final Long chargeId, final LoanTransaction chargesPayment,
final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds) {
existingTransactionIds.addAll(loanTransactionRepository.findTransactionIdsByLoan(loan));
existingReversedTransactionIds.addAll(loanTransactionRepository.findReversedTransactionIdsByLoan(loan));
private void handlePayDisbursementTransaction(final Loan loan, final Long chargeId, final LoanTransaction chargesPayment) {
LoanCharge charge = null;
for (final LoanCharge loanCharge : loan.getCharges()) {
if (loanCharge.isActive() && chargeId.equals(loanCharge.getId())) {
@@ -474,38 +456,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
charge.markAsFullyPaid();
}
private void postJournalEntries(final Loan loanAccount, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds, boolean isAccountTransfer) {
postJournalEntries(loanAccount, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, false);
}
private void postJournalEntries(final Loan loanAccount, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds, boolean isAccountTransfer, boolean isLoanToLoanTransfer) {
final MonetaryCurrency currency = loanAccount.getCurrency();
if (loanAccount.isChargedOff()) {
final List<AccountingBridgeDataDTO> accountingBridgeDataList = loanAccountingBridgeMapper
.deriveAccountingBridgeDataForChargeOff(currency.getCode(), existingTransactionIds, existingReversedTransactionIds,
isAccountTransfer, loanAccount);
if (isLoanToLoanTransfer) {
accountingBridgeDataList.forEach(dto -> dto.getNewLoanTransactions().forEach(tx -> tx.setLoanToLoanTransfer(true)));
}
for (AccountingBridgeDataDTO accountingBridgeData : accountingBridgeDataList) {
this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
}
} else {
final AccountingBridgeDataDTO accountingBridgeData = loanAccountingBridgeMapper.deriveAccountingBridgeData(currency.getCode(),
existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, loanAccount);
if (isLoanToLoanTransfer) {
accountingBridgeData.getNewLoanTransactions().forEach(tx -> tx.setLoanToLoanTransfer(true));
}
this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
}
}
private void checkClientOrGroupActive(final Loan loan) {
final Client client = loan.client();
if (client != null) {
@@ -525,7 +475,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
public LoanTransaction makeRefund(final Long accountId, final CommandProcessingResultBuilder builderResult,
final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final String noteText,
final ExternalId txnExternalId) {
boolean isAccountTransfer = true;
final Loan loan = this.loanAccountAssembler.assembleFrom(accountId);
checkClientOrGroupActive(loan);
if (loan.isChargedOff() && DateUtils.isBefore(transactionDate, loan.getChargedOffOnDate())) {
@@ -535,8 +484,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
loan.getId());
}
businessEventNotifierService.notifyPreBusinessEvent(new LoanRefundPreBusinessEvent(loan));
final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>();
final Money refundAmount = Money.of(loan.getCurrency(), transactionAmount);
final LoanTransaction newRefundTransaction = LoanTransaction.refund(loan.getOffice(), refundAmount, paymentDetail, transactionDate,
@@ -552,7 +499,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
loanTransactionValidator.validateRepaymentDateIsOnNonWorkingDay(newRefundTransaction.getTransactionDate(), workingDays,
allowTransactionsOnNonWorkingDay);
loanRefundService.makeRefund(loan, newRefundTransaction, existingTransactionIds, existingReversedTransactionIds);
loanRefundService.makeRefund(loan, newRefundTransaction);
loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(newRefundTransaction);
this.loanRepositoryWrapper.saveAndFlush(loan);
@@ -562,8 +509,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
this.noteRepository.save(note);
}
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
journalEntryPoster.postJournalEntriesForLoanTransaction(newRefundTransaction, true, false);
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
businessEventNotifierService.notifyPostBusinessEvent(new LoanRefundPostBusinessEvent(newRefundTransaction));
builderResult.withEntityId(newRefundTransaction.getId()).withOfficeId(loan.getOfficeId()).withClientId(loan.getClientId())
@@ -591,9 +537,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
+ " backdated transaction is not allowed. Transaction date cannot be earlier than the charge-off date of the loan",
loan.getId());
}
boolean isAccountTransfer = true;
final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>();
final Money amount = Money.of(loan.getCurrency(), transactionAmount);
LoanTransaction disbursementTransaction = LoanTransaction.disbursement(loan, amount, paymentDetail, transactionDate, txnExternalId,
loan.getTotalOverpaidAsMoney());
@@ -611,7 +554,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
this.noteRepository.save(note);
}
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, isLoanToLoanTransfer);
journalEntryPoster.postJournalEntriesForLoanTransaction(disbursementTransaction, true, isLoanToLoanTransfer);
return disbursementTransaction;
}
@@ -676,8 +619,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
}
businessEventNotifierService.notifyPreBusinessEvent(new LoanCreditBalanceRefundPreBusinessEvent(loan));
final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>();
final Money refundAmount = Money.of(loan.getCurrency(), transactionAmount);
LoanTransaction newCreditBalanceRefundTransaction = LoanTransaction.creditBalanceRefund(loan, loan.getOffice(), refundAmount,
@@ -686,8 +627,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
loanDownPaymentTransactionValidator.validateAccountStatus(loan, LoanEvent.LOAN_CREDIT_BALANCE_REFUND);
loanTransactionValidator.validateRefundDateIsAfterLastRepayment(loan, newCreditBalanceRefundTransaction.getTransactionDate());
loanRefundService.creditBalanceRefund(loan, newCreditBalanceRefundTransaction, existingTransactionIds,
existingReversedTransactionIds);
loanRefundService.creditBalanceRefund(loan, newCreditBalanceRefundTransaction);
newCreditBalanceRefundTransaction = this.loanTransactionRepository.saveAndFlush(newCreditBalanceRefundTransaction);
@@ -697,10 +637,9 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
}
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, loan.isInterestBearingAndInterestRecalculationEnabled(),
false);
true);
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, false);
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
journalEntryPoster.postJournalEntriesForLoanTransaction(newCreditBalanceRefundTransaction, false, false);
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
businessEventNotifierService
.notifyPostBusinessEvent(new LoanCreditBalanceRefundPostBusinessEvent(newCreditBalanceRefundTransaction));
@@ -714,8 +653,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
final Loan loan = this.loanAccountAssembler.assembleFrom(accountId);
checkClientOrGroupActive(loan);
businessEventNotifierService.notifyPreBusinessEvent(new LoanRefundPreBusinessEvent(loan));
final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>();
final Money refundAmount = Money.of(loan.getCurrency(), transactionAmount);
if (loan.isChargedOff() && DateUtils.isBefore(transactionDate, loan.getChargedOffOnDate())) {
@@ -740,7 +677,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
allowTransactionsOnNonWorkingDay);
loanTransactionValidator.validateActivityNotBeforeClientOrGroupTransferDate(loan, LoanEvent.LOAN_REFUND,
newRefundTransaction.getTransactionDate());
loanRefundService.makeRefundForActiveLoan(loan, newRefundTransaction, existingTransactionIds, existingReversedTransactionIds);
loanRefundService.makeRefundForActiveLoan(loan, newRefundTransaction);
this.loanTransactionRepository.saveAndFlush(newRefundTransaction);
@@ -750,10 +687,9 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
}
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, loan.isInterestBearingAndInterestRecalculationEnabled(),
false);
true);
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, false);
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
journalEntryPoster.postJournalEntriesForLoanTransaction(newRefundTransaction, false, false);
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
businessEventNotifierService.notifyPostBusinessEvent(new LoanRefundPostBusinessEvent(newRefundTransaction));
@@ -777,10 +713,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
MonetaryCurrency currency = loan.getCurrency();
List<LoanTransaction> newTransactions = new ArrayList<>();
final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>();
existingTransactionIds.addAll(loanTransactionRepository.findTransactionIdsByLoan(loan));
existingReversedTransactionIds.addAll(loanTransactionRepository.findReversedTransactionIdsByLoan(loan));
final ScheduleGeneratorDTO scheduleGeneratorDTO = null;
final LoanRepaymentScheduleInstallment foreCloseDetail = loanBalanceService.fetchLoanForeclosureDetail(loan, foreClosureDate);
@@ -809,14 +741,15 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
loanDownPaymentTransactionValidator.validateAccountStatus(loan, LoanEvent.LOAN_FORECLOSURE);
handleForeClosureTransactions(loan, payment, scheduleGeneratorDTO);
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
loanAccrualsProcessingService.reprocessExistingAccruals(loan, true);
if (loan.isInterestBearingAndInterestRecalculationEnabled()) {
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan, true);
}
for (LoanTransaction newTransaction : newTransactions) {
LoanTransaction savedNewTransaction = loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(newTransaction);
loan.addLoanTransaction(savedNewTransaction);
journalEntryPoster.postJournalEntriesForLoanTransaction(newTransaction, false, false);
transactionIds.add(savedNewTransaction.getId());
}
changes.put("transactions", transactionIds);
@@ -830,8 +763,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
this.noteRepository.save(note);
}
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, false);
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
businessEventNotifierService.notifyPostBusinessEvent(new LoanForeClosurePostBusinessEvent(payment));
return payment;
@@ -1002,18 +933,13 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
final LocalDate transactionDate = DateUtils.getBusinessLocalDate();
final BigDecimal refundAmount = loanRefundRequest.getTotalAmount();
final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>();
existingTransactionIds.addAll(loanTransactionRepository.findTransactionIdsByLoan(loan));
existingReversedTransactionIds.addAll(loanTransactionRepository.findReversedTransactionIdsByLoan(loan));
final LoanTransaction interestRefundTransaction = LoanTransaction.interestRefund(loan, loan.getOffice(), refundAmount,
loanRefundRequest.getPrincipal(), loanRefundRequest.getInterest(), loanRefundRequest.getFeeCharges(),
loanRefundRequest.getPenaltyCharges(), paymentDetail, transactionDate, externalIdFactory.create());
interestRefundTransaction.updateLoan(loan);
loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(interestRefundTransaction);
loan.addLoanTransaction(interestRefundTransaction);
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, false);
journalEntryPoster.postJournalEntriesForLoanTransaction(interestRefundTransaction, false, false);
return interestRefundTransaction;
}
@@ -1089,12 +1015,9 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
}
@SuppressWarnings("null")
private void makeRepayment(final Loan loan, final LoanTransaction repaymentTransaction, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds, final ScheduleGeneratorDTO scheduleGeneratorDTO) {
private void makeRepayment(final Loan loan, final LoanTransaction repaymentTransaction,
final ScheduleGeneratorDTO scheduleGeneratorDTO) {
loanChargeValidator.validateRepaymentTypeTransactionNotBeforeAChargeRefund(loan, repaymentTransaction, "created");
existingTransactionIds.addAll(loanTransactionRepository.findTransactionIdsByLoan(loan));
existingReversedTransactionIds.addAll(loanTransactionRepository.findReversedTransactionIdsByLoan(loan));
loanDownPaymentHandlerService.handleRepaymentOrRecoveryOrWaiverTransaction(loan, repaymentTransaction, null, scheduleGeneratorDTO);
}
@@ -974,7 +974,7 @@ public class LoanScheduleAssembler {
final LocalDate recalculateFrom = null;
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
loanAccrualsProcessingService.reprocessExistingAccruals(loan, false);
}
@@ -1553,7 +1553,7 @@ public class LoanScheduleAssembler {
if (actualChanges.containsKey(LoanApiConstants.approvedLoanAmountParameterName)
|| actualChanges.containsKey("recalculateLoanSchedule") || actualChanges.containsKey("expectedDisbursementDate")) {
loanScheduleService.regenerateRepaymentSchedule(loan, loanUtilService.buildScheduleGeneratorDTO(loan, null));
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
loanAccrualsProcessingService.reprocessExistingAccruals(loan, false);
}
}
@@ -101,7 +101,7 @@ public class LoanScheduleWritePlatformServiceImpl implements LoanScheduleWritePl
final LocalDate recalculateFrom = null;
ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
loanAccrualsProcessingService.reprocessExistingAccruals(loan, false);
loanAccountService.saveLoanWithDataIntegrityViolationChecks(loan);
businessEventNotifierService.notifyPostBusinessEvent(new LoanScheduleVariationsDeletedBusinessEvent(loan));
return new CommandProcessingResultBuilder() //
@@ -32,7 +32,6 @@ import java.util.Optional;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
import org.apache.fineract.infrastructure.codes.domain.CodeValue;
import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
@@ -47,9 +46,7 @@ import org.apache.fineract.infrastructure.event.business.domain.loan.LoanBalance
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanRescheduledDueAdjustScheduleBusinessEvent;
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
import org.apache.fineract.portfolio.loanaccount.data.AccountingBridgeDataDTO;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
@@ -72,14 +69,12 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanRepayme
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryWritePlatformService;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanAccountingBridgeMapper;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanTermVariationsMapper;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.RescheduleLoansApiConstants;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidator;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequestRepository;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.exception.LoanRescheduleRequestNotFoundException;
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualTransactionBusinessEventService;
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualsProcessingService;
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeService;
@@ -108,20 +103,17 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
private final LoanRescheduleRequestRepository loanRescheduleRequestRepository;
private final LoanRepaymentScheduleHistoryRepository loanRepaymentScheduleHistoryRepository;
private final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService;
private final JournalEntryWritePlatformService journalEntryWritePlatformService;
private final LoanRepositoryWrapper loanRepositoryWrapper;
private final LoanAssembler loanAssembler;
private final LoanUtilService loanUtilService;
private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory;
private final LoanScheduleGeneratorFactory loanScheduleFactory;
private final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository;
private final LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService;
private final BusinessEventNotifierService businessEventNotifierService;
private final LoanAccrualsProcessingService loanAccrualsProcessingService;
private final LoanChargeService loanChargeService;
private final ReprocessLoanTransactionsService reprocessLoanTransactionsService;
private final LoanTermVariationsMapper loanTermVariationsMapper;
private final LoanAccountingBridgeMapper loanAccountingBridgeMapper;
private final LoanScheduleComponent loanSchedule;
private final LoanTransactionRepository loanTransactionRepository;
private final LoanLifecycleStateMachine loanLifecycleStateMachine;
@@ -355,9 +347,6 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
changes.put("approvedByUserId", appUser.getId());
Loan loan = loanRescheduleRequest.getLoan();
final List<Long> existingTransactionIds = new ArrayList<>(loanTransactionRepository.findTransactionIdsByLoan(loan));
final List<Long> existingReversedTransactionIds = new ArrayList<>(
loanTransactionRepository.findReversedTransactionIdsByLoan(loan));
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan,
loanRescheduleRequest.getRescheduleFromDate());
@@ -442,7 +431,7 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
} else {
loanSchedule.updateLoanSchedule(loan, loanScheduleDTO.getLoanScheduleModel());
}
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
loanAccrualsProcessingService.reprocessExistingAccruals(loan, true);
loanChargeService.recalculateAllCharges(loan);
reprocessLoanTransactionsService.reprocessTransactions(loan);
@@ -460,11 +449,9 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
}
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, true, false);
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, true, true);
businessEventNotifierService.notifyPostBusinessEvent(new LoanRescheduledDueAdjustScheduleBusinessEvent(loan));
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
if (hasInterestRateChange) {
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
}
@@ -507,14 +494,6 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
}
}
private void postJournalEntries(Loan loan, List<Long> existingTransactionIds, List<Long> existingReversedTransactionIds) {
final MonetaryCurrency currency = loan.getCurrency();
boolean isAccountTransfer = false;
final AccountingBridgeDataDTO accountingBridgeData = loanAccountingBridgeMapper.deriveAccountingBridgeData(currency.getCode(),
existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, loan);
this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
}
@Override
@Transactional
public CommandProcessingResult reject(JsonCommand jsonCommand) {
@@ -63,6 +63,7 @@ public class LoanAccrualActivityProcessingServiceImpl implements LoanAccrualActi
private final LoanAccountService loanAccountService;
private final LoanBalanceService loanBalanceService;
private final LoanTransactionRepository loanTransactionRepository;
private final LoanJournalEntryPoster journalEntryPoster;
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
@@ -245,6 +246,7 @@ public class LoanAccrualActivityProcessingServiceImpl implements LoanAccrualActi
loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(newLoanTransaction);
loan.addLoanTransaction(newLoanTransaction);
journalEntryPoster.postJournalEntriesForLoanTransaction(newLoanTransaction, false, false);
LoanAdjustTransactionBusinessEvent.Data data = new LoanAdjustTransactionBusinessEvent.Data(loanTransaction);
data.setNewTransactionDetail(newLoanTransaction);
@@ -277,6 +279,7 @@ public class LoanAccrualActivityProcessingServiceImpl implements LoanAccrualActi
if (newAccrualActivityTransaction != null) {
LoanTransaction savedNewTransaction = makeAccrualActivityTransaction(loan, newAccrualActivityTransaction);
loan.addLoanTransaction(savedNewTransaction);
journalEntryPoster.postJournalEntriesForLoanTransaction(savedNewTransaction, false, false);
}
}
@@ -50,7 +50,7 @@ public class LoanAccrualEventService {
LoanStatus status = loan.getStatus();
if (status.isClosedObligationsMet() || status.isOverpaid()) {
log.debug("Loan closure on accrual for loan {}", loan.getId());
loanAccrualsProcessingService.processAccrualsOnLoanClosure(loan, false);
loanAccrualsProcessingService.processAccrualsOnLoanClosure(loan, true);
loanAccrualActivityProcessingService.processAccrualActivityForLoanClosure(loan);
}
}
@@ -45,7 +45,6 @@ import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.accounting.common.AccountingRuleType;
import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.core.config.TaskExecutorConstant;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
@@ -55,7 +54,6 @@ import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanAdjustTransactionBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanAccrualAdjustmentTransactionBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanAccrualTransactionCreatedBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionBusinessEvent;
@@ -63,8 +61,6 @@ import org.apache.fineract.infrastructure.event.business.service.BusinessEventNo
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.loanaccount.data.AccountingBridgeDataDTO;
import org.apache.fineract.portfolio.loanaccount.data.AccountingBridgeLoanTransactionDTO;
import org.apache.fineract.portfolio.loanaccount.data.AccrualBalances;
import org.apache.fineract.portfolio.loanaccount.data.AccrualChargeData;
import org.apache.fineract.portfolio.loanaccount.data.AccrualPeriodData;
@@ -88,7 +84,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanAccountingBridgeMapper;
import org.apache.fineract.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
import org.springframework.beans.factory.annotation.Qualifier;
@@ -110,18 +105,16 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
private final BusinessEventNotifierService businessEventNotifierService;
private final ConfigurationDomainService configurationDomainService;
private final LoanRepositoryWrapper loanRepositoryWrapper;
private final LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService;
private final JournalEntryWritePlatformService journalEntryWritePlatformService;
private final LoanTransactionRepository loanTransactionRepository;
private final LoanScheduleGeneratorFactory loanScheduleFactory;
@Qualifier(TaskExecutorConstant.CONFIGURABLE_TASK_EXECUTOR_BEAN_NAME)
private final ThreadPoolTaskExecutor taskExecutor;
private final TransactionTemplate transactionTemplate;
private final LoanAccountingBridgeMapper loanAccountingBridgeMapper;
private final LoanChargeService loanChargeService;
private final LoanBalanceService loanBalanceService;
private final LoanChargePaidByRepository loanChargePaidByRepository;
private final LoanJournalEntryPoster journalEntryPoster;
/**
* method adds accrual for batch job "Add Periodic Accrual Transactions" and add accruals api for Loan
@@ -210,13 +203,13 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
* reschedule
*/
@Override
public void reprocessExistingAccruals(@NotNull Loan loan) {
public void reprocessExistingAccruals(@NotNull final Loan loan, final boolean addEvent) {
List<LoanTransaction> accrualTransactions = retrieveListOfAccrualTransactions(loan);
if (!accrualTransactions.isEmpty()) {
if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()) {
reprocessPeriodicAccruals(loan, accrualTransactions);
reprocessPeriodicAccruals(loan, accrualTransactions, addEvent);
} else if (loan.isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct()) {
reprocessNonPeriodicAccruals(loan, accrualTransactions);
reprocessNonPeriodicAccruals(loan, accrualTransactions, addEvent);
}
}
}
@@ -253,40 +246,37 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
if (isProgressiveAccrual(loan)) {
return;
}
final List<Long> existingTransactionIds = new ArrayList<>(loanTransactionRepository.findTransactionIdsByLoan(loan));
final List<Long> existingReversedTransactionIds = new ArrayList<>(loanTransactionRepository.findReversedTransactionIdsByLoan(loan));
processIncomePostingAndAccruals(loan);
processIncomePostingAndAccruals(loan, true);
this.loanRepositoryWrapper.saveAndFlush(loan);
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
}
/**
* method calculates accruals for loan with interest recalculation and compounding to be posted as income
*/
@Override
public void processIncomePostingAndAccruals(@NotNull Loan loan) {
public void processIncomePostingAndAccruals(@NotNull final Loan loan, final boolean addEvent) {
if (isProgressiveAccrual(loan)) {
return;
}
LoanInterestRecalculationDetails recalculationDetails = loan.getLoanInterestRecalculationDetails();
final LoanInterestRecalculationDetails recalculationDetails = loan.getLoanInterestRecalculationDetails();
if (recalculationDetails == null || !recalculationDetails.isCompoundingToBePostedAsTransaction()) {
return;
}
LocalDate lastCompoundingDate = loan.getDisbursementDate();
List<LoanInterestRecalcualtionAdditionalDetails> compoundingDetails = extractInterestRecalculationAdditionalDetails(loan);
final List<LoanInterestRecalcualtionAdditionalDetails> compoundingDetails = extractInterestRecalculationAdditionalDetails(loan);
for (LoanInterestRecalcualtionAdditionalDetails compoundingDetail : compoundingDetails) {
if (!DateUtils.isBeforeBusinessDate(compoundingDetail.getEffectiveDate())) {
break;
}
addUpdateIncomeAndAccrualTransaction(loan, compoundingDetail, lastCompoundingDate);
addUpdateIncomeAndAccrualTransaction(loan, compoundingDetail, lastCompoundingDate, addEvent);
lastCompoundingDate = compoundingDetail.getEffectiveDate();
}
List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
LoanRepaymentScheduleInstallment lastInstallment = LoanRepaymentScheduleInstallment.getLastNonDownPaymentInstallment(installments);
final List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
final LoanRepaymentScheduleInstallment lastInstallment = LoanRepaymentScheduleInstallment
.getLastNonDownPaymentInstallment(installments);
reverseTransactionsAfter(loan, Set.of(ACCRUAL, ACCRUAL_ADJUSTMENT, INCOME_POSTING), lastInstallment.getDueDate(), false);
reverseTransactionsAfter(loan, Set.of(ACCRUAL, ACCRUAL_ADJUSTMENT, INCOME_POSTING), lastInstallment.getDueDate(), addEvent);
}
/**
@@ -431,22 +421,14 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
loanTransactionRepository.flush();
if (addJournal) {
final List<AccountingBridgeLoanTransactionDTO> newTransactionDTOs = new ArrayList<>();
for (LoanTransaction accrualTransaction : accrualTransactions) {
final LoanTransactionBusinessEvent businessEvent = accrualTransaction.isAccrual()
? new LoanAccrualTransactionCreatedBusinessEvent(accrualTransaction)
: new LoanAccrualAdjustmentTransactionBusinessEvent(accrualTransaction);
businessEventNotifierService.notifyPostBusinessEvent(businessEvent);
final AccountingBridgeLoanTransactionDTO transactionDTO = loanAccountingBridgeMapper
.mapToLoanTransactionData(accrualTransaction, currency.getCode());
newTransactionDTOs.add(transactionDTO);
// Create journal entries immediately for this transaction
journalEntryPoster.postJournalEntriesForLoanTransaction(accrualTransaction, false, false);
}
final AccountingBridgeDataDTO accountingBridgeData = new AccountingBridgeDataDTO(loan.getId(), loan.getLoanProduct().getId(),
loan.getOfficeId(), loan.getCurrencyCode(), loan.getSummary().getTotalInterestCharged(),
loan.isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct(),
loan.isUpfrontAccrualAccountingEnabledOnLoanProduct(), loan.isPeriodicAccrualAccountingEnabledOnLoanProduct(), false,
false, false, null, false, newTransactionDTOs, loan.getLoanProductRelatedDetail().isMerchantBuyDownFee());
this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
}
}
@@ -749,7 +731,7 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
// ReprocessAccruals
private void reprocessPeriodicAccruals(Loan loan, final List<LoanTransaction> accrualTransactions) {
private void reprocessPeriodicAccruals(Loan loan, final List<LoanTransaction> accrualTransactions, final boolean addEvent) {
if (loan.isChargedOff()) {
return;
}
@@ -809,14 +791,15 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
boolean isBasedOnSubmittedOnDate = !isChargeOnDueDate;
for (LoanRepaymentScheduleInstallment installment : installments) {
checkAndUpdateAccrualsForInstallment(loan, accrualTransactions, installments, isBasedOnSubmittedOnDate, installment);
checkAndUpdateAccrualsForInstallment(loan, accrualTransactions, installments, isBasedOnSubmittedOnDate, installment,
addEvent);
}
}
// reverse accruals after last installment
reverseTransactionsAfter(loan, ACCRUAL_TYPES, lastDueDate, false);
reverseTransactionsAfter(loan, ACCRUAL_TYPES, lastDueDate, addEvent);
}
private void reprocessNonPeriodicAccruals(Loan loan, final List<LoanTransaction> accrualTransactions) {
private void reprocessNonPeriodicAccruals(Loan loan, final List<LoanTransaction> accrualTransactions, final boolean addEvent) {
if (isProgressiveAccrual(loan)) {
return;
}
@@ -828,6 +811,12 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
if (accrualTransaction.getInterestPortion(loan.getCurrency()).isGreaterThanZero()) {
if (accrualTransaction.getInterestPortion(loan.getCurrency()).isNotEqualTo(interestApplied)) {
accrualTransaction.reverse();
if (addEvent) {
journalEntryPoster.postJournalEntriesForLoanTransaction(accrualTransaction, false, false);
final LoanTransactionBusinessEvent businessEvent = new LoanAccrualAdjustmentTransactionBusinessEvent(
accrualTransaction);
businessEventNotifierService.notifyPostBusinessEvent(businessEvent);
}
if (isExternalIdAutoGenerationEnabled) {
externalId = ExternalId.generate();
}
@@ -835,6 +824,12 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
interestApplied, loan.getDisbursementDate(), externalId);
LoanTransaction savedInterestAccrualTransaction = loanTransactionRepository.save(interestAccrualTransaction);
loan.addLoanTransaction(savedInterestAccrualTransaction);
if (addEvent) {
journalEntryPoster.postJournalEntriesForLoanTransaction(savedInterestAccrualTransaction, false, false);
final LoanTransactionBusinessEvent businessEvent = new LoanAccrualTransactionCreatedBusinessEvent(
savedInterestAccrualTransaction);
businessEventNotifierService.notifyPostBusinessEvent(businessEvent);
}
}
} else {
Set<LoanChargePaidBy> chargePaidBies = accrualTransaction.getLoanChargesPaid();
@@ -843,7 +838,23 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
Money chargeAmount = loanCharge.getAmount(loan.getCurrency());
if (chargeAmount.isNotEqualTo(accrualTransaction.getAmount(loan.getCurrency()))) {
accrualTransaction.reverse();
loanChargeService.handleChargeAppliedTransaction(loan, loanCharge, accrualTransaction.getTransactionDate());
if (addEvent) {
journalEntryPoster.postJournalEntriesForLoanTransaction(accrualTransaction, false, false);
final LoanTransactionBusinessEvent businessEvent = new LoanAccrualAdjustmentTransactionBusinessEvent(
accrualTransaction);
businessEventNotifierService.notifyPostBusinessEvent(businessEvent);
}
final LoanTransaction applyLoanChargeTransaction = loanChargeService.handleChargeAppliedTransaction(loan,
loanCharge, accrualTransaction.getTransactionDate());
if (applyLoanChargeTransaction != null) {
LoanTransaction savedApplyLoanChargeTransaction = loanTransactionRepository.save(applyLoanChargeTransaction);
if (addEvent) {
journalEntryPoster.postJournalEntriesForLoanTransaction(savedApplyLoanChargeTransaction, false, false);
final LoanTransactionBusinessEvent businessEvent = new LoanAccrualTransactionCreatedBusinessEvent(
savedApplyLoanChargeTransaction);
businessEventNotifierService.notifyPostBusinessEvent(businessEvent);
}
}
}
}
}
@@ -852,7 +863,7 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
private void checkAndUpdateAccrualsForInstallment(Loan loan, List<LoanTransaction> accrualTransactions,
List<LoanRepaymentScheduleInstallment> installments, boolean isBasedOnSubmittedOnDate,
LoanRepaymentScheduleInstallment installment) {
LoanRepaymentScheduleInstallment installment, final boolean addEvent) {
MonetaryCurrency currency = loan.getCurrency();
Money zero = Money.zero(currency);
Money interest = zero;
@@ -870,6 +881,12 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
fee = fee.minus(accrualTransaction.getFeeChargesPortion(currency));
penalty = penalty.minus(accrualTransaction.getPenaltyChargesPortion(currency));
accrualTransaction.reverse();
if (addEvent) {
journalEntryPoster.postJournalEntriesForLoanTransaction(accrualTransaction, false, false);
final LoanTransactionBusinessEvent businessEvent = new LoanAccrualAdjustmentTransactionBusinessEvent(
accrualTransaction);
businessEventNotifierService.notifyPostBusinessEvent(businessEvent);
}
}
}
}
@@ -910,7 +927,7 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
}
private void addUpdateIncomeAndAccrualTransaction(Loan loan, LoanInterestRecalcualtionAdditionalDetails compoundingDetail,
LocalDate lastCompoundingDate) {
LocalDate lastCompoundingDate, boolean addEvent) {
BigDecimal interest = BigDecimal.ZERO;
BigDecimal fee = BigDecimal.ZERO;
BigDecimal penalties = BigDecimal.ZERO;
@@ -938,7 +955,7 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
}
createUpdateIncomePostingTransaction(loan, compoundingDetail, interest, fee, penalties, externalId);
createUpdateAccrualTransaction(loan, compoundingDetail, interest, fee, penalties, feeDetails, externalId);
createUpdateAccrualTransaction(loan, compoundingDetail, interest, fee, penalties, feeDetails, externalId, addEvent);
loanBalanceService.updateLoanOutstandingBalances(loan);
}
@@ -947,21 +964,25 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
final Optional<LoanTransaction> incomeTransaction = loanTransactionRepository.findNonReversedByLoanAndTypesAndDate(loan,
Set.of(INCOME_POSTING), compoundingDetail.getEffectiveDate());
if (incomeTransaction.isEmpty()) {
LoanTransaction transaction = LoanTransaction.incomePosting(loan, loan.getOffice(), compoundingDetail.getEffectiveDate(),
final LoanTransaction transaction = LoanTransaction.incomePosting(loan, loan.getOffice(), compoundingDetail.getEffectiveDate(),
compoundingDetail.getAmount(), interest, fee, penalties, externalId);
LoanTransaction savedTransaction = loanTransactionRepository.save(transaction);
final LoanTransaction savedTransaction = loanTransactionRepository.save(transaction);
loan.addLoanTransaction(savedTransaction);
journalEntryPoster.postJournalEntriesForLoanTransaction(savedTransaction, false, false);
} else if (incomeTransaction.get().getAmount(loan.getCurrency()).getAmount().compareTo(compoundingDetail.getAmount()) != 0) {
incomeTransaction.get().reverse();
LoanTransaction transaction = LoanTransaction.incomePosting(loan, loan.getOffice(), compoundingDetail.getEffectiveDate(),
journalEntryPoster.postJournalEntriesForLoanTransaction(incomeTransaction.get(), false, false);
final LoanTransaction transaction = LoanTransaction.incomePosting(loan, loan.getOffice(), compoundingDetail.getEffectiveDate(),
compoundingDetail.getAmount(), interest, fee, penalties, externalId);
LoanTransaction savedTransaction = loanTransactionRepository.save(transaction);
final LoanTransaction savedTransaction = loanTransactionRepository.save(transaction);
loan.addLoanTransaction(savedTransaction);
journalEntryPoster.postJournalEntriesForLoanTransaction(savedTransaction, false, false);
}
}
private void createUpdateAccrualTransaction(Loan loan, LoanInterestRecalcualtionAdditionalDetails compoundingDetail,
BigDecimal interest, BigDecimal fee, BigDecimal penalties, HashMap<String, Object> feeDetails, ExternalId externalId) {
BigDecimal interest, BigDecimal fee, BigDecimal penalties, HashMap<String, Object> feeDetails, ExternalId externalId,
boolean addEvent) {
if (configurationDomainService.isExternalIdAutoGenerationEnabled()) {
externalId = ExternalId.generate();
}
@@ -971,12 +992,24 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
Set.of(LoanTransactionType.ACCRUAL, LoanTransactionType.ACCRUAL_ADJUSTMENT), compoundingDetail.getEffectiveDate());
if (accrualTransaction.isEmpty() || !MathUtil.isEqualTo(accrualTransaction.get().getAmount(), compoundingDetail.getAmount())) {
accrualTransaction.ifPresent(LoanTransaction::reverse);
accrualTransaction.ifPresent(accrualTrans -> {
accrualTrans.reverse();
if (addEvent) {
journalEntryPoster.postJournalEntriesForLoanTransaction(accrualTrans, false, false);
final LoanTransactionBusinessEvent businessEvent = new LoanAccrualAdjustmentTransactionBusinessEvent(accrualTrans);
businessEventNotifierService.notifyPostBusinessEvent(businessEvent);
}
});
LoanTransaction accrual = LoanTransaction.accrueTransaction(loan, loan.getOffice(), compoundingDetail.getEffectiveDate(),
compoundingDetail.getAmount(), interest, fee, penalties, externalId);
updateLoanChargesPaidBy(loan, accrual, feeDetails, null);
LoanTransaction savedAccrual = loanTransactionRepository.save(accrual);
loan.addLoanTransaction(savedAccrual);
if (addEvent) {
journalEntryPoster.postJournalEntriesForLoanTransaction(savedAccrual, false, false);
final LoanTransactionBusinessEvent businessEvent = new LoanAccrualTransactionCreatedBusinessEvent(savedAccrual);
businessEventNotifierService.notifyPostBusinessEvent(businessEvent);
}
}
}
}
@@ -1039,6 +1072,7 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
interestToPost, feeToPost, penaltyToPost, externalId);
LoanTransaction savedFinalIncomeTransaction = loanTransactionRepository.save(finalIncomeTransaction);
loan.addLoanTransaction(savedFinalIncomeTransaction);
journalEntryPoster.postJournalEntriesForLoanTransaction(savedFinalIncomeTransaction, false, false);
if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()) {
final LocalDate lastAccruedDate = loanTransactionRepository
@@ -1055,6 +1089,7 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
updateLoanChargesPaidBy(loan, finalAccrual, feeDetails, null);
LoanTransaction savedFinalAccrual = loanTransactionRepository.save(finalAccrual);
loan.addLoanTransaction(savedFinalAccrual);
journalEntryPoster.postJournalEntriesForLoanTransaction(savedFinalAccrual, false, false);
}
}
@@ -1120,15 +1155,6 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
}
}
private void postJournalEntries(final Loan loan, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds) {
final MonetaryCurrency currency = loan.getCurrency();
boolean isAccountTransfer = false;
final AccountingBridgeDataDTO accountingBridgeData = loanAccountingBridgeMapper.deriveAccountingBridgeData(currency.getCode(),
existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, loan);
journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
}
private void ensureAccrualTransactionMappings(final Loan loan, final boolean chargeOnDueDate) {
final List<LoanChargePaidBy> entriesToProcess = loanChargePaidByRepository.findChargePaidByMappingsWithoutInstallmentNumber(loan);
@@ -1234,14 +1260,15 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
private void reverseTransactionsOnOrAfter(final Loan loan, final Set<LoanTransactionType> types, final LocalDate date) {
loanTransactionRepository.findNonReversedByLoanAndTypesAndOnOrAfterDate(loan, types, date)
.forEach(transaction -> reverseAccrual(transaction, false));
.forEach(transaction -> reverseAccrual(transaction, true));
}
private void reverseAccrual(final LoanTransaction transaction, final boolean addEvent) {
transaction.reverse();
if (addEvent) {
final LoanAdjustTransactionBusinessEvent.Data data = new LoanAdjustTransactionBusinessEvent.Data(transaction);
businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(data));
journalEntryPoster.postJournalEntriesForLoanTransaction(transaction, false, false);
final LoanTransactionBusinessEvent businessEvent = new LoanAccrualAdjustmentTransactionBusinessEvent(transaction);
businessEventNotifierService.notifyPostBusinessEvent(businessEvent);
}
}
@@ -625,7 +625,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
LocalDate recalculateFrom = null;
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
loanAccrualsProcessingService.reprocessExistingAccruals(loan, false);
}
loan.adjustNetDisbursalAmount(loan.getProposedPrincipal());
@@ -285,7 +285,7 @@ public class LoanAssemblerImpl implements LoanAssembler {
// TODO: review
loanChargeService.recalculateAllCharges(loanApplication);
topUpLoanConfiguration(element, loanApplication);
loanAccrualsProcessingService.reprocessExistingAccruals(loanApplication);
loanAccrualsProcessingService.reprocessExistingAccruals(loanApplication, false);
return loanApplication;
}
@@ -851,7 +851,7 @@ public class LoanAssemblerImpl implements LoanAssembler {
final LoanScheduleModel loanScheduleModel = this.calculationPlatformService.calculateLoanSchedule(query, false);
loanSchedule.updateLoanSchedule(loan, loanScheduleModel);
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
loanAccrualsProcessingService.reprocessExistingAccruals(loan, false);
loanChargeService.recalculateAllCharges(loan);
}
@@ -20,7 +20,6 @@ package org.apache.fineract.portfolio.loanaccount.service;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
@@ -59,9 +58,6 @@ public class LoanBuyDownFeeAmortizationProcessingServiceImpl implements LoanBuyD
@Override
@Transactional
public void processBuyDownFeeAmortizationTillDate(@NonNull Loan loan, @NonNull LocalDate tillDate, boolean addJournal) {
final List<Long> existingTransactionIds = loanTransactionRepository.findTransactionIdsByLoan(loan);
final List<Long> existingReversedTransactionIds = loanTransactionRepository.findReversedTransactionIdsByLoan(loan);
List<LoanBuyDownFeeBalance> balances = loanBuyDownFeeBalanceRepository.findAllByLoanId(loan.getId());
LocalDate maturityDate = loan.getMaturityDate() != null ? loan.getMaturityDate()
@@ -99,7 +95,7 @@ public class LoanBuyDownFeeAmortizationProcessingServiceImpl implements LoanBuyD
loanTransactionRepository.flush();
if (addJournal) {
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
journalEntryPoster.postJournalEntriesForLoanTransaction(transaction, false, false);
}
BusinessEvent<?> event = MathUtil.isGreaterThanZero(totalAmortizationAmount)
@@ -112,13 +108,6 @@ public class LoanBuyDownFeeAmortizationProcessingServiceImpl implements LoanBuyD
@Override
@Transactional
public void processBuyDownFeeAmortizationOnLoanClosure(@NonNull final Loan loan, final boolean addJournal) {
List<Long> existingTransactionIds = Collections.emptyList();
List<Long> existingReversedTransactionIds = Collections.emptyList();
if (addJournal) {
existingTransactionIds = loanTransactionRepository.findTransactionIdsByLoan(loan);
existingReversedTransactionIds = loanTransactionRepository.findReversedTransactionIdsByLoan(loan);
}
final LocalDate transactionDate = getFinalBuyDownFeeAmortizationTransactionDate(loan);
final Optional<LoanTransaction> amortizationTransaction = createBuyDownFeeAmortizationTransaction(loan, transactionDate, false,
null);
@@ -130,20 +119,16 @@ public class LoanBuyDownFeeAmortizationProcessingServiceImpl implements LoanBuyD
businessEventNotifierService
.notifyPostBusinessEvent(new LoanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent(loanTransaction));
}
if (addJournal) {
journalEntryPoster.postJournalEntriesForLoanTransaction(amortizationTransaction.get(), false, false);
}
});
if (addJournal) {
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
}
}
@Override
@Transactional
public void processBuyDownFeeAmortizationOnLoanChargeOff(@NonNull final Loan loan,
@NonNull final LoanTransaction chargeOffTransaction) {
final List<Long> existingTransactionIds = loanTransactionRepository.findTransactionIdsByLoan(loan);
final List<Long> existingReversedTransactionIds = loanTransactionRepository.findReversedTransactionIdsByLoan(loan);
LocalDate transactionDate = loan.getChargedOffOnDate();
if (transactionDate == null) {
transactionDate = DateUtils.getBusinessLocalDate();
@@ -152,7 +137,7 @@ public class LoanBuyDownFeeAmortizationProcessingServiceImpl implements LoanBuyD
final Optional<LoanTransaction> amortizationTransaction = createBuyDownFeeAmortizationTransaction(loan, transactionDate, true,
chargeOffTransaction);
if (amortizationTransaction.isPresent()) {
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
journalEntryPoster.postJournalEntriesForLoanTransaction(amortizationTransaction.get(), false, false);
if (amortizationTransaction.get().isBuyDownFeeAmortization()) {
businessEventNotifierService.notifyPostBusinessEvent(
new LoanBuyDownFeeAmortizationTransactionCreatedBusinessEvent(amortizationTransaction.get()));
@@ -167,8 +152,6 @@ public class LoanBuyDownFeeAmortizationProcessingServiceImpl implements LoanBuyD
@Transactional
public void processBuyDownFeeAmortizationOnLoanUndoChargeOff(@NonNull final LoanTransaction loanTransaction) {
final Loan loan = loanTransaction.getLoan();
final List<Long> existingTransactionIds = loanTransactionRepository.findTransactionIdsByLoan(loan);
final List<Long> existingReversedTransactionIds = loanTransactionRepository.findReversedTransactionIdsByLoan(loan);
loan.getLoanTransactions().stream().filter(LoanTransaction::isBuyDownFeeAmortization)
.filter(transaction -> transaction.getTransactionDate().equals(loanTransaction.getTransactionDate())
@@ -177,17 +160,15 @@ public class LoanBuyDownFeeAmortizationProcessingServiceImpl implements LoanBuyD
&& rel.getToTransaction().equals(loanTransaction)))
.forEach(transaction -> {
transaction.reverse();
journalEntryPoster.postJournalEntriesForLoanTransaction(transaction, false, false);
final LoanAdjustTransactionBusinessEvent.Data data = new LoanAdjustTransactionBusinessEvent.Data(transaction);
businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(data));
});
for (LoanBuyDownFeeBalance balance : loanBuyDownFeeBalanceRepository.findAllByLoanId(loan.getId())) {
balance.setUnrecognizedAmount(balance.getChargedOffAmount());
balance.setChargedOffAmount(BigDecimal.ZERO);
}
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
}
private Optional<LoanTransaction> createBuyDownFeeAmortizationTransaction(final Loan loan, final LocalDate transactionDate,
@@ -20,7 +20,6 @@ package org.apache.fineract.portfolio.loanaccount.service;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
@@ -62,13 +61,6 @@ public class LoanCapitalizedIncomeAmortizationProcessingServiceImpl implements L
@Override
@Transactional
public void processCapitalizedIncomeAmortizationOnLoanClosure(@NotNull final Loan loan, final boolean addJournal) {
List<Long> existingTransactionIds = Collections.emptyList();
List<Long> existingReversedTransactionIds = Collections.emptyList();
if (addJournal) {
existingTransactionIds = loanTransactionRepository.findTransactionIdsByLoan(loan);
existingReversedTransactionIds = loanTransactionRepository.findReversedTransactionIdsByLoan(loan);
}
final LocalDate transactionDate = getFinalCapitalizedIncomeAmortizationTransactionDate(loan);
final Optional<LoanTransaction> amortizationTransaction = createCapitalizedIncomeAmortizationTransaction(loan, transactionDate,
false, null);
@@ -80,20 +72,16 @@ public class LoanCapitalizedIncomeAmortizationProcessingServiceImpl implements L
businessEventNotifierService.notifyPostBusinessEvent(
new LoanCapitalizedIncomeAmortizationAdjustmentTransactionCreatedBusinessEvent(loanTransaction));
}
if (addJournal) {
journalEntryPoster.postJournalEntriesForLoanTransaction(amortizationTransaction.get(), false, false);
}
});
if (addJournal) {
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
}
}
@Override
@Transactional
public void processCapitalizedIncomeAmortizationOnLoanChargeOff(@NotNull final Loan loan,
@NonNull final LoanTransaction chargeOffTransaction) {
final List<Long> existingTransactionIds = loanTransactionRepository.findTransactionIdsByLoan(loan);
final List<Long> existingReversedTransactionIds = loanTransactionRepository.findReversedTransactionIdsByLoan(loan);
LocalDate transactionDate = loan.getChargedOffOnDate();
if (transactionDate == null) {
transactionDate = DateUtils.getBusinessLocalDate();
@@ -102,7 +90,7 @@ public class LoanCapitalizedIncomeAmortizationProcessingServiceImpl implements L
final Optional<LoanTransaction> amortizationTransaction = createCapitalizedIncomeAmortizationTransaction(loan, transactionDate,
true, chargeOffTransaction);
if (amortizationTransaction.isPresent()) {
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
journalEntryPoster.postJournalEntriesForLoanTransaction(amortizationTransaction.get(), false, false);
if (amortizationTransaction.get().isCapitalizedIncomeAmortization()) {
businessEventNotifierService.notifyPostBusinessEvent(
new LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent(amortizationTransaction.get()));
@@ -162,8 +150,6 @@ public class LoanCapitalizedIncomeAmortizationProcessingServiceImpl implements L
@Transactional
public void processCapitalizedIncomeAmortizationOnLoanUndoChargeOff(@NotNull final LoanTransaction loanTransaction) {
final Loan loan = loanTransaction.getLoan();
final List<Long> existingTransactionIds = loanTransactionRepository.findTransactionIdsByLoan(loan);
final List<Long> existingReversedTransactionIds = loanTransactionRepository.findReversedTransactionIdsByLoan(loan);
loan.getLoanTransactions().stream().filter(LoanTransaction::isCapitalizedIncomeAmortization)
.filter(transaction -> transaction.getTransactionDate().equals(loanTransaction.getTransactionDate())
@@ -174,24 +160,19 @@ public class LoanCapitalizedIncomeAmortizationProcessingServiceImpl implements L
transaction.reverse();
final LoanAdjustTransactionBusinessEvent.Data data = new LoanAdjustTransactionBusinessEvent.Data(transaction);
businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(data));
journalEntryPoster.postJournalEntriesForLoanTransaction(transaction, false, false);
});
for (LoanCapitalizedIncomeBalance balance : loanCapitalizedIncomeBalanceRepository.findAllByLoanId(loan.getId())) {
balance.setUnrecognizedAmount(balance.getChargedOffAmount());
balance.setChargedOffAmount(BigDecimal.ZERO);
}
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
}
@Override
@Transactional
public void processCapitalizedIncomeAmortizationTillDate(@NonNull final Loan loan, @NonNull final LocalDate tillDate,
final boolean addJournal) {
final List<Long> existingTransactionIds = loanTransactionRepository.findTransactionIdsByLoan(loan);
final List<Long> existingReversedTransactionIds = loanTransactionRepository.findReversedTransactionIdsByLoan(loan);
List<LoanCapitalizedIncomeBalance> balances = loanCapitalizedIncomeBalanceRepository.findAllByLoanId(loan.getId());
LocalDate maturityDate = loan.getMaturityDate() != null ? loan.getMaturityDate()
@@ -229,7 +210,7 @@ public class LoanCapitalizedIncomeAmortizationProcessingServiceImpl implements L
loanTransactionRepository.flush();
if (addJournal) {
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
journalEntryPoster.postJournalEntriesForLoanTransaction(transaction, false, false);
}
BusinessEvent<?> event = MathUtil.isGreaterThanZero(totalAmortizationAmount)
@@ -36,7 +36,6 @@ import java.util.Set;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
@@ -90,7 +89,6 @@ import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import org.apache.fineract.portfolio.group.domain.Group;
import org.apache.fineract.portfolio.group.exception.GroupNotActiveException;
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
import org.apache.fineract.portfolio.loanaccount.data.AccountingBridgeDataDTO;
import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidByData;
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
@@ -129,7 +127,6 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.data.OverdueLoanSc
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.DefaultScheduledDateGenerator;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.ScheduledDateGenerator;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanAccountingBridgeMapper;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeApiJsonValidator;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanDownPaymentTransactionValidator;
@@ -156,7 +153,6 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
private final LoanTransactionRepository loanTransactionRepository;
private final AccountTransfersWritePlatformService accountTransfersWritePlatformService;
private final LoanRepositoryWrapper loanRepositoryWrapper;
private final JournalEntryWritePlatformService journalEntryWritePlatformService;
private final LoanAccountDomainService loanAccountDomainService;
private final LoanChargeRepository loanChargeRepository;
private final LoanWritePlatformService loanWritePlatformService;
@@ -172,7 +168,6 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
private final LoanChargeAssembler loanChargeAssembler;
private final PaymentDetailWritePlatformService paymentDetailWritePlatformService;
private final NoteRepository noteRepository;
private final LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService;
private final LoanAccrualsProcessingService loanAccrualsProcessingService;
private final LoanDownPaymentTransactionValidator loanDownPaymentTransactionValidator;
private final LoanChargeValidator loanChargeValidator;
@@ -180,8 +175,8 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
private final ReprocessLoanTransactionsService reprocessLoanTransactionsService;
private final LoanAccountService loanAccountService;
private final LoanAdjustmentService loanAdjustmentService;
private final LoanAccountingBridgeMapper loanAccountingBridgeMapper;
private final LoanChargeService loanChargeService;
private final LoanJournalEntryPoster loanJournalEntryPoster;
private static boolean isPartOfThisInstallment(LoanCharge loanCharge, LoanRepaymentScheduleInstallment e) {
return DateUtils.isAfter(loanCharge.getDueDate(), e.getFromDate()) && !DateUtils.isAfter(loanCharge.getDueDate(), e.getDueDate());
@@ -207,8 +202,6 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
// apply this charge
validateAddingNewChargeAllowed(loanDisburseDetails);
}
final List<Long> existingTransactionIds = new ArrayList<>(loanTransactionRepository.findTransactionIdsByLoan(loan));
final List<Long> existingReversedTransactionIds = new ArrayList<>(loanTransactionRepository.findReversedTransactionIdsByLoan(loan));
boolean isAppliedOnBackDate = false;
LoanCharge loanCharge = null;
@@ -301,14 +294,11 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
if (loan.isInterestBearingAndInterestRecalculationEnabled() && isAppliedOnBackDate
&& loan.isFeeCompoundingEnabledForInterestRecalculation()) {
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, true, false);
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, true, true);
}
this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
businessEventNotifierService.notifyPostBusinessEvent(new LoanAddChargeBusinessEvent(loanCharge));
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
return new CommandProcessingResultBuilder().withCommandId(command.commandId()) //
@@ -546,8 +536,6 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
final Map<String, Object> changes = new LinkedHashMap<>();
changes.put(LoanApiConstants.externalIdParameterName, externalId);
final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>();
LocalDate recalculateFrom = null;
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
@@ -561,21 +549,21 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
}
loanChargeValidator.validateLoanIsNotClosed(loan, loanCharge);
final LoanTransaction waiveTransaction = waiveLoanCharge(loan, loanCharge, changes, existingTransactionIds,
existingReversedTransactionIds, loanInstallmentNumber, scheduleGeneratorDTO, accruedCharge, externalId);
final LoanTransaction waiveTransaction = waiveLoanCharge(loan, loanCharge, changes, loanInstallmentNumber, scheduleGeneratorDTO,
accruedCharge, externalId);
if (loan.isInterestBearingAndInterestRecalculationEnabled()
&& DateUtils.isBefore(loanCharge.getDueLocalDate(), DateUtils.getBusinessLocalDate())) {
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
loanAccrualsProcessingService.reprocessExistingAccruals(loan, true);
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan, true);
}
this.loanTransactionRepository.saveAndFlush(waiveTransaction);
this.loanRepositoryWrapper.save(loan);
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
loanJournalEntryPoster.postJournalEntriesForLoanTransaction(waiveTransaction, false, false);
this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
businessEventNotifierService.notifyPostBusinessEvent(new LoanWaiveChargeBusinessEvent(loanCharge));
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
return new CommandProcessingResultBuilder() //
@@ -830,8 +818,6 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
if (optPenaltyCharge.isEmpty()) {
return;
}
final List<Long> existingTransactionIds = loanTransactionRepository.findTransactionIdsByLoan(loan);
final List<Long> existingReversedTransactionIds = loanTransactionRepository.findReversedTransactionIdsByLoan(loan);
boolean runInterestRecalculation = false;
LocalDate recalculateFrom = DateUtils.getBusinessLocalDate();
LocalDate lastChargeDate = null;
@@ -881,20 +867,15 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
if (loan.isInterestBearingAndInterestRecalculationEnabled() && runInterestRecalculation
&& loan.isFeeCompoundingEnabledForInterestRecalculation()) {
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, true, false);
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, true, true);
}
this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
}
}
private LoanTransaction applyChargeAdjustment(final Loan loan, final LoanCharge loanCharge, final BigDecimal transactionAmount,
final LocalDate transactionDate, final ExternalId txnExternalId, PaymentDetail paymentDetail) {
businessEventNotifierService.notifyPreBusinessEvent(new LoanChargeAdjustmentPreBusinessEvent(loan));
final List<Long> existingTransactionIds = new ArrayList<>(loanTransactionRepository.findTransactionIdsByLoan(loan));
final List<Long> existingReversedTransactionIds = new ArrayList<>(loanTransactionRepository.findReversedTransactionIdsByLoan(loan));
LoanTransaction loanChargeAdjustmentTransaction = LoanTransaction.chargeAdjustment(loan, transactionAmount, transactionDate,
txnExternalId, paymentDetail);
@@ -921,11 +902,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
loanLifecycleStateMachine.determineAndTransition(loan, loan.getLastUserTransactionDate());
loanAccountService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
final AccountingBridgeDataDTO accountingBridgeData = loanAccountingBridgeMapper.deriveAccountingBridgeData(
loan.getCurrency().getCode(), existingTransactionIds, existingReversedTransactionIds, false, loan);
this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
loanJournalEntryPoster.postJournalEntriesForLoanTransaction(loanChargeAdjustmentTransaction, false, false);
loanAccountDomainService.setLoanDelinquencyTag(loan, transactionDate);
return loanChargeAdjustmentTransaction;
@@ -943,8 +920,6 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
private void undoInstalmentFee(Map<String, Object> changes, Loan loan, LoanTransaction loanTransaction,
LoanChargePaidBy loanChargePaidBy) {
final List<Long> existingTransactionIds = loanTransactionRepository.findTransactionIdsByLoan(loan);
final List<Long> existingReversedTransactionIds = loanTransactionRepository.findReversedTransactionIdsByLoan(loan);
LoanCharge loanCharge = loanChargePaidBy.getLoanCharge();
final Integer installmentNumber = loanChargePaidBy.getInstallmentNumber();
LoanInstallmentCharge chargePerInstallment;
@@ -989,7 +964,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
loanCharge.undoWaived();
}
loan.updateLoanSummaryForUndoWaiveCharge(amountWaived, loanCharge.isPenaltyCharge());
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
loanJournalEntryPoster.postJournalEntriesForLoanTransaction(loanTransaction, false, false);
changes.put(AMOUNT, amountWaived);
} else {
throw new InstallmentNotFoundException(loanTransaction.getId());
@@ -998,9 +973,6 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
private void undoSpecifiedDueDateCharge(final Map<String, Object> changes, final Loan loan, final LoanTransaction loanTransaction,
final LoanChargePaidBy loanChargePaidBy) {
final List<Long> existingTransactionIds = loanTransactionRepository.findTransactionIdsByLoan(loan);
final List<Long> existingReversedTransactionIds = loanTransactionRepository.findReversedTransactionIdsByLoan(loan);
LoanCharge loanCharge = loanChargePaidBy.getLoanCharge();
BigDecimal amountWaived = loanCharge.getAmountWaived(loan.getCurrency()).getAmount();
if (!loanCharge.isWaived() || amountWaived == null) {
@@ -1017,7 +989,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
.filter(e -> isPartOfThisInstallment(loanCharge, e)).findFirst().orElseThrow();
updateRepaymentInstalmentWithWaivedAmount(loanCharge, installment, amountWaived);
loan.updateLoanSummaryForUndoWaiveCharge(amountWaived, loanCharge.isPenaltyCharge());
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
loanJournalEntryPoster.postJournalEntriesForLoanTransaction(loanTransaction, false, false);
changes.put(AMOUNT, amountWaived);
}
@@ -1104,6 +1076,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
final LoanTransaction applyLoanChargeTransaction = loanChargeService.handleChargeAppliedTransaction(loan, loanCharge, null);
if (applyLoanChargeTransaction != null) {
this.loanTransactionRepository.saveAndFlush(applyLoanChargeTransaction);
loanJournalEntryPoster.postJournalEntriesForLoanTransaction(applyLoanChargeTransaction, false, false);
businessEventNotifierService
.notifyPostBusinessEvent(new LoanAccrualTransactionCreatedBusinessEvent(applyLoanChargeTransaction));
}
@@ -1111,6 +1084,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
&& DateUtils.getBusinessLocalDate().isAfter(loan.getMaturityDate())) {
final LoanTransaction loanTransaction = loanChargeService.createChargeAppliedTransaction(loan, loanCharge, null);
this.loanTransactionRepository.saveAndFlush(loanTransaction);
loanJournalEntryPoster.postJournalEntriesForLoanTransaction(loanTransaction, false, false);
businessEventNotifierService.notifyPostBusinessEvent(new LoanAccrualTransactionCreatedBusinessEvent(loanTransaction));
}
@@ -1212,13 +1186,11 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
public Loan runScheduleRecalculation(Loan loan, final LocalDate recalculateFrom) {
if (loan.isInterestBearingAndInterestRecalculationEnabled() && !loan.isChargedOff()) {
final List<Long> existingTransactionIds = loanTransactionRepository.findTransactionIdsByLoan(loan);
ScheduleGeneratorDTO generatorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
loanScheduleService.handleRegenerateRepaymentScheduleWithInterestRecalculation(loan, generatorDTO);
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
loanAccrualsProcessingService.reprocessExistingAccruals(loan, true);
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan, true);
loan = loanAccountService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
}
return loan;
}
@@ -1289,16 +1261,6 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
return refundableAmount;
}
private void postJournalEntries(final Loan loan, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds) {
final MonetaryCurrency currency = loan.getCurrency();
boolean isAccountTransfer = false;
final AccountingBridgeDataDTO accountingBridgeData = loanAccountingBridgeMapper.deriveAccountingBridgeData(currency.getCode(),
existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, loan);
this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
}
private LoanCharge retrieveLoanChargeBy(final Long loanId, final Long loanChargeId) {
final LoanCharge loanCharge = this.loanChargeRepository.findById(loanChargeId)
.orElseThrow(() -> new LoanChargeNotFoundException(loanChargeId));
@@ -1423,8 +1385,8 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
}
public LoanTransaction waiveLoanCharge(final Loan loan, final LoanCharge loanCharge, final Map<String, Object> changes,
final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds, final Integer loanInstallmentNumber,
final ScheduleGeneratorDTO scheduleGeneratorDTO, final Money accruedCharge, final ExternalId externalId) {
final Integer loanInstallmentNumber, final ScheduleGeneratorDTO scheduleGeneratorDTO, final Money accruedCharge,
final ExternalId externalId) {
final Money amountWaived = loanCharge.waive(loan.getCurrency(), loanInstallmentNumber);
changes.put("amount", amountWaived.getAmount());
@@ -1474,9 +1436,6 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
loan.updateSummaryWithTotalFeeChargesDueAtDisbursement(loan.deriveSumTotalOfChargesDueAtDisbursement());
existingTransactionIds.addAll(loanTransactionRepository.findTransactionIdsByLoan(loan));
existingReversedTransactionIds.addAll(loanTransactionRepository.findReversedTransactionIdsByLoan(loan));
final LoanTransaction waiveLoanChargeTransaction = LoanTransaction.waiveLoanCharge(loan, loan.getOffice(), amountWaived,
transactionDate, feeChargesWaived, penaltyChargesWaived, unrecognizedIncome, externalId);
final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(waiveLoanChargeTransaction, loanCharge,
@@ -52,6 +52,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTrancheCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTrancheDisbursementCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanDisbursementValidator;
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
@@ -64,6 +65,8 @@ public class LoanDisbursementService {
private final ReprocessLoanTransactionsService reprocessLoanTransactionsService;
private final LoanChargeService loanChargeService;
private final LoanBalanceService loanBalanceService;
private final LoanJournalEntryPoster loanJournalEntryPoster;
private final LoanTransactionRepository loanTransactionRepository;
public void updateDisbursementDetails(final Loan loan, final JsonCommand jsonCommand, final Map<String, Object> actualChanges) {
final List<Long> disbursementList = loan.fetchDisbursementIds();
@@ -210,7 +213,12 @@ public class LoanDisbursementService {
}
} else if (disbursedOn.equals(loan.getActualDisbursementDate())
&& loan.isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct()) {
loanChargeService.handleChargeAppliedTransaction(loan, charge, disbursedOn);
final LoanTransaction applyLoanChargeTransaction = loanChargeService.handleChargeAppliedTransaction(loan, charge,
disbursedOn);
if (applyLoanChargeTransaction != null) {
loanTransactionRepository.saveAndFlush(applyLoanChargeTransaction);
loanJournalEntryPoster.postJournalEntriesForLoanTransaction(applyLoanChargeTransaction, false, false);
}
}
}
@@ -219,6 +227,8 @@ public class LoanDisbursementService {
chargesPayment.updateComponentsAndTotal(zero, zero, disbursentMoney, zero);
chargesPayment.updateLoan(loan);
loan.addLoanTransaction(chargesPayment);
loanTransactionRepository.saveAndFlush(chargesPayment);
loanJournalEntryPoster.postJournalEntriesForLoanTransaction(chargesPayment, false, false);
loanBalanceService.updateLoanOutstandingBalances(loan);
}
@@ -18,13 +18,12 @@
*/
package org.apache.fineract.portfolio.loanaccount.service;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.portfolio.loanaccount.data.AccountingBridgeDataDTO;
import org.apache.fineract.investor.domain.ExternalAssetOwner;
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransfer;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanAccountingBridgeMapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.springframework.stereotype.Component;
@Component
@@ -32,25 +31,20 @@ import org.springframework.stereotype.Component;
public class LoanJournalEntryPosterImpl implements LoanJournalEntryPoster {
private final JournalEntryWritePlatformService journalEntryWritePlatformService;
private final LoanAccountingBridgeMapper loanAccountingBridgeMapper;
@Override
public void postJournalEntries(final Loan loan, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds) {
final MonetaryCurrency currency = loan.getCurrency();
boolean isAccountTransfer = false;
if (loan.isChargedOff()) {
List<AccountingBridgeDataDTO> accountingBridgeDataList = loanAccountingBridgeMapper.deriveAccountingBridgeDataForChargeOff(
currency.getCode(), existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, loan);
for (AccountingBridgeDataDTO accountingBridgeData : accountingBridgeDataList) {
this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
}
} else {
AccountingBridgeDataDTO accountingBridgeData = loanAccountingBridgeMapper.deriveAccountingBridgeData(currency.getCode(),
existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, loan);
this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
}
public void postJournalEntriesForLoanTransaction(final LoanTransaction loanTransaction, final boolean isAccountTransfer,
final boolean isLoanToLoanTransfer) {
this.journalEntryWritePlatformService.createJournalEntriesForLoanTransaction(loanTransaction, isAccountTransfer,
isLoanToLoanTransfer);
}
@Override
public void postJournalEntriesForExternalOwnerTransfer(final Loan loan, final Object externalAssetOwnerTransfer,
final Object previousOwner) {
// Cast to proper types
final ExternalAssetOwnerTransfer transfer = (ExternalAssetOwnerTransfer) externalAssetOwnerTransfer;
final ExternalAssetOwner prevOwner = (ExternalAssetOwner) previousOwner;
this.journalEntryWritePlatformService.createJournalEntriesForExternalOwnerTransfer(loan, transfer, prevOwner);
}
}
@@ -51,7 +51,6 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
import org.apache.fineract.cob.exceptions.AccountLockCannotBeOverruledException;
import org.apache.fineract.cob.service.LoanAccountLockService;
import org.apache.fineract.infrastructure.codes.domain.CodeValue;
@@ -109,7 +108,6 @@ import org.apache.fineract.infrastructure.security.service.PlatformSecurityConte
import org.apache.fineract.organisation.holiday.domain.Holiday;
import org.apache.fineract.organisation.holiday.domain.HolidayRepositoryWrapper;
import org.apache.fineract.organisation.holiday.service.HolidayUtil;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.organisation.office.domain.Office;
import org.apache.fineract.organisation.staff.domain.Staff;
@@ -153,7 +151,6 @@ import org.apache.fineract.portfolio.group.domain.Group;
import org.apache.fineract.portfolio.group.exception.GroupNotActiveException;
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
import org.apache.fineract.portfolio.loanaccount.command.LoanUpdateCommand;
import org.apache.fineract.portfolio.loanaccount.data.AccountingBridgeDataDTO;
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository;
@@ -200,7 +197,6 @@ import org.apache.fineract.portfolio.loanaccount.guarantor.service.GuarantorDoma
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelPeriod;
import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryWritePlatformService;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanAccountingBridgeMapper;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanMapper;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationValidator;
@@ -242,7 +238,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private final LoanTransactionRepository loanTransactionRepository;
private final LoanTransactionRelationRepository loanTransactionRelationRepository;
private final LoanAssembler loanAssembler;
private final JournalEntryWritePlatformService journalEntryWritePlatformService;
private final CalendarInstanceRepository calendarInstanceRepository;
private final PaymentDetailWritePlatformService paymentDetailWritePlatformService;
private final HolidayRepositoryWrapper holidayRepository;
@@ -287,7 +282,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private final LoanAccountService loanAccountService;
private final LoanJournalEntryPoster journalEntryPoster;
private final LoanAdjustmentService loanAdjustmentService;
private final LoanAccountingBridgeMapper loanAccountingBridgeMapper;
private final LoanMapper loanMapper;
private final LoanTransactionProcessingService loanTransactionProcessingService;
private final LoanBalanceService loanBalanceService;
@@ -356,9 +350,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
businessEventNotifierService.notifyPreBusinessEvent(new LoanDisbursalBusinessEvent(loan));
List<Long> existingTransactionIds = new ArrayList<>();
List<Long> existingReversedTransactionIds = new ArrayList<>();
final AppUser currentUser = getAppUserIfPresent();
final Map<String, Object> changes = new LinkedHashMap<>();
@@ -398,16 +389,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
LoanTransaction disbursementTransaction = null;
if (isAccountTransfer) {
disburseLoanToSavings(loan, command, amountToDisburse, paymentDetail);
existingTransactionIds.addAll(loanTransactionRepository.findTransactionIdsByLoan(loan));
existingReversedTransactionIds.addAll(loanTransactionRepository.findReversedTransactionIdsByLoan(loan));
} else {
existingTransactionIds.addAll(loanTransactionRepository.findTransactionIdsByLoan(loan));
existingReversedTransactionIds.addAll(loanTransactionRepository.findReversedTransactionIdsByLoan(loan));
disbursementTransaction = LoanTransaction.disbursement(loan, amountToDisburse, paymentDetail, actualDisbursementDate,
txnExternalId, loan.getTotalOverpaidAsMoney());
disbursementTransaction.updateLoan(loan);
loan.addLoanTransaction(disbursementTransaction);
loanTransactionRepository.saveAndFlush(disbursementTransaction);
journalEntryPoster.postJournalEntriesForLoanTransaction(disbursementTransaction, false, false);
}
if (loan.getRepaymentScheduleInstallments().isEmpty()) {
/*
@@ -426,12 +414,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
scheduleGeneratorDTO);
loan.adjustNetDisbursalAmount(amountToDisburse.getAmount());
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
loanAccrualsProcessingService.reprocessExistingAccruals(loan, true);
LocalDate firstInstallmentDueDate = loan.fetchRepaymentScheduleInstallment(1).getDueDate();
if (loan.isInterestBearingAndInterestRecalculationEnabled()
&& (DateUtils.isBeforeBusinessDate(firstInstallmentDueDate) || loan.isDisbursementMissed())) {
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan, true);
}
if (loan.isAutoRepaymentForDownPaymentEnabled() && !isWithoutAutoPayment) {
@@ -462,9 +450,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
} else {
loanDownPaymentHandlerService.handleDownPayment(scheduleGeneratorDTO, command, disbursementTransaction, loan);
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
loanAccrualsProcessingService.reprocessExistingAccruals(loan, true);
if (loan.isInterestBearingAndInterestRecalculationEnabled()) {
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan, true);
}
}
}
@@ -501,7 +489,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
updateRecurringCalendarDatesForInterestRecalculation(loan);
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, loan.isInterestBearingAndInterestRecalculationEnabled(),
false);
true);
this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
// Post Dated Checks
@@ -525,8 +513,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
businessEventNotifierService.notifyPostBusinessEvent(new LoanDisbursalTransactionBusinessEvent(disbursalTransaction));
}
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
return new CommandProcessingResultBuilder() //
@@ -594,6 +580,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final LoanTransaction interestAppliedTransaction = LoanTransaction.accrueInterest(loan.getOffice(), loan, interestApplied,
actualDisbursementDate1, externalId);
loan.addLoanTransaction(interestAppliedTransaction);
loanTransactionRepository.saveAndFlush(interestAppliedTransaction);
journalEntryPoster.postJournalEntriesForLoanTransaction(interestAppliedTransaction, false, false);
}
if (loan.getLoanProduct().isMultiDisburseLoan() || loan.isProgressiveSchedule()) {
@@ -753,9 +741,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
checkClientOrGroupActive(loan);
businessEventNotifierService.notifyPreBusinessEvent(new LoanDisbursalBusinessEvent(loan));
final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>();
final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
// Bulk disbursement should happen on meeting date (mostly from
@@ -773,16 +758,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final ExternalId txnExternalId = externalIdFactory.createFromCommand(command, LoanApiConstants.externalIdParameterName);
if (isAccountTransfer) {
disburseLoanToSavings(loan, command, disburseAmount, paymentDetail);
existingTransactionIds.addAll(loanTransactionRepository.findTransactionIdsByLoan(loan));
existingReversedTransactionIds.addAll(loanTransactionRepository.findReversedTransactionIdsByLoan(loan));
} else {
existingTransactionIds.addAll(loanTransactionRepository.findTransactionIdsByLoan(loan));
existingReversedTransactionIds.addAll(loanTransactionRepository.findReversedTransactionIdsByLoan(loan));
LoanTransaction disbursementTransaction = LoanTransaction.disbursement(loan, disburseAmount, paymentDetail,
actualDisbursementDate, txnExternalId, loan.getTotalOverpaidAsMoney());
disbursementTransaction.updateLoan(loan);
loan.addLoanTransaction(disbursementTransaction);
loanTransactionRepository.saveAndFlush(disbursementTransaction);
journalEntryPoster.postJournalEntriesForLoanTransaction(disbursementTransaction, false, false);
businessEventNotifierService
.notifyPostBusinessEvent(new LoanDisbursalTransactionBusinessEvent(disbursementTransaction));
}
@@ -797,19 +779,17 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
disburseLoan(command, configurationDomainService.isPaymentTypeApplicableForDisbursementCharge(), paymentDetail, loan,
currentUser, changes, scheduleGeneratorDTO);
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
loanAccrualsProcessingService.reprocessExistingAccruals(loan, true);
LocalDate firstInstallmentDueDate = loan.fetchRepaymentScheduleInstallment(1).getDueDate();
if (loan.isInterestBearingAndInterestRecalculationEnabled()
&& (DateUtils.isBeforeBusinessDate(firstInstallmentDueDate) || loan.isDisbursementMissed())) {
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan, true);
}
}
if (!changes.isEmpty()) {
createNote(loan, command, changes);
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
}
final Set<LoanCharge> loanCharges = loan.getActiveCharges();
final Map<Long, BigDecimal> disBuLoanCharges = new HashMap<>();
@@ -879,10 +859,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
businessEventNotifierService.notifyPreBusinessEvent(new LoanUndoDisbursalBusinessEvent(loan));
removeLoanCycle(loan);
final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>();
//
final MonetaryCurrency currency = loan.getCurrency();
final LocalDate recalculateFrom = null;
loan.setActualDisbursementDate(null);
@@ -894,10 +870,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loanDownPaymentTransactionValidator.validateAccountStatus(loan, LoanEvent.LOAN_DISBURSAL_UNDO);
loanTransactionValidator.validateActivityNotBeforeClientOrGroupTransferDate(loan, LoanEvent.LOAN_DISBURSAL_UNDO,
loan.getDisbursementDate());
final Map<String, Object> changes = undoDisbursal(loan, scheduleGeneratorDTO, existingTransactionIds,
existingReversedTransactionIds);
final Map<String, Object> changes = undoDisbursal(loan, scheduleGeneratorDTO);
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
loanAccrualsProcessingService.reprocessExistingAccruals(loan, true);
if (!changes.isEmpty()) {
if (loan.isTopup() && loan.getClientId() != null) {
@@ -912,11 +887,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
this.accountTransfersWritePlatformService.reverseAllTransactions(loanId, PortfolioAccountType.LOAN);
createNote(loan, command, changes);
boolean isAccountTransfer = false;
final AccountingBridgeDataDTO accountingBridgeData = loanAccountingBridgeMapper.deriveAccountingBridgeData(currency.getCode(),
existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, loan);
journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
businessEventNotifierService.notifyPostBusinessEvent(new LoanUndoDisbursalBusinessEvent(loan));
}
@@ -1031,8 +1001,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
reprocessLoanTransactionsService.reprocessTransactions(loan);
if (loan.isInterestBearingAndInterestRecalculationEnabled()) {
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
loanAccrualsProcessingService.reprocessExistingAccruals(loan, true);
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan, true);
}
}
@@ -1046,10 +1016,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
businessEventNotifierService.notifyPreBusinessEvent(new LoanTransactionInterestPaymentWaiverPreBusinessEvent(loan));
// save already existing transaction ids
final List<Long> existingTransactionIds = new ArrayList<>(loanTransactionRepository.findTransactionIdsByLoan(loan));
final List<Long> existingReversedTransactionIds = new ArrayList<>(loanTransactionRepository.findReversedTransactionIdsByLoan(loan));
final String noteText = command.stringValueOfParameterNamed("note");
final Map<String, Object> changes = new LinkedHashMap<>();
@@ -1061,13 +1027,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loanTransactionValidator.validateLoanTransactionInterestPaymentWaiverAfterRecalculation(loan);
loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(newInterestPaymentWaiverTransaction);
journalEntryPoster.postJournalEntriesForLoanTransaction(newInterestPaymentWaiverTransaction, false, false);
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
saveNote(noteText, loan, newInterestPaymentWaiverTransaction);
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, loan.isInterestBearingAndInterestRecalculationEnabled(),
false);
true);
loanAccountDomainService.setLoanDelinquencyTag(loan, newInterestPaymentWaiverTransaction.getTransactionDate());
// disable all active standing orders linked to this loan if status changes to closed
@@ -1079,9 +1046,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
// Finished Notification
final LoanTransactionBusinessEvent transactionRepaymentEvent = new LoanTransactionInterestPaymentWaiverPostBusinessEvent(
newInterestPaymentWaiverTransaction);
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
businessEventNotifierService.notifyPostBusinessEvent(transactionRepaymentEvent);
@@ -1170,7 +1134,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled();
for (final SingleRepaymentCommand singleLoanRepaymentCommand : repaymentCommand) {
if (singleLoanRepaymentCommand != null) {
Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(singleLoanRepaymentCommand.getLoanId());
Loan loan = this.loanAssembler.assembleFrom(singleLoanRepaymentCommand.getLoanId());
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(),
singleLoanRepaymentCommand.getTransactionDate());
final WorkingDays workingDays = this.workingDaysRepository.findOne();
@@ -1318,9 +1282,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
checkClientOrGroupActive(loan);
final List<Long> existingTransactionIds = loanTransactionRepository.findTransactionIdsByLoan(loan);
final List<Long> existingReversedTransactionIds = loanTransactionRepository.findReversedTransactionIdsByLoan(loan);
businessEventNotifierService.notifyPreBusinessEvent(new LoanChargebackTransactionBusinessEvent(loanTransaction));
final LocalDate transactionDate = DateUtils.getBusinessLocalDate();
@@ -1351,6 +1312,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
handleChargebackTransaction(loan, newTransaction);
newTransaction = this.loanTransactionRepository.saveAndFlush(newTransaction);
// Create journal entries immediately for this transaction
journalEntryPoster.postJournalEntriesForLoanTransaction(newTransaction, false, false);
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
@@ -1361,9 +1324,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.noteRepository.save(note);
}
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
this.loanAccountDomainService.setLoanDelinquencyTag(loan, transactionDate);
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
businessEventNotifierService.notifyPostBusinessEvent(new LoanChargebackTransactionBusinessEvent(newTransaction));
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
@@ -1412,9 +1373,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
Loan loan = this.loanAssembler.assembleFrom(loanId);
checkClientOrGroupActive(loan);
final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>();
final Money transactionAmountAsMoney = Money.of(loan.getCurrency(), transactionAmount);
Money unrecognizedIncome = transactionAmountAsMoney.zero();
Money interestComponent = transactionAmountAsMoney;
@@ -1439,14 +1397,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
LoanEvent.LOAN_REPAYMENT_OR_WAIVER);
loanTransactionValidator.validateActivityNotBeforeClientOrGroupTransferDate(loan, LoanEvent.LOAN_REPAYMENT_OR_WAIVER,
waiveInterestTransaction.getTransactionDate());
waiveInterest(loan, waiveInterestTransaction, existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO);
waiveInterest(loan, waiveInterestTransaction, scheduleGeneratorDTO);
if (loan.isInterestBearingAndInterestRecalculationEnabled()) {
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
loanAccrualsProcessingService.reprocessExistingAccruals(loan, true);
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan, true);
}
this.loanTransactionRepository.saveAndFlush(waiveInterestTransaction);
journalEntryPoster.postJournalEntriesForLoanTransaction(waiveInterestTransaction, false, false);
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
final String noteText = command.stringValueOfParameterNamed("note");
@@ -1457,11 +1416,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, loan.isInterestBearingAndInterestRecalculationEnabled(),
false);
true);
loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
businessEventNotifierService.notifyPostBusinessEvent(new LoanWaiveInterestBusinessEvent(waiveInterestTransaction));
return new CommandProcessingResultBuilder() //
@@ -1508,10 +1464,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
StatusEnum.WRITE_OFF.getValue(), EntityTables.LOAN.getForeignKeyColumnNameOnDatatable(), loan.productId());
removeLoanCycle(loan);
final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>();
updateLoanCounters(loan, loan.getDisbursementDate());
LocalDate recalculateFrom = null;
@@ -1533,16 +1485,17 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
.withLoanId(loanId) //
.with(changes);
final Optional<LoanTransaction> loanTransactionOptional = closeAsWrittenOff(loan, command, changes, existingTransactionIds,
existingReversedTransactionIds, currentUser, scheduleGeneratorDTO);
final Optional<LoanTransaction> loanTransactionOptional = closeAsWrittenOff(loan, command, changes, currentUser,
scheduleGeneratorDTO);
if (loanTransactionOptional.isPresent()) {
final LoanTransaction loanTransaction = loanTransactionOptional.get();
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
loanAccrualsProcessingService.reprocessExistingAccruals(loan, true);
if (loan.isInterestBearingAndInterestRecalculationEnabled()) {
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan, true);
}
this.loanTransactionRepository.saveAndFlush(loanTransaction);
journalEntryPoster.postJournalEntriesForLoanTransaction(loanTransaction, false, false);
saveLoanWithDataIntegrityViolationChecks(loan);
final String noteText = command.stringValueOfParameterNamed("note");
if (StringUtils.isNotBlank(noteText)) {
@@ -1551,11 +1504,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.noteRepository.save(note);
}
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan,
loan.isInterestBearingAndInterestRecalculationEnabled(), false);
loan.isInterestBearingAndInterestRecalculationEnabled(), true);
loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
businessEventNotifierService.notifyPostBusinessEvent(new LoanWrittenOffPostBusinessEvent(loanTransaction));
@@ -1587,9 +1537,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
changes.put("locale", command.locale());
changes.put("dateFormat", command.dateFormat());
final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>();
updateLoanCounters(loan, loan.getDisbursementDate());
LocalDate recalculateFrom = null;
@@ -1601,15 +1548,17 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final LocalDate closureDate = command.localDateValueOfParameterNamed(TRANSACTION_DATE);
loanDownPaymentTransactionValidator.validateAccountStatus(loan, LoanEvent.LOAN_CLOSED);
loanTransactionValidator.validateActivityNotBeforeClientOrGroupTransferDate(loan, LoanEvent.REPAID_IN_FULL, closureDate);
final Optional<LoanTransaction> loanTransactionOptional = close(loan, command, changes, existingTransactionIds,
existingReversedTransactionIds, scheduleGeneratorDTO);
final Optional<LoanTransaction> loanTransactionOptional = close(loan, command, changes, scheduleGeneratorDTO);
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
loanAccrualsProcessingService.reprocessExistingAccruals(loan, true);
if (loan.isInterestBearingAndInterestRecalculationEnabled()) {
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan, true);
}
loanTransactionOptional.ifPresent(this.loanTransactionRepository::saveAndFlush);
loanTransactionOptional.ifPresent(loanTransaction -> {
this.loanTransactionRepository.saveAndFlush(loanTransaction);
journalEntryPoster.postJournalEntriesForLoanTransaction(loanTransaction, false, false);
});
saveLoanWithDataIntegrityViolationChecks(loan);
final String noteText = command.stringValueOfParameterNamed("note");
@@ -1619,9 +1568,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.noteRepository.save(note);
}
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, loan.isInterestBearingAndInterestRecalculationEnabled(),
false);
true);
loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
@@ -1633,9 +1581,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
// disable all active standing instructions linked to the loan
this.loanAccountDomainService.disableStandingInstructionsLinkedToClosedLoan(loan);
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
CommandProcessingResult result;
if (loanTransactionOptional.isPresent()) {
result = new CommandProcessingResultBuilder() //
@@ -1756,17 +1701,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
businessEventNotifierService.notifyPreBusinessEvent(new LoanInitiateTransferBusinessEvent(loan));
final List<Long> existingTransactionIds = new ArrayList<>(loanTransactionRepository.findTransactionIdsByLoan(loan));
final List<Long> existingReversedTransactionIds = new ArrayList<>(loanTransactionRepository.findReversedTransactionIdsByLoan(loan));
ExternalId externalId = externalIdFactory.create();
final LoanTransaction newTransferTransaction = LoanTransaction.initiateTransfer(loan.getOffice(), loan, transferDate, externalId);
loan.addLoanTransaction(newTransferTransaction);
loanLifecycleStateMachine.transition(LoanEvent.LOAN_INITIATE_TRANSFER, loan);
this.loanTransactionRepository.saveAndFlush(newTransferTransaction);
journalEntryPoster.postJournalEntriesForLoanTransaction(newTransferTransaction, false, false);
saveLoanWithDataIntegrityViolationChecks(loan);
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
businessEventNotifierService.notifyPostBusinessEvent(new LoanInitiateTransferBusinessEvent(loan));
return newTransferTransaction;
}
@@ -1776,8 +1719,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
public LoanTransaction acceptLoanTransfer(final Loan loan, final LocalDate transferDate, final Office acceptedInOffice,
final Staff loanOfficer) {
businessEventNotifierService.notifyPreBusinessEvent(new LoanAcceptTransferBusinessEvent(loan));
final List<Long> existingTransactionIds = new ArrayList<>(loanTransactionRepository.findTransactionIdsByLoan(loan));
final List<Long> existingReversedTransactionIds = new ArrayList<>(loanTransactionRepository.findReversedTransactionIdsByLoan(loan));
ExternalId externalId = externalIdFactory.create();
final LoanTransaction newTransferAcceptanceTransaction = LoanTransaction.approveTransfer(acceptedInOffice, loan, transferDate,
externalId);
@@ -1792,9 +1733,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
this.loanTransactionRepository.saveAndFlush(newTransferAcceptanceTransaction);
journalEntryPoster.postJournalEntriesForLoanTransaction(newTransferAcceptanceTransaction, false, false);
saveLoanWithDataIntegrityViolationChecks(loan);
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
businessEventNotifierService.notifyPostBusinessEvent(new LoanAcceptTransferBusinessEvent(loan));
return newTransferAcceptanceTransaction;
@@ -1805,9 +1745,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
public LoanTransaction withdrawLoanTransfer(final Loan loan, final LocalDate transferDate) {
businessEventNotifierService.notifyPreBusinessEvent(new LoanWithdrawTransferBusinessEvent(loan));
final List<Long> existingTransactionIds = new ArrayList<>(loanTransactionRepository.findTransactionIdsByLoan(loan));
final List<Long> existingReversedTransactionIds = new ArrayList<>(loanTransactionRepository.findReversedTransactionIdsByLoan(loan));
ExternalId externalId = externalIdFactory.create();
final LoanTransaction newTransferAcceptanceTransaction = LoanTransaction.withdrawTransfer(loan.getOffice(), loan, transferDate,
@@ -1816,9 +1753,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loanLifecycleStateMachine.transition(LoanEvent.LOAN_WITHDRAW_TRANSFER, loan);
this.loanTransactionRepository.saveAndFlush(newTransferAcceptanceTransaction);
journalEntryPoster.postJournalEntriesForLoanTransaction(newTransferAcceptanceTransaction, false, false);
saveLoanWithDataIntegrityViolationChecks(loan);
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
businessEventNotifierService.notifyPostBusinessEvent(new LoanWithdrawTransferBusinessEvent(loan));
return newTransferAcceptanceTransaction;
@@ -2019,9 +1955,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
holidays, workingDays, isSkipRepaymentOnFirstMonth, numberOfDays);
}
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
loanAccrualsProcessingService.reprocessExistingAccruals(loan, false);
if (loan.isInterestBearingAndInterestRecalculationEnabled()) {
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan, false);
}
saveLoanWithDataIntegrityViolationChecks(loan);
businessEventNotifierService.notifyPostBusinessEvent(new LoanRescheduledDueCalendarChangeBusinessEvent(loan));
@@ -2193,8 +2129,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
Loan loan = this.loanAssembler.assembleFrom(loanId);
checkClientOrGroupActive(loan);
final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>();
if (!loan.isClosedWrittenOff()) {
throw new PlatformServiceUnavailableException("error.msg.loan.status.not.written.off.update.not.allowed",
"Loan :" + loanId + " update not allowed as loan status is not written off", loanId);
@@ -2207,20 +2141,19 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
businessEventNotifierService.notifyPreBusinessEvent(new LoanUndoWrittenOffBusinessEvent(writeOffTransaction));
loanDownPaymentTransactionValidator.validateAccountStatus(loan, LoanEvent.WRITE_OFF_OUTSTANDING_UNDO);
undoWrittenOff(loan, existingTransactionIds, existingReversedTransactionIds);
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
undoWrittenOff(loan);
loanAccrualsProcessingService.reprocessExistingAccruals(loan, true);
if (loan.isInterestBearingAndInterestRecalculationEnabled()) {
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan, true);
}
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, loan.isInterestBearingAndInterestRecalculationEnabled(),
false);
true);
this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
journalEntryPoster.postJournalEntriesForLoanTransaction(writeOffTransaction, false, false);
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
businessEventNotifierService.notifyPostBusinessEvent(new LoanUndoWrittenOffBusinessEvent(writeOffTransaction));
@@ -2331,8 +2264,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private CommandProcessingResult processLoanDisbursementDetail(Loan loan, Long loanId, JsonCommand command,
LoanDisbursementDetails loanDisbursementDetails) {
final List<Long> existingTransactionIds = loanTransactionRepository.findTransactionIdsByLoan(loan);
final List<Long> existingReversedTransactionIds = loanTransactionRepository.findReversedTransactionIdsByLoan(loan);
final Map<String, Object> changes = new LinkedHashMap<>();
LocalDate recalculateFrom = null;
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
@@ -2352,9 +2283,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
}
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
loanAccrualsProcessingService.reprocessExistingAccruals(loan, true);
if (loan.isInterestBearingAndInterestRecalculationEnabled()) {
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan, true);
}
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
@@ -2362,12 +2293,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
createLoanScheduleArchive(loan, scheduleGeneratorDTO);
}
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, loan.isInterestBearingAndInterestRecalculationEnabled(),
false);
true);
this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
return new CommandProcessingResultBuilder() //
.withOfficeId(loan.getOfficeId()) //
.withClientId(loan.getClientId()) //
@@ -2416,18 +2344,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
ScheduleGeneratorDTO generatorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
loanScheduleService.recalculateScheduleFromLastTransaction(loan, generatorDTO, existingTransactionIds,
existingReversedTransactionIds);
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
loanAccrualsProcessingService.reprocessExistingAccruals(loan, true);
if (loan.isInterestBearingAndInterestRecalculationEnabled()) {
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan, true);
}
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan,
loan.isInterestBearingAndInterestRecalculationEnabled(), false);
loan.isInterestBearingAndInterestRecalculationEnabled(), true);
businessEventNotifierService.notifyPostBusinessEvent(new LoanInterestRecalculationBusinessEvent(loan));
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
} else {
loanScheduleService.recalculateScheduleFromLastTransaction(loan, null, existingTransactionIds, existingReversedTransactionIds,
true);
@@ -2511,10 +2436,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
}
}
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
loanAccrualsProcessingService.reprocessExistingAccruals(loan, true);
if (loan.isInterestBearingAndInterestRecalculationEnabled()) {
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan, true);
}
}
@@ -2681,31 +2606,20 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
businessEventNotifierService.notifyPreBusinessEvent(new LoanUndoLastDisbursalBusinessEvent(loan));
final MonetaryCurrency currency = loan.getCurrency();
final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>();
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFromDate);
loanDownPaymentTransactionValidator.validateAccountStatus(loan, LoanEvent.LOAN_DISBURSAL_UNDO_LAST);
loanTransactionValidator.validateActivityNotBeforeClientOrGroupTransferDate(loan, LoanEvent.LOAN_DISBURSAL_UNDO_LAST,
loan.getDisbursementDate());
final Map<String, Object> changes = undoLastDisbursal(scheduleGeneratorDTO, existingTransactionIds, existingReversedTransactionIds,
loan);
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
if (loan.isInterestBearingAndInterestRecalculationEnabled()) {
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
}
final Map<String, Object> changes = undoLastDisbursal(scheduleGeneratorDTO, loan);
if (!changes.isEmpty()) {
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
loanAccrualsProcessingService.reprocessExistingAccruals(loan, true);
if (loan.isInterestBearingAndInterestRecalculationEnabled()) {
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan, true);
}
createNote(loan, command, changes);
boolean isAccountTransfer = false;
final AccountingBridgeDataDTO accountingBridgeData = loanAccountingBridgeMapper.deriveAccountingBridgeData(currency.getCode(),
existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, loan);
journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
businessEventNotifierService.notifyPostBusinessEvent(new LoanUndoLastDisbursalBusinessEvent(loan));
}
@@ -2813,10 +2727,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} else {
loan.markAsChargedOff(transactionDate, currentUser, null);
}
final List<Long> existingTransactionIds = loanTransactionRepository.findTransactionIdsByLoan(loan);
final List<Long> existingReversedTransactionIds = loanTransactionRepository.findReversedTransactionIdsByLoan(loan);
final LoanTransaction chargeOffTransaction = LoanTransaction.chargeOff(loan, transactionDate, txnExternalId);
if (loan.isInterestBearingAndInterestRecalculationEnabled()) {
@@ -2833,6 +2743,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loan.addLoanTransaction(chargeOffTransaction);
}
loanTransactionRepository.saveAndFlush(chargeOffTransaction);
journalEntryPoster.postJournalEntriesForLoanTransaction(chargeOffTransaction, false, false);
saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
@@ -2849,13 +2760,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
: DateUtils.isAfter(transaction.getTransactionDate(), transactionDate))
.forEach(transaction -> {
transaction.reverse();
journalEntryPoster.postJournalEntriesForLoanTransaction(transaction, false, false);
final LoanAdjustTransactionBusinessEvent.Data data = new LoanAdjustTransactionBusinessEvent.Data(transaction);
businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(data));
});
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
businessEventNotifierService.notifyPostBusinessEvent(new LoanChargeOffPostBusinessEvent(chargeOffTransaction));
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withEntityId(chargeOffTransaction.getId()) //
@@ -2873,8 +2783,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.loanTransactionValidator.validateUndoChargeOff(command.json());
final Long loanId = command.getLoanId();
final Loan loan = this.loanAssembler.assembleFrom(loanId);
final List<Long> existingTransactionIds = loanTransactionRepository.findTransactionIdsByLoan(loan);
final List<Long> existingReversedTransactionIds = loanTransactionRepository.findReversedTransactionIdsByLoan(loan);
checkClientOrGroupActive(loan);
if (!loan.isOpen()) {
throw new GeneralPlatformDomainRuleException("error.msg.loan.is.not.active",
@@ -2906,6 +2814,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loan.liftChargeOff();
loanTransactionRepository.saveAndFlush(chargedOffTransaction);
journalEntryPoster.postJournalEntriesForLoanTransaction(chargedOffTransaction, false, false);
final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, null, null);
if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) {
@@ -2917,7 +2826,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
reprocessLoanTransactionsService.reprocessTransactions(loan);
saveLoanWithDataIntegrityViolationChecks(loan);
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
businessEventNotifierService.notifyPostBusinessEvent(new LoanUndoChargeOffBusinessEvent(chargedOffTransaction));
return new CommandProcessingResultBuilder() //
@@ -2956,9 +2864,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
// Create note
createNote(loan, command, changes);
// Initial transaction ids for journal entry generation
final List<Long> existingTransactionIds = loanTransactionRepository.findTransactionIdsByLoan(loan);
final List<Long> existingReversedTransactionIds = loanTransactionRepository.findReversedTransactionIdsByLoan(loan);
// Create refund transaction(s)
final Boolean interestRefundCalculation = command.booleanObjectValueOfParameterNamed("interestRefundCalculation");
Pair<LoanTransaction, LoanTransaction> refundTransactions = loanAccountDomainService.makeRefund(loan, scheduleGeneratorDTO,
@@ -2967,8 +2872,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
LoanTransaction interestRefundTransaction = refundTransactions.getRight();
// Accrual reprocessing
if (loan.isInterestBearingAndInterestRecalculationEnabled()) {
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
loanAccrualsProcessingService.reprocessExistingAccruals(loan, true);
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan, true);
}
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
@@ -2980,20 +2885,21 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
//// Mark Post Dated Check as paid., Do we need this for refund?
loanAccountDomainService.updateAndSavePostDatedChecksForIndividualAccount(loan, refundTransaction);
if (interestRefundTransaction != null) {
loanAccountDomainService.updateAndSavePostDatedChecksForIndividualAccount(loan, refundTransaction);
loanAccountDomainService.updateAndSavePostDatedChecksForIndividualAccount(loan, interestRefundTransaction);
}
// Collateral management
loanAccountDomainService.updateAndSaveLoanCollateralTransactionsForIndividualAccounts(loan, refundTransaction);
if (interestRefundTransaction != null) {
loanAccountDomainService.updateAndSaveLoanCollateralTransactionsForIndividualAccounts(loan, refundTransaction);
loanAccountDomainService.updateAndSaveLoanCollateralTransactionsForIndividualAccounts(loan, interestRefundTransaction);
}
// Raise business events
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, loan.isInterestBearingAndInterestRecalculationEnabled(),
false);
true);
// Create journal entries for the new transaction(s)
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
journalEntryPoster.postJournalEntriesForLoanTransaction(refundTransaction, false, false);
if (interestRefundTransaction != null) {
journalEntryPoster.postJournalEntriesForLoanTransaction(interestRefundTransaction, false, false);
}
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
Long entityId = refundTransaction.getId();
@@ -3070,9 +2976,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
createNote(loan, command, changes);
final List<Long> existingTransactionIds = loanTransactionRepository.findTransactionIdsByLoan(loan);
final List<Long> existingReversedTransactionIds = loanTransactionRepository.findReversedTransactionIdsByLoan(loan);
final LoanTransaction interestRefundTxn = loanAccountDomainService.createManualInterestRefundWithAmount(loan, targetTransaction,
amount, paymentDetail, txnExternalId);
@@ -3111,8 +3014,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
// Accrual reprocessing
if (loan.isInterestBearingAndInterestRecalculationEnabled()) {
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
loanAccrualsProcessingService.reprocessExistingAccruals(loan, true);
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan, true);
}
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
@@ -3123,10 +3026,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loanAccountDomainService.updateAndSaveLoanCollateralTransactionsForIndividualAccounts(loan, interestRefundTxn);
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, loan.isInterestBearingAndInterestRecalculationEnabled(),
false);
true);
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
journalEntryPoster.postJournalEntriesForLoanTransaction(interestRefundTxn, false, false);
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
return new CommandProcessingResultBuilder() //
@@ -3194,13 +3096,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
}
private Map<String, Object> undoDisbursal(final Loan loan, final ScheduleGeneratorDTO scheduleGeneratorDTO,
final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds) {
private Map<String, Object> undoDisbursal(final Loan loan, final ScheduleGeneratorDTO scheduleGeneratorDTO) {
final Map<String, Object> actualChanges = new LinkedHashMap<>();
final LoanStatus currentStatus = loan.getStatus();
final LoanStatus statusEnum = this.loanLifecycleStateMachine.dryTransition(LoanEvent.LOAN_DISBURSAL_UNDO, loan);
existingTransactionIds.addAll(loanTransactionRepository.findTransactionIdsByLoan(loan));
existingReversedTransactionIds.addAll(loanTransactionRepository.findReversedTransactionIdsByLoan(loan));
if (!statusEnum.hasStateOf(currentStatus)) {
this.loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSAL_UNDO, loan); // tis will update the loan
// status
@@ -3293,6 +3192,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
for (final LoanTransaction transaction : loan.getLoanTransactions()) {
loanChargeValidator.validateRepaymentTypeTransactionNotBeforeAChargeRefund(transaction.getLoan(), transaction, "reversed");
transaction.reverse();
journalEntryPoster.postJournalEntriesForLoanTransaction(transaction, false, false);
if (transaction.getId() != null) {
retainTransactions.add(transaction);
}
@@ -3301,8 +3201,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
private Optional<LoanTransaction> closeAsWrittenOff(final Loan loan, final JsonCommand command, final Map<String, Object> changes,
final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds, final AppUser currentUser,
final ScheduleGeneratorDTO scheduleGeneratorDTO) {
final AppUser currentUser, final ScheduleGeneratorDTO scheduleGeneratorDTO) {
closeDisbursements(loan, scheduleGeneratorDTO);
final LocalDate writtenOffOnLocalDate = command.localDateValueOfParameterNamed(TRANSACTION_DATE);
@@ -3315,9 +3214,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
return Optional.empty();
}
existingTransactionIds.addAll(loanTransactionRepository.findTransactionIdsByLoan(loan));
existingReversedTransactionIds.addAll(loanTransactionRepository.findReversedTransactionIdsByLoan(loan));
final String txnExternalId = command.stringValueOfParameterNamedAllowingNull(EXTERNAL_ID);
ExternalId externalId = ExternalIdFactory.produce(txnExternalId);
@@ -3379,11 +3275,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
private Optional<LoanTransaction> close(final Loan loan, final JsonCommand command, final Map<String, Object> changes,
final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
final ScheduleGeneratorDTO scheduleGeneratorDTO) {
existingTransactionIds.addAll(loanTransactionRepository.findTransactionIdsByLoan(loan));
existingReversedTransactionIds.addAll(loanTransactionRepository.findReversedTransactionIdsByLoan(loan));
final LocalDate closureDate = command.localDateValueOfParameterNamed(TRANSACTION_DATE);
final String txnExternalId = command.stringValueOfParameterNamedAllowingNull(EXTERNAL_ID);
@@ -3489,17 +3381,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
reprocessLoanTransactionsService.reprocessTransactions(loan);
}
private Map<String, Object> undoLastDisbursal(final ScheduleGeneratorDTO scheduleGeneratorDTO, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds, final Loan loan) {
private Map<String, Object> undoLastDisbursal(final ScheduleGeneratorDTO scheduleGeneratorDTO, final Loan loan) {
final Map<String, Object> actualChanges = new LinkedHashMap<>();
List<LoanTransaction> loanTransactions = loan.retrieveListOfTransactionsByType(LoanTransactionType.DISBURSEMENT);
loanTransactions.sort(Comparator.comparing(LoanTransaction::getId));
final LoanTransaction lastDisbursalTransaction = loanTransactions.getLast();
final LocalDate lastTransactionDate = lastDisbursalTransaction.getTransactionDate();
existingTransactionIds.addAll(loanTransactionRepository.findTransactionIdsByLoan(loan));
existingReversedTransactionIds.addAll(loanTransactionRepository.findReversedTransactionIdsByLoan(loan));
loanTransactions = loanTransactionRepository.findNonReversedMonetaryTransactionsByLoan(loan);
Collections.reverse(loanTransactions);
for (final LoanTransaction previousTransaction : loanTransactions) {
@@ -3530,18 +3418,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
return actualChanges;
}
private void waiveInterest(final Loan loan, final LoanTransaction waiveInterestTransaction, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds, final ScheduleGeneratorDTO scheduleGeneratorDTO) {
existingTransactionIds.addAll(loanTransactionRepository.findTransactionIdsByLoan(loan));
existingReversedTransactionIds.addAll(loanTransactionRepository.findReversedTransactionIdsByLoan(loan));
private void waiveInterest(final Loan loan, final LoanTransaction waiveInterestTransaction,
final ScheduleGeneratorDTO scheduleGeneratorDTO) {
loanDownPaymentHandlerService.handleRepaymentOrRecoveryOrWaiverTransaction(loan, waiveInterestTransaction, null,
scheduleGeneratorDTO);
}
private void undoWrittenOff(final Loan loan, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds) {
existingTransactionIds.addAll(loanTransactionRepository.findTransactionIdsByLoan(loan));
existingReversedTransactionIds.addAll(loanTransactionRepository.findReversedTransactionIdsByLoan(loan));
private void undoWrittenOff(final Loan loan) {
final LoanTransaction writeOffTransaction = loan.findWriteOffTransaction();
loanChargeValidator.validateRepaymentTypeTransactionNotBeforeAChargeRefund(writeOffTransaction.getLoan(), writeOffTransaction,
"reversed");
@@ -3558,7 +3441,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
/**
* Reverse only disbursement, accruals, and repayments at disbursal transactions
*/
public void reverseExistingTransactionsTillLastDisbursal(final Loan loan, final LoanTransaction lastDisbursalTransaction) {
public void reverseExistingTransactionsTillLastDisbursal(Loan loan, final LoanTransaction lastDisbursalTransaction) {
LoanCharge chargeToDeactivate = null;
for (final LoanCharge charge : loan.getCharges()) {
@@ -3590,6 +3473,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
&& transaction.isAllowTypeTransactionAtTheTimeOfLastUndo()) {
loanChargeValidator.validateRepaymentTypeTransactionNotBeforeAChargeRefund(transaction.getLoan(), transaction, "reversed");
transaction.reverse();
journalEntryPoster.postJournalEntriesForLoanTransaction(transaction, false, false);
}
}
@@ -3609,9 +3493,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
.max(Comparator.comparing(LoanTransaction::getId));
// reverse the down-payment transaction
downPaymentTransaction.ifPresent(tr -> {
loanChargeValidator.validateRepaymentTypeTransactionNotBeforeAChargeRefund(tr.getLoan(), tr, "reversed");
tr.reverse();
downPaymentTransaction.ifPresent(transaction -> {
loanChargeValidator.validateRepaymentTypeTransactionNotBeforeAChargeRefund(transaction.getLoan(), transaction, "reversed");
transaction.reverse();
journalEntryPoster.postJournalEntriesForLoanTransaction(transaction, false, false);
});
}
}
@@ -24,6 +24,10 @@ import java.util.Optional;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanAccrualAdjustmentTransactionBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanAccrualTransactionCreatedBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionBusinessEvent;
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.interestpauses.service.LoanAccountTransfersService;
@@ -56,6 +60,8 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
private final LoanBalanceService loanBalanceService;
private final LoanTransactionRepository loanTransactionRepository;
private final LoanTransactionService loanTransactionService;
private final LoanJournalEntryPoster loanJournalEntryPoster;
private final BusinessEventNotifierService businessEventNotifierService;
@Override
public void reprocessTransactions(final Loan loan) {
@@ -191,8 +197,19 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(newTransaction);
// Create journal entries for new transaction
loanJournalEntryPoster.postJournalEntriesForLoanTransaction(newTransaction, false, false);
if (oldTransaction == null && (newTransaction.isAccrual() || newTransaction.isAccrualAdjustment())) {
final LoanTransactionBusinessEvent businessEvent = newTransaction.isAccrual()
? new LoanAccrualTransactionCreatedBusinessEvent(newTransaction)
: new LoanAccrualAdjustmentTransactionBusinessEvent(newTransaction);
businessEventNotifierService.notifyPostBusinessEvent(businessEvent);
}
if (oldTransaction != null) {
loanAccountTransfersService.updateLoanTransaction(oldTransaction.getId(), newTransaction);
// Create reversal journal entries for old transaction if it exists (reverse-replay scenario)
loanJournalEntryPoster.postJournalEntriesForLoanTransaction(oldTransaction, false, false);
}
}
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
@@ -64,7 +64,6 @@ import org.apache.fineract.portfolio.loanaccount.repository.LoanCapitalizedIncom
import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanTransactionValidator;
import org.apache.fineract.portfolio.loanaccount.service.BuyDownFeePlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualTransactionBusinessEventService;
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualsProcessingService;
import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
import org.apache.fineract.portfolio.loanaccount.service.LoanDownPaymentHandlerService;
@@ -94,7 +93,6 @@ public class LoanAdjustmentServiceImpl implements LoanAdjustmentService {
private final LoanUtilService loanUtilService;
private final LoanRepaymentScheduleInstallmentRepository loanRepaymentScheduleInstallmentRepository;
private final LoanLifecycleStateMachine loanLifecycleStateMachine;
private final LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService;
private final LoanDownPaymentHandlerService loanDownPaymentHandlerService;
private final LoanAccrualsProcessingService loanAccrualsProcessingService;
private final LoanChargeValidator loanChargeValidator;
@@ -115,9 +113,6 @@ public class LoanAdjustmentServiceImpl implements LoanAdjustmentService {
ExternalId reversalTxnExternalId = parameter.getReversalTxnExternalId();
String noteText = parameter.getNoteText();
final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>();
final Money transactionAmountAsMoney = Money.of(loan.getCurrency(), transactionAmount);
LoanTransaction newTransactionDetail = LoanTransaction.repaymentType(transactionToAdjust.getTypeOf(), loan.getOffice(),
transactionAmountAsMoney, paymentDetail, transactionDate, txnExternalId, transactionToAdjust.getChargeRefundChargeType());
@@ -220,12 +215,11 @@ public class LoanAdjustmentServiceImpl implements LoanAdjustmentService {
loanTransactionValidator.validateRepaymentDateIsOnNonWorkingDay(newTransactionDetail.getTransactionDate(),
holidayDetailDTO.getWorkingDays(), holidayDetailDTO.isAllowTransactionsOnNonWorkingDay());
adjustExistingTransaction(loan, newTransactionDetail, transactionToAdjust, existingTransactionIds, existingReversedTransactionIds,
scheduleGeneratorDTO, reversalTxnExternalId);
adjustExistingTransaction(loan, newTransactionDetail, transactionToAdjust, scheduleGeneratorDTO, reversalTxnExternalId);
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
loanAccrualsProcessingService.reprocessExistingAccruals(loan, true);
if (loan.isInterestBearingAndInterestRecalculationEnabled()) {
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan, true);
}
boolean thereIsNewTransaction = newTransactionDetail.isGreaterThanZero();
@@ -234,6 +228,7 @@ public class LoanAdjustmentServiceImpl implements LoanAdjustmentService {
this.paymentDetailWritePlatformService.persistPaymentDetail(paymentDetail);
}
this.loanTransactionRepository.saveAndFlush(newTransactionDetail);
journalEntryPoster.postJournalEntriesForLoanTransaction(newTransactionDetail, false, false);
}
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
@@ -267,7 +262,7 @@ public class LoanAdjustmentServiceImpl implements LoanAdjustmentService {
loanLifecycleStateMachine.determineAndTransition(loan, loan.getLastUserTransactionDate());
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, loan.isInterestBearingAndInterestRecalculationEnabled(),
false);
true);
this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
@@ -283,8 +278,7 @@ public class LoanAdjustmentServiceImpl implements LoanAdjustmentService {
entityExternalId = newTransactionDetail.getExternalId();
}
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
journalEntryPoster.postJournalEntriesForLoanTransaction(transactionToAdjust, false, false);
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(eventData));
@@ -300,12 +294,8 @@ public class LoanAdjustmentServiceImpl implements LoanAdjustmentService {
}
public void adjustExistingTransaction(final Loan loan, final LoanTransaction newTransactionDetail,
final LoanTransaction transactionForAdjustment, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds, final ScheduleGeneratorDTO scheduleGeneratorDTO,
final LoanTransaction transactionForAdjustment, final ScheduleGeneratorDTO scheduleGeneratorDTO,
final ExternalId reversalExternalId) {
existingTransactionIds.addAll(loanTransactionRepository.findTransactionIdsByLoan(loan));
existingReversedTransactionIds.addAll(loanTransactionRepository.findReversedTransactionIdsByLoan(loan));
loanTransactionValidator.validateActivityNotBeforeClientOrGroupTransferDate(loan, LoanEvent.LOAN_REPAYMENT_OR_WAIVER,
transactionForAdjustment.getTransactionDate());
@@ -336,6 +326,7 @@ public class LoanAdjustmentServiceImpl implements LoanAdjustmentService {
loanTransaction, "reversed");
loanTransaction.reverse();
loanTransaction.manuallyAdjustedOrReversed();
journalEntryPoster.postJournalEntriesForLoanTransaction(loanTransaction, false, false);
LoanAdjustTransactionBusinessEvent.Data eventData = new LoanAdjustTransactionBusinessEvent.Data(loanTransaction);
businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(eventData));
});
@@ -18,7 +18,6 @@
*/
package org.apache.fineract.portfolio.loanaccount.starter;
import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
import org.apache.fineract.cob.service.LoanAccountLockService;
import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormatRepositoryWrapper;
import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper;
@@ -83,7 +82,6 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanSchedul
import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleAssembler;
import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleCalculationPlatformService;
import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryWritePlatformService;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanAccountingBridgeMapper;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanChargeMapper;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanCollateralManagementMapper;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanMapper;
@@ -306,29 +304,27 @@ public class LoanAccountConfiguration {
LoanAssembler loanAssembler, ChargeRepositoryWrapper chargeRepository,
BusinessEventNotifierService businessEventNotifierService, LoanTransactionRepository loanTransactionRepository,
AccountTransfersWritePlatformService accountTransfersWritePlatformService, LoanRepositoryWrapper loanRepositoryWrapper,
JournalEntryWritePlatformService journalEntryWritePlatformService, LoanAccountDomainService loanAccountDomainService,
LoanChargeRepository loanChargeRepository, LoanWritePlatformService loanWritePlatformService, LoanUtilService loanUtilService,
LoanAccountDomainService loanAccountDomainService, LoanChargeRepository loanChargeRepository,
LoanWritePlatformService loanWritePlatformService, LoanUtilService loanUtilService,
LoanChargeReadPlatformService loanChargeReadPlatformService, LoanLifecycleStateMachine loanLifecycleStateMachine,
AccountAssociationsReadPlatformService accountAssociationsReadPlatformService, FromJsonHelper fromApiJsonHelper,
ConfigurationDomainService configurationDomainService,
LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
ExternalIdFactory externalIdFactory, AccountTransferDetailRepository accountTransferDetailRepository,
LoanChargeAssembler loanChargeAssembler, PaymentDetailWritePlatformService paymentDetailWritePlatformService,
NoteRepository noteRepository, LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService,
LoanAccrualsProcessingService loanAccrualsProcessingService,
NoteRepository noteRepository, LoanAccrualsProcessingService loanAccrualsProcessingService,
LoanDownPaymentTransactionValidator loanDownPaymentTransactionValidator, LoanChargeValidator loanChargeValidator,
LoanScheduleService loanScheduleService, ReprocessLoanTransactionsService reprocessLoanTransactionsService,
LoanAccountService loanAccountService, LoanAdjustmentService loanAdjustmentService,
LoanAccountingBridgeMapper loanAccountingBridgeMapper, LoanChargeService loanChargeService) {
LoanAccountService loanAccountService, LoanAdjustmentService loanAdjustmentService, LoanChargeService loanChargeService,
LoanJournalEntryPoster loanJournalEntryPoster) {
return new LoanChargeWritePlatformServiceImpl(loanChargeApiJsonValidator, loanAssembler, chargeRepository,
businessEventNotifierService, loanTransactionRepository, accountTransfersWritePlatformService, loanRepositoryWrapper,
journalEntryWritePlatformService, loanAccountDomainService, loanChargeRepository, loanWritePlatformService, loanUtilService,
loanChargeReadPlatformService, loanLifecycleStateMachine, accountAssociationsReadPlatformService, fromApiJsonHelper,
configurationDomainService, loanRepaymentScheduleTransactionProcessorFactory, externalIdFactory,
accountTransferDetailRepository, loanChargeAssembler, paymentDetailWritePlatformService, noteRepository,
loanAccrualTransactionBusinessEventService, loanAccrualsProcessingService, loanDownPaymentTransactionValidator,
loanAccountDomainService, loanChargeRepository, loanWritePlatformService, loanUtilService, loanChargeReadPlatformService,
loanLifecycleStateMachine, accountAssociationsReadPlatformService, fromApiJsonHelper, configurationDomainService,
loanRepaymentScheduleTransactionProcessorFactory, externalIdFactory, accountTransferDetailRepository, loanChargeAssembler,
paymentDetailWritePlatformService, noteRepository, loanAccrualsProcessingService, loanDownPaymentTransactionValidator,
loanChargeValidator, loanScheduleService, reprocessLoanTransactionsService, loanAccountService, loanAdjustmentService,
loanAccountingBridgeMapper, loanChargeService);
loanChargeService, loanJournalEntryPoster);
}
@Bean
@@ -415,10 +411,10 @@ public class LoanAccountConfiguration {
LoanUpdateCommandFromApiJsonDeserializer loanUpdateCommandFromApiJsonDeserializer, LoanRepositoryWrapper loanRepositoryWrapper,
LoanAccountDomainService loanAccountDomainService, NoteRepository noteRepository,
LoanTransactionRepository loanTransactionRepository, LoanTransactionRelationRepository loanTransactionRelationRepository,
LoanAssembler loanAssembler, JournalEntryWritePlatformService journalEntryWritePlatformService,
CalendarInstanceRepository calendarInstanceRepository, PaymentDetailWritePlatformService paymentDetailWritePlatformService,
HolidayRepositoryWrapper holidayRepository, ConfigurationDomainService configurationDomainService,
WorkingDaysRepositoryWrapper workingDaysRepository, AccountTransfersWritePlatformService accountTransfersWritePlatformService,
LoanAssembler loanAssembler, CalendarInstanceRepository calendarInstanceRepository,
PaymentDetailWritePlatformService paymentDetailWritePlatformService, HolidayRepositoryWrapper holidayRepository,
ConfigurationDomainService configurationDomainService, WorkingDaysRepositoryWrapper workingDaysRepository,
AccountTransfersWritePlatformService accountTransfersWritePlatformService,
AccountTransfersReadPlatformService accountTransfersReadPlatformService,
AccountAssociationsReadPlatformService accountAssociationsReadPlatformService, LoanReadPlatformService loanReadPlatformService,
FromJsonHelper fromApiJsonHelper, CalendarRepository calendarRepository,
@@ -440,25 +436,24 @@ public class LoanAccountConfiguration {
LoanDisbursementService loanDisbursementService, LoanScheduleService loanScheduleService,
LoanChargeValidator loanChargeValidator, LoanOfficerService loanOfficerService,
ReprocessLoanTransactionsService reprocessLoanTransactionsService, LoanAccountService loanAccountService,
LoanJournalEntryPoster journalEntryPoster, LoanAdjustmentService loanAdjustmentService,
LoanAccountingBridgeMapper loanAccountingBridgeMapper, LoanMapper loanMapper,
LoanJournalEntryPoster journalEntryPoster, LoanAdjustmentService loanAdjustmentService, LoanMapper loanMapper,
LoanTransactionProcessingService loanTransactionProcessingService, final LoanBalanceService loanBalanceService,
LoanTransactionService loanTransactionService) {
return new LoanWritePlatformServiceJpaRepositoryImpl(context, loanTransactionValidator, loanUpdateCommandFromApiJsonDeserializer,
loanRepositoryWrapper, loanAccountDomainService, noteRepository, loanTransactionRepository,
loanTransactionRelationRepository, loanAssembler, journalEntryWritePlatformService, calendarInstanceRepository,
paymentDetailWritePlatformService, holidayRepository, configurationDomainService, workingDaysRepository,
accountTransfersWritePlatformService, accountTransfersReadPlatformService, accountAssociationsReadPlatformService,
loanReadPlatformService, fromApiJsonHelper, calendarRepository, loanScheduleHistoryWritePlatformService,
loanApplicationValidator, accountAssociationRepository, accountTransferDetailRepository, businessEventNotifierService,
guarantorDomainService, loanUtilService, entityDatatableChecksWritePlatformService, codeValueRepository,
cashierTransactionDataValidator, glimRepository, loanRepository, repaymentWithPostDatedChecksAssembler,
postDatedChecksRepository, loanRepaymentScheduleInstallmentRepository, loanLifecycleStateMachine, loanAccountLockService,
externalIdFactory, loanAccrualTransactionBusinessEventService, errorHandler, loanDownPaymentHandlerService,
loanTransactionAssembler, loanAccrualsProcessingService, loanOfficerValidator, loanDownPaymentTransactionValidator,
loanDisbursementService, loanScheduleService, loanChargeValidator, loanOfficerService, reprocessLoanTransactionsService,
loanAccountService, journalEntryPoster, loanAdjustmentService, loanAccountingBridgeMapper, loanMapper,
loanTransactionProcessingService, loanBalanceService, loanTransactionService);
loanTransactionRelationRepository, loanAssembler, calendarInstanceRepository, paymentDetailWritePlatformService,
holidayRepository, configurationDomainService, workingDaysRepository, accountTransfersWritePlatformService,
accountTransfersReadPlatformService, accountAssociationsReadPlatformService, loanReadPlatformService, fromApiJsonHelper,
calendarRepository, loanScheduleHistoryWritePlatformService, loanApplicationValidator, accountAssociationRepository,
accountTransferDetailRepository, businessEventNotifierService, guarantorDomainService, loanUtilService,
entityDatatableChecksWritePlatformService, codeValueRepository, cashierTransactionDataValidator, glimRepository,
loanRepository, repaymentWithPostDatedChecksAssembler, postDatedChecksRepository,
loanRepaymentScheduleInstallmentRepository, loanLifecycleStateMachine, loanAccountLockService, externalIdFactory,
loanAccrualTransactionBusinessEventService, errorHandler, loanDownPaymentHandlerService, loanTransactionAssembler,
loanAccrualsProcessingService, loanOfficerValidator, loanDownPaymentTransactionValidator, loanDisbursementService,
loanScheduleService, loanChargeValidator, loanOfficerService, reprocessLoanTransactionsService, loanAccountService,
journalEntryPoster, loanAdjustmentService, loanMapper, loanTransactionProcessingService, loanBalanceService,
loanTransactionService);
}
@Bean
@@ -476,11 +471,12 @@ public class LoanAccountConfiguration {
LoanRefundService loanRefundService, LoanRefundValidator loanRefundValidator,
ReprocessLoanTransactionsService reprocessLoanTransactionsService,
LoanTransactionProcessingService loanTransactionProcessingService, LoanLifecycleStateMachine loanLifecycleStateMachine,
LoanBalanceService loanBalanceService, LoanTransactionService loanTransactionService) {
LoanBalanceService loanBalanceService, LoanTransactionService loanTransactionService,
LoanJournalEntryPoster journalEntryPoster) {
return new LoanDownPaymentHandlerServiceImpl(loanTransactionRepository, businessEventNotifierService,
loanDownPaymentTransactionValidator, loanScheduleService, loanRefundService, loanRefundValidator,
reprocessLoanTransactionsService, loanTransactionProcessingService, loanLifecycleStateMachine, loanBalanceService,
loanTransactionService);
loanTransactionService, journalEntryPoster);
}
@Bean
@@ -493,19 +489,18 @@ public class LoanAccountConfiguration {
@ConditionalOnMissingBean(LoanDisbursementService.class)
public LoanDisbursementService loanDisbursementService(LoanChargeValidator loanChargeValidator,
LoanDisbursementValidator loanDisbursementValidator, ReprocessLoanTransactionsService reprocessLoanTransactionsService,
LoanChargeService loanChargeService, LoanBalanceService loanBalanceService) {
LoanChargeService loanChargeService, LoanBalanceService loanBalanceService, LoanJournalEntryPoster journalEntryPoster,
LoanTransactionRepository loanTransactionRepository) {
return new LoanDisbursementService(loanChargeValidator, loanDisbursementValidator, reprocessLoanTransactionsService,
loanChargeService, loanBalanceService);
loanChargeService, loanBalanceService, journalEntryPoster, loanTransactionRepository);
}
@Bean
@ConditionalOnMissingBean(LoanChargeService.class)
public LoanChargeService loanChargeService(final LoanChargeValidator loanChargeValidator,
final LoanTransactionProcessingService loanTransactionProcessingService,
final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanBalanceService loanBalanceService,
final LoanTransactionRepository loanTransactionRepository) {
return new LoanChargeService(loanChargeValidator, loanTransactionProcessingService, loanLifecycleStateMachine, loanBalanceService,
loanTransactionRepository);
final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanBalanceService loanBalanceService) {
return new LoanChargeService(loanChargeValidator, loanTransactionProcessingService, loanLifecycleStateMachine, loanBalanceService);
}
@Bean
@@ -528,9 +523,8 @@ public class LoanAccountConfiguration {
@ConditionalOnMissingBean(LoanRefundService.class)
public LoanRefundService loanRefundService(final LoanRefundValidator loanRefundValidator,
final LoanTransactionProcessingService loanTransactionProcessingService,
final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransactionRepository loanTransactionRepository) {
return new LoanRefundService(loanRefundValidator, loanTransactionProcessingService, loanLifecycleStateMachine,
loanTransactionRepository);
final LoanLifecycleStateMachine loanLifecycleStateMachine) {
return new LoanRefundService(loanRefundValidator, loanTransactionProcessingService, loanLifecycleStateMachine);
}
@Bean
@@ -131,8 +131,7 @@ public class LoanAccountDelinquencyRangeEventSerializerTest {
private AvroDateTimeMapper mapper;
private final LoanChargeService loanChargeService = new LoanChargeService(mock(LoanChargeValidator.class),
mock(LoanTransactionProcessingService.class), mock(LoanLifecycleStateMachine.class), mock(LoanBalanceService.class),
mock(LoanTransactionRepository.class));
mock(LoanTransactionProcessingService.class), mock(LoanLifecycleStateMachine.class), mock(LoanBalanceService.class));
private MockedStatic<MoneyHelper> moneyHelper = Mockito.mockStatic(MoneyHelper.class);
@@ -28,9 +28,7 @@ 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;
@@ -194,6 +192,8 @@ class LoanAdjustmentServiceImplTest {
private LoanAccrualsProcessingService loanAccrualsProcessingService;
@Mock
private LoanChargeValidator loanChargeValidator;
@Mock
private LoanJournalEntryPoster journalEntryPoster;
@Test
void givenMerchantIssuedRefundTransactionWithRelatedTransactions_whenAdjustExistingTransaction_thenRelatedTransactionsAreReversedAndEventsTriggered() {
@@ -204,9 +204,6 @@ class LoanAdjustmentServiceImplTest {
ScheduleGeneratorDTO scheduleGeneratorDTO = mock(ScheduleGeneratorDTO.class);
ExternalId reversalExternalId = ExternalId.generate();
List<Long> existingTransactionIds = new ArrayList<>();
List<Long> existingReversedTransactionIds = new ArrayList<>();
// Mock transaction type
when(transactionForAdjustment.getTypeOf()).thenReturn(LoanTransactionType.MERCHANT_ISSUED_REFUND);
when(transactionForAdjustment.isNotRepaymentLikeType()).thenReturn(false);
@@ -233,16 +230,12 @@ class LoanAdjustmentServiceImplTest {
List<LoanTransaction> loanTransactions = Arrays.asList(transactionForAdjustment, relatedTransaction);
when(loan.getLoanTransactions()).thenReturn(loanTransactions);
// Mock methods called inside adjustExistingTransaction
when(loanTransactionRepository.findTransactionIdsByLoan(loan)).thenReturn(Collections.emptyList());
when(loanTransactionRepository.findReversedTransactionIdsByLoan(loan)).thenReturn(Collections.emptyList());
doNothing().when(loanTransactionValidator).validateActivityNotBeforeClientOrGroupTransferDate(any(), any(), any());
when(loan.isClosedWrittenOff()).thenReturn(false);
when(newTransactionDetail.isRepaymentLikeType()).thenReturn(true);
// Act
underTest.adjustExistingTransaction(loan, newTransactionDetail, transactionForAdjustment, existingTransactionIds,
existingReversedTransactionIds, scheduleGeneratorDTO, reversalExternalId);
underTest.adjustExistingTransaction(loan, newTransactionDetail, transactionForAdjustment, scheduleGeneratorDTO, reversalExternalId);
// Assert
// Verify that related transaction is reversed and event is triggered
@@ -265,9 +258,6 @@ class LoanAdjustmentServiceImplTest {
ScheduleGeneratorDTO scheduleGeneratorDTO = mock(ScheduleGeneratorDTO.class);
ExternalId reversalExternalId = ExternalId.generate();
List<Long> existingTransactionIds = new ArrayList<>();
List<Long> existingReversedTransactionIds = new ArrayList<>();
// Mock transaction type
when(transactionForAdjustment.getTypeOf()).thenReturn(LoanTransactionType.REPAYMENT);
when(transactionForAdjustment.isNotRepaymentLikeType()).thenReturn(false);
@@ -279,15 +269,12 @@ class LoanAdjustmentServiceImplTest {
LoanTransaction unrelatedTransaction = mock(LoanTransaction.class);
// Mock methods called inside adjustExistingTransaction
when(loanTransactionRepository.findTransactionIdsByLoan(loan)).thenReturn(Collections.emptyList());
when(loanTransactionRepository.findReversedTransactionIdsByLoan(loan)).thenReturn(Collections.emptyList());
doNothing().when(loanTransactionValidator).validateActivityNotBeforeClientOrGroupTransferDate(any(), any(), any());
when(loan.isClosedWrittenOff()).thenReturn(false);
when(newTransactionDetail.isRepaymentLikeType()).thenReturn(true);
// Act
underTest.adjustExistingTransaction(loan, newTransactionDetail, transactionForAdjustment, existingTransactionIds,
existingReversedTransactionIds, scheduleGeneratorDTO, reversalExternalId);
underTest.adjustExistingTransaction(loan, newTransactionDetail, transactionForAdjustment, scheduleGeneratorDTO, reversalExternalId);
// Assert
// Verify that no related transactions are reversed
@@ -19,10 +19,7 @@
package org.apache.fineract.portfolio.loanaccount.service;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.never;
@@ -35,7 +32,6 @@ import java.time.LocalDate;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.stream.Stream;
import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
@@ -61,7 +57,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.loanaccount.domain.SingleLoanChargeRepaymentScheduleProcessingWrapper;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanAccountingBridgeMapper;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeApiJsonValidator;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
@@ -155,9 +150,6 @@ class LoanChargeWritePlatformServiceImplTest {
@Mock
private LoanAccountService loanAccountService;
@Mock
private LoanAccountingBridgeMapper loanAccountingBridgeMapper;
@Mock
private LoanChargeService loanChargeService;
@@ -170,6 +162,9 @@ class LoanChargeWritePlatformServiceImplTest {
@Mock
private LoanLifecycleStateMachine loanLifecycleStateMachine;
@Mock
private LoanJournalEntryPoster journalEntryPoster;
@BeforeEach
void setUp() {
when(loanAssembler.assembleFrom(LOAN_ID)).thenReturn(loan);
@@ -190,10 +185,6 @@ class LoanChargeWritePlatformServiceImplTest {
when(loan.getCurrency()).thenReturn(monetaryCurrency);
when(monetaryCurrency.getCode()).thenReturn(CURRENCY_CODE);
when(loanAccountService.saveAndFlushLoanWithDataIntegrityViolationChecks(any())).thenReturn(loan);
List<Long> existingTransactionIds = new ArrayList<>();
List<Long> existingReversedTransactionIds = new ArrayList<>();
when(loanTransactionRepository.findTransactionIdsByLoan(loan)).thenReturn(existingTransactionIds);
when(loanTransactionRepository.findReversedTransactionIdsByLoan(loan)).thenReturn(existingReversedTransactionIds);
when(loan.getLoanCharges()).thenReturn(new HashSet<>());
when(loan.getDisbursementDate()).thenReturn(LocalDate.now(ZoneId.systemDefault()));
@@ -209,7 +200,6 @@ class LoanChargeWritePlatformServiceImplTest {
when(loan.isCashBasedAccountingEnabledOnLoanProduct()).thenReturn(false);
when(loan.isUpfrontAccrualAccountingEnabledOnLoanProduct()).thenReturn(false);
when(loanAccountingBridgeMapper.deriveAccountingBridgeData(anyString(), anyList(), anyList(), anyBoolean(), any(Loan.class))).thenReturn(new AccountingBridgeDataDTO());
doNothing().when(journalEntryWritePlatformService).createJournalEntriesForLoan(any(AccountingBridgeDataDTO.class));
doNothing().when(loanChargeService).addLoanCharge(any(Loan.class), any(LoanCharge.class));
}
@@ -117,6 +117,9 @@ public class LoanDownPaymentHandlerServiceImplTest {
@Mock
private LoanTransactionService loanTransactionService;
@Mock
private LoanJournalEntryPoster loanJournalEntryPoster;
private LoanDownPaymentHandlerServiceImpl underTest;
@BeforeEach
@@ -124,7 +127,7 @@ public class LoanDownPaymentHandlerServiceImplTest {
underTest = new LoanDownPaymentHandlerServiceImpl(loanTransactionRepository, businessEventNotifierService,
loanDownPaymentTransactionValidator, loanScheduleService, loanRefundService, loanRefundValidator,
reprocessLoanTransactionsService, loanTransactionProcessingService, loanLifecycleStateMachine, loanBalanceService,
loanTransactionService);
loanTransactionService, loanJournalEntryPoster);
moneyHelper.when(MoneyHelper::getMathContext).thenReturn(new MathContext(12, RoundingMode.UP));
moneyHelper.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.UP);
tempConfigServiceMock.when(TemporaryConfigurationServiceContainer::isExternalIdAutoGenerationEnabled).thenReturn(true);