diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java index 00e2687e77..d6dd4643c3 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java @@ -32,6 +32,7 @@ import java.util.List; import java.util.Set; import lombok.Getter; +import lombok.Setter; import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.MathUtil; @@ -74,6 +75,7 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa @Column(name = "interest_completed_derived", scale = 6, precision = 19, nullable = true) private BigDecimal interestPaid; + @Setter @Column(name = "interest_waived_derived", scale = 6, precision = 19, nullable = true) private BigDecimal interestWaived; 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 38fe5ccbcd..3763c7269b 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 @@ -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; @@ -184,6 +186,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; @@ -198,6 +201,7 @@ 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.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; @@ -209,6 +213,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; @@ -279,12 +284,14 @@ 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; @Transactional @Override @@ -3260,7 +3267,7 @@ public CommandProcessingResult chargeOff(JsonCommand command) { final List existingTransactionIds = loan.findExistingTransactionIds(); final List existingReversedTransactionIds = loan.findExistingReversedTransactionIds(); - LoanTransaction chargeOffTransaction = LoanTransaction.chargeOff(loan, transactionDate, txnExternalId); + final LoanTransaction chargeOffTransaction = LoanTransaction.chargeOff(loan, transactionDate, txnExternalId); loanTransactionRepository.saveAndFlush(chargeOffTransaction); loan.addLoanTransaction(chargeOffTransaction); saveAndFlushLoanWithDataIntegrityViolationChecks(loan); @@ -3274,6 +3281,38 @@ public CommandProcessingResult chargeOff(JsonCommand command) { postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); businessEventNotifierService.notifyPostBusinessEvent(new LoanChargeOffPostBusinessEvent(chargeOffTransaction)); + + if (loan.getLoanProduct().isInterestRecalculationEnabled()) { + final Pair result = advancedPaymentScheduleTransactionProcessor + .reprocessProgressiveLoanTransactions(loan.getDisbursementDate(), transactionDate, + loan.retrieveListOfTransactionsForReprocessing(), loan.getLoanRepaymentScheduleDetail().getCurrency(), + loan.getRepaymentScheduleInstallments(), loan.getActiveCharges()); + + final ProgressiveLoanInterestScheduleModel scheduleModel = result.getRight(); + + loan.getRepaymentScheduleInstallments().forEach(installment -> { + if (!installment.getFromDate().isAfter(transactionDate) && installment.getDueDate().isAfter(transactionDate)) { + 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.setInterestWaived(defaultToZeroIfNull(installment.getInterestWaived()).add(interestRemoved)); + installment.updateInterestCharged(newInterest); + } + }); + } else { + calculatePartialPeriodInterest(loan, transactionDate); + } + + loan.getRepaymentScheduleInstallments().forEach(installment -> { + if (installment.getFromDate().isAfter(transactionDate)) { + installment.updatePrincipal(defaultToZeroIfNull(installment.getPrincipal()).add(installment.getInterestCharged())); + installment.setInterestWaived(defaultToZeroIfNull(installment.getInterestWaived()).add(installment.getInterestCharged())); + installment.updateInterestCharged(BigDecimal.ZERO); + } + }); + return new CommandProcessingResultBuilder() // .withCommandId(command.commandId()) // .withEntityId(chargeOffTransaction.getId()) // @@ -3884,4 +3923,27 @@ 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.setInterestWaived(defaultToZeroIfNull(installment.getInterestWaived()).add(interestRemoved)); + installment.updateInterestCharged(interestTillChargeOff); + }); + } + + private BigDecimal defaultToZeroIfNull(final BigDecimal possibleNullValue) { + return possibleNullValue != null ? possibleNullValue : BigDecimal.ZERO; + } } 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 fdfaa60760..377120b0c2 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 @@ -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; @@ -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; @@ -380,7 +382,8 @@ 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) { return new LoanWritePlatformServiceJpaRepositoryImpl(transactionProcessorFactory, context, loanTransactionValidator, loanUpdateCommandFromApiJsonDeserializer, loanRepositoryWrapper, loanAccountDomainService, noteRepository, loanTransactionRepository, loanTransactionRelationRepository, loanAssembler, journalEntryWritePlatformService, @@ -394,8 +397,10 @@ 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); } @Bean