MIFOSX-1421 : fixed interest recalculation with RBI strategy

This commit is contained in:
Pramod Nuthakki
2014-08-14 01:17:44 +05:30
parent f2e9aa7cb0
commit 80e1d67862
7 changed files with 202 additions and 87 deletions
@@ -1759,8 +1759,9 @@ public class Loan extends AbstractPersistable<Long> {
}
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()
&& fetchRepaymentScheduleInstallment(1).getDueDate().isBefore(LocalDate.now())) {
LocalDate recalculateFrom = null;
regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate,
isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation);
isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, recalculateFrom);
}
updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate);
updateLoanSummaryDerivedFields();
@@ -2295,6 +2296,11 @@ public class Loan extends AbstractPersistable<Long> {
LoanStatus statusEnum = null;
LocalDate recalculateFrom = loanTransaction.getTransactionDate();
if (adjustedTransaction != null && adjustedTransaction.getTransactionDate().isBefore(recalculateFrom)) {
recalculateFrom = adjustedTransaction.getTransactionDate();
}
if (loanTransaction.isRecoveryRepayment()) {
statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_RECOVERY_PAYMENT, LoanStatus.fromInt(this.loanStatus));
} else {
@@ -2382,7 +2388,7 @@ public class Loan extends AbstractPersistable<Long> {
if (reprocess) {
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate,
isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation);
isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, recalculateFrom);
}
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(),
@@ -2395,7 +2401,7 @@ public class Loan extends AbstractPersistable<Long> {
* of the transactions. for this need to save the reversed
* transactions first and then new transactions.
*/
// this.loanTransactions.addAll(changedTransactionDetail.getNewTransactionMappings().values());
this.loanTransactions.addAll(changedTransactionDetail.getNewTransactionMappings().values());
}
updateLoanSummaryDerivedFields();
@@ -2408,6 +2414,9 @@ public class Loan extends AbstractPersistable<Long> {
doPostLoanTransactionChecks(loanTransaction.getTransactionDate(), loanLifecycleStateMachine);
}
if (changedTransactionDetail != null) {
this.loanTransactions.removeAll(changedTransactionDetail.getNewTransactionMappings().values());
}
return changedTransactionDetail;
}
@@ -2434,6 +2443,18 @@ public class Loan extends AbstractPersistable<Long> {
return repaymentsOrWaivers;
}
private List<LoanTransaction> retreiveListOfTransactionsPostDisbursementExcludeAccruals() {
final List<LoanTransaction> repaymentsOrWaivers = new ArrayList<>();
for (final LoanTransaction transaction : this.loanTransactions) {
if (!transaction.isDisbursement() && transaction.isNotReversed() && !transaction.isAccrual()) {
repaymentsOrWaivers.add(transaction);
}
}
final LoanTransactionComparator transactionComparator = new LoanTransactionComparator();
Collections.sort(repaymentsOrWaivers, transactionComparator);
return repaymentsOrWaivers;
}
private void doPostLoanTransactionChecks(final LocalDate transactionDate, final LoanLifecycleStateMachine loanLifecycleStateMachine) {
if (isOverPaid()) {
@@ -2651,8 +2672,9 @@ public class Loan extends AbstractPersistable<Long> {
.determineProcessor(this.transactionProcessingStrategy);
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
LocalDate recalculateFrom = null;
regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate,
isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation);
isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, recalculateFrom);
}
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(
getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments,
@@ -2797,8 +2819,9 @@ public class Loan extends AbstractPersistable<Long> {
regenerateRepaymentSchedule(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays,
workingDays);
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
LocalDate recalculateFrom = null;
regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate,
isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation);
isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, recalculateFrom);
}
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(),
@@ -3897,8 +3920,9 @@ public class Loan extends AbstractPersistable<Long> {
workingDays);
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
LocalDate recalculateFrom = null;
regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate,
isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation);
isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, recalculateFrom);
}
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
@@ -3976,17 +4000,24 @@ public class Loan extends AbstractPersistable<Long> {
return maturityDate;
}
public ChangedTransactionDetail recalculateSchedule(final LoanScheduleGeneratorFactory loanScheduleFactory,
public ChangedTransactionDetail recalculateScheduleFromLastTransaction(final LoanScheduleGeneratorFactory loanScheduleFactory,
final ApplicationCurrency applicationCurrency, final LocalDate calculatedRepaymentsStartingFromDate,
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays,
final CalendarInstance calendarInstanceForInterestRecalculation, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds) {
existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
LocalDate recalculateFrom = null;
List<LoanTransaction> loanTransactions = this.retreiveListOfTransactionsPostDisbursementExcludeAccruals();
for (LoanTransaction loanTransaction : loanTransactions) {
if (recalculateFrom == null || loanTransaction.getTransactionDate().isAfter(recalculateFrom)) {
recalculateFrom = loanTransaction.getTransactionDate();
}
}
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation);
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays,
calendarInstanceForInterestRecalculation, recalculateFrom);
}
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy);
@@ -4002,10 +4033,11 @@ public class Loan extends AbstractPersistable<Long> {
* the transactions. for this need to save the reversed transactions
* first and then new transactions.
*/
// this.loanTransactions.addAll(changedTransactionDetail.getNewTransactionMappings().values());
this.loanTransactions.addAll(changedTransactionDetail.getNewTransactionMappings().values());
updateLoanSummaryDerivedFields();
this.loanTransactions.removeAll(changedTransactionDetail.getNewTransactionMappings().values());
return changedTransactionDetail;
}
@@ -4013,7 +4045,7 @@ public class Loan extends AbstractPersistable<Long> {
private void regenerateRepaymentScheduleWithInterestRecalculation(final LoanScheduleGeneratorFactory loanScheduleFactory,
final ApplicationCurrency applicationCurrency, final LocalDate calculatedRepaymentsStartingFromDate,
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays,
final CalendarInstance calendarInstanceForInterestRecalculation) {
final CalendarInstance calendarInstanceForInterestRecalculation, LocalDate recalculateFrom) {
if (!this.repaymentScheduleDetail().isInterestRecalculationEnabled() || isNpa) { return; }
@@ -4056,8 +4088,10 @@ public class Loan extends AbstractPersistable<Long> {
.determineProcessor(this.transactionProcessingStrategy);
final LoanScheduleModel loanSchedule = loanScheduleGenerator.rescheduleNextInstallments(mc, applicationCurrency,
loanApplicationTerms, charges(), isHolidayEnabled, holidays, workingDays, retreiveListOfTransactionsPostDisbursement(),
loanRepaymentScheduleTransactionProcessor);
loanApplicationTerms, charges(), isHolidayEnabled, holidays, workingDays,
retreiveListOfTransactionsPostDisbursementExcludeAccruals(), loanRepaymentScheduleTransactionProcessor,
this.repaymentScheduleInstallments, recalculateFrom);
if (loanSchedule == null) { return; }
updateLoanSchedule(loanSchedule);
LocalDate lastRepaymentDate = this.getLastRepaymentPeriodDueDate();
@@ -104,11 +104,11 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
final List<LoanRepaymentScheduleInstallment> processInstallments = new ArrayList<>(1);
processInstallments.add(chargePaidDetail.getInstallment());
Money processAmt = chargePaidDetail.getAmount();
if(processAmt.isGreaterThan(unprocessed)){
if (processAmt.isGreaterThan(unprocessed)) {
processAmt = unprocessed;
}
unprocessed = handleTransactionAndCharges(loanTransaction, currency, processInstallments,
transferCharges, processAmt, chargePaidDetail.isFeeCharge());
unprocessed = handleTransactionAndCharges(loanTransaction, currency, processInstallments, transferCharges, processAmt,
chargePaidDetail.isFeeCharge());
if (!unprocessed.isGreaterThanZero()) {
break;
}
@@ -437,19 +437,6 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
// empty implementation by default.
}
@Override
public Map<LocalDate, Money> handleRecalculation(final LocalDate disbursementDate,
final List<LoanTransaction> transactionsPostDisbursement, final MonetaryCurrency currency,
final List<LoanRepaymentScheduleInstallment> installments, LoanRepaymentScheduleInstallment currentinstallment,
final Map<LocalDate, LocalDate> recalculationDates) {
for (final LoanRepaymentScheduleInstallment currentInstallment : installments) {
currentInstallment.resetDerivedComponents();
currentInstallment.updateDerivedFields(currency, disbursementDate);
}
return handleRepaymentSchedule(transactionsPostDisbursement, currency, installments, currentinstallment, recalculationDates);
}
@Override
public Map<LocalDate, Money> handleRepaymentSchedule(final List<LoanTransaction> transactionsPostDisbursement,
final MonetaryCurrency currency, final List<LoanRepaymentScheduleInstallment> installments,
@@ -458,7 +445,9 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
Money currentInstallmentOutstanding = installment.getTotalOutstanding(currency);
for (final LoanTransaction loanTransaction : transactionsPostDisbursement) {
Money amountToProcess = null;
Money unProcessed = processTransaction(loanTransaction, currency, installments, amountToProcess);
final LoanTransaction newLoanTransaction = LoanTransaction.copyTransactionProperties(loanTransaction);
newLoanTransaction.resetDerivedComponents();
Money unProcessed = processTransaction(newLoanTransaction, currency, installments, amountToProcess);
if (loanTransaction.getTransactionDate().isAfter(installment.getFromDate())
&& recalculationDates.get(loanTransaction.getTransactionDate()).isBefore(installment.getDueDate())) {
Money earlyPayment = currentInstallmentOutstanding.minus(installment.getTotalOutstanding(currency));
@@ -28,10 +28,8 @@ public interface LoanRepaymentScheduleTransactionProcessor {
void handleWriteOff(LoanTransaction loanTransaction, MonetaryCurrency loanCurrency,
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments);
Map<LocalDate, Money> handleRecalculation(LocalDate disbursementDate, List<LoanTransaction> transactionsPostDisbursement,
MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments, LoanRepaymentScheduleInstallment currentinstallment, Map<LocalDate, LocalDate> recalculationDates);
Map<LocalDate, Money> handleRepaymentSchedule(List<LoanTransaction> transactionsPostDisbursement, MonetaryCurrency currency,
List<LoanRepaymentScheduleInstallment> installments, LoanRepaymentScheduleInstallment currentInstallment, Map<LocalDate, LocalDate> recalculationDates);
List<LoanRepaymentScheduleInstallment> installments, LoanRepaymentScheduleInstallment currentInstallment,
Map<LocalDate, LocalDate> recalculationDates);
}
@@ -212,8 +212,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
}
if (diffAmt != null && !diffAmt.isEmpty()) {
Money interestDueToLatePayment = totalCumulativeInterest.zero();
Money interestReducedDueToEarlyPayment = totalCumulativeInterest.zero();
BigDecimal interestDueToLatePayment = BigDecimal.ZERO;
BigDecimal interestReducedDueToEarlyPayment = BigDecimal.ZERO;
for (RecalculationDetail detail : diffAmt) {
if (!detail.isLatePayment() && detail.getStartDate().isAfter(periodStartDate)
&& !detail.getStartDate().isAfter(scheduledDueDate)) {
@@ -231,7 +231,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
principalForThisPeriod = principalForThisPeriod.plus(actualOutstanding);
}
}
interestReducedDueToEarlyPayment = interestReducedDueToEarlyPayment.plus(loanApplicationTerms.interestRateFor(
interestReducedDueToEarlyPayment = interestReducedDueToEarlyPayment.add(loanApplicationTerms.interestRateFor(
this.paymentPeriodsInOneYearCalculator, mc, detail.getAmount(), detail.getStartDate(), scheduledDueDate));
} else if (detail.isLatePayment() && detail.isOverlapping(periodStartDate, scheduledDueDate)
@@ -247,7 +247,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
if (toDate.isAfter(LocalDate.now())) {
toDate = LocalDate.now();
}
interestDueToLatePayment = interestDueToLatePayment.plus(loanApplicationTerms.interestRateFor(
interestDueToLatePayment = interestDueToLatePayment.add(loanApplicationTerms.interestRateFor(
this.paymentPeriodsInOneYearCalculator, mc, detail.getAmount(), fromDate, toDate));
}
}
@@ -332,17 +332,19 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
LocalDate fromDate = detail.getStartDate();
LocalDate toDate = detail.getToDate();
if (!toDate.isAfter(LocalDate.now())) {
Money interestDueToLatePayment = loanApplicationTerms.interestRateFor(this.paymentPeriodsInOneYearCalculator, mc,
BigDecimal interestForLatePayment = loanApplicationTerms.interestRateFor(this.paymentPeriodsInOneYearCalculator, mc,
detail.getAmount(), fromDate, toDate);
Money interestDueToLatePayment = Money.of(detail.getAmount().getCurrency(), interestForLatePayment);
if (interestDueToLatePayment.isGreaterThanZero()) {
totalInterestCharged = totalInterestCharged.add(interestDueToLatePayment.getAmount());
totalRepaymentExpected = totalRepaymentExpected.add(interestDueToLatePayment.getAmount());
totalInterestCharged = totalInterestCharged.add(interestDueToLatePayment.getAmount());
totalRepaymentExpected = totalRepaymentExpected.add(interestDueToLatePayment.getAmount());
final LoanScheduleModelPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(periodNumber, fromDate, toDate,
interestDueToLatePayment.zero(), interestDueToLatePayment.zero(), interestDueToLatePayment,
interestDueToLatePayment.zero(), interestDueToLatePayment.zero(), interestDueToLatePayment);
periods.add(installment);
periodNumber++;
final LoanScheduleModelPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(periodNumber, fromDate,
toDate, interestDueToLatePayment.zero(), interestDueToLatePayment.zero(), interestDueToLatePayment,
interestDueToLatePayment.zero(), interestDueToLatePayment.zero(), interestDueToLatePayment);
periods.add(installment);
periodNumber++;
}
}
}
}
@@ -520,47 +522,95 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
public LoanScheduleModel rescheduleNextInstallments(final MathContext mc, final ApplicationCurrency applicationCurrency,
final LoanApplicationTerms loanApplicationTerms, final Set<LoanCharge> loanCharges, final boolean isHolidayEnabled,
final List<Holiday> holidays, final WorkingDays workingDays, final List<LoanTransaction> transactions,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
final List<LoanRepaymentScheduleInstallment> previousSchedule, LocalDate recalculateFrom) {
int installmentNumber = 0;
final List<RecalculationDetail> recalculationDetails = new ArrayList<>();
LoanScheduleModel loanScheduleModel = generate(mc, applicationCurrency, loanApplicationTerms, loanCharges, isHolidayEnabled,
holidays, workingDays);
LoanScheduleModel loanScheduleModel = null;
List<LoanRepaymentScheduleInstallment> installments = previousSchedule;
List<LoanRepaymentScheduleInstallment> removeInstallments = new ArrayList<>();
for (LoanRepaymentScheduleInstallment installment : installments) {
if (!loanApplicationTerms.isPrincipalGraceApplicableForThisPeriod(installment.getInstallmentNumber())
&& installment.getPrincipal(loanApplicationTerms.getCurrency()).isZero()) {
removeInstallments.add(installment);
}
}
installments.removeAll(removeInstallments);
if (previousSchedule == null) {
loanScheduleModel = generate(mc, applicationCurrency, loanApplicationTerms, loanCharges, isHolidayEnabled, holidays,
workingDays);
installments = retrieveRepaymentSchedule(loanScheduleModel);
}
if (recalculateFrom == null) {
recalculateFrom = loanApplicationTerms.getExpectedDisbursementDate();
}
while (!recalculateFrom.isAfter(LocalDate.now())) {
int installmentNumber = findLastProcessedInstallmentNumber(installments, recalculateFrom);
List<LoanTransaction> processTransactions = processTransactions(transactions, recalculateFrom);
loanScheduleModel = recalculateInstallment(mc, applicationCurrency, loanApplicationTerms, loanCharges, isHolidayEnabled,
holidays, workingDays, loanRepaymentScheduleTransactionProcessor, previousSchedule, loanScheduleModel, installments,
processTransactions, installmentNumber);
while (installmentNumber < loanScheduleModel.getPeriods().size() - 1) {
RecalculatedSchedule recalculatedSchedule = recalculateInterest(mc, applicationCurrency, loanApplicationTerms, loanCharges,
isHolidayEnabled, holidays, workingDays, transactions, loanRepaymentScheduleTransactionProcessor, installmentNumber,
loanScheduleModel, recalculationDetails);
installmentNumber = recalculatedSchedule.getInstallmentNumber();
loanScheduleModel = recalculatedSchedule.getLoanScheduleModel();
recalculateFrom = getNextRecalculateFromDate(transactions, recalculateFrom);
}
return loanScheduleModel;
}
private LocalDate getNextRecalculateFromDate(List<LoanTransaction> processTransactions, LocalDate preCalculationDate) {
LocalDate recalculateFrom = null;
for (LoanTransaction loanTransaction : processTransactions) {
if (preCalculationDate.isBefore(loanTransaction.getTransactionDate())
&& (recalculateFrom == null || recalculateFrom.isAfter(loanTransaction.getTransactionDate()))) {
recalculateFrom = loanTransaction.getTransactionDate();
}
}
if (recalculateFrom == null) {
recalculateFrom = LocalDate.now().plusDays(1);
}
return recalculateFrom;
}
private LoanScheduleModel recalculateInstallment(final MathContext mc, final ApplicationCurrency applicationCurrency,
final LoanApplicationTerms loanApplicationTerms, final Set<LoanCharge> loanCharges, final boolean isHolidayEnabled,
final List<Holiday> holidays, final WorkingDays workingDays,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
final List<LoanRepaymentScheduleInstallment> previousSchedule, LoanScheduleModel loanScheduleModel,
List<LoanRepaymentScheduleInstallment> installments, List<LoanTransaction> processTransactions, final int skipRecalculation) {
int processInstallmentsFrom = 0;
Integer numberOfInstallments = previousSchedule.size() - 1;
final List<RecalculationDetail> recalculationDetails = new ArrayList<>();
while (processInstallmentsFrom < numberOfInstallments) {
RecalculatedSchedule recalculatedSchedule = recalculateInterest(mc, applicationCurrency, loanApplicationTerms, loanCharges,
isHolidayEnabled, holidays, workingDays, processTransactions, loanRepaymentScheduleTransactionProcessor,
processInstallmentsFrom, installments, recalculationDetails, skipRecalculation);
processInstallmentsFrom = recalculatedSchedule.getInstallmentNumber();
if (recalculatedSchedule.getLoanScheduleModel() != null) {
loanScheduleModel = recalculatedSchedule.getLoanScheduleModel();
installments = retrieveRepaymentSchedule(loanScheduleModel);
numberOfInstallments = installments.size() - 1;
}
}
return loanScheduleModel;
}
private RecalculatedSchedule recalculateInterest(final MathContext mc, final ApplicationCurrency applicationCurrency,
final LoanApplicationTerms loanApplicationTerms, final Set<LoanCharge> loanCharges, final boolean isHolidayEnabled,
final List<Holiday> holidays, final WorkingDays workingDays, final List<LoanTransaction> transactions,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final int installmentNumber,
LoanScheduleModel model, final List<RecalculationDetail> recalculationDetails) {
final List<LoanRepaymentScheduleInstallment> installments, final List<RecalculationDetail> recalculationDetails,
final int skipRecalculation) {
boolean processRecalculate = false;
int processedInstallmentNumber = installmentNumber;
final List<LoanRepaymentScheduleInstallment> installments = new ArrayList<>();
final List<RecalculationDetail> diffAmt = new ArrayList<>();
Money unpaidPricipal = loanApplicationTerms.getPrincipal();
for (final LoanScheduleModelPeriod scheduledLoanInstallment : model.getPeriods()) {
if (scheduledLoanInstallment.isRepaymentPeriod()) {
final LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(null,
scheduledLoanInstallment.periodNumber(), scheduledLoanInstallment.periodFromDate(),
scheduledLoanInstallment.periodDueDate(), scheduledLoanInstallment.principalDue(),
scheduledLoanInstallment.interestDue(), scheduledLoanInstallment.feeChargesDue(),
scheduledLoanInstallment.penaltyChargesDue());
installments.add(installment);
}
}
MonetaryCurrency currency = loanApplicationTerms.getPrincipal().getCurrency();
final List<LoanRepaymentScheduleInstallment> processinstallmets = new ArrayList<>();
for (LoanRepaymentScheduleInstallment installment : installments) {
installment.resetDerivedComponents();
installment.updateDerivedFields(currency, loanApplicationTerms.getExpectedDisbursementDate());
}
for (LoanRepaymentScheduleInstallment installment : installments) {
processinstallmets.add(installment);
unpaidPricipal = unpaidPricipal.minus(installment.getPrincipal(currency));
@@ -577,7 +627,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
isHolidayEnabled, holidays, workingDays);
for (LoanTransaction loanTransaction : transactions) {
LocalDate loantransactionDate = loanTransaction.getTransactionDate();
if (!loantransactionDate.isAfter(transactionsDate) && !loanTransaction.isAccrual()) {
if (!loantransactionDate.isAfter(transactionsDate)) {
transactionsForInstallment.add(loanTransaction);
recalculationDates.put(
loantransactionDate,
@@ -585,9 +635,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
workingDays));
}
}
Map<LocalDate, Money> earlyPaymentMap = loanRepaymentScheduleTransactionProcessor.handleRecalculation(
loanApplicationTerms.getExpectedDisbursementDate(), transactionsForInstallment, currency, processinstallmets,
installment, recalculationDates);
Map<LocalDate, Money> earlyPaymentMap = loanRepaymentScheduleTransactionProcessor.handleRepaymentSchedule(
transactionsForInstallment, currency, processinstallmets, installment, recalculationDates);
for (Map.Entry<LocalDate, Money> entry : earlyPaymentMap.entrySet()) {
LocalDate startDate = entry.getKey();
@@ -645,20 +694,37 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
}
}
if (!diffAmt.isEmpty()) {
if ((!diffAmt.isEmpty() && skipRecalculation <= installment.getInstallmentNumber())
|| installment.getInstallmentNumber() == installments.size() - 1) {
processRecalculate = true;
}
break;
}
LoanScheduleModel model = null;
recalculationDetails.addAll(diffAmt);
if (processRecalculate) {
recalculationDetails.addAll(diffAmt);
model = generate(mc, applicationCurrency, loanApplicationTerms, loanCharges, isHolidayEnabled, holidays, workingDays,
recalculationDetails);
}
return new RecalculatedSchedule(model, processedInstallmentNumber);
}
private List<LoanRepaymentScheduleInstallment> retrieveRepaymentSchedule(LoanScheduleModel model) {
final List<LoanRepaymentScheduleInstallment> installments = new ArrayList<>();
for (final LoanScheduleModelPeriod scheduledLoanInstallment : model.getPeriods()) {
if (scheduledLoanInstallment.isRepaymentPeriod()) {
final LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(null,
scheduledLoanInstallment.periodNumber(), scheduledLoanInstallment.periodFromDate(),
scheduledLoanInstallment.periodDueDate(), scheduledLoanInstallment.principalDue(),
scheduledLoanInstallment.interestDue(), scheduledLoanInstallment.feeChargesDue(),
scheduledLoanInstallment.penaltyChargesDue());
installments.add(installment);
}
}
return installments;
}
private void applyRest(List<LoanTransaction> loanTransactions, LocalDate from, LocalDate to,
LoanRepaymentScheduleInstallment installment,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, MonetaryCurrency currency,
@@ -668,7 +734,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
Map<LocalDate, LocalDate> recalculationDates = new HashMap<>();
for (LoanTransaction transaction : loanTransactions) {
LocalDate loantransactionDate = transaction.getTransactionDate();
if (loantransactionDate.isAfter(from) && !transaction.getTransactionDate().isAfter(to) && !transaction.isAccrual()) {
if (loantransactionDate.isAfter(from) && !transaction.getTransactionDate().isAfter(to)) {
transactions.add(transaction);
recalculationDates.put(
loantransactionDate,
@@ -714,10 +780,31 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
}
}
Money interest = applicationTerms.interestRateFor(this.paymentPeriodsInOneYearCalculator, mc, prepaymentAmount,
BigDecimal interest = applicationTerms.interestRateFor(this.paymentPeriodsInOneYearCalculator, mc, prepaymentAmount,
calculateInterestFrom, LocalDate.now());
prepaymentAmount = prepaymentAmount.plus(interest).plus(amount);
return prepaymentAmount;
}
private Integer findLastProcessedInstallmentNumber(final List<LoanRepaymentScheduleInstallment> installments, LocalDate date) {
int installmentNumber = 0;
for (LoanRepaymentScheduleInstallment installment : installments) {
if (!installment.getDueDate().isAfter(date) && installmentNumber < installment.getInstallmentNumber()) {
installmentNumber = installment.getInstallmentNumber();
}
}
return installmentNumber;
}
private List<LoanTransaction> processTransactions(final List<LoanTransaction> transactions, final LocalDate tillDate) {
List<LoanTransaction> toProcess = new ArrayList<>();
for (LoanTransaction loanTransaction : transactions) {
if (!loanTransaction.getTransactionDate().isAfter(tillDate)) {
toProcess.add(loanTransaction);
}
}
return toProcess;
}
}
@@ -663,8 +663,8 @@ public final class LoanApplicationTerms {
return periodicInterestRate;
}
public Money interestRateFor(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc, final Money outstandingBalance,
final LocalDate fromDate, final LocalDate toDate) {
public BigDecimal interestRateFor(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc,
final Money outstandingBalance, final LocalDate fromDate, final LocalDate toDate) {
long loanTermPeriodsInOneYear = calculator.calculate(PeriodFrequencyType.DAYS).longValue();
int repaymentEvery = Days.daysBetween(fromDate, toDate).getDays();
@@ -678,7 +678,7 @@ public final class LoanApplicationTerms {
final BigDecimal oneDayOfYearInterestRate = this.annualNominalInterestRate.divide(loanTermPeriodsInYearBigDecimal, mc).divide(
divisor, mc);
BigDecimal interestRate = oneDayOfYearInterestRate.multiply(BigDecimal.valueOf(repaymentEvery), mc);
return outstandingBalance.multiplyRetainScale(interestRate, mc.getRoundingMode());
return outstandingBalance.getAmount().multiply(interestRate, mc);
}
private long calculatePeriodsInOneYear(final PaymentPeriodsInOneYearCalculator calculator) {
@@ -709,7 +709,7 @@ public final class LoanApplicationTerms {
return totalNumberOfRepaymentPeriods - getPrincipalGrace();
}
private boolean isPrincipalGraceApplicableForThisPeriod(final int periodNumber) {
public boolean isPrincipalGraceApplicableForThisPeriod(final int periodNumber) {
return periodNumber > 0 && periodNumber <= getPrincipalGrace();
}
@@ -981,7 +981,7 @@ public final class LoanApplicationTerms {
if (this.interestCalculationPeriodMethod.getValue().equals(InterestCalculationPeriodMethod.SAME_AS_REPAYMENT_PERIOD.getValue())) {
switch (this.repaymentPeriodFrequencyType) {
case WEEKS:
int days = getPeriodsBetween(fromDate, toDate);
int days = Days.daysBetween(fromDate, toDate).getDays();
isSameAsRepaymentPeriod = (days % 7) == 0;
break;
case MONTHS:
@@ -1016,9 +1016,14 @@ public final class LoanApplicationTerms {
PeriodType periodType = PeriodType.yearMonthDay();
Period difference = new Period(fromDate, toDate, periodType);
switch (this.repaymentPeriodFrequencyType) {
case WEEKS:
case DAYS:
numberOfPeriods = difference.getDays();
break;
case WEEKS:
periodType = PeriodType.weeks();
difference = new Period(fromDate, toDate, periodType);
numberOfPeriods = difference.getWeeks();
break;
case MONTHS:
numberOfPeriods = difference.getMonths();
break;
@@ -9,6 +9,7 @@ import java.math.MathContext;
import java.util.List;
import java.util.Set;
import org.joda.time.LocalDate;
import org.mifosplatform.organisation.holiday.domain.Holiday;
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
@@ -27,7 +28,8 @@ public interface LoanScheduleGenerator {
LoanScheduleModel rescheduleNextInstallments(MathContext mc, ApplicationCurrency applicationCurrency,
LoanApplicationTerms loanApplicationTerms, Set<LoanCharge> loanCharges, boolean isHolidayEnabled, List<Holiday> holidays,
WorkingDays workingDays, List<LoanTransaction> transactions,
LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor);
LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
List<LoanRepaymentScheduleInstallment> previousSchedule, LocalDate recalculateFrom);
Money fetchPrepaymentAmount(List<LoanRepaymentScheduleInstallment> installments, MonetaryCurrency currency,
LoanApplicationTerms applicationTerms, MathContext mc);
@@ -2205,7 +2205,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate());
workingDays = this.workingDaysRepository.findOne();
}
ChangedTransactionDetail changedTransactionDetail = loan.recalculateSchedule(loanScheduleFactory, applicationCurrency,
ChangedTransactionDetail changedTransactionDetail = loan.recalculateScheduleFromLastTransaction(loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, restCalendarInstance,
existingTransactionIds, existingReversedTransactionIds);