public static CashRequestModel Create(DecisionHistoryDBModel item) { var setupFeeCalculator = new SetupFeeCalculator(item.ManualSetupFeePercent, item.BrokerSetupFeePercent); CashRequestOriginator originator; string originatorStr = item.Originator; if (Enum.TryParse(item.Originator, out originator)) { originatorStr = originator.DescriptionAttr(); } return(new CashRequestModel { Id = item.CashRequestID, Action = item.Action, Amount = item.ApprovedSum, StartDate = item.OfferStart, EndDate = item.OfferValidUntil, Comments = item.Comment, InterestRate = item.InterestRate, SetupFee = setupFeeCalculator.Calculate(item.ApprovedSum).Total, RepaymentPeriod = item.ApprovedRepaymentPeriod, UnderwriterDecision = item.UnderwriterDecision, LoanType = item.LoanType, DiscountPlan = item.DiscountPlan, LoanSourceName = item.LoanSourceName, Originator = originatorStr, IsOpenPlatform = string.IsNullOrEmpty(item.FundingType) ? "No" : "Yes" }); }
} // class CashRequestRelevantData protected override void SetTemplateAndVariables() { var cashRequestRelevantData = DB.FillFirst <CashRequestRelevantData>( "GetCashRequestData", new QueryParameter("@CustomerId", CustomerData.Id) ); SetupFeeCalculator sfCalculator = new SetupFeeCalculator(cashRequestRelevantData.ManualSetupFeePercent, cashRequestRelevantData.BrokerSetupFeePercent); var setupFeeAmount = sfCalculator.Calculate(cashRequestRelevantData.ManagerApprovedSum).Total; this.setupFeePercents = (cashRequestRelevantData.ManualSetupFeePercent + cashRequestRelevantData.BrokerSetupFeePercent) * 100; this.interestRatePercents = cashRequestRelevantData.InterestRate * 100; this.setupFeePercents = MathUtils.Round2DecimalDown(this.setupFeePercents); decimal remainingPercentsAfterSetupFee = 100 - this.setupFeePercents; Variables = new Dictionary <string, string> { { "FirstName", CustomerData.FirstName }, { "LoanAmount", this.loanAmount.ToString("#,#") }, { "ValidFor", this.validHours.ToString(CultureInfo.InvariantCulture) }, { "AmountInUsd", MathUtils.Round2DecimalDown(this.amountInUsd).ToString("#,#.00") }, { "AlibabaId", CustomerData.AlibabaId.ToString(CultureInfo.InvariantCulture) }, { "InterestRate", MathUtils.Round2DecimalDown(this.interestRatePercents).ToString("#,#.00") }, { "SetupFee", this.setupFeePercents.ToString("#,#.00") }, { "RemainingPercentsAfterSetupFee", remainingPercentsAfterSetupFee.ToString(CultureInfo.InvariantCulture) }, { "RefNum", CustomerData.RefNum.ToString(CultureInfo.InvariantCulture) }, { "Surname", CustomerData.Surname.ToString(CultureInfo.InvariantCulture) }, { "RequestedLoanAmount", CustomerData.RequestedLoanAmount.ToString("#,#") }, { "ReportedAnnualTurnover", CustomerData.ReportedAnnualTurnover.ToString("#,#") } }; if (CustomerData.IsAlibaba) { TemplateName = "Mandrill - Alibaba - Approval"; } else if (CustomerData.IsCampaign) { TemplateName = "Mandrill - Approval Campaign (1st time)"; } else if (this.isFirst) { TemplateName = "Mandrill - Approval (1st time)"; } else { TemplateName = "Mandrill - Approval (not 1st time)"; } } // SetTemplateAndVariables
/** - loan - fees - history - schedules - broker comissions ? - update NLLoanID - fund transfer - pacnet transaction - agreements - loan options row */ public override void Execute() { if (!CurrentValues.Instance.NewLoanRun) { NL_AddLog(LogType.Info, "NL disabled by configuration", null, null, null, null); return; } NL_AddLog(LogType.Info, "Strategy Start", this.strategyArgs, Error, null, null); try { if (model.CustomerID == 0) { Error = NL_ExceptionCustomerNotFound.DefaultMessage; NL_AddLog(LogType.Error, "Strategy failed", this.strategyArgs, null, Error, null); return; } if (model.Loan == null) { Error = NL_ExceptionRequiredDataNotFound.Loan; NL_AddLog(LogType.Error, "Strategy failed", this.strategyArgs, null, Error, null); return; } if (model.Loan.OldLoanID == null) { Error = NL_ExceptionRequiredDataNotFound.OldLoan; NL_AddLog(LogType.Error, "Strategy failed", this.strategyArgs, null, Error, null); return; } var history = model.Loan.LastHistory(); if (history == null) { Error = NL_ExceptionRequiredDataNotFound.LastHistory; NL_AddLog(LogType.Error, "Strategy failed", this.strategyArgs, null, Error, null); return; } if (history.Agreements == null || history.Agreements.Count == 0) { Error = string.Format("Expected input data not found (NL_Model initialized by: NLAgreementItem list). Customer {0}", model.CustomerID); NL_AddLog(LogType.Error, "Strategy failed", this.strategyArgs, null, Error, null); return; } if (string.IsNullOrEmpty(history.AgreementModel)) { Error = string.Format("Expected input data not found (NL_Model initialized by: AgreementModel in JSON). Customer {0}", model.CustomerID); NL_AddLog(LogType.Error, "Strategy failed", this.strategyArgs, null, Error, null); return; } BuildLoanFromOffer dataForLoan = new BuildLoanFromOffer(model); try { dataForLoan.Execute(); // ReSharper disable once CatchAllClause } catch (Exception ex) { Log.Alert(ex.Message); } if (!string.IsNullOrEmpty(dataForLoan.Error)) { Error = dataForLoan.Error; NL_AddLog(LogType.DataExsistense, "Strategy failed - Failed to generate Schedule/fees", this.strategyArgs, null, Error, null); return; } model = dataForLoan.Result; // prevent to create the same loan (by refnum) if (!string.IsNullOrEmpty(model.Loan.Refnum) && !string.IsNullOrEmpty(dataForLoan.DataForLoan.ExistsRefnums) && dataForLoan.DataForLoan.ExistsRefnums.Contains(model.Loan.Refnum)) { Error = NL_ExceptionLoanExists.DefaultMessage; NL_AddLog(LogType.Info, "Strategy End", this.strategyArgs, null, Error, null); return; } // setup/distributed fees // for now: only one-time or "spreaded" setup fees supported // add full fees 2.0 support later var offerFees = model.Offer.OfferFees; // don't create LoanFees if OfferFees Percent == 0 or AbsoluteAmount == 0 var setupFee = offerFees.FirstOrDefault(f => f.LoanFeeTypeID == (int)NLFeeTypes.SetupFee && (f.Percent > 0 || f.AbsoluteAmount > 0)); var servicingFee = offerFees.FirstOrDefault(f => f.LoanFeeTypeID == (int)NLFeeTypes.ServicingFee && (f.Percent > 0 || f.AbsoluteAmount > 0)); // equal to "setup spreaded" decimal? brokerFeePercent = model.Offer.BrokerSetupFeePercent; var feeCalculator = new SetupFeeCalculator(setupFee!=null ?setupFee.Percent: servicingFee.Percent, brokerFeePercent); SetupFeeCalculator.AbsoluteFeeAmount ff = feeCalculator.Calculate(history.Amount); decimal setupFeeAmount = ff.Total; model.BrokerComissions = ff.Broker; // send ot Calculator to distribute and attach to schedule planned dates history.DistributedFees = servicingFee == null ? 0: setupFeeAmount; ALoanCalculator nlCalculator = new LegacyLoanCalculator(model); // 2. Init Schedule and Fees try { // model should contain Schedule and Fees after this invocation nlCalculator.CreateSchedule(); // create primary dates/p/r/f distribution of schedules (P/n) and setup/servicing fees. 7 September - fully completed schedule + fee + amounts due, without payments. } catch (NoInitialDataException noDataException) { Error = noDataException.Message; } catch (InvalidInitialAmountException amountException) { Error = amountException.Message; } catch (InvalidInitialInterestRateException interestRateException) { Error = interestRateException.Message; } catch (InvalidInitialRepaymentCountException paymentsException) { Error = paymentsException.Message; // ReSharper disable once CatchAllClause } catch (Exception ex) { Error = string.Format("Failed to get calculator instance/Schedule. customer {0}, err: {1}", model.CustomerID, ex.Message); NL_AddLog(LogType.Error, "Strategy failed", this.strategyArgs, null, Error, ex.StackTrace); return; } //// prevent creation of same loan //if (!string.IsNullOrEmpty(Error)) { // Log.Info("Failed to calculate Schedule. customer {0}, err: {1}", model.CustomerID, Error); // NL_AddLog(LogType.Error, "Strategy " + string.Format("Failed to calculate Schedule. customer {0}, err: {1}", model.CustomerID, Error), this.strategyArgs, null, Error, null); // return; //} history.OutstandingInterest = nlCalculator.Interest; List<NL_LoanSchedules> nlSchedule = new List<NL_LoanSchedules>(); List<NL_LoanFees> nlFees = new List<NL_LoanFees>(); List<NL_LoanAgreements> nlAgreements = new List<NL_LoanAgreements>(); // get updated history filled with Schedule history = model.Loan.LastHistory(); // copy to local schedules list history.Schedule.ForEach(s => nlSchedule.Add(s)); if (nlSchedule.Count == 0) { Error += "Failed to generate Schedule/fees"; NL_AddLog(LogType.Info, "Strategy failed", this.strategyArgs, null, Error, null); return; } // 3. complete NL_Loans object data model.Loan = dataForLoan.Result.Loan; model.Loan.CreationTime = nowTime; model.Loan.LoanStatusID = (int)NLLoanStatuses.Live; model.Loan.Position += 1; ConnectionWrapper pconn = DB.GetPersistent(); try { pconn.BeginTransaction(); // 4. save loan LoanID = DB.ExecuteScalar<long>(pconn, "NL_LoansSave", CommandSpecies.StoredProcedure, DB.CreateTableParameter("Tbl", model.Loan)); model.Loan.LoanID = LoanID; //Log.Debug("NL_LoansSave: LoanID: {0}", this.LoanID); // 5. fees // copy to local fees list model.Loan.Fees.ForEach(f => nlFees.Add(f)); foreach (NL_LoanFees f in nlFees) { f.CreatedTime = nowTime; // from calc-r f.AssignedByUserID = 1; // from calc-r f.LoanID = LoanID; } // setup as fee if (setupFee != null) { Log.Debug("setupFeeAmount: {0}", setupFeeAmount); nlFees.Add( new NL_LoanFees() { LoanID = LoanID, Amount = setupFeeAmount, AssignTime = history.EventTime, Notes = "setup fee one-part", LoanFeeTypeID = (int)NLFeeTypes.SetupFee, CreatedTime = nowTime, AssignedByUserID = 1 }); } nlFees.ForEach(f => Log.Debug("Adding fees: {0}", f)); // insert fees DB.ExecuteNonQuery(pconn, "NL_LoanFeesSave", CommandSpecies.StoredProcedure, DB.CreateTableParameter<NL_LoanFees>("Tbl", nlFees)); model.Loan.Fees.Clear(); model.Loan.Fees.AddRange(nlFees); // 7. history history.LoanID = LoanID; history.Description = "adding loan. oldID: " + model.Loan.OldLoanID; //Log.Debug("Adding history: {0}", history); history.LoanHistoryID = DB.ExecuteScalar<long>(pconn, "NL_LoanHistorySave", CommandSpecies.StoredProcedure, DB.CreateTableParameter("Tbl", history)); //Log.Debug("NL_LoanHistorySave: LoanID: {0}, LoanHistoryID: {1}", model.Loan.LoanID, history.LoanHistoryID); // 8. loan agreements history.Agreements.ForEach(a => nlAgreements.Add(a)); nlAgreements.ForEach(a => a.LoanHistoryID = history.LoanHistoryID); //nlAgreements.ForEach(a => Log.Debug("Adding agreement: {0}", a)); DB.ExecuteNonQuery(pconn, "NL_LoanAgreementsSave", CommandSpecies.StoredProcedure, DB.CreateTableParameter<NL_LoanAgreements>("Tbl", nlAgreements)); // 9. schedules nlSchedule.ForEach(s => s.LoanHistoryID = history.LoanHistoryID); //nlSchedule.ForEach(s => Log.Debug("Adding schedule: {0}", s)); DB.ExecuteNonQuery(pconn, "NL_LoanSchedulesSave", CommandSpecies.StoredProcedure, DB.CreateTableParameter<NL_LoanSchedules>("Tbl", nlSchedule)); // 10. Fund Transfer if (model.FundTransfer != null) { model.FundTransfer.LoanID = LoanID; model.FundTransfer.FundTransferID = DB.ExecuteScalar<long>(pconn, "NL_FundTransfersSave", CommandSpecies.StoredProcedure, DB.CreateTableParameter("Tbl", model.FundTransfer)); //Log.Debug("NL_FundTransfersSave: LoanID: {0}, fundTransferID: {1}", this.LoanID, model.FundTransfer.FundTransferID); } // 11. save default loan options record model.Loan.LoanOptions.LoanOptionsID = DB.ExecuteScalar<long>(pconn, "NL_LoanOptionsSave", CommandSpecies.StoredProcedure, DB.CreateTableParameter("Tbl", new NL_LoanOptions { LoanID = LoanID, UserID = 1, // default system user? InsertDate = nowTime, IsActive = true, Notes = "default options" }), new QueryParameter("@LoanID", LoanID) ); pconn.Commit(); // ReSharper disable once CatchAllClause } catch (Exception ex) { pconn.Rollback(); LoanID = 0; Error = ex.Message; Log.Error("Failed to add new loan: {0}", Error); SendMail("NL: loan rolled back", history, nlFees, nlSchedule, nlAgreements); NL_AddLog(LogType.Error, "Strategy failed - Failed to add new loan", this.strategyArgs, Error, ex.ToString(), ex.StackTrace); return; } // 7. Pacnet transaction try { if (model.FundTransfer != null && (model.FundTransfer.PacnetTransactions.Count > 0 && model.FundTransfer.FundTransferID > 0)) { var pacnetTransaction = model.FundTransfer.LastPacnetTransactions(); pacnetTransaction.FundTransferID = model.FundTransfer.FundTransferID; pacnetTransaction.PacnetTransactionID = DB.ExecuteScalar<long>("NL_PacnetTransactionsSave", CommandSpecies.StoredProcedure, DB.CreateTableParameter("Tbl", pacnetTransaction)); //Log.Debug("NL_PacnetTransactionsSave: LoanID: {0}, pacnetTransactionID: {1}", this.LoanID, pacnetTransaction.PacnetTransactionID); } // ReSharper disable once CatchAllClause } catch (Exception e1) { Error = e1.Message; Log.Error("Failed to save PacnetTransaction: {0}", Error); // PacnetTransaction error SendMail("NL: Failed to save PacnetTransaction", history, nlFees, nlSchedule, nlAgreements); } // 11. if setup fee - add payment to offset it SetupOffsetPayment(); // 6. broker commissions // done in controller. When old loan removed: check if this is the broker's customer, calc broker fees, insert into LoanBrokerCommission if (model.Offer.BrokerSetupFeePercent > 0) { DB.ExecuteNonQuery(string.Format("UPDATE dbo.LoanBrokerCommission SET NLLoanID = {0} WHERE LoanID = {1}", LoanID, model.Loan.OldLoanID)); } // OK SendMail("NL: Saved successfully", history, nlFees, nlSchedule, nlAgreements); // copy LoanCharges Ids into OldFeeID, NL_LoanFees DB.ExecuteNonQuery("NL_LoanFeesOldIDUpdate", CommandSpecies.StoredProcedure); // temporary - should be removed/modified after "old" loan remove CopyRebateTransaction(); //MigrateLoanTransaction sMigrateLoan = new MigrateLoanTransaction(); //try { // sMigrateLoan.Execute(); // // ReSharper disable once CatchAllClause //} catch (Exception mex) { // Error = mex.Message; // Log.Error("Failed sync migration: {0}", Error); // NL_AddLog(LogType.Error, "Failed sync migration", this.strategyArgs, Error, mex.ToString(), mex.StackTrace); //} NL_AddLog(LogType.Info, "Strategy End", this.strategyArgs, LoanID, Error, null); // ReSharper disable once CatchAllClause } catch (Exception ex) { NL_AddLog(LogType.Error, "Strategy failed", this.strategyArgs, Error, ex.ToString(), ex.StackTrace); } }//Execute
} // constructor /// <exception cref="OverflowException">Condition. </exception> public virtual void Execute() { // not accepted rollover if (Calculator.acceptedRollover.Rollover == null) { Log.Alert("RolloverRescheduling: no accepted rollover"); return; } // not accepted rollover if (!Calculator.acceptedRollover.Rollover.IsAccepted || !Calculator.acceptedRollover.Rollover.CustomerActionTime.HasValue) { Log.Alert("RolloverRescheduling: rollover not accepted (paid). {0}", Calculator.acceptedRollover.Rollover); return; } // rollover proceeseed if (Calculator.acceptedRollover.Rollover.CustomerActionTime.Value.Date == Calculator.currentHistory.EventTime.Date || Calculator.acceptedRolloverProcessed) { Log.Debug("RolloverRescheduling: History ({0}) for rollover {1:d}, rolloverID {2} already exists", Calculator.currentHistory.LoanHistoryID, Calculator.acceptedRollover.Rollover.CustomerActionTime.Value, Calculator.acceptedRollover.Rollover.LoanRolloverID); return; } var rolloverPayment = Calculator.events.FirstOrDefault(p => p.Payment != null && p.Payment.PaymentTime.Date.Equals(Calculator.acceptedRollover.Rollover.CustomerActionTime.Value.Date) && p.Payment.PaymentDestination.Equals(NLPaymentDestinations.Rollover.ToString())); // rollover not paid if (rolloverPayment == null) { Log.Alert("RolloverRescheduling: rollover payment not found. {0}", Calculator.acceptedRollover.Rollover); return; } DateTime acceptionTime = Calculator.acceptedRollover.Rollover.CustomerActionTime.Value.Date; NL_LoanHistory lastHistory = WorkingModel.Loan.LastHistory(); // 1. create new history NL_LoanHistory newHistory = new NL_LoanHistory { LoanID = lastHistory.LoanID, LoanLegalID = lastHistory.LoanLegalID, AgreementModel = lastHistory.AgreementModel, Agreements = lastHistory.Agreements, InterestRate = lastHistory.InterestRate, RepaymentIntervalTypeID = lastHistory.RepaymentIntervalTypeID, UserID = WorkingModel.CustomerID, Description = "accept rollover", Amount = Calculator.currentOpenPrincipal, EventTime = acceptionTime }; RepaymentIntervalTypes intervalType = (RepaymentIntervalTypes)lastHistory.RepaymentIntervalTypeID; int removedItems = 0; newHistory.RepaymentDate = DateTime.MinValue; // 2. mark removed schedules + add new schedules foreach (NL_LoanSchedules s in Calculator.schedule.Where(s => s.PlannedDate >= acceptionTime)) { s.SetStatusOnRescheduling(); s.ClosedTime = acceptionTime; removedItems++; DateTime plannedDate = Calculator.AddRepaymentIntervals(1, s.PlannedDate, intervalType); if (newHistory.RepaymentDate.Equals(DateTime.MinValue)) { newHistory.RepaymentDate = plannedDate; } // add new schedule instead of removed NL_LoanSchedules newSchedule = new NL_LoanSchedules() { LoanScheduleID = 0, LoanScheduleStatusID = (int)NLScheduleStatuses.StillToPay, Position = s.Position, PlannedDate = plannedDate, Principal = s.Principal, // (s.Principal - s.PrincipalPaid), InterestRate = s.InterestRate, TwoDaysDueMailSent = false, //s.TwoDaysDueMailSent, FiveDaysDueMailSent = false, //s.FiveDaysDueMailSent }; Log.Debug("schedule {0} replaced by {1}", s, newSchedule); newHistory.Schedule.Add(newSchedule); } newHistory.RepaymentCount = removedItems; WorkingModel.Loan.Histories.Add(newHistory); //List<NL_LoanFees> replacedDistributedFees = new List<NL_LoanFees>(); bool distributedFees = false; // 3. mark removed distributed fees + add new distributed fees foreach (NL_LoanFees fee in Calculator.distributedFeesList.Where(f => f.AssignTime.Date >= acceptionTime)) { fee.DisabledTime = acceptionTime; fee.Notes = "disabled on rollover"; fee.DeletedByUserID = WorkingModel.UserID ?? 1; distributedFees = true; //NL_LoanFees newFee = new NL_LoanFees() { // LoanFeeID = 0, // Amount = fee.Amount, // AssignTime = Calculator.AddRepaymentIntervals(1, fee.AssignTime, intervalType), // LoanID = WorkingModel.Loan.LoanID, // LoanFeeTypeID = fee.LoanFeeTypeID, // AssignedByUserID = fee.AssignedByUserID, // CreatedTime = acceptionTime, // Notes = fee.Notes //}; //Log.Debug("fee {0} replaced by {1}", fee, newFee); //replacedDistributedFees.Add(newFee); } //Calculator.distributedFeesList.AddRange(replacedDistributedFees); //WorkingModel.Loan.Fees.AddRange(replacedDistributedFees); if (distributedFees) { // offer-fees NL_OfferFees offerFees = WorkingModel.Offer.OfferFees.FirstOrDefault(); if (offerFees != null && offerFees.DistributedPartPercent != null && (decimal)offerFees.DistributedPartPercent == 1) { var feeCalculator = new SetupFeeCalculator(offerFees.Percent, null); decimal servicingFeeAmount = feeCalculator.Calculate(newHistory.Amount).Total; decimal servicingFeePaidAmount = WorkingModel.Loan.Fees.Where(f => f.LoanFeeTypeID == (int)NLFeeTypes.ServicingFee).Sum(f => f.PaidAmount); Log.Debug("servicingFeeAmount: {0}, servicingFeePaidAmount: {1}", servicingFeeAmount, servicingFeePaidAmount); // new "spreaded" amount Calculator.AttachDistributedFeesToLoanBySchedule(WorkingModel, (servicingFeeAmount - servicingFeePaidAmount), acceptionTime); } } // TODO could be reseted at all?????????????? // reset paid amount for deleted/closed schedules and disabled distributed fees foreach (NL_Payments p in WorkingModel.Loan.Payments) { foreach (NL_LoanSchedulePayments sp in p.SchedulePayments) { foreach (NL_LoanSchedules s in Calculator.schedule.Where(s => s.IsDeleted())) { if (s.LoanScheduleID == sp.LoanScheduleID) { sp.ResetInterestPaid = sp.PrincipalPaid; sp.ResetPrincipalPaid = sp.PrincipalPaid; sp.PrincipalPaid = 0; sp.InterestPaid = 0; } } } foreach (NL_LoanFeePayments fp in p.FeePayments) { foreach (NL_LoanFees f in Calculator.distributedFeesList.Where(f => f.DisabledTime.Equals(acceptionTime))) { if (f.LoanFeeID == fp.LoanFeeID) { fp.ResetAmount = fp.Amount; fp.Amount = 0; f.PaidAmount -= (decimal)fp.ResetAmount; } } } } Calculator.acceptedRolloverProcessed = true; }