From b1798a66df97dae51e02a0547bd378d849f99f56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Soma=20S=C3=B6r=C3=B6s?= Date: Tue, 23 Jul 2024 12:48:48 +0200 Subject: [PATCH] FINERACT-2104: Accrual Activity - Support final Accrual Activity transaction creation at the event of the loan got fully paid --- .../core/service/DateUtils.java | 9 + .../portfolio/loanaccount/domain/Loan.java | 3 +- .../loanaccount/domain/LoanCharge.java | 14 +- ...RepaymentScheduleTransactionProcessor.java | 23 +- .../LoanAccrualActivityProcessingService.java | 5 + .../service/LoanWritePlatformService.java | 7 + ...nAccrualActivityProcessingServiceImpl.java | 104 +++ .../LoanStatusChangePlatformServiceImpl.java | 20 + ...WritePlatformServiceJpaRepositoryImpl.java | 37 +- .../starter/LoanAccountConfiguration.java | 7 +- ...TransactionAccrualActivityPostingTest.java | 686 +++++++++++++++++- 11 files changed, 851 insertions(+), 64 deletions(-) diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java index bd235d4d2..be86204ab 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java @@ -394,4 +394,13 @@ public final class DateUtils { return formatter; } + public static boolean occursOnDayFromExclusiveAndUpToAndIncluding(final LocalDate fromNotInclusive, final LocalDate upToAndInclusive, + final LocalDate target) { + return DateUtils.isAfter(target, fromNotInclusive) && !DateUtils.isAfter(target, upToAndInclusive); + } + + public static boolean occursOnDayFromAndUpToAndIncluding(final LocalDate fromAndInclusive, final LocalDate upToAndInclusive, + final LocalDate target) { + return target != null && !DateUtils.isBefore(target, fromAndInclusive) && !DateUtils.isAfter(target, upToAndInclusive); + } } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java index f9e4eeea6..aff66589f 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java @@ -3877,7 +3877,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { public LocalDate getLastUserTransactionDate() { LocalDate currentTransactionDate = getDisbursementDate(); for (final LoanTransaction previousTransaction : this.loanTransactions) { - if (!(previousTransaction.isReversed() || previousTransaction.isAccrual() || previousTransaction.isIncomePosting()) + if (!(previousTransaction.isReversed() || previousTransaction.isAccrual() || previousTransaction.isIncomePosting() + || previousTransaction.isAccrualActivity()) && DateUtils.isBefore(currentTransactionDate, previousTransaction.getTransactionDate())) { currentTransactionDate = previousTransaction.getTransactionDate(); } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java index 2b6b18baf..249be5320 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java @@ -618,22 +618,12 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom { public boolean isDueForCollectionFromAndUpToAndIncluding(final LocalDate fromNotInclusive, final LocalDate upToAndInclusive) { final LocalDate dueDate = getDueLocalDate(); - return occursOnDayFromExclusiveAndUpToAndIncluding(fromNotInclusive, upToAndInclusive, dueDate); + return DateUtils.occursOnDayFromExclusiveAndUpToAndIncluding(fromNotInclusive, upToAndInclusive, dueDate); } public boolean isDueForCollectionFromIncludingAndUpToAndIncluding(final LocalDate fromAndInclusive, final LocalDate upToAndInclusive) { final LocalDate dueDate = getDueLocalDate(); - return occursOnDayFromAndUpToAndIncluding(fromAndInclusive, upToAndInclusive, dueDate); - } - - private boolean occursOnDayFromExclusiveAndUpToAndIncluding(final LocalDate fromNotInclusive, final LocalDate upToAndInclusive, - final LocalDate target) { - return DateUtils.isAfter(target, fromNotInclusive) && !DateUtils.isAfter(target, upToAndInclusive); - } - - private boolean occursOnDayFromAndUpToAndIncluding(final LocalDate fromAndInclusive, final LocalDate upToAndInclusive, - final LocalDate target) { - return target != null && !DateUtils.isBefore(target, fromAndInclusive) && !DateUtils.isAfter(target, upToAndInclusive); + return DateUtils.occursOnDayFromAndUpToAndIncluding(fromAndInclusive, upToAndInclusive, dueDate); } public boolean isFeeCharge() { diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java index f1151b6e9..742f2bb51 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java @@ -220,13 +220,20 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen Money interestPortion = Money.zero(currency); Money feeChargesPortion = Money.zero(currency); Money penaltychargesPortion = Money.zero(currency); - for (final LoanRepaymentScheduleInstallment currentInstallment : installments) { - if (loanTransaction.getDateOf().isEqual(currentInstallment.getDueDate())) { - interestPortion = interestPortion.plus(currentInstallment.getInterestCharged(currency)); - feeChargesPortion = feeChargesPortion.plus(currentInstallment.getFeeChargesCharged(currency)); - penaltychargesPortion = penaltychargesPortion.plus(currentInstallment.getPenaltyChargesCharged(currency)); - } - } + final int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments); + + final LoanRepaymentScheduleInstallment currentInstallment = installments.stream() + .filter(installment -> installment.getInstallmentNumber().equals(firstNormalInstallmentNumber) + ? DateUtils.occursOnDayFromExclusiveAndUpToAndIncluding(installment.getFromDate(), installment.getDueDate(), + loanTransaction.getTransactionDate()) + : DateUtils.occursOnDayFromAndUpToAndIncluding(installment.getFromDate(), installment.getDueDate(), + loanTransaction.getTransactionDate())) + .findFirst().orElseThrow(); + + interestPortion = interestPortion.plus(currentInstallment.getInterestCharged(currency)); + feeChargesPortion = feeChargesPortion.plus(currentInstallment.getFeeChargesCharged(currency)); + penaltychargesPortion = penaltychargesPortion.plus(currentInstallment.getPenaltyChargesCharged(currency)); + loanTransaction.updateComponentsAndTotal(principalPortion, interestPortion, feeChargesPortion, penaltychargesPortion); } @@ -602,7 +609,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen installmentNumber = installments.get(0).getInstallmentNumber(); } - if (loanTransaction.isNotWaiver() && !loanTransaction.isAccrual()) { + if (loanTransaction.isNotWaiver() && !loanTransaction.isAccrual() && !loanTransaction.isAccrualActivity()) { Money feeCharges = loanTransaction.getFeeChargesPortion(currency); Money penaltyCharges = loanTransaction.getPenaltyChargesPortion(currency); if (chargeAmountToProcess != null && feeCharges.isGreaterThan(chargeAmountToProcess)) { diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingService.java index a7f1dc255..31520704b 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingService.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingService.java @@ -29,4 +29,9 @@ public interface LoanAccrualActivityProcessingService { Loan makeAccrualActivityTransaction(Loan loan, LocalDate currentDate); + @Transactional + void processAccrualActivityForLoanClosure(Loan loan); + + @Transactional + void processAccrualActivityForLoanReopen(Loan loan); } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformService.java index fa2e1553b..c0490df11 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformService.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformService.java @@ -53,9 +53,16 @@ public interface LoanWritePlatformService { CommandProcessingResult makeLoanRepaymentWithChargeRefundChargeType(LoanTransactionType repaymentTransactionType, Long loanId, JsonCommand command, boolean isRecoveryRepayment, String chargeRefundChargeType); + @Transactional + Loan reverseReplayAccrualActivityTransaction(Loan loan, LoanTransaction loanTransaction, LoanRepaymentScheduleInstallment installment, + LocalDate transactionDate); + @Transactional Loan makeAccrualActivityTransaction(Loan loan, LoanRepaymentScheduleInstallment installment, LocalDate transactionDate); + @Transactional + Loan makeAccrualActivityTransaction(Loan loan, LoanTransaction accrualActivityTransaction); + @Transactional CommandProcessingResult makeInterestPaymentWaiver(JsonCommand command); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImpl.java index 51159ba2a..daf83a7ca 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImpl.java @@ -18,15 +18,23 @@ */ package org.apache.fineract.portfolio.loanaccount.service; +import java.math.BigDecimal; import java.time.LocalDate; +import java.util.Comparator; import java.util.List; import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +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.event.business.domain.loan.LoanAdjustTransactionBusinessEvent; +import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @@ -38,6 +46,8 @@ public class LoanAccrualActivityProcessingServiceImpl implements LoanAccrualActi private final LoanRepositoryWrapper loanRepositoryWrapper; private final LoanWritePlatformService loanWritePlatformService; + private final ExternalIdFactory externalIdFactory; + private final BusinessEventNotifierService businessEventNotifierService; @Override @Transactional(propagation = Propagation.REQUIRES_NEW) @@ -66,4 +76,98 @@ public class LoanAccrualActivityProcessingServiceImpl implements LoanAccrualActi return loan; } + @Override + @Transactional + public void processAccrualActivityForLoanClosure(Loan loan) { + LocalDate date = loan.isOverPaid() ? loan.getOverpaidOnDate() : loan.getClosedOnDate(); + List accrualActivityTransaction = loan.getLoanTransactions().stream().filter(LoanTransaction::isNotReversed) + .filter(LoanTransaction::isAccrualActivity).filter(loanTransaction -> loanTransaction.getDateOf().isAfter(date)).toList(); + if (!accrualActivityTransaction.isEmpty()) { + accrualActivityTransaction.forEach(this::reverseAccrualActivityTransaction); + } + LoanTransaction loanTransaction = assembleClosingAccrualActivityTransaction(loan, date); + if (loanTransaction.getAmount().compareTo(BigDecimal.ZERO) != 0) { + loanWritePlatformService.makeAccrualActivityTransaction(loan, loanTransaction); + } + } + + private void reverseAccrualActivityTransaction(LoanTransaction loanTransaction) { + loanTransaction.reverse(); + LoanAdjustTransactionBusinessEvent.Data data = new LoanAdjustTransactionBusinessEvent.Data(loanTransaction); + businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(data)); + } + + private LoanTransaction assembleClosingAccrualActivityTransaction(Loan loan, LocalDate date) { + // collect fees + BigDecimal feeChargesPortion = BigDecimal.ZERO; + // collect penalties + BigDecimal penaltyChargesPortion = BigDecimal.ZERO; + // collect interests + BigDecimal interestPortion = BigDecimal.ZERO; + var currency = loan.getCurrency(); + // sum up all accruals + for (LoanRepaymentScheduleInstallment installment : loan.getRepaymentScheduleInstallments()) { + feeChargesPortion = installment.getFeeAccrued(currency).getAmount().add(feeChargesPortion); + penaltyChargesPortion = installment.getPenaltyAccrued(currency).getAmount().add(penaltyChargesPortion); + interestPortion = installment.getInterestAccrued(currency).getAmount().add(interestPortion); + } + List accrualActivities = loan.getLoanTransactions().stream().filter(LoanTransaction::isAccrualActivity) + .filter(LoanTransaction::isNotReversed).toList(); + // subtract already Posted accruals + for (LoanTransaction accrualActivity : accrualActivities) { + if (accrualActivity.getFeeChargesPortion() != null) { + feeChargesPortion = feeChargesPortion.subtract(accrualActivity.getFeeChargesPortion()); + } + if (accrualActivity.getPenaltyChargesPortion() != null) { + penaltyChargesPortion = penaltyChargesPortion.subtract(accrualActivity.getPenaltyChargesPortion()); + } + if (accrualActivity.getInterestPortion() != null) { + interestPortion = interestPortion.subtract(accrualActivity.getInterestPortion()); + } + } + + BigDecimal transactionAmount = feeChargesPortion.add(penaltyChargesPortion).add(interestPortion); + ExternalId externalId = externalIdFactory.create(); + + return new LoanTransaction(loan, loan.getOffice(), LoanTransactionType.ACCRUAL_ACTIVITY.getValue(), date, transactionAmount, null, + interestPortion, feeChargesPortion, penaltyChargesPortion, null, false, null, externalId); + } + + @Override + @Transactional + public void processAccrualActivityForLoanReopen(Loan loan) { + LoanTransaction lastAccrualActivityMarkedToReverse = null; + List accrualActivityTransaction = loan.getLoanTransactions().stream() + .filter(loanTransaction -> loanTransaction.isNotReversed() && loanTransaction.isAccrualActivity()) + .sorted(Comparator.comparing(LoanTransaction::getDateOf)).toList(); + // grab the latest AccrualActivityTransaction + // it does not matter if it is on an installment due date or not because it was posted due to loan close + if (!accrualActivityTransaction.isEmpty()) { + lastAccrualActivityMarkedToReverse = accrualActivityTransaction.get(accrualActivityTransaction.size() - 1); + } + final LocalDate lastAccrualActivityTransactionDate = lastAccrualActivityMarkedToReverse == null ? null + : lastAccrualActivityMarkedToReverse.getDateOf(); + LocalDate today = DateUtils.getBusinessLocalDate(); + final List installmentsBetweenBusinessDateAndLastAccrualActivityTransactionDate = loan + .getRepaymentScheduleInstallments().stream() + .filter(installment -> installment.getDueDate().isBefore(today) && (lastAccrualActivityTransactionDate == null + || installment.getDueDate().isAfter(lastAccrualActivityTransactionDate) + // if close event happened on installment due date + // we should reverse replay it to calculate installment related accrual parts only + || installment.getDueDate().isEqual(lastAccrualActivityTransactionDate))) + .sorted(Comparator.comparing(LoanRepaymentScheduleInstallment::getDueDate)).toList(); + for (LoanRepaymentScheduleInstallment installment : installmentsBetweenBusinessDateAndLastAccrualActivityTransactionDate) { + if (lastAccrualActivityMarkedToReverse != null) { + loanWritePlatformService.reverseReplayAccrualActivityTransaction(loan, lastAccrualActivityMarkedToReverse, installment, + installment.getDueDate()); + lastAccrualActivityMarkedToReverse = null; + } else { + loanWritePlatformService.makeAccrualActivityTransaction(loan, installment, installment.getDueDate()); + } + } + if (lastAccrualActivityMarkedToReverse != null) { + reverseAccrualActivityTransaction(lastAccrualActivityMarkedToReverse); + } + } + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanStatusChangePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanStatusChangePlatformServiceImpl.java index e9f077e35..ba6284515 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanStatusChangePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanStatusChangePlatformServiceImpl.java @@ -32,10 +32,13 @@ public class LoanStatusChangePlatformServiceImpl implements LoanStatusChangePlat private final BusinessEventNotifierService businessEventNotifierService; private final LoanAccrualsProcessingService loanAccrualsProcessingService; + private final LoanAccrualActivityProcessingService loanAccrualActivityProcessingService; @PostConstruct public void addListeners() { businessEventNotifierService.addPostBusinessEventListener(LoanStatusChangedBusinessEvent.class, new LoanStatusChangedListener()); + businessEventNotifierService.addPostBusinessEventListener(LoanStatusChangedBusinessEvent.class, + new LoanAccrualActivityPostingLoanStatusChangedListener()); } private final class LoanStatusChangedListener implements BusinessEventListener { @@ -53,4 +56,21 @@ public class LoanStatusChangePlatformServiceImpl implements LoanStatusChangePlat } } } + + private final class LoanAccrualActivityPostingLoanStatusChangedListener + implements BusinessEventListener { + + @Override + public void onBusinessEvent(LoanStatusChangedBusinessEvent event) { + final Loan loan = event.get(); + if (loan.getLoanProductRelatedDetail().isEnableAccrualActivityPosting()) { + if (loan.getStatus().isClosedObligationsMet() || loan.getStatus().isOverpaid()) { + loanAccrualActivityProcessingService.processAccrualActivityForLoanClosure(loan); + } + if (loan.isOpen()) { + loanAccrualActivityProcessingService.processAccrualActivityForLoanReopen(loan); + } + } + } + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index df76140dc..51f1d04f2 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -1162,15 +1162,50 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf return changedTransactionDetail; } + @Transactional + @Override + public Loan reverseReplayAccrualActivityTransaction(Loan loan, final LoanTransaction loanTransaction, + final LoanRepaymentScheduleInstallment installment, final LocalDate transactionDate) { + + LoanTransaction newLoanTransaction = loanTransactionAssembler.assembleAccrualActivityTransaction(loan, installment, + transactionDate); + if (!newLoanTransaction.getDateOf().isEqual(loanTransaction.getDateOf()) + || !LoanTransaction.transactionAmountsMatch(loan.getCurrency(), loanTransaction, newLoanTransaction)) { + loanTransaction.reverse(); + loanTransaction.updateExternalId(null); + newLoanTransaction.copyLoanTransactionRelations(loanTransaction.getLoanTransactionRelations()); + newLoanTransaction.getLoanTransactionRelations().add(LoanTransactionRelation.linkToTransaction(newLoanTransaction, + loanTransaction, LoanTransactionRelationTypeEnum.REPLAYED)); + loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(newLoanTransaction); + loan.addLoanTransaction(newLoanTransaction); + + LoanAdjustTransactionBusinessEvent.Data data = new LoanAdjustTransactionBusinessEvent.Data(loanTransaction); + data.setNewTransactionDetail(newLoanTransaction); + businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(data)); + } + return loan; + } + @Transactional @Override public Loan makeAccrualActivityTransaction(Loan loan, final LoanRepaymentScheduleInstallment installment, final LocalDate transactionDate) { - businessEventNotifierService.notifyPreBusinessEvent(new LoanTransactionAccrualActivityPreBusinessEvent(loan)); LoanTransaction newAccrualActivityTransaction = loanTransactionAssembler.assembleAccrualActivityTransaction(loan, installment, transactionDate); + if (newAccrualActivityTransaction.getAmount().compareTo(BigDecimal.ZERO) == 0) { + return loan; + } + + return makeAccrualActivityTransaction(loan, newAccrualActivityTransaction); + } + + @Transactional + @Override + public Loan makeAccrualActivityTransaction(Loan loan, LoanTransaction newAccrualActivityTransaction) { + businessEventNotifierService.notifyPreBusinessEvent(new LoanTransactionAccrualActivityPreBusinessEvent(loan)); + loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(newAccrualActivityTransaction); loan.addLoanTransaction(newAccrualActivityTransaction); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java index 18287ad93..468547166 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java @@ -94,6 +94,7 @@ import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoReadPlat import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoReadPlatformServiceImpl; import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoWritePlatformService; import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoWritePlatformServiceImpl; +import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualActivityProcessingService; import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualTransactionBusinessEventService; import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualTransactionBusinessEventServiceImpl; import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualsProcessingService; @@ -329,8 +330,10 @@ public class LoanAccountConfiguration { @Bean @ConditionalOnMissingBean(LoanStatusChangePlatformService.class) public LoanStatusChangePlatformService loanStatusChangePlatformService(BusinessEventNotifierService businessEventNotifierService, - LoanAccrualsProcessingService loanAccrualsProcessingService) { - return new LoanStatusChangePlatformServiceImpl(businessEventNotifierService, loanAccrualsProcessingService); + LoanAccrualsProcessingService loanAccrualsProcessingService, + LoanAccrualActivityProcessingService loanAccrualActivityProcessingService) { + return new LoanStatusChangePlatformServiceImpl(businessEventNotifierService, loanAccrualsProcessingService, + loanAccrualActivityProcessingService); } @Bean diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionAccrualActivityPostingTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionAccrualActivityPostingTest.java index dad8b4578..f9b15df13 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionAccrualActivityPostingTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionAccrualActivityPostingTest.java @@ -28,6 +28,7 @@ import io.restassured.http.ContentType; import io.restassured.specification.RequestSpecification; import io.restassured.specification.ResponseSpecification; import java.math.BigDecimal; +import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.List; @@ -35,6 +36,11 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; import org.apache.fineract.client.models.AdvancedPaymentData; +import org.apache.fineract.client.models.AllowAttributeOverrides; +import org.apache.fineract.client.models.ChargeData; +import org.apache.fineract.client.models.ChargeToGLAccountMapper; +import org.apache.fineract.client.models.GetLoanFeeToIncomeAccountMappings; +import org.apache.fineract.client.models.GetLoanPaymentChannelToFundSourceMappings; import org.apache.fineract.client.models.PaymentAllocationOrder; import org.apache.fineract.client.models.PostChargesRequest; import org.apache.fineract.client.models.PostChargesResponse; @@ -119,7 +125,7 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati final String repaymentPeriod1OneDayAfterCloseDate = "03 February 2023"; final String repaymentPeriod2DueDate = "01 March 2023"; final String repaymentPeriod3DueDate = "01 April 2023"; - final String creationBusinessDay = "15 May 2023"; + final String creationBusinessDay = "15 January 2023"; AtomicReference loanId = new AtomicReference<>(); runAt(creationBusinessDay, () -> { @@ -127,10 +133,6 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati loanId.set(applyForLoanApplicationWithInterest(client.getClientId(), localLoanProductId, BigDecimal.valueOf(40000), disbursementDay)); - LOG.info("Test Loan Product Id {1} http://localhost:4200/#/products/loan-products/{}/general", localLoanProductId); - LOG.info("Test Client Id {1} http://localhost:4200/#/clients/{}", client.getClientId()); - LOG.info("Test Loan Id {2} http://localhost:4200/#/clients/{}/loans-accounts/{}/transactions", client.getClientId(), loanId); - loanTransactionHelper.approveLoan(loanId.get(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)) .dateFormat(DATETIME_PATTERN).approvedOnDate(disbursementDay).locale("en")); @@ -193,7 +195,7 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati final String repaymentPeriod1OneDayAfterCloseDate = "03 February 2023"; final String repaymentPeriod2DueDate = "01 March 2023"; final String repaymentPeriod3DueDate = "01 April 2023"; - final String creationBusinessDay = "15 May 2023"; + final String creationBusinessDay = "15 January 2023"; AtomicReference loanId = new AtomicReference<>(); runAt(creationBusinessDay, () -> { @@ -201,10 +203,6 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati loanId.set(applyForLoanApplicationWithInterest(client.getClientId(), localLoanProductId, BigDecimal.valueOf(40000), disbursementDay)); - LOG.info("Test Loan Product Id {1} http://localhost:4200/#/products/loan-products/{}/general", localLoanProductId); - LOG.info("Test Client Id {1} http://localhost:4200/#/clients/{}", client.getClientId()); - LOG.info("Test Loan Id {2} http://localhost:4200/#/clients/{}/loans-accounts/{}/transactions", client.getClientId(), loanId); - loanTransactionHelper.approveLoan(loanId.get(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)) .dateFormat(DATETIME_PATTERN).approvedOnDate(disbursementDay).locale("en")); @@ -256,7 +254,7 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati final String disbursementDay = "01 January 2023"; final String repaymentPeriod1DueDate = "01 February 2023"; final String repaymentPeriod1CloseDate = "02 February 2023"; - final String creationBusinessDay = "15 May 2023"; + final String creationBusinessDay = "15 January 2023"; AtomicReference loanId1 = new AtomicReference<>(); AtomicReference loanId2 = new AtomicReference<>(); runAt(creationBusinessDay, () -> { @@ -316,7 +314,7 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati final String disbursementDay = "01 January 2023"; final String repaymentPeriod1CloseDate = "02 February 2023"; final String repaymentPeriod1OneDayAfterCloseDate = "03 February 2023"; - final String creationBusinessDay = "15 May 2023"; + final String creationBusinessDay = "15 January 2023"; AtomicReference loanId = new AtomicReference<>(); runAt(creationBusinessDay, () -> { @@ -399,7 +397,7 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati final String disbursementDay = "01 January 2023"; final String repaymentPeriod1CloseDate = "02 February 2023"; final String repaymentPeriod1OneDayAfterCloseDate = "03 February 2023"; - final String creationBusinessDay = "15 May 2023"; + final String creationBusinessDay = "15 January 2023"; AtomicReference loanId = new AtomicReference<>(); runAt(creationBusinessDay, () -> { @@ -413,10 +411,6 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati loanTransactionHelper.disburseLoan(loanId.get(), new PostLoansLoanIdRequest().actualDisbursementDate(disbursementDay) .dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(1000.0)).locale("en")); - LOG.info("Test Loan Product Id {1} http://localhost:4200/#/products/loan-products/{}/general", localLoanProductId); - LOG.info("Test Client Id {1} http://localhost:4200/#/clients/{}", client.getClientId()); - LOG.info("Test Loan Id {2} http://localhost:4200/#/clients/{}/loans-accounts/{}/transactions", client.getClientId(), loanId); - chargePenalty(loanId.get(), 20.0, chargeDueDate1st); addRepaymentForLoan(loanId.get(), 50.0, "10 January 2023"); @@ -482,7 +476,7 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati final String disbursementDay = "01 January 2023"; final String repaymentPeriod1CloseDate = "02 February 2023"; final String repaymentPeriod1OneDayAfterCloseDate = "03 February 2023"; - final String creationBusinessDay = "15 May 2023"; + final String creationBusinessDay = "15 January 2023"; AtomicReference loanId = new AtomicReference<>(); runAt(creationBusinessDay, () -> { @@ -496,10 +490,6 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati loanTransactionHelper.disburseLoan(loanId.get(), new PostLoansLoanIdRequest().actualDisbursementDate(disbursementDay) .dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(1000.0)).locale("en")); - LOG.info("Test Loan Product Id {1} http://localhost:4200/#/products/loan-products/{}/general", localLoanProductId); - LOG.info("Test Client Id {1} http://localhost:4200/#/clients/{}", client.getClientId()); - LOG.info("Test Loan Id {2} http://localhost:4200/#/clients/{}/loans-accounts/{}/transactions", client.getClientId(), loanId); - chargePenalty(loanId.get(), 20.0, chargeDueDate1st); verifyTransactions(loanId.get(), // @@ -532,9 +522,566 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati }); } + @Test + public void testAccrualActivityPostingForProgressiveLoanWithEarlyRepaymentAndReverseRepayment() { + final String disbursementDay = "01 January 2023"; + final String repaymentDate1 = "15 January 2023"; + final String repaymentPeriod1DueDate = "01 February 2023"; + final String repaymentPeriod1CloseDate = "02 February 2023"; + final String creationBusinessDay = "15 January 2023"; + AtomicReference loanId = new AtomicReference<>(); + AtomicReference repaymentId = new AtomicReference<>(); + runAt(creationBusinessDay, () -> { + Long localLoanProductId = createLoanProductAccountingAccrualPeriodicAdvancedPaymentAllocation(); + loanId.set(applyForLoanApplicationAdvancedPaymentAllocation(client.getClientId(), localLoanProductId, BigDecimal.valueOf(40000), + disbursementDay)); + + loanTransactionHelper.approveLoan(loanId.get(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)) + .dateFormat(DATETIME_PATTERN).approvedOnDate(disbursementDay).locale("en")); + + loanTransactionHelper.disburseLoan(loanId.get(), new PostLoansLoanIdRequest().actualDisbursementDate(disbursementDay) + .dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(1000.0)).locale("en")); + + chargePenalty(loanId.get(), 30.0, repaymentPeriod1DueDate); + chargeFee(loanId.get(), 40.0, repaymentPeriod1DueDate); + + repaymentId.set(addRepaymentForLoan(loanId.get(), 1070.0, repaymentDate1)); + + verifyTransactions(loanId.get(), + transaction(1000.0, "Disbursement", disbursementDay, 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), // + transaction(1070.0, "Repayment", repaymentDate1, 0.0, 1000.0, 0.0, 40.0, 30, 0.0, 0.0, false), // + transaction(70.0, "Accrual", repaymentDate1, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false), // + transaction(70.0, "Accrual Activity", repaymentDate1, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false)); // + + }); + runAt(repaymentPeriod1CloseDate, () -> { + + inlineLoanCOBHelper.executeInlineCOB(List.of(loanId.get())); + + verifyTransactions(loanId.get(), + transaction(1000.0, "Disbursement", disbursementDay, 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), // + transaction(1070.0, "Repayment", repaymentDate1, 0.0, 1000.0, 0.0, 40.0, 30, 0.0, 0.0, false), // + transaction(70.0, "Accrual", repaymentDate1, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false), // + transaction(70.0, "Accrual Activity", repaymentDate1, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false)); // + + loanTransactionHelper.reverseRepayment(loanId.get().intValue(), repaymentId.get().intValue(), repaymentDate1); + + verifyTransactions(loanId.get(), + transaction(1000.0, "Disbursement", disbursementDay, 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), // + transaction(1070.0, "Repayment", repaymentDate1, 0.0, 1000.0, 0.0, 40.0, 30, 0.0, 0.0, true), // + transaction(70.0, "Accrual", repaymentDate1, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false), // + transaction(70.0, "Accrual Activity", repaymentPeriod1DueDate, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false)); // + + }); + } + + @Test + public void testAccrualActivityPostingForLoanWithEarlyRepaymentAndReverseRepayment() { + final String disbursementDay = "01 January 2023"; + final String repaymentDate1 = "15 January 2023"; + final String repaymentPeriod1DueDate = "01 February 2023"; + final String repaymentPeriod1CloseDate = "02 February 2023"; + final String creationBusinessDay = "15 January 2023"; + AtomicReference loanId = new AtomicReference<>(); + AtomicReference repaymentId = new AtomicReference<>(); + runAt(creationBusinessDay, () -> { + Long localLoanProductId = createLoanProductAccountingAccrualPeriodicWithInterest(); + loanId.set(applyForLoanApplicationWithInterest(client.getClientId(), localLoanProductId, BigDecimal.valueOf(40000), + disbursementDay)); + + loanTransactionHelper.approveLoan(loanId.get(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)) + .dateFormat(DATETIME_PATTERN).approvedOnDate(disbursementDay).locale("en")); + + loanTransactionHelper.disburseLoan(loanId.get(), new PostLoansLoanIdRequest().actualDisbursementDate(disbursementDay) + .dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(1000.0)).locale("en")); + + chargePenalty(loanId.get(), 30.0, repaymentPeriod1DueDate); + chargeFee(loanId.get(), 40.0, repaymentPeriod1DueDate); + + repaymentId.set(addRepaymentForLoan(loanId.get(), 1150.0, repaymentDate1)); + + verifyTransactions(loanId.get(), + transaction(1000.0, "Disbursement", disbursementDay, 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), // + transaction(1150.0, "Repayment", repaymentDate1, 0.0, 1000.0, 80.0, 40.0, 30, 0.0, 0.0, false), // + transaction(150.0, "Accrual", repaymentDate1, 0.0, 0.0, 80.0, 40.0, 30.0, 0.0, 0.0, false), // + transaction(150.0, "Accrual Activity", repaymentDate1, 0.0, 0.0, 80.0, 40.0, 30.0, 0.0, 0.0, false)); // + + }); + runAt(repaymentPeriod1CloseDate, () -> { + + inlineLoanCOBHelper.executeInlineCOB(List.of(loanId.get())); + + verifyTransactions(loanId.get(), + transaction(1000.0, "Disbursement", disbursementDay, 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), // + transaction(1150.0, "Repayment", repaymentDate1, 0.0, 1000.0, 80.0, 40, 30, 0.0, 0.0, false), // + transaction(150.0, "Accrual", repaymentDate1, 0.0, 0.0, 80.0, 40.0, 30.0, 0.0, 0.0, false), // + transaction(150.0, "Accrual Activity", repaymentDate1, 0.0, 0.0, 80.0, 40.0, 30.0, 0.0, 0.0, false)); // + + loanTransactionHelper.reverseRepayment(loanId.get().intValue(), repaymentId.get().intValue(), repaymentDate1); + + verifyTransactions(loanId.get(), + transaction(1000.0, "Disbursement", disbursementDay, 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), // + transaction(1150.0, "Repayment", repaymentDate1, 0.0, 1000.0, 80.0, 40.0, 30, 0.0, 0.0, true), // + transaction(90.0, "Accrual Activity", repaymentPeriod1DueDate, 0.0, 0.0, 20.0, 40.0, 30.0, 0.0, 0.0, false)); // + + }); + } + + @Test + public void testAccrualActivityPostingForProgressiveMultiDisburseLoanWithEarlyRepayment2ndDisbursement() { + final String disbursementDay = "01 January 2023"; + final String disbursementDay2 = "25 January 2023"; + final String repaymentDate1 = "15 January 2023"; + final String repaymentPeriod1DueDate = "01 February 2023"; + final String repaymentPeriod1CloseDate = "02 February 2023"; + final String creationBusinessDay = "15 January 2023"; + AtomicReference loanId = new AtomicReference<>(); + runAt(creationBusinessDay, () -> { + Long localLoanProductId = createLoanProductAccountingAccrualPeriodicAdvancedPaymentAllocation(true); + + loanId.set(applyForLoanApplicationAdvancedPaymentAllocation(client.getClientId(), localLoanProductId, BigDecimal.valueOf(40000), + disbursementDay, disbursementDay2)); + + loanTransactionHelper.approveLoan(loanId.get(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)) + .dateFormat(DATETIME_PATTERN).approvedOnDate(disbursementDay).locale("en")); + + loanTransactionHelper.disburseLoan(loanId.get(), new PostLoansLoanIdRequest().actualDisbursementDate(disbursementDay) + .dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(500.0)).locale("en")); + + chargePenalty(loanId.get(), 30.0, repaymentPeriod1DueDate); + chargeFee(loanId.get(), 40.0, repaymentPeriod1DueDate); + + addRepaymentForLoan(loanId.get(), 650.0, repaymentDate1); + + verifyTransactions(loanId.get(), transaction(650.0, "Repayment", repaymentDate1, 0.0, 500.0, 0.0, 40.0, 30.0, 0.0, 80.0, false), // + transaction(70.0, "Accrual", repaymentDate1, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false), // + transaction(70.0, "Accrual Activity", repaymentDate1, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false), // + transaction(500.0, "Disbursement", disbursementDay, 500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false)); // + + }); + runAt(disbursementDay2, () -> { + + loanTransactionHelper.disburseLoan(loanId.get(), new PostLoansLoanIdRequest().actualDisbursementDate(disbursementDay2) + .dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(500.0)).locale("en")); + + verifyTransactions(loanId.get(), transaction(650.0, "Repayment", repaymentDate1, 0.0, 500.0, 0.0, 40.0, 30.0, 0.0, 80.0, false), // + transaction(70.0, "Accrual", repaymentDate1, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false), // + transaction(500.0, "Disbursement", disbursementDay2, 420.0, 0.0, 0.0, 0.0, 0.0, 0.0, 80.0, false), // + transaction(500.0, "Disbursement", disbursementDay, 500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false)); // + + }); + runAt(repaymentPeriod1CloseDate, () -> { + + inlineLoanCOBHelper.executeInlineCOB(List.of(loanId.get())); + + verifyTransactions(loanId.get(), transaction(650.0, "Repayment", repaymentDate1, 0.0, 500.0, 0.0, 40.0, 30.0, 0.0, 80.0, false), // + transaction(70.0, "Accrual", repaymentDate1, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false), // + transaction(500.0, "Disbursement", disbursementDay2, 420.0, 0.0, 0.0, 0.0, 0.0, 0.0, 80.0, false), // + transaction(70.0, "Accrual Activity", repaymentPeriod1DueDate, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false), // + transaction(500.0, "Disbursement", disbursementDay, 500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false)); // + + }); + } + + @Test + public void testAccrualActivityPostingForProgressiveMultiDisburseLoanWithEarlyRepaymentBackdated2ndDisbursement() { + final String disbursementDay = "01 January 2023"; + final String disbursementDay2 = "25 January 2023"; + final String repaymentDate1 = "15 January 2023"; + final String repaymentPeriod1DueDate = "01 February 2023"; + final String repaymentPeriod1CloseDate = "02 February 2023"; + final String creationBusinessDay = "15 January 2023"; + AtomicReference loanId = new AtomicReference<>(); + runAt(creationBusinessDay, () -> { + Long localLoanProductId = createLoanProductAccountingAccrualPeriodicAdvancedPaymentAllocation(true); + + loanId.set(applyForLoanApplicationAdvancedPaymentAllocation(client.getClientId(), localLoanProductId, BigDecimal.valueOf(40000), + disbursementDay, disbursementDay2)); + + loanTransactionHelper.approveLoan(loanId.get(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)) + .dateFormat(DATETIME_PATTERN).approvedOnDate(disbursementDay).locale("en")); + + loanTransactionHelper.disburseLoan(loanId.get(), new PostLoansLoanIdRequest().actualDisbursementDate(disbursementDay) + .dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(500.0)).locale("en")); + + chargePenalty(loanId.get(), 30.0, repaymentPeriod1DueDate); + chargeFee(loanId.get(), 40.0, repaymentPeriod1DueDate); + + addRepaymentForLoan(loanId.get(), 650.0, repaymentDate1); + + verifyTransactions(loanId.get(), transaction(650.0, "Repayment", repaymentDate1, 0.0, 500.0, 0.0, 40.0, 30.0, 0.0, 80.0, false), // + transaction(70.0, "Accrual", repaymentDate1, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false), // + transaction(70.0, "Accrual Activity", repaymentDate1, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false), // + transaction(500.0, "Disbursement", disbursementDay, 500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false)); // + + }); + runAt(repaymentPeriod1CloseDate, () -> { + + inlineLoanCOBHelper.executeInlineCOB(List.of(loanId.get())); + + verifyTransactions(loanId.get(), transaction(650.0, "Repayment", repaymentDate1, 0.0, 500.0, 0.0, 40.0, 30.0, 0.0, 80.0, false), // + transaction(70.0, "Accrual", repaymentDate1, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false), // + transaction(70.0, "Accrual Activity", repaymentDate1, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false), // + transaction(500.0, "Disbursement", disbursementDay, 500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false)); // + + loanTransactionHelper.disburseLoan(loanId.get(), new PostLoansLoanIdRequest().actualDisbursementDate(disbursementDay2) + .dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(500.0)).locale("en")); + + verifyTransactions(loanId.get(), transaction(650.0, "Repayment", repaymentDate1, 0.0, 500.0, 0.0, 40.0, 30.0, 0.0, 80.0, false), // + transaction(70.0, "Accrual", repaymentDate1, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false), // + transaction(500.0, "Disbursement", disbursementDay2, 420.0, 0.0, 0.0, 0.0, 0.0, 0.0, 80.0, false), // + transaction(70.0, "Accrual Activity", repaymentPeriod1DueDate, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false), // + transaction(500.0, "Disbursement", disbursementDay, 500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false)); // + + }); + } + + @Test + public void testAccrualActivityPostingForMultiDisburseLoanWithEarlyRepaymentBackdated2ndDisbursement() { + final String disbursementDay = "01 January 2023"; + final String disbursementDay2 = "25 January 2023"; + final String repaymentDate1 = "15 January 2023"; + final String repaymentPeriod1DueDate = "01 February 2023"; + final String repaymentPeriod1CloseDate = "02 February 2023"; + final String creationBusinessDay = "15 January 2023"; + AtomicReference loanId = new AtomicReference<>(); + runAt(creationBusinessDay, () -> { + Long localLoanProductId = createLoanProductAccountingAccrualPeriodicWithInterest(true); + loanId.set(applyForLoanApplicationWithInterest(client.getClientId(), localLoanProductId, BigDecimal.valueOf(40000), + disbursementDay, disbursementDay2)); + + loanTransactionHelper.approveLoan(loanId.get(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)) + .dateFormat(DATETIME_PATTERN).approvedOnDate(disbursementDay).locale("en")); + + loanTransactionHelper.disburseLoan(loanId.get(), new PostLoansLoanIdRequest().actualDisbursementDate(disbursementDay) + .dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(500.0)).locale("en")); + + chargePenalty(loanId.get(), 30.0, repaymentPeriod1DueDate); + chargeFee(loanId.get(), 40.0, repaymentPeriod1DueDate); + + addRepaymentForLoan(loanId.get(), 650.0, repaymentDate1); + + verifyTransactions(loanId.get(), transaction(109.45, "Accrual", repaymentDate1, 0.0, 0.0, 39.45, 40.0, 30.0, 0.0, 0.0, false), // + transaction(109.45, "Accrual Activity", repaymentDate1, 0.0, 0.0, 39.45, 40.0, 30.0, 0.0, 0.0, false), // + transaction(650.0, "Repayment", repaymentDate1, 0.0, 500.0, 39.45, 40.0, 30.0, 0.0, 40.55, false), // + transaction(500.0, "Disbursement", disbursementDay, 500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false)); // + }); + runAt(repaymentPeriod1CloseDate, () -> { + + inlineLoanCOBHelper.executeInlineCOB(List.of(loanId.get())); + + verifyTransactions(loanId.get(), transaction(109.45, "Accrual", repaymentDate1, 0.0, 0.0, 39.45, 40.0, 30.0, 0.0, 0.0, false), // + transaction(109.45, "Accrual Activity", repaymentDate1, 0.0, 0.0, 39.45, 40.0, 30.0, 0.0, 0.0, false), // + transaction(650.0, "Repayment", repaymentDate1, 0.0, 500.0, 39.45, 40.0, 30.0, 0.0, 40.55, false), // + transaction(500.0, "Disbursement", disbursementDay, 500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false)); // + + loanTransactionHelper.disburseLoan(loanId.get(), new PostLoansLoanIdRequest().actualDisbursementDate(disbursementDay2) + .dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(500.0)).locale("en")); + + verifyTransactions(loanId.get(), + transaction(500.0, "Disbursement", disbursementDay, 500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), // + transaction(89.72, "Accrual Activity", repaymentPeriod1DueDate, 0.0, 0.0, 19.72, 40.0, 30.0, 0.0, 0.0, false), // + transaction(500.0, "Disbursement", disbursementDay2, 479.16, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), // + transaction(650.0, "Repayment", repaymentDate1, 0.0, 520.84, 59.16, 40.0, 30.0, 0.0, 0.0, false)); // + + }); + } + + @Test + public void testAccrualActivityPostingForMultiDisburseProgressiveLoan() { + final String disbursementDay = "01 January 2023"; + final String disbursementDay2 = "02 February 2023"; + final String repaymentDate1 = "15 January 2023"; + final String repaymentPeriod1DueDate = "01 February 2023"; + final String repaymentPeriod1CloseDate = "02 February 2023"; + final String repaymentPeriod2CloseDate = "02 March 2023"; + final String creationBusinessDay = "15 January 2023"; + AtomicReference loanId = new AtomicReference<>(); + runAt(creationBusinessDay, () -> { + Long localLoanProductId = createLoanProductAccountingAccrualPeriodicAdvancedPaymentAllocation(true); + loanId.set(applyForLoanApplicationAdvancedPaymentAllocation(client.getClientId(), localLoanProductId, BigDecimal.valueOf(1000), + disbursementDay, disbursementDay2)); + loanTransactionHelper.approveLoan(loanId.get(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)) + .dateFormat(DATETIME_PATTERN).approvedOnDate(disbursementDay).locale("en")); + + loanTransactionHelper.disburseLoan(loanId.get(), new PostLoansLoanIdRequest().actualDisbursementDate(disbursementDay) + .dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(500.0)).locale("en")); + chargePenalty(loanId.get(), 30.0, repaymentPeriod1DueDate); + chargeFee(loanId.get(), 40.0, repaymentPeriod1DueDate); + + addRepaymentForLoan(loanId.get(), 570.0, repaymentDate1); + + verifyTransactions(loanId.get(), transaction(500.0, "Disbursement", disbursementDay, 500.0, 0, 0, 0, 0, 0, 0, false), + transaction(570, "Repayment", repaymentDate1, 0.0, 500.0, 0, 40.0, 30.0, 0, 0, false), + transaction(70.0, "Accrual", repaymentDate1, 0, 0, 0.0, 40.0, 30.0, 0, 0, false), + transaction(70.0, "Accrual Activity", repaymentDate1, 0, 0, 0.0, 40.0, 30.0, 0, 0, false)); + }); + runAt(repaymentPeriod1CloseDate, () -> { + + inlineLoanCOBHelper.executeInlineCOB(List.of(loanId.get())); + + verifyTransactions(loanId.get(), transaction(500.0, "Disbursement", disbursementDay, 500.0, 0, 0, 0, 0, 0, 0, false), + transaction(570, "Repayment", repaymentDate1, 0.0, 500.0, 0, 40.0, 30.0, 0, 0, true), + transaction(70.0, "Accrual", repaymentDate1, 0, 0, 0.0, 40.0, 30.0, 0, 0, false), + transaction(70.0, "Accrual Activity", repaymentDate1, 0, 0, 0.0, 40.0, 30.0, 0, 0, false)); + + loanTransactionHelper.disburseLoan(loanId.get(), new PostLoansLoanIdRequest().actualDisbursementDate(disbursementDay2) + .dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(500.0)).locale("en")); + + verifyTransactions(loanId.get(), transaction(500.0, "Disbursement", disbursementDay, 500.0, 0, 0, 0, 0, 0, 0, false), + transaction(570, "Repayment", repaymentDate1, 0.0, 500.0, 0, 40.0, 30.0, 0, 0, false), + transaction(70.0, "Accrual", repaymentDate1, 0, 0, 0.0, 40.0, 30.0, 0, 0, false), + transaction(70.0, "Accrual Activity", repaymentPeriod1DueDate, 0, 0, 0.0, 40.0, 30.0, 0, 0, false), + transaction(500.0, "Disbursement", disbursementDay2, 500, 0, 0, 0, 0, 0, 0, false)); + }); + runAt(repaymentPeriod2CloseDate, () -> { + + inlineLoanCOBHelper.executeInlineCOB(List.of(loanId.get())); + + verifyTransactions(loanId.get(), transaction(500.0, "Disbursement", disbursementDay, 500.0, 0, 0, 0, 0, 0, 0, false), + transaction(70.0, "Accrual", repaymentDate1, 0, 0, 0.0, 40.0, 30.0, 0, 0, false), + transaction(570.0, "Repayment", repaymentDate1, 0.0, 500.0, 0, 40.0, 30.0, 0, 0, false), + transaction(70.0, "Accrual Activity", repaymentPeriod1DueDate, 0, 0, 0.0, 40.0, 30.0, 0, 0, false), + transaction(500.0, "Disbursement", disbursementDay2, 500.0, 0, 0, 0, 0, 0, 0, false)); + }); + } + + @Test + public void testAccrualActivityPostingForMultiDisburseLoan() { + final String disbursementDay = "01 January 2023"; + final String disbursementDay2 = "02 February 2023"; + final String repaymentDate1 = "15 January 2023"; + final String repaymentPeriod1DueDate = "01 February 2023"; + final String repaymentPeriod1CloseDate = "02 February 2023"; + final String repaymentPeriod2DueDate = "01 March 2023"; + final String repaymentPeriod2CloseDate = "02 March 2023"; + final String creationBusinessDay = "15 January 2023"; + AtomicReference loanId = new AtomicReference<>(); + runAt(creationBusinessDay, () -> { + Long localLoanProductId = createLoanProductAccountingAccrualPeriodicWithInterest(true); + loanId.set(applyForLoanApplicationWithInterest(client.getClientId(), localLoanProductId, BigDecimal.valueOf(40000), + disbursementDay, disbursementDay2)); + loanTransactionHelper.approveLoan(loanId.get(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)) + .dateFormat(DATETIME_PATTERN).approvedOnDate(disbursementDay).locale("en")); + + loanTransactionHelper.disburseLoan(loanId.get(), new PostLoansLoanIdRequest().actualDisbursementDate(disbursementDay) + .dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(500.0)).locale("en")); + chargePenalty(loanId.get(), 30.0, repaymentPeriod1DueDate); + chargeFee(loanId.get(), 40.0, repaymentPeriod1DueDate); + addRepaymentForLoan(loanId.get(), 650.0, repaymentDate1); + verifyTransactions(loanId.get(), + transaction(650.0, "Repayment", repaymentDate1, 0.0, 500.0, 39.45, 40.0, 30.0, 0.0, 40.55, false), // transaction(109.45, + // "Accrual", + // repaymentDate1, + // 0.0, + // 0, + // 39.45, + // 40.0, + // 30.0, + // 0, + // 0, + // false), + // // + transaction(109.45, "Accrual Activity", repaymentDate1, 0.0, 0.0, 39.45, 40.0, 30.0, 0.0, 0.0, false), // + transaction(109.45, "Accrual", repaymentDate1, 0.0, 0.0, 39.45, 40.0, 30.0, 0.0, 0.0, false), // + transaction(500.0, "Disbursement", disbursementDay, 500.0, 0, 0, 0, 0, 0, 0, false) // + ); + }); + runAt(repaymentPeriod1CloseDate, () -> { + inlineLoanCOBHelper.executeInlineCOB(List.of(loanId.get())); + verifyTransactions(loanId.get(), + transaction(650.0, "Repayment", repaymentDate1, 0.0, 500.0, 39.45, 40.0, 30.0, 0.0, 40.55, false), // transaction(109.45, + // "Accrual", + // repaymentDate1, + // 0.0, + // 0, + // 39.45, + // 40.0, + // 30.0, + // 0, + // 0, + // false), + // // + transaction(109.45, "Accrual Activity", repaymentDate1, 0.0, 0.0, 39.45, 40.0, 30.0, 0.0, 0.0, false), // + transaction(109.45, "Accrual", repaymentDate1, 0.0, 0.0, 39.45, 40.0, 30.0, 0.0, 0.0, false), // + transaction(500.0, "Disbursement", disbursementDay, 500.0, 0, 0, 0, 0, 0, 0, false) // + ); + loanTransactionHelper.disburseLoan(loanId.get(), new PostLoansLoanIdRequest().actualDisbursementDate(disbursementDay2) + .dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(500.0)).locale("en")); + verifyTransactions(loanId.get(), + transaction(500.0, "Disbursement", disbursementDay2, 479.16, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), // + transaction(650.0, "Repayment", repaymentDate1, 0.0, 520.84, 59.16, 40.0, 30.0, 0.0, 0.0, false), // + transaction(89.72, "Accrual Activity", repaymentPeriod1DueDate, 0.0, 0.0, 19.72, 40.0, 30.0, 0.0, 0.0, false), // + transaction(500.0, "Disbursement", disbursementDay, 500.0, 0, 0, 0, 0, 0, 0, false) // + ); + }); + runAt(repaymentPeriod2CloseDate, () -> { + inlineLoanCOBHelper.executeInlineCOB(List.of(loanId.get())); + verifyTransactions(loanId.get(), + transaction(500.0, "Disbursement", disbursementDay2, 479.16, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), // + transaction(650.0, "Repayment", repaymentDate1, 0.0, 520.84, 59.16, 40.0, 30.0, 0.0, 0.0, false), // + transaction(89.72, "Accrual Activity", repaymentPeriod1DueDate, 0.0, 0.0, 19.72, 40.0, 30.0, 0.0, 0.0, false), // + transaction(89.72, "Accrual", repaymentPeriod1DueDate, 0.0, 0.0, 19.72, 40.0, 30.0, 0.0, 0.0, false), // + transaction(19.72, "Accrual", repaymentPeriod2DueDate, 0.0, 0.0, 19.72, 0.0, 0.0, 0.0, 0.0, false), // + transaction(19.72, "Accrual Activity", repaymentPeriod2DueDate, 0.0, 0.0, 19.72, 0.0, 0.0, 0.0, 0.0, false), // + transaction(500.0, "Disbursement", disbursementDay, 500.0, 0, 0, 0, 0, 0, 0, false) // + ); + }); + } + + @Test + public void test() { + final String disbursementDay = "01 January 2023"; + final String repaymentDate1 = "15 January 2023"; + final String repaymentPeriod1DueDate = "31 January 2023"; + final String repaymentPeriod1CloseDate = "01 February 2023"; + final String creationBusinessDay = "15 January 2023"; + AtomicReference loanId = new AtomicReference<>(); + runAt(creationBusinessDay, () -> { + Long localLoanProductId = loanTransactionHelper + .createLoanProduct(loanProductsRequestPin30InterestDecliningBalanceDailyRecalculationCompoundingNoneAccrualActivity()) + .getResourceId(); + loanId.set(applyForLoanApplication(client.getClientId(), localLoanProductId, BigDecimal.valueOf(1000), disbursementDay)); + loanTransactionHelper.approveLoan(loanId.get(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)) + .dateFormat(DATETIME_PATTERN).approvedOnDate(disbursementDay).locale("en")); + + loanTransactionHelper.disburseLoan(loanId.get(), new PostLoansLoanIdRequest().actualDisbursementDate(disbursementDay) + .dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(1000.0)).locale("en")); + }); + runAt(repaymentPeriod1CloseDate, () -> { + inlineLoanCOBHelper.executeInlineCOB(List.of(loanId.get())); + verifyTransactions(loanId.get(), + transaction(1.64, "Accrual", repaymentPeriod1DueDate, 0.0, 0.0, 1.64, 0.0, 0.0, 0.0, 0.0, false), // + transaction(1.64, "Accrual Activity", repaymentPeriod1DueDate, 0.0, 0.0, 1.64, 0.0, 0.0, 0.0, 0.0, false), // + transaction(1000.0, "Disbursement", disbursementDay, 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false)); + loanTransactionHelper.makeLoanRepayment(repaymentDate1, 150.0F, loanId.get().intValue()); + + verifyTransactions(loanId.get(), + transaction(150.0, "Repayment", repaymentDate1, 851.52, 148.48, 1.52, 0.0, 0.0, 0.0, 0.0, false), // + transaction(1.52, "Accrual Activity", repaymentPeriod1DueDate, 0.0, 0.0, 1.52, 0.0, 0.0, 0.0, 0.0, false), // + transaction(1.52, "Accrual", repaymentPeriod1DueDate, 0.0, 0.0, 1.52, 0.0, 0.0, 0.0, 0.0, false), // + transaction(1000.0, "Disbursement", disbursementDay, 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false)); + }); + } + + private PostLoanProductsRequest loanProductsRequestPin30InterestDecliningBalanceDailyRecalculationCompoundingNoneAccrualActivity() { + String name = Utils.uniqueRandomStringGenerator("LOAN_PRODUCT_", 6); + String shortName = Utils.uniqueRandomStringGenerator("", 4); + + List principalVariationsForBorrowerCycle = new ArrayList<>(); + List numberOfRepaymentVariationsForBorrowerCycle = new ArrayList<>(); + List interestRateVariationsForBorrowerCycle = new ArrayList<>(); + List charges = new ArrayList<>(); + List penaltyToIncomeAccountMappings = new ArrayList<>(); + List feeToIncomeAccountMappings = new ArrayList<>(); + + List paymentChannelToFundSourceMappings = new ArrayList<>(); + GetLoanPaymentChannelToFundSourceMappings loanPaymentChannelToFundSourceMappings = new GetLoanPaymentChannelToFundSourceMappings(); + loanPaymentChannelToFundSourceMappings.fundSourceAccountId(fundSource.getAccountID().longValue()); + loanPaymentChannelToFundSourceMappings.paymentTypeId(1L); + paymentChannelToFundSourceMappings.add(loanPaymentChannelToFundSourceMappings); + return new PostLoanProductsRequest()// + .name(name)// + .enableAccrualActivityPosting(true)// + .shortName(shortName)// + .description( + "LP1 with 12% DECLINING BALANCE interest, interest period: Daily, Interest recalculation-Daily, Compounding:none")// + .fundId(1L)// + .startDate(null)// + .closeDate(null)// + .includeInBorrowerCycle(false)// + .currencyCode("EUR")// + .digitsAfterDecimal(2)// + .inMultiplesOf(1)// + .installmentAmountInMultiplesOf(1)// + .useBorrowerCycle(false)// + .minPrincipal(100.0)// + .principal(1000.0)// + .maxPrincipal(10000.0)// + .minNumberOfRepayments(1)// + .numberOfRepayments(1)// + .maxNumberOfRepayments(30)// + .isLinkedToFloatingInterestRates(false)// + .minInterestRatePerPeriod(0.0)// + .interestRatePerPeriod(12.0)// + .maxInterestRatePerPeriod(30.0)// + .interestRateFrequencyType(3)// + .repaymentEvery(30)// + .repaymentFrequencyType(0L)// + .principalVariationsForBorrowerCycle(principalVariationsForBorrowerCycle)// + .numberOfRepaymentVariationsForBorrowerCycle(numberOfRepaymentVariationsForBorrowerCycle)// + .interestRateVariationsForBorrowerCycle(interestRateVariationsForBorrowerCycle)// + .amortizationType(1)// + .interestType(0)// + .isEqualAmortization(false)// + .interestCalculationPeriodType(0)// + .transactionProcessingStrategyCode("mifos-standard-strategy")// + .daysInYearType(1)// + .daysInMonthType(1)// + .canDefineInstallmentAmount(true)// + .graceOnArrearsAgeing(3)// + .overdueDaysForNPA(179)// + .accountMovesOutOfNPAOnlyOnArrearsCompletion(false)// + .principalThresholdForLastInstallment(50)// + .allowVariableInstallments(false)// + .canUseForTopup(false)// + .holdGuaranteeFunds(false)// + .multiDisburseLoan(false)// + .allowAttributeOverrides(new AllowAttributeOverrides()// + .amortizationType(true)// + .interestType(true)// + .transactionProcessingStrategyCode(true)// + .interestCalculationPeriodType(true)// + .inArrearsTolerance(true)// + .repaymentEvery(true)// + .graceOnPrincipalAndInterestPayment(true)// + .graceOnArrearsAgeing(true)) + .outstandingLoanBalance(10000.0)// + .charges(charges)// + .accountingRule(3)// + + .fundSourceAccountId(suspenseAccount.getAccountID().longValue())// + .loanPortfolioAccountId(loansReceivableAccount.getAccountID().longValue())// + .transfersInSuspenseAccountId(suspenseAccount.getAccountID().longValue())// + .interestOnLoanAccountId(interestIncomeAccount.getAccountID().longValue())// + .incomeFromFeeAccountId(feeIncomeAccount.getAccountID().longValue())// + .incomeFromPenaltyAccountId(penaltyIncomeAccount.getAccountID().longValue())// + .incomeFromRecoveryAccountId(recoveriesAccount.getAccountID().longValue())// + .writeOffAccountId(writtenOffAccount.getAccountID().longValue())// + .overpaymentLiabilityAccountId(overpaymentAccount.getAccountID().longValue())// + .receivableInterestAccountId(interestReceivableAccount.getAccountID().longValue())// + .receivableFeeAccountId(feeReceivableAccount.getAccountID().longValue())// + .receivablePenaltyAccountId(penaltyReceivableAccount.getAccountID().longValue())// + .goodwillCreditAccountId(goodwillExpenseAccount.getAccountID().longValue())// + .incomeFromGoodwillCreditInterestAccountId(interestIncomeChargeOffAccount.getAccountID().longValue())// + .incomeFromGoodwillCreditFeesAccountId(feeChargeOffAccount.getAccountID().longValue())// + .incomeFromGoodwillCreditPenaltyAccountId(feeChargeOffAccount.getAccountID().longValue())// + .incomeFromChargeOffInterestAccountId(interestIncomeChargeOffAccount.getAccountID().longValue())// + .incomeFromChargeOffFeesAccountId(feeChargeOffAccount.getAccountID().longValue())// + .chargeOffExpenseAccountId(chargeOffExpenseAccount.getAccountID().longValue())// + .chargeOffFraudExpenseAccountId(chargeOffFraudExpenseAccount.getAccountID().longValue())// + .incomeFromChargeOffPenaltyAccountId(penaltyChargeOffAccount.getAccountID().longValue())// + + .dateFormat("dd MMMM yyyy")// + .locale("en")// + .disallowExpectedDisbursements(false)// + .allowApprovedDisbursedAmountsOverApplied(false)// + .delinquencyBucketId(1L)// + .paymentChannelToFundSourceMappings(paymentChannelToFundSourceMappings)// + .penaltyToIncomeAccountMappings(penaltyToIncomeAccountMappings)// + .feeToIncomeAccountMappings(feeToIncomeAccountMappings)// + .isInterestRecalculationEnabled(true)// + .preClosureInterestCalculationStrategy(1)// + .rescheduleStrategyMethod(3)// + .interestRecalculationCompoundingMethod(0)// + .recalculationRestFrequencyType(2)// + .recalculationRestFrequencyInterval(1)// + .allowPartialPeriodInterestCalcualtion(false);// + + } + private void chargeFee(Long loanId, Double amount, String dueDate) { - LOG.info("----------------------------------CHARGE FEE---------------------------------"); - LOG.info("FEE amount {} dueDate {}", amount, dueDate); + LOG.info("Charge FEE amount {} dueDate {}", amount, dueDate); PostChargesResponse feeCharge = chargesHelper.createCharges(new PostChargesRequest().penalty(false).amount(9.0) .chargeCalculationType(ChargeCalculationType.FLAT.getValue()).chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE.getValue()) .chargePaymentMode(ChargePaymentMode.REGULAR.getValue()).currencyCode("USD") @@ -548,8 +1095,7 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati } private void chargePenalty(Long loanId, Double amount, String dueDate) { - LOG.info("----------------------------------CHARGE PENALTY---------------------------------"); - LOG.info("PENALTY amount {} dueDate {}", amount, dueDate); + LOG.info("Charge PENALTY amount {} dueDate {}", amount, dueDate); PostChargesResponse penaltyCharge = chargesHelper.createCharges(new PostChargesRequest().penalty(true).amount(10.0) .chargeCalculationType(ChargeCalculationType.FLAT.getValue()).chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE.getValue()) .chargePaymentMode(ChargePaymentMode.REGULAR.getValue()).currencyCode("USD") @@ -560,21 +1106,25 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati .amount(amount).dueDate(dueDate)); assertNotNull(penaltyLoanChargeResult); assertNotNull(penaltyLoanChargeResult.getResourceId()); - LOG.info("----------------------------------CHARGE PENALTY---------------------------------"); } private Long createLoanProductAccountingAccrualPeriodicWithInterest() { - LOG.info("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------"); + return createLoanProductAccountingAccrualPeriodicWithInterest(false); + } + + private Long createLoanProductAccountingAccrualPeriodicWithInterest(boolean isMultiDisburse) { String name = Utils.uniqueRandomStringGenerator("LOAN_PRODUCT_", 6); String shortName = Utils.uniqueRandomStringGenerator("", 4); - return loanTransactionHelper.createLoanProduct(new PostLoanProductsRequest().name(name).shortName(shortName) + Long resourceId = loanTransactionHelper.createLoanProduct(new PostLoanProductsRequest().name(name).shortName(shortName) + .multiDisburseLoan(isMultiDisburse).maxTrancheCount(isMultiDisburse ? 2 : 1).interestType(isMultiDisburse ? 0 : 1) + .interestCalculationPeriodType(isMultiDisburse ? 0 : 1).disallowExpectedDisbursements(isMultiDisburse) .description("Test loan description").currencyCode("USD").digitsAfterDecimal(2).daysInYearType(1).daysInMonthType(1) .interestRecalculationCompoundingMethod(0).recalculationRestFrequencyType(1).rescheduleStrategyMethod(1) .recalculationRestFrequencyInterval(0).isInterestRecalculationEnabled(false).interestRateFrequencyType(2).locale("en_GB") .numberOfRepayments(4).repaymentFrequencyType(2L).interestRatePerPeriod(2.0).repaymentEvery(1).minPrincipal(100.0) - .principal(1000.0).maxPrincipal(10000000.0).amortizationType(1).interestType(1).interestCalculationPeriodType(1) - .dateFormat("dd MMMM yyyy").transactionProcessingStrategyCode(DEFAULT_STRATEGY).accountingRule(3) - .enableAccrualActivityPosting(true).fundSourceAccountId(fundSource.getAccountID().longValue())// + .principal(1000.0).maxPrincipal(10000000.0).amortizationType(1).dateFormat("dd MMMM yyyy") + .transactionProcessingStrategyCode(DEFAULT_STRATEGY).accountingRule(3).enableAccrualActivityPosting(true) + .fundSourceAccountId(fundSource.getAccountID().longValue())// .loanPortfolioAccountId(loansReceivableAccount.getAccountID().longValue())// .transfersInSuspenseAccountId(suspenseAccount.getAccountID().longValue())// .interestOnLoanAccountId(interestIncomeAccount.getAccountID().longValue())// @@ -596,32 +1146,68 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati .chargeOffFraudExpenseAccountId(chargeOffFraudExpenseAccount.getAccountID().longValue())// .incomeFromChargeOffPenaltyAccountId(penaltyChargeOffAccount.getAccountID().longValue())// ).getResourceId(); + LOG.info("Test Loan Product With Interest Id {} isMultiDisburse {} http://localhost:4200/#/products/loan-products/{1}/general", + resourceId, isMultiDisburse); + return resourceId; + } + + private static Long applyForLoanApplication(final Long clientID, final Long loanProductID, BigDecimal principal, + String applicationDisbursementDate) { + final PostLoansRequest loanRequest = new PostLoansRequest() // + .locale("en_GB").dateFormat("dd MMMM yyyy").expectedDisbursementDate(applicationDisbursementDate) + .submittedOnDate(applicationDisbursementDate).interestCalculationPeriodType(0).repaymentFrequencyType(0).repaymentEvery(30) + .principal(BigDecimal.valueOf(1000)).loanTermFrequency(120).loanTermFrequencyType(0) + .transactionProcessingStrategyCode(DEFAULT_STRATEGY).interestType(0).loanType("individual").numberOfRepayments(4) + .amortizationType(1).interestRatePerPeriod(BigDecimal.valueOf(2)).clientId(clientID).productId(loanProductID); + Long loanId = loanTransactionHelper.applyLoan(loanRequest).getLoanId(); + LOG.info("Test Loan http://localhost:4200/#/clients/{}/loans-accounts/{}/transactions", client.getClientId(), loanId); + return loanId; } private static Long applyForLoanApplicationWithInterest(final Long clientID, final Long loanProductID, BigDecimal principal, String applicationDisbursementDate) { - LOG.info("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------"); final PostLoansRequest loanRequest = new PostLoansRequest() // .loanTermFrequency(4).locale("en_GB").loanTermFrequencyType(2).numberOfRepayments(4).repaymentFrequencyType(2) .interestRatePerPeriod(BigDecimal.valueOf(2)).repaymentEvery(1).principal(principal).amortizationType(1).interestType(1) .interestCalculationPeriodType(1).dateFormat("dd MMMM yyyy").transactionProcessingStrategyCode(DEFAULT_STRATEGY) .loanType("individual").expectedDisbursementDate(applicationDisbursementDate).submittedOnDate(applicationDisbursementDate) .clientId(clientID).productId(loanProductID); - return loanTransactionHelper.applyLoan(loanRequest).getLoanId(); + Long loanId = loanTransactionHelper.applyLoan(loanRequest).getLoanId(); + LOG.info("Test Loan with Interest Id {2} http://localhost:4200/#/clients/{}/loans-accounts/{}/transactions", client.getClientId(), + loanId); + return loanId; + } + + private static Long applyForLoanApplicationWithInterest(final Long clientID, final Long loanProductID, BigDecimal principal, + String applicationDisbursementDate, String applicationDisbursementDate2) { + final PostLoansRequest loanRequest = new PostLoansRequest() // + .loanTermFrequency(4).locale("en_GB").loanTermFrequencyType(2).numberOfRepayments(4).repaymentFrequencyType(2) + .interestRatePerPeriod(BigDecimal.valueOf(2)).repaymentEvery(1).principal(principal).amortizationType(1).interestType(1) + .interestCalculationPeriodType(0).dateFormat("dd MMMM yyyy").transactionProcessingStrategyCode(DEFAULT_STRATEGY) + .loanType("individual").submittedOnDate(applicationDisbursementDate).expectedDisbursementDate(applicationDisbursementDate2) + .clientId(clientID).productId(loanProductID); + Long loanId = loanTransactionHelper.applyLoan(loanRequest).getLoanId(); + LOG.info("Test Loan with Interest Id MultiDisbursed {2} http://localhost:4200/#/clients/{}/loans-accounts/{}/transactions", + client.getClientId(), loanId); + return loanId; } private Long createLoanProductAccountingAccrualPeriodicAdvancedPaymentAllocation() { - LOG.info("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------"); + return createLoanProductAccountingAccrualPeriodicAdvancedPaymentAllocation(false); + } + + private Long createLoanProductAccountingAccrualPeriodicAdvancedPaymentAllocation(boolean isMultiDisburse) { String name = Utils.uniqueRandomStringGenerator("LOAN_PRODUCT_", 6); String shortName = Utils.uniqueRandomStringGenerator("", 4); AdvancedPaymentData defaultAllocation = createDefaultPaymentAllocation(); - return loanTransactionHelper.createLoanProduct(new PostLoanProductsRequest().name(name).shortName(shortName) + Long resourceId = loanTransactionHelper.createLoanProduct(new PostLoanProductsRequest().name(name).shortName(shortName) + .multiDisburseLoan(isMultiDisburse).maxTrancheCount(isMultiDisburse ? 2 : 1).interestType(isMultiDisburse ? 0 : 1) + .interestCalculationPeriodType(isMultiDisburse ? 0 : 1).disallowExpectedDisbursements(isMultiDisburse) .description("Test loan description").currencyCode("USD").digitsAfterDecimal(2).daysInYearType(1).daysInMonthType(1) .recalculationRestFrequencyType(1).rescheduleStrategyMethod(1).loanScheduleType(LoanScheduleType.PROGRESSIVE.name()) .recalculationRestFrequencyInterval(0).isInterestRecalculationEnabled(false).locale("en_GB").numberOfRepayments(4) .repaymentFrequencyType(2L).repaymentEvery(1).minPrincipal(100.0).principal(1000.0).maxPrincipal(10000000.0) - .amortizationType(1).interestType(0).interestRatePerPeriod(0.0).interestRateFrequencyType(1) - .interestCalculationPeriodType(1).dateFormat("dd MMMM yyyy") + .amortizationType(1).interestRatePerPeriod(0.0).interestRateFrequencyType(1).dateFormat("dd MMMM yyyy") .transactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION_STRATEGY).paymentAllocation(List.of(defaultAllocation)) .accountingRule(3).enableAccrualActivityPosting(true).fundSourceAccountId(fundSource.getAccountID().longValue())// .loanPortfolioAccountId(loansReceivableAccount.getAccountID().longValue())// @@ -645,6 +1231,9 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati .chargeOffFraudExpenseAccountId(chargeOffFraudExpenseAccount.getAccountID().longValue())// .incomeFromChargeOffPenaltyAccountId(penaltyChargeOffAccount.getAccountID().longValue())// ).getResourceId(); + LOG.info("Test Progressive Loan Product Id {} isMultiDisburse {} http://localhost:4200/#/products/loan-products/{1}/general", + resourceId, isMultiDisburse); + return resourceId; } private static List getPaymentAllocationOrder(PaymentAllocationType... paymentAllocationTypes) { @@ -674,7 +1263,6 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati private static Long applyForLoanApplicationAdvancedPaymentAllocation(final Long clientID, final Long loanProductID, BigDecimal principal, String applicationDisbursementDate) { - LOG.info("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------"); final PostLoansRequest loanRequest = new PostLoansRequest() // .loanTermFrequency(4).locale("en_GB").loanTermFrequencyType(2).numberOfRepayments(4).repaymentFrequencyType(2) .repaymentEvery(1).principal(principal).amortizationType(1).interestType(0).interestRatePerPeriod(BigDecimal.ZERO) @@ -682,7 +1270,25 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati .transactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION_STRATEGY).loanType("individual") .expectedDisbursementDate(applicationDisbursementDate).submittedOnDate(applicationDisbursementDate).clientId(clientID) .productId(loanProductID); - return loanTransactionHelper.applyLoan(loanRequest).getLoanId(); + Long loanId = loanTransactionHelper.applyLoan(loanRequest).getLoanId(); + LOG.info("Test Progressive Loan Id {2} http://localhost:4200/#/clients/{}/loans-accounts/{}/transactions", client.getClientId(), + loanId); + return loanId; + } + + private static Long applyForLoanApplicationAdvancedPaymentAllocation(final Long clientID, final Long loanProductID, + BigDecimal principal, String applicationDisbursementDate, String applicationDisbursementDate2) { + final PostLoansRequest loanRequest = new PostLoansRequest() // + .loanTermFrequency(4).locale("en_GB").loanTermFrequencyType(2).numberOfRepayments(4).repaymentFrequencyType(2) + .repaymentEvery(1).principal(principal).amortizationType(1).interestType(0).interestRatePerPeriod(BigDecimal.ZERO) + .interestCalculationPeriodType(0).dateFormat("dd MMMM yyyy") + .transactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION_STRATEGY).loanType("individual") + .submittedOnDate(applicationDisbursementDate).clientId(clientID).expectedDisbursementDate(applicationDisbursementDate2) + .productId(loanProductID); + Long loanId = loanTransactionHelper.applyLoan(loanRequest).getLoanId(); + LOG.info("Test Progressive Loan Multi Disbursed Id {2} http://localhost:4200/#/clients/{}/loans-accounts/{}/transactions", + client.getClientId(), loanId); + return loanId; } }