in fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractCumulativeLoanScheduleGenerator.java [716:951]
private void handleRecalculationForNonDueDateTransactions(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO, LoanScheduleParams scheduleParams,
final Collection<LoanScheduleModelPeriod> periods, final Money totalInterestChargedForFullLoanTerm,
final LocalDate idealDisbursementDate, LocalDate firstRepaymentDate, final LocalDate lastRestDate,
final LocalDate scheduledDueDate, final LocalDate periodStartDateForInterest,
final Collection<RecalculationDetail> applicableTransactions, final ScheduleCurrentPeriodParams currentPeriodParams) {
if (scheduleParams.applyInterestRecalculation()) {
final MonetaryCurrency currency = MonetaryCurrency.fromCurrencyData(scheduleParams.getCurrency());
final Collection<LoanTermVariationsData> interestRates = loanApplicationTerms.getLoanTermVariations().getInterestRateChanges();
boolean checkForOutstanding = true;
List<RecalculationDetail> unprocessedTransactions = new ArrayList<>();
List<RecalculationDetail> processTransactions = new ArrayList<>();
LoanScheduleModelPeriod installment = null;
LocalDate periodStartDateApplicableForInterest = periodStartDateForInterest;
for (RecalculationDetail detail : applicableTransactions) {
if (detail.isProcessed()) {
continue;
}
boolean updateLatePaymentMap = false;
final LocalDate transactionDate = detail.getTransactionDate();
if (DateUtils.isBefore(transactionDate, scheduledDueDate)) {
if (scheduleParams.getLoanRepaymentScheduleTransactionProcessor() != null && scheduleParams
.getLoanRepaymentScheduleTransactionProcessor().isInterestFirstRepaymentScheduleTransactionProcessor()) {
if (detail.getTransaction().isWaiver()) {
processTransactions.add(detail);
continue;
}
List<LoanTransaction> currentTransactions = new ArrayList<>();
for (RecalculationDetail processDetail : processTransactions) {
currentTransactions.addAll(createCurrentTransactionList(processDetail));
}
processTransactions.clear();
currentTransactions.addAll(createCurrentTransactionList(detail));
if (!DateUtils.isEqual(transactionDate, scheduleParams.getPeriodStartDate()) || scheduleParams.isFirstPeriod()) {
int periodDays = DateUtils.getExactDifferenceInDays(scheduleParams.getPeriodStartDate(), transactionDate);
// calculates period start date for interest
// calculation as per the configuration
periodStartDateApplicableForInterest = calculateInterestStartDateForPeriod(loanApplicationTerms,
scheduleParams.getPeriodStartDate(), idealDisbursementDate, firstRepaymentDate,
loanApplicationTerms.isInterestChargedFromDateSameAsDisbursalDateEnabled(),
loanApplicationTerms.getExpectedDisbursementDate());
int daysInPeriodApplicable = DateUtils.getExactDifferenceInDays(periodStartDateApplicableForInterest,
transactionDate);
Money interestForCurrentInstallment = Money.zero(currency);
if (daysInPeriodApplicable > 0) {
// 5 determine interest till the transaction
// date
PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(
getPaymentPeriodsInOneYearCalculator(),
currentPeriodParams.getInterestCalculationGraceOnRepaymentPeriodFraction(),
scheduleParams.getTotalCumulativePrincipal().minus(scheduleParams.getReducePrincipal()),
scheduleParams.getTotalCumulativeInterest(), totalInterestChargedForFullLoanTerm,
scheduleParams.getTotalOutstandingInterestPaymentDueToGrace(),
scheduleParams.getOutstandingBalanceAsPerRest(), loanApplicationTerms,
scheduleParams.getPeriodNumber(), mc, mergeVariationsToMap(loanApplicationTerms, scheduleParams),
scheduleParams.getCompoundingMap(), periodStartDateApplicableForInterest, transactionDate,
interestRates);
interestForCurrentInstallment = principalInterestForThisPeriod.interest();
scheduleParams.setTotalOutstandingInterestPaymentDueToGrace(
principalInterestForThisPeriod.interestPaymentDueToGrace());
}
Money principalForThisPeriod = Money.zero(currency);
// applies all the applicable charges to the
// newly
// created installment
PrincipalInterest principalInterest = new PrincipalInterest(principalForThisPeriod,
interestForCurrentInstallment, null);
Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(scheduleParams.getPeriodStartDate(),
transactionDate, loanCharges, currency, principalInterest, scheduleParams.getPrincipalToBeScheduled(),
scheduleParams.getTotalCumulativeInterest(), false, scheduleParams.isFirstPeriod(), mc);
Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(scheduleParams.getPeriodStartDate(),
transactionDate, loanCharges, currency, principalInterest, scheduleParams.getPrincipalToBeScheduled(),
scheduleParams.getTotalCumulativeInterest(), false, scheduleParams.isFirstPeriod(), mc);
// sum up real totalInstallmentDue from
// components
final Money totalInstallmentDue = principalForThisPeriod.plus(interestForCurrentInstallment)
.plus(feeChargesForInstallment).plus(penaltyChargesForInstallment);
// create repayment period from parts
installment = LoanScheduleModelRepaymentPeriod.repayment(scheduleParams.getInstalmentNumber(),
scheduleParams.getPeriodStartDate(), transactionDate, principalForThisPeriod,
scheduleParams.getOutstandingBalance(), interestForCurrentInstallment, feeChargesForInstallment,
penaltyChargesForInstallment, totalInstallmentDue, true, mc);
periods.add(installment);
addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), installment);
updateCompoundingMap(loanApplicationTerms, holidayDetailDTO, scheduleParams, lastRestDate, scheduledDueDate);
// update outstanding balance for interest
// calculation as per the rest
updateOutstandingBalanceAsPerRest(loanApplicationTerms, scheduleParams, transactionDate);
// handle cumulative fields
scheduleParams.addLoanTermInDays(periodDays);
scheduleParams.addTotalRepaymentExpected(totalInstallmentDue);
scheduleParams.addTotalCumulativeInterest(interestForCurrentInstallment);
scheduleParams.addTotalFeeChargesCharged(feeChargesForInstallment);
scheduleParams.addTotalPenaltyChargesCharged(penaltyChargesForInstallment);
scheduleParams.setPeriodStartDate(transactionDate);
periodStartDateApplicableForInterest = scheduleParams.getPeriodStartDate();
updateLatePaymentMap = true;
scheduleParams.incrementInstalmentNumber();
populateCompoundingDatesInPeriod(scheduleParams.getPeriodStartDate(), scheduledDueDate, loanApplicationTerms,
holidayDetailDTO, scheduleParams, loanCharges, currency, mc);
// creates and insert Loan repayment schedule
// for
// the period
} else if (installment == null) {
installment = ((List<LoanScheduleModelPeriod>) periods).get(periods.size() - 1);
}
// applies the transaction as per transaction
// strategy
// on scheduled installments to identify the
// unprocessed(early payment ) amounts
Money unprocessed = scheduleParams.getLoanRepaymentScheduleTransactionProcessor()
.handleRepaymentSchedule(currentTransactions, currency, scheduleParams.getInstallments(), loanCharges);
if (unprocessed.isGreaterThanZero()) {
if (loanApplicationTerms.getPreClosureInterestCalculationStrategy().calculateTillRestFrequencyEnabled()) {
LocalDate applicableDate = getNextRestScheduleDate(transactionDate.minusDays(1), loanApplicationTerms,
holidayDetailDTO);
checkForOutstanding = DateUtils.isEqual(transactionDate, applicableDate);
}
// reduces actual outstanding balance
scheduleParams.reduceOutstandingBalance(unprocessed);
// if outstanding balance becomes less than zero
// then adjusts the princiapal
Money addToPrincipal = Money.zero(currency);
if (!scheduleParams.getOutstandingBalance().isGreaterThanZero()) {
addToPrincipal = addToPrincipal.plus(scheduleParams.getOutstandingBalance());
scheduleParams.setOutstandingBalance(Money.zero(currency));
currentPeriodParams.setLastInstallment(installment);
}
// updates principal portion map with the early
// payment amounts and applicable date as per
// rest
updateAmountsBasedOnEarlyPayment(loanApplicationTerms, holidayDetailDTO, scheduleParams, installment, detail,
unprocessed, addToPrincipal);
// method applies early payment strategy
scheduleParams.addReducePrincipal(unprocessed);
scheduleParams
.setReducePrincipal(applyEarlyPaymentStrategy(loanApplicationTerms, scheduleParams.getReducePrincipal(),
scheduleParams.getTotalCumulativePrincipal(), scheduleParams.getPeriodNumber(), mc));
}
// identify late payments and add compounding
// details to
// map for interest calculation
handleLatePayments(loanApplicationTerms, holidayDetailDTO, currency, scheduleParams, lastRestDate, detail);
if (updateLatePaymentMap) {
updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, scheduleParams.getLatePaymentMap(),
scheduledDueDate, scheduleParams.getInstallments(), true, lastRestDate);
}
} else if (scheduleParams.getLoanRepaymentScheduleTransactionProcessor() != null) {
LocalDate applicableDate = getNextRestScheduleDate(transactionDate.minusDays(1), loanApplicationTerms,
holidayDetailDTO);
if (DateUtils.isBefore(applicableDate, scheduledDueDate)) {
List<LoanTransaction> currentTransactions = createCurrentTransactionList(detail);
Money unprocessed = scheduleParams.getLoanRepaymentScheduleTransactionProcessor()
.handleRepaymentSchedule(currentTransactions, currency, scheduleParams.getInstallments(), loanCharges);
Money arrears = fetchArrears(loanApplicationTerms, currency, detail.getTransaction());
if (unprocessed.isGreaterThanZero()) {
updateMapWithAmount(scheduleParams.getPrincipalPortionMap(), unprocessed, applicableDate);
currentPeriodParams.plusEarlyPaidAmount(unprocessed);
// this check is to identify pre-closure and
// apply interest calculation as per
// configuration for non due date payments
if (!scheduleParams.getOutstandingBalance().isGreaterThan(unprocessed) && !loanApplicationTerms
.getPreClosureInterestCalculationStrategy().calculateTillRestFrequencyEnabled()) {
scheduleParams.getCompoundingDateVariations().put(periodStartDateApplicableForInterest,
new TreeMap<>(scheduleParams.getCompoundingMap()));
PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(
getPaymentPeriodsInOneYearCalculator(),
currentPeriodParams.getInterestCalculationGraceOnRepaymentPeriodFraction(),
scheduleParams.getTotalCumulativePrincipal().minus(scheduleParams.getReducePrincipal()),
scheduleParams.getTotalCumulativeInterest(), totalInterestChargedForFullLoanTerm,
scheduleParams.getTotalOutstandingInterestPaymentDueToGrace(),
scheduleParams.getOutstandingBalanceAsPerRest(), loanApplicationTerms,
scheduleParams.getPeriodNumber(), mc,
mergeVariationsToMap(loanApplicationTerms, scheduleParams), scheduleParams.getCompoundingMap(),
periodStartDateApplicableForInterest, transactionDate, interestRates);
if (!principalInterestForThisPeriod.interest()
.plus(principalInterestForThisPeriod.interestPaymentDueToGrace())
.plus(scheduleParams.getOutstandingBalance()).isGreaterThan(unprocessed)) {
currentPeriodParams.minusEarlyPaidAmount(unprocessed);
updateMapWithAmount(scheduleParams.getPrincipalPortionMap(), unprocessed.negated(), applicableDate);
LoanTransaction loanTransaction = LoanTransaction.repayment(null, unprocessed, null,
transactionDate, null);
RecalculationDetail recalculationDetail = new RecalculationDetail(transactionDate, loanTransaction);
unprocessedTransactions.add(recalculationDetail);
break;
}
}
LoanTransaction loanTransaction = LoanTransaction.repayment(null, unprocessed, null, scheduledDueDate,
null);
RecalculationDetail recalculationDetail = new RecalculationDetail(scheduledDueDate, loanTransaction);
unprocessedTransactions.add(recalculationDetail);
checkForOutstanding = false;
scheduleParams.reduceOutstandingBalance(unprocessed);
// if outstanding balance becomes less than
// zero
// then adjusts the princiapal
Money addToPrincipal = Money.zero(currency);
if (scheduleParams.getOutstandingBalance().isLessThanZero()) {
addToPrincipal = addToPrincipal.plus(scheduleParams.getOutstandingBalance());
scheduleParams.setOutstandingBalance(Money.zero(currency));
updateMapWithAmount(scheduleParams.getPrincipalPortionMap(), addToPrincipal, applicableDate);
currentPeriodParams.plusEarlyPaidAmount(addToPrincipal);
}
}
if (arrears.isGreaterThanZero() && DateUtils.isBefore(applicableDate, lastRestDate)) {
handleLatePayments(loanApplicationTerms, holidayDetailDTO, currency, scheduleParams, lastRestDate, detail);
}
}
}
}
}
applicableTransactions.addAll(unprocessedTransactions);
if (checkForOutstanding && scheduleParams.getOutstandingBalance().isZero() && scheduleParams.getDisburseDetailMap().isEmpty()) {
currentPeriodParams.setSkipCurrentLoop(true);
}
}
}