private void SaveLoanInterestFreeze(LoanInterestFreeze loanInterestFreeze, int loanID) { int customerId = this._loans.Get(loanInterestFreeze.Loan.Id).Customer.Id; long newLoanId = this.serviceClient.Instance.GetLoanByOldID(loanID, customerId, this._context.UserId).Value; if (newLoanId < 0) { return; } NL_LoanInterestFreeze nlLoanInterestFreeze = new NL_LoanInterestFreeze() { StartDate = loanInterestFreeze.StartDate, OldID = loanInterestFreeze.Id, ActivationDate = loanInterestFreeze.ActivationDate, DeactivationDate = loanInterestFreeze.DeactivationDate, EndDate = loanInterestFreeze.EndDate, InterestRate = loanInterestFreeze.InterestRate, LoanID = newLoanId, AssignedByUserID = this._context.UserId, DeletedByUserID = null, }; this.serviceClient.Instance.AddLoanInterestFreeze(this._context.UserId, customerId, nlLoanInterestFreeze); }
private void SaveLoanInterestFreeze(LoanInterestFreeze loanInterestFreeze, int customerId, int loanID) { long nlLoanId = this.serviceClient.Instance.GetLoanByOldID(loanID, customerId, this.context.UserId).Value; if (nlLoanId == 0) { return; } NL_LoanInterestFreeze nlLoanInterestFreeze = new NL_LoanInterestFreeze() { StartDate = loanInterestFreeze.StartDate, OldID = loanInterestFreeze.Id, ActivationDate = loanInterestFreeze.ActivationDate, DeactivationDate = loanInterestFreeze.DeactivationDate, EndDate = loanInterestFreeze.EndDate, InterestRate = loanInterestFreeze.InterestRate, LoanID = nlLoanId, AssignedByUserID = this.context.UserId, DeletedByUserID = null, }; log.Debug("ADDING NL FREESE: {0} \n olffreeze: {1}", nlLoanInterestFreeze, loanInterestFreeze); var nlStrategy = this.serviceClient.Instance.AddLoanInterestFreeze(this.context.UserId, customerId, nlLoanInterestFreeze).Value; }
public JsonResult RemoveFreezeInterval(int id, int intervalid) { Loan loan = this._loans.Get(id); LoanInterestFreeze lif = loan.InterestFreeze.FirstOrDefault(v => v.Id == intervalid); var loan1 = loan; new Transactional(() => { if (lif != null) { lif.DeactivationDate = DateTime.UtcNow; } this._loans.SaveOrUpdate(loan1); }).Execute(); DeactivateLoanInterestFreeze(lif); Log.DebugFormat("remove freeze interest for customer {0}", loan.Customer.Id); loan = this._loans.Get(id); var calc = new LoanRepaymentScheduleCalculator(loan, DateTime.UtcNow, CurrentValues.Instance.AmountToChargeFrom); calc.GetState(); try { long nlLoanId = this.serviceClient.Instance.GetLoanByOldID(id, loan.Customer.Id, this._context.UserId).Value; if (nlLoanId > 0) { var nlModel = this.serviceClient.Instance.GetLoanState(loan.Customer.Id, nlLoanId, DateTime.UtcNow, this._context.UserId, true).Value; Log.InfoFormat("<<< NL_Compare: {0}\n===============loan: {1} >>>", nlModel, loan); } // ReSharper disable once CatchAllClause } catch (Exception ex) { Log.InfoFormat("<<< NL_Compare fail at: {0}, err: {1}", Environment.StackTrace, ex.Message); } EditLoanDetailsModel model = this._loanModelBuilder.BuildModel(loan); model.Options = this.loanOptionsRepository.GetByLoanId(id) ?? LoanOptions.GetDefault(id); RescheduleSetmodel(id, model); return(Json(model)); } // RemoveFreezeInterval
private void DeactivateLoanInterestFreeze(LoanInterestFreeze loanInterestFreeze, int customerId) { long nlLoanId = this.serviceClient.Instance.GetLoanByOldID(loanInterestFreeze.Loan.Id, customerId, this.context.UserId).Value; if (nlLoanId == 0) { return; } NL_LoanInterestFreeze nlLoanInterestFreeze = new NL_LoanInterestFreeze() { OldID = loanInterestFreeze.Id, DeactivationDate = loanInterestFreeze.DeactivationDate, LoanID = nlLoanId, AssignedByUserID = this.context.UserId, DeletedByUserID = null, }; var nlStrategy = this.serviceClient.Instance.DeactivateLoanInterestFreeze(this.context.UserId, customerId, nlLoanInterestFreeze).Value; }
public ReschedulingResult Result; // output public override void Execute() { NL_AddLog(LogType.Info, "Strategy Start", this.ReschedulingArguments, null, null, null); if (!this.ReschedulingArguments.RescheduleIn && this.ReschedulingArguments.PaymentPerInterval == null) { this.Result.Error = "Weekly/monthly payment amount for OUT rescheduling not provided"; this.loanRep.Clear(); return; } try { this.loanRep.BeginTransaction(); GetCurrentLoanState(); if (this.tLoan == null) { this.Result.Error = string.Format("Loan ID {0} not found", this.ReschedulingArguments.LoanID); this.Result.BlockAction = true; ExitStrategy("Exit_1"); return; } // check status, don't continue for "PaidOff" if (this.tLoan.Status == LoanStatus.PaidOff) { this.Result.Error = string.Format("Loan ID {0} paid off. Loan balance: {1}", this.tLoan.Id, 0m.ToString("C2", this.cultureInfo)); this.Result.BlockAction = true; ExitStrategy("Exit_2", this.sendDebugMail); return; } // input validation for "IN" if (this.ReschedulingArguments.RescheduleIn && (this.Result.FirstItemDate > this.Result.LoanCloseDate)) { this.Result.Error = "Within loan arrangement is impossible"; this.Result.BlockAction = true; ExitStrategy("Exit_3", this.sendDebugMail); return; } // "IN" - check between interval boundaries if (this.ReschedulingArguments.RescheduleIn && (this.Result.FirstItemDate < this.Result.ReschedulingIntervalStart || this.Result.FirstItemDate > this.Result.ReschedulingIntervalEnd)) { this.Result.Error = "Wrong re-scheduling date sent (any day on the calendar within next 30 days allowed)"; this.Result.BlockAction = true; ExitStrategy("Exit_3a", this.sendDebugMail); return; } // "OUT" - check past date if (this.ReschedulingArguments.RescheduleIn == false && this.Result.FirstItemDate.Date < this.Result.ReschedulingIntervalStart) { this.Result.Error = "Wrong re-scheduling date sent (only future date allowed)"; this.Result.BlockAction = true; ExitStrategy("Exit_3b", this.sendDebugMail); return; } this.Result.OpenPrincipal = this.tLoan.Principal; // if sent "default" value (0), replace by default calculated if (!this.ReschedulingArguments.RescheduleIn && this.ReschedulingArguments.PaymentPerInterval == 0) { this.ReschedulingArguments.PaymentPerInterval = this.Result.DefaultPaymentPerInterval; } Log.Debug("\n==========RE-SCHEDULING======ARGUMENTS: {0}==========LoanState: {1}\n", this.ReschedulingArguments, this.tLoan); // check Marking loan {0} as 'PaidOff' in \ezbob\Integration\DatabaseLib\Model\Loans\Loan.cs(362) var calc = new LoanRepaymentScheduleCalculator(this.tLoan, DateTime.UtcNow, CurrentValues.Instance.AmountToChargeFrom); try { if (calc.NextEarlyPayment() == 0) { this.Result.Error = string.Format("Loan {0} marked as 'PaidOff'. Loan balance: {1}", this.tLoan.Id, 0m.ToString("C2", this.cultureInfo)); this.Result.BlockAction = true; ExitStrategy("Exit_4", this.sendDebugMail); return; } // ReSharper disable once CatchAllClause } catch (Exception calcEx) { Log.Info("LoanRepaymentScheduleCalculator NextEarlyPayment EXCEPTION: {0}", calcEx.Message); } var lastPaidSchedule = this.tLoan.Schedule.OrderBy(s => s.Date).LastOrDefault(s => (s.Status == LoanScheduleStatus.Paid || s.Status == LoanScheduleStatus.PaidOnTime || s.Status == LoanScheduleStatus.PaidEarly)); // if StopFutureInterest checked - add active "freeze inteval" from FirstItemDate untill NoLimitDate if (this.ReschedulingArguments.RescheduleIn == false && this.ReschedulingArguments.StopFutureInterest) { //TODO : also add it to NL_InterestFreeze && deactivating. LoanInterestFreeze freeze = new LoanInterestFreeze { Loan = this.tLoan, StartDate = lastPaidSchedule != null ? lastPaidSchedule.Date : this.tLoan.Date.Date, //this.Result.FirstItemDate, EndDate = this.noLimitDate, // NoLimitDate from LoanEditorController.cs - move to common area InterestRate = 0, ActivationDate = this.Result.FirstItemDate, DeactivationDate = null }; if (!this.tLoan.InterestFreeze.Contains(freeze)) { this.tLoan.InterestFreeze.Add(freeze); } calc.GetState(); // reload state with freeze consideration } decimal totalEarlyPayment = calc.TotalEarlyPayment(); decimal P = this.Result.OpenPrincipal; decimal F = calc.FeesToPay; // unpaid fees //decimal I = lastPaidSchedule != null ? (calc.GetInterestRate(lastPaidSchedule.Date.AddDays(1), this.Result.FirstItemDate) *P) : (calc.GetInterestRate(this.tLoan.Date.Date.AddDays(1), this.Result.FirstItemDate) * P); // unpaid interest till rescheduling start date decimal I = (totalEarlyPayment - P - F); // unpaid interest till first rescheduled item I = I < 0 ? 0 : I; // bugfix EZ-4236 decimal r = ((this.ReschedulingArguments.RescheduleIn == false && this.ReschedulingArguments.StopFutureInterest)) ? 0 : this.tLoan.InterestRate; this.Result.ReschedulingBalance = (P + I + F); // not final - add to I period from rescheduling date untill new maturity date Log.Debug("--------------P: {0}, I: {1}, F: {2}, LoanCloseDate: {3}, totalEarlyPayment: {4}, r: {5}, ReschedulingBalance: {6}, \n lastPaidSchedule: {7}", P, I, F, this.Result.LoanCloseDate.Date, totalEarlyPayment, r, this.Result.ReschedulingBalance, lastPaidSchedule); // 3. intervals number // IN if (this.ReschedulingArguments.RescheduleIn) { // add "grace" period - 14 days to maturity date DateTime closeDateWithGrace = this.Result.LoanCloseDate.Date.AddDays(14); this.Result.IntervalsNum = this.ReschedulingArguments.ReschedulingRepaymentIntervalType == RepaymentIntervalTypes.Month ? MiscUtils.DateDiffInMonths(this.Result.FirstItemDate, closeDateWithGrace) : MiscUtils.DateDiffInWeeks(this.Result.FirstItemDate, closeDateWithGrace); // adjust intervals number +1 if needed DateTime rescheduledCloseDate = this.ReschedulingArguments.ReschedulingRepaymentIntervalType == RepaymentIntervalTypes.Month ? this.Result.FirstItemDate.AddMonths(this.Result.IntervalsNum) : this.Result.FirstItemDate.AddDays(this.Result.IntervalsNum * 7); Log.Debug("rescheduledCloseDate: {0}, Result.IntervalsNum: {1}, Result.LoanCloseDate: {2}, closeDateWithGrace: {3}", rescheduledCloseDate, this.Result.IntervalsNum, this.Result.LoanCloseDate.Date, closeDateWithGrace); TimeSpan ts = closeDateWithGrace.Date.Subtract(rescheduledCloseDate.Date); if (ts.Days > 0) { this.Result.IntervalsNum += 1; } Log.Debug("Adjusted intervals: rescheduledCloseDate: {0}, Result.IntervalsNum: {1}, Result.LoanCloseDate: {2}, closeDateWithGrace: {3}, dDays: {4}", rescheduledCloseDate, this.Result.IntervalsNum, this.Result.LoanCloseDate.Date, closeDateWithGrace, ts.Days); } // OUT - real intervals (k) calculation if (this.ReschedulingArguments.RescheduleIn == false) { // too much payment per interval if (this.ReschedulingArguments.PaymentPerInterval > this.Result.ReschedulingBalance) { // ReSharper disable once PossibleInvalidOperationException this.message = string.Format("The entered amount accedes the outstanding balance of {0} for payment of {1}", this.Result.ReschedulingBalance.ToString("C2", this.cultureInfo), this.ReschedulingArguments.PaymentPerInterval.Value.ToString("C2", this.cultureInfo)); this.Result.Error = this.message; ExitStrategy("Exit_6", this.sendDebugMail); return; } // ReSharper disable once PossibleInvalidOperationException decimal m = (decimal)this.ReschedulingArguments.PaymentPerInterval; // System.DivideByZeroException: Attempted to divide by zero prevent decimal kDiv = (m - this.Result.ReschedulingBalance * r); if (kDiv == 0) { kDiv = 1; } var k = (int)Math.Ceiling(this.Result.ReschedulingBalance / kDiv); this.Result.IntervalsNum = k; Log.Debug("k: {0}, P: {1}, I: {2}, F: {3}, r: {4}, oustandingBalance: {5}, m: {6}, StopFutureInterest: {7}", k, P, I, F, r, this.Result.ReschedulingBalance, m, this.ReschedulingArguments.StopFutureInterest); // uncovered loan - too small payment per interval if (k < 0) { this.Result.Error = "Chosen amount is not sufficient for covering the loan overtime, i.e. accrued interest will be always greater than the repaid amount per payment"; ExitStrategy("Exit_7", this.sendDebugMail); return; } this.Result.LoanCloseDate = this.ReschedulingArguments.ReschedulingRepaymentIntervalType == RepaymentIntervalTypes.Month ? this.Result.FirstItemDate.AddMonths(k) : this.Result.FirstItemDate.AddDays(k * 7); Log.Debug("new close date: {0}", this.Result.LoanCloseDate); // DON'T DELETE - can be used in new calculator //int n = (int)Math.Ceiling(P / (m - P * r)); //decimal x = 0m; = this.Result.ReschedulingBalance * r * (int)((k + 1) / 2) - P * r * (int)((n + 1) / 2); //Log.Debug("n: {0}, k: {1}, P: {2}, I: {3}, F: {4}, r: {5}, oustandingBalance: {6}, m: {7}, X: {8}, closeDate: {9}, Result.IntervalsNum: {10}", n, k, P, I, F, r, this.Result.ReschedulingBalance, m, x, this.Result.LoanCloseDate, this.Result.IntervalsNum); } Log.Debug("close date: {0}, intervals: {1}", this.Result.LoanCloseDate, this.Result.IntervalsNum); if (this.Result.IntervalsNum == 0) { this.Result.Error = "Rescheduling impossible (calculated payments number 0)"; this.Result.BlockAction = true; ExitStrategy("Exit_8", this.sendDebugMail); return; } // remove unpaid (lates, stilltopays passed) and future unpaid schedule items foreach (var rmv in this.tLoan.Schedule.ToList <LoanScheduleItem>()) { // if loan has future items that already paid ("paid early"), re-scheduling not allowed if ((rmv.Status == LoanScheduleStatus.Paid || rmv.Status == LoanScheduleStatus.PaidOnTime || rmv.Status == LoanScheduleStatus.PaidEarly) && rmv.Date > this.Result.FirstItemDate) { this.Result.Error = string.Format("Currently it is not possible to apply rescheduling future if payment/s relaying in the future have been already covered with early made payment, partially or entirely. " + "You can apply rescheduling option after [last covered payment day]."); this.Result.BlockAction = true; ExitStrategy("Exit_5", this.sendDebugMail); return; } if (rmv.Date >= this.Result.FirstItemDate) { this.tLoan.Schedule.Remove(rmv); } if (rmv.Date <= this.Result.FirstItemDate && rmv.Status == LoanScheduleStatus.Late) { this.tLoan.Schedule.Remove(rmv); this.tLoan.TryAddRemovedOnReschedule(new LoanScheduleDeleted().CloneScheduleItem(rmv)); } if (rmv.Date <= this.Result.FirstItemDate && rmv.Status == LoanScheduleStatus.StillToPay) { this.tLoan.Schedule.Remove(rmv); this.tLoan.TryAddRemovedOnReschedule(new LoanScheduleDeleted().CloneScheduleItem(rmv)); } } decimal balance = P; decimal iPrincipal = Decimal.Round(P / this.Result.IntervalsNum); decimal firstPrincipal = (P - iPrincipal * (this.Result.IntervalsNum - 1)); //check "first iPrincipal negative" case: if first iPrincipal <= 0, remove this and reduce this.Result.IntervalsNum if (firstPrincipal < 0) { Log.Debug("AAA Periods: {0}, newInstalment: {1}, close date: {2}, balance: {3}, firstItemDate: {4}, firstPrincipal: {5}, " + "P: {6}, I: {7}, F: {8}, r: {9}", this.Result.IntervalsNum, iPrincipal, this.Result.LoanCloseDate, this.Result.ReschedulingBalance, this.Result.FirstItemDate, firstPrincipal, P, I, F, r); this.Result.IntervalsNum -= 1; firstPrincipal = iPrincipal; if ((iPrincipal * (this.Result.IntervalsNum - 1) + firstPrincipal) != balance) { this.Result.Error = "Failed to create new schedule."; ExitStrategy("Exit_9", this.sendDebugMail); } } Log.Debug("Periods: {0}, newInstalment: {1}, close date: {2}, balance: {3}, firstItemDate: {4}, firstPrincipal: {5}, " + "P: {6}, I: {7}, F: {8}, r: {9}", this.Result.IntervalsNum, iPrincipal, this.Result.LoanCloseDate, this.Result.ReschedulingBalance, this.Result.FirstItemDate, firstPrincipal, P, I, F, r); // add new re-scheduled items, both for IN/OUT int position = this.tLoan.Schedule.Count; for (int j = 0; j < this.Result.IntervalsNum; j++) { DateTime iStartDate = this.ReschedulingArguments.ReschedulingRepaymentIntervalType == RepaymentIntervalTypes.Month ? this.Result.FirstItemDate.AddMonths(j) : this.Result.FirstItemDate.AddDays(7 * j); decimal iLoanRepayment = (j == 0) ? firstPrincipal : iPrincipal; balance -= iLoanRepayment; LoanScheduleItem item = new LoanScheduleItem() { Date = iStartDate.Date, InterestRate = r, Status = LoanScheduleStatus.StillToPay, Loan = this.tLoan, LoanRepayment = iLoanRepayment, Balance = balance, Position = ++position }; this.tLoan.Schedule.Add(item); } //Log.Debug("--------------Loan modified: \n {0}", this.tLoan); // after modification if (CheckValidateLoanState(calc) == false) { ExitStrategy("Exit_10", this.sendDebugMail); return; } Log.Debug("--------------Loan recalculated: \n {0}", this.tLoan); // prevent schedules with negative iPrincipal (i.e. LoanRepayment:-4.00) var negativeIPrincipal = this.tLoan.Schedule.FirstOrDefault(s => s.LoanRepayment < 0); if (negativeIPrincipal != null) { this.Result.Error = "Negative principal in loan schedule"; ExitStrategy("Exit_11", this.sendDebugMail); return; } // prevent "paidEarly" for newly created schedule items var newPaidEarly = this.tLoan.Schedule.FirstOrDefault(s => s.Date > this.ReschedulingArguments.ReschedulingDate && s.Status == LoanScheduleStatus.PaidEarly); if (newPaidEarly != null) { this.Result.Error = "Wrong balance for re-scheduling calculated. Please, contact support."; ExitStrategy("Exit_12", this.sendDebugMail); return; } var firstRescheduledItem = this.tLoan.Schedule.FirstOrDefault(s => s.Date.Date == this.Result.FirstItemDate); if (firstRescheduledItem != null) { this.Result.FirstPaymentInterest = firstRescheduledItem.Interest; } if (this.ReschedulingArguments.RescheduleIn == false) // OUT // NOT POSSIBLE WITH CURRENT CALCULATOR < DON'T DELETE //OffsetX(x); //if (CheckValidateLoanState(calc) == false) // return; //Log.Debug("-------Loan recalculated+adjusted to X \n {0}", this.tLoan); // unsufficient payment per period { LoanScheduleItem overInstalment = this.tLoan.Schedule.FirstOrDefault(s => s.AmountDue > this.ReschedulingArguments.PaymentPerInterval); if (overInstalment != null) { // ReSharper disable once PossibleInvalidOperationException this.message = string.Format("{0}ly payment of {1} not sufficient to pay the loan outstanding balance. Accrued interest: {2}, accumulated fees: {3}, first new instalment: {4}. " + "You can choose to reduce the accumulated fees & interest by clearing them via manual payment, before setting the new payment schedule.", this.ReschedulingArguments.ReschedulingRepaymentIntervalType, this.ReschedulingArguments.PaymentPerInterval.Value.ToString("C2", this.cultureInfo), overInstalment.Interest.ToString("C2", this.cultureInfo), //I.ToString("C2", this.cultureInfo), overInstalment.Fees.ToString("C2", this.cultureInfo), overInstalment.AmountDue.ToString("C2", this.cultureInfo) ); this.Result.Error = this.message; ExitStrategy("Exit_13", this.sendDebugMail); return; } } if (!this.ReschedulingArguments.SaveToDB) { ExitStrategy("Exit_14", this.sendDebugMail); return; } LoanRescheduleSave(); NL_AddLog(LogType.Info, "Strategy End", this.ReschedulingArguments, this.Result, null, null); // ReSharper disable once CatchAllClause } catch (Exception ex) { Log.Alert(ex, "Failed to get rescheduling data for loan {0}", this.ReschedulingArguments.LoanID); NL_AddLog(LogType.Error, "Strategy Faild", this.ReschedulingArguments, null, ex.ToString(), ex.StackTrace); } }