Skip to content

Commit

Permalink
FINERACT-2148: update instalments interest with zero after charge off…
Browse files Browse the repository at this point in the history
… with interest recalculation enabled
  • Loading branch information
oleksii-novikov-onix committed Dec 9, 2024
1 parent bc2ef08 commit 30f53f7
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.github.resilience4j.retry.annotation.Retry;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
Expand Down Expand Up @@ -109,6 +111,9 @@
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.organisation.holiday.domain.Holiday;
import org.apache.fineract.organisation.holiday.domain.HolidayRepositoryWrapper;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.organisation.office.domain.Office;
Expand Down Expand Up @@ -154,13 +159,15 @@
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
import org.apache.fineract.portfolio.loanaccount.command.LoanUpdateCommand;
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
import org.apache.fineract.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData;
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
import org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository;
import org.apache.fineract.portfolio.loanaccount.domain.GroupLoanIndividualMonitoringAccount;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeOffBehaviour;
import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
import org.apache.fineract.portfolio.loanaccount.domain.LoanEvent;
import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
Expand All @@ -184,6 +191,7 @@
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.exception.DateMismatchException;
import org.apache.fineract.portfolio.loanaccount.exception.ExceedingTrancheCountException;
import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanStateTransitionException;
Expand All @@ -198,6 +206,9 @@
import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException;
import org.apache.fineract.portfolio.loanaccount.exception.UndoLastTrancheDisbursementException;
import org.apache.fineract.portfolio.loanaccount.guarantor.service.GuarantorDomainService;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLoanInterestScheduleModel;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelPeriod;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
Expand All @@ -209,6 +220,7 @@
import org.apache.fineract.portfolio.loanaccount.serialization.LoanOfficerValidator;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanTransactionValidator;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanUpdateCommandFromApiJsonDeserializer;
import org.apache.fineract.portfolio.loanproduct.calc.ProgressiveEMICalculator;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.apache.fineract.portfolio.loanproduct.exception.LinkedAccountRequiredException;
import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
Expand Down Expand Up @@ -279,12 +291,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private final AccountTransferRepository accountTransferRepository;
private final LoanTransactionAssembler loanTransactionAssembler;
private final LoanAccrualsProcessingService loanAccrualsProcessingService;
private final AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor;
private final LoanOfficerValidator loanOfficerValidator;
private final LoanDownPaymentTransactionValidator loanDownPaymentTransactionValidator;
private final LoanDisbursementService loanDisbursementService;
private final LoanScheduleService loanScheduleService;
private final LoanChargeValidator loanChargeValidator;
private final LoanOfficerService loanOfficerService;
private final ProgressiveEMICalculator emiCalculator;
private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository;

@Transactional
@Override
Expand Down Expand Up @@ -3260,7 +3275,43 @@ public CommandProcessingResult chargeOff(JsonCommand command) {
final List<Long> existingTransactionIds = loan.findExistingTransactionIds();
final List<Long> existingReversedTransactionIds = loan.findExistingReversedTransactionIds();

LoanTransaction chargeOffTransaction = LoanTransaction.chargeOff(loan, transactionDate, txnExternalId);
if (LoanChargeOffBehaviour.ZERO_INTEREST.equals(loan.getLoanProductRelatedDetail().getChargeOffBehaviour())) {
final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments = loan.getRepaymentScheduleInstallments();

if (loan.isInterestRecalculationEnabled()) {
final Pair<ChangedTransactionDetail, ProgressiveLoanInterestScheduleModel> result = advancedPaymentScheduleTransactionProcessor
.reprocessProgressiveLoanTransactions(loan.getDisbursementDate(), transactionDate,
loan.retrieveListOfTransactionsForReprocessing(), loan.getLoanRepaymentScheduleDetail().getCurrency(),
repaymentScheduleInstallments, loan.getActiveCharges());

final ProgressiveLoanInterestScheduleModel scheduleModel = result.getRight();

repaymentScheduleInstallments.stream()
.filter(installment -> !installment.getFromDate().isAfter(transactionDate) && installment.getDueDate().isAfter(transactionDate))
.forEach(installment -> {
final BigDecimal newInterest = emiCalculator
.getPeriodInterestTillDate(scheduleModel, installment.getDueDate(), transactionDate).getAmount();
final BigDecimal interestRemoved = installment.getInterestCharged().subtract(newInterest);
installment.updatePrincipal(defaultToZeroIfNull(installment.getPrincipal()).add(interestRemoved));
installment.updateInterestCharged(newInterest);
});
} else {
calculatePartialPeriodInterest(loan, transactionDate);
}

repaymentScheduleInstallments.stream()
.filter(installment -> installment.getFromDate().isAfter(transactionDate))
.forEach(installment -> {
installment.updatePrincipal(defaultToZeroIfNull(installment.getPrincipal()).add(installment.getInterestCharged()));
installment.updateInterestCharged(BigDecimal.ZERO);
});

loan.updateLoanSummaryDerivedFields();

updateLastInstallmentPrincipal(loan, repaymentScheduleInstallments);
}

final LoanTransaction chargeOffTransaction = LoanTransaction.chargeOff(loan, transactionDate, txnExternalId);

if (loan.isInterestBearing() && loan.isInterestRecalculationEnabled()
&& DateUtils.isBefore(loan.getInterestRecalculatedOn(), DateUtils.getBusinessLocalDate())) {
Expand Down Expand Up @@ -3292,6 +3343,7 @@ public CommandProcessingResult chargeOff(JsonCommand command) {

postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
businessEventNotifierService.notifyPostBusinessEvent(new LoanChargeOffPostBusinessEvent(chargeOffTransaction));

return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withEntityId(chargeOffTransaction.getId()) //
Expand Down Expand Up @@ -3902,4 +3954,56 @@ public void closeAsMarkedForReschedule(final Loan loan, final JsonCommand comman

loanTransactionValidator.validateLoanRescheduleDate(loan);
}

private void calculatePartialPeriodInterest(final Loan loan, final LocalDate chargeOffDate) {
loan.getRepaymentScheduleInstallments().stream()
.filter(installment -> !installment.getFromDate().isAfter(chargeOffDate) && installment.getDueDate().isAfter(chargeOffDate))
.forEach(installment -> {
final BigDecimal totalInterest = installment.getInterestOutstanding(loan.getCurrency()).getAmount();
final long totalDaysInPeriod = ChronoUnit.DAYS.between(installment.getFromDate(), installment.getDueDate());
final long daysTillChargeOff = ChronoUnit.DAYS.between(installment.getFromDate(), chargeOffDate);

final BigDecimal interestTillChargeOff = totalInterest
.divide(BigDecimal.valueOf(totalDaysInPeriod), 10, RoundingMode.HALF_UP)
.multiply(BigDecimal.valueOf(daysTillChargeOff));

final BigDecimal interestRemoved = totalInterest.subtract(interestTillChargeOff);
installment.updatePrincipal(defaultToZeroIfNull(installment.getPrincipal()).add(interestRemoved));
installment.updateInterestCharged(interestTillChargeOff);
});
}

private BigDecimal defaultToZeroIfNull(final BigDecimal possibleNullValue) {
return possibleNullValue != null ? possibleNullValue : BigDecimal.ZERO;
}

private void updateLastInstallmentPrincipal(final Loan loan,
final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments) {
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(loan.getCurrency());
final CurrencyData currencyData = new CurrencyData(loan.getCurrencyCode(), applicationCurrency.getName(),
loan.getCurrency().getDigitsAfterDecimal(), applicationCurrency.getCurrencyInMultiplesOf(),
applicationCurrency.getDisplaySymbol(), applicationCurrency.getNameCode());
final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData = new RepaymentScheduleRelatedLoanData(
loan.getExpectedDisbursementDate(), loan.getActualDisbursementDate(), currencyData, loan.getPrincipal().getAmount(),
loan.getInArrearsTolerance().getAmount(),
loan.getSummary().getTotalFeeChargesDueAtDisbursement(loan.getCurrency()).getAmount());
final LoanScheduleData repaymentSchedule = this.loanReadPlatformService.retrieveRepaymentSchedule(loan.getId(),
repaymentScheduleRelatedData, loan.getDisbursementData(), loan.isInterestRecalculationEnabled(),
loan.getLoanRepaymentScheduleDetail().getLoanScheduleType());

final int installmentsCount = repaymentScheduleInstallments.size();
repaymentScheduleInstallments.get(installmentsCount - 1)
.updatePrincipal(repaymentSchedule.getPeriods().stream()
.filter(period -> period.getPeriod() != null && period.getPeriod() == (installmentsCount - 1)).findFirst().get()
.getPrincipalLoanBalanceOutstanding());

final int lastInstallmentIndex = repaymentScheduleInstallments.size() - 1;
final Optional<LoanSchedulePeriodData> preLastPeriod = repaymentSchedule.getPeriods().stream()
.filter(period -> period.getPeriod() != null && period.getPeriod() == lastInstallmentIndex).findFirst();

preLastPeriod.ifPresent(loanSchedulePeriodData -> repaymentScheduleInstallments.get(lastInstallmentIndex)
.updatePrincipal(loanSchedulePeriodData.getPrincipalLoanBalanceOutstanding()));

loan.updateLoanSummaryDerivedFields();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
import org.apache.fineract.portfolio.loanaccount.domain.LoanSummaryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.guarantor.service.GuarantorDomainService;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleAssembler;
Expand Down Expand Up @@ -135,6 +136,7 @@
import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformServiceJpaRepositoryImpl;
import org.apache.fineract.portfolio.loanaccount.service.ReplayedTransactionBusinessEventService;
import org.apache.fineract.portfolio.loanaccount.service.ReplayedTransactionBusinessEventServiceImpl;
import org.apache.fineract.portfolio.loanproduct.calc.ProgressiveEMICalculator;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
import org.apache.fineract.portfolio.loanproduct.service.LoanDropdownReadPlatformService;
import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformService;
Expand Down Expand Up @@ -380,7 +382,9 @@ public LoanWritePlatformService loanWritePlatformService(LoanRepaymentScheduleTr
LoanTransactionAssembler loanTransactionAssembler, LoanAccrualsProcessingService loanAccrualsProcessingService,
LoanOfficerValidator loanOfficerValidator, LoanDownPaymentTransactionValidator loanDownPaymentTransactionValidator,
LoanDisbursementService loanDisbursementService, LoanScheduleService loanScheduleService,
LoanChargeValidator loanChargeValidator, LoanOfficerService loanOfficerService) {
LoanChargeValidator loanChargeValidator, LoanOfficerService loanOfficerService,
AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor,
ProgressiveEMICalculator progressiveEMICalculator, ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository) {
return new LoanWritePlatformServiceJpaRepositoryImpl(transactionProcessorFactory, context, loanTransactionValidator,
loanUpdateCommandFromApiJsonDeserializer, loanRepositoryWrapper, loanAccountDomainService, noteRepository,
loanTransactionRepository, loanTransactionRelationRepository, loanAssembler, journalEntryWritePlatformService,
Expand All @@ -394,8 +398,9 @@ public LoanWritePlatformService loanWritePlatformService(LoanRepaymentScheduleTr
postDatedChecksRepository, loanRepaymentScheduleInstallmentRepository, defaultLoanLifecycleStateMachine,
loanAccountLockService, externalIdFactory, replayedTransactionBusinessEventService,
loanAccrualTransactionBusinessEventService, errorHandler, loanDownPaymentHandlerService, accountTransferRepository,
loanTransactionAssembler, loanAccrualsProcessingService, loanOfficerValidator, loanDownPaymentTransactionValidator,
loanDisbursementService, loanScheduleService, loanChargeValidator, loanOfficerService);
loanTransactionAssembler, loanAccrualsProcessingService, advancedPaymentScheduleTransactionProcessor, loanOfficerValidator,
loanDownPaymentTransactionValidator, loanDisbursementService, loanScheduleService, loanChargeValidator, loanOfficerService,
progressiveEMICalculator, applicationCurrencyRepository);
}

@Bean
Expand Down

0 comments on commit 30f53f7

Please sign in to comment.