diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanScheduleAccrualData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanScheduleAccrualData.java index 6fcb359ca54..02982d6331e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanScheduleAccrualData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanScheduleAccrualData.java @@ -21,6 +21,9 @@ import java.math.BigDecimal; import java.time.LocalDate; import java.util.Map; + +import lombok.Getter; +import lombok.Setter; import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; @@ -51,6 +54,9 @@ public class LoanScheduleAccrualData { private BigDecimal dueDatePenaltyIncome; private BigDecimal accruableIncome; + @Setter@Getter + private BigDecimal interestCompletedIncome; + public LoanScheduleAccrualData(final Long loanId, final Long officeId, final Integer installmentNumber, final LocalDate accruedTill, final PeriodFrequencyType repaymentFrequency, final Integer repayEvery, final LocalDate dueDate, final LocalDate fromDate, final Long repaymentScheduleId, final Long loanProductId, final BigDecimal interestIncome, final BigDecimal feeIncome, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java index 03d1681c4ed..8e6e2e6653b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java @@ -136,6 +136,7 @@ public void addPeriodicAccruals(final LocalDate tilldate, Long loanId, Collectio private void addAccrualTillSpecificDate(final LocalDate tilldate, final LoanScheduleAccrualData accrualData) throws Exception { LocalDate interestStartDate = accrualData.getFromDateAsLocaldate(); + if (accrualData.getInterestCalculatedFrom() != null && accrualData.getFromDateAsLocaldate().isBefore(accrualData.getInterestCalculatedFrom())) { if (accrualData.getInterestCalculatedFrom().isBefore(accrualData.getDueDateAsLocaldate())) { @@ -146,7 +147,24 @@ private void addAccrualTillSpecificDate(final LocalDate tilldate, final LoanSche } int totalNumberOfDays = Math.toIntExact(ChronoUnit.DAYS.between(interestStartDate, accrualData.getDueDateAsLocaldate())); - LocalDate startDate = accrualData.getFromDateAsLocaldate(); + double totalInterest = accrualData.getAccruableIncome().doubleValue(); + double paidInterest = accrualData.getInterestCompletedIncome() != null ? accrualData.getInterestCompletedIncome().doubleValue() : 0.0; + double remainingInterest = totalInterest - paidInterest; + + if (remainingInterest <= 0) { + return; // No remaining interest to accrue. + } + + double interestPerDay = totalInterest / totalNumberOfDays; + int daysPaid = (int) Math.ceil(paidInterest / interestPerDay); + + // Adjust start date based on paid days + LocalDate adjustedStartDate = interestStartDate.plusDays(daysPaid); + if (adjustedStartDate.isAfter(tilldate)) { + return; // No accrual needed as paid interest covers till the given date. + } + + LocalDate startDate = adjustedStartDate; if (accrualData.getInterestCalculatedFrom() != null && startDate.isBefore(accrualData.getInterestCalculatedFrom())) { if (accrualData.getInterestCalculatedFrom().isBefore(tilldate)) { startDate = accrualData.getInterestCalculatedFrom(); @@ -154,18 +172,20 @@ private void addAccrualTillSpecificDate(final LocalDate tilldate, final LoanSche startDate = tilldate; } } + int daysToBeAccrued = Math.toIntExact(ChronoUnit.DAYS.between(startDate, tilldate)); - double interestPerDay = accrualData.getAccruableIncome().doubleValue() / totalNumberOfDays; + BigDecimal interestportion; BigDecimal amount = BigDecimal.ZERO; - BigDecimal interestportion = null; BigDecimal feeportion = accrualData.getDueDateFeeIncome(); BigDecimal penaltyportion = accrualData.getDueDatePenaltyIncome(); - if (daysToBeAccrued >= totalNumberOfDays) { - interestportion = accrualData.getAccruableIncome(); + + if (daysToBeAccrued >= totalNumberOfDays - daysPaid) { + interestportion = BigDecimal.valueOf(remainingInterest); } else { - double iterest = interestPerDay * daysToBeAccrued; - interestportion = BigDecimal.valueOf(iterest); + double interest = interestPerDay * daysToBeAccrued; + interestportion = BigDecimal.valueOf(interest); } + interestportion = interestportion.setScale(accrualData.getCurrencyData().decimalPlaces(), MoneyHelper.getRoundingMode()); BigDecimal totalAccInterest = accrualData.getAccruedInterestIncome(); @@ -183,6 +203,7 @@ private void addAccrualTillSpecificDate(final LocalDate tilldate, final LoanSche interestportion = null; } } + if (feeportion != null) { if (totalAccFee == null) { totalAccFee = BigDecimal.ZERO; @@ -206,12 +227,14 @@ private void addAccrualTillSpecificDate(final LocalDate tilldate, final LoanSche penaltyportion = null; } } + if (amount.compareTo(BigDecimal.ZERO) > 0) { addAccrualAccounting(accrualData, amount, interestportion, totalAccInterest, feeportion, totalAccFee, penaltyportion, totalAccPenalty, tilldate); } } + @Transactional public void addAccrualAccounting(LoanScheduleAccrualData scheduleAccrualData) throws Exception { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index ca181ce8a2c..45b0c946673 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -1905,7 +1905,8 @@ public Collection retrivePeriodicAccrualData(final Loca " where (recaldet.is_compounding_to_be_posted_as_transaction is null or recaldet.is_compounding_to_be_posted_as_transaction = false) ") .append(" and (((ls.fee_charges_amount <> COALESCE(ls.accrual_fee_charges_derived, 0))") .append(" or (ls.penalty_charges_amount <> COALESCE(ls.accrual_penalty_charges_derived, 0))") - .append(" or (ls.interest_amount <> COALESCE(ls.accrual_interest_derived, 0)))") + .append(" or (ls.interest_amount > COALESCE(ls.interest_completed_derived, 0)") + .append(" and (ls.interest_amount <> COALESCE(ls.accrual_interest_derived, 0))))") .append(" and loan.loan_status_id=:active and mpl.accounting_type=:type and (loan.closedon_date <= :tillDate or loan.closedon_date is null)") .append(" and loan.is_npa=false and (ls.duedate <= :tillDate or (ls.duedate > :tillDate and ls.fromdate < :tillDate))) "); Map paramMap = new HashMap<>(4); @@ -1930,7 +1931,7 @@ public String schema() { .append("loan.interest_calculated_from_date as interestCalculatedFrom, ").append("loan.repay_every as repayEvery,") .append("ls.installment as installmentNumber, ") .append("ls.duedate as duedate,ls.fromdate as fromdate ,ls.id as scheduleId,loan.product_id as productId,") - .append("ls.interest_amount as interest, ls.interest_waived_derived as interestWaived,") + .append("ls.interest_amount as interest, ls.interest_completed_derived as interestCompleted, ls.interest_waived_derived as interestWaived,") .append("ls.penalty_charges_amount as penalty, ").append("ls.fee_charges_amount as charges, ") .append("ls.accrual_interest_derived as accinterest,ls.accrual_fee_charges_derived as accfeecharege,ls.accrual_penalty_charges_derived as accpenalty,") .append(" loan.currency_code as currencyCode,loan.currency_digits as currencyDigits,loan.currency_multiplesof as inMultiplesOf,") @@ -1960,6 +1961,7 @@ public LoanScheduleAccrualData mapRow(ResultSet rs, @SuppressWarnings("unused") final Long repaymentScheduleId = rs.getLong("scheduleId"); final Long loanProductId = rs.getLong("productId"); final BigDecimal interestIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "interest"); + final BigDecimal interestCompleted = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "interestCompleted"); final BigDecimal feeIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "charges"); final BigDecimal penaltyIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "penalty"); final BigDecimal interestIncomeWaived = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "interestWaived"); @@ -1977,9 +1979,11 @@ public LoanScheduleAccrualData mapRow(ResultSet rs, @SuppressWarnings("unused") final CurrencyData currencyData = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, currencyDisplaySymbol, currencyNameCode, intCode); - return new LoanScheduleAccrualData(loanId, officeId, installmentNumber, accruedTill, frequency, repayEvery, dueDate, fromDate, + LoanScheduleAccrualData loanScheduleAccrualData = new LoanScheduleAccrualData(loanId, officeId, installmentNumber, accruedTill, frequency, repayEvery, dueDate, fromDate, repaymentScheduleId, loanProductId, interestIncome, feeIncome, penaltyIncome, accruedInterestIncome, accruedFeeIncome, accruedPenaltyIncome, currencyData, interestCalculatedFrom, interestIncomeWaived); + loanScheduleAccrualData.setInterestCompletedIncome(interestCompleted); + return loanScheduleAccrualData; } }