Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FINERACT-1981: Update Payable Interest Calculation For LoanSummaryData #4176

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,10 @@

import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Collection;
import java.util.Optional;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.Accessors;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;

/**
* Immutable data object representing loan summary information.
Expand Down Expand Up @@ -105,169 +96,4 @@ public class LoanSummaryData {
private BigDecimal totalUnpaidPayableDueInterest;
private BigDecimal totalUnpaidPayableNotDueInterest;

public static LoanSummaryData withTransactionAmountsSummary(final LoanSummaryData defaultSummaryData,
final LoanScheduleData repaymentSchedule, final Collection<LoanTransactionBalance> loanTransactionBalances) {
final LocalDate businessDate = DateUtils.getBusinessLocalDate();

BigDecimal totalMerchantRefund = BigDecimal.ZERO;
BigDecimal totalMerchantRefundReversed = BigDecimal.ZERO;
BigDecimal totalPayoutRefund = BigDecimal.ZERO;
BigDecimal totalPayoutRefundReversed = BigDecimal.ZERO;
BigDecimal totalGoodwillCredit = BigDecimal.ZERO;
BigDecimal totalGoodwillCreditReversed = BigDecimal.ZERO;
BigDecimal totalChargeAdjustment = BigDecimal.ZERO;
BigDecimal totalChargeAdjustmentReversed = BigDecimal.ZERO;
BigDecimal totalChargeback = BigDecimal.ZERO;
BigDecimal totalCreditBalanceRefund = BigDecimal.ZERO;
BigDecimal totalCreditBalanceRefundReversed = BigDecimal.ZERO;
BigDecimal totalRepaymentTransaction = BigDecimal.ZERO;
BigDecimal totalRepaymentTransactionReversed = BigDecimal.ZERO;
BigDecimal totalInterestPaymentWaiver = BigDecimal.ZERO;
BigDecimal totalInterestRefund = BigDecimal.ZERO;
BigDecimal totalUnpaidPayableDueInterest = BigDecimal.ZERO;
BigDecimal totalUnpaidPayableNotDueInterest = BigDecimal.ZERO;

totalChargeAdjustment = fetchLoanTransactionBalanceByType(loanTransactionBalances,
LoanTransactionType.CHARGE_ADJUSTMENT.getValue());
totalChargeAdjustmentReversed = fetchLoanTransactionBalanceReversedByType(loanTransactionBalances,
LoanTransactionType.CHARGE_ADJUSTMENT.getValue());

totalChargeback = fetchLoanTransactionBalanceByType(loanTransactionBalances, LoanTransactionType.CHARGEBACK.getValue());

totalCreditBalanceRefund = fetchLoanTransactionBalanceByType(loanTransactionBalances,
LoanTransactionType.CREDIT_BALANCE_REFUND.getValue());
totalCreditBalanceRefundReversed = fetchLoanTransactionBalanceReversedByType(loanTransactionBalances,
LoanTransactionType.CREDIT_BALANCE_REFUND.getValue());

totalGoodwillCredit = fetchLoanTransactionBalanceByType(loanTransactionBalances, LoanTransactionType.GOODWILL_CREDIT.getValue());
totalGoodwillCreditReversed = fetchLoanTransactionBalanceReversedByType(loanTransactionBalances,
LoanTransactionType.GOODWILL_CREDIT.getValue());

totalInterestRefund = fetchLoanTransactionBalanceByType(loanTransactionBalances, LoanTransactionType.INTEREST_REFUND.getValue());

totalInterestPaymentWaiver = fetchLoanTransactionBalanceByType(loanTransactionBalances,
LoanTransactionType.INTEREST_PAYMENT_WAIVER.getValue());

totalMerchantRefund = fetchLoanTransactionBalanceByType(loanTransactionBalances,
LoanTransactionType.MERCHANT_ISSUED_REFUND.getValue());
totalMerchantRefundReversed = fetchLoanTransactionBalanceReversedByType(loanTransactionBalances,
LoanTransactionType.MERCHANT_ISSUED_REFUND.getValue());

totalPayoutRefund = fetchLoanTransactionBalanceByType(loanTransactionBalances, LoanTransactionType.PAYOUT_REFUND.getValue());
totalPayoutRefundReversed = fetchLoanTransactionBalanceReversedByType(loanTransactionBalances,
LoanTransactionType.PAYOUT_REFUND.getValue());

totalRepaymentTransaction = fetchLoanTransactionBalanceByType(loanTransactionBalances, LoanTransactionType.REPAYMENT.getValue())
.add(fetchLoanTransactionBalanceByType(loanTransactionBalances, LoanTransactionType.DOWN_PAYMENT.getValue()));
totalRepaymentTransactionReversed = fetchLoanTransactionBalanceReversedByType(loanTransactionBalances,
LoanTransactionType.REPAYMENT.getValue());

if (repaymentSchedule != null) {
// Outstanding Interest on Past due installments
totalUnpaidPayableDueInterest = computeTotalUnpaidPayableDueInterestAmount(repaymentSchedule.getPeriods(), businessDate);

// Accumulated daily interest of the current Installment period
totalUnpaidPayableNotDueInterest = computeTotalUnpaidPayableNotDueInterestAmountOnActualPeriod(repaymentSchedule.getPeriods(),
businessDate, defaultSummaryData.currency);
}

return LoanSummaryData.builder().currency(defaultSummaryData.currency).principalDisbursed(defaultSummaryData.principalDisbursed)
.principalAdjustments(defaultSummaryData.principalAdjustments).principalPaid(defaultSummaryData.principalPaid)
.principalWrittenOff(defaultSummaryData.principalWrittenOff).principalOutstanding(defaultSummaryData.principalOutstanding)
.principalOverdue(defaultSummaryData.principalOverdue).interestCharged(defaultSummaryData.interestCharged)
.interestPaid(defaultSummaryData.interestPaid).interestWaived(defaultSummaryData.interestWaived)
.interestWrittenOff(defaultSummaryData.interestWrittenOff).interestOutstanding(defaultSummaryData.interestOutstanding)
.interestOverdue(defaultSummaryData.interestOverdue).feeChargesCharged(defaultSummaryData.feeChargesCharged)
.feeAdjustments(defaultSummaryData.feeAdjustments)
.feeChargesDueAtDisbursementCharged(defaultSummaryData.feeChargesDueAtDisbursementCharged)
.feeChargesPaid(defaultSummaryData.feeChargesPaid).feeChargesWaived(defaultSummaryData.feeChargesWaived)
.feeChargesWrittenOff(defaultSummaryData.feeChargesWrittenOff)
.feeChargesOutstanding(defaultSummaryData.feeChargesOutstanding).feeChargesOverdue(defaultSummaryData.feeChargesOverdue)
.penaltyChargesCharged(defaultSummaryData.penaltyChargesCharged).penaltyAdjustments(defaultSummaryData.penaltyAdjustments)
.penaltyChargesPaid(defaultSummaryData.penaltyChargesPaid).penaltyChargesWaived(defaultSummaryData.penaltyChargesWaived)
.penaltyChargesWrittenOff(defaultSummaryData.penaltyChargesWrittenOff)
.penaltyChargesOutstanding(defaultSummaryData.penaltyChargesOutstanding)
.penaltyChargesOverdue(defaultSummaryData.penaltyChargesOverdue)
.totalExpectedRepayment(defaultSummaryData.totalExpectedRepayment).totalRepayment(defaultSummaryData.totalRepayment)
.totalExpectedCostOfLoan(defaultSummaryData.totalExpectedCostOfLoan).totalCostOfLoan(defaultSummaryData.totalCostOfLoan)
.totalWaived(defaultSummaryData.totalWaived).totalWrittenOff(defaultSummaryData.totalWrittenOff)
.totalOutstanding(defaultSummaryData.totalOutstanding).totalOverdue(defaultSummaryData.totalOverdue)
.overdueSinceDate(defaultSummaryData.overdueSinceDate).writeoffReasonId(defaultSummaryData.writeoffReasonId)
.writeoffReason(defaultSummaryData.writeoffReason).totalRecovered(defaultSummaryData.totalRecovered)
.chargeOffReasonId(defaultSummaryData.chargeOffReasonId).chargeOffReason(defaultSummaryData.chargeOffReason)
.totalMerchantRefund(totalMerchantRefund).totalMerchantRefundReversed(totalMerchantRefundReversed)
.totalPayoutRefund(totalPayoutRefund).totalPayoutRefundReversed(totalPayoutRefundReversed)
.totalGoodwillCredit(totalGoodwillCredit).totalGoodwillCreditReversed(totalGoodwillCreditReversed)
.totalChargeAdjustment(totalChargeAdjustment).totalChargeAdjustmentReversed(totalChargeAdjustmentReversed)
.totalChargeback(totalChargeback).totalCreditBalanceRefund(totalCreditBalanceRefund)
.totalCreditBalanceRefundReversed(totalCreditBalanceRefundReversed).totalRepaymentTransaction(totalRepaymentTransaction)
.totalRepaymentTransactionReversed(totalRepaymentTransactionReversed).totalInterestPaymentWaiver(totalInterestPaymentWaiver)
.totalUnpaidPayableDueInterest(totalUnpaidPayableDueInterest)
.totalUnpaidPayableNotDueInterest(totalUnpaidPayableNotDueInterest).totalInterestRefund(totalInterestRefund).build();
}

private static BigDecimal fetchLoanTransactionBalanceByType(final Collection<LoanTransactionBalance> loanTransactionBalances,
final Integer transactionType) {
final Optional<LoanTransactionBalance> optLoanTransactionBalance = loanTransactionBalances.stream()
.filter(balance -> balance.getTransactionType().equals(transactionType) && !balance.isReversed()).findFirst();
return optLoanTransactionBalance.isPresent() ? optLoanTransactionBalance.get().getAmount() : BigDecimal.ZERO;
}

private static BigDecimal fetchLoanTransactionBalanceReversedByType(final Collection<LoanTransactionBalance> loanTransactionBalances,
final Integer transactionType) {
final Optional<LoanTransactionBalance> optLoanTransactionBalance = loanTransactionBalances.stream()
.filter(balance -> balance.getTransactionType().equals(transactionType) && balance.isReversed()
&& balance.isManuallyAdjustedOrReversed())
.findFirst();
return optLoanTransactionBalance.isPresent() ? optLoanTransactionBalance.get().getAmount() : BigDecimal.ZERO;
}

public static LoanSummaryData withOnlyCurrencyData(CurrencyData currencyData) {
return LoanSummaryData.builder().currency(currencyData).build();
}

private static BigDecimal computeTotalUnpaidPayableDueInterestAmount(Collection<LoanSchedulePeriodData> periods,
final LocalDate businessDate) {
return periods.stream().filter(period -> !period.getDownPaymentPeriod() && businessDate.compareTo(period.getDueDate()) >= 0)
.map(period -> period.getInterestOutstanding()).reduce(BigDecimal.ZERO, BigDecimal::add);
}

private static BigDecimal computeTotalUnpaidPayableNotDueInterestAmountOnActualPeriod(final Collection<LoanSchedulePeriodData> periods,
final LocalDate businessDate, final CurrencyData currency) {
// Find the current Period (If exists one) based on the Business date
final Optional<LoanSchedulePeriodData> optCurrentPeriod = periods.stream()
.filter(period -> !period.getDownPaymentPeriod() && period.isActualPeriodForNotDuePayableCalculation(businessDate))
.findFirst();

if (optCurrentPeriod.isPresent()) {
final LoanSchedulePeriodData currentPeriod = optCurrentPeriod.get();
final long remainingDays = currentPeriod.getDaysInPeriod()
- DateUtils.getDifferenceInDays(currentPeriod.getFromDate(), businessDate);

return computeAccruedInterestTillDay(currentPeriod, remainingDays, currency);
}
// Default value equal to Zero
return BigDecimal.ZERO;
}

public static BigDecimal computeAccruedInterestTillDay(final LoanSchedulePeriodData period, final long untilDay,
final CurrencyData currency) {
Integer remainingDays = period.getDaysInPeriod();
BigDecimal totalAccruedInterest = BigDecimal.ZERO;
while (remainingDays > untilDay) {
final BigDecimal accruedInterest = period.getInterestDue().subtract(totalAccruedInterest)
.divide(BigDecimal.valueOf(remainingDays), MoneyHelper.getMathContext());
totalAccruedInterest = totalAccruedInterest.add(accruedInterest);
remainingDays--;
}

totalAccruedInterest = totalAccruedInterest.subtract(period.getInterestPaid()).subtract(period.getInterestWaived());
if (MathUtil.isLessThanZero(totalAccruedInterest)) {
// Set Zero If the Interest Paid + Waived is greather than Interest Accrued
totalAccruedInterest = BigDecimal.ZERO;
}

return Money.of(currency, totalAccruedInterest).getAmount();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,13 @@
import org.apache.fineract.portfolio.loanaccount.data.CollectionData;
import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData;
import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData;
import org.apache.fineract.portfolio.loanaccount.data.LoanSummaryData;
import org.apache.fineract.portfolio.loanaccount.domain.LoanSummaryBalancesRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations;
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanSummaryDataProvider;
import org.apache.fineract.portfolio.loanaccount.service.LoanSummaryProviderDelegate;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

@Component
Expand All @@ -52,6 +54,8 @@ public class LoanBusinessEventSerializer implements BusinessEventSerializer {
private final DelinquencyReadPlatformService delinquencyReadPlatformService;
private final LoanInstallmentLevelDelinquencyEventProducer installmentLevelDelinquencyEventProducer;
private final LoanSummaryBalancesRepository loanSummaryBalancesRepository;
@Lazy
private final LoanSummaryProviderDelegate loanSummaryProviderDelegate;

@Override
public <T> boolean canSerialize(BusinessEvent<T> event) {
Expand All @@ -74,12 +78,15 @@ public <T> ByteBufferSerializable toAvroDTO(BusinessEvent<T> rawEvent) {
CollectionData delinquentData = delinquencyReadPlatformService.calculateLoanCollectionData(loanId);
data.setDelinquent(delinquentData);

LoanSummaryDataProvider loanSummaryDataProvider = loanSummaryProviderDelegate
.resolveLoanSummaryDataProvider(data.getTransactionProcessingStrategyCode());

if (data.getSummary() != null) {
data.setSummary(LoanSummaryData.withTransactionAmountsSummary(data.getSummary(), data.getRepaymentSchedule(),
loanSummaryBalancesRepository.retrieveLoanSummaryBalancesByTransactionType(loanId,
data.setSummary(loanSummaryDataProvider.withTransactionAmountsSummary(data.getId(), data.getSummary(),
data.getRepaymentSchedule(), loanSummaryBalancesRepository.retrieveLoanSummaryBalancesByTransactionType(loanId,
LoanApiConstants.LOAN_SUMMARY_TRANSACTION_TYPES)));
} else {
data.setSummary(LoanSummaryData.withOnlyCurrencyData(data.getCurrency()));
data.setSummary(loanSummaryDataProvider.withOnlyCurrencyData(data.getCurrency()));
}

List<LoanInstallmentDelinquencyBucketDataV1> installmentsDelinquencyData = installmentLevelDelinquencyEventProducer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,6 @@
import org.apache.fineract.portfolio.loanaccount.data.LoanApprovalData;
import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData;
import org.apache.fineract.portfolio.loanaccount.data.LoanCollateralManagementData;
import org.apache.fineract.portfolio.loanaccount.data.LoanSummaryData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
import org.apache.fineract.portfolio.loanaccount.data.PaidInAdvanceData;
Expand All @@ -150,6 +149,8 @@
import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanSummaryDataProvider;
import org.apache.fineract.portfolio.loanaccount.service.LoanSummaryProviderDelegate;
import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
import org.apache.fineract.portfolio.loanproduct.data.LoanProductData;
import org.apache.fineract.portfolio.loanproduct.data.TransactionProcessingStrategyData;
Expand Down Expand Up @@ -289,6 +290,7 @@ public class LoansApiResource {
private final LoanSummaryBalancesRepository loanSummaryBalancesRepository;
private final ClientReadPlatformService clientReadPlatformService;
private final LoanTermVariationsRepository loanTermVariationsRepository;
private final LoanSummaryProviderDelegate loanSummaryProviderDelegate;

/*
* This template API is used for loan approval, ideally this should be invoked on loan that are pending for
Expand Down Expand Up @@ -1115,9 +1117,12 @@ private String retrieveLoan(final Long loanId, final String loanExternalIdStr, b

// updating summary with transaction amounts summary
if (loanBasicDetails.getSummary() != null) {
loanBasicDetails.setSummary(LoanSummaryData.withTransactionAmountsSummary(loanBasicDetails.getSummary(), repaymentSchedule,
loanSummaryBalancesRepository.retrieveLoanSummaryBalancesByTransactionType(loanBasicDetails.getId(),
LoanApiConstants.LOAN_SUMMARY_TRANSACTION_TYPES)));
LoanSummaryDataProvider loanSummaryDataProvider = loanSummaryProviderDelegate
.resolveLoanSummaryDataProvider(loanBasicDetails.getTransactionProcessingStrategyCode());
loanBasicDetails.setSummary(
loanSummaryDataProvider.withTransactionAmountsSummary(loanBasicDetails.getId(), loanBasicDetails.getSummary(),
repaymentSchedule, loanSummaryBalancesRepository.retrieveLoanSummaryBalancesByTransactionType(
loanBasicDetails.getId(), LoanApiConstants.LOAN_SUMMARY_TRANSACTION_TYPES)));
}

final LoanAccountData loanAccount = loanBasicDetails.associationsAndTemplate(repaymentSchedule, loanRepayments, charges,
Expand Down
Loading
Loading