FINERACT-2348: Calculate and store Capitalized income / Buydown fee amortization allocation schedule

This commit is contained in:
mariiaKraievska
2025-09-08 14:00:06 +02:00
committed by Adam Saghy
parent 9790f59e4a
commit cd0ecfcda0
34 changed files with 2094 additions and 91 deletions
@@ -0,0 +1,124 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.loanaccount.service;
import java.math.BigDecimal;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.portfolio.loanaccount.data.AmortizationAllocationBaseTransactionDTO;
import org.apache.fineract.portfolio.loanaccount.data.AmortizationAllocationMappingDTO;
import org.apache.fineract.portfolio.loanaccount.data.LoanAmortizationAllocationData;
import org.apache.fineract.portfolio.loanaccount.data.LoanAmortizationAllocationData.AmortizationMappingData;
import org.apache.fineract.portfolio.loanaccount.domain.AmortizationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanAmortizationAllocationMapping;
import org.apache.fineract.portfolio.loanaccount.domain.LoanAmortizationAllocationMappingRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanTransactionTypeException;
import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionNotFoundException;
import org.apache.fineract.portfolio.loanaccount.repository.LoanBuyDownFeeBalanceRepository;
import org.apache.fineract.portfolio.loanaccount.repository.LoanCapitalizedIncomeBalanceRepository;
import org.springframework.dao.EmptyResultDataAccessException;
@RequiredArgsConstructor
public class LoanAmortizationAllocationServiceImpl implements LoanAmortizationAllocationService {
private final LoanAmortizationAllocationMappingRepository loanAmortizationAllocationMappingRepository;
private final LoanTransactionRepository loanTransactionRepository;
private final LoanCapitalizedIncomeBalanceRepository capitalizedIncomeBalanceRepository;
private final LoanBuyDownFeeBalanceRepository buyDownFeeBalanceRepository;
@Override
public LoanAmortizationAllocationData retrieveLoanAmortizationAllocationsForBuyDownFeeTransaction(final Long loanTransactionId,
final Long loanId) {
final LoanTransaction loanTransaction = this.loanTransactionRepository.findByIdAndLoanId(loanTransactionId, loanId)
.orElseThrow(() -> new LoanTransactionNotFoundException(loanTransactionId));
if (!LoanTransactionType.BUY_DOWN_FEE.equals(loanTransaction.getTypeOf())) {
throw new InvalidLoanTransactionTypeException("transaction", "is.not.a.buydown.fee.transaction",
"Transaction with ID " + loanTransactionId + " is not a Buy Down Fee transaction");
}
return retrieveLoanAmortizationAllocationData(loanTransaction, loanId);
}
@Override
public LoanAmortizationAllocationData retrieveLoanAmortizationAllocationsForCapitalizedIncomeTransaction(final Long loanTransactionId,
final Long loanId) {
final LoanTransaction loanTransaction = this.loanTransactionRepository.findByIdAndLoanId(loanTransactionId, loanId)
.orElseThrow(() -> new LoanTransactionNotFoundException(loanTransactionId));
if (!LoanTransactionType.CAPITALIZED_INCOME.equals(loanTransaction.getTypeOf())) {
throw new InvalidLoanTransactionTypeException("transaction", "is.not.a.capitalized.income.transaction",
"Transaction with ID " + loanTransactionId + " is not a Capitalized Income transaction");
}
return retrieveLoanAmortizationAllocationData(loanTransaction, loanId);
}
private LoanAmortizationAllocationData retrieveLoanAmortizationAllocationData(final LoanTransaction loanTransaction,
final Long loanId) {
try {
final Long loanTransactionId = loanTransaction.getId();
final AmortizationAllocationBaseTransactionDTO baseTransactionInfo = getBaseTransactionInfo(loanTransaction, loanId);
if (baseTransactionInfo == null) {
throw new LoanTransactionNotFoundException(loanTransactionId);
}
final List<AmortizationAllocationMappingDTO> amortizationMappings = loanAmortizationAllocationMappingRepository
.findAmortizationMappingsByBaseTransactionAndLoan(loanTransactionId, loanId);
final List<AmortizationMappingData> mappings = amortizationMappings.stream()
.map(dto -> AmortizationMappingData.builder().amortizationLoanTransactionId(dto.getAmortizationLoanTransactionId())
.amortizationLoanTransactionExternalId(dto.getAmortizationLoanTransactionExternalId())
.date(dto.getAmortizationDate()).type(dto.getAmortizationType()).amount(dto.getAmount()).build())
.toList();
return new LoanAmortizationAllocationData(baseTransactionInfo.getLoanId(), baseTransactionInfo.getLoanExternalId(),
baseTransactionInfo.getBaseLoanTransactionId(), baseTransactionInfo.getBaseLoanTransactionDate(),
baseTransactionInfo.getBaseLoanTransactionAmount(), baseTransactionInfo.getUnrecognizedAmount(),
baseTransactionInfo.getChargedOffAmount(), baseTransactionInfo.getAdjustmentAmount(), mappings);
} catch (final EmptyResultDataAccessException e) {
throw new LoanTransactionNotFoundException(loanTransaction.getId(), e);
}
}
private AmortizationAllocationBaseTransactionDTO getBaseTransactionInfo(final LoanTransaction loanTransaction, final Long loanId) {
if (loanTransaction.isBuyDownFee()) {
return buyDownFeeBalanceRepository.findBaseTransactionInfo(loanTransaction.getId(), loanId);
} else {
return capitalizedIncomeBalanceRepository.findBaseTransactionInfo(loanTransaction.getId(), loanId);
}
}
@Override
public LoanAmortizationAllocationMapping createAmortizationAllocationMappingWithBaseLoanTransaction(
final LoanTransaction loanTransaction, final BigDecimal amount, final AmortizationType amortizationType) {
return new LoanAmortizationAllocationMapping(loanTransaction.getLoan().getId(), loanTransaction.getId(), null, null,
amortizationType, amount);
}
@Override
public void setAmortizationTransactionDataAndSaveAmortizationAllocationMapping(
final LoanAmortizationAllocationMapping amortizationAllocationMapping, final LoanTransaction amortizationTransaction) {
final LoanAmortizationAllocationMapping updatedMapping = new LoanAmortizationAllocationMapping(
amortizationAllocationMapping.getLoanId(), amortizationAllocationMapping.getBaseLoanTransactionId(),
amortizationTransaction.getTransactionDate(), amortizationTransaction.getId(),
amortizationAllocationMapping.getAmortizationType(), amortizationAllocationMapping.getAmount());
loanAmortizationAllocationMappingRepository.save(updatedMapping);
}
}
@@ -20,6 +20,7 @@ package org.apache.fineract.portfolio.loanaccount.service;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
@@ -33,7 +34,10 @@ import org.apache.fineract.infrastructure.event.business.domain.loan.transaction
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanBuyDownFeeAmortizationTransactionCreatedBusinessEvent;
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.loanaccount.data.LoanAmortizationAllocationData;
import org.apache.fineract.portfolio.loanaccount.domain.AmortizationType;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanAmortizationAllocationMapping;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeBalance;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
@@ -54,34 +58,83 @@ public class LoanBuyDownFeeAmortizationProcessingServiceImpl implements LoanBuyD
private final BusinessEventNotifierService businessEventNotifierService;
private final LoanJournalEntryPoster journalEntryPoster;
private final ExternalIdFactory externalIdFactory;
private final LoanAmortizationAllocationService loanAmortizationAllocationService;
@Override
@Transactional
public void processBuyDownFeeAmortizationTillDate(@NonNull Loan loan, @NonNull LocalDate tillDate, boolean addJournal) {
List<LoanBuyDownFeeBalance> balances = loanBuyDownFeeBalanceRepository.findAllByLoanId(loan.getId());
final List<LoanBuyDownFeeBalance> balances = loanBuyDownFeeBalanceRepository.findAllByLoanIdAndClosedFalse(loan.getId());
LocalDate maturityDate = loan.getMaturityDate() != null ? loan.getMaturityDate()
final LocalDate maturityDate = loan.getMaturityDate() != null ? loan.getMaturityDate()
: getFinalBuyDownFeeAmortizationTransactionDate(loan);
LocalDate tillDatePlusOne = tillDate.plusDays(1);
if (tillDatePlusOne.isAfter(maturityDate)) {
tillDatePlusOne = maturityDate;
}
final List<LoanAmortizationAllocationMapping> loanAmortizationAllocationMappings = new ArrayList<>();
Money totalAmortization = Money.zero(loan.getCurrency());
final BigDecimal totalAmortized = loanTransactionRepository.getAmortizedAmountBuyDownFee(loan);
BigDecimal totalAmortizedAccumulator = totalAmortized;
for (LoanBuyDownFeeBalance balance : balances) {
List<LoanTransaction> adjustments = loanTransactionRepository.findAdjustments(balance.getLoanTransaction());
Money amortizationTillDate = BuyDownFeeAmortizationUtil.calculateTotalAmortizationTillDate(balance, adjustments, maturityDate,
loan.getLoanProductRelatedDetail().getBuyDownFeeStrategy(), tillDatePlusOne, loan.getCurrency());
totalAmortization = totalAmortization.add(amortizationTillDate);
balance.setUnrecognizedAmount(balance.getAmount().subtract(MathUtil.nullToZero(balance.getAmountAdjustment()))
.subtract(amortizationTillDate.getAmount()));
BigDecimal amortizationAmount;
AmortizationType amortizationType;
if (!balance.isDeleted()) {
final List<LoanTransaction> adjustments = loanTransactionRepository.findAdjustments(balance.getLoanTransaction());
final Money amortizationTillDate = BuyDownFeeAmortizationUtil.calculateTotalAmortizationTillDate(balance, adjustments,
maturityDate, loan.getLoanProductRelatedDetail().getBuyDownFeeStrategy(), tillDatePlusOne, loan.getCurrency());
totalAmortization = totalAmortization.add(amortizationTillDate);
if (!adjustments.isEmpty()) {
BigDecimal alreadyAmortizedAmount = balance.getAmount().subtract(MathUtil.nullToZero(balance.getAmountAdjustment()))
.subtract(MathUtil.nullToZero(balance.getChargedOffAmount())).subtract(balance.getUnrecognizedAmount());
if (alreadyAmortizedAmount.compareTo(BigDecimal.ZERO) == 0) {
final LoanAmortizationAllocationData loanAmortizationAllocationData = loanAmortizationAllocationService
.retrieveLoanAmortizationAllocationsForBuyDownFeeTransaction(balance.getLoanTransaction().getId(),
loan.getId());
BigDecimal amortizedAmount = BigDecimal.ZERO;
for (LoanAmortizationAllocationData.AmortizationMappingData loanAmortizationMapping : loanAmortizationAllocationData
.getAmortizationMappings()) {
if (AmortizationType.AM.equals(loanAmortizationMapping.getType())) {
amortizedAmount = amortizedAmount.add(loanAmortizationMapping.getAmount());
} else if (AmortizationType.AM_ADJ.equals(loanAmortizationMapping.getType())) {
amortizedAmount = amortizedAmount.subtract(loanAmortizationMapping.getAmount());
}
}
amortizationAmount = amortizedAmount;
amortizationType = AmortizationType.AM_ADJ;
totalAmortizedAccumulator = totalAmortizedAccumulator.subtract(amortizationAmount);
} else if (alreadyAmortizedAmount.compareTo(amortizationTillDate.getAmount()) > 0) {
amortizationAmount = alreadyAmortizedAmount.subtract(amortizationTillDate.getAmount());
amortizationType = AmortizationType.AM_ADJ;
totalAmortizedAccumulator = totalAmortizedAccumulator.subtract(amortizationAmount);
} else {
amortizationAmount = amortizationTillDate.getAmount().subtract(alreadyAmortizedAmount);
amortizationType = AmortizationType.AM;
totalAmortizedAccumulator = totalAmortizedAccumulator.add(amortizationAmount);
}
} else {
amortizationAmount = totalAmortization.getAmount().subtract(totalAmortizedAccumulator);
amortizationType = AmortizationType.AM;
totalAmortizedAccumulator = totalAmortizedAccumulator.add(amortizationAmount);
}
balance.setUnrecognizedAmount(balance.getAmount().subtract(MathUtil.nullToZero(balance.getAmountAdjustment()))
.subtract(amortizationTillDate.getAmount()));
} else {
amortizationAmount = balance.getAmount().subtract(balance.getUnrecognizedAmount());
amortizationType = AmortizationType.AM_ADJ;
balance.setClosed(true);
totalAmortizedAccumulator = totalAmortizedAccumulator.subtract(amortizationAmount);
}
if (amortizationAmount.compareTo(BigDecimal.ZERO) > 0) {
final LoanAmortizationAllocationMapping loanAmortizationAllocationMapping = loanAmortizationAllocationService
.createAmortizationAllocationMappingWithBaseLoanTransaction(balance.getLoanTransaction(), amortizationAmount,
amortizationType);
loanAmortizationAllocationMappings.add(loanAmortizationAllocationMapping);
}
}
loanBuyDownFeeBalanceRepository.saveAll(balances);
BigDecimal totalAmortized = loanTransactionRepository.getAmortizedAmountBuyDownFee(loan);
BigDecimal totalAmortizationAmount = totalAmortization.getAmount().subtract(totalAmortized);
final BigDecimal totalAmortizationAmount = totalAmortization.getAmount().subtract(totalAmortized);
if (!MathUtil.isZero(totalAmortizationAmount)) {
LoanTransaction transaction = MathUtil.isGreaterThanZero(totalAmortizationAmount)
@@ -91,14 +144,17 @@ public class LoanBuyDownFeeAmortizationProcessingServiceImpl implements LoanBuyD
Money.of(loan.getCurrency(), MathUtil.negate(totalAmortizationAmount)), tillDate, externalIdFactory.create());
loan.addLoanTransaction(transaction);
transaction = loanTransactionRepository.save(transaction);
loanTransactionRepository.flush();
transaction = loanTransactionRepository.saveAndFlush(transaction);
final LoanTransaction finalTransaction = transaction;
loanAmortizationAllocationMappings.forEach(loanAmortizationAllocationMapping -> loanAmortizationAllocationService
.setAmortizationTransactionDataAndSaveAmortizationAllocationMapping(loanAmortizationAllocationMapping,
finalTransaction));
if (addJournal) {
journalEntryPoster.postJournalEntriesForLoanTransaction(transaction, false, false);
}
BusinessEvent<?> event = MathUtil.isGreaterThanZero(totalAmortizationAmount)
final BusinessEvent<?> event = MathUtil.isGreaterThanZero(totalAmortizationAmount)
? new LoanBuyDownFeeAmortizationTransactionCreatedBusinessEvent(transaction)
: new LoanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent(transaction);
businessEventNotifierService.notifyPostBusinessEvent(event);
@@ -165,7 +221,7 @@ public class LoanBuyDownFeeAmortizationProcessingServiceImpl implements LoanBuyD
businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(data));
});
for (LoanBuyDownFeeBalance balance : loanBuyDownFeeBalanceRepository.findAllByLoanId(loan.getId())) {
for (LoanBuyDownFeeBalance balance : loanBuyDownFeeBalanceRepository.findAllByLoanIdAndDeletedFalseAndClosedFalse(loan.getId())) {
balance.setUnrecognizedAmount(balance.getChargedOffAmount());
balance.setChargedOffAmount(BigDecimal.ZERO);
}
@@ -173,25 +229,75 @@ public class LoanBuyDownFeeAmortizationProcessingServiceImpl implements LoanBuyD
private Optional<LoanTransaction> createBuyDownFeeAmortizationTransaction(final Loan loan, final LocalDate transactionDate,
final boolean isChargeOff, final LoanTransaction chargeOffTransaction) {
ExternalId externalId = externalIdFactory.create();
final ExternalId externalId = externalIdFactory.create();
final List<LoanBuyDownFeeBalance> balances = loanBuyDownFeeBalanceRepository.findAllByLoanId(loan.getId());
final List<LoanBuyDownFeeBalance> balances = loanBuyDownFeeBalanceRepository.findAllByLoanIdAndClosedFalse(loan.getId());
final List<LoanAmortizationAllocationMapping> loanAmortizationAllocationMappings = new ArrayList<>();
BigDecimal totalAmortizationAmount = BigDecimal.ZERO;
BigDecimal totalAmortization = BigDecimal.ZERO;
final BigDecimal totalAmortized = loanTransactionRepository.getAmortizedAmountBuyDownFee(loan);
BigDecimal totalAmortizedAccumulator = totalAmortized;
for (LoanBuyDownFeeBalance balance : balances) {
List<LoanTransaction> adjustments = loanTransactionRepository.findAdjustments(balance.getLoanTransaction());
LocalDate maturityDate = loan.getMaturityDate() != null ? loan.getMaturityDate() : transactionDate;
final Money amortizationTillDate = BuyDownFeeAmortizationUtil.calculateTotalAmortizationTillDate(balance, adjustments,
maturityDate, loan.getLoanProductRelatedDetail().getBuyDownFeeStrategy(), maturityDate, loan.getCurrency());
totalAmortizationAmount = totalAmortizationAmount.add(amortizationTillDate.getAmount());
if (isChargeOff) {
balance.setChargedOffAmount(balance.getUnrecognizedAmount());
BigDecimal amortizationAmount;
AmortizationType amortizationType;
if (!balance.isDeleted()) {
final List<LoanTransaction> adjustments = loanTransactionRepository.findAdjustments(balance.getLoanTransaction());
final LocalDate maturityDate = loan.getMaturityDate() != null ? loan.getMaturityDate() : transactionDate;
final Money amortizationTillDate = BuyDownFeeAmortizationUtil.calculateTotalAmortizationTillDate(balance, adjustments,
maturityDate, loan.getLoanProductRelatedDetail().getBuyDownFeeStrategy(), maturityDate, loan.getCurrency());
totalAmortization = totalAmortization.add(amortizationTillDate.getAmount());
if (!adjustments.isEmpty()) {
BigDecimal alreadyAmortizedAmount = balance.getAmount().subtract(MathUtil.nullToZero(balance.getAmountAdjustment()))
.subtract(MathUtil.nullToZero(balance.getChargedOffAmount())).subtract(balance.getUnrecognizedAmount());
if (alreadyAmortizedAmount.compareTo(BigDecimal.ZERO) == 0) {
final LoanAmortizationAllocationData loanAmortizationAllocationData = loanAmortizationAllocationService
.retrieveLoanAmortizationAllocationsForBuyDownFeeTransaction(balance.getLoanTransaction().getId(),
loan.getId());
BigDecimal amortizedAmount = BigDecimal.ZERO;
for (LoanAmortizationAllocationData.AmortizationMappingData loanAmortizationMapping : loanAmortizationAllocationData
.getAmortizationMappings()) {
if (AmortizationType.AM.equals(loanAmortizationMapping.getType())) {
amortizedAmount = amortizedAmount.add(loanAmortizationMapping.getAmount());
} else if (AmortizationType.AM_ADJ.equals(loanAmortizationMapping.getType())) {
amortizedAmount = amortizedAmount.subtract(loanAmortizationMapping.getAmount());
}
}
amortizationAmount = amortizedAmount;
amortizationType = AmortizationType.AM_ADJ;
totalAmortizedAccumulator = totalAmortizedAccumulator.subtract(amortizationAmount);
} else if (alreadyAmortizedAmount.compareTo(amortizationTillDate.getAmount()) > 0) {
amortizationAmount = alreadyAmortizedAmount.subtract(amortizationTillDate.getAmount());
amortizationType = AmortizationType.AM_ADJ;
totalAmortizedAccumulator = totalAmortizedAccumulator.subtract(amortizationAmount);
} else {
amortizationAmount = amortizationTillDate.getAmount().subtract(alreadyAmortizedAmount);
amortizationType = AmortizationType.AM;
totalAmortizedAccumulator = totalAmortizedAccumulator.add(amortizationAmount);
}
} else {
amortizationAmount = totalAmortization.subtract(totalAmortizedAccumulator);
amortizationType = AmortizationType.AM;
totalAmortizedAccumulator = totalAmortizedAccumulator.add(amortizationAmount);
}
if (isChargeOff) {
balance.setChargedOffAmount(balance.getUnrecognizedAmount());
}
balance.setUnrecognizedAmount(BigDecimal.ZERO);
} else {
amortizationAmount = balance.getAmount().subtract(balance.getUnrecognizedAmount());
amortizationType = AmortizationType.AM_ADJ;
balance.setClosed(true);
totalAmortizedAccumulator = totalAmortizedAccumulator.subtract(amortizationAmount);
}
if (amortizationAmount.compareTo(BigDecimal.ZERO) > 0) {
final LoanAmortizationAllocationMapping loanAmortizationAllocationMapping = loanAmortizationAllocationService
.createAmortizationAllocationMappingWithBaseLoanTransaction(balance.getLoanTransaction(), amortizationAmount,
amortizationType);
loanAmortizationAllocationMappings.add(loanAmortizationAllocationMapping);
}
balance.setUnrecognizedAmount(BigDecimal.ZERO);
}
BigDecimal amortizedAmount = loanTransactionRepository.getAmortizedAmountBuyDownFee(loan);
BigDecimal totalUnrecognizedAmount = totalAmortizationAmount.subtract(amortizedAmount);
final BigDecimal totalUnrecognizedAmount = totalAmortization.subtract(totalAmortized);
if (MathUtil.isZero(totalUnrecognizedAmount)) {
return Optional.empty();
}
@@ -207,6 +313,9 @@ public class LoanBuyDownFeeAmortizationProcessingServiceImpl implements LoanBuyD
loan.addLoanTransaction(amortizationTransaction);
loanTransactionRepository.saveAndFlush(amortizationTransaction);
loanAmortizationAllocationMappings.forEach(loanAmortizationAllocationMapping -> loanAmortizationAllocationService
.setAmortizationTransactionDataAndSaveAmortizationAllocationMapping(loanAmortizationAllocationMapping,
amortizationTransaction));
return Optional.of(amortizationTransaction);
}
@@ -20,6 +20,7 @@ package org.apache.fineract.portfolio.loanaccount.service;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
@@ -34,7 +35,10 @@ import org.apache.fineract.infrastructure.event.business.domain.loan.transaction
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent;
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.loanaccount.data.LoanAmortizationAllocationData;
import org.apache.fineract.portfolio.loanaccount.domain.AmortizationType;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanAmortizationAllocationMapping;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeBalance;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
@@ -56,6 +60,7 @@ public class LoanCapitalizedIncomeAmortizationProcessingServiceImpl implements L
private final BusinessEventNotifierService businessEventNotifierService;
private final LoanJournalEntryPoster journalEntryPoster;
private final ExternalIdFactory externalIdFactory;
private final LoanAmortizationAllocationService loanAmortizationAllocationService;
@Override
@Transactional
@@ -108,23 +113,75 @@ public class LoanCapitalizedIncomeAmortizationProcessingServiceImpl implements L
externalId = ExternalId.generate();
}
final List<LoanCapitalizedIncomeBalance> balances = loanCapitalizedIncomeBalanceRepository.findAllByLoanId(loan.getId());
final List<LoanCapitalizedIncomeBalance> balances = loanCapitalizedIncomeBalanceRepository
.findAllByLoanIdAndClosedFalse(loan.getId());
final List<LoanAmortizationAllocationMapping> loanAmortizationAllocationMappings = new ArrayList<>();
BigDecimal totalAmortizationAmount = BigDecimal.ZERO;
BigDecimal totalAmortization = BigDecimal.ZERO;
final BigDecimal totalAmortized = loanTransactionRepository.getAmortizedAmountCapitalizedIncome(loan);
BigDecimal totalAmortizedAccumulator = totalAmortized;
for (LoanCapitalizedIncomeBalance balance : balances) {
List<LoanTransaction> adjustments = loanTransactionRepository.findAdjustments(balance.getLoanTransaction());
LocalDate maturityDate = loan.getMaturityDate() != null ? loan.getMaturityDate() : transactionDate;
final Money amortizationTillDate = CapitalizedIncomeAmortizationUtil.calculateTotalAmortizationTillDate(balance, adjustments,
maturityDate, loan.getLoanProductRelatedDetail().getCapitalizedIncomeStrategy(), maturityDate, loan.getCurrency());
totalAmortizationAmount = totalAmortizationAmount.add(amortizationTillDate.getAmount());
if (isChargeOff) {
balance.setChargedOffAmount(balance.getUnrecognizedAmount());
BigDecimal amortizationAmount;
AmortizationType amortizationType;
if (!balance.isDeleted()) {
final List<LoanTransaction> adjustments = loanTransactionRepository.findAdjustments(balance.getLoanTransaction());
final LocalDate maturityDate = loan.getMaturityDate() != null ? loan.getMaturityDate() : transactionDate;
final Money amortizationTillDate = CapitalizedIncomeAmortizationUtil.calculateTotalAmortizationTillDate(balance,
adjustments, maturityDate, loan.getLoanProductRelatedDetail().getCapitalizedIncomeStrategy(), maturityDate,
loan.getCurrency());
totalAmortization = totalAmortization.add(amortizationTillDate.getAmount());
if (!adjustments.isEmpty()) {
BigDecimal alreadyAmortizedAmount = balance.getAmount().subtract(MathUtil.nullToZero(balance.getAmountAdjustment()))
.subtract(MathUtil.nullToZero(balance.getChargedOffAmount())).subtract(balance.getUnrecognizedAmount());
if (alreadyAmortizedAmount.compareTo(BigDecimal.ZERO) == 0) {
final LoanAmortizationAllocationData loanAmortizationAllocationData = loanAmortizationAllocationService
.retrieveLoanAmortizationAllocationsForCapitalizedIncomeTransaction(balance.getLoanTransaction().getId(),
loan.getId());
BigDecimal amortizedAmount = BigDecimal.ZERO;
for (LoanAmortizationAllocationData.AmortizationMappingData loanAmortizationMapping : loanAmortizationAllocationData
.getAmortizationMappings()) {
if (AmortizationType.AM.equals(loanAmortizationMapping.getType())) {
amortizedAmount = amortizedAmount.add(loanAmortizationMapping.getAmount());
} else if (AmortizationType.AM_ADJ.equals(loanAmortizationMapping.getType())) {
amortizedAmount = amortizedAmount.subtract(loanAmortizationMapping.getAmount());
}
}
amortizationAmount = amortizedAmount;
amortizationType = AmortizationType.AM_ADJ;
totalAmortizedAccumulator = totalAmortizedAccumulator.subtract(amortizationAmount);
} else if (alreadyAmortizedAmount.compareTo(amortizationTillDate.getAmount()) > 0) {
amortizationAmount = alreadyAmortizedAmount.subtract(amortizationTillDate.getAmount());
amortizationType = AmortizationType.AM_ADJ;
totalAmortizedAccumulator = totalAmortizedAccumulator.subtract(amortizationAmount);
} else {
amortizationAmount = amortizationTillDate.getAmount().subtract(alreadyAmortizedAmount);
amortizationType = AmortizationType.AM;
totalAmortizedAccumulator = totalAmortizedAccumulator.add(amortizationAmount);
}
} else {
amortizationAmount = totalAmortization.subtract(totalAmortizedAccumulator);
amortizationType = AmortizationType.AM;
totalAmortizedAccumulator = totalAmortizedAccumulator.add(amortizationAmount);
}
if (isChargeOff) {
balance.setChargedOffAmount(balance.getUnrecognizedAmount());
}
balance.setUnrecognizedAmount(BigDecimal.ZERO);
} else {
amortizationAmount = balance.getAmount().subtract(balance.getUnrecognizedAmount());
amortizationType = AmortizationType.AM_ADJ;
balance.setClosed(true);
totalAmortizedAccumulator = totalAmortizedAccumulator.subtract(amortizationAmount);
}
if (amortizationAmount.compareTo(BigDecimal.ZERO) > 0) {
final LoanAmortizationAllocationMapping loanAmortizationAllocationMapping = loanAmortizationAllocationService
.createAmortizationAllocationMappingWithBaseLoanTransaction(balance.getLoanTransaction(), amortizationAmount,
amortizationType);
loanAmortizationAllocationMappings.add(loanAmortizationAllocationMapping);
}
balance.setUnrecognizedAmount(BigDecimal.ZERO);
}
BigDecimal amortizedAmount = loanTransactionRepository.getAmortizedAmountCapitalizedIncome(loan);
BigDecimal totalUnrecognizedAmount = totalAmortizationAmount.subtract(amortizedAmount);
final BigDecimal totalUnrecognizedAmount = totalAmortization.subtract(totalAmortized);
if (MathUtil.isZero(totalUnrecognizedAmount)) {
return Optional.empty();
}
@@ -141,6 +198,9 @@ public class LoanCapitalizedIncomeAmortizationProcessingServiceImpl implements L
loan.addLoanTransaction(amortizationTransaction);
loanTransactionRepository.saveAndFlush(amortizationTransaction);
loanAmortizationAllocationMappings.forEach(loanAmortizationAllocationMapping -> loanAmortizationAllocationService
.setAmortizationTransactionDataAndSaveAmortizationAllocationMapping(loanAmortizationAllocationMapping,
amortizationTransaction));
return Optional.of(amortizationTransaction);
}
@@ -162,7 +222,8 @@ public class LoanCapitalizedIncomeAmortizationProcessingServiceImpl implements L
journalEntryPoster.postJournalEntriesForLoanTransaction(transaction, false, false);
});
for (LoanCapitalizedIncomeBalance balance : loanCapitalizedIncomeBalanceRepository.findAllByLoanId(loan.getId())) {
for (LoanCapitalizedIncomeBalance balance : loanCapitalizedIncomeBalanceRepository
.findAllByLoanIdAndDeletedFalseAndClosedFalse(loan.getId())) {
balance.setUnrecognizedAmount(balance.getChargedOffAmount());
balance.setChargedOffAmount(BigDecimal.ZERO);
}
@@ -172,30 +233,80 @@ public class LoanCapitalizedIncomeAmortizationProcessingServiceImpl implements L
@Transactional
public void processCapitalizedIncomeAmortizationTillDate(@NonNull final Loan loan, @NonNull final LocalDate tillDate,
final boolean addJournal) {
List<LoanCapitalizedIncomeBalance> balances = loanCapitalizedIncomeBalanceRepository.findAllByLoanId(loan.getId());
final List<LoanCapitalizedIncomeBalance> balances = loanCapitalizedIncomeBalanceRepository
.findAllByLoanIdAndClosedFalse(loan.getId());
LocalDate maturityDate = loan.getMaturityDate() != null ? loan.getMaturityDate()
final LocalDate maturityDate = loan.getMaturityDate() != null ? loan.getMaturityDate()
: getFinalCapitalizedIncomeAmortizationTransactionDate(loan);
LocalDate tillDatePlusOne = tillDate.plusDays(1);
if (tillDatePlusOne.isAfter(maturityDate)) {
tillDatePlusOne = maturityDate;
}
final List<LoanAmortizationAllocationMapping> loanAmortizationAllocationMappings = new ArrayList<>();
Money totalAmortization = Money.zero(loan.getCurrency());
final BigDecimal totalAmortized = loanTransactionRepository.getAmortizedAmountCapitalizedIncome(loan);
BigDecimal totalAmortizedAccumulator = totalAmortized;
for (LoanCapitalizedIncomeBalance balance : balances) {
List<LoanTransaction> adjustments = loanTransactionRepository.findAdjustments(balance.getLoanTransaction());
Money amortizationTillDate = CapitalizedIncomeAmortizationUtil.calculateTotalAmortizationTillDate(balance, adjustments,
maturityDate, loan.getLoanProductRelatedDetail().getCapitalizedIncomeStrategy(), tillDatePlusOne, loan.getCurrency());
totalAmortization = totalAmortization.add(amortizationTillDate);
balance.setUnrecognizedAmount(balance.getAmount().subtract(MathUtil.nullToZero(balance.getAmountAdjustment()))
.subtract(amortizationTillDate.getAmount()));
BigDecimal amortizationAmount;
AmortizationType amortizationType;
if (!balance.isDeleted()) {
final List<LoanTransaction> adjustments = loanTransactionRepository.findAdjustments(balance.getLoanTransaction());
final Money amortizationTillDate = CapitalizedIncomeAmortizationUtil.calculateTotalAmortizationTillDate(balance,
adjustments, maturityDate, loan.getLoanProductRelatedDetail().getCapitalizedIncomeStrategy(), tillDatePlusOne,
loan.getCurrency());
totalAmortization = totalAmortization.add(amortizationTillDate);
if (!adjustments.isEmpty()) {
BigDecimal alreadyAmortizedAmount = balance.getAmount().subtract(MathUtil.nullToZero(balance.getAmountAdjustment()))
.subtract(MathUtil.nullToZero(balance.getChargedOffAmount())).subtract(balance.getUnrecognizedAmount());
if (alreadyAmortizedAmount.compareTo(BigDecimal.ZERO) == 0) {
final LoanAmortizationAllocationData loanAmortizationAllocationData = loanAmortizationAllocationService
.retrieveLoanAmortizationAllocationsForCapitalizedIncomeTransaction(balance.getLoanTransaction().getId(),
loan.getId());
BigDecimal amortizedAmount = BigDecimal.ZERO;
for (LoanAmortizationAllocationData.AmortizationMappingData loanAmortizationMapping : loanAmortizationAllocationData
.getAmortizationMappings()) {
if (AmortizationType.AM.equals(loanAmortizationMapping.getType())) {
amortizedAmount = amortizedAmount.add(loanAmortizationMapping.getAmount());
} else if (AmortizationType.AM_ADJ.equals(loanAmortizationMapping.getType())) {
amortizedAmount = amortizedAmount.subtract(loanAmortizationMapping.getAmount());
}
}
amortizationAmount = amortizedAmount;
amortizationType = AmortizationType.AM_ADJ;
totalAmortizedAccumulator = totalAmortizedAccumulator.subtract(amortizationAmount);
} else if (alreadyAmortizedAmount.compareTo(amortizationTillDate.getAmount()) > 0) {
amortizationAmount = alreadyAmortizedAmount.subtract(amortizationTillDate.getAmount());
amortizationType = AmortizationType.AM_ADJ;
totalAmortizedAccumulator = totalAmortizedAccumulator.subtract(amortizationAmount);
} else {
amortizationAmount = amortizationTillDate.getAmount().subtract(alreadyAmortizedAmount);
amortizationType = AmortizationType.AM;
totalAmortizedAccumulator = totalAmortizedAccumulator.add(amortizationAmount);
}
} else {
amortizationAmount = totalAmortization.getAmount().subtract(totalAmortizedAccumulator);
amortizationType = AmortizationType.AM;
totalAmortizedAccumulator = totalAmortizedAccumulator.add(amortizationAmount);
}
balance.setUnrecognizedAmount(balance.getAmount().subtract(MathUtil.nullToZero(balance.getAmountAdjustment()))
.subtract(amortizationTillDate.getAmount()));
} else {
amortizationAmount = balance.getAmount().subtract(balance.getUnrecognizedAmount());
amortizationType = AmortizationType.AM_ADJ;
balance.setClosed(true);
totalAmortizedAccumulator = totalAmortizedAccumulator.subtract(amortizationAmount);
}
if (amortizationAmount.compareTo(BigDecimal.ZERO) > 0) {
final LoanAmortizationAllocationMapping loanAmortizationAllocationMapping = loanAmortizationAllocationService
.createAmortizationAllocationMappingWithBaseLoanTransaction(balance.getLoanTransaction(), amortizationAmount,
amortizationType);
loanAmortizationAllocationMappings.add(loanAmortizationAllocationMapping);
}
}
loanCapitalizedIncomeBalanceRepository.saveAll(balances);
BigDecimal totalAmortized = loanTransactionRepository.getAmortizedAmountCapitalizedIncome(loan);
BigDecimal totalAmortizationAmount = totalAmortization.getAmount().subtract(totalAmortized);
final BigDecimal totalAmortizationAmount = totalAmortization.getAmount().subtract(totalAmortized);
if (!MathUtil.isZero(totalAmortizationAmount)) {
LoanTransaction transaction = MathUtil.isGreaterThanZero(totalAmortizationAmount)
@@ -205,14 +316,17 @@ public class LoanCapitalizedIncomeAmortizationProcessingServiceImpl implements L
Money.of(loan.getCurrency(), MathUtil.negate(totalAmortizationAmount)), tillDate, externalIdFactory.create());
loan.addLoanTransaction(transaction);
transaction = loanTransactionRepository.save(transaction);
loanTransactionRepository.flush();
transaction = loanTransactionRepository.saveAndFlush(transaction);
final LoanTransaction finalTransaction = transaction;
loanAmortizationAllocationMappings.forEach(loanAmortizationAllocationMapping -> loanAmortizationAllocationService
.setAmortizationTransactionDataAndSaveAmortizationAllocationMapping(loanAmortizationAllocationMapping,
finalTransaction));
if (addJournal) {
journalEntryPoster.postJournalEntriesForLoanTransaction(transaction, false, false);
}
BusinessEvent<?> event = MathUtil.isGreaterThanZero(totalAmortizationAmount)
final BusinessEvent<?> event = MathUtil.isGreaterThanZero(totalAmortizationAmount)
? new LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent(transaction)
: new LoanCapitalizedIncomeAmortizationAdjustmentTransactionCreatedBusinessEvent(transaction);
businessEventNotifierService.notifyPostBusinessEvent(event);
@@ -493,8 +493,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
final Loan loan = loanRepositoryWrapper.findOneWithNotFoundDetection(loanId);
BigDecimal capitalizedIncomeBalance = BigDecimal.ZERO;
if (loan.getLoanProduct().getLoanProductRelatedDetail().isEnableIncomeCapitalization()) {
capitalizedIncomeBalance = loanCapitalizedIncomeBalanceRepository.findAllByLoanId(loanId).stream()
.map(LoanCapitalizedIncomeBalance::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
capitalizedIncomeBalance = loanCapitalizedIncomeBalanceRepository.findAllByLoanIdAndDeletedFalseAndClosedFalse(loanId)
.stream().map(LoanCapitalizedIncomeBalance::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
}
transactionAmount = loan.getLoanProduct().isAllowApprovedDisbursedAmountsOverApplied()
? loanMaximumAmountCalculator.getOverAppliedMax(loan)
@@ -517,7 +517,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
break;
case CAPITALIZED_INCOME_ADJUSTMENT:
final LoanCapitalizedIncomeBalance loanCapitalizedIncomeBalance = loanCapitalizedIncomeBalanceRepository
.findByLoanIdAndLoanTransactionId(loanId, transactionId);
.findByLoanIdAndLoanTransactionIdAndDeletedFalseAndClosedFalse(loanId, transactionId);
transactionAmount = (loanCapitalizedIncomeBalance == null) ? BigDecimal.ZERO
: loanCapitalizedIncomeBalance.getAmount()
@@ -527,8 +527,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
paymentOptions, retriveLoanCurrencyData(loanId), classificationOptions);
break;
case BUY_DOWN_FEE_ADJUSTMENT:
final LoanBuyDownFeeBalance loanBuyDownFeeBalance = loanBuyDownFeeBalanceRepository.findByLoanIdAndLoanTransactionId(loanId,
transactionId);
final LoanBuyDownFeeBalance loanBuyDownFeeBalance = loanBuyDownFeeBalanceRepository
.findByLoanIdAndLoanTransactionIdAndDeletedFalseAndClosedFalse(loanId, transactionId);
transactionAmount = (loanBuyDownFeeBalance == null) ? BigDecimal.ZERO
: loanBuyDownFeeBalance.getAmount().subtract(MathUtil.nullToZero(loanBuyDownFeeBalance.getAmountAdjustment()));
@@ -2522,6 +2522,19 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
return resolvedLoanId;
}
@Override
public Long getResolvedLoanTransactionId(final Long transactionId, final ExternalId externalTransactionId) {
Long resolvedLoanTransactionId = transactionId;
if (resolvedLoanTransactionId == null) {
externalTransactionId.throwExceptionIfEmpty();
resolvedLoanTransactionId = retrieveLoanTransactionIdByExternalId(externalTransactionId);
if (resolvedLoanTransactionId == null) {
throw new LoanTransactionNotFoundException(externalTransactionId);
}
}
return resolvedLoanTransactionId;
}
private LoanTransaction deriveDefaultInterestWaiverTransaction(final Loan loan) {
final Money totalInterestOutstanding = loan.getTotalInterestOutstandingOnLoan();
Money possibleInterestToWaive = totalInterestOutstanding.copy();
@@ -147,12 +147,13 @@ public class LoanAdjustmentServiceImpl implements LoanAdjustmentService {
}
LoanCapitalizedIncomeBalance capitalizedIncomeBalance = loanCapitalizedIncomeBalanceRepository
.findByLoanIdAndLoanTransactionId(loan.getId(), transactionToAdjust.getId());
.findByLoanIdAndLoanTransactionIdAndDeletedFalseAndClosedFalse(loan.getId(), transactionToAdjust.getId());
if (MathUtil.isGreaterThanZero(capitalizedIncomeBalance.getAmountAdjustment())) {
throw new InvalidLoanTransactionTypeException("transaction", "capitalizedIncome.cannot.be.reversed.when.adjusted",
"Capitalized income transaction cannot be reversed when non-reversed adjustment exists for it.");
}
loanCapitalizedIncomeBalanceRepository.delete(capitalizedIncomeBalance);
capitalizedIncomeBalance.setDeleted(true);
loanCapitalizedIncomeBalanceRepository.saveAndFlush(capitalizedIncomeBalance);
}
if (transactionToAdjust.isCapitalizedIncomeAdjustment()) {
if (newTransactionDetail.isNotZero()) {
@@ -174,14 +175,15 @@ public class LoanAdjustmentServiceImpl implements LoanAdjustmentService {
"Buy down fee transaction cannot be adjusted");
}
LoanBuyDownFeeBalance buyDownFeeBalance = loanBuyDownFeeBalanceRepository.findByLoanIdAndLoanTransactionId(loan.getId(),
transactionToAdjust.getId());
LoanBuyDownFeeBalance buyDownFeeBalance = loanBuyDownFeeBalanceRepository
.findByLoanIdAndLoanTransactionIdAndDeletedFalseAndClosedFalse(loan.getId(), transactionToAdjust.getId());
if (MathUtil.isGreaterThanZero(buyDownFeeBalance.getAmountAdjustment())) {
throw new InvalidLoanTransactionTypeException("transaction", "buy.down.fee.cannot.be.reversed.when.adjusted",
"Buy down fee transaction cannot be reversed when non-reversed adjustment exists for it.");
}
loanBuyDownFeeBalanceRepository.delete(buyDownFeeBalance);
buyDownFeeBalance.setDeleted(true);
loanBuyDownFeeBalanceRepository.saveAndFlush(buyDownFeeBalance);
}
if (transactionToAdjust.isBuyDownFeeAdjustment()) {
if (newTransactionDetail.isNotZero()) {
@@ -70,6 +70,7 @@ import org.apache.fineract.portfolio.interestpauses.service.InterestPauseWritePl
import org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService;
import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountService;
import org.apache.fineract.portfolio.loanaccount.domain.LoanAmortizationAllocationMappingRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallmentRepository;
@@ -118,6 +119,8 @@ import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualEventService
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualTransactionBusinessEventService;
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualTransactionBusinessEventServiceImpl;
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualsProcessingService;
import org.apache.fineract.portfolio.loanaccount.service.LoanAmortizationAllocationService;
import org.apache.fineract.portfolio.loanaccount.service.LoanAmortizationAllocationServiceImpl;
import org.apache.fineract.portfolio.loanaccount.service.LoanApplicationWritePlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanApplicationWritePlatformServiceJpaRepositoryImpl;
import org.apache.fineract.portfolio.loanaccount.service.LoanArrearsAgingService;
@@ -566,9 +569,10 @@ public class LoanAccountConfiguration {
final ConfigurationDomainService configurationDomainService, final LoanTransactionRepository loanTransactionRepository,
final LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository,
final BusinessEventNotifierService businessEventNotifierService, final LoanJournalEntryPoster journalEntryPoster,
final ExternalIdFactory externalIdFactory) {
final ExternalIdFactory externalIdFactory, final LoanAmortizationAllocationService loanAmortizationAllocationService) {
return new LoanCapitalizedIncomeAmortizationProcessingServiceImpl(configurationDomainService, loanTransactionRepository,
loanCapitalizedIncomeBalanceRepository, businessEventNotifierService, journalEntryPoster, externalIdFactory);
loanCapitalizedIncomeBalanceRepository, businessEventNotifierService, journalEntryPoster, externalIdFactory,
loanAmortizationAllocationService);
}
@Bean
@@ -577,9 +581,9 @@ public class LoanAccountConfiguration {
final LoanTransactionRepository loanTransactionRepository,
final LoanBuyDownFeeBalanceRepository loanBuyDownFeeBalanceRepository,
final BusinessEventNotifierService businessEventNotifierService, final LoanJournalEntryPoster journalEntryPoster,
final ExternalIdFactory externalIdFactory) {
final ExternalIdFactory externalIdFactory, final LoanAmortizationAllocationService loanAmortizationAllocationService) {
return new LoanBuyDownFeeAmortizationProcessingServiceImpl(loanTransactionRepository, loanBuyDownFeeBalanceRepository,
businessEventNotifierService, journalEntryPoster, externalIdFactory);
businessEventNotifierService, journalEntryPoster, externalIdFactory, loanAmortizationAllocationService);
}
@Bean
@@ -589,4 +593,15 @@ public class LoanAccountConfiguration {
LoanBuyDownFeeAmortizationProcessingService loanBuyDownFeeAmortizationProcessingService) {
return new LoanBuyDownFeeAmortizationEventService(businessEventNotifierService, loanBuyDownFeeAmortizationProcessingService);
}
@Bean
@ConditionalOnMissingBean(LoanAmortizationAllocationService.class)
public LoanAmortizationAllocationService loanAmortizationAllocationService(
final LoanAmortizationAllocationMappingRepository loanAmortizationAllocationMappingRepository,
final LoanTransactionRepository loanTransactionRepository,
final LoanCapitalizedIncomeBalanceRepository capitalizedIncomeBalanceRepository,
final LoanBuyDownFeeBalanceRepository buyDownFeeBalanceRepository) {
return new LoanAmortizationAllocationServiceImpl(loanAmortizationAllocationMappingRepository, loanTransactionRepository,
capitalizedIncomeBalanceRepository, buyDownFeeBalanceRepository);
}
}
@@ -213,4 +213,7 @@
<include file="parts/0192_create_loan_approved_amount_history.xml" relativeToChangelogFile="true" />
<include file="parts/0193_add_column_ip.xml" relativeToChangelogFile="true" />
<include file="parts/0194_fix_missing_permission.xml" relativeToChangelogFile="true" />
<include file="parts/0195_create_loan_amortization_allocation_mapping.xml" relativeToChangelogFile="true" />
<include file="parts/0196_add_deleted_and_closed_to_buy_down_fee_balance.xml" relativeToChangelogFile="true" />
<include file="parts/0197_add_deleted_and_closed_to_capitalized_income_balance.xml" relativeToChangelogFile="true" />
</databaseChangeLog>
@@ -0,0 +1,107 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.3.xsd">
<changeSet author="fineract" id="1" context="postgresql">
<createTable tableName="m_loan_amortization_allocation_mapping">
<column autoIncrement="true" name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true" primaryKeyName="pk_m_loan_amortization_allocation_mapping"/>
</column>
<column name="loan_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="base_loan_transaction_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="date" type="DATE">
<constraints nullable="false"/>
</column>
<column name="amortization_loan_transaction_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="amortization_type" type="VARCHAR(20)">
<constraints nullable="false"/>
</column>
<column name="amount" type="DECIMAL(19, 6)">
<constraints nullable="false"/>
</column>
<column name="created_by" type="BIGINT"/>
<column name="created_on_utc" type="timestamp with time zone"/>
<column name="last_modified_by" type="BIGINT"/>
<column name="last_modified_on_utc" type="timestamp with time zone"/>
</createTable>
</changeSet>
<changeSet author="fineract" id="2" context="mysql">
<createTable tableName="m_loan_amortization_allocation_mapping">
<column autoIncrement="true" name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true" primaryKeyName="pk_m_loan_amortization_allocation_mapping"/>
</column>
<column name="loan_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="base_loan_transaction_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="date" type="DATE">
<constraints nullable="false"/>
</column>
<column name="amortization_loan_transaction_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="amortization_type" type="VARCHAR(20)">
<constraints nullable="false"/>
</column>
<column name="amount" type="DECIMAL(19, 6)">
<constraints nullable="false"/>
</column>
<column name="created_by" type="BIGINT"/>
<column name="created_on_utc" type="DATETIME"/>
<column name="last_modified_by" type="BIGINT"/>
<column name="last_modified_on_utc" type="DATETIME"/>
</createTable>
</changeSet>
<changeSet author="fineract" id="3">
<addForeignKeyConstraint baseColumnNames="created_by" baseTableName="m_loan_amortization_allocation_mapping"
constraintName="FK_laam_created_by" deferrable="false" initiallyDeferred="false"
onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="id"
referencedTableName="m_appuser" validate="true"/>
<addForeignKeyConstraint baseColumnNames="last_modified_by" baseTableName="m_loan_amortization_allocation_mapping"
constraintName="FK_laam_last_modified_by" deferrable="false" initiallyDeferred="false"
onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="id"
referencedTableName="m_appuser" validate="true"/>
<addForeignKeyConstraint baseColumnNames="loan_id" baseTableName="m_loan_amortization_allocation_mapping"
constraintName="FK_laam_loan_id" deferrable="false" initiallyDeferred="false"
onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="id"
referencedTableName="m_loan" validate="true"/>
<addForeignKeyConstraint baseColumnNames="base_loan_transaction_id" baseTableName="m_loan_amortization_allocation_mapping"
constraintName="FK_laam_base_transaction_id" deferrable="false" initiallyDeferred="false"
onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="id"
referencedTableName="m_loan_transaction" validate="true"/>
<addForeignKeyConstraint baseColumnNames="amortization_loan_transaction_id" baseTableName="m_loan_amortization_allocation_mapping"
constraintName="FK_laam_amortization_transaction_id" deferrable="false" initiallyDeferred="false"
onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="id"
referencedTableName="m_loan_transaction" validate="true"/>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,34 @@
<!--
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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
<changeSet author="fineract" id="1">
<addColumn tableName="m_loan_buy_down_fee_balance">
<column defaultValueBoolean="false" name="is_deleted" type="boolean">
<constraints nullable="false"/>
</column>
<column defaultValueBoolean="false" name="is_closed" type="boolean">
<constraints nullable="false"/>
</column>
</addColumn>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,34 @@
<!--
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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
<changeSet author="fineract" id="1">
<addColumn tableName="m_loan_capitalized_income_balance">
<column defaultValueBoolean="false" name="is_deleted" type="boolean">
<constraints nullable="false"/>
</column>
<column defaultValueBoolean="false" name="is_closed" type="boolean">
<constraints nullable="false"/>
</column>
</addColumn>
</changeSet>
</databaseChangeLog>