private DateTime[] _Reschedule_AdjustDates(ReschedulingOptions ro) { DateTime[] retval = new DateTime[2]; DateTime oldDate = DateTime.MinValue; DateTime newDate = DateTime.MinValue; bool first = false; int number = 0; for (int i = 0; i < _contract.InstallmentList.Count; i++) { Installment installment = _contract.GetInstallment(i); if (installment.IsRepaid) continue; if (!first) { first = true; oldDate = installment.ExpectedDate; newDate = installment.ExpectedDate; if (0 == ro.RepaymentDateOffset) break; newDate = newDate.AddDays(ro.RepaymentDateOffset); DateTime actualNewDate = _nwdS.GetTheNearestValidDate(newDate, _generalSettings.IsIncrementalDuringDayOff, _generalSettings.DoNotSkipNonWorkingDays, true); installment.ExpectedDate = actualNewDate; } installment.ExpectedDate = _contract.CalculateInstallmentDate(newDate, number); number++; } retval[0] = oldDate; retval[1] = newDate; return retval; }
public RescheduleLoanEvent Reschedule(ReschedulingOptions ro, Loan contract, NonWorkingDateSingleton nwdS, ApplicationSettings applicationSettings) { _contract = contract; _nwdS = nwdS; _generalSettings = applicationSettings; switch (contract.Product.LoanType) { case OLoanTypes.Flat: _Reschedule_Flat(ro); break; case OLoanTypes.DecliningFixedPrincipal: _Reschedule_FixedPrincipal(ro); break; case OLoanTypes.DecliningFixedInstallments: _Reschedule_DecliningFixedInstallments(ro); break; } _Reschedule_AdjustOverpaid(); RescheduleLoanEvent rSe = new RescheduleLoanEvent { Date = ro.ReschedulingDate, Amount = contract.CalculateActualOlb(), Interest = contract.GetTotalInterestDue(), ClientType = contract.ClientType, BadLoan = contract.BadLoan, NbOfMaturity = ro.NewInstallments, DateOffset = ro.RepaymentDateOffset, GracePeriod = ro.GracePeriod, ChargeInterestDuringShift = ro.ChargeInterestDuringShift, ChargeInterestDuringGracePeriod = ro.ChargeInterestDuringGracePeriod }; return rSe; }
private void _Reschedule_Flat(ReschedulingOptions ro) { _Reschedule_ExtendMaturity(ro.NewInstallments); DateTime[] dates = _Reschedule_AdjustDates(ro); int roundDecimals = _contract.UseCents ? 2 : 0; bool firstNonRepaid = true; Installment firstUnpaidInstallment = _contract.GetFirstUnpaidInstallment(); int moveToNext = 0; foreach (Installment installment in _contract.InstallmentList) { if (installment.IsRepaid) continue; if (firstNonRepaid) { firstNonRepaid = false; if (ro.ChargeInterestDuringShift || _contract.InterestRate != ro.InterestRate || ro.NewInstallments > 0) { TimeSpan oldSpan; TimeSpan newSpan; if (1 == installment.Number) { DateTime endDate = _contract.StartDate.AddMonths(_contract.InstallmentType.NbOfMonths).AddDays( _contract.InstallmentType.NbOfDays); endDate = _nwdS.GetTheNearestValidDate(endDate, _generalSettings.IsIncrementalDuringDayOff, _generalSettings.DoNotSkipNonWorkingDays, true); newSpan = (ro.ChargeInterestDuringShift ? installment.ExpectedDate : dates[0]) - _contract.StartDate; oldSpan = endDate - _contract.StartDate; } else { DateTime startDate = _contract.GetInstallment(installment.Number - 2).ExpectedDate; oldSpan = dates[0] - startDate; newSpan = (ro.ChargeInterestDuringShift ? dates[1] : dates[0]) - startDate; } if (ro.GracePeriod > 0 && installment.IsPartiallyRepaid) { installment.InterestsRepayment = installment.PaidInterests; moveToNext = 1; } else { if (installment.Number >= ro.GracePeriod + firstUnpaidInstallment.Number) { OCurrency interest = _contract.Amount * newSpan.Days * ro.InterestRate / oldSpan.Days; interest = Math.Round(interest.Value, roundDecimals, MidpointRounding.AwayFromZero); installment.InterestsRepayment = interest; } else { if (ro.ChargeInterestDuringGracePeriod) { OCurrency interest = _contract.Amount * newSpan.Days * ro.InterestRate / oldSpan.Days; interest = Math.Round(interest.Value, roundDecimals, MidpointRounding.AwayFromZero); installment.InterestsRepayment = interest; } else { installment.InterestsRepayment = 0; } } } } } else { if (ro.GracePeriod > 0 && installment.IsPartiallyRepaid) { installment.InterestsRepayment = installment.PaidInterests; moveToNext = 1; } else { if (installment.Number > ro.GracePeriod + firstUnpaidInstallment.Number - 1 + moveToNext) { OCurrency interest = _contract.Amount * ro.InterestRate; interest = Math.Round(interest.Value, roundDecimals, MidpointRounding.AwayFromZero); installment.InterestsRepayment = interest; } else { if (ro.ChargeInterestDuringGracePeriod) { OCurrency interest = _contract.Amount * ro.InterestRate; interest = Math.Round(interest.Value, roundDecimals, MidpointRounding.AwayFromZero); installment.InterestsRepayment = interest; } else { installment.InterestsRepayment = 0; } } } } } if (null == firstUnpaidInstallment) return; if (!firstUnpaidInstallment.IsPartiallyRepaid && 0 == ro.NewInstallments) return; OCurrency unpaidAmount = 0; foreach (Installment installment in _contract.InstallmentList) { unpaidAmount += installment.CapitalRepayment - installment.PaidCapital; } int nbOfUnpaid = _contract.InstallmentList.Count - firstUnpaidInstallment.Number + 1; int nextNumber = 0; if (firstUnpaidInstallment.IsPartiallyRepaid && ro.GracePeriod > 0) { nextNumber = 1; } double monthly = Math.Round( Convert.ToDouble(unpaidAmount.Value / (nbOfUnpaid - ro.GracePeriod - nextNumber)), roundDecimals, MidpointRounding.AwayFromZero); for (int i = firstUnpaidInstallment.Number - 1; i < _contract.InstallmentList.Count; i++) { Installment installment = _contract.GetInstallment(i); if (installment.Number > ro.GracePeriod + firstUnpaidInstallment.Number - 1 + nextNumber) { installment.CapitalRepayment = Convert.ToDecimal(monthly); } else { installment.CapitalRepayment = 0; } } firstUnpaidInstallment.CapitalRepayment += firstUnpaidInstallment.PaidCapital; double diff = Convert.ToDouble(unpaidAmount.Value) - monthly * (nbOfUnpaid - ro.GracePeriod - nextNumber); if (diff != 0) { if (nextNumber != 1) if (ro.GracePeriod > 0) { _contract.GetInstallment(firstUnpaidInstallment.Number + ro.GracePeriod).CapitalRepayment += Convert.ToDecimal(diff); } else { firstUnpaidInstallment.CapitalRepayment += Convert.ToDecimal(diff); } else { _contract.GetInstallment(firstUnpaidInstallment.Number + ro.GracePeriod).CapitalRepayment += Convert.ToDecimal(diff); } } OCurrency olb = firstUnpaidInstallment.OLB; for (int i = firstUnpaidInstallment.Number - 1; i < _contract.InstallmentList.Count; i++) { Installment installment = _contract.GetInstallment(i); installment.OLB = olb; olb -= installment.CapitalRepayment; } }
private void _Reschedule_FixedPrincipal(ReschedulingOptions ro) { _Reschedule_ExtendMaturity(ro.NewInstallments); int roundDecimals = _contract.UseCents ? 2 : 0; Installment firstUnpaidInstallment = _contract.GetFirstUnpaidInstallment(); OCurrency paidCapital = 0; if (firstUnpaidInstallment.IsPartiallyRepaid && firstUnpaidInstallment.PaidCapital != 0 && ro.GracePeriod > 0) { paidCapital = firstUnpaidInstallment.PaidCapital; firstUnpaidInstallment.CapitalRepayment = firstUnpaidInstallment.PaidCapital; firstUnpaidInstallment = _contract.GetInstallment(firstUnpaidInstallment.Number); } int firstUnpaidInstallmentNumber = firstUnpaidInstallment.Number; int toNext = firstUnpaidInstallment.IsPartiallyRepaid && ro.GracePeriod > 0 ? 1 : 0; if ((firstUnpaidInstallment.IsPartiallyRepaid) || ro.NewInstallments > 0) { OCurrency unpaidAmount = 0; foreach (Installment installment in _contract.InstallmentList) { unpaidAmount += installment.IsRepaid ? 0 : installment.CapitalRepayment - installment.PaidCapital; } unpaidAmount += paidCapital; int nbOfUnpaid = _contract.InstallmentList.Count - firstUnpaidInstallmentNumber + 1 - toNext; double monthly = Math.Round(Convert.ToDouble(unpaidAmount.Value / (nbOfUnpaid - ro.GracePeriod)), roundDecimals, MidpointRounding.AwayFromZero); for (int i = firstUnpaidInstallment.Number - 1; i < _contract.InstallmentList.Count; i++) { Installment installment = _contract.GetInstallment(i); if (installment.Number > ro.GracePeriod + firstUnpaidInstallment.Number - 1 + toNext) { installment.CapitalRepayment = Convert.ToDecimal(monthly); } else { installment.CapitalRepayment = 0; } } Installment installmentOfCorrection = _contract.GetInstallment(firstUnpaidInstallmentNumber + ro.GracePeriod - 1 + toNext); installmentOfCorrection.CapitalRepayment += installmentOfCorrection.PaidCapital; double diff = Convert.ToDouble(unpaidAmount.Value) - monthly * (nbOfUnpaid - ro.GracePeriod); if (diff != 0) installmentOfCorrection.CapitalRepayment += Convert.ToDecimal(diff); OCurrency olb = firstUnpaidInstallment.OLB + paidCapital; for (int i = firstUnpaidInstallment.Number - 1; i < _contract.InstallmentList.Count; i++) { Installment installment = _contract.GetInstallment(i); installment.OLB = olb; olb -= installment.CapitalRepayment; } } DateTime[] dates = _Reschedule_AdjustDates(ro); bool firstNonRepaid = true; if(firstUnpaidInstallment.IsPartiallyRepaid && ro.GracePeriod > 0) { firstUnpaidInstallment.InterestsRepayment = firstUnpaidInstallment.PaidInterests; firstUnpaidInstallment.CapitalRepayment = firstUnpaidInstallment.PaidCapital; firstNonRepaid = false; } //interest calculation foreach (Installment installment in _contract.InstallmentList) { if (installment.IsRepaid) continue; if (firstNonRepaid) { firstNonRepaid = false; if (ro.ChargeInterestDuringShift || _contract.InterestRate != ro.InterestRate || ro.NewInstallments > 0) { TimeSpan oldSpan; TimeSpan newSpan; if ((installment.Number > ro.GracePeriod + firstUnpaidInstallment.Number - 1 + toNext) || (ro.ChargeInterestDuringGracePeriod)) { if (1 == installment.Number) { DateTime endDate = _contract.StartDate.AddMonths(_contract.InstallmentType.NbOfMonths).AddDays(_contract.InstallmentType.NbOfDays); endDate = _nwdS.GetTheNearestValidDate(endDate, _generalSettings.IsIncrementalDuringDayOff, _generalSettings.DoNotSkipNonWorkingDays, true); newSpan = (ro.ChargeInterestDuringShift ? installment.ExpectedDate : dates[0]) - _contract.StartDate; oldSpan = endDate - _contract.StartDate; } else { DateTime startDate = _contract.GetInstallment(installment.Number - 2).ExpectedDate; oldSpan = dates[0] - startDate; newSpan = (ro.ChargeInterestDuringShift ? dates[1] : dates[0]) - startDate; } OCurrency amount = installment.OLB - installment.PaidCapital; double interest = Convert.ToDouble(amount.Value * newSpan.Days) * ro.InterestRate / oldSpan.Days; interest = Math.Round(interest, roundDecimals, MidpointRounding.AwayFromZero); installment.InterestsRepayment = Convert.ToDecimal(interest); } else { installment.InterestsRepayment = 0; } } } else { if (_contract.InterestRate != ro.InterestRate || (firstUnpaidInstallment.IsPartiallyRepaid) || ro.NewInstallments > 0) { if ((installment.Number > ro.GracePeriod + firstUnpaidInstallment.Number - 1 + toNext) || (ro.ChargeInterestDuringGracePeriod)) { double interest = Convert.ToDouble(installment.OLB.Value) * ro.InterestRate; interest = Math.Round(interest, roundDecimals, MidpointRounding.AwayFromZero); installment.InterestsRepayment = Convert.ToDecimal(interest); } else { installment.InterestsRepayment = 0; } } } } }
private void _Reschedule_DecliningFixedInstallments(ReschedulingOptions ro) { int months = _contract.InstallmentType.NbOfMonths; int days = _contract.InstallmentType.NbOfDays; int rounding = _contract.UseCents ? 2 : 0; // Split installments into two lists - paid and unpaid List<Installment> paidInstallments = new List<Installment>(); List<Installment> unpaidInstallments = new List<Installment>(); foreach (Installment installment in _contract.InstallmentList) { if (installment.IsRepaid) { paidInstallments.Add(installment); } else { unpaidInstallments.Add(installment); } } // Add new installments if (ro.NewInstallments > 0) { int lastNumber = unpaidInstallments[unpaidInstallments.Count - 1].Number; for (int i = 1; i <= ro.NewInstallments; i++) { Installment installment = new Installment(); installment.Number = lastNumber + i; installment.CapitalRepayment = 0; installment.PaidCapital = 0; installment.InterestsRepayment = 0; installment.PaidInterests = 0; installment.FeesUnpaid = 0; installment.CommissionsUnpaid = 0; unpaidInstallments.Add(installment); } } // We want to recalculate installments if new installments are introduced, // interest rate is changed, or the first unpaid installment is partially paid. int moveToNext = 0; if (unpaidInstallments[0].IsPartiallyRepaid && ro.GracePeriod > 0) { moveToNext = 1; } if (ro.NewInstallments > 0 || _contract.InterestRate != ro.InterestRate || unpaidInstallments[0].IsPartiallyRepaid) { OCurrency initialOlb = unpaidInstallments[0].OLB - unpaidInstallments[0].PaidCapital; int n = unpaidInstallments.Count - ro.GracePeriod - moveToNext; // We use the formula below to calculate monthly annuity payments //OCurrency monthly = 0 == r ? initialOlb / n : initialOlb * r * Math.Pow(1 + r, n) / (Math.Pow(1 + r, n) - 1); OCurrency monthly = _contract.VPM(initialOlb, n, ro.InterestRate); monthly = Math.Round(monthly.Value, rounding, MidpointRounding.AwayFromZero); //_Reschedule_Round(monthly); // Now loop through the unpaid installments and calculate the principal and the interest OCurrency sum = 0; OCurrency olb = initialOlb; foreach (Installment installment in unpaidInstallments) { OCurrency interest = olb * ro.InterestRate; if (installment.IsPartiallyRepaid && ro.GracePeriod > 0) { installment.InterestsRepayment = installment.PaidInterests; installment.CapitalRepayment = installment.PaidCapital; } else { if (installment.Number > ro.GracePeriod + unpaidInstallments[0].Number - 1 + moveToNext) { OCurrency principal = monthly - interest; installment.CapitalRepayment = Math.Round(principal.Value, rounding, MidpointRounding.AwayFromZero); } else { installment.CapitalRepayment = 0; } if ((installment.Number > ro.GracePeriod + unpaidInstallments[0].Number - 1 + moveToNext) || (ro.ChargeInterestDuringGracePeriod)) { installment.InterestsRepayment = Math.Round(interest.Value, rounding, MidpointRounding.AwayFromZero); } else { installment.InterestsRepayment = 0; } if (ro.GracePeriod > 0) { if (!installment.IsPartiallyRepaid) { sum += installment.CapitalRepayment; installment.OLB = olb; olb -= installment.CapitalRepayment.Value; } } else { sum += installment.CapitalRepayment; installment.OLB = olb; olb -= installment.CapitalRepayment.Value; } } } // As a result of rounding there might be some difference between the // initial OLB and the sum of installment repayments. Thus we have to // compensate for this difference. OCurrency diff = sum - initialOlb; if (diff != 0) { unpaidInstallments[ro.GracePeriod + moveToNext].CapitalRepayment -= diff; int firstNumber = unpaidInstallments[ro.GracePeriod + moveToNext].Number; // Iterate through all unpaid but first foreach (Installment installment in unpaidInstallments.FindAll(x => x.Number > firstNumber)) { installment.OLB += diff; } } } // Adjust the amount of interest for the first installment if ((ro.RepaymentDateOffset > 0 && ro.ChargeInterestDuringShift) || (_contract.FirstDateChanged && 1 == unpaidInstallments[0].Number)) { DateTime startDate; DateTime endDate1; DateTime endDate2; if (0 == paidInstallments.Count) { startDate = _contract.StartDate; endDate1 = startDate.AddMonths(months).AddDays(days); endDate2 = _contract.FirstInstallmentDate.AddDays(ro.RepaymentDateOffset * (ro.ChargeInterestDuringShift ? 1 : 0)); } else { startDate = paidInstallments[paidInstallments.Count - 1].ExpectedDate; endDate1 = startDate.AddMonths(months).AddDays(days); endDate2 = endDate1.AddDays(ro.RepaymentDateOffset * (ro.ChargeInterestDuringShift ? 1 : 0)); } TimeSpan oldSpan = endDate1 - startDate; TimeSpan newSpan = endDate2 - startDate; double olb = Convert.ToDouble(unpaidInstallments[0].OLB.Value); double ratio; if (ro.GracePeriod > 0 && !ro.ChargeInterestDuringGracePeriod) { ratio = (double)(newSpan.Days - oldSpan.Days) / oldSpan.Days; } else { ratio = (double)newSpan.Days / oldSpan.Days; } OCurrency interest = Convert.ToDecimal(olb * ro.InterestRate * ratio); interest = Math.Round(interest.Value, rounding, MidpointRounding.AwayFromZero); unpaidInstallments[0].InterestsRepayment = interest; } if (unpaidInstallments[0].IsPartiallyRepaid) { int instNum = ro.GracePeriod > 0 ? ro.GracePeriod - 1 : 0; unpaidInstallments[instNum].CapitalRepayment += unpaidInstallments[0].PaidCapital; unpaidInstallments[instNum].OLB += unpaidInstallments[0].PaidCapital; } // Adjust dates DateTime baseDate = unpaidInstallments[0].ExpectedDate.AddDays(ro.RepaymentDateOffset); int num = 0; foreach (Installment installment in unpaidInstallments) { DateTime d = baseDate.AddMonths(months * num).AddDays(days * num); d = _nwdS.GetTheNearestValidDate(d, _generalSettings.IsIncrementalDuringDayOff, _generalSettings.DoNotSkipNonWorkingDays, true); installment.ExpectedDate = d; num++; } // Merge the lists back into one _contract.InstallmentList = paidInstallments; _contract.InstallmentList.AddRange(unpaidInstallments); _contract.NbOfInstallments = _contract.InstallmentList.Count(); }
public RescheduleLoanEvent Reschedule(ReschedulingOptions ro) { if (Closed) throw new ReschedulingContractClosedException(); RescheduleLoan reschedule = new RescheduleLoan(); RescheduleLoanEvent rSe = reschedule.Reschedule(ro, this, _nwdS, _generalSettings); _nbOfInstallments = _installmentList.Count; Events.Add(rSe); //Book(rSe); return rSe; }