FINERACT-2232: Capitalized income - Amortization rework
This commit is contained in:
+1
-1
@@ -1763,7 +1763,7 @@ public class LoanStepDef extends AbstractStepDef {
|
||||
final List<GetLoansLoanIdTransactions> transactions = loanDetailsResponse.body().getTransactions();
|
||||
final Optional<GetLoansLoanIdTransactions> transactionsMatch = transactions.stream()
|
||||
.filter(t -> formatter.format(t.getDate()).equals(transactionDate) && t.getType().getCapitalizedIncomeAmortization())
|
||||
.findFirst();
|
||||
.reduce((one, two) -> two);
|
||||
if (transactionsMatch.isPresent()) {
|
||||
testContext().set(TestContextKey.LOAN_CAPITALIZED_INCOME_AMORTIZATION_ID, transactionsMatch.get().getId());
|
||||
}
|
||||
|
||||
+253
-254
File diff suppressed because it is too large
Load Diff
+19
@@ -348,4 +348,23 @@ public interface LoanTransactionRepository extends JpaRepository<LoanTransaction
|
||||
List<LoanTransaction> findNonReversedByLoanAndType(@Param("loan") Loan loan, @Param("type") LoanTransactionType type,
|
||||
Pageable pageable);
|
||||
|
||||
@Query("""
|
||||
SELECT COALESCE(SUM(CASE WHEN lt.typeOf = org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION THEN lt.amount
|
||||
WHEN lt.typeOf = org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION_ADJUSTMENT THEN -lt.amount
|
||||
ELSE 0 END), 0) FROM LoanTransaction lt
|
||||
WHERE lt.loan = :loan
|
||||
AND lt.reversed = false
|
||||
AND (lt.typeOf = org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION
|
||||
OR lt.typeOf = org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION_ADJUSTMENT)
|
||||
""")
|
||||
BigDecimal getAmortizedAmount(@Param("loan") Loan loan);
|
||||
|
||||
@Query("""
|
||||
SELECT lt FROM LoanTransaction lt, LoanTransactionRelation ltr
|
||||
WHERE lt.reversed = false
|
||||
AND lt = ltr.fromTransaction
|
||||
AND ltr.toTransaction = :capitalizedIncome
|
||||
AND ltr.relationType = org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum.ADJUSTMENT
|
||||
""")
|
||||
List<LoanTransaction> findAdjustmentsForCapitalizedIncome(@Param("capitalizedIncome") LoanTransaction capitalizedIncome);
|
||||
}
|
||||
|
||||
+4
-1
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.service;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.springframework.lang.NonNull;
|
||||
@@ -26,7 +27,9 @@ public interface LoanCapitalizedIncomeAmortizationProcessingService {
|
||||
|
||||
void processCapitalizedIncomeAmortizationOnLoanClosure(@NonNull Loan loan);
|
||||
|
||||
void processCapitalizedIncomeAmortizationOnLoanChargeOff(@NonNull Loan loan);
|
||||
void processCapitalizedIncomeAmortizationOnLoanChargeOff(@NonNull Loan loan, @NonNull LoanTransaction chargeOffTransaction);
|
||||
|
||||
void processCapitalizedIncomeAmortizationOnLoanUndoChargeOff(@NonNull LoanTransaction loanTransaction);
|
||||
|
||||
void processCapitalizedIncomeAmortizationTillDate(@NonNull Loan loan, @NonNull LocalDate tillDate);
|
||||
}
|
||||
|
||||
-1
@@ -142,7 +142,6 @@ public class CapitalizedIncomeWritePlatformServiceImpl implements CapitalizedInc
|
||||
// Post journal entries
|
||||
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
|
||||
// Accounting uses the original balance, balance update HAS TO HAPPEN AFTER postJournalEntries
|
||||
LoanCapitalizedIncomeBalance capitalizedIncomeBalance = capitalizedIncomeBalanceRepository.findByLoanIdAndLoanTransactionId(loanId,
|
||||
capitalizedIncomeTransactionId);
|
||||
capitalizedIncomeBalance
|
||||
|
||||
+1
-46
@@ -21,12 +21,10 @@ package org.apache.fineract.accounting.journalentry.service;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.accounting.closure.domain.GLClosure;
|
||||
import org.apache.fineract.accounting.common.AccountingConstants.AccrualAccountsForLoan;
|
||||
@@ -36,19 +34,12 @@ import org.apache.fineract.accounting.journalentry.data.ChargePaymentDTO;
|
||||
import org.apache.fineract.accounting.journalentry.data.GLAccountBalanceHolder;
|
||||
import org.apache.fineract.accounting.journalentry.data.LoanDTO;
|
||||
import org.apache.fineract.accounting.journalentry.data.LoanTransactionDTO;
|
||||
import org.apache.fineract.accounting.journalentry.domain.JournalEntry;
|
||||
import org.apache.fineract.accounting.journalentry.domain.JournalEntryRepository;
|
||||
import org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMapping;
|
||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||
import org.apache.fineract.organisation.office.domain.Office;
|
||||
import org.apache.fineract.portfolio.PortfolioProductType;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionEnumData;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeBalance;
|
||||
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.repository.LoanCapitalizedIncomeBalanceRepository;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -246,46 +237,10 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
|
||||
}
|
||||
|
||||
// Resolve Debit
|
||||
GLAccount accountIncomeFromCapitalization = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
|
||||
AccrualAccountsForLoan.INCOME_FROM_CAPITALIZATION.getValue(), paymentTypeId);
|
||||
GLAccount accountDeferredIncome = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
|
||||
AccrualAccountsForLoan.DEFERRED_INCOME_LIABILITY.getValue(), paymentTypeId);
|
||||
|
||||
Optional<LoanTransaction> transactionOpt = loanTransactionRepository.findById(Long.parseLong(transactionId));
|
||||
if (transactionOpt.isEmpty()) {
|
||||
throw new PlatformDataIntegrityException("transaction.for.accounting.not.found", "Transaction for accounting not found");
|
||||
}
|
||||
Optional<LoanTransactionRelation> relationToOriginalTransactionOpt = transactionOpt.get().getLoanTransactionRelations().stream()//
|
||||
.filter(rel -> LoanTransactionRelationTypeEnum.REPLAYED.equals(rel.getRelationType()))//
|
||||
.min(Comparator.comparing(AbstractPersistableCustom::getId));
|
||||
if (relationToOriginalTransactionOpt.isPresent()) {
|
||||
// Reverse-replay case
|
||||
final LoanTransaction originalTransaction = relationToOriginalTransactionOpt.get().getToTransaction();
|
||||
final List<JournalEntry> originalJournalEntries = this.journalEntryRepository.findJournalEntries(
|
||||
AccountingProcessorHelper.LOAN_TRANSACTION_IDENTIFIER + originalTransaction.getId(),
|
||||
PortfolioProductType.LOAN.getValue());
|
||||
|
||||
for (JournalEntry originalJournalEntry : originalJournalEntries) {
|
||||
if (originalJournalEntry.isDebitEntry()) {
|
||||
if (accountIncomeFromCapitalization.getId().equals(originalJournalEntry.getGlAccount().getId())) {
|
||||
glAccountBalanceHolder.addToDebit(accountIncomeFromCapitalization, originalJournalEntry.getAmount());
|
||||
} else if (accountDeferredIncome.getId().equals(originalJournalEntry.getGlAccount().getId())) {
|
||||
glAccountBalanceHolder.addToDebit(accountDeferredIncome, originalJournalEntry.getAmount());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LoanCapitalizedIncomeBalance capitalizedIncomeBalance = loanCapitalizedIncomeBalanceRepository
|
||||
.findBalanceForAdjustment(Long.parseLong(loanTransactionDTO.getTransactionId()));
|
||||
if (MathUtil.isGreaterThan(transactionAmount, capitalizedIncomeBalance.getUnrecognizedAmount())) {
|
||||
BigDecimal amortizedAmount = transactionAmount.subtract(capitalizedIncomeBalance.getUnrecognizedAmount());
|
||||
|
||||
glAccountBalanceHolder.addToDebit(accountIncomeFromCapitalization, amortizedAmount);
|
||||
glAccountBalanceHolder.addToDebit(accountDeferredIncome, transactionAmount.subtract(amortizedAmount));
|
||||
} else {
|
||||
glAccountBalanceHolder.addToDebit(accountDeferredIncome, transactionAmount);
|
||||
}
|
||||
}
|
||||
glAccountBalanceHolder.addToDebit(accountDeferredIncome, transactionAmount);
|
||||
}
|
||||
|
||||
// create credit entries
|
||||
|
||||
+3
-61
@@ -19,30 +19,12 @@
|
||||
package org.apache.fineract.cob.loan;
|
||||
|
||||
import jakarta.transaction.Transactional;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.time.LocalDate;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
|
||||
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.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.organisation.monetary.domain.MoneyHelper;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.AccountingBridgeDataDTO;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.AccountingBridgeLoanTransactionDTO;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeBalance;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.mapper.LoanAccountingBridgeMapper;
|
||||
import org.apache.fineract.portfolio.loanaccount.repository.LoanCapitalizedIncomeBalanceRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.util.CapitalizedIncomeAmortizationUtil;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanCapitalizedIncomeAmortizationProcessingService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@@ -50,54 +32,14 @@ import org.springframework.stereotype.Component;
|
||||
@Slf4j
|
||||
public class CapitalizedIncomeAmortizationBusinessStep implements LoanCOBBusinessStep {
|
||||
|
||||
private final LoanCapitalizedIncomeBalanceRepository capitalizedIncomeBalanceRepository;
|
||||
private final BusinessEventNotifierService businessEventNotifierService;
|
||||
private final JournalEntryWritePlatformService journalEntryWritePlatformService;
|
||||
private final LoanAccountingBridgeMapper loanAccountingBridgeMapper;
|
||||
private final LoanTransactionRepository loanTransactionRepository;
|
||||
private final ExternalIdFactory externalIdFactory;
|
||||
private final LoanCapitalizedIncomeAmortizationProcessingService loanCapitalizedIncomeAmortizationProcessingService;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public Loan execute(Loan loan) {
|
||||
MathContext mc = MoneyHelper.getMathContext();
|
||||
LocalDate businessDate = DateUtils.getBusinessLocalDate();
|
||||
List<LoanCapitalizedIncomeBalance> balances = capitalizedIncomeBalanceRepository.findAllByLoanId(loan.getId());
|
||||
|
||||
BigDecimal totalAmortizationAmount = BigDecimal.ZERO;
|
||||
for (LoanCapitalizedIncomeBalance balance : balances) {
|
||||
if (DateUtils.isAfterInclusive(businessDate, balance.getDate())) {
|
||||
long daysUntilMaturity = businessDate.until(loan.getMaturityDate(), ChronoUnit.DAYS);
|
||||
Money dailyAmortization = CapitalizedIncomeAmortizationUtil.calculateDailyAmortization(
|
||||
loan.getLoanProductRelatedDetail().getCapitalizedIncomeStrategy(), daysUntilMaturity,
|
||||
balance.getUnrecognizedAmount(), loan.getCurrency().toData());
|
||||
totalAmortizationAmount = totalAmortizationAmount.add(dailyAmortization.getAmount(), mc);
|
||||
balance.setUnrecognizedAmount(balance.getUnrecognizedAmount().subtract(dailyAmortization.getAmount(), mc));
|
||||
}
|
||||
}
|
||||
|
||||
capitalizedIncomeBalanceRepository.saveAll(balances);
|
||||
|
||||
if (MathUtil.isGreaterThanZero(totalAmortizationAmount)) {
|
||||
LoanTransaction transaction = LoanTransaction.capitalizedIncomeAmortization(loan, loan.getOffice(), businessDate,
|
||||
totalAmortizationAmount, externalIdFactory.create());
|
||||
loan.addLoanTransaction(transaction);
|
||||
|
||||
transaction = loanTransactionRepository.save(transaction);
|
||||
loanTransactionRepository.flush();
|
||||
|
||||
final AccountingBridgeLoanTransactionDTO transactionDTO = loanAccountingBridgeMapper.mapToLoanTransactionData(transaction,
|
||||
loan.getCurrency().getCode());
|
||||
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, loan.isClosedWrittenOff(), List.of(transactionDTO));
|
||||
this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
|
||||
|
||||
businessEventNotifierService
|
||||
.notifyPostBusinessEvent(new LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent(transaction));
|
||||
}
|
||||
loanCapitalizedIncomeAmortizationProcessingService.processCapitalizedIncomeAmortizationTillDate(loan, businessDate);
|
||||
|
||||
return loan;
|
||||
}
|
||||
|
||||
+18
-1
@@ -21,10 +21,12 @@ package org.apache.fineract.portfolio.loanaccount.service;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.event.business.BusinessEventListener;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanBalanceChangedBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanCloseBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanChargeOffPostBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanChargeOffPreBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanUndoChargeOffBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
@@ -45,6 +47,7 @@ public class LoanCapitalizedIncomeAmortizationEventService {
|
||||
businessEventNotifierService.addPostBusinessEventListener(LoanChargeOffPostBusinessEvent.class, new LoanChargeOffEventListener());
|
||||
businessEventNotifierService.addPostBusinessEventListener(LoanUndoChargeOffBusinessEvent.class,
|
||||
new LoanUndoChargeOffEventListener());
|
||||
businessEventNotifierService.addPreBusinessEventListener(LoanChargeOffPreBusinessEvent.class, new LoanChargeOffPreEventListener());
|
||||
}
|
||||
|
||||
private final class LoanCloseListener implements BusinessEventListener<LoanCloseBusinessEvent> {
|
||||
@@ -83,7 +86,21 @@ public class LoanCapitalizedIncomeAmortizationEventService {
|
||||
final Loan loan = loanTransaction.getLoan();
|
||||
if (loan.getLoanProductRelatedDetail().isEnableIncomeCapitalization() && loan.isChargedOff() && loanTransaction.isChargeOff()) {
|
||||
log.debug("Loan charge-off on capitalized income amortization for loan {}", loan.getId());
|
||||
loanCapitalizedIncomeAmortizationProcessingService.processCapitalizedIncomeAmortizationOnLoanChargeOff(loan);
|
||||
loanCapitalizedIncomeAmortizationProcessingService.processCapitalizedIncomeAmortizationOnLoanChargeOff(loan,
|
||||
loanTransaction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class LoanChargeOffPreEventListener implements BusinessEventListener<LoanChargeOffPreBusinessEvent> {
|
||||
|
||||
@Override
|
||||
public void onBusinessEvent(final LoanChargeOffPreBusinessEvent event) {
|
||||
final Loan loan = event.get();
|
||||
if (loan.getLoanProductRelatedDetail().isEnableIncomeCapitalization()) {
|
||||
log.debug("Loan pre charge-off capitalized income amortization for loan {}", loan.getId());
|
||||
loanCapitalizedIncomeAmortizationProcessingService.processCapitalizedIncomeAmortizationTillDate(loan,
|
||||
DateUtils.getBusinessLocalDate());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+105
-16
@@ -26,16 +26,24 @@ import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
|
||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||
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.event.business.domain.BusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanAdjustTransactionBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanCapitalizedIncomeAmortizationAdjustmentTransactionCreatedBusinessEvent;
|
||||
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.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeBalance;
|
||||
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.repository.LoanCapitalizedIncomeBalanceRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.util.CapitalizedIncomeAmortizationUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@@ -48,20 +56,29 @@ public class LoanCapitalizedIncomeAmortizationProcessingServiceImpl implements L
|
||||
private final LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository;
|
||||
private final BusinessEventNotifierService businessEventNotifierService;
|
||||
private final LoanJournalEntryPoster journalEntryPoster;
|
||||
private final ExternalIdFactory externalIdFactory;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void processCapitalizedIncomeAmortizationOnLoanClosure(@NotNull final Loan loan) {
|
||||
final LocalDate transactionDate = getFinalCapitalizedIncomeAmortizationTransactionDate(loan);
|
||||
final Optional<LoanTransaction> amortizationTransaction = createCapitalizedIncomeAmortizationTransaction(loan, transactionDate,
|
||||
false);
|
||||
amortizationTransaction.ifPresent(loanTransaction -> businessEventNotifierService
|
||||
.notifyPostBusinessEvent(new LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent(loanTransaction)));
|
||||
false, null);
|
||||
amortizationTransaction.ifPresent(loanTransaction -> {
|
||||
if (loanTransaction.isCapitalizedIncomeAmortization()) {
|
||||
businessEventNotifierService
|
||||
.notifyPostBusinessEvent(new LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent(loanTransaction));
|
||||
} else {
|
||||
businessEventNotifierService.notifyPostBusinessEvent(
|
||||
new LoanCapitalizedIncomeAmortizationAdjustmentTransactionCreatedBusinessEvent(loanTransaction));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void processCapitalizedIncomeAmortizationOnLoanChargeOff(@NotNull final Loan loan) {
|
||||
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);
|
||||
|
||||
@@ -71,39 +88,57 @@ public class LoanCapitalizedIncomeAmortizationProcessingServiceImpl implements L
|
||||
}
|
||||
|
||||
final Optional<LoanTransaction> amortizationTransaction = createCapitalizedIncomeAmortizationTransaction(loan, transactionDate,
|
||||
true);
|
||||
true, chargeOffTransaction);
|
||||
if (amortizationTransaction.isPresent()) {
|
||||
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(
|
||||
new LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent(amortizationTransaction.get()));
|
||||
if (amortizationTransaction.get().isCapitalizedIncomeAmortization()) {
|
||||
businessEventNotifierService.notifyPostBusinessEvent(
|
||||
new LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent(amortizationTransaction.get()));
|
||||
} else {
|
||||
businessEventNotifierService.notifyPostBusinessEvent(
|
||||
new LoanCapitalizedIncomeAmortizationAdjustmentTransactionCreatedBusinessEvent(amortizationTransaction.get()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<LoanTransaction> createCapitalizedIncomeAmortizationTransaction(final Loan loan, final LocalDate transactionDate,
|
||||
final boolean isChargeOff) {
|
||||
final boolean isChargeOff, final LoanTransaction chargeOffTransaction) {
|
||||
ExternalId externalId = ExternalId.empty();
|
||||
BigDecimal totalUnrecognizedAmount = BigDecimal.ZERO;
|
||||
|
||||
if (configurationDomainService.isExternalIdAutoGenerationEnabled()) {
|
||||
externalId = ExternalId.generate();
|
||||
}
|
||||
|
||||
final List<LoanCapitalizedIncomeBalance> balances = loanCapitalizedIncomeBalanceRepository.findAllByLoanId(loan.getId());
|
||||
|
||||
BigDecimal totalAmortizationAmount = BigDecimal.ZERO;
|
||||
for (LoanCapitalizedIncomeBalance balance : balances) {
|
||||
final BigDecimal unrecognizedAmount = balance.getUnrecognizedAmount();
|
||||
totalUnrecognizedAmount = totalUnrecognizedAmount.add(unrecognizedAmount);
|
||||
List<LoanTransaction> adjustments = loanTransactionRepository.findAdjustmentsForCapitalizedIncome(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(unrecognizedAmount);
|
||||
balance.setChargedOffAmount(balance.getUnrecognizedAmount());
|
||||
}
|
||||
balance.setUnrecognizedAmount(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
if (MathUtil.isLessThanOrEqualZero(totalUnrecognizedAmount)) {
|
||||
BigDecimal amortizedAmount = loanTransactionRepository.getAmortizedAmount(loan);
|
||||
BigDecimal totalUnrecognizedAmount = totalAmortizationAmount.subtract(amortizedAmount);
|
||||
if (MathUtil.isZero(totalUnrecognizedAmount)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
final LoanTransaction amortizationTransaction = LoanTransaction.capitalizedIncomeAmortization(loan, loan.getOffice(),
|
||||
transactionDate, totalUnrecognizedAmount, externalId);
|
||||
final LoanTransaction amortizationTransaction = MathUtil.isGreaterThanZero(totalUnrecognizedAmount)
|
||||
? LoanTransaction.capitalizedIncomeAmortization(loan, loan.getOffice(), transactionDate, totalUnrecognizedAmount,
|
||||
externalId)
|
||||
: LoanTransaction.capitalizedIncomeAmortizationAdjustment(loan,
|
||||
Money.of(loan.getCurrency(), MathUtil.negate(totalUnrecognizedAmount)), transactionDate, externalId);
|
||||
if (isChargeOff) {
|
||||
amortizationTransaction.getLoanTransactionRelations().add(LoanTransactionRelation.linkToTransaction(amortizationTransaction,
|
||||
chargeOffTransaction, LoanTransactionRelationTypeEnum.RELATED));
|
||||
}
|
||||
|
||||
loan.addLoanTransaction(amortizationTransaction);
|
||||
loanTransactionRepository.saveAndFlush(amortizationTransaction);
|
||||
@@ -119,7 +154,10 @@ public class LoanCapitalizedIncomeAmortizationProcessingServiceImpl implements L
|
||||
final List<Long> existingReversedTransactionIds = loanTransactionRepository.findReversedTransactionIdsByLoan(loan);
|
||||
|
||||
loan.getLoanTransactions().stream().filter(LoanTransaction::isCapitalizedIncomeAmortization)
|
||||
.filter(transaction -> transaction.getTransactionDate().equals(loanTransaction.getTransactionDate()))
|
||||
.filter(transaction -> transaction.getTransactionDate().equals(loanTransaction.getTransactionDate())
|
||||
&& transaction.getLoanTransactionRelations().stream()
|
||||
.anyMatch(rel -> LoanTransactionRelationTypeEnum.RELATED.equals(rel.getRelationType())
|
||||
&& rel.getToTransaction().equals(loanTransaction)))
|
||||
.forEach(transaction -> {
|
||||
transaction.reverse();
|
||||
final LoanAdjustTransactionBusinessEvent.Data data = new LoanAdjustTransactionBusinessEvent.Data(transaction);
|
||||
@@ -135,6 +173,57 @@ public class LoanCapitalizedIncomeAmortizationProcessingServiceImpl implements L
|
||||
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void processCapitalizedIncomeAmortizationTillDate(@NonNull Loan loan, @NonNull LocalDate tillDate) {
|
||||
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()
|
||||
: getFinalCapitalizedIncomeAmortizationTransactionDate(loan);
|
||||
LocalDate tillDatePlusOne = tillDate.plusDays(1);
|
||||
if (tillDatePlusOne.isAfter(maturityDate)) {
|
||||
tillDatePlusOne = maturityDate;
|
||||
}
|
||||
|
||||
Money totalAmortization = Money.zero(loan.getCurrency());
|
||||
for (LoanCapitalizedIncomeBalance balance : balances) {
|
||||
List<LoanTransaction> adjustments = loanTransactionRepository.findAdjustmentsForCapitalizedIncome(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()));
|
||||
}
|
||||
|
||||
loanCapitalizedIncomeBalanceRepository.saveAll(balances);
|
||||
|
||||
BigDecimal totalAmortized = loanTransactionRepository.getAmortizedAmount(loan);
|
||||
BigDecimal totalAmortizationAmount = totalAmortization.getAmount().subtract(totalAmortized);
|
||||
|
||||
if (!MathUtil.isZero(totalAmortizationAmount)) {
|
||||
LoanTransaction transaction = MathUtil.isGreaterThanZero(totalAmortizationAmount)
|
||||
? LoanTransaction.capitalizedIncomeAmortization(loan, loan.getOffice(), tillDate, totalAmortizationAmount,
|
||||
externalIdFactory.create())
|
||||
: LoanTransaction.capitalizedIncomeAmortizationAdjustment(loan,
|
||||
Money.of(loan.getCurrency(), MathUtil.negate(totalAmortizationAmount)), tillDate, externalIdFactory.create());
|
||||
loan.addLoanTransaction(transaction);
|
||||
|
||||
transaction = loanTransactionRepository.save(transaction);
|
||||
loanTransactionRepository.flush();
|
||||
|
||||
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
|
||||
BusinessEvent<?> event = MathUtil.isGreaterThanZero(totalAmortizationAmount)
|
||||
? new LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent(transaction)
|
||||
: new LoanCapitalizedIncomeAmortizationAdjustmentTransactionCreatedBusinessEvent(transaction);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
private LocalDate getFinalCapitalizedIncomeAmortizationTransactionDate(final Loan loan) {
|
||||
return switch (loan.getStatus()) {
|
||||
case CLOSED_OBLIGATIONS_MET -> loan.getClosedOnDate();
|
||||
|
||||
-13
@@ -37,7 +37,6 @@ import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
|
||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanAdjustTransactionBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanBalanceChangedBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanCapitalizedIncomeAmortizationAdjustmentTransactionCreatedBusinessEvent;
|
||||
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;
|
||||
@@ -156,9 +155,6 @@ public class LoanAdjustmentServiceImpl implements LoanAdjustmentService {
|
||||
throw new InvalidLoanTransactionTypeException("transaction", "capitalizedIncome.cannot.be.reversed.when.adjusted",
|
||||
"Capitalized income transaction cannot be reversed when non-reversed adjustment exists for it.");
|
||||
}
|
||||
BigDecimal recognizedAmount = capitalizedIncomeBalance.getAmount().subtract(capitalizedIncomeBalance.getUnrecognizedAmount());
|
||||
capitalizedIncomeAmortizationAdjustmentTransaction = LoanTransaction.capitalizedIncomeAmortizationAdjustment(loan,
|
||||
Money.of(loan.getCurrency(), recognizedAmount), DateUtils.getBusinessLocalDate(), externalIdFactory.create());
|
||||
loanCapitalizedIncomeBalanceRepository.delete(capitalizedIncomeBalance);
|
||||
}
|
||||
if (transactionToAdjust.isCapitalizedIncomeAdjustment()) {
|
||||
@@ -213,15 +209,6 @@ public class LoanAdjustmentServiceImpl implements LoanAdjustmentService {
|
||||
this.loanTransactionRepository.saveAndFlush(newTransactionDetail);
|
||||
}
|
||||
|
||||
if (capitalizedIncomeAmortizationAdjustmentTransaction != null && capitalizedIncomeAmortizationAdjustmentTransaction.isNotZero()) {
|
||||
LoanTransaction savedCapitalizedIncomeAmortizationAdjustmentTransaction = loanTransactionRepository
|
||||
.saveAndFlush(capitalizedIncomeAmortizationAdjustmentTransaction);
|
||||
loan.addLoanTransaction(savedCapitalizedIncomeAmortizationAdjustmentTransaction);
|
||||
businessEventNotifierService
|
||||
.notifyPostBusinessEvent(new LoanCapitalizedIncomeAmortizationAdjustmentTransactionCreatedBusinessEvent(
|
||||
capitalizedIncomeAmortizationAdjustmentTransaction));
|
||||
}
|
||||
|
||||
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||
|
||||
if (StringUtils.isNotBlank(noteText)) {
|
||||
|
||||
+51
-6
@@ -19,21 +19,66 @@
|
||||
package org.apache.fineract.portfolio.loanaccount.util;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import org.apache.fineract.organisation.monetary.data.CurrencyData;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeBalance;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeStrategy;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
|
||||
public final class CapitalizedIncomeAmortizationUtil {
|
||||
|
||||
private CapitalizedIncomeAmortizationUtil() {}
|
||||
|
||||
public static Money calculateDailyAmortization(final LoanCapitalizedIncomeStrategy capitalizedIncomeStrategy,
|
||||
final long daysTillMaturity, final BigDecimal remainingAmount, final CurrencyData currencyData) {
|
||||
BigDecimal amortization = switch (capitalizedIncomeStrategy) {
|
||||
case EQUAL_AMORTIZATION -> remainingAmount.divide(BigDecimal.valueOf(daysTillMaturity), MoneyHelper.getMathContext());
|
||||
public static Money calculateTotalAmortizationTillDate(final LoanCapitalizedIncomeBalance capitalizedIncomeBalance,
|
||||
final List<LoanTransaction> adjustmentTransactions, final LocalDate maturityDate,
|
||||
final LoanCapitalizedIncomeStrategy capitalizedIncomeStrategy, final LocalDate tillDate, final MonetaryCurrency currency) {
|
||||
return switch (capitalizedIncomeStrategy) {
|
||||
case EQUAL_AMORTIZATION -> calculateTotalAmortizationTillDateEqualAmortization(capitalizedIncomeBalance, adjustmentTransactions,
|
||||
maturityDate, tillDate, currency);
|
||||
};
|
||||
}
|
||||
|
||||
return Money.of(currencyData, amortization);
|
||||
private static Money calculateTotalAmortizationTillDateEqualAmortization(LoanCapitalizedIncomeBalance balance,
|
||||
List<LoanTransaction> adjustmentTransactions, LocalDate maturityDate, LocalDate tillDate, MonetaryCurrency currency) {
|
||||
|
||||
BigDecimal unrecognizedAmount = balance.getAmount();
|
||||
BigDecimal totalAmortizationAmount = BigDecimal.ZERO;
|
||||
BigDecimal overAmortizationCorrection = BigDecimal.ZERO;
|
||||
|
||||
List<LoanTransaction> sortedAdjustmentTransactions = adjustmentTransactions.stream()
|
||||
.sorted(Comparator.comparing(LoanTransaction::getDateOf)).toList();
|
||||
LocalDate periodStart = balance.getDate();
|
||||
for (LoanTransaction adjustmentTransaction : sortedAdjustmentTransactions) {
|
||||
long daysUntilMaturity = DateUtils.getDifferenceInDays(periodStart, maturityDate);
|
||||
long daysOfPeriod = DateUtils.getDifferenceInDays(periodStart, adjustmentTransaction.getDateOf());
|
||||
BigDecimal periodAmortization = daysUntilMaturity == 0L ? BigDecimal.ZERO
|
||||
: unrecognizedAmount.multiply(BigDecimal.valueOf(daysOfPeriod)).divide(BigDecimal.valueOf(daysUntilMaturity),
|
||||
MoneyHelper.getMathContext());
|
||||
|
||||
totalAmortizationAmount = totalAmortizationAmount.add(periodAmortization);
|
||||
unrecognizedAmount = unrecognizedAmount.subtract(periodAmortization).subtract(adjustmentTransaction.getAmount());
|
||||
if (MathUtil.isLessThanZero(unrecognizedAmount)) {
|
||||
overAmortizationCorrection = overAmortizationCorrection.add(unrecognizedAmount);
|
||||
unrecognizedAmount = BigDecimal.ZERO;
|
||||
}
|
||||
periodStart = adjustmentTransaction.getDateOf();
|
||||
}
|
||||
if (periodStart.isBefore(tillDate)) {
|
||||
long daysUntilMaturity = DateUtils.getDifferenceInDays(periodStart, maturityDate);
|
||||
long daysOfPeriod = DateUtils.getDifferenceInDays(periodStart, tillDate);
|
||||
BigDecimal periodAmortization = unrecognizedAmount.multiply(BigDecimal.valueOf(daysOfPeriod))
|
||||
.divide(BigDecimal.valueOf(daysUntilMaturity), MoneyHelper.getMathContext());
|
||||
totalAmortizationAmount = totalAmortizationAmount.add(periodAmortization);
|
||||
} else if (balance.getDate().equals(maturityDate)) {
|
||||
totalAmortizationAmount = totalAmortizationAmount.add(unrecognizedAmount);
|
||||
}
|
||||
|
||||
return Money.of(currency, totalAmortizationAmount.add(overAmortizationCorrection));
|
||||
}
|
||||
}
|
||||
|
||||
-66
@@ -1,66 +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.util;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.math.RoundingMode;
|
||||
import org.apache.fineract.organisation.monetary.data.CurrencyData;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeStrategy;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
class CapitalizedIncomeAmortizationUtilTest {
|
||||
|
||||
private static final MockedStatic<MoneyHelper> MONEY_HELPER = Mockito.mockStatic(MoneyHelper.class);
|
||||
|
||||
@BeforeAll
|
||||
public static void init() {
|
||||
MONEY_HELPER.when(MoneyHelper::getMathContext).thenReturn(new MathContext(19, RoundingMode.HALF_EVEN));
|
||||
MONEY_HELPER.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.HALF_EVEN);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void deregister() {
|
||||
MONEY_HELPER.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void calculateDailyAmortizationEqualAmortization() {
|
||||
CurrencyData currency = new CurrencyData("USD", 2, null);
|
||||
|
||||
BigDecimal remaining = BigDecimal.valueOf(50.0);
|
||||
Money dailyAmortization = CapitalizedIncomeAmortizationUtil
|
||||
.calculateDailyAmortization(LoanCapitalizedIncomeStrategy.EQUAL_AMORTIZATION, 91L, remaining, currency);
|
||||
|
||||
Assertions.assertEquals(BigDecimal.valueOf(0.55), dailyAmortization.getAmount());
|
||||
|
||||
BigDecimal remaining2 = BigDecimal.valueOf(8.2);
|
||||
Money dailyAmortization2 = CapitalizedIncomeAmortizationUtil
|
||||
.calculateDailyAmortization(LoanCapitalizedIncomeStrategy.EQUAL_AMORTIZATION, 15L, remaining2, currency);
|
||||
|
||||
Assertions.assertEquals(BigDecimal.valueOf(0.55), dailyAmortization2.getAmount());
|
||||
}
|
||||
}
|
||||
+25
-9
@@ -232,6 +232,7 @@ public class LoanCapitalizedIncomeTest extends BaseLoanIntegrationTest {
|
||||
public void testLoanCapitalizedIncomeAdjustmentWithAmortizationAccounting() {
|
||||
final AtomicReference<Long> loanIdRef = new AtomicReference<>();
|
||||
final AtomicReference<Long> capitalizedIncomeIdRef = new AtomicReference<>();
|
||||
final AtomicReference<Long> capitalizedIncomeAdjustmentTransactionIdRef = new AtomicReference<>();
|
||||
|
||||
final PostClientsResponse client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
|
||||
|
||||
@@ -290,6 +291,11 @@ public class LoanCapitalizedIncomeTest extends BaseLoanIntegrationTest {
|
||||
|
||||
Long capitalizedIncomeAdjustmentTransactionId = loanTransactionHelper
|
||||
.capitalizedIncomeAdjustment(loanId, capitalizedIncomeIdRef.get(), "3 January 2024", 100.0).getResourceId();
|
||||
capitalizedIncomeAdjustmentTransactionIdRef.set(capitalizedIncomeAdjustmentTransactionId);
|
||||
});
|
||||
runAt("4 January 2024", () -> {
|
||||
Long loanId = loanIdRef.get();
|
||||
executeInlineCOB(loanId);
|
||||
|
||||
verifyTransactions(loanId, //
|
||||
transaction(100.0, "Disbursement", "01 January 2024"), //
|
||||
@@ -297,7 +303,9 @@ public class LoanCapitalizedIncomeTest extends BaseLoanIntegrationTest {
|
||||
transaction(1.10, "Capitalized Income Amortization", "01 January 2024"), //
|
||||
transaction(0.04, "Accrual", "02 January 2024"), //
|
||||
transaction(1.10, "Capitalized Income Amortization", "02 January 2024"), //
|
||||
transaction(100.0, "Capitalized Income Adjustment", "03 January 2024") //
|
||||
transaction(100.0, "Capitalized Income Adjustment", "03 January 2024"), //
|
||||
transaction(0.04, "Accrual", "03 January 2024"), //
|
||||
transaction(2.20, "Capitalized Income Amortization Adjustment", "03 January 2024") //
|
||||
);
|
||||
|
||||
verifyJournalEntries(loanId, //
|
||||
@@ -313,8 +321,11 @@ public class LoanCapitalizedIncomeTest extends BaseLoanIntegrationTest {
|
||||
journalEntry(1.10, feeIncomeAccount, "CREDIT"), //
|
||||
journalEntry(99.92, loansReceivableAccount, "CREDIT"), //
|
||||
journalEntry(0.08, interestReceivableAccount, "CREDIT"), //
|
||||
journalEntry(100.0, deferredIncomeLiabilityAccount, "DEBIT"), //
|
||||
journalEntry(0.04, interestReceivableAccount, "DEBIT"), //
|
||||
journalEntry(0.04, interestIncomeAccount, "CREDIT"), //
|
||||
journalEntry(2.20, feeIncomeAccount, "DEBIT"), //
|
||||
journalEntry(97.80, deferredIncomeLiabilityAccount, "DEBIT") //
|
||||
journalEntry(2.20, deferredIncomeLiabilityAccount, "CREDIT") //
|
||||
);
|
||||
|
||||
// Reverse-replay
|
||||
@@ -328,19 +339,16 @@ public class LoanCapitalizedIncomeTest extends BaseLoanIntegrationTest {
|
||||
verifyTRJournalEntries(replayedCapitalizedIncomeAdjustmentOpt.get().getId(), //
|
||||
journalEntry(99.98, loansReceivableAccount, "CREDIT"), //
|
||||
journalEntry(0.02, interestReceivableAccount, "CREDIT"), //
|
||||
journalEntry(2.20, feeIncomeAccount, "DEBIT"), //
|
||||
journalEntry(97.80, deferredIncomeLiabilityAccount, "DEBIT") //
|
||||
journalEntry(100.0, deferredIncomeLiabilityAccount, "DEBIT") //
|
||||
);
|
||||
|
||||
verifyTRJournalEntries(capitalizedIncomeAdjustmentTransactionId, //
|
||||
verifyTRJournalEntries(capitalizedIncomeAdjustmentTransactionIdRef.get(), //
|
||||
journalEntry(99.92, loansReceivableAccount, "CREDIT"), //
|
||||
journalEntry(0.08, interestReceivableAccount, "CREDIT"), //
|
||||
journalEntry(2.20, feeIncomeAccount, "DEBIT"), //
|
||||
journalEntry(97.80, deferredIncomeLiabilityAccount, "DEBIT"), //
|
||||
journalEntry(100.0, deferredIncomeLiabilityAccount, "DEBIT"), //
|
||||
journalEntry(99.92, loansReceivableAccount, "DEBIT"), //
|
||||
journalEntry(0.08, interestReceivableAccount, "DEBIT"), //
|
||||
journalEntry(2.20, feeIncomeAccount, "CREDIT"), //
|
||||
journalEntry(97.80, deferredIncomeLiabilityAccount, "CREDIT") //
|
||||
journalEntry(100.0, deferredIncomeLiabilityAccount, "CREDIT") //
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -486,12 +494,18 @@ public class LoanCapitalizedIncomeTest extends BaseLoanIntegrationTest {
|
||||
|
||||
loanTransactionHelper.reverseLoanTransaction(loanId, capitalizedIncomeTransactionIdRef.get(), "3 January 2024");
|
||||
|
||||
});
|
||||
runAt("4 January 2024", () -> {
|
||||
Long loanId = loanIdRef.get();
|
||||
executeInlineCOB(loanId);
|
||||
|
||||
verifyTransactions(loanId, //
|
||||
transaction(100.0, "Disbursement", "01 January 2024"), //
|
||||
transaction(50.0, "Capitalized Income", "01 January 2024"), //
|
||||
transaction(0.55, "Capitalized Income Amortization", "01 January 2024"), //
|
||||
transaction(0.03, "Accrual", "02 January 2024"), //
|
||||
transaction(0.55, "Capitalized Income Amortization", "02 January 2024"), //
|
||||
transaction(0.01, "Accrual", "03 January 2024"), //
|
||||
transaction(1.10, "Capitalized Income Amortization Adjustment", "03 January 2024") //
|
||||
);
|
||||
|
||||
@@ -508,6 +522,8 @@ public class LoanCapitalizedIncomeTest extends BaseLoanIntegrationTest {
|
||||
journalEntry(0.55, feeIncomeAccount, "CREDIT"), //
|
||||
journalEntry(50, deferredIncomeLiabilityAccount, "DEBIT"), //
|
||||
journalEntry(50, loansReceivableAccount, "CREDIT"), //
|
||||
journalEntry(0.01, interestReceivableAccount, "DEBIT"), //
|
||||
journalEntry(0.01, interestIncomeAccount, "CREDIT"), //
|
||||
journalEntry(1.10, feeIncomeAccount, "DEBIT"), //
|
||||
journalEntry(1.10, deferredIncomeLiabilityAccount, "CREDIT") //
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user