You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by ra...@apache.org on 2016/05/04 14:26:17 UTC
[05/10] incubator-fineract git commit: FINERACT-60 : Interest
compounding, nth day rest frequency and meeting calendar date changes
http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/763cf18b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java
index 2f8b728..d06b3f1 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java
@@ -45,6 +45,7 @@ import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalcualtionAdditionalDetails;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanSummary;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
@@ -124,7 +125,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
LocalDate firstRepaymentdate = this.scheduledDateGenerator.generateNextRepaymentDate(
loanApplicationTerms.getExpectedDisbursementDate(), loanApplicationTerms, isFirstRepayment, holidayDetailDTO);
final LocalDate idealDisbursementDate = this.scheduledDateGenerator.idealDisbursementDateBasedOnFirstRepaymentDate(
- loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery(), firstRepaymentdate);
+ loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery(), firstRepaymentdate,
+ loanApplicationTerms.getLoanCalendar(), loanApplicationTerms.getHolidayDetailDTO(), loanApplicationTerms);
if (!scheduleParams.isPartialUpdate()) {
// Set Fixed Principal Amount
@@ -203,13 +205,6 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
if (scheduleParams.getPeriodStartDate().isAfter(scheduledDueDate)) { throw new ScheduleDateException(
"Due date can't be before period start date", scheduledDueDate); }
- if (!scheduleParams.getLatePaymentMap().isEmpty()) {
- populateCompoundingDatesInPeriod(scheduleParams.getPeriodStartDate(), scheduledDueDate, currentDate, loanApplicationTerms,
- holidayDetailDTO, scheduleParams.getCompoundingMap(), loanCharges, currency);
- scheduleParams.getCompoundingDateVariations().put(scheduleParams.getPeriodStartDate(),
- new TreeMap<>(scheduleParams.getCompoundingMap()));
- }
-
if (extendTermForDailyRepayments) {
scheduleParams.setActualRepaymentDate(scheduledDueDate);
}
@@ -220,6 +215,10 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
scheduledDueDate = scheduleParams.getScheduleTillDate();
isNextRepaymentAvailable = false;
}
+ if (loanApplicationTerms.isInterestRecalculationEnabled()) {
+ populateCompoundingDatesInPeriod(scheduleParams.getPeriodStartDate(), scheduledDueDate, loanApplicationTerms,
+ holidayDetailDTO, scheduleParams, loanCharges, currency);
+ }
// populates the collection with transactions till the due date of
// the period for interest recalculation enabled loans
@@ -316,6 +315,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
currentPeriodParams.getFeeChargesForInstallment(), currentPeriodParams.getPenaltyChargesForInstallment(),
totalInstallmentDue, false);
+ addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), installment);
// apply loan transactions on installments to identify early/late
// payments for interest recalculation
installment = handleRecalculationForTransactions(mc, loanApplicationTerms, holidayDetailDTO, currency, scheduleParams,
@@ -327,7 +327,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
// Updates principal paid map with efective date for reducing
// the amount from outstanding balance(interest calculation)
updateAmountsWithEffectiveDate(loanApplicationTerms, holidayDetailDTO, scheduleParams, scheduledDueDate, currentPeriodParams,
- installment);
+ installment, lastRestDate);
// handle cumulative fields
@@ -337,7 +337,6 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
scheduleParams.setPeriodStartDate(scheduledDueDate);
scheduleParams.incrementInstalmentNumber();
scheduleParams.incrementPeriodNumber();
- scheduleParams.getCompoundingDateVariations().clear();
if (termVariationParams.isRecalculateAmounts()) {
loanApplicationTerms.setCurrentPeriodFixedEmiAmount(null);
loanApplicationTerms.setCurrentPeriodFixedPrincipalAmount(null);
@@ -377,6 +376,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
final BigDecimal totalPrincipalPaid = BigDecimal.ZERO;
final BigDecimal totalOutstanding = BigDecimal.ZERO;
+ updateCompoundingDetails(periods, scheduleParams, loanApplicationTerms);
return LoanScheduleModel.from(periods, applicationCurrency, scheduleParams.getLoanTermInDays(),
scheduleParams.getPrincipalToBeScheduled(), scheduleParams.getTotalCumulativePrincipal().getAmount(), totalPrincipalPaid,
scheduleParams.getTotalCumulativeInterest().getAmount(), scheduleParams.getTotalFeeChargesCharged().getAmount(),
@@ -384,6 +384,30 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
totalOutstanding);
}
+ private void updateCompoundingDetails(final Collection<LoanScheduleModelPeriod> periods, final LoanScheduleParams params,
+ final LoanApplicationTerms loanApplicationTerms) {
+ final Map<LocalDate, Map<LocalDate, Money>> compoundingDetails = params.getCompoundingDateVariations();
+ if (compoundingDetails.isEmpty()) { return; }
+ for (LoanScheduleModelPeriod loanScheduleModelPeriod : periods) {
+ if (loanScheduleModelPeriod.isRepaymentPeriod() && loanScheduleModelPeriod.getLoanCompoundingDetails().isEmpty()) {
+ Map<LocalDate, Money> periodCompoundingDetails = compoundingDetails.get(loanScheduleModelPeriod.periodFromDate());
+ if (periodCompoundingDetails != null) {
+ for (Map.Entry<LocalDate, Money> entry : periodCompoundingDetails.entrySet()) {
+ if (entry.getValue().isGreaterThanZero() && !entry.getKey().isAfter(loanScheduleModelPeriod.periodDueDate())) {
+ LocalDate effectiveDate = entry.getKey();
+ if (loanApplicationTerms.allowCompoundingOnEod()) {
+ effectiveDate = effectiveDate.minusDays(1);
+ }
+ LoanInterestRecalcualtionAdditionalDetails additionalDetails = new LoanInterestRecalcualtionAdditionalDetails(
+ effectiveDate, entry.getValue().getAmount());
+ loanScheduleModelPeriod.getLoanCompoundingDetails().add(additionalDetails);
+ }
+ }
+ }
+ }
+ }
+ }
+
private void applyChargesForCurrentPeriod(final Set<LoanCharge> loanCharges, final MonetaryCurrency currency,
LoanScheduleParams scheduleParams, LocalDate scheduledDueDate, ScheduleCurrentPeriodParams currentPeriodParams) {
PrincipalInterest principalInterest = new PrincipalInterest(currentPeriodParams.getPrincipalForThisPeriod(),
@@ -422,13 +446,14 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
private void updateAmountsWithEffectiveDate(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO,
LoanScheduleParams scheduleParams, LocalDate scheduledDueDate, ScheduleCurrentPeriodParams currentPeriodParams,
- LoanScheduleModelPeriod installment) {
+ LoanScheduleModelPeriod installment, LocalDate lastRestDate) {
LocalDate amountApplicableDate = installment.periodDueDate();
if (loanApplicationTerms.isInterestRecalculationEnabled()) {
amountApplicableDate = getNextRestScheduleDate(installment.periodDueDate().minusDays(1), loanApplicationTerms, holidayDetailDTO);
}
updateMapWithAmount(scheduleParams.getPrincipalPortionMap(),
currentPeriodParams.getPrincipalForThisPeriod().minus(currentPeriodParams.getReducedBalance()), amountApplicableDate);
+ updateCompoundingMap(loanApplicationTerms, holidayDetailDTO, scheduleParams, lastRestDate, scheduledDueDate);
// update outstanding balance for interest calculation
updateOutstandingBalanceAsPerRest(scheduleParams, scheduledDueDate);
@@ -445,7 +470,6 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
LoanScheduleModelPeriod modifiedInstallment = installment;
if (scheduleParams.applyInterestRecalculation() && loanRepaymentScheduleTransactionProcessor != null) {
Money principalProcessed = Money.zero(currency);
- addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), modifiedInstallment);
for (RecalculationDetail detail : applicableTransactions) {
if (!detail.isProcessed()) {
LocalDate transactionDate = detail.getTransactionDate();
@@ -489,10 +513,12 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
}
}
+ adjustCompoundedAmountWithPaidDetail(scheduleParams, lastRestDate, currentTransactions, loanApplicationTerms,
+ holidayDetailDTO);
}
}
updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, scheduleParams.getLatePaymentMap(), scheduledDueDate,
- scheduleParams.getInstallments(), true, lastRestDate, scheduleParams.getCompoundingMap());
+ scheduleParams.getInstallments(), true, lastRestDate);
currentPeriodParams.minusPrincipalForThisPeriod(principalProcessed);
}
return modifiedInstallment;
@@ -600,9 +626,6 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
scheduleParams.getCompoundingMap().clear();
scheduleParams.getCompoundingMap().putAll(
scheduleParams.getCompoundingDateVariations().get(periodStartDateApplicableForInterest));
- } else {
- scheduleParams.getCompoundingDateVariations().put(periodStartDateApplicableForInterest,
- new TreeMap<>(scheduleParams.getCompoundingMap()));
}
}
@@ -656,10 +679,6 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
if (daysInPeriodApplicable > 0) {
// 5 determine interest till the transaction
// date
- if (!scheduleParams.getCompoundingDateVariations().containsKey(periodStartDateApplicableForInterest)) {
- scheduleParams.getCompoundingDateVariations().put(periodStartDateApplicableForInterest,
- new TreeMap<>(scheduleParams.getCompoundingMap()));
- }
PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(
this.paymentPeriodsInOneYearCalculator,
currentPeriodParams.getInterestCalculationGraceOnRepaymentPeriodFraction(), scheduleParams
@@ -700,6 +719,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
scheduleParams.getOutstandingBalance(), interestForThisinstallment, feeChargesForInstallment,
penaltyChargesForInstallment, totalInstallmentDue, true);
periods.add(installment);
+ addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), installment);
+ updateCompoundingMap(loanApplicationTerms, holidayDetailDTO, scheduleParams, lastRestDate, scheduledDueDate);
// update outstanding balance for interest
// calculation as per the rest
@@ -716,10 +737,12 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
periodStartDateApplicableForInterest = scheduleParams.getPeriodStartDate();
updateLatePaymentMap = true;
scheduleParams.incrementInstalmentNumber();
+ populateCompoundingDatesInPeriod(scheduleParams.getPeriodStartDate(), scheduledDueDate, loanApplicationTerms,
+ holidayDetailDTO, scheduleParams, loanCharges, currency);
// creates and insert Loan repayment schedule
// for
// the period
- addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), installment);
+
} else if (installment == null) {
installment = ((List<LoanScheduleModelPeriod>) periods).get(periods.size() - 1);
}
@@ -762,12 +785,10 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
// identify late payments and add compounding
// details to
// map for interest calculation
- handleLatePayments(loanApplicationTerms, holidayDetailDTO, currency, scheduleParams, lastRestDate,
- periodStartDateApplicableForInterest, detail);
+ handleLatePayments(loanApplicationTerms, holidayDetailDTO, currency, scheduleParams, lastRestDate, detail);
if (updateLatePaymentMap) {
updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, scheduleParams.getLatePaymentMap(),
- scheduledDueDate, scheduleParams.getInstallments(), true, lastRestDate,
- scheduleParams.getCompoundingMap());
+ scheduledDueDate, scheduleParams.getInstallments(), true, lastRestDate);
}
} else if (scheduleParams.getLoanRepaymentScheduleTransactionProcessor() != null) {
LocalDate applicableDate = getNextRestScheduleDate(transactionDate.minusDays(1), loanApplicationTerms,
@@ -776,9 +797,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
List<LoanTransaction> currentTransactions = createCurrentTransactionList(detail);
Money unprocessed = scheduleParams.getLoanRepaymentScheduleTransactionProcessor().handleRepaymentSchedule(
currentTransactions, currency, scheduleParams.getInstallments());
- Money arrears = fetchCompoundedArrears(loanApplicationTerms, currency, detail.getTransaction());
+ Money arrears = fetchArrears(loanApplicationTerms, currency, detail.getTransaction());
if (unprocessed.isGreaterThanZero()) {
- arrears = getTotalAmount(scheduleParams.getLatePaymentMap(), currency);
updateMapWithAmount(scheduleParams.getPrincipalPortionMap(), unprocessed, applicableDate);
currentPeriodParams.plusEarlyPaidAmount(unprocessed);
@@ -790,10 +810,6 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
.calculateTillRestFrequencyEnabled()) {
LocalDate calculateTill = transactionDate;
- if (!scheduleParams.getCompoundingDateVariations().containsKey(periodStartDateApplicableForInterest)) {
- scheduleParams.getCompoundingDateVariations().put(periodStartDateApplicableForInterest,
- new TreeMap<>(scheduleParams.getCompoundingMap()));
- }
PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(
this.paymentPeriodsInOneYearCalculator,
currentPeriodParams.getInterestCalculationGraceOnRepaymentPeriodFraction(), scheduleParams
@@ -836,8 +852,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
}
if (arrears.isGreaterThanZero() && applicableDate.isBefore(lastRestDate)) {
- handleLatePayments(loanApplicationTerms, holidayDetailDTO, currency, scheduleParams, lastRestDate,
- periodStartDateApplicableForInterest, detail);
+ handleLatePayments(loanApplicationTerms, holidayDetailDTO, currency, scheduleParams, lastRestDate, detail);
}
}
@@ -862,12 +877,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
* @param detail
*/
private void handleLatePayments(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO,
- final MonetaryCurrency currency, LoanScheduleParams scheduleParams, LocalDate lastRestDate,
- LocalDate periodStartDateApplicableForInterest, RecalculationDetail detail) {
+ final MonetaryCurrency currency, LoanScheduleParams scheduleParams, LocalDate lastRestDate, RecalculationDetail detail) {
updateLatePaidAmountsToPrincipalMap(detail.getTransaction(), loanApplicationTerms, currency, holidayDetailDTO, lastRestDate,
scheduleParams);
- scheduleParams.getCompoundingDateVariations().put(periodStartDateApplicableForInterest,
- new TreeMap<>(scheduleParams.getCompoundingMap()));
}
private void updateAmountsBasedOnEarlyPayment(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO,
@@ -1064,9 +1076,16 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
return isAmountChanged;
}
- private Money fetchCompoundedArrears(final LoanApplicationTerms loanApplicationTerms, final MonetaryCurrency currency,
+ private Money fetchArrears(final LoanApplicationTerms loanApplicationTerms, final MonetaryCurrency currency,
final LoanTransaction transaction) {
Money arrears = transaction.getPrincipalPortion(currency);
+ arrears = arrears.plus(fetchCompoundedArrears(loanApplicationTerms, currency, transaction));
+ return arrears;
+ }
+
+ private Money fetchCompoundedArrears(final LoanApplicationTerms loanApplicationTerms, final MonetaryCurrency currency,
+ final LoanTransaction transaction) {
+ Money arrears = Money.zero(currency);
if (loanApplicationTerms.getInterestRecalculationCompoundingMethod().isInterestCompoundingEnabled()) {
arrears = arrears.plus(transaction.getInterestPortion(currency));
}
@@ -1096,7 +1115,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
final Collection<RecalculationDetail> transactions, final Set<LoanCharge> loanCharges, final LoanScheduleParams params) {
boolean isFirstRepayment = false;
LocalDate startDate = params.getPeriodStartDate();
- Money outstanding = Money.zero(currency);
+ Money outstanding = params.getOutstandingBalanceAsPerRest();
Money totalInterest = Money.zero(currency);
Money totalCumulativeInterest = Money.zero(currency);
double interestCalculationGraceOnRepaymentPeriodFraction = Double.valueOf(0);
@@ -1111,15 +1130,12 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
if (params.getActualRepaymentDate().isAfter(currentDate)) {
params.setActualRepaymentDate(currentDate);
}
- outstanding = updateOutstandingFromLatePayment(params.getPeriodStartDate(), params.getLatePaymentMap(), outstanding);
Collection<RecalculationDetail> applicableTransactions = getApplicableTransactionsForPeriod(
params.applyInterestRecalculation(), params.getActualRepaymentDate(), transactions);
- if (!params.getLatePaymentMap().isEmpty()) {
- populateCompoundingDatesInPeriod(params.getPeriodStartDate(), params.getActualRepaymentDate(), currentDate,
- loanApplicationTerms, holidayDetailDTO, params.getCompoundingMap(), loanCharges, currency);
- }
+ populateCompoundingDatesInPeriod(params.getPeriodStartDate(), params.getActualRepaymentDate(), loanApplicationTerms,
+ holidayDetailDTO, params, loanCharges, currency);
for (RecalculationDetail detail : applicableTransactions) {
if (detail.isProcessed()) {
@@ -1146,22 +1162,30 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
totalCumulativeInterest = totalCumulativeInterest.plus(totalInterest);
totalInterest = totalInterest.zero();
addLoanRepaymentScheduleInstallment(params.getInstallments(), installment);
+ updateCompoundingMap(loanApplicationTerms, holidayDetailDTO, params, lastRestDate, transactionDate);
+ populateCompoundingDatesInPeriod(installment.periodDueDate(), params.getActualRepaymentDate(), loanApplicationTerms,
+ holidayDetailDTO, params, loanCharges, currency);
+ params.setCompoundedInLastInstallment(params.getUnCompoundedAmount());
params.setPeriodStartDate(transactionDate);
startDate = transactionDate;
}
loanRepaymentScheduleTransactionProcessor.handleRepaymentSchedule(currentTransactions, currency, params.getInstallments());
+ updateLatePaidAmountsToPrincipalMap(detail.getTransaction(), loanApplicationTerms, currency, holidayDetailDTO,
+ lastRestDate, params);
updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, params.getLatePaymentMap(), currentDate,
- params.getInstallments(), false, lastRestDate, params.getCompoundingMap());
- outstanding = outstanding.zero();
- outstanding = updateOutstandingFromLatePayment(params.getPeriodStartDate(), params.getLatePaymentMap(), outstanding);
- outstanding = updateBalanceForInterestCalculation(params.getPrincipalPortionMap(), params.getPeriodStartDate(),
- outstanding, false);
- if (params.getLatePaymentMap().isEmpty() && !outstanding.isGreaterThanZero()) {
+ params.getInstallments(), false, lastRestDate);
+ if (params.getLatePaymentMap().isEmpty() && isCompleted(params.getInstallments())) {
+ outstanding = outstanding.zero();
+ } else {
+ outstanding = updateBalanceForInterestCalculation(params.getPrincipalPortionMap(), params.getPeriodStartDate(),
+ outstanding, false);
+ }
+ if (params.getLatePaymentMap().isEmpty() && outstanding.isZero()) {
break;
}
}
- if (outstanding.isGreaterThanZero()) {
+ if (!outstanding.isZero()) {
PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(
this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, totalInterest.zero(),
totalInterest.zero(), totalInterest.zero(), totalInterest.zero(), outstanding, loanApplicationTerms,
@@ -1169,15 +1193,31 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
params.getActualRepaymentDate(), applicableVariations);
Money interest = principalInterestForThisPeriod.interest();
totalInterest = totalInterest.plus(interest);
- if (loanApplicationTerms.getInterestRecalculationCompoundingMethod().isInterestCompoundingEnabled()) {
- LocalDate compoundingEffectiveDate = getNextCompoundScheduleDate(params.getActualRepaymentDate().minusDays(1),
- loanApplicationTerms, holidayDetailDTO);
- params.getLatePaymentMap().put(compoundingEffectiveDate, interest);
+ Money uncompounded = params.getUnCompoundedAmount();
+ Money compounded = uncompounded.zero();
+ for (Map.Entry<LocalDate, Money> mapEntry : params.getCompoundingMap().entrySet()) {
+ if (mapEntry.getKey().isAfter(params.getPeriodStartDate())) {
+ compounded = compounded.plus(mapEntry.getValue());
+ }
+ }
+ Money compoundedForThisPeriod = compounded.minus(uncompounded);
+ Money uncompoundedForThisPeriod = interest.minus(compoundedForThisPeriod);
+ params.setUnCompoundedAmount(uncompoundedForThisPeriod);
+ LocalDate compoundingDate = params.getPeriodStartDate();
+ if (loanApplicationTerms.allowCompoundingOnEod()) {
+ compoundingDate = compoundingDate.minusDays(1);
+ }
+ compoundingDate = getNextCompoundScheduleDate(compoundingDate, loanApplicationTerms, holidayDetailDTO);
+ if(compoundingDate.isEqual(params.getActualRepaymentDate())){
+ params.getCompoundingMap().put(compoundingDate, uncompoundedForThisPeriod);
+ params.setUnCompoundedAmount(uncompoundedForThisPeriod.zero());
}
+
+
}
params.setPeriodStartDate(params.getActualRepaymentDate());
- } while (params.getActualRepaymentDate().isBefore(currentDate) && outstanding.isGreaterThanZero());
+ } while (params.getActualRepaymentDate().isBefore(currentDate) && !outstanding.isZero());
if (totalInterest.isGreaterThanZero()) {
LoanScheduleModelRepaymentPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(params.getInstalmentNumber(),
@@ -1185,11 +1225,23 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
totalInterest.zero(), totalInterest.zero(), totalInterest, true);
params.incrementInstalmentNumber();
periods.add(installment);
+ params.getCompoundingDateVariations().put(startDate, new TreeMap<>(params.getCompoundingMap()));
totalCumulativeInterest = totalCumulativeInterest.plus(totalInterest);
}
return totalCumulativeInterest;
}
+ private boolean isCompleted(List<LoanRepaymentScheduleInstallment> installments) {
+ boolean isCompleted = true;
+ for (LoanRepaymentScheduleInstallment installment : installments) {
+ if (installment.isNotFullyPaidOff()) {
+ isCompleted = false;
+ break;
+ }
+ }
+ return isCompleted;
+ }
+
private Collection<RecalculationDetail> getApplicableTransactionsForPeriod(final boolean applyInterestRecalculation,
LocalDate repaymentDate, final Collection<RecalculationDetail> transactions) {
Collection<RecalculationDetail> applicableTransactions = new ArrayList<>();
@@ -1221,21 +1273,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
currentTransactions.add(detail.getTransaction());
detail.setProcessed(true);
return currentTransactions;
- }
- private Money updateOutstandingFromLatePayment(LocalDate periodStartDate, Map<LocalDate, Money> latePaymentMap, Money outstanding) {
- Map<LocalDate, Money> retainEntries = new HashMap<>();
- for (Map.Entry<LocalDate, Money> mapEntry : latePaymentMap.entrySet()) {
- if (!mapEntry.getKey().isAfter(periodStartDate)) {
- outstanding = outstanding.plus(mapEntry.getValue());
- } else {
- retainEntries.put(mapEntry.getKey(), mapEntry.getValue());
- }
- }
- latePaymentMap.clear();
- latePaymentMap.putAll(retainEntries);
- retainEntries.clear();
- return outstanding;
}
/**
@@ -1315,22 +1353,13 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
holidayDetailDTO);
Money principalPortion = loanTransaction.getPrincipalPortion(currency);
- Money compoundedLatePayments = Money.zero(currency);
- if (applicationTerms.getInterestRecalculationCompoundingMethod().isInterestCompoundingEnabled()) {
- compoundedLatePayments = compoundedLatePayments.plus(loanTransaction.getInterestPortion(currency));
- }
- if (applicationTerms.getInterestRecalculationCompoundingMethod().isFeeCompoundingEnabled()) {
- compoundedLatePayments = compoundedLatePayments.plus(loanTransaction.getFeeChargesPortion(currency)).plus(
- loanTransaction.getPenaltyChargesPortion(currency));
- }
- updateCompoundingAmount(params.getPrincipalPortionMap(), params.getLatePaymentMap(), currency, lastRestDate, principalPortion,
- applicableDate);
- updateCompoundingAmount(params.getPrincipalPortionMap(), params.getCompoundingMap(), currency, lastRestDate,
- compoundedLatePayments, applicableDate);
+ updateLatePaymentCompoundingAmount(params.getPrincipalPortionMap(), params.getLatePaymentMap(), currency, lastRestDate,
+ principalPortion, applicableDate);
+ adjustCompoundedAmountWithPaidDetail(params, lastRestDate, applicableDate, loanTransaction, applicationTerms);
}
- private void updateCompoundingAmount(final Map<LocalDate, Money> principalVariationMap,
+ private void updateLatePaymentCompoundingAmount(final Map<LocalDate, Money> principalVariationMap,
final Map<LocalDate, Money> latePaymentCompoundingMap, final MonetaryCurrency currency, final LocalDate lastRestDate,
Money compoundedPortion, final LocalDate applicableDate) {
Money appliedOnPrincipalVariationMap = Money.zero(currency);
@@ -1368,17 +1397,11 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
*/
private void updateLatePaymentsToMap(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO,
final MonetaryCurrency currency, final Map<LocalDate, Money> latePaymentMap, final LocalDate scheduledDueDate,
- List<LoanRepaymentScheduleInstallment> installments, boolean applyRestFrequencyForPrincipal, final LocalDate lastRestDate,
- final TreeMap<LocalDate, Money> compoundingMap) {
+ List<LoanRepaymentScheduleInstallment> installments, boolean applyRestFrequencyForPrincipal, final LocalDate lastRestDate) {
latePaymentMap.clear();
LocalDate currentDate = DateUtils.getLocalDateOfTenant();
Money totalCompoundingAmount = Money.zero(currency);
- Money compoundedMoney = Money.zero(currency);
- if (!compoundingMap.isEmpty()) {
- compoundedMoney = compoundingMap.get(lastRestDate);
- }
- boolean clearCompoundingMap = true;
for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : installments) {
if (loanRepaymentScheduleInstallment.isNotFullyPaidOff()
&& !loanRepaymentScheduleInstallment.getDueDate().isAfter(scheduledDueDate)
@@ -1395,104 +1418,181 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
.plus(loanRepaymentScheduleInstallment.getPrincipalOutstanding(currency));
}
- final Money changedCompoundedMoney = updateMapWithCompoundingDetails(loanApplicationTerms, holidayDetailDTO, currency,
- compoundingMap, loanRepaymentScheduleInstallment, lastRestDate, compoundedMoney, scheduledDueDate);
- if (compoundedMoney.isZero() || !compoundedMoney.isEqualTo(changedCompoundedMoney)) {
- compoundedMoney = changedCompoundedMoney;
- clearCompoundingMap = false;
- }
}
}
if (totalCompoundingAmount.isGreaterThanZero()) {
updateMapWithAmount(latePaymentMap, totalCompoundingAmount.negated(), lastRestDate);
}
- if (clearCompoundingMap) {
- compoundingMap.clear();
- }
}
- private Money updateMapWithCompoundingDetails(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO,
- final MonetaryCurrency currency, final TreeMap<LocalDate, Money> compoundingMap,
- final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment, final LocalDate lastRestDate,
- final Money compoundedMoney, final LocalDate scheduledDueDate) {
- Money ignoreMoney = compoundedMoney;
- if (loanApplicationTerms.getInterestRecalculationCompoundingMethod().isCompoundingEnabled()) {
- LocalDate compoundingEffectiveDate = getNextCompoundScheduleDate(loanRepaymentScheduleInstallment.getDueDate().minusDays(1),
- loanApplicationTerms, holidayDetailDTO);
+ private void updateCompoundingMap(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO,
+ final LoanScheduleParams params, final LocalDate lastRestDate, final LocalDate scheduledDueDate) {
+ if (loanApplicationTerms.isInterestRecalculationEnabled()
+ && loanApplicationTerms.getInterestRecalculationCompoundingMethod().isCompoundingEnabled()) {
+ final MonetaryCurrency currency = params.getCurrency();
+ Money totalCompoundedAmount = Money.zero(currency);
+ boolean lastInstallmentIsPastDate = false;
+ for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : params.getInstallments()) {
+ if (params.getCompoundingDateVariations().containsKey(loanRepaymentScheduleInstallment.getFromDate())) {
+ lastInstallmentIsPastDate = params.applyInterestRecalculation()
+ && loanRepaymentScheduleInstallment.getDueDate().isBefore(DateUtils.getLocalDateOfTenant());
+ } else {
+ final boolean isPastDate = params.applyInterestRecalculation()
+ && loanRepaymentScheduleInstallment.getDueDate().isBefore(DateUtils.getLocalDateOfTenant());
+ boolean periodHasCompoundingDate = false;
+ Money amountCharged = getIncomeForCompounding(loanApplicationTerms, currency, loanRepaymentScheduleInstallment);
+ final Map<LocalDate, Money> compoundingMap = params.getCompoundingMap();
+ LocalDate effectiveStartDate = loanRepaymentScheduleInstallment.getFromDate();
+ if (loanApplicationTerms.allowCompoundingOnEod()) {
+ effectiveStartDate = loanRepaymentScheduleInstallment.getFromDate().minusDays(1);
+ }
+ LocalDate compoundingEffectiveDate = getNextCompoundScheduleDate(effectiveStartDate, loanApplicationTerms,
+ holidayDetailDTO);
+ final LocalDate restDate = getNextRestScheduleDate(scheduledDueDate.minusDays(1), loanApplicationTerms,
+ holidayDetailDTO);
+ if (!compoundingEffectiveDate.isAfter(loanRepaymentScheduleInstallment.getDueDate())) {
+ totalCompoundedAmount = totalCompoundedAmount.minus(params.getUnCompoundedAmount());
+ periodHasCompoundingDate = true;
+ }
+ while (!compoundingEffectiveDate.isAfter(loanRepaymentScheduleInstallment.getDueDate())) {
+ if (compoundingEffectiveDate.isEqual(loanRepaymentScheduleInstallment.getDueDate())) {
+ Money amountToBeCompounding = amountCharged.minus(totalCompoundedAmount);
+ updateMapWithAmount(compoundingMap, amountToBeCompounding, compoundingEffectiveDate);
+ totalCompoundedAmount = totalCompoundedAmount.plus(amountToBeCompounding);
+
+ } else if (compoundingMap.containsKey(compoundingEffectiveDate)) {
+ Money compounedAmount = compoundingMap.get(compoundingEffectiveDate);
+ totalCompoundedAmount = totalCompoundedAmount.plus(compounedAmount);
+ }
- if (compoundingEffectiveDate.isBefore(DateUtils.getLocalDateOfTenant())) {
- Money amount = Money.zero(currency);
- switch (loanApplicationTerms.getInterestRecalculationCompoundingMethod()) {
- case INTEREST:
- amount = amount.plus(loanRepaymentScheduleInstallment.getInterestOutstanding(currency));
- break;
- case FEE:
- amount = amount.plus(loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency));
- amount = amount.plus(loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency));
- break;
- case INTEREST_AND_FEE:
- amount = amount.plus(loanRepaymentScheduleInstallment.getInterestOutstanding(currency));
- amount = amount.plus(loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency));
- amount = amount.plus(loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency));
- break;
- default:
- break;
- }
- if (compoundingEffectiveDate.isBefore(scheduledDueDate)) {
- ignoreMoney = ignoreMoney.plus(amount);
- if (ignoreMoney.isGreaterThanZero()) {
- updateMapWithAmount(compoundingMap, ignoreMoney, compoundingEffectiveDate);
- updateMapWithAmount(compoundingMap, ignoreMoney.negated(), lastRestDate);
- ignoreMoney = ignoreMoney.zero();
+ if (!loanApplicationTerms.allowCompoundingOnEod()) {
+ compoundingEffectiveDate = compoundingEffectiveDate.plusDays(1);
+ }
+ compoundingEffectiveDate = getNextCompoundScheduleDate(compoundingEffectiveDate, loanApplicationTerms,
+ holidayDetailDTO);
}
- } else {
- if (ignoreMoney.isLessThanZero()) {
- LocalDate firstKey = compoundingMap.firstKey();
- updateMapWithAmount(compoundingMap, ignoreMoney, firstKey);
- updateMapWithAmount(compoundingMap, ignoreMoney.negated(), lastRestDate);
- ignoreMoney = ignoreMoney.zero();
+ if (periodHasCompoundingDate) {
+ if (isPastDate) {
+ updateMapWithAmount(params.getPrincipalPortionMap(),
+ totalCompoundedAmount.plus(params.getUnCompoundedAmount()), lastRestDate);
+ } else {
+ Money amountToBeEffected = amountCharged;
+ if (lastInstallmentIsPastDate) {
+ amountToBeEffected = amountToBeEffected.plus(params.getUnCompoundedAmount());
+ }
+ updateMapWithAmount(params.getPrincipalPortionMap(), amountToBeEffected, restDate);
+ }
+ }
+ if (totalCompoundedAmount.isGreaterThanZero()) {
+ params.getCompoundingDateVariations().put(loanRepaymentScheduleInstallment.getFromDate(),
+ new TreeMap<>(params.getCompoundingMap()));
+ for (Map.Entry<LocalDate, Money> mapEntry : params.getCompoundingMap().entrySet()) {
+ if (!mapEntry.getKey().isAfter(loanRepaymentScheduleInstallment.getDueDate())) {
+ updateMapWithAmount(params.getPrincipalPortionMap(), mapEntry.getValue().negated(), mapEntry.getKey());
+ }else if(params.getUnCompoundedAmount().isEqualTo( mapEntry.getValue())){
+ totalCompoundedAmount = totalCompoundedAmount.plus(params.getUnCompoundedAmount());
+ }
+ }
+ params.minusUnCompoundedAmount(params.getUnCompoundedAmount());
+ params.getCompoundingMap().clear();
+ params.addUnCompoundedAmount(amountCharged.minus(totalCompoundedAmount.minus(params.getCompoundedInLastInstallment())));
+ } else {
+ params.getCompoundingMap().clear();
+ params.getCompoundingDateVariations().put(loanRepaymentScheduleInstallment.getFromDate(),
+ new TreeMap<>(params.getCompoundingMap()));
+ params.addUnCompoundedAmount(amountCharged);
}
- updateMapWithAmount(compoundingMap, amount, compoundingEffectiveDate);
- updateMapWithAmount(compoundingMap, amount.negated(), lastRestDate);
+ params.setCompoundedInLastInstallment(amountCharged.zero());
+ lastInstallmentIsPastDate = isPastDate;
}
+
}
}
- return ignoreMoney;
+
+ }
+
+ private Money getIncomeForCompounding(final LoanApplicationTerms loanApplicationTerms, final MonetaryCurrency currency,
+ LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment) {
+ Money interestCharged = Money.zero(currency);
+ Money feeCharged = Money.zero(currency);
+ Money penaltyCharged = Money.zero(currency);
+ Money amountCharged = Money.zero(currency);
+ switch (loanApplicationTerms.getInterestRecalculationCompoundingMethod()) {
+ case INTEREST:
+ interestCharged = interestCharged.plus(loanRepaymentScheduleInstallment.getInterestCharged(currency));
+ break;
+ case FEE:
+ feeCharged = feeCharged.plus(loanRepaymentScheduleInstallment.getFeeChargesCharged(currency));
+ penaltyCharged = penaltyCharged.plus(loanRepaymentScheduleInstallment.getPenaltyChargesCharged(currency));
+ break;
+ case INTEREST_AND_FEE:
+ interestCharged = interestCharged.plus(loanRepaymentScheduleInstallment.getInterestCharged(currency));
+ feeCharged = feeCharged.plus(loanRepaymentScheduleInstallment.getFeeChargesCharged(currency));
+ penaltyCharged = penaltyCharged.plus(loanRepaymentScheduleInstallment.getPenaltyChargesCharged(currency));
+ break;
+ default:
+ break;
+ }
+ amountCharged = interestCharged.plus(feeCharged).plus(penaltyCharged);
+ return amountCharged;
+ }
+
+ private void adjustCompoundedAmountWithPaidDetail(final LoanScheduleParams params, final LocalDate lastRestDate,
+ final Collection<LoanTransaction> transactions, final LoanApplicationTerms loanApplicationTerms,
+ HolidayDetailDTO holidayDetailDTO) {
+ for (LoanTransaction loanTransaction : transactions) {
+ final LocalDate amountApplicableDate = getNextRestScheduleDate(loanTransaction.getTransactionDate().minusDays(1),
+ loanApplicationTerms, holidayDetailDTO);
+ adjustCompoundedAmountWithPaidDetail(params, lastRestDate, amountApplicableDate, loanTransaction, loanApplicationTerms);
+ }
+ }
+
+ private void adjustCompoundedAmountWithPaidDetail(final LoanScheduleParams params, final LocalDate lastRestDate,
+ final LocalDate amountApplicableDate, final LoanTransaction transaction, final LoanApplicationTerms loanApplicationTerms) {
+ adjustCompoundedAmountWithPaidDetail(params.getPrincipalPortionMap(), lastRestDate, amountApplicableDate, transaction,
+ loanApplicationTerms, params.getCurrency());
+ }
+
+ private void adjustCompoundedAmountWithPaidDetail(final Map<LocalDate, Money> principalPortionMap, final LocalDate lastRestDate,
+ final LocalDate amountApplicableDate, final LoanTransaction transaction, final LoanApplicationTerms loanApplicationTerms,
+ final MonetaryCurrency currency) {
+ if (!amountApplicableDate.isEqual(lastRestDate)) {
+ Money compoundedIncome = fetchCompoundedArrears(loanApplicationTerms, currency, transaction);
+ updateMapWithAmount(principalPortionMap, compoundedIncome, amountApplicableDate);
+ updateMapWithAmount(principalPortionMap, compoundedIncome.negated(), lastRestDate);
+ }
}
- private void populateCompoundingDatesInPeriod(final LocalDate startDate, final LocalDate endDate, final LocalDate currentDate,
+ private void populateCompoundingDatesInPeriod(final LocalDate startDate, final LocalDate endDate,
final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO,
- final Map<LocalDate, Money> compoundingMap, final Set<LoanCharge> charges, MonetaryCurrency currency) {
+ final LoanScheduleParams scheduleParams, final Set<LoanCharge> charges, MonetaryCurrency currency) {
if (loanApplicationTerms.getInterestRecalculationCompoundingMethod().isCompoundingEnabled()) {
+ final Map<LocalDate, Money> compoundingMap = scheduleParams.getCompoundingMap();
LocalDate lastCompoundingDate = startDate;
LocalDate compoundingDate = startDate;
- while (compoundingDate.isBefore(endDate) && compoundingDate.isBefore(currentDate)) {
+ boolean addUncompounded = true;
+ while (compoundingDate.isBefore(endDate)) {
+ if (loanApplicationTerms.allowCompoundingOnEod()) {
+ compoundingDate = compoundingDate.minusDays(1);
+ }
compoundingDate = getNextCompoundScheduleDate(compoundingDate, loanApplicationTerms, holidayDetailDTO);
- if (!compoundingDate.isBefore(currentDate)) {
- break;
- } else if (compoundingDate.isAfter(endDate)) {
- updateMapWithAmount(compoundingMap, Money.zero(currency), compoundingDate);
- } else {
+
+ if (compoundingDate.isBefore(endDate)) {
Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(lastCompoundingDate, compoundingDate, charges, currency,
null, loanApplicationTerms.getPrincipal(), null, false);
Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(lastCompoundingDate, compoundingDate, charges,
currency, null, loanApplicationTerms.getPrincipal(), null, false);
- updateMapWithAmount(compoundingMap, feeChargesForInstallment.plus(penaltyChargesForInstallment), compoundingDate);
+ Money compoundAmount = feeChargesForInstallment.plus(penaltyChargesForInstallment);
+ if (addUncompounded) {
+ compoundAmount = compoundAmount.plus(scheduleParams.getUnCompoundedAmount());
+ addUncompounded = false;
+ }
+ updateMapWithAmount(compoundingMap, compoundAmount, compoundingDate);
}
- lastCompoundingDate = compoundingDate;
- }
- }
- }
- protected void clearMapDetails(final LocalDate startDate, final Map<LocalDate, Money> compoundingMap) {
- Map<LocalDate, Money> temp = new HashMap<>();
- for (LocalDate date : compoundingMap.keySet()) {
- if (!date.isBefore(startDate)) {
- temp.put(date, compoundingMap.get(date));
+ lastCompoundingDate = compoundingDate;
}
}
- compoundingMap.clear();
- compoundingMap.putAll(temp);
}
/**
@@ -1580,16 +1680,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
principalPaid = map.get(amountApplicableDate).plus(principalPaid);
}
map.put(amountApplicableDate, principalPaid);
- }
- private Money getTotalAmount(final Map<LocalDate, Money> map, final MonetaryCurrency currency) {
- Money total = Money.zero(currency);
- for (Map.Entry<LocalDate, Money> mapEntry : map.entrySet()) {
- if (mapEntry.getKey().isBefore(DateUtils.getLocalDateOfTenant())) {
- total = total.plus(mapEntry.getValue());
- }
- }
- return total;
}
@Override
@@ -1737,8 +1828,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
}
// get the loan application terms from the Loan object
- final LoanApplicationTerms loanApplicationTerms = loan.getLoanApplicationTerms(applicationCurrency, restCalendarInstance,
- compoundingCalendarInstance, loanCalendar, floatingRateDTO, isSkipRepaymentonmonthFirst, numberofdays);
+ final LoanApplicationTerms loanApplicationTerms = loan
+ .getLoanApplicationTerms(applicationCurrency, restCalendarInstance, compoundingCalendarInstance, loanCalendar,
+ floatingRateDTO, isSkipRepaymentonmonthFirst, numberofdays, holidayDetailDTO);
// for applying variations
Collection<LoanTermVariationsData> loanTermVariations = loanApplicationTerms.getLoanTermVariations().getInterestRateChanges();
@@ -2117,6 +2209,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
// action will be performed on this value
Money reducePrincipal = outstandingBalanceAsPerRest.zero();
+ Money uncompoundedAmount = outstandingBalanceAsPerRest.zero();
// principal changes will be added along with date(after applying
// rest)
// from when these amounts will effect the outstanding balance for
@@ -2133,6 +2226,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
// from when these amounts will effect the outstanding balance for
// interest calculation
final TreeMap<LocalDate, Money> compoundingMap = new TreeMap<>();
+ final Map<LocalDate, Map<LocalDate, Money>> compoundingDateVariations = new HashMap<>();
LocalDate currentDate = DateUtils.getLocalDateOfTenant();
LocalDate lastRestDate = currentDate;
if (loanApplicationTerms.getRestCalendarInstance() != null) {
@@ -2179,6 +2273,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
// process the installment only if recalculate from date is
// greater than due date
if (installment.getDueDate().isAfter(lastInstallmentDate)) {
+ if (totalCumulativePrincipal.isGreaterThanOrEqualTo(loanApplicationTerms.getTotalDisbursedAmount())) {
+ break;
+ }
LocalDate previousRepaymentDate = actualRepaymentDate;
actualRepaymentDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate, loanApplicationTerms,
isFirstRepayment, holidayDetailDTO);
@@ -2272,6 +2369,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
// updates map with the installment principal amount excluding
// unprocessed amount since this amount is already accounted.
updateMapWithAmount(principalPortionMap, installment.getPrincipal(currency).minus(unprocessed), amountApplicableDate);
+ uncompoundedAmount = updateCompoundingDetailsForPartialScheduleGeneration(installment, loanApplicationTerms,
+ principalPortionMap, compoundingDateVariations, uncompoundedAmount, applicableTransactions, lastRestDate,
+ holidayDetailDTO);
// update outstanding balance for interest calculation
outstandingBalanceAsPerRest = updateBalanceForInterestCalculation(principalPortionMap, installment.getDueDate(),
outstandingBalanceAsPerRest, false);
@@ -2284,7 +2384,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
// updates the map with over due amounts
updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, latePaymentMap, lastInstallmentDate,
- newRepaymentScheduleInstallments, true, lastRestDate, compoundingMap);
+ newRepaymentScheduleInstallments, true, lastRestDate);
// for partial schedule generation
if (!newRepaymentScheduleInstallments.isEmpty() && totalCumulativeInterest.isGreaterThanZero()) {
@@ -2293,10 +2393,11 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
loanTermInDays, periodStartDate, actualRepaymentDate, totalCumulativePrincipal, totalCumulativeInterest,
totalFeeChargesCharged, totalPenaltyChargesCharged, totalRepaymentExpected,
totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap, compoundingMap,
- disburseDetailMap, principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest,
+ uncompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest,
newRepaymentScheduleInstallments, recalculationDetails, loanRepaymentScheduleTransactionProcessor,
scheduleTillDate, currency, applyInterestRecalculation);
retainedInstallments.addAll(newRepaymentScheduleInstallments);
+ loanScheduleParams.getCompoundingDateVariations().putAll(compoundingDateVariations);
}
}
@@ -2328,6 +2429,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
final Map<LocalDate, Money> principalPortionMap, LoanRepaymentScheduleInstallment installment,
Collection<RecalculationDetail> applicableTransactions, Money actualPrincipalPortion) {
Money unprocessed = Money.zero(currency);
+ Money totalUnprocessed = Money.zero(currency);
for (RecalculationDetail detail : applicableTransactions) {
if (!detail.isProcessed()) {
Money principalProcessed = installment.getPrincipalCompleted(currency);
@@ -2357,10 +2459,53 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
LocalDate applicableDate = getNextRestScheduleDate(detail.getTransactionDate().minusDays(1), loanApplicationTerms,
holidayDetailDTO);
updateMapWithAmount(principalPortionMap, unprocessed, applicableDate);
+ totalUnprocessed = totalUnprocessed.plus(unprocessed);
+
+ }
+ }
+ return totalUnprocessed;
+ }
+ private Money updateCompoundingDetailsForPartialScheduleGeneration(final LoanRepaymentScheduleInstallment installment,
+ LoanApplicationTerms loanApplicationTerms, Map<LocalDate, Money> principalMap,
+ final Map<LocalDate, Map<LocalDate, Money>> compoundingDateVariations, final Money uncompoundedAmount,
+ final Collection<RecalculationDetail> applicableTransactions, LocalDate lastRestDate, HolidayDetailDTO holidayDetailDTO) {
+ Money uncompounded = uncompoundedAmount;
+ MonetaryCurrency currency = uncompoundedAmount.getCurrency();
+ for (RecalculationDetail detail : applicableTransactions) {
+ LocalDate applicableDate = getNextRestScheduleDate(detail.getTransactionDate().minusDays(1), loanApplicationTerms,
+ holidayDetailDTO);
+ adjustCompoundedAmountWithPaidDetail(principalMap, lastRestDate, applicableDate, detail.getTransaction(), loanApplicationTerms,
+ currency);
+ }
+ Money amountCharged = getIncomeForCompounding(loanApplicationTerms, currency, installment);
+ final List<LoanInterestRecalcualtionAdditionalDetails> details = installment.getLoanCompoundingDetails();
+ Money totalCompounded = Money.zero(currency);
+ Map<LocalDate, Money> compoundingMap = new TreeMap<>();
+ for (LoanInterestRecalcualtionAdditionalDetails additionalDetails : details) {
+ LocalDate effectiveDate = additionalDetails.getEffectiveDate();
+ if (loanApplicationTerms.allowCompoundingOnEod()) {
+ effectiveDate = effectiveDate.plusDays(1);
+ }
+ compoundingMap.put(effectiveDate, Money.of(currency, additionalDetails.getAmount()));
+ totalCompounded = totalCompounded.plus(additionalDetails.getAmount());
+ updateMapWithAmount(principalMap, Money.of(currency, additionalDetails.getAmount()).negated(), effectiveDate);
+ }
+ compoundingDateVariations.put(installment.getFromDate(), compoundingMap);
+ if (totalCompounded.isGreaterThanZero()) {
+ final boolean isPastDate = installment.getDueDate().isBefore(DateUtils.getLocalDateOfTenant());
+ final LocalDate restDate = getNextRestScheduleDate(installment.getDueDate().minusDays(1), loanApplicationTerms,
+ holidayDetailDTO);
+ if (isPastDate) {
+ updateMapWithAmount(principalMap, totalCompounded, lastRestDate);
+ } else {
+ updateMapWithAmount(principalMap, totalCompounded, restDate);
}
+ uncompounded = amountCharged.plus(uncompounded).minus(totalCompounded);
+ } else {
+ uncompounded = uncompounded.plus(amountCharged);
}
- return unprocessed;
+ return uncompounded;
}
private void updateAmortization(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, int periodNumber,
@@ -2432,7 +2577,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
scheduledLoanInstallment.periodFromDate(), scheduledLoanInstallment.periodDueDate(),
scheduledLoanInstallment.principalDue(), scheduledLoanInstallment.interestDue(),
scheduledLoanInstallment.feeChargesDue(), scheduledLoanInstallment.penaltyChargesDue(),
- scheduledLoanInstallment.isRecalculatedInterestComponent());
+ scheduledLoanInstallment.isRecalculatedInterestComponent(), scheduledLoanInstallment.getLoanCompoundingDetails());
installments.add(installment);
}
return installment;
@@ -2473,6 +2618,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
} else {
CalendarInstance calendarInstance = loanApplicationTerms.getCompoundingCalendarInstance();
nextScheduleDate = CalendarUtils.getNextScheduleDate(calendarInstance.getCalendar(), startDate);
+ if (loanApplicationTerms.allowCompoundingOnEod()) {
+ nextScheduleDate = nextScheduleDate.plusDays(1);
+ }
}
return nextScheduleDate;
@@ -2510,9 +2658,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
penaltyCharges = penaltyCharges.plus(currentInstallment.getPenaltyChargesOutstanding(currency));
}
}
-
+ final List<LoanInterestRecalcualtionAdditionalDetails> compoundingDetails = null;
return new LoanRepaymentScheduleInstallment(null, 0, onDate, onDate, totalPrincipal.getAmount(), totalInterest.getAmount(),
- feeCharges.getAmount(), penaltyCharges.getAmount(), false);
+ feeCharges.getAmount(), penaltyCharges.getAmount(), false, compoundingDetails);
}
/**
http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/763cf18b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java
index f53a50e..1bbefa4 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java
@@ -25,7 +25,6 @@ import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
-import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod;
@@ -70,7 +69,6 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS
LocalDate interestStartDate = periodStartDate;
Money interestForThisInstallment = totalCumulativePrincipal.zero();
- Money compoundedMoney = totalCumulativePrincipal.zero();
Money compoundedInterest = totalCumulativePrincipal.zero();
Money balanceForInterestCalculation = outstandingBalance;
Money cumulatingInterestDueToGrace = cumulatingInterestPaymentDueToGrace;
@@ -89,13 +87,6 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS
}
}
if (principalVariation != null) {
- // identifies rest date after current date for reducing all
- // compounding
- // values
- LocalDate compoundingEndDate = principalVariation.ceilingKey(DateUtils.getLocalDateOfTenant());
- if (compoundingEndDate == null) {
- compoundingEndDate = DateUtils.getLocalDateOfTenant();
- }
for (Map.Entry<LocalDate, Money> principal : principalVariation.entrySet()) {
@@ -120,7 +111,7 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS
}
// fee compounding will be done after calculation
compoundFee = compoundingMap.get(principal.getKey());
- compoundedMoney = compoundedMoney.plus(interestToBeCompounded).plus(compoundFee);
+ compoundingMap.put(principal.getKey(), interestToBeCompounded.plus(compoundFee));
}
balanceForInterestCalculation = balanceForInterestCalculation.plus(principal.getValue()).plus(compoundFee);
if (interestRates.containsKey(principal.getKey())) {
@@ -129,14 +120,6 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS
}
}
- if (!periodEndDate.isBefore(compoundingEndDate)) {
- balanceForInterestCalculation = balanceForInterestCalculation.minus(compoundedMoney);
- compoundingMap.clear();
- } else if (compoundedMoney.isGreaterThanZero()) {
- compoundingMap.put(periodEndDate, compoundedMoney);
- compoundingMap.put(compoundingEndDate, compoundedMoney.negated());
- clearMapDetails(periodEndDate, compoundingMap);
- }
}
final PrincipalInterest result = loanApplicationTerms.calculateTotalInterestForPeriod(calculator,
http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/763cf18b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java
index 7afc002..b57f215 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java
@@ -62,8 +62,8 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
} else {
Calendar currentCalendar = loanApplicationTerms.getLoanCalendar();
dueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms.getRepaymentPeriodFrequencyType(),
- loanApplicationTerms.getRepaymentEvery(), lastRepaymentDate, loanApplicationTerms.getNthDay(),
- loanApplicationTerms.getWeekDayType());
+ loanApplicationTerms.getRepaymentEvery(), lastRepaymentDate, null,
+ null);
dueRepaymentPeriodDate = CalendarUtils.adjustDate(dueRepaymentPeriodDate, loanApplicationTerms.getSeedDate(),
loanApplicationTerms.getRepaymentPeriodFrequencyType());
if (currentCalendar != null) {
@@ -219,7 +219,8 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
@Override
public LocalDate idealDisbursementDateBasedOnFirstRepaymentDate(final PeriodFrequencyType repaymentPeriodFrequencyType,
- final int repaidEvery, final LocalDate firstRepaymentDate) {
+ final int repaidEvery, final LocalDate firstRepaymentDate, final Calendar loanCalendar, final HolidayDetailDTO holidayDetailDTO,
+ final LoanApplicationTerms loanApplicationTerms) {
LocalDate idealDisbursementDate = null;
@@ -231,7 +232,15 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
idealDisbursementDate = firstRepaymentDate.minusWeeks(repaidEvery);
break;
case MONTHS:
- idealDisbursementDate = firstRepaymentDate.minusMonths(repaidEvery);
+ if (loanCalendar == null) {
+ idealDisbursementDate = firstRepaymentDate.minusMonths(repaidEvery);
+ } else {
+ idealDisbursementDate = CalendarUtils.getNewRepaymentMeetingDate(loanCalendar.getRecurrence(),
+ firstRepaymentDate.minusMonths(repaidEvery), firstRepaymentDate.minusMonths(repaidEvery), repaidEvery,
+ CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(repaymentPeriodFrequencyType),
+ holidayDetailDTO.getWorkingDays(), loanApplicationTerms.isSkipRepaymentOnFirstDayofMonth(),
+ loanApplicationTerms.getNumberOfdays());
+ }
break;
case YEARS:
idealDisbursementDate = firstRepaymentDate.minusYears(repaidEvery);